:root {
  color-scheme: dark;
  --bg: #0b0b0d;
  --panel: #17171c;
  --panel2: #111115;
  --line: #2c2c33;
  --text: #f6f6f8;
  --muted: #92929e;
  --soft: #c8c8d0;

  /* Type scale. The stylesheet had grown twenty distinct sizes — every integer
     from 8 to 22, plus a handful above — with 10, 11, 12 and 13px each used
     twenty-odd times. Four sizes doing one job means a size stops signalling
     anything, so hierarchy reads as noise. These seven steps replace them.

     11px is the floor. Nothing that carries meaning is set smaller: the genre
     and language chips on a song card were 10px, which is the smallest text in
     the app doing the most work on it.

     The ratio widens as it climbs — the small steps sit close together because
     they separate a label from its value, while the display sizes need real
     distance to read as headings. */
  --fs-2xs: 11px;
  --fs-xs: 12px;
  --fs-sm: 13px;
  --fs-base: 15px;
  --fs-lg: 18px;
  --fs-xl: 22px;
  --fs-display: 32px;

  /* Measure: the comfortable line length for running text. Chat bubbles were
     620px at 16px, about 95 characters — well past the 45–75 the eye tracks
     without losing its place on the return sweep. */
  --measure: 68ch;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  overflow: hidden;
  background: radial-gradient(circle at 50% -20%,#252532,#0b0b0d 42%);
  color: var(--text);
  font-family: Inter,system-ui,sans-serif;
}

.shell {
  height: 100dvh;
  max-width: 1000px;
  margin: auto;
  overflow: hidden;
  display: grid;
  grid-template-rows: 60px auto minmax(0,1fr) auto;
  background: #0b0b0dcc;
}

.app-header,
.shell>main,
.shell>.composer-wrap {
  min-height: 0;
}

header {
  position: relative;
  z-index: 100;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 16px;
  border-bottom: 1px solid var(--line);
  background: #0b0b0df2;
  backdrop-filter: blur(18px);
}

.logo {
  width: 34px;
  height: 34px;
  border-radius: 11px;
  background: #fff;
  color: #111;
  display: grid;
  place-items: center;
  font-weight: 800;
}

.grow {
  flex: 1;
}

.muted {
  color: var(--muted);
  font-size: var(--fs-xs);
}

.nav {
  display: flex;
  gap: 6px;
}

.nav button {
  display: flex;
  align-items: center;
  gap: 6px;
}

.nav button,
/* The primary action button. Extracted from .sidebar-new, whose rules live inside
   @media (min-width:1001px) because the sidebar is desktop-only — reusing that class
   anywhere else would leave the button unstyled on a phone. */

.button-primary {
  min-width: 116px;
  padding: 14px 16px;
  border: 0;
  border-radius: 14px;
  background: linear-gradient(145deg,#b39bf0,#9b7fe8);
  color: #1a1225;
  font: 700 var(--fs-base)/1 Inter,system-ui,sans-serif;
  text-align: center;
  cursor: pointer;
  transition: filter .18s,transform .18s;
}

/* A secondary action alongside a primary one: the same size and shape, so a row of
   buttons reads as one set, distinguished by weight rather than by dimensions. The
   minimum width is New Playlist's natural width, which is the widest of the short
   labels — so "Play" no longer sits at 61px beside a 116px sibling. */

.button-secondary {
  min-width: 116px;
  /* 13px + the 1px border == the primary's 14px, so the two are the same height.
     box-sizing is border-box globally, but these buttons size from their content. */
  padding: 13px 15px;
  border: 1px solid #4a4458;
  border-radius: 14px;
  background: #1f1d27;
  color: #ded9e8;
  font: 700 var(--fs-base)/1 Inter,system-ui,sans-serif;
  text-align: center;
  cursor: pointer;
  transition: filter .18s,transform .18s,background .18s;
}

.button-secondary:hover:not(:disabled) {
  background: #2a2635;
  transform: translateY(-1px);
}

.button-primary:disabled,
.button-secondary:disabled {
  opacity: .45;
  cursor: default;
  transform: none;
}

.button-primary:hover {
  filter: brightness(1.08);
  transform: translateY(-1px);
}

/* No padding override in a page header: the button keeps .button-primary's own 14px,
   so Add Playlist stands exactly as tall as New session. It sits beside a heading
   rather than filling a sidebar column, so only its width differs. */

.ghost {
  border: 1px solid var(--line);
  background: #17171c;
  color: #cfcfd5;
  border-radius: 999px;
  padding: 7px 11px;
  font-size: var(--fs-xs);
  cursor: pointer;
}

.nav button.active {
  background: #f3f3f5;
  color: #111;
  border-color: #f3f3f5;
}

.chat-songs-nav svg {
  width: 15px;
  height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

main {
  overflow: auto;
  /* 12px, like .composer-wrap: the chat column and the composer card share edges. */
  padding: 20px 12px;
}

.view {
  display: none;
}

.view.active {
  display: block;
}

.feed {
  max-width: 720px;
  margin: auto;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.feed.songs-only>:not(.track):not(.playback-scroll-space):not(.songs-empty) {
  display: none;
}

.songs-empty {
  min-height: 42vh;
  display: grid;
  place-content: center;
  gap: 7px;
  text-align: center;
  color: #d9d3df;
}

.songs-empty span {
  color: var(--muted);
  font-size: var(--fs-xs);
}

.empty {
  text-align: center;
  padding-top: 15vh;
}

.empty h1 {
  font-size: clamp(28px,6vw,48px);
  margin: 0 0 10px;
}

.msg {
  max-width: 84%;
  line-height: 1.45;
  white-space: pre-wrap;
}

.user {
  align-self: flex-end;
  background: #eeeef2;
  color: #111;
  padding: 10px 14px;
  border-radius: 18px 18px 5px 18px;
}

.ai {
  align-self: flex-start;
  color: #ddd;
}

.track {
  border: 1px solid var(--line);
  background: linear-gradient(#18181d,#111115);
  padding: 14px;
  border-radius: 18px;
}

.track-top {
  display: flex;
  gap: 12px;
  align-items: center;
}

.track-open {
  cursor: pointer;
  min-width: 0;
  flex: 1;
}

/* Two lines before the ellipsis, not one. A single nowrap line cut
   "سرّ الحب (Το Μυστικό της Αγάπης)" at its first word: this library is
   multilingual and its titles carry a native-script name and a translation
   together, so the half that got dropped was usually the half a reader could
   read. Two lines fit the common bilingual title whole and still bound the
   card's height. */

.track-open strong {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  /* Long unbroken tokens (a URL-ish title, a run of Latin in an RTL string)
     would otherwise widen the flex item past the card instead of wrapping. */
  overflow-wrap: anywhere;
}

.cover {
  width: 54px;
  height: 54px;
  border-radius: 14px;
  background: linear-gradient(135deg,#c7aa76,#51475b);
  display: grid;
  place-items: center;
  font-size: var(--fs-xl);
  flex: none;
  cursor: pointer;
}

.actions {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 12px;
}

.actions button {
  width: 39px;
  height: 39px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: #202026;
  color: #fff;
  cursor: pointer;
}

.wave {
  flex: 1;
  height: 30px;
  background: repeating-linear-gradient(90deg,#555 0 3px,transparent 3px 7px);
  opacity: .55;
  border-radius: 8px;
}

.tags {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 10px;
}

.tag {
  font-size: var(--fs-2xs);
  color: #bbb;
  background: #202026;
  border: 1px solid var(--line);
  padding: 5px 8px;
  border-radius: 999px;
}

.composer-wrap {
  padding: 8px 12px max(12px,env(safe-area-inset-bottom));
  background: linear-gradient(transparent,#0b0b0d 22%);
}

.composer-wrap.hidden {
  display: none;
}

.composer {
  max-width: 720px;
  margin: auto;
  display: grid;
  grid-template-columns: minmax(0,1fr) auto;
  gap: 8px;
  align-items: end;
  background: #18181d;
  border: 1px solid #33333b;
  border-radius: 20px;
  padding: 9px;
}

.input-column {
  min-width: 0;
}

.composer textarea {
  display: block;
  width: 100%;
  min-height: 46px;
  max-height: min(36vh,320px);
  resize: none;
  overflow-y: auto;
  border: 0;
  outline: 0;
  background: transparent;
  color: #fff;
  padding: 10px 10px 4px;
  font: inherit;
  font-size: var(--fs-base);
  line-height: 1.45;
}

.composer textarea::placeholder {
  color: #858590;
}

.composer button {
  width: 42px;
  height: 42px;
  border: 0;
  border-radius: 14px;
  background: #fff;
  color: #111;
  font-size: var(--fs-xl);
  margin-bottom: 1px;
  cursor: pointer;
}

.composer-meta {
  display: flex;
  justify-content: flex-end;
  padding: 0 10px 3px;
  min-height: 18px;
}

.char-count {
  font-size: var(--fs-2xs);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.char-count.near-limit {
  color: #d7b06a;
}

.library {
  max-width: 900px;
  margin: auto;
}

.library-head {
  display: flex;
  align-items: end;
  justify-content: space-between;
  gap: 16px;
  margin: 8px 0 20px;
}

.library-head h1 {
  margin: 0;
  font-size: var(--fs-display);
}

.library-grid {
  display: grid;
  grid-template-columns: repeat(2,minmax(0,1fr));
  gap: 12px;
}

.library-card {
  border: 1px solid var(--line);
  border-radius: 18px;
  background: linear-gradient(145deg,#19191f,#111115);
  padding: 14px;
  cursor: pointer;
  display: grid;
  grid-template-columns: 64px minmax(0,1fr);
  gap: 12px;
  align-items: center;
}

.library-card:hover {
  border-color: #4a4a55;
}

.library-cover {
  width: 64px;
  height: 64px;
  border-radius: 16px;
  background: linear-gradient(135deg,#826f91,#c4a36c);
  display: grid;
  place-items: center;
  font-size: var(--fs-display);
}

.library-card h3 {
  margin: 0 0 5px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.library-meta {
  font-size: var(--fs-xs);
  color: var(--muted);
  line-height: 1.5;
}

/* The library card's "..." — the same component the chat card and player use. It is
   positioned out of the two-column grid so it cannot stretch the row, and the card
   already carries .has-card-menu, which supplies position:relative. */

.library-card .card-menu {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 30px;
  height: 30px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: #8b8494;
  cursor: pointer;
  transition: background .16s,color .16s;
}

.library-card .card-menu:hover,
.library-card .card-menu.active {
  background: #221f29;
  color: #e6e0ee;
}

.library-card .card-menu svg {
  width: 18px;
  height: 18px;
}

.library-card .commands-panel {
  top: 42px;
  right: 8px;
}

/* Keep the title clear of the button. */

.library-card .library-open {
  padding-right: 26px;
}


.detail {
  max-width: 860px;
  margin: auto;
}

.back {
  margin-bottom: 18px;
}

.hero {
  display: grid;
  grid-template-columns: 180px minmax(0,1fr);
  gap: 24px;
  align-items: end;
  padding: 20px 0 24px;
}

.hero-cover {
  width: 180px;
  height: 180px;
  border-radius: 28px;
  background: linear-gradient(135deg,#b39a6e,#50445e);
  display: grid;
  place-items: center;
  font-size: 64px;
  box-shadow: 0 20px 55px #0008;
}

.hero h1 {
  margin: 0 0 8px;
  font-size: clamp(30px,6vw,50px);
  line-height: 1.05;
}

/* One player, one playhead. The detail view keeps this element so its
   lip-sync and meter wiring stays valid, but never shows it: playback
   belongs to the app's single player. */

.hero-audio {
  display: none;
}

.detail-tags {
  display: flex;
  gap: 7px;
  flex-wrap: wrap;
  margin-top: 12px;
}

.tabs {
  display: flex;
  gap: 5px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 18px;
}

.tab {
  border: 0;
  background: transparent;
  color: var(--muted);
  padding: 11px 14px;
  border-bottom: 2px solid transparent;
  cursor: pointer;
}

.tab.active {
  color: #fff;
  border-bottom-color: #fff;
}

.tab-panel {
  display: none;
}

.tab-panel.active {
  display: block;
}

.lyrics {
  max-width: 680px;
  margin: 10px auto 50px;
  font-size: var(--fs-xl);
  line-height: 1.8;
  white-space: pre-wrap;
  color: #f2f2f4;
}

.lyrics .section {
  margin: 28px 0;
}

.lyrics .section-name {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: .12em;
  color: var(--muted);
  margin-bottom: 7px;
}

.details-grid {
  display: grid;
  grid-template-columns: 170px 1fr;
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: 14px;
  overflow: hidden;
}

.details-grid>div {
  background: var(--panel2);
  padding: 12px;
}

.details-grid .label {
  color: var(--muted);
  font-size: var(--fs-xs);
}

.dev-block {
  margin: 0 0 16px;
  border: 1px solid var(--line);
  border-radius: 14px;
  overflow: hidden;
}

.dev-title {
  padding: 10px 13px;
  background: #1a1a20;
  color: #bdbdc6;
  font-size: var(--fs-xs);
}

.dev-block pre {
  margin: 0;
  padding: 14px;
  white-space: pre-wrap;
  overflow: auto;
  font: 12px/1.55 ui-monospace,SFMono-Regular,Menlo,monospace;
  color: #dedee5;
  background: #101014;
}

.status {
  padding: 30px;
  text-align: center;
  color: var(--muted);
}

@media (max-width:700px) {
  .library-grid {
    grid-template-columns: 1fr;
  }
  .hero {
    grid-template-columns: 100px 1fr;
    gap: 16px;
  }
  .hero-cover {
    width: 100px;
    height: 100px;
    border-radius: 20px;
    font-size: 38px;
  }
  .details-grid {
    grid-template-columns: 120px 1fr;
  }
}

@media (max-width:600px) {
  header {
    padding: 0 9px;
  }
  .subtitle {
    display: none;
  }
  .provider {
    display: none;
  }
  main {
    /* 12px sides: .composer-wrap's later shorthand (padding: 8px 12px …) wins
       over its own 8px phone rule, so the composer card sits at 12px here. */
    padding: 16px 12px;
  }
  .msg {
    max-width: 92%;
  }
  .tags {
    display: none;
  }
  .composer-wrap {
    padding-left: 8px;
    padding-right: 8px;
  }
  .composer textarea {
    font-size: var(--fs-base);
    max-height: 42vh;
  }
  .nav button {
    padding: 7px 9px;
  }
  .chat-songs-nav span {
    display: none;
  }
  .lyrics {
    font-size: var(--fs-lg);
  }
}

.generation-progress {
  display: flex;
  align-items: center;
  gap: 36px;
  align-self: stretch;
  border: 1px solid #393342;
  border-radius: 24px;
  padding: 25px 28px;
  background: radial-gradient(ellipse at 0% 0%,#382b4b88,transparent 65%),#15141b;
  min-width: 0;
}

.progress-scene {
  width: 108px;
  height: 124px;
  position: relative;
  flex: none;
}

.music-buddy {
  position: absolute;
  left: 19px;
  top: 36px;
  width: 72px;
  height: 66px;
  animation: ponder 3s ease-in-out infinite;
  transform-origin: 50% 80%;
}

.buddy-face {
  position: absolute;
  inset: 5px 5px 0;
  border-radius: 27px 27px 23px 23px;
  background: linear-gradient(135deg,#f0d7bd,#cbb3fa);
  box-shadow: inset -6px -5px 0 #6d539524,0 0 28px #b58bff26;
  display: flex;
  gap: 18px;
  justify-content: center;
  padding-top: 24px;
}

.eye {
  width: 5px;
  height: 8px;
  border-radius: 4px;
  background: #382943;
  animation: blink 5s infinite;
}

.smile {
  position: absolute;
  top: 38px;
  left: 27px;
  width: 10px;
  height: 6px;
  border-bottom: 2px solid #614875;
  border-radius: 50%;
}

.headphones {
  position: absolute;
  z-index: 2;
  inset: 0 -2px 15px;
  border: 5px solid #9478ba;
  border-bottom: 0;
  border-radius: 35px 35px 0 0;
}

.headphones:before,
.headphones:after {
  content: "";
  position: absolute;
  top: 28px;
  width: 12px;
  height: 23px;
  border-radius: 6px;
  background: #685277;
  border: 2px solid #ad94cd;
}

.headphones:before {
  left: -8px;
}

.headphones:after {
  right: -8px;
}

.buddy-shadow {
  position: absolute;
  left: 28px;
  bottom: 8px;
  width: 56px;
  height: 7px;
  background: #07050c88;
  filter: blur(3px);
}

.thought {
  position: absolute;
  color: #dfc4ff;
  animation: thought-drift 3s ease-in-out infinite;
}

.thought-one {
  left: 8px;
  top: 22px;
  font-size: var(--fs-lg);
}

.thought-two {
  left: 61px;
  top: 2px;
  font-size: var(--fs-base);
  animation-delay: -1s;
}

.thought-three {
  right: 0;
  top: 22px;
  font-size: var(--fs-xl);
  animation-delay: -2s;
}

.progress-content {
  min-width: 0;
  flex: 1;
}

.progress-eyebrow {
  font-size: var(--fs-2xs);
  font-weight: 600;
  letter-spacing: .16em;
  color: #b5a0cc;
  margin-bottom: 9px;
}

.progress-title {
  display: block;
  font-size: var(--fs-lg);
  line-height: 1.3;
  color: #f5edff;
  letter-spacing: -.025em;
}

.progress-description {
  font-size: var(--fs-sm);
  line-height: 1.55;
  color: #b4aebf;
  margin: 7px 0 15px;
}

.progress-meta {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
}

.progress-steps {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  font-size: var(--fs-2xs);
  color: #88818f;
}

.progress-steps>span {
  border: 1px solid #39333f;
  border-radius: 20px;
  padding: 5px 9px;
}

.progress-steps .active {
  border-color: #a88ac7;
  color: #e6d1ff;
  background: #b58cff14;
}

.progress-steps .complete {
  color: #b7d5bf;
  border-color: #405549;
}

.progress-steps .complete:before {
  content: "✓ ";
}

.progress-steps .skipped {
  opacity: .6;
}

.progress-times {
  display: flex;
  gap: 13px;
  margin-left: auto;
  color: #a39aa9;
  font-size: var(--fs-2xs);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.progress-times b {
  color: #eee3f5;
  font-weight: 500;
  margin-left: 3px;
}

.composer button:disabled {
  opacity: .4;
  cursor: wait;
}

[data-stage=music] .music-buddy {
  animation: groove .85s ease-in-out infinite;
}

[data-stage=music] .thought {
  animation: note-dance 1.7s ease-out infinite;
}

[data-stage=music] .thought-two {
  animation-delay: -.6s;
}

[data-stage=music] .thought-three {
  animation-delay: -1.2s;
}

[data-stage=done] .music-buddy,
[data-stage=error] .music-buddy,
[data-stage=done] .eye,
[data-stage=error] .eye {
  animation: none;
}

[data-stage=done] .thought,
[data-stage=error] .thought {
  animation: none;
  opacity: .45;
}

[data-stage=done] {
  border-color: #354d3f;
}

[data-stage=error] {
  border-color: #6e474c;
}

[data-stage=error] .progress-title {
  color: #efb6b9;
}

/* While a song is being made, .progress-title and .progress-description hold a
   short rotating status line, and clamping them to one ellipsised row keeps the
   card a fixed height. On failure the same two elements hold the only copy of
   the provider's error, and the clamp hid the part that says what to do: a Suno
   refusal read "Kie Suno generation failed: Your tags contai…", losing the
   artist name it was naming. Let both wrap in the error state only — the
   running card is untouched.

   These two selectors are (0,1,1) against the (0,1,0) of .progress-title and
   .progress-description in 52-director-card.css, so they win on specificity
   rather than on file order. AGENTS.md adversarial item 12. */
[data-stage=error] .progress-title,
[data-stage=error] .progress-description {
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
}

@keyframes ponder {
  0%,
  100% {
    transform: translateY(0) rotate(-7deg);
  }
  50% {
    transform: translateY(-5px) rotate(5deg);
  }
}

@keyframes blink {
  0%,
  43%,
  47%,
  100% {
    transform: scaleY(1);
  }
  45% {
    transform: scaleY(.12);
  }
}

@keyframes thought-drift {
  0%,
  100% {
    transform: translateY(2px);
    opacity: .35;
  }
  50% {
    transform: translateY(-4px);
    opacity: 1;
  }
}

@keyframes groove {
  0%,
  100% {
    transform: translateY(0) rotate(-8deg);
  }
  50% {
    transform: translateY(-7px) rotate(8deg);
  }
}

@keyframes note-dance {
  0% {
    transform: translateY(9px) rotate(-12deg);
    opacity: 0;
  }
  45% {
    opacity: 1;
  }
  100% {
    transform: translateY(-15px) rotate(12deg);
    opacity: 0;
  }
}

@media (max-width:600px) {
  .generation-progress {
    gap: 20px;
    padding: 18px 14px;
  }
  .progress-scene {
    width: 72px;
    transform: scale(.78);
    transform-origin: center;
    margin-left: -10px;
    margin-right: -4px;
  }
  .progress-title {
    font-size: var(--fs-base);
  }
  .progress-eyebrow {
    font-size: var(--fs-2xs);
    letter-spacing: .1em;
  }
  .progress-meta {
    align-items: flex-start;
    gap: 7px;
  }
  .progress-steps {
    gap: 5px;
  }
  .progress-times {
    gap: 7px;
    font-size: var(--fs-2xs);
  }
  .progress-steps>span {
    padding: 5px 7px;
  }
}

@media (prefers-reduced-motion:reduce) {
  .generation-progress * {
    animation: none !important;
  }
}

/* Song cards and sticky player */

.track {
  overflow: hidden;
  transition: border-color .25s,box-shadow .25s,background .25s;
}

.track.expanded {
  border-color: #554766;
  box-shadow: 0 18px 55px #0005;
  background: radial-gradient(circle at 0 0,#32273e66,transparent 48%),linear-gradient(#19181f,#101014);
}

.track-top {
  position: relative;
}

.track-top .like {
  flex: none;
  width: 32px;
  height: 32px;
  border: 0;
  background: transparent;
  box-shadow: none;
}

.track-top .like:hover {
  border: 0;
  background: transparent;
  transform: scale(1.08);
}

.track-top .share-toggle,
.track-top .menu-toggle {
  flex: none;
  width: 32px;
  height: 32px;
}

.chat-card .player,
.chat-card .lyrics-panel,
.chat-card .tags {
  display: none !important;
}

.chat-card {
  padding: 14px 16px;
}

.chat-card .track-top {
  align-items: center;
}

.track-top .track-open strong {
  font-size: var(--fs-sm);
  line-height: 1.3;
}

.track-top .track-open .muted {
  font-size: var(--fs-xs);
  line-height: 1.25;
  margin-top: 2px;
}

/* 72px, the producer card's avatar size, centred in a 108px header band so the
   song card's text starts exactly where the producer card's does — two cards in
   the same feed, at the same width, whose text no longer begins 26px apart.
   The band: .track-top starts at 17 (the card's 16px padding plus its 1px
   border), the 1px here makes 18, which is (108-72)/2; 18 + 72 + the 18px gap
   on .chat-card .track-top = 108. */

.chat-card .cover {
  width: 72px;
  height: 72px;
  margin-left: 1px;
}

.chat-card.expanded {
  border-color: #554766;
}

.has-card-menu .commands-panel.open,
.chat-card .share-panel.open {
  display: block;
}

.cover {
  box-shadow: inset 0 0 0 1px #fff1,0 8px 24px #0004;
}

.playback-scroll-space {
  height: 0;
  flex: none;
  pointer-events: none;
}

.player {
  margin-top: 9px;
}

.transport {
  display: grid;
  grid-template-columns: minmax(172px,2fr) auto minmax(96px,1fr) 40px;
  gap: 8px;
  align-items: center;
}

.icon-btn {
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  border-radius: 999px;
  border: 1px solid #35343c;
  background: #202026;
  color: #f8f7fb;
  cursor: pointer;
  transition: transform .18s,background .18s,border-color .18s;
}

.icon-btn:hover {
  transform: translateY(-1px);
  background: #292831;
  border-color: #4b4754;
}

.icon-btn:focus-visible,
.timeline-play:focus-visible,
.command:focus-visible,
.share-target:focus-visible,
.timeline:focus-visible {
  outline: 2px solid #c8a8f0;
  outline-offset: 2px;
}

.icon-btn svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.timeline-box {
  position: relative;
  min-width: 0;
  height: 40px;
  display: flex;
  align-items: center;
}

.timeline {
  --played: 0%;
  position: absolute;
  inset: 0;
  z-index: 1;
  display: block;
  width: 100%;
  height: 40px;
  margin: 0;
  appearance: none;
  background: transparent;
  cursor: pointer;
}

.timeline-play {
  position: absolute;
  left: 0;
  top: 4px;
  z-index: 2;
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  border: 2px solid #f7efff;
  border-radius: 50%;
  background: linear-gradient(145deg,#f7efff,#bfa2e5);
  color: #201728;
  box-shadow: 0 5px 18px #b38dde55;
  cursor: grab;
  touch-action: none;
  transition: background .18s,box-shadow .18s;
}

.timeline-play:hover {
  background: linear-gradient(145deg,#fff,#cbb0eb);
  box-shadow: 0 6px 22px #c39bed77;
}

.timeline-play.dragging {
  cursor: grabbing;
  box-shadow: 0 4px 24px #d4b0ff99;
}

.timeline-play svg {
  width: 12px;
  height: 12px;
  fill: currentColor;
  stroke: none;
  pointer-events: none;
}

.timeline::-webkit-slider-runnable-track {
  height: 7px;
  border-radius: 99px;
  background: linear-gradient(90deg,#d7b8fb 0 var(--played),#3a3841 var(--played) 100%);
  box-shadow: inset 0 1px 2px #0007;
}

.timeline::-moz-range-track {
  height: 7px;
  border-radius: 99px;
  background: linear-gradient(90deg,#d7b8fb 0 var(--played),#3a3841 var(--played) 100%);
}

.timeline::-webkit-slider-thumb {
  appearance: none;
  width: 32px;
  height: 32px;
  margin-top: -12.5px;
  border: 0;
  background: transparent;
}

.timeline::-moz-range-thumb {
  width: 32px;
  height: 32px;
  border: 0;
  background: transparent;
}

.time-readout {
  min-height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 8px;
  border: 1px solid #35333c;
  border-radius: 9px;
  background: linear-gradient(#1e1d23,#151419);
  box-shadow: inset 0 1px 0 #ffffff0a,0 4px 12px #0003;
  color: #aaa4b1;
  font: 10px/1 ui-monospace,SFMono-Regular,Menlo,monospace;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.stereo-meter {
  width: 100%;
  min-width: 0;
  display: grid;
  grid-template-columns: 10px 1fr;
  gap: 3px 5px;
  align-items: center;
  color: #77727f;
  font: 8px/1 ui-monospace,monospace;
  letter-spacing: .05em;
}

.meter-track {
  height: 7px;
  border-radius: 9px;
  overflow: hidden;
  background: repeating-linear-gradient(90deg,#28272d 0 4px,transparent 4px 6px);
}

.meter-level {
  width: var(--level,2%);
  height: 100%;
  border-radius: inherit;
  background: repeating-linear-gradient(90deg,#8ee0ba 0 4px,transparent 4px 6px);
  filter: drop-shadow(0 0 4px #7bdcaf66);
  transition: width 70ms linear;
}

.meter-track.right .meter-level {
  background: repeating-linear-gradient(90deg,#d5b3fb 0 4px,transparent 4px 6px);
  filter: drop-shadow(0 0 4px #bd8df866);
}

.icon-btn.active {
  background: #dac0f3;
  color: #24192c;
  border-color: #ead9fb;
}

.like.liked {
  color: #c81450;
  background: transparent;
  border: 0;
}

.like.liked svg {
  fill: #c81450;
  stroke: #ff729e;
  stroke-width: 1.25;
  filter: drop-shadow(0 0 3px #ff477d99) drop-shadow(0 5px 8px #70052788);
  transform: scale(1.08);
}

.panel {
  display: none;
  margin-top: 13px;
  padding-top: 14px;
  border-top: 1px solid #302e36;
}

.panel.open {
  display: block;
  animation: panel-in .22s ease-out;
}

.panel-heading {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}

.panel-heading strong {
  font-size: var(--fs-sm);
}

.panel-heading span {
  font-size: var(--fs-2xs);
  color: #817c87;
}

.player-lyrics {
  height: min(340px,42vh);
  overflow: auto;
  padding: 118px 7px 118px 2px;
  color: #e9e5ed;
  font-size: var(--fs-base);
  line-height: 1.7;
  white-space: pre-wrap;
  perspective: 620px;
  scrollbar-width: none;
  overscroll-behavior: contain;
}

.player-lyrics::-webkit-scrollbar {
  display: none;
}

.player-lyrics .section {
  margin: 0 0 25px;
}

.player-lyrics .section:last-child {
  margin-bottom: 4px;
}

.player-lyrics .section-name {
  font-size: var(--fs-2xs);
  line-height: 1.4;
  letter-spacing: .15em;
  text-transform: uppercase;
  color: #9b8ba9;
  margin-bottom: 6px;
  opacity: .35;
  transition: color 2.4s cubic-bezier(.4,0,.2,1),opacity 2.4s cubic-bezier(.4,0,.2,1);
}

.lyrics-follow-status {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: #baa6ca !important;
}

.lyrics-follow-status:before {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: #c9a7ef;
  box-shadow: 0 0 0 0 #c9a7ef66;
  animation: lyric-pulse 1.8s ease-out infinite;
}

.player-lyrics.lyrics-following {
  scroll-behavior: smooth;
  mask-image: linear-gradient(transparent,#000 16%,#000 84%,transparent);
}

.player-lyrics .lyric-line {
  display: block;
  padding: 3px 10px;
  border-left: 2px solid transparent;
  color: #807986;
  opacity: .08;
  font-size: 1em;
  transform-origin: left center;
  transform: translateY(12px) rotateX(-14deg) scale(.93);
  filter: blur(1px);
  transition: color 2.4s cubic-bezier(.4,0,.2,1),opacity 2.4s cubic-bezier(.4,0,.2,1),border-color 2.4s cubic-bezier(.4,0,.2,1),text-shadow 2.4s cubic-bezier(.4,0,.2,1),font-size 2.4s cubic-bezier(.4,0,.2,1),transform 2.4s cubic-bezier(.4,0,.2,1),filter 2.4s cubic-bezier(.4,0,.2,1);
}

.player-lyrics .lyric-line.lyric-past {
  transform: translateY(-12px) rotateX(14deg) scale(.93);
}

.player-lyrics .lyric-line.lyric-previous {
  color: #aaa2b0;
  opacity: .38;
  transform: translateY(-4px) rotateX(6deg) scale(.97);
  filter: blur(.2px);
}

.player-lyrics .lyric-line.lyric-next {
  color: #aaa2b0;
  opacity: .34;
  transform: translateY(4px) rotateX(-6deg) scale(.97);
  filter: blur(.2px);
}

.player-lyrics .lyric-line.active {
  color: #fff;
  opacity: 1;
  font-size: 1.25em;
  border-left-color: #caa7f2;
  text-shadow: 0 0 18px #c79bf655;
  transform: translateY(0) rotateX(0) scale(1);
  filter: none;
}

.player-lyrics .active-section .section-name {
  color: #c7aedf;
  opacity: 1;
}

.player-lyrics .lyric-gap,
.lyrics .lyric-gap {
  display: block;
  height: .7em;
}

.lyrics .lyric-line {
  display: block;
}

@keyframes lyric-pulse {
  70% {
    box-shadow: 0 0 0 7px #c9a7ef00;
  }
  100% {
    box-shadow: 0 0 0 0 #c9a7ef00;
  }
}

.share-grid {
  display: grid;
  grid-template-columns: repeat(7,minmax(66px,1fr));
  gap: 10px;
  overflow-x: auto;
  padding: 2px 1px 7px;
}

.share-target {
  min-width: 66px;
  border: 0;
  background: transparent;
  color: #c9c5cf;
  cursor: pointer;
  display: grid;
  justify-items: center;
  gap: 7px;
  font-size: var(--fs-2xs);
}

.share-bubble {
  width: 45px;
  height: 45px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: #26242b;
  border: 1px solid #3b3842;
  color: #fff;
  font-size: var(--fs-base);
  font-weight: 700;
  box-shadow: 0 7px 19px #0004;
  transition: transform .18s,filter .18s;
}

.share-bubble svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
}

.share-target:hover .share-bubble {
  transform: translateY(-2px);
  filter: brightness(1.15);
}

.share-bubble.copy {
  background: #655177;
}

.share-bubble.device {
  background: #ecebf0;
  color: #222;
}

.share-bubble.whatsapp {
  background: #25d366;
}

.share-bubble.telegram {
  background: #229ed9;
}

.share-bubble.facebook {
  background: #1877f2;
}

.share-bubble.x {
  background: #09090a;
}

.share-bubble.email {
  background: #b76d43;
}

.commands {
  display: flex;
  gap: 9px;
  overflow-x: auto;
  padding: 2px 1px 6px;
}

.command {
  min-width: max-content;
  border: 1px solid #383640;
  border-radius: 13px;
  background: #201f25;
  color: #eeebf1;
  padding: 11px 14px;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: var(--fs-xs);
}

.command:hover {
  background: #29272f;
  border-color: #514b5b;
}

.command:disabled {
  opacity: .45;
  cursor: not-allowed;
}

.command svg {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
}

.tray-status {
  min-height: 16px;
  margin-top: 8px;
  color: #a69eab;
  font-size: var(--fs-2xs);
}

.tags {
  margin-top: 10px;
}

@keyframes panel-in {
  from {
    opacity: 0;
    transform: translateY(-4px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* Song deletion */

.commands .command,
.delete-confirm button {
  height: 32px;
  padding: 0 11px;
  font: 700 var(--fs-2xs)/1 Inter,system-ui,sans-serif;
}

.command.delete-song {
  margin-left: auto;
  border-color: #7d263c;
  background: linear-gradient(145deg,#6e1830,#3d111e);
  color: #ffdce5;
}

.command.delete-song:hover {
  border-color: #e44d72;
  background: linear-gradient(145deg,#8b1d3b,#501326);
}

.delete-confirm {
  display: flex;
  align-items: center;
  gap: 9px;
  margin-top: 10px;
  padding: 10px 11px;
  border: 1px solid #68263a;
  border-radius: 13px;
  background: #2b1119;
  color: #f4dfe5;
  font-size: var(--fs-2xs);
}

.delete-confirm[hidden] {
  display: none;
}

.delete-confirm span {
  min-width: 0;
  flex: 1;
}

.delete-confirm button {
  flex: none;
  border-radius: 10px;
  cursor: pointer;
}

.delete-cancel {
  border: 1px solid #4e4248;
  background: #221f23;
  color: #ddd;
}

.delete-now {
  border: 1px solid #ef557a;
  background: #b20e3f;
  color: #fff;
}

.track.deleting {
  opacity: .52;
  pointer-events: none;
}

.track.deleted {
  opacity: 0;
  transform: scale(.97);
  transition: opacity .28s,transform .28s;
}

/* Director expressions */

.affinity-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 5px;
  flex: none;
}

.more-like {
  width: 0;
  height: 32px;
  display: grid;
  place-items: center;
  overflow: hidden;
  opacity: 0;
  transform: translateX(12px) scale(.72);
  padding: 0;
  border: 0 solid #773149;
  border-radius: 999px;
  background: radial-gradient(circle at 35% 30%,#4b2937,#20151c 72%);
  cursor: pointer;
  pointer-events: none;
  transition: width .62s cubic-bezier(.16,1,.3,1),border-width .2s,opacity .32s ease,transform .62s cubic-bezier(.16,1,.3,1),filter .2s,box-shadow .2s;
}

.more-like.visible {
  width: 42px;
  border-width: 1px;
  opacity: 1;
  transform: none;
  pointer-events: auto;
  box-shadow: inset 0 1px 0 #ffffff12,0 6px 18px #5b092b40;
  animation: more-like-reveal .78s cubic-bezier(.16,1,.3,1);
}

.more-like:hover {
  filter: brightness(1.2);
  transform: translateY(-1px) scale(1.04);
}

.more-like:focus-visible {
  outline: 2px solid #f0a8c3;
  outline-offset: 2px;
}

.more-like svg {
  width: 28px;
  height: 19px;
  overflow: visible;
}

.more-like .more-like-source {
  fill: #c60d48;
  stroke: #ff759f;
  stroke-width: 1.35;
  filter: drop-shadow(0 2px 3px #6b082b);
}

.more-like .more-like-next {
  fill: #21151b;
  stroke: #f5dbe4;
  stroke-width: 1.65;
}

.more-like .more-like-plus {
  fill: none;
  stroke: #fff;
  stroke-width: 2;
  stroke-linecap: round;
}

.like.liked {
  color: #bb0e42 !important;
}

.like.liked svg {
  fill: #b7083d !important;
  stroke: #e51e5b !important;
  stroke-width: 1.55 !important;
  filter: drop-shadow(0 0 4px #ff285fbb) drop-shadow(0 6px 9px #710528aa) !important;
  transform: scale(1.1) !important;
}

@keyframes more-like-reveal {
  0% {
    transform: translateX(12px) scale(.68);
  }
  62% {
    transform: translateX(-2px) scale(1.08);
  }
  100% {
    transform: none;
  }
}

.buddy-face {
  transform-origin: 50% 70%;
  transition: transform .55s ease,background .45s;
}

.eye {
  position: relative;
  overflow: visible;
  transition: transform .35s,height .25s;
}

.eye:after {
  content: "";
  position: absolute;
  width: 2px;
  height: 2px;
  border-radius: 50%;
  background: #f8eefe;
  left: 1px;
  top: 1px;
  transition: transform .35s;
}

.brow {
  position: absolute;
  top: 18px;
  width: 12px;
  height: 4px;
  border-top: 2px solid #6b4d78;
  border-radius: 50%;
  opacity: .6;
  transition: transform .35s;
}

.brow-left {
  left: 13px;
}

.brow-right {
  right: 13px;
}

.cheek {
  position: absolute;
  top: 36px;
  width: 9px;
  height: 5px;
  border-radius: 50%;
  background: #d88caa55;
  opacity: 0;
  transition: opacity .35s;
}

.cheek-left {
  left: 9px;
}

.cheek-right {
  right: 9px;
}

[data-stage=connecting] .music-buddy {
  animation: peek 2.3s ease-in-out infinite;
}

[data-stage=connecting] .buddy-face {
  transform: rotate(-6deg) translateX(-2px);
}

[data-stage=director] .music-buddy {
  animation: listen 2.7s ease-in-out infinite;
}

[data-stage=director] .buddy-face {
  transform: rotate(7deg) translateX(3px);
}

[data-stage=director] .eye {
  transform: translateX(2px);
}

[data-stage=director] .eye:after {
  transform: translateX(1px);
}

[data-stage=director] .brow-right {
  transform: rotate(8deg);
}

[data-stage=music] .smile {
  width: 13px;
  left: 25px;
}

[data-stage=saving] .music-buddy {
  animation: nod 1.2s ease-in-out infinite;
}

[data-stage=saving] .eye {
  height: 4px;
  border-radius: 0 0 5px 5px;
}

[data-stage=saving] .smile {
  transform: rotate(-4deg);
}

[data-mood=patient] .music-buddy {
  animation-duration: 4.2s;
}

[data-mood=patient] .buddy-face {
  transform: rotate(-2deg);
}

[data-mood=focus] .eye {
  height: 6px;
  transform: translateX(2px);
}

[data-mood=focus] .brow {
  opacity: 1;
}

[data-mood=deep] .music-buddy {
  animation: deep-thought 3.6s ease-in-out infinite;
}

[data-mood=deep] .brow-left {
  transform: rotate(-8deg);
}

[data-mood=deep] .brow-right {
  transform: rotate(8deg);
}

[data-mood=groove] .music-buddy {
  animation: groove .72s ease-in-out infinite;
}

[data-stage=done] .buddy-face {
  transform: translateY(-2px);
}

[data-stage=done] .eye {
  height: 5px;
  border-radius: 0 0 6px 6px;
}

[data-stage=done] .smile {
  width: 17px;
  height: 9px;
  left: 23px;
  top: 36px;
  border-width: 3px;
}

[data-stage=done] .cheek {
  opacity: 1;
}

[data-stage=done] .thought-one {
  opacity: 1 !important;
  color: #a8e3b8;
}

[data-stage=done] .thought-one:after {
  content: "✓";
  font-size: var(--fs-base);
}

[data-stage=done] .thought-one {
  font-size: 0;
}

[data-stage=error] .brow-left {
  transform: rotate(14deg);
}

[data-stage=error] .brow-right {
  transform: rotate(-14deg);
}

[data-stage=error] .smile {
  top: 42px;
  border-bottom: 0;
  border-top: 2px solid #754f66;
}

@keyframes peek {
  0%,
  100% {
    transform: translateY(1px) rotate(-5deg);
  }
  50% {
    transform: translateY(-4px) rotate(2deg);
  }
}

@keyframes listen {
  0%,
  100% {
    transform: translateX(0) rotate(1deg);
  }
  50% {
    transform: translateX(4px) rotate(5deg);
  }
}

@keyframes nod {
  0%,
  100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(3px) rotate(2deg);
  }
}

@keyframes deep-thought {
  0%,
  100% {
    transform: translateY(1px) rotate(-2deg);
  }
  50% {
    transform: translateY(-2px) rotate(1deg);
  }
}

@media (max-width:600px) {
  .transport {
    grid-template-columns: minmax(82px,2fr) auto minmax(38px,1fr) 32px;
    gap: 5px;
  }
  .stereo-meter {
    grid-template-columns: 8px 1fr;
    gap: 3px;
  }
  .time-readout {
    font-size: var(--fs-2xs);
  }
  .share-grid {
    grid-template-columns: repeat(7,66px);
  }
  .player-lyrics {
    font-size: var(--fs-base);
  }
  .track {
    padding: 12px;
  }
  .icon-btn {
    width: 36px;
    height: 36px;
  }
  .track-top .like,
  .track-top .share-toggle,
  .track-top .menu-toggle {
    width: 32px;
    height: 32px;
  }
  .chat-card {
    padding: 11px;
  }
  .chat-card .cover {
    width: 54px;
    height: 54px;
  }
  .chat-card .affinity-actions,
  .track.global-full-card .affinity-actions {
    gap: 3px;
  }
}

@media (prefers-reduced-motion:reduce) {
  .panel.open,
  .more-like.visible {
    animation: none;
  }
  .more-like {
    transition: none;
  }
  .meter-level,
  .player-lyrics .lyric-line {
    transition: none;
  }
  .player-lyrics.lyrics-following {
    scroll-behavior: auto;
  }
  .lyrics-follow-status:before {
    animation: none;
  }
}

.player-lyrics {
  --lyric-slot-height: 64px;
  position: relative;
  height: calc(var(--lyric-slot-height)*5);
  overflow: hidden;
  padding: 0 7px 0 2px;
  perspective: none;
  scroll-behavior: auto;
  mask-image: none !important;
}

.player-lyrics.lyrics-following {
  scroll-behavior: auto;
}

.player-lyrics .section {
  position: relative;
  margin: 0;
}

.player-lyrics .section:last-child {
  margin-bottom: 0;
}

.player-lyrics:after {
  content: "";
  display: block;
  height: calc(var(--lyric-slot-height)*5);
  pointer-events: none;
}

.player-lyrics .section-name {
  display: none;
}

.lyrics-panel-heading {
  justify-content: flex-end;
  margin-bottom: 8px;
}

.lyrics-section-sticky {
  position: sticky;
  z-index: 3;
  top: 5px;
  display: block;
  width: max-content;
  height: 22px;
  margin: 0 3px -22px auto;
  padding: 4px 8px;
  border: 1px solid #4b4059;
  border-radius: 999px;
  background: #211c2aeb;
  color: #d8c2ed;
  font: 600 var(--fs-2xs)/1.4 Inter,system-ui,sans-serif;
  letter-spacing: .14em;
  text-align: right;
  text-transform: uppercase;
  white-space: nowrap;
  cursor: pointer;
  box-shadow: 0 5px 16px #0005;
  transition: background .2s,border-color .2s,color .2s,opacity .9s ease,transform 1.2s cubic-bezier(.22,1,.36,1);
}

.player-lyrics.lyrics-outro .lyrics-section-sticky {
  opacity: 0;
  transform: translateY(-10px);
  pointer-events: none;
}

.lyrics-section-sticky:hover {
  background: #30263c;
  border-color: #715b86;
  color: #f0e4fb;
}

.lyrics-section-sticky:focus-visible {
  outline: 2px solid #c8a8f0;
  outline-offset: 2px;
}

.lyrics-section-sticky:empty {
  display: none;
}

.player-lyrics .lyric-line {
  position: relative;
  z-index: 1;
  box-sizing: border-box;
  display: flex;
  height: var(--lyric-slot-height);
  min-height: var(--lyric-slot-height);
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  overflow: hidden;
  padding: 6px 10px;
  line-height: 1.18;
  transform: translateY(8px) scale(.95);
  filter: blur(.7px);
  transition: color 1.8s cubic-bezier(.22,1,.36,1),opacity 1.8s cubic-bezier(.22,1,.36,1),border-color 1.8s cubic-bezier(.22,1,.36,1),text-shadow 1.8s cubic-bezier(.22,1,.36,1),font-size 1.8s cubic-bezier(.22,1,.36,1),transform 1.8s cubic-bezier(.22,1,.36,1),filter 1.8s cubic-bezier(.22,1,.36,1);
}

.player-lyrics .lyric-line.lyric-past,
.player-lyrics .lyric-line.lyric-future {
  opacity: 0;
  transform: translateY(-6px) scale(.96);
  filter: blur(.5px);
}

.player-lyrics .lyric-line.lyric-future {
  transform: translateY(6px) scale(.96);
}

.player-lyrics .lyric-line.lyric-previous-two,
.player-lyrics .lyric-line.lyric-next-two {
  color: #938b99;
  opacity: .42;
  transform: translateY(0) scale(.96);
  filter: blur(.25px);
}

.player-lyrics .lyric-line.lyric-previous,
.player-lyrics .lyric-line.lyric-next {
  color: #bbb3c1;
  opacity: .68;
  transform: translateY(0) scale(.98);
  filter: none;
}

.player-lyrics .lyric-line.lyric-arriving {
  color: #bbb3c1;
  opacity: .68;
  transform: translateY(0) scale(.98);
  filter: none;
}

.player-lyrics .lyric-line.active {
  transform: translateY(0) scale(1);
  filter: none;
}

.lyric-original,
.lyric-translation {
  display: block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.lyric-translation {
  margin-top: 3px;
  color: #93879e;
  font-size: .64em;
  font-style: italic;
  line-height: 1.15;
  text-shadow: none;
}

.player-lyrics .lyric-line.active .lyric-translation {
  color: #cfbadf;
  opacity: .92;
}

.lyrics .lyric-line {
  margin-bottom: .55em;
}

.lyrics .lyric-translation {
  white-space: normal;
  color: #9e93a6;
  font-size: .68em;
  line-height: 1.35;
}

.player-lyrics .lyric-gap {
  display: none;
  height: 0;
}

@media (max-width:600px) {
  .player-lyrics {
    --lyric-slot-height: 62px;
  }
  .lyrics-section-sticky {
    padding-right: 1px;
  }
}

.brand-logo {
  width: 174px;
  height: 48px;
  flex: none;
  overflow: hidden;
  padding: 0;
  border: 0;
  background: #0c0c10;
  border-radius: 11px;
  cursor: pointer;
}

.brand-logo img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

@media (max-width:600px) {
  .brand-logo {
    width: 138px;
    height: 42px;
  }
}

/* Song artwork: MusiCat can lean beyond the cover and perform on hover/playback. */

.track {
  overflow: visible;
}

.cover,
.library-cover,
.hero-cover {
  position: relative;
  isolation: isolate;
  overflow: visible;
  background: transparent;
  box-shadow: none;
}

.cover:before,
.library-cover:before,
.hero-cover:before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  background: radial-gradient(circle at 50% 35%,#423450,#17131e 72%);
  box-shadow: inset 0 0 0 1px #fff1,0 8px 24px #0004;
  opacity: 1;
  transform: scale(1);
  transition: opacity .72s ease,transform .82s cubic-bezier(.16,1,.3,1),box-shadow .72s ease;
}

.cover:is(:hover,
:focus-visible),
.track.is-playing .cover,
.library-cover:hover,
.hero-cover:is(:hover,
.is-playing) {
  z-index: 3;
}

.cover:is(:hover,
:focus-visible):before,
.track.is-playing .cover:before,
.library-cover:is(:hover,
:focus-visible):before,
.hero-cover:is(:hover,
.is-playing):before {
  opacity: .16;
  transform: scale(.86);
  box-shadow: inset 0 0 0 1px #fff0,0 4px 14px #0002;
}

.musicat-art {
  position: relative;
  z-index: 1;
  display: block;
  width: 98%;
  height: 98%;
  overflow: visible;
  opacity: 1;
  filter: drop-shadow(0 5px 7px #09060d88);
  transform-origin: 50% 72%;
  transition: transform .92s cubic-bezier(.16,1,.3,1),filter .78s ease,opacity .52s ease;
  will-change: transform;
}

.library-cover .musicat-art {
  width: 96%;
  height: 96%;
}

.hero-cover .musicat-art {
  width: 90%;
  height: 90%;
  filter: drop-shadow(0 16px 20px #09060daa);
}

.musicat-stage,
.musicat-character,
.musicat-note,
.musicat-ears,
.musicat-earcup,
.musicat-eye-open,
.musicat-eye-closed,
.musicat-pupils,
.musicat-mouth,
.musicat-mouth-open,
.musicat-mic,
.musicat-hand,
.musicat-effect {
  transform-box: fill-box;
  transform-origin: center;
}

.musicat-stage,
.musicat-character {
  will-change: transform;
}

.musicat-eye-open {
  animation: musicat-blink 5.1s ease-in-out infinite;
}

.musicat-pupils {
  animation: musicat-look 5.1s ease-in-out infinite;
}

.musicat-eye-closed,
.musicat-mouth-open,
.musicat-mic,
.musicat-hand,
.musicat-effect {
  opacity: 0;
}

.musicat-eye-open,
.musicat-eye-closed,
.musicat-mouth,
.musicat-hand,
.musicat-effect {
  transition: opacity .32s ease,transform .62s cubic-bezier(.22,1,.36,1);
}

.musicat-mouth-open {
  transition: opacity 70ms linear,transform 90ms ease-out;
}

.musicat-mic {
  transform: translate(7px,12px) rotate(8deg) scale(.72);
  transform-origin: 80px 78px;
  transition: opacity .28s ease,transform .46s cubic-bezier(.22,1,.36,1);
}

.musicat-hand-left {
  transform: translate(-5px,13px) scale(.68);
}

.musicat-hand-right {
  transform: translate(5px,13px) scale(.68);
}

:is(.track:not(.is-playing) .cover:is(:hover,
:focus-visible),
.library-cover:is(:hover,
:focus-visible),
.hero-cover:not(.is-playing):hover) .musicat-art {
  transform: translateY(-12px) scale(1.42);
  filter: drop-shadow(0 15px 13px #09060ddd) drop-shadow(0 0 12px #b68ee266);
}

:is(.track:not(.is-playing) .cover:is(:hover,
:focus-visible),
.library-cover:is(:hover,
:focus-visible),
.hero-cover:not(.is-playing):hover) .musicat-stage {
  animation: musicat-stage-curious 5.4s ease-in-out .92s infinite;
}

:is(.track:not(.is-playing) .cover:is(:hover,
:focus-visible),
.library-cover:is(:hover,
:focus-visible),
.hero-cover:not(.is-playing):hover) .musicat-character {
  animation: musicat-curious-head 5.4s ease-in-out infinite;
}

:is(.track:not(.is-playing) .cover:is(:hover,
:focus-visible),
.library-cover:is(:hover,
:focus-visible),
.hero-cover:not(.is-playing):hover) .musicat-pupils {
  animation: musicat-curious-look 5.4s ease-in-out infinite;
}

:is(.track:not(.is-playing) .cover:is(:hover,
:focus-visible),
.library-cover:is(:hover,
:focus-visible),
.hero-cover:not(.is-playing):hover) .musicat-eye-open {
  animation: musicat-curious-open-eyes 5.4s ease-in-out infinite;
}

:is(.track:not(.is-playing) .cover:is(:hover,
:focus-visible),
.library-cover:is(:hover,
:focus-visible),
.hero-cover:not(.is-playing):hover) .musicat-eye-closed {
  animation: musicat-curious-closed-eyes 5.4s ease-in-out infinite;
}

:is(.track:not(.is-playing) .cover:is(:hover,
:focus-visible),
.library-cover:is(:hover,
:focus-visible),
.hero-cover:not(.is-playing):hover) .musicat-ears {
  animation: musicat-curious-ears 5.4s ease-in-out infinite;
}

:is(.track:not(.is-playing) .cover:is(:hover,
:focus-visible),
.library-cover:is(:hover,
:focus-visible),
.hero-cover:not(.is-playing):hover) .musicat-note {
  animation: musicat-curious-note 5.4s ease-in-out infinite;
}

:is(.track:not(.is-playing) .cover:is(:hover,
:focus-visible),
.library-cover:is(:hover,
:focus-visible),
.hero-cover:not(.is-playing):hover) .musicat-hand-left {
  animation: musicat-curious-wave 5.4s ease-in-out infinite;
}

:is(.track:not(.is-playing) .cover:is(:hover,
:focus-visible),
.library-cover:is(:hover,
:focus-visible),
.hero-cover:not(.is-playing):hover) .musicat-hand-right {
  animation: musicat-curious-listen 5.4s ease-in-out infinite;
}

:is(.track:not(.is-playing) .cover:is(:hover,
:focus-visible),
.library-cover:is(:hover,
:focus-visible),
.hero-cover:not(.is-playing):hover) .musicat-effect {
  animation: musicat-curious-waveform 5.4s ease-in-out infinite;
}

.track.is-playing .cover .musicat-art,
.hero-cover.is-playing .musicat-art {
  transform: translateY(var(--musicat-lift,4px)) rotate(var(--musicat-lean,0deg)) scale(var(--musicat-scale,1.36));
  filter: drop-shadow(0 15px 13px #09060ddd) drop-shadow(0 0 var(--musicat-glow,10px) #b68ee288);
  transition: transform 90ms linear,filter .24s ease,opacity .52s ease;
}

.track.is-playing .musicat-character,
.hero-cover.is-playing .musicat-character {
  animation: musicat-enjoy var(--musicat-groove-cycle,2.8s) ease-in-out infinite;
}

.track.is-playing .musicat-host.is-singing .musicat-character,
.hero-cover.is-playing.is-singing .musicat-character {
  animation: musicat-singer-bow calc(var(--musicat-groove-cycle,2.8s)*1.35) cubic-bezier(.45,0,.25,1) infinite;
}

.track.is-playing .musicat-hand-left,
.hero-cover.is-playing .musicat-hand-left {
  opacity: 1;
  animation: musicat-conduct-left var(--musicat-beat-cycle,1.1s) ease-in-out infinite;
}

.track.is-playing .musicat-hand-right,
.hero-cover.is-playing .musicat-hand-right {
  opacity: 1;
  animation: musicat-conduct-right var(--musicat-beat-cycle,1.1s) ease-in-out var(--musicat-beat-delay,-.55s) infinite;
}

.track.is-playing .musicat-host.is-singing .musicat-mic,
.hero-cover.is-playing.is-singing .musicat-mic {
  opacity: 1;
  animation: musicat-mic-sing var(--musicat-beat-cycle,1.1s) ease-in-out infinite;
}

.track.is-playing .musicat-host.is-singing .musicat-hand-right,
.hero-cover.is-playing.is-singing .musicat-hand-right {
  animation: musicat-hold-mic var(--musicat-beat-cycle,1.1s) ease-in-out infinite;
}

.musicat-host.is-singing:not(.mouth-closed) .musicat-mouth {
  opacity: 0;
  transform: scaleX(.84) translateY(-1px);
}

.musicat-host.is-singing:not(.mouth-closed) .musicat-mouth-open {
  opacity: 1;
  transform: scale(var(--musicat-mouth-x,1),var(--musicat-mouth-y,1));
}

.musicat-host.is-singing.mouth-closed .musicat-mouth {
  opacity: 1;
  transform: scaleX(.92) translateY(-1px);
}

.musicat-host.is-singing.mouth-closed .musicat-mouth-open {
  opacity: 0;
  transform: scale(.55,.25);
}

.track.is-playing .musicat-eye-open,
.hero-cover.is-playing .musicat-eye-open {
  animation: musicat-play-open-eyes var(--musicat-groove-cycle,2.8s) ease-in-out infinite;
}

.track.is-playing .musicat-eye-closed,
.hero-cover.is-playing .musicat-eye-closed {
  animation: musicat-play-closed-eyes var(--musicat-groove-cycle,2.8s) ease-in-out infinite;
}

.track.is-playing .musicat-pupils,
.hero-cover.is-playing .musicat-pupils {
  animation: musicat-play-look var(--musicat-groove-cycle,2.8s) ease-in-out infinite;
}

.track.is-playing .musicat-ears,
.hero-cover.is-playing .musicat-ears {
  animation: musicat-ear-dance var(--musicat-beat-cycle,1.1s) ease-in-out infinite;
}

.track.is-playing .musicat-note,
.hero-cover.is-playing .musicat-note {
  animation: musicat-note-pulse var(--musicat-beat-cycle,1.1s) ease-in-out infinite;
}

.track.is-playing .musicat-earcup,
.hero-cover.is-playing .musicat-earcup {
  animation: musicat-earcup-glow var(--musicat-beat-cycle,1.1s) ease-in-out infinite;
}

.track.is-playing .musicat-effect,
.hero-cover.is-playing .musicat-effect {
  opacity: var(--musicat-wave-opacity,.7);
  animation: musicat-listening-wave var(--musicat-beat-cycle,1.1s) ease-in-out infinite;
}

.track.is-playing .musicat-effect-right,
.hero-cover.is-playing .musicat-effect-right {
  animation-delay: var(--musicat-beat-delay,-.55s);
}

.track.is-playing .musicat-host.peak-turn .musicat-stage,
.hero-cover.is-playing.peak-turn .musicat-stage {
  transform-style: preserve-3d;
  animation: musicat-peak-turn 1.85s cubic-bezier(.42,0,.18,1) both;
}

@keyframes musicat-blink {
  0%,
  40%,
  44%,
  100% {
    transform: scaleY(1);
  }
  42% {
    transform: scaleY(.08);
  }
}

@keyframes musicat-look {
  0%,
  22%,
  100% {
    transform: translateX(0);
  }
  28%,
  48% {
    transform: translateX(2px);
  }
  55%,
  76% {
    transform: translateX(-1px);
  }
}

@keyframes musicat-stage-curious {
  0%,
  100% {
    transform: none;
  }
  18% {
    transform: translateY(-2px) scale(1.025);
  }
  38% {
    transform: translateX(1.5px) rotate(1.5deg);
  }
  58% {
    transform: translateY(-3px) scale(1.015);
  }
  76% {
    transform: translateY(-1px) rotate(-1deg);
  }
  90% {
    transform: none;
  }
}

@keyframes musicat-curious-head {
  0%,
  16%,
  100% {
    transform: translateY(-1px) rotate(0);
  }
  30%,
  42% {
    transform: translateX(3px) rotate(4deg);
  }
  54%,
  62% {
    transform: translateY(-3px) rotate(0);
  }
  72% {
    transform: translateY(-1px) rotate(-2deg);
  }
  82% {
    transform: translateY(-3px) rotate(2deg);
  }
}

@keyframes musicat-curious-look {
  0%,
  18%,
  90%,
  100% {
    transform: translate(0,0);
  }
  28%,
  42% {
    transform: translateX(3.5px);
  }
  52%,
  60% {
    transform: translateY(-3px);
  }
  68%,
  82% {
    transform: translate(-1px,1px);
  }
}

@keyframes musicat-curious-open-eyes {
  0%,
  58%,
  88%,
  100% {
    opacity: 1;
    transform: scaleY(1);
  }
  64%,
  82% {
    opacity: 0;
    transform: scaleY(.15);
  }
}

@keyframes musicat-curious-closed-eyes {
  0%,
  58%,
  88%,
  100% {
    opacity: 0;
  }
  64%,
  82% {
    opacity: 1;
  }
}

@keyframes musicat-curious-ears {
  0%,
  44%,
  70%,
  100% {
    transform: translateY(0) scaleY(1);
  }
  52%,
  62% {
    transform: translateY(-2px) scaleY(1.08);
  }
  76% {
    transform: rotate(-1.5deg);
  }
}

@keyframes musicat-curious-note {
  0%,
  42%,
  100% {
    transform: translateY(0) rotate(0) scale(1);
  }
  50% {
    transform: translateY(-4px) rotate(-5deg) scale(1.08);
  }
  58% {
    transform: translateY(-1px) rotate(4deg) scale(1.03);
  }
}

@keyframes musicat-curious-wave {
  0%,
  4%,
  30%,
  100% {
    opacity: 0;
    transform: translate(-5px,13px) scale(.68);
  }
  9% {
    opacity: 1;
    transform: translate(-2px,1px) rotate(-16deg) scale(.96);
  }
  15% {
    opacity: 1;
    transform: translate(1px,-2px) rotate(13deg) scale(1);
  }
  23% {
    opacity: 1;
    transform: translate(-1px,1px) rotate(-9deg) scale(.96);
  }
}

@keyframes musicat-curious-listen {
  0%,
  58%,
  88%,
  100% {
    opacity: 0;
    transform: translate(5px,13px) scale(.68);
  }
  65%,
  82% {
    opacity: 1;
    transform: translate(-2px,-2px) rotate(-12deg) scale(.94);
  }
}

@keyframes musicat-curious-waveform {
  0%,
  61%,
  88%,
  100% {
    opacity: 0;
    transform: scale(.82);
  }
  68%,
  80% {
    opacity: 1;
    transform: scale(1.05);
  }
}

@keyframes musicat-stage-groove {
  0%,
  100% {
    transform: translate(-1px,-7px) rotate(-1deg) scale(1.22);
  }
  25% {
    transform: translate(0,-10px) rotate(1.5deg) scale(1.24);
  }
  50% {
    transform: translate(1px,-7px) rotate(1deg) scale(1.22);
  }
  75% {
    transform: translate(0,-10px) rotate(-1.5deg) scale(1.24);
  }
}

@keyframes musicat-enjoy {
  0%,
  100% {
    transform: translateX(-2px) rotate(-2deg);
  }
  25% {
    transform: translateY(-2px) rotate(1deg);
  }
  50% {
    transform: translateX(2px) rotate(2deg);
  }
  75% {
    transform: translateY(-2px) rotate(-1deg);
  }
}

@keyframes musicat-play-open-eyes {
  0%,
  12%,
  78%,
  100% {
    opacity: 1;
    transform: scaleY(1);
  }
  18%,
  70% {
    opacity: 0;
    transform: scaleY(.12);
  }
}

@keyframes musicat-play-closed-eyes {
  0%,
  12%,
  78%,
  100% {
    opacity: 0;
  }
  18%,
  70% {
    opacity: 1;
  }
}

@keyframes musicat-play-look {
  0%,
  78%,
  100% {
    transform: translateX(0);
  }
  84%,
  91% {
    transform: translateX(2px);
  }
}

@keyframes musicat-singer-bow {
  0%,
  16%,
  100% {
    transform: translate(0,0) rotate(0);
  }
  42%,
  64% {
    transform: translate(4px,5px) rotate(8deg);
  }
  54% {
    transform: translate(5px,7px) rotate(10deg);
  }
  82% {
    transform: translate(1px,1px) rotate(2deg);
  }
}

@keyframes musicat-mic-sing {
  0%,
  100% {
    transform: translate(0,0) rotate(-2deg) scale(1);
  }
  42%,
  64% {
    transform: translate(1px,1px) rotate(2deg) scale(var(--musicat-accent-scale,1));
  }
  54% {
    transform: translate(2px,2px) rotate(3deg) scale(var(--musicat-accent-scale,1));
  }
}

@keyframes musicat-conduct-left {
  0%,
  100% {
    opacity: .92;
    transform: translate(-1px,2px) rotate(-9deg) scale(.94);
  }
  50% {
    opacity: 1;
    transform: translate(2px,var(--musicat-paw-lift,-5px)) rotate(11deg) scale(var(--musicat-accent-scale,1));
  }
}

@keyframes musicat-conduct-right {
  0%,
  100% {
    opacity: .92;
    transform: translate(1px,2px) rotate(9deg) scale(.94);
  }
  50% {
    opacity: 1;
    transform: translate(-2px,var(--musicat-paw-lift,-5px)) rotate(-11deg) scale(var(--musicat-accent-scale,1));
  }
}

@keyframes musicat-hold-mic {
  0%,
  100% {
    opacity: 1;
    transform: translate(-6px,10px) rotate(-3deg) scale(.86);
  }
  42%,
  64% {
    opacity: 1;
    transform: translate(-6px,10px) rotate(1deg) scale(.9);
  }
  54% {
    opacity: 1;
    transform: translate(-6px,11px) rotate(2deg) scale(var(--musicat-accent-scale,.92));
  }
}

@keyframes musicat-ear-dance {
  0%,
  100% {
    transform: translateY(0) rotate(-1deg);
  }
  50% {
    transform: translateY(-2px) rotate(1deg);
  }
}

@keyframes musicat-note-pulse {
  0%,
  100% {
    transform: translateY(0) rotate(-3deg) scale(.98);
  }
  50% {
    transform: translateY(var(--musicat-note-lift,-4px)) rotate(3deg) scale(var(--musicat-note-scale,1.09));
  }
}

@keyframes musicat-earcup-glow {
  0%,
  100% {
    filter: drop-shadow(0 0 0 #b996ef00);
  }
  50% {
    filter: drop-shadow(0 0 var(--musicat-glow,4px) #c9a5ffdd);
  }
}

@keyframes musicat-listening-wave {
  0%,
  100% {
    opacity: .2;
    transform: scale(.88);
  }
  50% {
    opacity: var(--musicat-wave-opacity,.8);
    transform: scale(var(--musicat-wave-scale,1.05));
  }
}

@keyframes musicat-peak-turn {
  0% {
    transform: translateX(0) rotateY(0) scale(1);
  }
  12% {
    transform: translateX(-3px) rotateY(-28deg) scale(1.06);
  }
  45% {
    transform: translateX(-7px) rotateY(-175deg) scale(1.11);
  }
  72% {
    transform: translateX(-3px) rotateY(-292deg) scale(1.07);
  }
  88% {
    transform: translateX(1px) rotateY(-370deg) scale(1.025);
  }
  100% {
    transform: translateX(0) rotateY(-360deg) scale(1);
  }
}

@media (prefers-reduced-motion:reduce) {
  .musicat-art,
  .musicat-stage,
  .musicat-character,
  .musicat-note,
  .musicat-ears,
  .musicat-earcup,
  .musicat-eye-open,
  .musicat-eye-closed,
  .musicat-pupils,
  .musicat-mouth,
  .musicat-mouth-open,
  .musicat-mic,
  .musicat-hand,
  .musicat-effect {
    animation: none !important;
    transition: none !important;
  }
  .track.is-playing .cover .musicat-art,
  .hero-cover.is-playing .musicat-art {
    transform: translateY(4px) scale(1.36) !important;
  }
  .track.is-playing .musicat-host.is-singing .musicat-mic,
  .hero-cover.is-playing.is-singing .musicat-mic {
    opacity: 1;
    transform: none;
  }
}

/* During a vocal break, MusiCat moves from the sleeve into the lyrics stage. */

.lyrics-scene {
  position: relative;
  height: calc(var(--lyric-slot-height,64px)*5);
  overflow: hidden;
  border-radius: 16px;
  background: radial-gradient(circle at 50% 60%,#382a4855,transparent 52%);
}

.lyrics-scene .player-lyrics {
  height: 100%;
  transition: opacity .58s ease,transform .78s cubic-bezier(.22,1,.36,1),filter .58s ease;
}

.lyrics-instrumental-stage {
  position: absolute;
  z-index: 5;
  inset: 0;
  display: grid;
  place-items: center;
  overflow: hidden;
  pointer-events: none;
  opacity: 0;
  transform: scale(.82) translateY(18px);
  filter: blur(7px);
  transition: opacity .66s ease,transform .9s cubic-bezier(.16,1,.3,1),filter .66s ease;
}

.lyrics-instrumental-stage:before {
  content: "";
  position: absolute;
  inset: 12% 22%;
  border-radius: 50%;
  background: radial-gradient(circle,#c79cf02b,transparent 68%);
  filter: blur(8px);
  animation: musicat-dance-aura 2.2s ease-in-out infinite;
}

.lyrics-instrumental-label {
  position: absolute;
  right: 12px;
  bottom: 10px;
  color: #a890b8;
  font: 700 var(--fs-2xs)/1 Inter,system-ui,sans-serif;
  letter-spacing: .17em;
  text-transform: uppercase;
}

.musicat-dancer-art {
  position: relative;
  z-index: 1;
  display: block;
  width: min(218px,48%);
  height: 96%;
  overflow: visible;
  filter: drop-shadow(0 18px 18px #08050caa) drop-shadow(0 0 17px #a978d74a);
  transform-origin: 50% 88%;
}

.musicat-dancer-body,
.musicat-dancer-head,
.musicat-dancer-tail,
.musicat-dancer-arm,
.musicat-dancer-note,
.musicat-dancer-shadow {
  transform-box: fill-box;
  transform-origin: center;
}

.lyrics-panel.instrumental-dance .player-lyrics {
  opacity: 0;
  transform: scale(.96) translateY(-8px);
  filter: blur(4px);
}

.lyrics-panel.instrumental-dance .lyrics-instrumental-stage {
  opacity: 1;
  transform: scale(1) translateY(0);
  filter: none;
}

.lyrics-panel.instrumental-dance .musicat-dancer-art {
  animation: musicat-dancer-bounce var(--musicat-beat-cycle,1.1s) ease-in-out infinite;
}

.lyrics-panel.instrumental-dance .musicat-dancer-head {
  animation: musicat-dancer-head var(--musicat-groove-cycle,2.8s) ease-in-out infinite;
}

.lyrics-panel.instrumental-dance .musicat-dancer-tail {
  animation: musicat-dancer-tail var(--musicat-groove-cycle,2.8s) ease-in-out infinite;
}

.lyrics-panel.instrumental-dance .musicat-dancer-arm-left {
  animation: musicat-dancer-arm-left var(--musicat-beat-cycle,1.1s) ease-in-out infinite;
}

.lyrics-panel.instrumental-dance .musicat-dancer-arm-right {
  animation: musicat-dancer-arm-right var(--musicat-beat-cycle,1.1s) ease-in-out var(--musicat-beat-delay,-.55s) infinite;
}

.lyrics-panel.instrumental-dance .musicat-dancer-note {
  animation: musicat-dancer-note var(--musicat-groove-cycle,2.8s) ease-in-out infinite;
}

.lyrics-panel.instrumental-dance .musicat-dancer-shadow {
  animation: musicat-dancer-shadow var(--musicat-beat-cycle,1.1s) ease-in-out infinite;
}

.track.is-playing .cover.in-lyrics:before {
  opacity: 1;
  transform: scale(1);
  box-shadow: inset 0 0 0 1px #fff1,0 8px 24px #0004;
}

.track.is-playing .cover.in-lyrics .musicat-art {
  opacity: 0;
  transform: translateY(8px) rotate(0) scale(.12);
  filter: blur(5px);
  transition: opacity .42s ease,transform .72s cubic-bezier(.7,0,.84,0),filter .42s ease;
}

@keyframes musicat-dancer-bounce {
  0%,
  100% {
    transform: translateY(2px) rotate(-1deg) scale(1);
  }
  50% {
    transform: translateY(-8px) rotate(1deg) scale(1.025);
  }
}

@keyframes musicat-dancer-head {
  0%,
  100% {
    transform: rotate(-4deg) translateX(-1px);
  }
  50% {
    transform: rotate(5deg) translateX(2px);
  }
}

@keyframes musicat-dancer-tail {
  0%,
  100% {
    transform: rotate(-9deg);
  }
  50% {
    transform: rotate(16deg);
  }
}

@keyframes musicat-dancer-arm-left {
  0%,
  100% {
    transform: rotate(14deg) translateY(1px);
  }
  50% {
    transform: rotate(-24deg) translateY(-5px);
  }
}

@keyframes musicat-dancer-arm-right {
  0%,
  100% {
    transform: rotate(-14deg) translateY(1px);
  }
  50% {
    transform: rotate(24deg) translateY(-5px);
  }
}

@keyframes musicat-dancer-note {
  0%,
  100% {
    transform: translateY(2px) rotate(-5deg) scale(.96);
    opacity: .65;
  }
  50% {
    transform: translateY(-6px) rotate(5deg) scale(1.08);
    opacity: 1;
  }
}

@keyframes musicat-dancer-shadow {
  0%,
  100% {
    transform: scaleX(1);
    opacity: .34;
  }
  50% {
    transform: scaleX(.78);
    opacity: .2;
  }
}

@keyframes musicat-dance-aura {
  0%,
  100% {
    transform: scale(.9);
    opacity: .45;
  }
  50% {
    transform: scale(1.1);
    opacity: .82;
  }
}

@media (max-width:600px) {
  .lyrics-scene {
    height: calc(62px*5);
  }
  .musicat-dancer-art {
    width: min(190px,58%);
  }
}

@media (prefers-reduced-motion:reduce) {
  .lyrics-instrumental-stage,
  .lyrics-scene .player-lyrics,
  .track.is-playing .cover.in-lyrics .musicat-art {
    transition: none !important;
  }
  .musicat-dancer-art,
  .musicat-dancer-art *,
  .lyrics-instrumental-stage:before {
    animation: none !important;
  }
}

.suggestions-panel {
  max-width: 720px;
  margin: 0 auto 9px;
  min-width: 0;
}

.suggestions-panel[hidden] {
  display: none;
}

.suggestions-heading {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 0 4px 7px;
}

.suggestions-heading strong {
  font-size: var(--fs-xs);
  letter-spacing: .03em;
}

.suggestions-heading span {
  color: var(--muted);
  font-size: var(--fs-2xs);
}

.suggestion-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  padding: 1px 3px 7px;
  max-width: 100%;
}

.suggestion-chip {
  --chip-hue: 280;
  flex: none;
  max-width: 100%;
  border: 1px solid hsl(var(--chip-hue) 58% 55% / .55);
  background: linear-gradient(145deg,hsl(var(--chip-hue) 54% 24% / .92),hsl(calc(var(--chip-hue) + 22) 42% 13% / .96));
  color: hsl(var(--chip-hue) 75% 89%);
  box-shadow: inset 0 1px 0 #ffffff12,0 5px 15px hsl(var(--chip-hue) 70% 12% / .28);
  border-radius: 999px;
  padding: 8px 12px;
  cursor: pointer;
  font: 600 var(--fs-xs)/1.2 Inter,system-ui,sans-serif;
  white-space: normal;
  transition: border-color .2s,background .2s,transform .2s;
  animation: suggestion-in .32s cubic-bezier(.22,1,.36,1) both;
}

.suggestion-chip:hover {
  border-color: hsl(var(--chip-hue) 78% 72% / .9);
  filter: saturate(1.2) brightness(1.12);
  transform: translateY(-2px);
}

.suggestion-chip.step {
  background: linear-gradient(145deg,hsl(var(--chip-hue) 58% 27% / .94),hsl(calc(var(--chip-hue) + 28) 46% 14% / .98));
}

.suggestion-loading {
  color: var(--muted);
  font-size: var(--fs-xs);
  padding: 7px 5px;
}

.suggestion-question {
  margin: 1px 3px 8px;
  padding: 13px;
  border: 1px solid #4a3f55;
  border-radius: 16px;
  background: radial-gradient(circle at 4% 0,#30243c,#17151c 55%);
  box-shadow: 0 12px 30px #0005;
}

.suggestion-question[hidden] {
  display: none;
}

.suggestion-question-top {
  display: flex;
  align-items: start;
  gap: 10px;
}

.suggestion-question-copy {
  min-width: 0;
  flex: 1;
}

.suggestion-kicker {
  color: #c4a5e2;
  font-size: var(--fs-2xs);
  font-weight: 750;
  letter-spacing: .13em;
  text-transform: uppercase;
}

.suggestion-question label {
  display: block;
  margin-top: 4px;
  font-size: var(--fs-base);
  font-weight: 650;
  color: #f4eff8;
}

.suggestion-close {
  border: 0;
  background: transparent;
  color: #aaa;
  font-size: var(--fs-lg);
  line-height: 1;
  cursor: pointer;
  padding: 1px 3px;
}

.suggestion-answer-row {
  display: grid;
  grid-template-columns: minmax(0,1fr) auto;
  gap: 7px;
  margin-top: 10px;
}

.suggestion-answer {
  min-width: 0;
  border: 1px solid #403949;
  border-radius: 12px;
  background: #0f0f13;
  color: #fff;
  padding: 10px 11px;
  font: 14px/1.35 Inter,system-ui,sans-serif;
  outline: none;
}

.suggestion-answer:focus {
  border-color: #b997de;
  box-shadow: 0 0 0 3px #b997de22;
}

.suggestion-use {
  border: 0;
  border-radius: 12px;
  background: linear-gradient(135deg,#e7d3ff,#b993e7);
  color: #211629;
  padding: 0 15px;
  font-weight: 750;
  cursor: pointer;
}

.suggestion-use:disabled {
  opacity: .55;
  cursor: wait;
}

.suggestion-status {
  min-height: 16px;
  margin: 6px 2px 0;
  color: #d9a9aa;
  font-size: var(--fs-2xs);
}

.suggestion-status:empty {
  display: none;
}

.composer-selections {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 4px 8px 1px;
}

.composer-selections[hidden] {
  display: none;
}

.composer-selection {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 100%;
  border: 1px solid #715b86;
  border-radius: 999px;
  background: linear-gradient(145deg,#382b45,#241d2d);
  color: #eddef9;
  padding: 5px 7px 5px 10px;
  font: 650 var(--fs-2xs)/1.2 Inter,system-ui,sans-serif;
  animation: suggestion-in .34s cubic-bezier(.22,1,.36,1) both;
}

.composer-selection span:first-child {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.composer-selection .remove-selection {
  border: 0;
  background: #aa8ac433;
  color: #e9d9f8;
  width: 17px;
  height: 17px;
  margin: 0;
  border-radius: 50%;
  padding: 0;
  display: grid;
  place-items: center;
  font: 13px/1 Inter,system-ui,sans-serif;
  cursor: pointer;
}

.composer-selection .remove-selection:hover {
  background: #c4a5df55;
}

.composer.has-selections textarea {
  min-height: 40px;
  padding-top: 6px;
}

.composer.ready {
  border-color: #665277;
  box-shadow: 0 0 0 1px #b792dc22;
}

.composer-ready-note {
  display: none;
  color: #bea7d0;
  font-size: var(--fs-2xs);
  margin-right: auto;
}

.composer.ready .composer-ready-note {
  display: inline;
}

@keyframes suggestion-in {
  from {
    opacity: 0;
    transform: translateY(5px) scale(.96);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@media (max-width:600px) {
  .suggestions-heading {
    padding-left: 2px;
  }
  .suggestions-heading span {
    display: none;
  }
  .suggestion-answer-row {
    grid-template-columns: 1fr;
  }
  .suggestion-use {
    min-height: 39px;
  }
}

/* Account and settings */

.account-trigger {
  height: 36px;
  display: flex;
  align-items: center;
  gap: 8px;
  border: 1px solid #37343f;
  border-radius: 999px;
  background: #19181e;
  color: #eeeaf2;
  padding: 3px 10px 3px 4px;
  cursor: pointer;
  transition: border-color .2s,background .2s,transform .2s;
}

.account-trigger:hover {
  border-color: #68577b;
  background: #24212b;
  transform: translateY(-1px);
}

.account-avatar {
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: linear-gradient(145deg,#eedfff,#a67ed2);
  color: #24152f;
  font-size: var(--fs-2xs);
  font-weight: 850;
  letter-spacing: .02em;
  box-shadow: inset 0 1px 0 #fff8;
}

.account-label {
  max-width: 92px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--fs-2xs);
  font-weight: 700;
}

.account-backdrop {
  position: fixed;
  z-index: 50;
  inset: 0;
  background: #05040899;
  backdrop-filter: blur(4px);
  opacity: 0;
  pointer-events: none;
  transition: opacity .28s ease;
}

.account-backdrop.open {
  opacity: 1;
  pointer-events: auto;
}

.account-panel {
  position: fixed;
  z-index: 51;
  top: 12px;
  right: 12px;
  bottom: 12px;
  width: min(430px,calc(100vw - 24px));
  display: flex;
  flex-direction: column;
  border: 1px solid #41394c;
  border-radius: 25px;
  background: radial-gradient(circle at 85% 0,#39284c88,transparent 38%),linear-gradient(165deg,#1c1a22,#100f14 75%);
  box-shadow: 0 28px 90px #000c;
  transform: translateX(calc(100% + 28px));
  opacity: .6;
  transition: transform .42s cubic-bezier(.22,1,.36,1),opacity .28s;
  overflow: hidden;
}

.account-panel.open {
  transform: none;
  opacity: 1;
}

.account-panel-top {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 22px 22px 14px;
}

.account-panel-title {
  min-width: 0;
  flex: 1;
}

.account-kicker {
  color: #bfa4da;
  font-size: var(--fs-2xs);
  font-weight: 800;
  letter-spacing: .17em;
}

.account-panel h2 {
  margin: 4px 0 0;
  font-size: var(--fs-display);
  letter-spacing: -.04em;
}

.account-close {
  width: 34px;
  height: 34px;
  border: 1px solid #3b3741;
  border-radius: 50%;
  background: #211f26;
  color: #ddd;
  font-size: var(--fs-xl);
  cursor: pointer;
}

.account-tabs {
  display: flex;
  gap: 7px;
  padding: 0 22px 15px;
  border-bottom: 1px solid #302d36;
}

.account-tab {
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: #918b99;
  padding: 8px 12px;
  font: 700 var(--fs-2xs)/1 Inter,system-ui,sans-serif;
  cursor: pointer;
}

.account-tab.active {
  background: #eee7f4;
  color: #211725;
}

.account-content {
  overflow: auto;
  padding: 22px;
}

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

.profile-hero {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 22px;
}

.profile-avatar {
  width: 58px;
  height: 58px;
  display: grid;
  place-items: center;
  flex: none;
  overflow: hidden;
  border-radius: 19px;
  background: linear-gradient(145deg,#f3e8ff,#a177cf);
  color: #25162e;
  font-size: var(--fs-lg);
  font-weight: 900;
  box-shadow: 0 12px 28px #8d5eb33d;
}

.profile-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.profile-hero strong {
  display: block;
  font-size: var(--fs-lg);
}

.profile-hero span {
  display: block;
  margin-top: 4px;
  color: #96909d;
  font-size: var(--fs-xs);
  overflow-wrap: anywhere;
}

.verified-badge {
  display: inline-flex !important;
  width: max-content;
  align-items: center;
  gap: 5px;
  margin-top: 7px !important;
  border: 1px solid #38614e;
  border-radius: 999px;
  padding: 4px 7px;
  color: #a8e1c1 !important;
  background: #183126;
  font-size: var(--fs-2xs) !important;
  font-weight: 800;
}

.account-form {
  display: grid;
  gap: 13px;
}

.account-field {
  display: grid;
  gap: 6px;
}

.account-field span {
  color: #aaa3b0;
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: .04em;
}

.account-field input,
.account-field select {
  width: 100%;
  border: 1px solid #3a3541;
  border-radius: 12px;
  background: #111015;
  color: #fff;
  padding: 12px 13px;
  font: 14px/1.2 Inter,system-ui,sans-serif;
  outline: none;
}

.account-field input:focus,
.account-field select:focus {
  border-color: #b491d6;
  box-shadow: 0 0 0 3px #a77bce26;
}

.account-primary,
.account-secondary,
.account-danger,
.google-signin {
  height: 42px;
  border-radius: 13px;
  font-weight: 800;
  cursor: pointer;
}

.account-primary {
  border: 0;
  background: linear-gradient(135deg,#f0ddff,#b78dde);
  color: #24162c;
}

.account-secondary {
  border: 1px solid #45404c;
  background: #211f26;
  color: #eee;
}

.account-danger {
  border: 1px solid #713044;
  background: #32141d;
  color: #ffc8d5;
}

.google-signin {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  border: 1px solid #d8d4dc;
  background: #f8f7fa;
  color: #242126;
}

.google-signin:disabled,
.account-primary:disabled {
  opacity: .5;
  cursor: wait;
}

.google-mark {
  display: grid;
  place-items: center;
  width: 23px;
  height: 23px;
  border-radius: 50%;
  background: conic-gradient(from -45deg,#4285f4 0 25%,#34a853 0 50%,#fbbc05 0 75%,#ea4335 0);
  color: #fff;
  font: 900 var(--fs-sm)/1 Inter,sans-serif;
}

.auth-divider {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 13px 0;
  color: #746e7a;
  font-size: var(--fs-2xs);
  font-weight: 800;
  letter-spacing: .12em;
}

.auth-divider:before,
.auth-divider:after {
  content: "";
  height: 1px;
  flex: 1;
  background: #332f38;
}

.email-auth {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}

.email-auth .account-field {
  grid-column: 1/-1;
}

.email-auth button {
  width: 100%;
}

.auth-status {
  min-height: 18px;
  margin-top: 10px;
  color: #c5a7de;
  font-size: var(--fs-2xs);
  line-height: 1.45;
}

.auth-status.error {
  color: #e9a2b1;
}

.auth-status.success {
  color: #93dbb4;
}

.account-note {
  margin: 10px 1px 0;
  color: #827c89;
  font-size: var(--fs-2xs);
  line-height: 1.5;
}

.profile-actions {
  display: grid;
  gap: 9px;
}

.dna-card {
  margin: 0 0 17px;
  padding: 15px;
  border: 1px solid #463951;
  border-radius: 17px;
  background: radial-gradient(circle at 0 0,#392744,#17151d 65%);
}

.dna-card-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.dna-card strong {
  font-size: var(--fs-sm);
}

.dna-card small {
  color: #a991bc;
}

.dna-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 10px;
}

.dna-tag {
  border: 1px solid #5e4a6c;
  border-radius: 999px;
  background: #2c2234;
  color: #dfcbed;
  padding: 5px 8px;
  font-size: var(--fs-2xs);
}

.onboarding-progress {
  display: grid;
  grid-template-columns: repeat(4,1fr);
  gap: 5px;
  margin-bottom: 20px;
}

.onboarding-progress i {
  height: 4px;
  border-radius: 99px;
  background: #302b36;
}

.onboarding-progress i.done {
  background: linear-gradient(90deg,#a97bd4,#8ee0ba);
}

.wizard-kicker {
  color: #b99ad3;
  font-size: var(--fs-2xs);
  font-weight: 850;
  letter-spacing: .15em;
}

.wizard-title {
  margin: 6px 0 7px;
  font-size: var(--fs-xl);
  line-height: 1.08;
  letter-spacing: -.035em;
}

.wizard-copy {
  margin: 0 0 17px;
  color: #98919f;
  font-size: var(--fs-xs);
  line-height: 1.5;
}

.wizard-fields {
  display: grid;
  gap: 12px;
}

.wizard-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
}

.wizard-chip {
  border: 1px solid #403a48;
  border-radius: 999px;
  background: #1b1920;
  color: #bab3c0;
  padding: 8px 11px;
  font: 700 var(--fs-2xs)/1 Inter,system-ui,sans-serif;
  cursor: pointer;
  transition: .18s;
}

.wizard-chip:hover {
  border-color: #735c87;
  color: #eee;
}

.wizard-chip.selected {
  border-color: #bd92df;
  background: linear-gradient(145deg,#5b3e73,#30203f);
  color: #fff;
  box-shadow: 0 4px 14px #9b63c329;
}

.wizard-section-label {
  display: block;
  margin: 15px 0 8px;
  color: #bdb4c3;
  font-size: var(--fs-2xs);
  font-weight: 800;
  letter-spacing: .05em;
}

.wizard-choices {
  display: grid;
  gap: 8px;
}

.wizard-choice {
  display: grid;
  grid-template-columns: auto minmax(0,1fr);
  gap: 10px;
  align-items: center;
  padding: 12px;
  border: 1px solid #38333f;
  border-radius: 14px;
  background: #17151c;
  cursor: pointer;
}

.wizard-choice:has(input:checked) {
  border-color: #a77acf;
  background: #281d31;
}

.wizard-choice input {
  accent-color: #af80d8;
}

.wizard-choice strong {
  display: block;
  font-size: var(--fs-xs);
}

.wizard-choice span {
  display: block;
  margin-top: 3px;
  color: #8f8797;
  font-size: var(--fs-2xs);
}

.wizard-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 9px;
  margin-top: 22px;
}

.wizard-actions button {
  min-width: 104px;
}

.wizard-error {
  min-height: 17px;
  margin-top: 9px;
  color: #e5a1b0;
  font-size: var(--fs-2xs);
}

.settings-list {
  display: grid;
  gap: 10px;
}

.setting-row {
  display: grid;
  grid-template-columns: minmax(0,1fr) auto;
  align-items: center;
  gap: 18px;
  padding: 15px;
  border: 1px solid #302d37;
  border-radius: 16px;
  background: #17161c;
}

.setting-copy strong {
  display: block;
  font-size: var(--fs-sm);
}

.setting-copy span {
  display: block;
  margin-top: 4px;
  color: #8f8997;
  font-size: var(--fs-2xs);
  line-height: 1.4;
}

.setting-toggle {
  position: relative;
  width: 43px;
  height: 25px;
}

.setting-toggle input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.setting-toggle i {
  position: absolute;
  inset: 0;
  border-radius: 99px;
  background: #35313b;
  cursor: pointer;
  transition: background .22s;
}

.setting-toggle i:after {
  content: "";
  position: absolute;
  left: 3px;
  top: 3px;
  width: 19px;
  height: 19px;
  border-radius: 50%;
  background: #ddd8e1;
  transition: transform .22s,background .22s;
}

.setting-toggle input:checked+i {
  background: #8f65b8;
}

.setting-toggle input:checked+i:after {
  transform: translateX(18px);
  background: #fff;
}

.setting-toggle input:focus-visible+i {
  outline: 2px solid #d2b3ef;
  outline-offset: 3px;
}

.settings-saved {
  min-height: 18px;
  margin-top: 13px;
  color: #ac93c2;
  font-size: var(--fs-2xs);
}

.hide-translations .lyric-translation {
  display: none !important;
}

body.reduce-character-motion .musicat-art,
body.reduce-character-motion .musicat-art *,
body.reduce-character-motion .musicat-dancer-art,
body.reduce-character-motion .musicat-dancer-art *,
body.reduce-character-motion .music-buddy,
body.reduce-character-motion .thought {
  animation: none !important;
  transition: none !important;
}

@media (max-width:720px) {
  .account-label {
    display: none;
  }
  .account-trigger {
    padding-right: 4px;
  }
  .provider {
    display: none;
  }
}

@media (max-width:600px) {
  header {
    gap: 7px;
  }
  .brand-logo {
    width: 112px;
  }
  .nav {
    gap: 4px;
  }
  .nav button {
    padding: 7px 8px;
  }
  .account-panel {
    top: 7px;
    right: 7px;
    bottom: 7px;
    width: calc(100vw - 14px);
    border-radius: 21px;
  }
  .account-content {
    padding: 18px;
  }
  .account-panel-top {
    padding: 19px 18px 13px;
  }
  .account-tabs {
    padding-left: 18px;
    padding-right: 18px;
  }
}

.genre-families {
  display: grid;
  grid-template-columns: repeat(3,minmax(0,1fr));
  gap: 7px;
}

.genre-family {
  --genre: #b78dde;
  min-width: 0;
  border: 1px solid color-mix(in srgb,var(--genre) 45%,#343039);
  border-radius: 12px;
  background: color-mix(in srgb,var(--genre) 11%,#17151c);
  color: #ded9e2;
  padding: 9px 5px;
  font: 800 var(--fs-2xs)/1 Inter,system-ui,sans-serif;
  cursor: pointer;
  transition: .2s;
}

.genre-family:hover,
.genre-family.active {
  border-color: var(--genre);
  background: color-mix(in srgb,var(--genre) 25%,#17151c);
  color: #fff;
  transform: translateY(-1px);
}

.genre-family-count {
  display: inline-grid;
  place-items: center;
  min-width: 15px;
  height: 15px;
  margin-left: 4px;
  border-radius: 99px;
  background: var(--genre);
  color: #161319;
  font-size: var(--fs-2xs);
}

.subgenre-panel {
  margin-top: 10px;
  padding: 12px;
  border: 1px solid color-mix(in srgb,var(--genre) 42%,#38333f);
  border-radius: 15px;
  background: color-mix(in srgb,var(--genre) 8%,#121116);
}

.subgenre-panel .wizard-chip {
  border-color: color-mix(in srgb,var(--genre) 40%,#403a48);
}

.subgenre-panel .wizard-chip.selected {
  border-color: var(--genre);
  background: color-mix(in srgb,var(--genre) 44%,#201924);
  box-shadow: 0 4px 14px color-mix(in srgb,var(--genre) 28%,transparent);
}

.wizard-count {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin: 0 0 12px;
  color: #aaa2b1;
  font-size: var(--fs-2xs);
}

.wizard-count strong {
  color: #eee;
  font-size: var(--fs-xs);
}

.wizard-actions button:disabled {
  opacity: .35;
  cursor: not-allowed;
  filter: saturate(.4);
}

.global-player {
  position: relative;
  z-index: 12;
  min-height: 0;
  padding: 9px 14px 8px;
  border-bottom: 1px solid #292630;
  background: linear-gradient(180deg,#0e0d12f5,#0b0b0df0);
  backdrop-filter: blur(18px);
}

.global-player[hidden],
.global-player [hidden] {
  display: none !important;
}

.global-full {
  max-width: 748px;
  max-height: calc(100dvh - 70px);
  overflow: auto;
  margin: auto;
  padding: 0 2px 8px;
  scrollbar-width: thin;
}

.global-full-card {
  margin: 0;
  box-shadow: 0 15px 42px #0008;
  border-color: #4b4057;
}

.track.global-full-card .player-lyrics {
  height: min(300px,36vh);
}

.global-full-card .track-top {
  padding-right: 0;
}

.global-full-card .transport {
  grid-template-columns: minmax(172px,1fr) auto auto;
  gap: 11px;
}

.audio-visuals {
  height: 48px;
  display: flex;
  align-items: stretch;
  gap: 10px;
  padding: 3px 0;
}

.stereo-meter.vertical {
  width: 34px;
  min-width: 34px;
  height: 42px;
  display: grid;
  grid-template-columns: repeat(2,1fr);
  gap: 4px;
  color: #8d8794;
  font: 7px/1 ui-monospace,monospace;
}

.stereo-meter.vertical .meter-channel {
  display: grid;
  grid-template-rows: 1fr 8px;
  gap: 2px;
  text-align: center;
}

.stereo-meter.vertical .meter-track {
  position: relative;
  width: 10px;
  height: 31px;
  margin: auto;
  border-radius: 4px;
  background: repeating-linear-gradient(0deg,#2d2b32 0 3px,transparent 3px 5px);
}

.stereo-meter.vertical .meter-level {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  width: 100%;
  height: var(--level,2%);
  background: repeating-linear-gradient(0deg,#8ee0ba 0 3px,transparent 3px 5px);
  transition: height 70ms linear;
}

.stereo-meter.vertical .meter-track.right .meter-level {
  background: repeating-linear-gradient(0deg,#d5b3fb 0 3px,transparent 3px 5px);
}

.graphic-eq {
  height: 42px;
  width: 72px;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 3px;
  padding: 4px 5px;
  border-left: 1px solid #302d36;
}

.graphic-eq i {
  width: 5px;
  height: var(--eq-height);
  border-radius: 4px;
  background: linear-gradient(#8ee0ba,#c696ef);
  opacity: .65;
  transform-origin: bottom;
}

.track.is-playing .graphic-eq i {
  animation: eq-dance var(--eq-speed,.72s) ease-in-out infinite alternate;
  animation-delay: var(--eq-delay,0s);
}

@keyframes eq-dance {
  to {
    height: calc(var(--eq-height)*.42);
    filter: brightness(1.3);
  }
}

.player-actions {
  display: flex;
  align-items: center;
  gap: 3px;
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid #2e2b34;
}

.player-stats {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-right: auto;
  color: #8f8997;
  font: 650 var(--fs-2xs)/1 Inter,system-ui,sans-serif;
  white-space: nowrap;
}

.player-stat {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.player-stat svg {
  width: 13px;
  height: 13px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
}

.player-action {
  width: 31px;
  height: 31px;
  flex: none;
  border: 0;
  border-radius: 9px;
  background: transparent;
  color: #aaa2b4;
  padding: 0;
  display: grid;
  place-items: center;
  cursor: pointer;
  transition: color .2s,transform .2s,background .2s;
}

.player-action:hover {
  color: #f4eaff;
  transform: translateY(-1px);
  background: #ffffff0a;
}

.player-action span {
  display: none;
}

.player-action svg {
  width: 17px;
  height: 17px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.player-action.remix {
  color: #b78cff;
}

.player-action.like.liked,
.player-action.dislike.disliked {
  color: #a982f1 !important;
}

.player-action.like.liked svg,
.player-action.dislike.disliked svg {
  fill: none !important;
  stroke: currentColor !important;
  filter: drop-shadow(0 0 4px #a982f177) !important;
  transform: none !important;
}

.player-action.saved.saved {
  color: #ff648d;
}

.player-action.saved.saved svg {
  fill: #e51854;
  stroke: #ff759a;
  filter: drop-shadow(0 0 5px #ed1e5b88);
}

.player-action.active {
  color: #d2aff8;
  background: #c79cf018;
}

.player-action:disabled {
  opacity: .35;
  cursor: not-allowed;
  transform: none;
}

/* The chat card and the player each have a dedicated Download button in their action
   row, so the menu's WAV item would be a duplicate there. A library card has no action
   row, so it is that card's only way to reach the WAV and must stay visible. */

.chat-card .commands-panel .download-wav,
.global-full-card .commands-panel .download-wav {
  display: none;
}

.chat-card .player-actions {
  display: none !important;
}

@media (max-width:680px) {
  .global-player {
    padding: 7px 9px;
  }
  .global-full {
    max-height: calc(100dvh - 64px);
  }
  .global-full-card {
    padding: 11px;
  }
  .global-full-card .transport {
    grid-template-columns: minmax(112px,1fr) auto auto;
    gap: 6px;
  }
  .global-full-card .icon-btn {
    width: 32px;
    height: 32px;
  }
  .graphic-eq {
    width: 55px;
    gap: 2px;
  }
  .player-actions {
    grid-template-columns: repeat(8,55px);
  }
}

/* Local player layout correction */

.global-player {
  max-height: min(48dvh,480px);
  overflow: hidden;
  transition: max-height .78s cubic-bezier(.22,1,.36,1),padding .62s cubic-bezier(.22,1,.36,1);
}

.global-full {
  max-height: min(48dvh,480px);
  transition: max-height .78s cubic-bezier(.22,1,.36,1);
}

.global-player.tray-open {
  max-height: calc(100dvh - 60px);
  overflow-y: auto;
}

.global-player.tray-open .global-full {
  max-height: none;
  overflow: visible;
}

/* Retired: the chips are how a song is started, so they stay available whatever the player's trays are doing. Restore by reinstating this rule. */

/* .global-player.tray-open~.composer-wrap .suggestions-panel{display:none} */

.composer-wrap {
  position: relative;
  z-index: 90;
  max-height: min(34dvh,270px);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.global-full-card .track-top {
  min-height: 58px;
}

.track-top>.audio-visuals {
  height: 54px;
  flex: none;
  align-self: center;
}

/* The song card's right-hand module. A vertical rule divides the header: the
   title, chips and the "..." sit on its left, the artwork and meters on its
   right. Idle it shows the Music Director's clapperboard, the same art as the
   producer card above it; playing, that gives way to the equalizer, which is
   the real one — the analyser in playback() writes --eq-level onto these bars
   from the audio's own frequency data, seven bands, so it moves with the song
   rather than animating on a timer. */

/* Level with the title and subtitle, not with the card: the text block is the
   tallest thing in the header now, so centring on the whole row floated the
   button below the words it belongs to. The top inset matches .track-open's. */

.chat-card.has-card-menu .track-top>.card-menu {
  align-self: flex-start;
  margin-top: 3px;
}

.chat-card .track-top>.card-aside {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  flex: none;
  align-self: stretch;
  padding-left: 14px;
  border-left: 1px solid #302d36;
}

/* The player is a clone of a chat card, so it carries this module too — and the
   equalizer and the stereo meter it needs live INSIDE it. Hiding the whole aside
   took the player's own visuals with it; display:contents dissolved it, which
   dropped the meter and the equalizer into the player's column as separate rows,
   stacked down the left edge instead of standing side by side. It stays a row of
   its own here, holding the two the way .audio-visuals used to, with only the
   card's parts removed: the dividing rule, and the artwork the player has no room
   for. One more class than the card's selector, so this wins wherever it sits. */

.track.global-full-card>.track-top>.card-aside {
  display: flex;
  align-items: center;
  gap: 10px;
  border-left: 0;
  padding-left: 0;
}

.track.global-full-card>.track-top>.card-aside>.card-aside-art {
  display: none;
}

/* The two states occupy the same slot, so the header does not resize when a
   song starts: one is shown, the other is not. */

/* The same 72x42 box the equalizer occupies, so the header keeps its height
   when the art gives way to the bars. */

.chat-card .card-aside-art {
  display: grid;
  place-items: center;
  width: 72px;
  height: 42px;
}

/* The song's disc: the Director's ring and core, still. Its own elements and its
   own class, so restyling the Director's scene — which animates, because a song
   is being made there — cannot reach a finished song's card. */

.chat-card .card-aside-art .song-disc {
  position: relative;
  width: 42px;
  height: 42px;
}

.song-disc-ring {
  position: absolute;
  inset: 0;
  border: 1px solid #554965;
  border-radius: 50%;
}

/* The two lights the Director's ring carries, held still at opposite sides. */

.song-disc-ring:before,
.song-disc-ring:after {
  content: "";
  position: absolute;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: #9ee9c2;
  box-shadow: 0 0 7px #7ce2b4;
}

.song-disc-ring:before {
  left: -3px;
  top: 15px;
}

.song-disc-ring:after {
  right: -3px;
  top: 21px;
  background: #d4a9f7;
  box-shadow: 0 0 7px #c295ef;
}

/* The hub, centred in the ring: a record's label. */

.song-disc-core {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 14px;
  height: 14px;
  margin: -7px 0 0 -7px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%,#fff,#c6a0eb 38%,#6e4f8c 72%);
  box-shadow: 0 0 12px #c09ae788;
}

.chat-card .card-aside>.graphic-eq,
.chat-card .card-aside>.stereo-meter {
  display: none;
}

.chat-card.is-playing .card-aside-art {
  display: none;
}

/* No height here: .graphic-eq's own 42px is what the bars' percentage heights
   resolve against, and overriding it with a taller box left them computing
   against the wrong basis and flattening to 3px whatever the level said. */

.chat-card.is-playing .card-aside>.graphic-eq {
  display: flex;
  border-left: 0;
}

.global-full-card .transport {
  grid-template-columns: minmax(172px,1fr) auto;
}

.graphic-eq i {
  height: var(--eq-level,8%);
  animation: none !important;
  border-radius: 3px 3px 1px 1px;
  background: repeating-linear-gradient(0deg,#8ee0ba 0 3px,transparent 3px 5px);
  filter: drop-shadow(0 0 4px #7bdcaf44);
  transition: height 70ms linear,filter 70ms linear;
}

.track.is-playing .graphic-eq i {
  filter: brightness(1.25);
}

.track-open .muted,
.library-meta {
  display: block;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.chat-card {
  position: relative;
  overflow: visible;
  padding-bottom: 24px;
}

.song-card-footer {
  position: absolute;
  left: 14px;
  right: 14px;
  bottom: 6px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  pointer-events: none;
}

.song-reaction-control {
  position: relative;
  display: flex;
  align-items: center;
  gap: 5px;
  transform: translateY(18px);
  pointer-events: auto;
}

.song-reaction-selected,
.song-reaction-trigger {
  height: 29px;
  min-width: 29px;
  display: grid;
  place-items: center;
  border: 1px solid #3a3740;
  border-radius: 999px;
  background: #1b1a20;
  color: #aaa4af;
  box-shadow: 0 4px 13px #0007;
  cursor: pointer;
}

.song-reaction-selected {
  padding: 0 8px;
  font-size: var(--fs-base);
  color: #fff;
}

.song-reaction-selected[hidden] {
  display: none;
}

.song-reaction-trigger {
  font-size: var(--fs-base);
  filter: grayscale(1);
  transition: transform .18s,border-color .18s;
}

.song-reaction-trigger:hover {
  transform: scale(1.06);
  border-color: #706677;
}

.song-reaction-palette {
  position: absolute;
  left: 0;
  bottom: 37px;
  display: flex;
  align-items: center;
  gap: 3px;
  padding: 6px;
  border: 1px solid #45414b;
  border-radius: 999px;
  background: #ece9e3;
  color: #222;
  box-shadow: 0 9px 28px #0009;
  opacity: 0;
  visibility: hidden;
  transform: translateY(5px) scale(.96);
  transform-origin: left bottom;
  transition: opacity .16s,transform .16s,visibility .16s;
  z-index: 20;
}

.song-reaction-control.open .song-reaction-palette {
  opacity: 1;
  visibility: visible;
  transform: none;
}

.song-reaction {
  width: 31px;
  height: 31px;
  border: 0;
  border-radius: 50%;
  background: transparent;
  font-size: var(--fs-lg);
  filter: grayscale(1);
  opacity: .72;
  cursor: pointer;
  transition: transform .16s,filter .16s,opacity .16s;
}

.song-reaction:hover,
.song-reaction.selected {
  transform: scale(1.18);
  filter: none;
  opacity: 1;
}

.song-created {
  margin-left: auto;
  color: #7f7986;
  font-size: var(--fs-2xs);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  pointer-events: auto;
}

.global-full-card .song-card-footer {
  display: none;
}

@media (max-width:680px) {
  .global-player,
  .global-full {
    max-height: min(44dvh,390px);
  }
  .composer-wrap {
    max-height: min(32dvh,230px);
  }
  .global-full-card .transport {
    grid-template-columns: minmax(100px,1fr) auto;
  }
  .track-top>.audio-visuals {
    gap: 5px;
  }
  .graphic-eq {
    width: 50px;
  }
  .stereo-meter.vertical {
    width: 29px;
    min-width: 29px;
  }
}

.composer-wrap {
  position: relative !important;
  left: auto;
  bottom: auto;
  transform: none;
  width: 100%;
  max-height: min(36dvh,300px);
  overflow-y: auto;
  z-index: 90;
}

.composer {
  box-shadow: 0 -12px 28px #0b0b0dcc;
}

main {
  padding-bottom: 20px;
}

@media (max-width:680px) {
  main {
    padding-bottom: 16px;
  }
  .composer-wrap {
    max-height: min(38dvh,270px);
  }
}

.app-header {
  position: fixed;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: min(100%,1000px);
  height: 60px;
  z-index: 100;
}

.app-header {
  grid-row: 1;
}

.shell>.global-player {
  grid-row: 2;
}

.shell>main {
  grid-row: 3;
}

.shell>.composer-wrap {
  grid-row: 4;
}

/* ---------------------------------------------------------------------------
   The player page's back control, in the header before the logo.

   It lives in <header> rather than inside the player so it reads as page
   navigation, alongside Chat and Library, instead of as a player control.
   `hidden` is honoured normally here: the attribute is toggled by
   openPlayerPage()/closePlayerPage(). ------------------------------------- */

.player-back {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: none;
  height: 36px;
  padding: 0 12px 0 9px;
  margin-right: 4px;
  border: 1px solid #37343f;
  border-radius: 999px;
  background: #19181e;
  color: #eeeaf2;
  cursor: pointer;
  font: 700 var(--fs-xs)/1 Inter,system-ui,sans-serif;
  transition: border-color .2s,background .2s,transform .2s;
}

.player-back:hover {
  border-color: #68577b;
  background: #24212b;
  transform: translateY(-1px);
}

.player-back svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

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

/* On the phone player page the header carries Back and the logo only: the nav
   and the account button belong to the views behind it, and the page is left
   with Back. Phone only — the desktop header is unchanged. */

@media (max-width:1000px) {
  body.player-page .app-header .nav,
  body.player-page .app-header .provider,
  body.player-page .app-header .account-trigger {
    display: none;
  }
}

/* ---------------------------------------------------------------------------
   The phone player page.

   Opening a song takes over the screen: the player, and nothing else. The feed
   and the composer are hidden rather than scrolled past, so the page is the
   player instead of the chat page with a player on top of it.

   These selectors match `.shell>main{grid-row:3}` above on specificity (0,2,0)
   — a bare `body.player-page main` would tie and lose on source order, which is
   why the rows are re-stated here rather than just `display:none`.

   Desktop is excluded: `openPlayerPage()` returns early above 1000px, and the
   two rules inside that media query already handle it. --------------------- */

/* ---------------------------------------------------------------------------
   The sticky player is retired.

   It was the in-chat strip: once a song played, the player stayed docked in the
   conversation on every view. The player page replaces it, so on a phone the
   player is shown only on that page.

   Retired, not deleted — `globalPlayer.hidden` is still managed exactly as
   before, and the markup and behaviour are untouched. Add the class
   `legacy-chat-player` to <body> to bring the strip back. --------------------- */

@media (max-width:1000px) {
  body:not(.player-page):not(.legacy-chat-player) .shell>.global-player {
    display: none;
  }
}

@media (max-width:1000px) {
  body.player-page .shell>main,
  body.player-page .shell>.composer-wrap {
    display: none;
  }
  /* The player takes the rows the feed and composer gave up, so it fills the
     screen under the fixed 60px header rather than sitting in a 277px band. */
  body.player-page .shell>.global-player {
    grid-row: 2/5;
    max-height: none;
    min-height: 0;
    overflow-y: auto;
    justify-content: flex-start;
    padding-bottom: max(16px,env(safe-area-inset-bottom));
  }
  /* The card is capped at 44dvh for the in-chat strip; on its own page there
     is nothing to share the screen with. */
  body.player-page .global-full {
    max-height: none;
  }
}

.composer-wrap {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: 8px 12px max(10px,env(safe-area-inset-bottom));
}

.composer-wrap .composer {
  position: relative;
  left: auto;
  bottom: auto;
  transform: none;
  width: 100%;
  flex: none;
  z-index: 101;
}

.global-full-card {
  transition: max-height .78s cubic-bezier(.22,1,.36,1),padding .78s cubic-bezier(.22,1,.36,1);
}

.global-full-card .track-top {
  transition: min-height .78s cubic-bezier(.22,1,.36,1),row-gap .78s cubic-bezier(.22,1,.36,1);
}

.global-full-card .cover {
  transition: width .78s cubic-bezier(.22,1,.36,1),height .78s cubic-bezier(.22,1,.36,1),border-radius .78s cubic-bezier(.22,1,.36,1),background .78s ease,box-shadow .78s ease,transform .78s cubic-bezier(.22,1,.36,1);
}

.global-full-card .cover .musicat-art {
  transition: width .78s cubic-bezier(.22,1,.36,1),height .78s cubic-bezier(.22,1,.36,1),transform .78s cubic-bezier(.22,1,.36,1);
}

.global-full-card .track-open,
.global-full-card .audio-visuals {
  transition: opacity .42s ease,transform .78s cubic-bezier(.22,1,.36,1);
}

.global-full-card.performance-mode .track-top {
  display: grid;
  grid-template-columns: minmax(0,1fr) auto;
  grid-template-areas: "stage stage" "copy visuals";
}

.global-full-card/* Note rain — the backdrop MusiCat performs against. Only in performance mode,
only while playing: a static card with notes falling behind it would read as decoration rather than as performance. Pointer-events none throughout,
since the cover itself is the play button. */ .note-rain {
  position: absolute;
  inset: -14% -8% 0;
  z-index: 0;
  overflow: hidden;
  border-radius: 22px;
  opacity: 0;
  transition: opacity .9s ease;
  pointer-events: none;
}

.performance-mode .cover .note-rain {
  opacity: 1;
}

.note-rain .note {
  position: absolute;
  top: -16%;
  font-size: var(--size,17px);
  line-height: 1;
  color: var(--tint,#c9a7ef);
  opacity: 0;
  will-change: transform,opacity;
  filter: drop-shadow(0 0 7px currentColor);
  animation: note-fall var(--fall,6.5s) linear var(--delay,0s) infinite;
}

@keyframes note-fall {
  0% {
    transform: translate3d(0,-10%,0) rotate(var(--spin-from,-18deg)) scale(.82);
    opacity: 0;
  }
  12% {
    opacity: var(--peak,.72);
  }
  84% {
    opacity: var(--peak,.72);
  }
  100% {
    transform: translate3d(var(--drift,12px),340%,0) rotate(var(--spin-to,22deg)) scale(1.04);
    opacity: 0;
  }
}

/* A screen-reader hears the song, not a list of note glyphs; and anyone who
   asked for less motion gets a still backdrop rather than none at all. */

@media (prefers-reduced-motion:reduce) {
  .note-rain .note {
    animation: none;
    opacity: .2;
  }
}

.global-full-card.performance-mode .cover {
  grid-area: stage;
  width: min(310px,72%);
  height: 132px;
  justify-self: center;
  border-radius: 22px;
  overflow: visible;
  background: radial-gradient(ellipse at 50% 72%,#6f4f8655,transparent 64%);
  box-shadow: none;
}

.global-full-card.performance-mode .cover .musicat-art {
  width: 132px;
  height: 132px;
  transform: translateY(-4px) scale(1.08);
}

.global-full-card.performance-mode .track-open {
  grid-area: copy;
}

.global-full-card.performance-mode .audio-visuals {
  grid-area: visuals;
}

.global-full-card.performance-mode .track-top {
  row-gap: 5px;
}

/* Left-aligned, not centred: the title, the subtitle and the chip row now share
   one left edge, so the eye reads down a single margin instead of re-finding the
   centre on each line. A long title also grows to the right rather than pushing
   both ends outward. */

.global-full-card.performance-mode .track-open {
  text-align: start;
  grid-column: 1/-1;
}

/* The scrubber and its clock sit above the title, directly under the avatar:
   the thing you reach for while a song plays comes before the thing you read
   once. Only the transport moves — Lyrics, Remix, Like and Share stay in the
   action row below the title, where they were.

   The transport and the actions are siblings inside .player, so ordering .player
   as a whole would carry the buttons up with the scrubber. Instead .player is
   made to span the card's rows — display:contents drops it from layout — and its
   two children are ordered individually against .track-top.

   Ordering rather than reparenting: the player is a clone of a chat card, so
   moving the element in the DOM would move it on every song card too. Order on
   the player's own class changes this surface alone. */

.global-full-card.performance-mode {
  display: flex;
  flex-direction: column;
}

.global-full-card.performance-mode>.player {
  display: contents;
}

/* Stretched, not shrunk to its content: display:contents made the transport a
   direct flex child of the card, and a flex item's default cross-axis size is
   its content, so the scrubber stopped short of the column's width. */

.global-full-card.performance-mode>.player>.transport {
  order: 0;
  align-self: stretch;
}

.global-full-card.performance-mode>.track-top {
  order: 1;
}

.global-full-card.performance-mode>.player>.player-actions {
  order: 2;
}

/* Everything after the header keeps its place, behind the action row. */

.global-full-card.performance-mode>.lyrics-panel,
.global-full-card.performance-mode>.share-panel,
.global-full-card.performance-mode>.tags,
.global-full-card.performance-mode>.song-card-footer {
  order: 3;
}

.global-full-card.performance-mode .audio-visuals {
  position: absolute;
  right: 12px;
  bottom: 0;
}

@media (max-width:600px) {
  .global-full-card.performance-mode .cover {
    height: 112px;
  }
  .global-full-card.performance-mode .cover .musicat-art {
    width: 116px;
    height: 116px;
  }
}

/* Music Director card */

/* The avatar column plus the gap put this card's text at 108, the same edge as
   the song card's below it: 16px padding + 1px border = 17, then a 77px column
   and a 14px gap. The 72px avatar centres in that column at 54 from the card's
   edge, where the song card's centres too. */

.generation-progress {
  display: grid;
  grid-template-columns: 77px minmax(0,1fr) 87px;
  align-items: center;
  gap: 14px;
  padding: 15px 16px;
  border-radius: 18px;
  overflow: hidden;
  background: radial-gradient(circle at 9% 28%,#36284788,transparent 36%),linear-gradient(145deg,#191720,#111116);
}

/* justify-items:start with a 1px offset rather than centring: this card's padding
   is 15px to the song card's 16px, so a centred 72px avatar landed 1.5px to the
   right of the song card's. Pinning it puts both avatar axes at 54. */

.progress-scene {
  width: 77px;
  height: 78px;
  display: grid;
  place-items: center start;
  padding-left: 1px;
  box-sizing: border-box;
  position: relative;
}

.progress-musicat {
  width: 72px;
  height: 72px;
  display: grid;
  place-items: center;
  transform-origin: 50% 65%;
  transition: transform .42s cubic-bezier(.22,1,.36,1),filter .3s;
}

.progress-musicat .musicat-art {
  width: 76px;
  height: 76px;
  overflow: visible;
}

.progress-musicat .musicat-mic {
  display: none;
}

.progress-content {
  min-width: 0;
}

.progress-eyebrow {
  margin: 0 0 4px;
  font-size: var(--fs-2xs);
  line-height: 1.2;
  font-weight: 750;
  letter-spacing: .14em;
  color: #b89dce;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.progress-title {
  font-size: var(--fs-base);
  line-height: 1.18;
  font-weight: 750;
  color: #f6f2fa;
  letter-spacing: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.progress-description {
  margin: 3px 0 7px;
  font-size: var(--fs-2xs);
  line-height: 1.35;
  color: #9c96a4;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.progress-meta {
  display: flex;
  align-items: center;
  min-width: 0;
}

.progress-steps {
  display: none;
}

.progress-times {
  margin: 0;
  display: flex;
  gap: 10px;
  color: #837b89;
  font-size: var(--fs-2xs);
}

.progress-times b {
  color: #cfc4d8;
  font-weight: 650;
}

.progress-creation {
  position: relative;
  width: 104px;
  height: 70px;
  border-left: 1px solid #2e2934;
  overflow: hidden;
}

.creation-orbit {
  position: absolute;
  left: 24px;
  top: 13px;
  width: 46px;
  height: 46px;
  border: 1px solid #554965;
  border-radius: 50%;
  animation: creation-orbit 5.6s linear infinite;
}

.creation-orbit:before,
.creation-orbit:after {
  content: "";
  position: absolute;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #9ee9c2;
  box-shadow: 0 0 10px #7ce2b4;
}

.creation-orbit:before {
  left: -4px;
  top: 17px;
}

.creation-orbit:after {
  right: 2px;
  top: 1px;
  background: #d4a9f7;
  box-shadow: 0 0 10px #c295ef;
}

.creation-core {
  position: absolute;
  left: 40px;
  top: 29px;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%,#fff,#c6a0eb 38%,#6e4f8c 72%);
  box-shadow: 0 0 18px #c09ae788;
  animation: creation-breathe 2.4s ease-in-out infinite;
}

.creation-note {
  position: absolute;
  color: #bca1d4;
  font-size: var(--fs-sm);
  opacity: .34;
  animation: creation-note 2.8s ease-in-out infinite;
}

.creation-note.n1 {
  left: 12px;
  top: 11px;
}

.creation-note.n2 {
  right: 7px;
  top: 35px;
  animation-delay: -1.4s;
}

.creation-wave {
  position: absolute;
  left: 11px;
  right: 6px;
  bottom: 8px;
  height: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 3px;
}

.creation-wave i {
  width: 3px;
  height: var(--h);
  border-radius: 3px;
  background: repeating-linear-gradient(0deg,#8ee0ba 0 2px,transparent 2px 3px);
  animation: creation-wave .75s ease-in-out infinite alternate;
  animation-delay: var(--d);
}

.generation-progress[data-phase=planning] .progress-musicat {
  transform: perspective(160px) rotateY(-8deg) translateX(1px);
}

.generation-progress[data-phase=planning] .musicat-pupils {
  transform: translateX(2px);
  transition: transform .16s ease-out;
}

.generation-progress[data-phase=planning] .creation-wave {
  opacity: .22;
}

.generation-progress[data-phase=composing] .progress-musicat {
  transform: perspective(160px) rotateY(-5deg);
}

.generation-progress[data-phase=composing] .musicat-note {
  animation: director-note 1.7s ease-in-out infinite;
}

.generation-progress[data-phase=composing] .creation-note {
  opacity: .85;
  animation-duration: 1.65s;
}

.generation-progress[data-phase=generating] .progress-musicat {
  animation: director-listen 4.4s ease-in-out infinite;
}

.generation-progress[data-phase=generating] .musicat-eye-open {
  opacity: 0;
}

.generation-progress[data-phase=generating] .musicat-eye-closed {
  opacity: 1;
}

.generation-progress[data-phase=generating] .musicat-hand {
  opacity: 1;
}

.generation-progress[data-phase=generating] .musicat-hand-left {
  transform: translate(7px,-17px);
}

.generation-progress[data-phase=generating] .musicat-hand-right {
  transform: translate(-7px,-17px);
}

.generation-progress[data-phase=generating] .creation-orbit {
  animation-duration: 2.8s;
}

.generation-progress[data-phase=mixing] .musicat-hand {
  opacity: 1;
}

.generation-progress[data-phase=mixing] .musicat-hand-left {
  transform: translate(7px,-17px);
}

.generation-progress[data-phase=mixing] .musicat-hand-right {
  animation: director-tap 1.3s ease-in-out infinite;
}

.generation-progress[data-phase=mixing] .musicat-pupils {
  transform: translateX(2px);
}

.generation-progress[data-phase=mixing] .creation-wave i {
  animation-duration: .46s;
}

.generation-progress[data-phase=finalizing] .progress-musicat {
  transform: perspective(160px) rotateY(-7deg) scale(1.03);
  filter: drop-shadow(0 0 8px #c99af844);
}

.generation-progress[data-phase=finalizing] .creation-note.n2 {
  font-size: var(--fs-lg);
  color: #d3adf2;
  opacity: 1;
}

.generation-progress[data-phase=ready] .progress-musicat {
  animation: director-ready 1.05s cubic-bezier(.22,1,.36,1) both;
}

.generation-progress[data-phase=ready] .musicat-mouth {
  opacity: 0;
}

.generation-progress[data-phase=ready] .musicat-mouth-open {
  opacity: 1;
}

.generation-progress[data-phase=ready] .creation-core {
  background: #9ee9c2;
  box-shadow: 0 0 22px #7ce2b4;
}

.generation-progress[data-phase=ready] .creation-orbit {
  animation-play-state: paused;
  border-color: #426655;
}

.generation-progress[data-stage=error] .progress-musicat {
  filter: saturate(.55);
}

.generation-progress.easter-drum .musicat-hand,
.generation-progress.easter-watch .musicat-hand-left {
  opacity: 1;
}

.generation-progress.easter-drum .musicat-hand-left {
  animation: easter-left-tap .7s ease-in-out 2;
}

.generation-progress.easter-drum .musicat-hand-right {
  animation: easter-right-tap .7s ease-in-out 2;
}

.generation-progress.easter-dance .progress-musicat {
  animation: easter-two-beat .9s ease-in-out 2;
}

.generation-progress.easter-watch .musicat-pupils {
  animation: easter-watch-eyes 2.2s ease-in-out;
}

.generation-progress.easter-watch .musicat-hand-left {
  animation: easter-watch-hand 2.2s ease-in-out;
}

@keyframes creation-orbit {
  to {
    transform: rotate(360deg);
  }
}

@keyframes creation-breathe {
  50% {
    transform: scale(1.22);
    filter: brightness(1.28);
  }
}

@keyframes creation-note {
  50% {
    transform: translateY(-5px);
    opacity: .9;
  }
}

@keyframes creation-wave {
  to {
    height: 4px;
    filter: brightness(1.35);
  }
}

@keyframes director-note {
  50% {
    transform: translateY(-2px) rotate(4deg);
  }
}

@keyframes director-listen {
  0%,
  100% {
    transform: translateX(-1px) rotate(-1deg);
  }
  50% {
    transform: translateX(2px) rotate(1deg);
  }
}

@keyframes director-tap {
  0%,
  70%,
  100% {
    transform: none;
  }
  82% {
    transform: translateY(-5px);
  }
  90% {
    transform: translateY(1px);
  }
}

@keyframes director-ready {
  0% {
    transform: rotateY(0) scale(.96);
  }
  45% {
    transform: rotateY(-10deg) scale(1.06);
  }
  100% {
    transform: rotateY(-6deg) scale(1);
  }
}

@keyframes easter-left-tap {
  50% {
    transform: translateY(7px) rotate(7deg);
  }
}

@keyframes easter-right-tap {
  50% {
    transform: translateY(7px) rotate(-7deg);
  }
}

@keyframes easter-two-beat {
  25% {
    transform: translateX(-3px) rotate(-3deg);
  }
  75% {
    transform: translateX(3px) rotate(3deg);
  }
}

@keyframes easter-watch-eyes {
  20%,
  65% {
    transform: translate(-2px,3px);
  }
}

@keyframes easter-watch-hand {
  25%,
  70% {
    transform: translate(13px,-2px);
  }
}

@media (max-width:600px) {
  .generation-progress {
    grid-template-columns: 62px minmax(0,1fr) 68px;
    gap: 9px;
    padding: 13px 11px;
  }
  .progress-scene {
    width: 62px;
    height: 66px;
    transform: none;
    margin: 0;
  }
  .progress-musicat,
  .progress-musicat .musicat-art {
    width: 62px;
    height: 62px;
  }
  .progress-creation {
    width: 68px;
  }
  .creation-orbit {
    left: 13px;
  }
  .creation-core {
    left: 29px;
  }
  .creation-note.n2 {
    display: none;
  }
  .progress-description {
    max-width: 100%;
  }
}

@media (prefers-reduced-motion:reduce) {
  .progress-musicat,
  .progress-musicat *,
  .progress-creation * {
    animation: none !important;
    transition: none !important;
  }
}

@media (prefers-reduced-motion:reduce) {
  .global-player,
  .global-full,
  .global-full-card,
  .global-full-card .track-top,
  .global-full-card .cover,
  .global-full-card .cover .musicat-art,
  .global-full-card .track-open,
  .global-full-card .audio-visuals {
    transition: none !important;
  }
}

/* ===========================================================================
   Desktop.

   Everything here is inside a min-width query, so the phone layout below
   1001px is untouched and cannot regress.

   The body grid holds exactly two items — the sidebar and the shell. The
   <script> tag is also a child of <body>, so it is taken out of flow
   explicitly: left in, it becomes a grid item and claims a column.

   The player is NOT moved in the DOM. It lives inside .shell and is owned by
   handlers that hold a reference to it, so the shell becomes the grid and the
   player takes a column there. Moving the element would detach it and restart
   playback.
   =========================================================================== */

.sidebar {
  display: none;
}

@media (min-width:1001px) {
  body {
    display: grid;
    grid-template-columns: var(--sidebar-w,272px) minmax(0,1fr);
    /* The topbar spans both columns; the sidebar and shell share the row beneath it.
       minmax(0,1fr) rather than 1fr, or a tall child stretches the row past the
       viewport instead of scrolling inside it. */
    grid-template-rows: var(--topbar-h,72px) minmax(0,1fr);
    grid-template-areas:
      "topbar topbar"
      "sidebar shell";
    height: 100dvh;
    overflow: hidden;
    /* The grips are absolutely positioned children; without this they would
       resolve against the viewport instead of the grid. */
    position: relative;
  }
  /* A script tag is a grid item unless told otherwise. */
  body>script {
    display: none;
  }
  .sidebar {
    grid-area: sidebar;
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 16px 14px;
    min-height: 0;
    background: #0a0a0c;
    border-right: 1px solid #1e1c24;
    overflow: hidden;
  }

  /* The logo lives in the topbar now, directly above this column — a second copy
     here would be two logos within 60px of each other. */

  .sidebar-brand {
    display: none;
  }
  .sidebar-brand img {
    width: 132px;
    height: auto;
    margin: 2px 0 18px 8px;
  }
  .sidebar-new {
    margin-bottom: 18px;
    padding: 14px 16px;
    border: 0;
    border-radius: 14px;
    background: linear-gradient(145deg,#b39bf0,#9b7fe8);
    color: #1a1225;
    font: 700 var(--fs-base)/1 Inter,system-ui,sans-serif;
    cursor: pointer;
    transition: filter .18s,transform .18s;
  }
  .sidebar-new:hover {
    filter: brightness(1.08);
    transform: translateY(-1px);
  }
  .sidebar-nav {
    display: flex;
    flex-direction: column;
    gap: 2px;
  }
  .sidebar-link {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    padding: 12px 14px;
    border: 0;
    border-radius: 12px;
    background: transparent;
    color: #c9c4d2;
    font: 500 var(--fs-base)/1 Inter,system-ui,sans-serif;
    text-align: left;
    cursor: pointer;
    transition: background .16s,color .16s;
  }
  .sidebar-link:hover {
    background: #15141a;
    color: #f2eff7;
  }
  .sidebar-link.is-active {
    background: #1b1922;
    color: #fff;
  }
  .sidebar-link svg {
    width: 19px;
    height: 19px;
    flex: none;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.7;
    stroke-linecap: round;
    stroke-linejoin: round;
  }
  .sidebar-link svg.heart {
    stroke: #e4657f;
  }
  .sidebar-count {
    margin-left: auto;
    font-style: normal;
    font-size: var(--fs-xs);
    color: #7d7689;
  }
  .sidebar-section {
    margin: 22px 0 8px 14px;
    font: 600 var(--fs-2xs)/1 Inter,system-ui,sans-serif;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: #6f6879;
  }
  .sidebar-recent {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 1px;
    scrollbar-width: thin;
  }
  .sidebar-recent::-webkit-scrollbar {
    width: 6px;
  }
  .sidebar-recent::-webkit-scrollbar-thumb {
    background: #2a2732;
    border-radius: 9px;
  }
  .recent-item {
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 8px 12px;
    border: 0;
    border-radius: 11px;
    background: transparent;
    color: #cdc7d6;
    font: 500 var(--fs-sm)/1.3 Inter,system-ui,sans-serif;
    text-align: left;
    cursor: pointer;
    transition: background .16s;
  }
  .recent-item:hover {
    background: #15141a;
  }
  .recent-art {
    width: 30px;
    height: 30px;
    flex: none;
    border-radius: 8px;
    object-fit: cover;
    background: #211d2a;
  }
  .recent-title {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .recent-heart {
    flex: none;
    width: 14px;
    height: 14px;
    fill: #e4657f;
    stroke: none;
  }
  .sidebar-foot {
    display: flex;
    align-items: center;
    gap: 11px;
    margin-top: 14px;
    padding-top: 16px;
    border-top: 1px solid #1e1c24;
    color: #9a93a6;
    font: 500 var(--fs-xs)/1.4 Inter,system-ui,sans-serif;
  }
  .sidebar-avatar {
    width: 36px;
    height: 36px;
    flex: none;
    display: grid;
    place-items: center;
    border-radius: 50%;
    background: #241f2e;
    color: #cbb6f0;
    font: 700 var(--fs-base)/1 Inter,system-ui,sans-serif;
  }
  .sidebar-foot strong {
    display: block;
    color: #e9e4f0;
    font-weight: 600;
    font-size: var(--fs-sm);
  }
  /* The shell fills its column and loses the phone-width cap. Its own top bar
     is redundant once the sidebar carries navigation. */
  .shell {
    grid-area: shell;
    max-width: none;
    width: 100%;
    min-width: 0;
    /* The base rule is height:100dvh, from when the shell owned the whole viewport.
       It is a grid row beneath the topbar now, so it must fill its AREA — 100dvh
       here overflows by exactly the topbar's height. */
    height: 100%;
    min-height: 0;
    position: relative;
  }
  /* The shell header IS the desktop topbar now: it moves out of the shell's flow and
     into the body grid's first row, spanning the full width. */
  .app-header {
    display: flex;
    grid-area: topbar;
    position: static;
    transform: none;
    width: 100%;
    max-width: none;
    height: var(--topbar-h,72px);
    padding: 0 24px;
    gap: 12px;
    border-bottom: 1px solid #1e1c24;
    border-radius: 0;
  }
  main {
    /* Side padding stays 12px, like .composer-wrap, so the feed and the
       composer card share edges here too; only the top grows. */
    padding: 26px 12px;
  }
  /* The library heading stays in view now that the shell header is gone. */
  .library-head {
    position: sticky;
    top: 0;
    z-index: 4;
    padding: 6px 0 14px;
    background: linear-gradient(180deg,#0b0b0d 72%,transparent);
  }
  .library-head h1 {
    font-size: var(--fs-display);
  }
}

@media (min-width:1001px) {
  /* The player gets its own column. The shell becomes the grid, because the
     player is its child — addressing it from the body grid did nothing. */
  /* .shell has six children: header, two account overlays, the player, main
     and the composer. Named areas rather than positions, so the overlays can
     be placed explicitly instead of being auto-flowed into a column. */
  .shell {
    grid-area: shell;
    display: grid;
    grid-template-columns: minmax(0,1fr) var(--player-w,392px);
    /* The base .shell declares FOUR rows (60px auto minmax(0,1fr) auto) for the
       phone layout. Leaving those in place while naming a two-row area grid
       meant the `player` area could not be formed against the real track list,
       so the player was auto-placed into row 2 — which is why it sat below the
       chat however it was aligned. Redeclare the rows here. */
    grid-template-rows: minmax(0,1fr) auto;
    grid-template-areas: "feed player" "composer player";
  }
  /* The pane keeps its column whether or not a song is loaded: a layout that
     reflows the chat every time playback starts is worse than a quiet empty pane,
     and the pane is where the player now lives permanently. */
  /* The pane is revealed by JS clearing `hidden` on desktop, not by overriding the
     attribute here: a CSS override would leave the attribute lying about the state,
     and assistive technology reads the attribute. */

  /* More specific than the blanket `.global-player-empty{display:none}` in
     64-desktop-topbar.css, which joins later and, at equal specificity, won:
     the message had never shown on desktop. Order must not decide this. */
  .shell>.global-player>.global-player-empty {
    display: block;
    margin: auto;
    padding: 0 24px;
    color: var(--muted);
    font-size: var(--fs-sm);
    line-height: 1.5;
    text-align: center;
  }

  /* Hidden once a song is loaded, not once the pane is shown: on desktop the
     pane is always shown, so keying on it hid the message for good and the
     pane sat blank before the first song. A loaded song is the next player
     revealed (desktop) or the current player's card built into the pane
     (phone). */
  .global-player:has(.stage-player:not([hidden]), .global-full-card) .global-player-empty {
    display: none;
  }
  #main {
    grid-area: feed;
    min-height: 0;
  }
  #composerWrap {
    grid-area: composer;
  }
  /* Overlays sit above everything rather than taking a cell. */
  #accountBackdrop,
  #accountPanel {
    grid-area: 1/1/-1/-1;
  }
  .global-player {
    max-height: none;
    min-height: 0;
    overflow-y: auto;
    padding: 22px;
    border-bottom: 0;
    border-left: 1px solid #1e1c24;
    background: #0a0a0c;
    display: flex;
    flex-direction: column;
    justify-content: center;
  }
  /* A wide screen shows the player already: a full-screen page would hide the
     conversation for nothing. */
  .player-back {
    display: none !important;
  }
  body.player-page .view {
    display: none;
  }
  body.player-page #chatView {
    display: flex;
  }
}

@media (min-width:1001px) {
  /* The player card was sized for a phone-width strip: a non-wrapping row of
     eight action buttons plus a 172px minimum on the transport gives it a hard
     floor of ~409px, which overflowed the 392px column and clipped the clock
     and download button off the viewport edge.

     Nothing here changes the phone layout — it is all inside the min-width
     query that introduces the column in the first place. */
  .global-player {
    padding: 18px 14px;
  }
  /* The phone caps this at min(48dvh,480px) so the strip cannot swallow the
     chat. The desktop column is full height and has nothing to share with, so
     the cap only clipped the action row off the bottom of the card. */
  .global-full {
    max-width: 100%;
    width: 100%;
    margin: 0;
    padding: 0;
    max-height: none;
  }
  .global-full-card {
    padding: 14px;
  }
  /* Let the transport's flexible track shrink with the column. */
  .global-full-card .transport {
    grid-template-columns: minmax(0,1fr) auto;
    gap: 8px;
  }
  /* Wrap rather than overflow. */
  .global-full-card .player-actions {
    flex-wrap: wrap;
    justify-content: center;
    gap: 4px;
  }
  .track.global-full-card>.tags {
    display: none;
  }
  /* The chips start where the title and subtitle start: one left edge down the
     whole block, rather than a centred row under a centred title. */
  .global-full-card .card-tags {
    justify-content: flex-start;
  }
  /* The card fills the pane so an open lyrics tray can reach its floor.
     .global-full and the card are blocks by default, so the height the pane
     offers never reached the tray. */
  .shell>.global-player>.global-full {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
    align-self: stretch;
  }
  .global-full>#globalFullCard {
    flex: 1;
    min-height: 0;
    display: flex;
  }
  #globalFullCard>.global-full-card {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
  }
  /* The transport keeps its natural height so the tray takes the slack. */
  .global-full-card>.player {
    flex: none;
  }
  /* The column is full height, so the player sits at the top of it rather than
     floating in the middle. justify-content:center is what centred it. */
  /* Span both grid rows explicitly. `grid-area:player` was resolving to
     `2 / player` — the name was taken as a row line, so the player sat in the
     bottom row and floated at y609 however it was aligned internally. Explicit
     line numbers cannot be misread: column 2, rows 1 through the end. */
  /* Use the named area, like #main and #composerWrap do — those resolve
     correctly, while explicit line numbers kept coming back as row 2. The area
     spans both rows, so the player fills the column and its content starts at
     the top. */
  /* `.shell>.global-player{grid-row:2}` (line ~154) pins the phone grid's four
     children to explicit rows. It is outside any media query and outranks a
     plain `.global-player` selector, so it kept the player in row 2 — below the
     chat — no matter how the area or alignment was written. Match its
     specificity to release the pin on desktop. */
  .shell>.global-player {
    grid-area: player;
    align-self: stretch;
    justify-content: flex-start;
  }
  .shell>main {
    grid-area: feed;
  }
  .shell>.composer-wrap {
    grid-area: composer;
  }
  .global-full {
    margin-top: 0;
  }
  /* Performance mode gets a proper rectangular stage here: the column is tall
     and narrow, so the phone's 132px band leaves the avatar looking cramped.
     Roughly double the height, full column width, avatar scaled to match. */
  .global-full-card.performance-mode .cover {
    width: 100%;
    height: 268px;
    border-radius: 20px;
  }
  .global-full-card.performance-mode .cover .musicat-art {
    width: 236px;
    height: 236px;
    transform: translateY(-6px) scale(1.06);
  }
  /* In performance mode the visuals are absolutely positioned bottom-right,
     which overlapped the centred title in this narrow column. Put them back in
     flow below the copy. */
  /* Performance mode lays track-top out as "stage stage" / "copy visuals", so
     the title and the meters share a row. That row is too narrow in this
     column and the meters landed on top of the title. Stack them instead. */
  .global-full-card.performance-mode .track-top {
    grid-template-columns: minmax(0,1fr);
    grid-template-areas: "stage" "copy" "visuals";
  }
  .global-full-card.performance-mode .track-top>.audio-visuals {
    position: static;
    right: auto;
    bottom: auto;
    justify-content: center;
    margin-top: 8px;
  }
  .global-full-card.performance-mode .track-open {
    padding: 0 4px;
  }
}

/* A failed generation is a notice about the system, not a line of conversation.
   Styling it as a chat message put raw backend errors in MusiCat's voice. */

/* Centred in the conversation, like the system notice it is — not aligned to
   either speaker. One line, so it reads as an aside rather than a message. */

.generation-failed {
  display: block;
  width: auto;
  padding: 8px 14px;
  border: 1px solid #4a3340;
  border-radius: 999px;
  background: linear-gradient(#211a1f,#1a1519);
  color: #c9b9bf;
  font: 500 var(--fs-xs)/1.5 Inter,system-ui,sans-serif;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.generation-failed-mark {
  width: 20px;
  height: 20px;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: #7a3d50;
  color: #fff;
  font: 700 var(--fs-xs)/1 Inter,system-ui,sans-serif;
}

.generation-failed-copy {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}

.generation-failed-copy strong {
  font: 600 var(--fs-base)/1.3 Inter,system-ui,sans-serif;
}

.generation-failed-detail {
  color: #a9979e;
  font: 400 var(--fs-xs)/1.45 ui-monospace,monospace;
  overflow-wrap: anywhere;
}

@media (min-width:1001px) {
  /* The pane holds exactly one player, so the card's list framing — a bordered,
     shadowed box inside another bordered box — describes a list that is not
     there. */
  .global-full-card {
    border: 0;
    background: transparent;
    box-shadow: none;
    margin: 0;
  }
  /* With a tray open the card grew to 936px inside an 840px pane, pushing the
     tags and footer below the fold. The transport and actions sit ABOVE the
     panels (inside .player), so they were never the part being lost.
     Let the card own the pane's height and give the open panel the leftover
     space, scrolling inside itself. */
  /* .global-full is display:block by default, so flex rules on the card did
     nothing and its max-height:100% resolved against an auto-height block.
     Make it a bounded flex column: that is what gives the card something to
     size against. */
  /* The pane is overflow:auto, so height:100% on a child resolves against the
     scrollable content box and never constrains anything — the card just grew
     and the pane scrolled. Stop the pane scrolling and make .global-full the
     flex child that absorbs the height instead. */
  /* The phone caps this at 100dvh-60px so a tray cannot outgrow the screen.
     The desktop pane is a grid cell of exactly the right height already, and
     the cap shrank it by 60px the moment a tray opened — the tray's background
     stopped 60px above the floor of the pane. */
  :is(.global-player, .legacy-window).tray-open {
    overflow: hidden;
    max-height: none;
  }
  /* The pane pads 18px and the card 14px, which held the tray 32px off the
     floor. The composer in the middle pane sits 12px up, so the card's padding
     stands down while a tray is open and the two line up. */
  :is(.global-player, .legacy-window).tray-open .global-full {
    padding-bottom: 0;
  }
  :is(.global-player, .legacy-window).tray-open .global-full-card {
    padding-bottom: 0;
  }
  :is(.global-player, .legacy-window).tray-open {
    padding-bottom: 12px;
  }
  :is(.global-player, .legacy-window).tray-open .global-full {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
    overflow: hidden;
  }
  /* There is an unnamed wrapper DIV between .global-full and the card, so the
     card is not .global-full's flex child — its flex:1 addressed a parent it
     does not have. The wrapper has to carry the chain through. */
  :is(.global-player, .legacy-window).tray-open .global-full>div {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
  }
  :is(.global-player, .legacy-window).tray-open .global-full-card {
    display: flex;
    flex-direction: column;
    min-height: 0;
    flex: 1;
  }
  :is(.global-player, .legacy-window).tray-open .track-top,
  :is(.global-player, .legacy-window).tray-open .player,
  :is(.global-player, .legacy-window).tray-open .tags,
  :is(.global-player, .legacy-window).tray-open .song-card-footer {
    flex: none;
  }
  :is(.global-player, .legacy-window).tray-open .panel.open {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
  }
  /* The lyrics sit inside .lyrics-scene, not directly in the panel, and
     `.lyrics-scene .player-lyrics{height:100%}` made them resolve against an
     unconstrained parent. The scene has to be a flex child too. */
  :is(.global-player, .legacy-window).tray-open .lyrics-scene {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
    height: auto;
  }
  /* `.global-full-card .player-lyrics{height:min(300px,36vh)}` is a fixed
     height, which outranks flex sizing — the panel scrolled the pane instead of
     itself. Release it so the lyrics take the leftover space and scroll there. */
  :is(.global-player, .legacy-window).tray-open .global-full-card .player-lyrics {
    flex: 1;
    min-height: 0;
    height: auto;
    max-height: none;
    overflow-y: auto;
  }
}

/* The count belongs over the box, not inside it: within the input column it
   forced the textarea to reserve a row of height that never held text, so the
   box stood about twice as tall as the send button beside it. */

.composer-meta {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 8px;
  max-width: 720px;
  margin: 0 auto;
  padding: 0 14px 3px;
  min-height: 13px;
}

.char-count {
  font-size: var(--fs-2xs);
  line-height: 1.2;
}

.composer-ready-note {
  font-size: var(--fs-2xs);
}

/* One row tall, matching the send button. updateComposer() grows it from
   scrollHeight as text wraps, so no min-height is set here — one would inflate
   scrollHeight and defeat the measurement. */

.composer textarea {
  min-height: 0;
  padding: 11px 10px 10px;
}

/* Playlists page. Follows the Library grid; only the "More" control is new. */

.playlist-more-wrap {
  display: flex;
  justify-content: center;
  margin-top: 16px;
}

.playlist-card .library-cover {
  background: linear-gradient(135deg,#6f7fb3,#8a6ca8);
}

/* Song card: tags and a "..." menu.

   The chat card hid .tags, so a song in the feed showed only its title. The
   genre chips are what identify a song at a glance, and they now sit under the
   subtitle in the header block.

   The action row stays hidden entirely — playback belongs to the one player,
   and the overflow menu moved to the header. (An earlier revision styled
   .player-actions to show only the menu; that is superseded below and was
   removed rather than left as a rule the cascade silently discards.) */

.chat-card .tags {
  display: flex !important;
  margin-top: 10px;
}

/* The tags sit above the footer's reaction row, which is absolutely placed. */

.chat-card {
  padding-bottom: 30px;
}

/* Lyrics tray: the stage is always there, the separator reaches the edges.

   The radial gradient behind the lyrics only appeared when the tray opened,
   which read as a background switching on rather than as a stage the lyrics
   move onto. It is now painted whether or not the panel is open.

   The separator is a border on .panel, which sits inside the card's 14px
   padding — so the line stopped 14px short on each side and looked cut. A
   negative margin with matching padding pushes it out to the card's edges
   without moving the content. */

.lyrics-scene {
  background: radial-gradient(circle at 50% 60%,#382a4855,transparent 52%) !important;
}

.panel {
  margin-left: -14px;
  margin-right: -14px;
  padding-left: 14px;
  padding-right: 14px;
}

/* Resizable panes.

   A 9px grab strip holding the 1px divider line. It overlays the existing
   border rather than taking a grid column, so the panes keep their exact widths
   and no layout shifts when the grip appears.

   Hidden below the desktop breakpoint: the phone has one column to resize. */

.pane-grip {
  display: none;
}

@media (min-width:1001px) {
  .pane-grip {
    display: block;
    position: absolute;
    top: 0;
    bottom: 0;
    width: 9px;
    /* Above the panes it resizes, below the floating windows (59 and up): a
       window dragged across a pane boundary must not have the grip's line
       drawn through it. */
    z-index: 40;
    padding: 0;
    border: 0;
    background: transparent;
    cursor: col-resize;
    /* The strip is the hit area; the line is drawn inside it. */
    -webkit-appearance: none;
    appearance: none;
  }
  .pane-grip::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: 4px;
    width: 1px;
    background: #1e1c24;
    transition: width .12s ease,background .12s ease,left .12s ease;
  }
  .pane-grip:hover::before,
  .pane-grip:focus-visible::before,
  .pane-grip.is-dragging::before {
    width: 3px;
    left: 3px;
    background: #b39bf0;
  }
  .pane-grip:focus-visible {
    outline: none;
  }
  /* Positioned on the boundary each one owns. */
  #gripSidebar {
    left: calc(var(--sidebar-w,272px) - 4px);
  }
  body.grip-active {
    cursor: col-resize;
    user-select: none;
  }
}

@media (min-width:1001px) {
  /* The player grip sits on the shell's inner boundary, so it is measured from
     the right edge rather than from the sidebar. */
  #gripPlayer {
    right: calc(var(--player-w,392px) - 4px);
    left: auto;
  }
}

@media (max-width:1300px) {
  /* No player column below this width, so nothing to resize. */
  #gripPlayer {
    display: none;
  }
}

/* The chat list never scrolls sideways.

   A single unbroken token — a pasted URL, or a run of 400 A's — set the feed's
   content width and dragged every card off to the left. The message must wrap
   inside its bubble instead of widening the column. */

main {
  overflow-x: hidden;
}

.feed {
  max-width: min(720px,100%);
  overflow-x: hidden;
}

.msg {
  overflow-wrap: anywhere;
  word-break: break-word;
}

/* The scrollbar itself stays out of sight; the list still scrolls. */

main {
  scrollbar-width: none;
}

main::-webkit-scrollbar {
  width: 0;
  height: 0;
}

/* Composer card.

   The composer and its chips sit in one raised card, and the chat scrolls
   behind it rather than stopping above it. The "+" opens and closes the chips
   panel, which is what the composer's own control should decide. */

.composer-wrap {
  background: linear-gradient(transparent,#0b0b0d 26%);
  pointer-events: none;
}

.composer-wrap>* {
  pointer-events: auto;
}

.suggestions-panel,
.composer-meta,
.composer {
  position: relative;
  z-index: 2;
}

/* One card holds the chips and the composer, and the chat scrolls behind it. */

.composer-card {
  max-width: 720px;
  margin: 0 auto;
  padding: 10px 10px 9px;
  border: 1px solid #33333b;
  border-radius: 22px;
  background: #141419;
  box-shadow: 0 14px 40px #0009;
}

.composer-card .suggestions-panel {
  margin: 0 0 9px;
}

.composer-card .composer {
  border: 0;
  border-radius: 0;
  background: transparent;
  padding: 0;
  align-items: end;
  grid-template-columns: auto minmax(0,1fr) auto;
}

.composer-card .composer-meta {
  padding: 0 4px 3px;
}

/* The "+" is a 34px control, sized against the send button, and it stays a
   plus: rotating it into an "x" reads as "close the composer", not "hide the
   chips". The open state is carried by colour instead. */

/* `.composer button{width:42px;height:42px}` matches this element too and wins
   on specificity, so the selector has to be at least as specific. */

.composer .chips-toggle,
.chips-toggle {
  width: 34px;
  height: 34px;
  min-width: 34px;
  flex: none;
  display: inline-grid;
  place-items: center;
  align-self: end;
  margin: 0 0 4px;
  padding: 0;
  border: 1px solid #33333b;
  border-radius: 11px;
  background: #1d1d23;
  color: #b9b3c4;
  font: 300 var(--fs-xl)/1 Inter,system-ui,sans-serif;
  cursor: pointer;
  transition: background .16s,color .16s,border-color .16s;
}

.chips-toggle:hover {
  background: #26242e;
  color: #efeaf6;
}

.chips-toggle.is-open {
  border-color: #5b4d70;
  background: #241f2e;
  color: #d8c7f2;
}

body.chips-closed .suggestions-panel {
  display: none !important;
}

@media (min-width:1001px) {
  /* No line across the feed when nothing is playing: the pane it divides is
     collapsed, so the grip has no boundary to sit on. */
  /* The grip stays: the pane it divides is always present now. */
}

/* The right-hand separator exists only while the now-playing pane does. The
   :has() rule inside the 1301 query covers the collapsed-column case; this one
   covers every other state, including widths where the pane never appears. */

#gripPlayer {
  display: none;
}

@media (min-width:1001px) {
  .shell:has(.global-player:not([hidden])) #gripPlayer {
    display: block;
  }
}

/* Markup order inside .composer is: .input-column, "+", send. The example puts
   the "+" on the left, so it is placed explicitly rather than reordered in the
   HTML — the textarea should stay first for tab order and for screen readers. */

.composer-card .composer {
  grid-template-columns: auto minmax(0,1fr) auto;
}

.composer-card .chips-toggle {
  grid-column: 1;
  grid-row: 1;
}

.composer-card .input-column {
  grid-column: 2;
  grid-row: 1;
}

.composer-card .composer-send {
  grid-column: 3;
  grid-row: 1;
}

/* Chips get their own card, above the composer's. */

.chips-card {
  max-width: 720px;
  margin: 0 auto 8px;
  padding: 10px 12px;
  border: 1px solid #2b2933;
  border-radius: 18px;
  background: #131318;
  box-shadow: 0 10px 30px #0007;
}

.chips-card .suggestions-panel {
  margin: 0;
}

body.chips-closed .chips-card {
  display: none;
}

/* Message bubbles are narrower than the card and align to their own side:
   MusiCat's start at the card's left edge, the listener's end at its right. */

.feed {
  --bubble-inset: 52px;
}

/* A percentage alone let the bubble grow with the pane: at 1440px an assistant
   reply ran 620px at 16px, about 95 characters a line. Past roughly 75 the eye
   loses the start of the next line on the return sweep, and these replies are
   full paragraphs. The ch cap holds the measure while the percentage keeps the
   bubble inside the column on a narrow window — whichever is smaller wins. */

.msg {
  max-width: min(var(--measure),calc(100% - var(--bubble-inset)));
}

.msg.ai {
  align-self: flex-start;
  margin-right: var(--bubble-inset);
}

.msg.user {
  align-self: flex-end;
  margin-left: var(--bubble-inset);
}

.generation-failed {
  align-self: flex-start;
  margin-right: var(--bubble-inset);
  max-width: calc(100% - var(--bubble-inset));
}

/* .composer-wrap is a flex column, so a card with only max-width shrinks to
   its content instead of filling the row. */

.chips-card,
.composer-card {
  width: 100%;
  align-self: stretch;
}

/* No black band behind the cards.

   .composer-wrap painted a gradient to fade the chat out above the composer,
   and .composer carried an upward box-shadow for the same purpose. With the
   composer now a discrete card, both read as a dark slab sitting under it. The
   chat simply scrolls behind the card instead. */

.composer-wrap {
  background: none;
}

.composer-card .composer {
  box-shadow: none;
  background: transparent;
  border: 0;
}

.composer-card .composer-meta {
  background: none;
}

/* The inner textarea box is the card now, so it loses its own frame. */

.composer-card .input-column {
  background: transparent;
}

/* Chips shown inside a request message. */

.msg.user {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 6px;
}

.msg-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  justify-content: flex-end;
}

.msg-chip {
  padding: 3px 9px;
  border-radius: 999px;
  background: #d7d2e4;
  color: #2a2533;
  font: 600 var(--fs-2xs)/1.5 Inter,system-ui,sans-serif;
}

.msg-text {
  display: block;
}

/* The cards track the column, not their contents.

   A flex item sizes to its content by default, so a long word in the textarea
   or a wide chip row could push the card wider or let it collapse narrower.
   Fixing the basis to the column width means the card only changes when the
   pane it sits in changes. */

.chips-card,
.composer-card {
  flex: 0 0 auto;
  width: 100%;
  max-width: 720px;
  min-width: 0;
}

.composer-card .input-column {
  min-width: 0;
}

.composer-card textarea {
  width: 100%;
  min-width: 0;
}

/* The listener's bubble ends short of the card's right edge, mirroring how
   MusiCat's starts short of the left. margin-left only pushed it off the other
   side, leaving it flush. */

.feed>.msg.user {
  margin-right: 0;
  align-self: flex-end;
}

.feed {
  padding-right: 0;
}

/* Song card header: avatar, title block, and the "..." on one baseline.

   The tags moved under the subtitle, where they read as part of the song's
   description rather than as a separate band across the card. */

/* The gap that actually decides where the text starts: 18px after the 72px
   avatar puts .track-open at 108, the producer card's text edge. */

.chat-card .track-top {
  align-items: center;
  gap: 18px;
}

.chat-card .track-open {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

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

/* 11px, not 10. These chips are how you scan a card for what a song actually is,
   so they were the smallest type in the app carrying the most weight. The extra
   pixel costs nothing in height — the padding absorbs it. */

.card-tags .tag {
  font-size: var(--fs-2xs);
  padding: 3px 8px;
}

/* The old bottom tag row and the actions-row menu are superseded. */

.chat-card>.tags {
  display: none !important;
}

.chat-card .player-actions {
  display: none !important;
}

/* The header "..." — vertically centred with the avatar and title. */

.has-card-menu .card-menu {
  width: 30px;
  height: 30px;
  flex: none;
  align-self: center;
  margin-left: auto;
  margin-right: 0;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: #8b8494;
  cursor: pointer;
  transition: background .16s,color .16s;
}

.has-card-menu .card-menu:hover {
  background: #221f29;
  color: #e6e0ee;
}

.has-card-menu .card-menu svg {
  width: 18px;
  height: 18px;
}

/* A dropdown menu, not a tray: it floats over the card from the button rather
   than expanding the card and pushing the conversation down. */

.has-card-menu {
  position: relative;
}

.has-card-menu .commands-panel {
  position: absolute;
  top: 46px;
  right: 14px;
  z-index: 30;
  width: 212px;
  margin: 0;
  padding: 6px;
  border: 1px solid #3a3743;
  border-radius: 14px;
  background: #1a1921;
  box-shadow: 0 18px 44px #000a;
}

.has-card-menu .commands-panel .commands {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.has-card-menu .commands-panel .command {
  width: 100%;
  justify-content: flex-start;
  gap: 10px;
  padding: 9px 10px;
  border: 0;
  border-radius: 9px;
  background: transparent;
  color: #d8d3e0;
  font: 500 var(--fs-sm)/1 Inter,system-ui,sans-serif;
  text-align: left;
  cursor: pointer;
}

.has-card-menu .commands-panel .command:hover:not(:disabled) {
  background: #262331;
}

.has-card-menu .commands-panel .command:disabled {
  opacity: .45;
  cursor: default;
}

.has-card-menu .commands-panel .command svg {
  width: 16px;
  height: 16px;
  flex: none;
}

/* Song cards and system notices size to their content, with a ceiling below
   the composer card's width — a maximum, not a fixed width, so a short card
   stays short instead of stretching to a uniform block. */

.feed>.track,
.feed>.generation-failed,
.feed>.generation-progress {
  align-self: flex-start;
  /* 70% of the column, which is the composer card's width: the song card,
     the Director card and the Producer card share one measure. On a phone
     that measure is ~256px and every title truncated ("Productio…"), so the
     phone rule below lets them fill the column. */
  width: 70%;
  max-width: 70%;
}

@media (max-width:600px) {
  .feed>.track,
  .feed>.generation-failed,
  .feed>.generation-progress {
    width: 100%;
    max-width: 100%;
  }
}

/* Centre it in the column; the earlier rules aligned it to the start. */

.feed>.generation-failed {
  align-self: center;
  margin: 0 auto;
  max-width: calc(100% - var(--bubble-inset));
}

/* Belt and braces: the player's card is a clone of the chat card, so a menu that ever
   rides along in .track-top must not appear. The player builds its own menu into
   .player-actions from the same component, so this is scoped to the cloned position
   rather than to every .card-menu — a blanket rule would hide the real one. */

.track.global-full-card>.track-top>.card-menu,
.track.global-full-card>.commands-panel:not(.player-menu-panel) {
  display: none !important;
}

/* Why the send button is off. Red, under the card, only when it applies. */

.composer-block {
  max-width: 720px;
  margin: 6px auto 0;
  padding: 0 14px;
  color: #e2707f;
  font: 500 var(--fs-xs)/1.4 Inter,system-ui,sans-serif;
  text-align: right;
}

.composer-send:disabled {
  opacity: .4;
  cursor: not-allowed;
}

/* ---------------------------------------------------------------------------
   Modal — the app's first. Centred, where the account panel is a right-edge
   sheet, because these are short forms rather than a settings surface.
   --------------------------------------------------------------------------- */

.modal-backdrop {
  position: fixed;
  z-index: 60;
  inset: 0;
  background: #05040899;
  backdrop-filter: blur(4px);
  opacity: 0;
  pointer-events: none;
  transition: opacity .28s ease;
}

.modal-backdrop.open {
  opacity: 1;
  pointer-events: auto;
}

.modal {
  position: fixed;
  z-index: 61;
  top: 50%;
  left: 50%;
  width: min(620px,calc(100vw - 32px));
  min-width: min(340px,calc(100vw - 32px));
  max-width: calc(100vw - 32px);
  height: min(640px,calc(100dvh - 48px));
  min-height: 280px;
  max-height: calc(100dvh - 48px);
  display: flex;
  flex-direction: column;
  /* resize needs an overflow other than visible to take effect. The body scrolls
     inside, so clipping here costs nothing. */
  overflow: hidden;
  border: 1px solid #41394c;
  border-radius: 22px;
  background: radial-gradient(circle at 85% 0,#39284c88,transparent 38%),linear-gradient(165deg,#1c1a22,#100f14 75%);
  box-shadow: 0 28px 90px #000c;
  transform: translate(-50%,-46%);
  opacity: 0;
  pointer-events: none;
  transition: transform .3s cubic-bezier(.22,1,.36,1),opacity .22s;
}

/* Draggable from the bottom-right. The element is centred with a transform, so a drag
   grows it about its own centre — it stays put rather than walking off toward a
   corner. */

.modal.open {
  transform: translate(-50%,-50%);
  opacity: 1;
  pointer-events: auto;
  resize: both;
}

/* The native grip is nearly invisible on a dark panel, so one is drawn in the corner
   of the actions row, where there is always space for it. */

.modal-actions {
  position: relative;
}

.modal-actions:after {
  content: "";
  position: absolute;
  right: 5px;
  bottom: 5px;
  width: 11px;
  height: 11px;
  opacity: .5;
  pointer-events: none;
  background:
    linear-gradient(135deg,transparent 46%,#8b8494 46%,#8b8494 60%,transparent 60%),
    linear-gradient(135deg,transparent 72%,#8b8494 72%,#8b8494 86%,transparent 86%);
}

.modal-top {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 20px 20px 10px;
}

.modal-heading {
  min-width: 0;
  flex: 1;
}

.modal h2 {
  margin: 0;
  font-size: var(--fs-xl);
  letter-spacing: -.03em;
}

.modal-subtitle {
  margin: 5px 0 0;
  overflow: hidden;
  color: var(--muted);
  font-size: var(--fs-sm);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.modal-close {
  width: 30px;
  height: 30px;
  flex: none;
  border: 1px solid #3b3741;
  border-radius: 50%;
  background: #211f26;
  color: #ddd;
  font-size: var(--fs-lg);
  line-height: 1;
  cursor: pointer;
}

/* min-height:0 or the body cannot scroll inside the flex column. */

.modal-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 6px 20px 4px;
}

/* The song chooser fills the body and hands the height down to its
   collection, so pulling the dialog's corner grows the cards; the filter and
   the count stay put above them. */

.modal-body > .song-chooser {
  display: flex;
  flex-direction: column;
  gap: 8px;
  height: 100%;
  min-height: 0;
}

.modal-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 20px 18px;
}

.modal-status {
  flex: 1;
  min-width: 0;
  color: #e6a5a5;
  font-size: var(--fs-xs);
}

/* A form's buttons follow the same dimension rules as the rest of the app: the shared
   116px minimum, 42px height and 14px radius. Cancel was a 61px pill beside a 124px
   confirm, which read as two unrelated controls rather than one pair. */

.modal-actions .modal-confirm {
  min-width: 116px;
  padding: 14px 16px;
  border: 0;
  border-radius: 14px;
  background: linear-gradient(145deg,#b39bf0,#9b7fe8);
  color: #1a1225;
  font: 700 var(--fs-base)/1 Inter,system-ui,sans-serif;
  text-align: center;
  cursor: pointer;
  transition: filter .18s,transform .18s;
}

.modal-actions .modal-confirm:hover:not(:disabled) {
  filter: brightness(1.08);
  transform: translateY(-1px);
}

/* Beats .ghost, which is a small outline pill and sets its own padding and radius. */

.modal-actions .modal-cancel {
  min-width: 116px;
  padding: 13px 15px;
  border: 1px solid #4a4458;
  border-radius: 14px;
  background: #1f1d27;
  color: #ded9e8;
  font: 700 var(--fs-base)/1 Inter,system-ui,sans-serif;
  text-align: center;
  cursor: pointer;
  transition: filter .18s,transform .18s,background .18s;
}

.modal-actions .modal-cancel:hover:not(:disabled) {
  background: #2a2635;
  transform: translateY(-1px);
}

.modal-actions .modal-confirm:disabled,
.modal-actions .modal-cancel:disabled {
  opacity: .45;
  cursor: default;
  transform: none;
}

.modal-field {
  display: block;
  margin-bottom: 14px;
}

.modal-field > span {
  display: block;
  margin-bottom: 6px;
  color: #cfcbd8;
  font-size: var(--fs-xs);
  font-weight: 600;
}

.modal-field em {
  color: var(--muted);
  font-style: normal;
  font-weight: 400;
}

.modal-field input[type=text],
.modal-field textarea {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid #3a3743;
  border-radius: 11px;
  background: #16151b;
  color: #ece9f2;
  font: 400 var(--fs-base)/1.45 Inter,system-ui,sans-serif;
  resize: vertical;
}

.modal-field input[type=text]:focus,
.modal-field textarea:focus {
  outline: 2px solid #9b7fe8;
  outline-offset: 1px;
}

/* Kept beside the rules it overrides rather than in one of the six other
   reduced-motion blocks: a media override separated from its base rule is what
   made the by-concern CSS split unsafe. */

@media (prefers-reduced-motion:reduce) {
  .modal,
  .modal-backdrop {
    transition: none;
  }

  .modal {
    transform: translate(-50%,-50%);
  }
}

/* The add-to-playlist picker. */

.playlist-picker {
  max-height: 260px;
  overflow-y: auto;
  margin-bottom: 12px;
  border: 1px solid #2c2933;
  border-radius: 12px;
  background: #141319;
}

.picker-status {
  padding: 16px 13px;
  color: var(--muted);
  font-size: var(--fs-sm);
  text-align: center;
}

.playlist-option {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 11px 13px;
  border: 0;
  border-bottom: 1px solid #221f29;
  background: transparent;
  color: #ddd8e6;
  font: 500 var(--fs-sm)/1.3 Inter,system-ui,sans-serif;
  text-align: left;
  cursor: pointer;
}

.playlist-option:last-child {
  border-bottom: 0;
}

.playlist-option:hover:not(:disabled) {
  background: #201d29;
}

.playlist-option.selected {
  background: #2d2440;
  box-shadow: inset 3px 0 0 #9b7fe8;
}

.playlist-option:disabled {
  opacity: .5;
  cursor: default;
}

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

.playlist-option-meta {
  flex: none;
  color: var(--muted);
  font-size: var(--fs-2xs);
}

.playlist-create-inline {
  width: 100%;
  margin-bottom: 4px;
  padding: 9px 11px;
}

/* Playlist cover: the uploaded image, and its preview in the create form. */

/* Above the cover's decorative ::before, which paints a radial gradient at full
   opacity and only fades to .16 on hover — so an uploaded cover was visible only while
   the pointer was over it. The overlay is a backdrop for the MusiCat artwork; a real
   image replaces it rather than sitting under it. */

.playlist-cover-image {
  position: relative;
  z-index: 1;
  width: 100%;
  height: 100%;
  border-radius: 16px;
  object-fit: cover;
}

/* No backdrop at all behind a real cover: the image IS the artwork. */

.library-cover.has-cover-image:before {
  display: none;
}

/* `display:block` would override the hidden attribute, which is how the empty preview
   showed as a bordered square before any file was chosen. */

.playlist-image-preview:not([hidden]) {
  display: block;
  width: 84px;
  height: 84px;
  margin-bottom: 14px;
  border: 1px solid #3a3743;
  border-radius: 14px;
  object-fit: cover;
}

.modal-field input[type=file] {
  width: 100%;
  color: #cfcbd8;
  font: 400 var(--fs-sm)/1.4 Inter,system-ui,sans-serif;
}

.modal-field input[type=file]::file-selector-button {
  margin-right: 10px;
  padding: 7px 11px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: #17171c;
  color: #cfcfd5;
  font-size: var(--fs-xs);
  cursor: pointer;
}

.modal-field input[type=file]::file-selector-button:hover {
  background: #201d29;
}

/* Playlist card menu: a separator, and Delete in red.

   The separator sets the destructive item apart from the three that are not, so Delete
   is never the neighbour of a routine action. It hides with the button during the
   confirm step, or it would float above an explanation with nothing under it. */

.commands-panel .command-separator {
  height: 1px;
  margin: 5px 6px;
  background: #322e3c;
}

.commands-panel .command.command-danger {
  color: #ff7b7b;
}

.commands-panel .command.command-danger svg {
  stroke: currentColor;
}

.commands-panel .command.command-danger:hover:not(:disabled) {
  background: #3a1f26;
  color: #ff9a9a;
}

/* The playlist card's "..." matches the library card's placement. */

.playlist-card .card-menu {
  position: absolute;
  top: 8px;
  right: 8px;
}

.playlist-card .commands-panel {
  top: 42px;
  right: 8px;
}

/* Multi-select song picker: a count, a Clear, and a checkbox per row. */

.picker-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 7px;
}

.picker-count {
  color: var(--muted);
  font-size: var(--fs-xs);
}

.picker-clear {
  padding: 5px 10px;
  font-size: var(--fs-2xs);
}

/* The box is drawn rather than an <input>, so the whole row stays one button: a nested
   checkbox would take its own click and split the hit target. */

.picker-check {
  width: 16px;
  height: 16px;
  flex: none;
  border: 1px solid #4a4658;
  border-radius: 5px;
  background: #1b1922;
  transition: background .14s,border-color .14s;
}

.playlist-option.selected .picker-check {
  border-color: #9b7fe8;
  background: #9b7fe8 url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 8.5l3 3 6-6' fill='none' stroke='%231a1225' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center/13px no-repeat;
}

.playlist-option:disabled .picker-check {
  border-style: dashed;
}

/* The name takes the slack so the check and the meta keep their size. */

.playlist-option .playlist-option-name {
  flex: 1;
}

/* On a phone the subtitle was winning the row and squeezing titles to "R…". The title
   is what someone picks a song by, so it keeps its width and the metadata yields. */

@media (max-width:600px) {
  .playlist-option {
    flex-wrap: wrap;
    row-gap: 2px;
  }

  .playlist-option .playlist-option-name {
    flex: 1 0 auto;
    max-width: calc(100% - 26px);
  }

  .playlist-option .playlist-option-meta {
    flex: 1 0 100%;
    margin-left: 26px;
    text-align: left;
  }
}

/* ---------------------------------------------------------------------------
   Collections — the shared grid/list used by Library, Playlists and a playlist.

   Both views render the SAME card markup and differ only here. Two templates
   would be two things to keep in step, and the row/tile distinction is layout,
   not content.
   --------------------------------------------------------------------------- */

.collection-toolbar {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
  margin-bottom: 14px;
}

/* The page's own action sits left, the layout toggle right. */

.collection-toolbar > :first-child:not(.view-toggle) {
  margin-right: auto;
}

.view-toggle {
  display: inline-flex;
  flex: none;
  padding: 3px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: #15141a;
}

/* 35px inside the pill's 3px padding and 1px border is the 43px the primary
   and secondary buttons measure: the toggle stands level with them and with
   the play pill beside it, one strip of controls rather than a small one
   next to tall ones. */
.view-toggle-option {
  width: 35px;
  height: 35px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: #8b8494;
  cursor: pointer;
  transition: background .16s,color .16s;
}

.view-toggle-option:hover {
  color: #e6e0ee;
}

.view-toggle-option.active {
  background: #2d2440;
  color: #d9c9ff;
}

.view-toggle-option svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
}

/* The grid icon's squares read better filled than outlined at 16px. */

.view-toggle-option svg rect {
  fill: currentColor;
  stroke: none;
}

.view-toggle-option svg circle {
  fill: currentColor;
  stroke: none;
}

.collection-items {
  display: grid;
  gap: 12px;
}

.collection-grid .collection-items {
  grid-template-columns: repeat(2,minmax(0,1fr));
}

/* List: one column, a shorter cover, and room for the title to breathe. */

.collection-list .collection-items {
  grid-template-columns: minmax(0,1fr);
  gap: 8px;
}

.collection-list .collection-card {
  grid-template-columns: 48px minmax(0,1fr);
  padding: 10px 12px;
  border-radius: 14px;
}

.collection-list .collection-card .library-cover {
  width: 48px;
  height: 48px;
  border-radius: 12px;
}

.collection-list .collection-card h3 {
  margin-bottom: 2px;
  font-size: var(--fs-base);
}

.collection-more-wrap {
  display: flex;
  justify-content: center;
  margin-top: 16px;
}

/* The play pill: Play, Shuffle and, on the Library, New playlist — the
   layout toggle's sibling, the same pill and the same 35px options, so the
   toolbar is one strip. Play carries the primary gradient: it is the row's
   one primary action, as the text Play button was on the playlist page. */

.collection-toolbar-start {
  display: flex;
  align-items: center;
  gap: 10px;
}

.collection-actions {
  display: inline-flex;
  flex: none;
  gap: 2px;
  padding: 3px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: #15141a;
}

.collection-action {
  width: 35px;
  height: 35px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: #cfc9d8;
  cursor: pointer;
  transition: background .16s,color .16s;
}

.collection-action:hover {
  background: #221f29;
  color: #e6e0ee;
}

.collection-action svg {
  width: 18px;
  height: 18px;
  fill: currentColor;
}

.collection-action-play {
  background: linear-gradient(145deg,#b39bf0,#9b7fe8);
  color: #141118;
}

.collection-action-play:hover {
  background: linear-gradient(145deg,#c3aef7,#a98ff0);
  color: #141118;
}

/* The playlist's song chooser: the same cards, grid and list, at a size that
   fits a dialog. A picked card wears a check on its artwork's corner. The
   collection is the flex child that takes the dialog's height, so the list
   grows with the window; the cards scroll inside it. */

/* The rules hang off the dialog's wrapper (.song-chooser), because the
   renderer sets the collection's own class on every render. */

.song-chooser .collection {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 2px 2px 8px;
  scrollbar-width: thin;
}

.song-chooser .collection-card {
  cursor: pointer;
}

.song-chooser .collection-card .library-open h3 {
  font-size: var(--fs-sm);
}

.song-chooser .collection-card .library-meta {
  font-size: var(--fs-2xs);
}

.song-chooser .library-cover {
  position: relative;
}

.song-chooser .collection-card.is-picked {
  border-color: #8f74d8;
  background: #1f1a2c;
}

.song-chooser .collection-card.is-picked .library-cover::after {
  content: "";
  position: absolute;
  top: 6px;
  right: 6px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23141118' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'><path d='m5 12 5 5 9-10'/></svg>"),linear-gradient(145deg,#b39bf0,#9b7fe8);
  background-size: 12px 12px,100% 100%;
  background-position: center;
  background-repeat: no-repeat;
  box-shadow: 0 2px 8px #0008;
}

.song-chooser .picker-status {
  padding: 24px 12px;
  color: var(--muted);
  font-size: var(--fs-sm);
  text-align: center;
}

@media (max-width:680px) {
  .collection-grid .collection-items {
    grid-template-columns: minmax(0,1fr);
  }
}

/* ---------------------------------------------------------------------------
   A single playlist's page.
   --------------------------------------------------------------------------- */

.playlist-page-head {
  margin-bottom: 20px;
}

.playlist-back {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 16px;
}

.playlist-back svg {
  width: 14px;
  height: 14px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.playlist-page-identity {
  display: flex;
  align-items: flex-end;
  gap: 18px;
}

.playlist-page-cover {
  width: 132px;
  height: 132px;
  flex: none;
  position: relative;
  display: grid;
  place-items: center;
  border-radius: 20px;
  box-shadow: 0 16px 40px #0007;
}

.playlist-page-cover .playlist-cover-image {
  border-radius: 20px;
}

.playlist-page-copy {
  min-width: 0;
  flex: 1;
}

.playlist-page-copy h1 {
  margin: 4px 0 0;
  overflow-wrap: anywhere;
}

.playlist-page-description {
  margin: 8px 0 6px;
  max-width: 62ch;
  color: #c8c3d2;
  font-size: var(--fs-sm);
  line-height: 1.55;
}

.playlist-page-actions {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 18px;
}

/* The layout toggle sits at the far end of the same row, so the page has one strip of
   controls rather than a second toolbar directly beneath the first. */

.playlist-page-actions .view-toggle {
  margin-left: auto;
}

@media (max-width:680px) {
  .playlist-page-identity {
    align-items: flex-start;
    gap: 14px;
  }

  .playlist-page-cover {
    width: 92px;
    height: 92px;
    border-radius: 16px;
  }

  .playlist-page-copy h1 {
    font-size: var(--fs-display);
  }
}

/* The playlist page hosts the toggle in its actions row, so the collection's own
   toolbar is not rendered and its spacing must not be assumed. */

.playlist-page-head + .collection .collection-items {
  margin-top: 4px;
}

/* ---------------------------------------------------------------------------
   Desktop topbar: search, notifications and account.

   The header spans both columns of the body grid, with the sidebar and shell
   beneath it. The logo lives here rather than in the sidebar, so there is one
   of it.
   --------------------------------------------------------------------------- */

.topbar-search {
  position: relative;
  display: none;
  flex: 1;
  align-items: center;
  max-width: 460px;
}

.topbar-search-icon {
  position: absolute;
  left: 11px;
  width: 15px;
  height: 15px;
  fill: none;
  stroke: #8b8494;
  stroke-width: 2;
  stroke-linecap: round;
  pointer-events: none;
}

.topbar-search input[type=search] {
  width: 100%;
  padding: 9px 32px 9px 34px;
  border: 1px solid #2c2933;
  border-radius: 999px;
  background: #15141a;
  color: #ece9f2;
  font: 400 var(--fs-sm)/1 Inter,system-ui,sans-serif;
  /* Safari draws its own clear affordance and a magnifier; both duplicate ours. */
  appearance: none;
}

.topbar-search input[type=search]::-webkit-search-decoration,
.topbar-search input[type=search]::-webkit-search-cancel-button {
  appearance: none;
}

.topbar-search input[type=search]:focus {
  outline: 2px solid #9b7fe8;
  outline-offset: 1px;
}

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

.topbar-search-clear {
  position: absolute;
  right: 8px;
  width: 20px;
  height: 20px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: #2a2733;
  color: #cfcbd8;
  font-size: var(--fs-base);
  line-height: 1;
  cursor: pointer;
}

.search-results {
  position: absolute;
  top: 40px;
  left: 0;
  right: 0;
  z-index: 120;
  max-height: 360px;
  overflow-y: auto;
  padding: 5px;
  border: 1px solid #3a3743;
  border-radius: 14px;
  background: #1a1921;
  box-shadow: 0 18px 44px #000a;
}

.search-group-label {
  padding: 7px 9px 4px;
  color: var(--muted);
  font-size: var(--fs-2xs);
  font-weight: 800;
  letter-spacing: .14em;
  text-transform: uppercase;
}

.search-result {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 9px;
  border: 0;
  border-radius: 9px;
  background: transparent;
  color: #d8d3e0;
  font: 500 var(--fs-sm)/1.3 Inter,system-ui,sans-serif;
  text-align: left;
  cursor: pointer;
}

.search-result:hover,
.search-result.active {
  background: #262331;
}

.search-result-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.search-result-meta {
  flex: none;
  color: var(--muted);
  font-size: var(--fs-2xs);
}

.search-empty {
  padding: 14px 10px;
  color: var(--muted);
  font-size: var(--fs-sm);
  text-align: center;
}

.topbar-icon-button {
  position: relative;
  display: none;
  width: 36px;
  height: 36px;
  flex: none;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: #b7b1c2;
  cursor: pointer;
  transition: background .16s,color .16s;
}

.topbar-icon-button:hover,
.topbar-icon-button[aria-expanded=true] {
  background: #221f29;
  color: #e6e0ee;
}

.topbar-icon-button svg {
  width: 19px;
  height: 19px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.notification-dot {
  position: absolute;
  top: 7px;
  right: 8px;
  width: 8px;
  height: 8px;
  border: 2px solid #0a0a0c;
  border-radius: 50%;
  background: #ff648d;
}

.notification-tray {
  position: fixed;
  z-index: 120;
  top: 56px;
  right: 14px;
  width: min(340px,calc(100vw - 28px));
  max-height: min(440px,calc(100dvh - 80px));
  display: flex;
  flex-direction: column;
  border: 1px solid #3a3743;
  border-radius: 16px;
  background: #1a1921;
  box-shadow: 0 18px 44px #000a;
}

.notification-tray[hidden] {
  display: none;
}

.notification-tray-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 13px 14px 9px;
}

.notification-tray-head h2 {
  margin: 0;
  font-size: var(--fs-base);
}

.notification-clear {
  padding: 5px 10px;
  font-size: var(--fs-2xs);
}

.notification-list {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 0 7px 9px;
}

.notification-item {
  width: 100%;
  display: block;
  padding: 9px 9px;
  border: 0;
  border-radius: 10px;
  background: transparent;
  color: #d8d3e0;
  font: 500 var(--fs-sm)/1.35 Inter,system-ui,sans-serif;
  text-align: left;
  cursor: pointer;
}

.notification-item:hover {
  background: #262331;
}

.notification-item time {
  display: block;
  margin-top: 2px;
  color: var(--muted);
  font-size: var(--fs-2xs);
  font-weight: 400;
}

.notification-empty {
  padding: 22px 12px;
  color: var(--muted);
  font-size: var(--fs-sm);
  text-align: center;
}

@media (min-width:1001px) {
  .topbar-search,
  .topbar-icon-button {
    display: flex;
  }

  .topbar-icon-button {
    display: grid;
  }

  /* The logo anchors the bar; the nav and provider label belong to the phone
     header and would crowd a bar that now has search in it. */
  .app-header .nav,
  .app-header .provider,
  .app-header .grow {
    display: none;
  }

  /* Search takes the middle and the account controls sit at the right edge, which
     is where every other desktop app puts them. */

  /* Three equal columns centre the middle cell by construction. Absolute positioning
     was resolving against the wrong containing block and landed the box at the far
     right; a grid needs no containing block at all. */
  .app-header {
    display: grid;
    grid-template-columns: 1fr minmax(0,auto) 1fr;
    align-items: center;
  }

  .topbar-search {
    justify-self: center;
    width: min(620px,42vw);
    max-width: none;
  }

  .topbar-search input[type=search] {
    padding: 14px 42px 14px 46px;
    border-radius: 999px;
    font-size: var(--fs-base);
  }

  .topbar-search-icon {
    left: 17px;
    width: 19px;
    height: 19px;
  }

  .topbar-search-clear {
    right: 12px;
    width: 25px;
    height: 25px;
    font-size: var(--fs-base);
  }

  .search-results {
    top: 56px;
  }

  .topbar-icon-button {
    width: 50px;
    height: 50px;
  }

  .topbar-icon-button svg {
    width: 26px;
    height: 26px;
  }

  .notification-dot {
    top: 11px;
    right: 12px;
    width: 10px;
    height: 10px;
  }

  .app-header .account-trigger {
    padding: 7px 17px 7px 7px;
    gap: 11px;
    font-size: var(--fs-base);
  }

  .app-header .account-avatar {
    width: 38px;
    height: 38px;
    font-size: var(--fs-base);
  }

  .app-header .brand-logo {
    justify-self: start;
  }

  /* The bell and the account button share the last cell, so they are wrapped in a
     flex row rather than both claiming column 3 — two grid items in one cell stack
     on separate rows, which dropped the account button below the bar. */
  .app-header .topbar-end {
    grid-column: 3;
    justify-self: end;
    display: flex;
    align-items: center;
    gap: 8px;
  }

  .app-header .brand-logo {
    flex: none;
    margin-right: 6px;
  }

  /* 3:1 artwork, so 196px is 65px tall and still clears the 72px bar. The inherited
     object-fit:cover was cropping it against a shorter button; `contain` shows the
     whole wordmark. */
  .app-header .brand-logo {
    height: auto;
  }

  .app-header .brand-logo img {
    width: 196px;
    height: auto;
    object-fit: contain;
  }
}

/* Only the desktop pane shows an empty state; elsewhere a hidden player is hidden. */

.global-player-empty {
  display: none;
}

/* The account panel opens beneath the topbar rather than over it, so the button that
   opened it stays visible and its toggle state is legible. */

@media (min-width:1001px) {
  .account-panel {
    top: calc(var(--topbar-h,72px) + 8px);
    bottom: 12px;
  }

  .account-backdrop {
    top: var(--topbar-h,72px);
  }
}

/* The pane grips sit above the shell, but the account panel is a dialog above both.
   At z-60 against the panel's z-51 the player grip drew a line across it. */

.account-panel.open ~ .pane-grip,
body:has(.account-panel.open) .pane-grip {
  z-index: 40;
}

/* Always open, at every desktop width. Between 1001 and 1250 a full 392px pane would
   leave the chat under 400px — narrower than a phone — so the pane narrows instead of
   vanishing. The grip can still widen it. */

@media (min-width:1001px) and (max-width:1249px) {
  .shell {
    grid-template-columns: minmax(0,1fr) var(--player-w,300px);
  }
}

/* Song chips, shared by every card. The first genre is what identifies a song at a
   glance, so it carries the emphasis and the rest recede. */

/* Tinted by the genre's own family colour, the same way .genre-family is in the
   onboarding chooser — a genre should look the same wherever it appears rather than
   purple on a card and yellow in the composer. --genre is set inline per chip and
   falls back to the app's accent for a genre no family claims. */

.tag-primary {
  --genre: #b78dde;
  border-color: color-mix(in srgb,var(--genre) 60%,#343039);
  background: color-mix(in srgb,var(--genre) 18%,#17151c);
  color: color-mix(in srgb,var(--genre) 55%,#ffffff);
  font-weight: 700;
}

.tag-more {
  color: var(--muted);
  cursor: default;
}

/* On a collection card the row sits under the subtitle and must not push the tile
   taller than its cover, so the chips are a touch smaller than the chat card's. */

.collection-card .card-tags {
  margin-top: 7px;
  gap: 4px;
}

.collection-card .card-tags .tag {
  padding: 2px 7px;
  font-size: var(--fs-2xs);
}

/* Three rows in list view too: title, subtitle, chips. Laying them out side by side
   to keep the row short put the chips on the same line as the title, which reads as
   one long strip rather than as a song with attributes. */

.collection-list .collection-card .card-tags {
  margin-top: 6px;
}

/* A genre chip is a button now: it filters the library by that genre. */

.tag-genre,
.tag-language {
  cursor: pointer;
  transition: filter .16s,transform .16s;
}

.tag-genre:hover,
.tag-language:hover {
  filter: brightness(1.25);
  transform: translateY(-1px);
}

.tag-genre:focus-visible,
.tag-language:focus-visible {
  outline: 2px solid var(--genre,#9b7fe8);
  outline-offset: 2px;
}

/* What the list is currently narrowed to, and the way out of it. */

.collection-filter-note {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
  padding: 9px 14px;
  border: 1px solid #2c2933;
  border-radius: 12px;
  background: #15141a;
  color: var(--muted);
  font-size: var(--fs-sm);
}

.collection-filter-clear {
  flex: none;
  padding: 5px 11px;
  font-size: var(--fs-2xs);
}

/* A hidden card must not hold its grid cell open. */

.collection-card[hidden] {
  display: none;
}

/* The chat button is the composer's one send. Every message goes to MusiCat;
   a song is made only when the Producer is approved, never from this row.

   The white "↑" that generated a song directly is retired, not deleted: it
   sits in the markup with `hidden`. Remove the attribute to bring it back; it
   then takes a fourth column after the envelope.

   `.composer button { background:#fff }` at line 362 matches this element too
   and is equally specific, so the selector carries the card ancestor to win on
   specificity rather than on source order — the trap AGENTS.md records for the
   cloned player. */

.composer-card .composer-send[hidden] {
  display: none;
}

.composer-card .composer-send:not(.composer-send-chat):not([hidden]) {
  grid-column: 4;
}

.composer-card .composer:has(.composer-send:not([hidden]):not(.composer-send-chat)) {
  grid-template-columns: auto minmax(0,1fr) auto auto;
}

.composer-card .composer-send-chat {
  grid-column: 3;
  grid-row: 1;
  background: #9b7fe8;
  color: #0b0b0d;
  font-size: var(--fs-base);
  font-weight: 700;
}

.composer-card .composer-send-chat:hover:not(:disabled) {
  background: #b39bf0;
}

.composer-card .composer-send-chat:disabled {
  background: #3a3743;
  color: #8b8494;
  cursor: default;
}


/* The Producer's line on a Director card it commissioned. Only the chat's
   produceFromBrief adds it; the white button's card has none. */
.generation-progress .progress-commission {
  font-size: var(--fs-2xs);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #3fa66b;
  margin-bottom: 6px;
}

/* Producer card: a Director card with the Producer's picture where the
   Director's creation animation goes. Same grid, same type, same colours. */
/* The rule lands where the song card's does — 400px in on a 504px card — so the
   two cards in the feed divide at the same place instead of 17px apart. The 87px
   is what puts it there: the card's 104px third column held the line further
   left. The 14px of padding is the song card's aside's, so the art clears the
   rule by the same gap on both. */

.producer-card .producer-scene {
  width: 87px;
  height: 70px;
  display: grid;
  place-items: center;
  padding-left: 14px;
  box-sizing: border-box;
  border-left: 1px solid #2e2934;
}
.producer-card .producer-art {
  width: 100%;
  height: 100%;
}
.producer-card .producer-brief {
  margin-top: 6px;
}
.producer-card .producer-brief summary {
  cursor: pointer;
  color: #8b8494;
  font-size: var(--fs-xs);
}
.producer-card .producer-brief pre {
  margin: 6px 0 0;
  padding: 8px;
  border-radius: 8px;
  background: rgba(0, 0, 0, 0.25);
  font-size: var(--fs-2xs);
  overflow-x: auto;
}
@media (max-width:600px) {
  .producer-card .producer-scene {
    width: 68px;
    height: 56px;
  }
}
/* Language tags sit after the genre buttons in the same row, muted. */
/* Colour carries one meaning on a card: a tinted chip is a genre, and a genre
   chip is clickable — it filters the library. A language is a fact about the
   song, not a control, so it stays neutral and reads as quieter than the thing
   beside it you can actually press. */

.tag-language {
  color: #b4aebf;
}

/* MusiCat's bubble. The face sits at the bubble's foot, the tail points at it,
   and the bubble carries its own direction (dir="auto"), so Hebrew reads
   right-to-left while the face stays on the left where MusiCat always is. */
.feed>.msg.bot.musicat-msg {
  display: flex;
  align-items: flex-end;
  gap: 10px;
  padding: 0;
  background: none;
  white-space: normal;
}
.musicat-msg-avatar {
  width: 38px;
  height: 38px;
  flex: none;
  display: grid;
  place-items: center;
  overflow: hidden;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #4a3a6a, #1a1722 70%);
  box-shadow: 0 0 0 1px #3d3550, 0 0 18px rgba(155, 127, 232, 0.35);
}
.musicat-msg-avatar .musicat-art {
  width: 34px;
  height: 34px;
}
.musicat-msg-bubble {
  position: relative;
  min-width: 0;
  padding: 10px 14px;
  border-radius: 18px 18px 18px 6px;
  border: 1px solid #3a3149;
  background: linear-gradient(145deg, #262038, #171420 70%);
  color: #efe9f7;
  line-height: 1.45;
  white-space: pre-wrap;
  text-align: start;
  box-shadow: 0 8px 22px rgba(0, 0, 0, 0.28), inset 0 1px 0 rgba(255, 255, 255, 0.04);
}
/* The tail: a rotated square tucked behind the bubble's bottom-left corner. */
.musicat-msg-bubble::before {
  content: "";
  position: absolute;
  left: -5px;
  bottom: 8px;
  width: 12px;
  height: 12px;
  border-left: 1px solid #3a3149;
  border-bottom: 1px solid #3a3149;
  border-radius: 0 0 0 4px;
  background: #1b1725;
  transform: rotate(45deg);
}
.musicat-msg-meta {
  margin-top: 6px;
  font-size: var(--fs-2xs);
  color: #8b8494;
  direction: ltr;
  text-align: start;
}
.musicat-msg.error .musicat-msg-bubble,
.musicat-msg.error .musicat-msg-bubble::before {
  border-color: #b3603a;
}

/* ===========================================================================
   The next player — #stagePlayer

   A one-column table. Every rule here is namespaced .stage-*, and nothing in
   this block names a class the song card or #globalPlayer uses, so the two
   players cannot reach into each other. That separation is the point: the
   current player is a clone of a card and shares every card selector, which is
   what made a change to one a bug in the other all through this file.
   =========================================================================== */

.stage-player {
  display: none;
}

/* Detached: popped out of the pane into a floating window over the chat, at
   the left. The same box it floated in while the two players were compared side
   by side; the pane is its home now, and this is the state a press on its
   detach control puts it in. */

@media (min-width:1001px) {
  .stage-player.detached:not([hidden]) {
    position: fixed;
    left: calc(var(--sidebar-w,272px) + 24px);
    top: calc(var(--topbar-h,72px) + 12px);
    z-index: 60;
    width: 360px;
    max-height: calc(100vh - var(--topbar-h,72px) - 36px);
    overflow: auto;
    box-shadow: 0 24px 60px #0009;
  }
}

/* Desktop only, beside the chat, the way the current player column is. On a
   phone it stays hidden until it carries what the strip player does. */

@media (min-width:1001px) {
  .stage-player:not([hidden]) {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 14px;
    align-self: stretch;
    padding: 16px;
    border: 1px solid var(--line);
    border-radius: 20px;
    background: linear-gradient(160deg,#17151c,#111016);
  }
}

/* The pane is this player's on desktop. The current player's slot above it is
   empty there — the clone is in its own window — and must take no space, or
   its flex share holds this player a third of the way down. And the pane must
   not clip: the avatar's ears and the note are meant to rise out of the player
   over whatever sits above it. */

@media (min-width:1001px) {
  .shell>.global-player>.global-full:not(:has(.global-full-card)) {
    display: none;
  }
  .shell>.global-player {
    overflow: visible;
  }
}

/* The two homes show different controls: docked, a small detach control in the
   top corner over the avatar; detached, the windows' shared header with the
   close that docks it back. */

.stage-window-top {
  /* Above the avatar: her ears and the note overflow upward over this row, and
     .hero-cover puts her at z-index 3 later in the markup, so at 3 or below a
     press on the header reached her and toggled the song instead of starting
     a drag. */
  position: relative;
  z-index: 4;
  display: grid;
  grid-template-columns: minmax(0,1fr) auto;
  gap: 10px;
  align-items: start;
}

.stage-player:not(.detached) .stage-window-top {
  display: none;
}

.stage-detach {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 2;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: #0008;
  color: var(--muted);
  cursor: pointer;
}

.stage-detach svg {
  width: 16px;
  height: 16px;
}

.stage-detach:hover {
  background: #221f29;
  color: #e6e0ee;
}

.stage-player.detached .stage-detach {
  display: none;
}

/* Row 1 — the avatar. The largest thing here, and the play button: a press
   toggles the song, the same as the scrubber's own button. */

.stage-avatar {
  position: relative;
  display: grid;
  place-items: center;
  width: 100%;
  height: 268px;
  /* visible, not hidden: her ears and the note above her head sit outside this
     box by design, and clipping the avatar to contain the rain cropped her. The
     rain clips itself instead. */
  overflow: visible;
  border-radius: 18px;
  background: radial-gradient(ellipse at 50% 72%,#6f4f8655,transparent 64%);
  cursor: pointer;
}

/* The same figure the current player draws: 250px lifted and scaled a touch, so
   she sits high in the frame with the note clear of her head. Above the rain,
   which is z-index 0 behind her. */

.stage-avatar .musicat-art {
  position: relative;
  z-index: 1;
  width: 250px;
  height: 250px;
  transform: translateY(-6px) scale(1.06);
}

/* The rain falls inside the avatar's box. .note-rain's own placement rule is
   scoped to .global-full-card, so this player states its own; the .note glyphs
   and their fall animation are unscoped and shared. */

.stage-avatar .note-rain {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  border-radius: 18px;
  opacity: 0;
  transition: opacity .9s ease;
  pointer-events: none;
}

/* Only while she is performing: notes falling behind a stopped song would read
   as decoration rather than as playback. */

.stage-player.is-playing .stage-avatar .note-rain {
  opacity: 1;
}

/* Row 2 — the transport, under the avatar and above the title: the control you
   reach for while a song plays comes before the words you read once. */

/* The track rides the avatar's bottom edge rather than clearing it: the scrubber
   reads as part of the artwork it is scrubbing, and the button straddles the
   boundary like a playhead on a record.

   The pull-up is half the row's height plus the column's gap, which lands the
   4px track exactly on the edge whatever the avatar's height is. z-index keeps
   the button above the avatar's own background. */

.stage-transport {
  position: relative;
  z-index: 1;
  margin-top: calc(-20px - 14px);
  /* The seek bar alone, full width; the clock moved down to the control row.
     One column: a second, empty column and its gap left the track 10px short. */
  display: grid;
  grid-template-columns: minmax(0,1fr);
  align-items: center;
}

/* Under the seek bar, one row: play/pause first at the start, a gap, then
   previous, from the beginning and next, and the clock at the end. Controls
   at one end and the readout at the other keep the two roles apart, and the
   seek bar above takes the full width. */

.stage-controls {
  display: flex;
  align-items: center;
  gap: 6px;
  /* Close under the track: the player's row gap is 14px, so this leaves 2px. */
  margin-top: -12px;
}

.stage-controls .stage-toggle {
  margin-right: 2px;
}

/* A short vertical line sets play/pause apart from the step chips. */

.stage-steps {
  display: flex;
  align-items: center;
  gap: 6px;
}

.stage-steps::before {
  content: "";
  width: 1px;
  height: 18px;
  margin-right: 2px;
  background: #35333c;
}

.stage-controls .stage-clock {
  margin-left: auto;
}

.stage-control {
  width: 34px;
  padding: 0;
  color: #cfc9d8;
  cursor: pointer;
  transition: color .16s,filter .16s;
}

.stage-control svg {
  width: 15px;
  height: 15px;
  fill: currentColor;
}

.stage-control:hover:not(:disabled) {
  color: #e6e0ee;
  filter: brightness(1.15);
}

.stage-control:disabled {
  color: #5e5868;
  cursor: default;
}

.stage-seek {
  position: relative;
  min-width: 0;
  display: flex;
  align-items: center;
  height: 40px;
}

.stage-range {
  width: 100%;
  height: 4px;
  margin: 0;
  border-radius: 999px;
  background: linear-gradient(90deg,#b39bf0 var(--stage-played,0%),#2c2c33 var(--stage-played,0%));
  appearance: none;
  cursor: pointer;
}

/* Transparent but the size of the button that rides above it, the way the
   current player's is: the visible marker is .stage-play, while the thumb stays
   the thing a drag actually grabs. A visible thumb would double the playhead. */

.stage-range::-webkit-slider-thumb {
  appearance: none;
  width: 34px;
  height: 34px;
  border: 0;
  background: transparent;
  cursor: grab;
}

.stage-range::-moz-range-thumb {
  width: 34px;
  height: 34px;
  border: 0;
  background: transparent;
  cursor: grab;
}

/* The button sits on the track and travels along it: syncStagePlayer() writes
   its left, so it marks the playhead as well as starting and stopping the song.
   Absolute against .stage-seek, which is why that box is the one measured. */

.stage-play {
  position: absolute;
  left: 0;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: linear-gradient(145deg,#b39bf0,#9b7fe8);
  color: #1a1225;
  box-shadow: 0 4px 12px #0006;
  cursor: grab;
  /* The drag is the app's, not the browser's: without this a press-and-move on a
     touch screen scrolls the page instead of scrubbing. */
  touch-action: none;
  pointer-events: auto;
}

/* Held: the same lit state the current player's button takes while scrubbing. */

.stage-play.dragging {
  cursor: grabbing;
  box-shadow: 0 4px 24px #d4b0ff99;
}

/* At rest the button is a small knob on the track; on hover, focus or a drag it
   grows into the play/pause button. Scaled rather than resized: its box stays
   34px, so the position the sync computes from its width does not jump. */

.stage-play {
  transform: scale(.55);
  transition: transform .18s cubic-bezier(.22,1,.36,1),box-shadow .18s;
}

.stage-play svg {
  width: 16px;
  height: 16px;
  opacity: 0;
  transition: opacity .12s;
}

.stage-seek:hover .stage-play,
.stage-play:focus-visible,
.stage-play.dragging {
  transform: scale(1);
}

.stage-seek:hover .stage-play svg,
.stage-play:focus-visible svg,
.stage-play.dragging svg {
  opacity: 1;
}

/* The same readout the current player's clock is: a recessed chip rather than
   loose text, so the numbers read as an instrument beside the scrubber. */

/* The readout chip: the clock, and the four control chips under the transport,
   one look — recessed, the same border, gradient and inset highlight. */
.stage-clock,
.stage-control {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-height: 28px;
  padding: 0 8px;
  border: 1px solid #35333c;
  border-radius: 9px;
  background: linear-gradient(#1e1d23,#151419);
  box-shadow: inset 0 1px 0 #ffffff0a,0 4px 12px #0003;
  font-size: var(--fs-2xs);
  font-variant-numeric: tabular-nums;
  color: #aaa4b1;
  white-space: nowrap;
}

/* Row 3 — the copy. One left edge for the title, the subtitle and the chips, so
   the eye reads down a single margin. `start`, not `left`: an Arabic or Hebrew
   title aligns to its own side. */

/* The titles and the gauges share one row: the title and its subtitle on the
   left, the meter and the equalizer at their end. The chips are not in this row —
   they are a full-width band of their own below, where a song with five of them
   has the space to show them.

   The copy takes the remaining width and the gauges keep their own, so a long
   title wraps rather than pushing them off the row; this library's titles are
   bilingual and run to two lines often. Centred against each other, so a one-line
   and a two-line title both look deliberate. */

.stage-info {
  display: flex;
  align-items: center;
  gap: 12px;
}

.stage-chips {
  margin-top: -4px;
}

.stage-copy {
  flex: 1;
  min-width: 0;
  text-align: start;
  cursor: pointer;
}

.stage-title {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  overflow-wrap: anywhere;
  font-size: var(--fs-lg);
  line-height: 1.3;
}

.stage-subtitle {
  margin-top: 3px;
  font-size: var(--fs-xs);
  color: var(--muted);
}

/* One line of chips, clipped with a fade, and the tags control at the end:
   the tags window has all of them. */

.stage-chips {
  display: flex;
  align-items: center;
  gap: 6px;
}

.stage-chips-row {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  mask-image: linear-gradient(90deg,#000 calc(100% - 28px),transparent);
}

.stage-chips .card-tags {
  flex-wrap: nowrap;
  justify-content: flex-start;
  white-space: nowrap;
  /* The card gives its chip row a top margin; in this row the chips sit level
     with the tags control beside them. */
  margin-top: 0;
}

/* A chip itself (.tag: the same padding, border, fill and pill curve as the
   chips beside it), holding the pencil at the chips' text size. */

.stage-tags-edit {
  flex: none;
  display: grid;
  place-items: center;
  /* 4px, not the chip's 5px: the pencil is 11px where the chip's text line is
     9px, so this is what makes the two the same 21px. */
  padding: 4px 8px;
  line-height: 1;
  cursor: pointer;
}

.stage-tags-edit svg {
  width: 11px;
  height: 11px;
}

.stage-tags-edit:hover {
  color: #e6e0ee;
  border-color: #6f57b8;
}

/* A toggle: lit while the tags panel is open, in the genre chip's accent. */

.stage-tags-edit[aria-pressed="true"] {
  border-color: #8f74d8;
  background: #2d2440;
  color: #d9c9ff;
}

.stage-tags-edit:hover {
  color: #e6e0ee;
  border-color: #6f57b8;
}

/* The action row: the counts on the left, the actions, and the "..." at the end
   — the same row the current player has, built from the same .player-stats and
   .player-action classes, which are unscoped and dress themselves. Same
   actions, same meaning, one owner: reuse, not a copy. Like, Dislike, Save,
   Remix and Download forward to the source card's own buttons; Lyrics opens the
   lyrics window, a surface of its own. */

.stage-actions {
  display: flex;
  align-items: center;
  gap: 3px;
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid #2e2b34;
}

/* The Lyrics button lit while its window is open, in the row's accent. */

.stage-actions .player-action.active {
  color: #b78cff;
}

/* The "..." panel. The shared placement (top:46px, right:14px) is tuned for a
   card's header; here the button sits in the action row at the player's foot,
   so the panel opens upward from it. The player is position:fixed, which is
   what the panel's absolute placement resolves against. */

.stage-player .commands-panel {
  top: auto;
  bottom: 58px;
  right: 16px;
}

/* Row 4 — the visuals. The one row whose contents sit side by side, because the
   meter and the equalizer are read together as one gauge. */

.stage-visuals {
  display: flex;
  align-items: flex-end;
  gap: 10px;
  height: 42px;
  flex: none;
}

/* The same instrument the current player shows: two segmented columns, mint for
   the left channel and purple for the right, lit from the floor. The ladder is a
   repeating gradient rather than drawn segments, so the lit part clips against
   the same rhythm as the unlit track behind it. */

.stage-meter {
  width: 34px;
  min-width: 34px;
  height: 42px;
  display: grid;
  grid-template-columns: repeat(2,1fr);
  gap: 4px;
  color: #8d8794;
  font: 7px/1 ui-monospace,monospace;
}

.stage-meter-channel {
  display: grid;
  grid-template-rows: 1fr 8px;
  gap: 2px;
  text-align: center;
}

.stage-level {
  position: relative;
  width: 10px;
  height: 31px;
  margin: auto;
  border-radius: 4px;
  background: repeating-linear-gradient(0deg,#2d2b32 0 3px,transparent 3px 5px);
}

/* Lit from the floor: the analyser writes --level, a height here because these
   columns stand up. */

.stage-level:after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: var(--level,2%);
  background: repeating-linear-gradient(0deg,#8ee0ba 0 3px,transparent 3px 5px);
  transition: height 70ms linear;
}

.stage-level.stage-right:after {
  background: repeating-linear-gradient(0deg,#d5b3fb 0 3px,transparent 3px 5px);
}

.stage-meter-channel b {
  font-weight: 400;
}

.stage-eq {
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 42px;
  padding: 4px 5px;
  /* A button now — it opens the equalizer — drawn as the bars it always was. */
  border: 1px solid transparent;
  border-radius: 8px;
  background: transparent;
  cursor: pointer;
  transition: border-color .16s,background .16s;
}

.stage-eq:hover,
.stage-eq[aria-pressed="true"] {
  border-color: #6f57b8;
  background: #1f1a2c;
}

/* The bars' percentage heights resolve against .stage-eq's own box, so nothing
   here may override that height — doing exactly that on the card's equalizer
   flattened every bar to 3px whatever the level said. */

.stage-eq i {
  width: 5px;
  height: var(--eq-level,6%);
  border-radius: 3px 3px 1px 1px;
  background: repeating-linear-gradient(0deg,#8ee0ba 0 3px,transparent 3px 5px);
}

/* ===========================================================================
   The lyrics window — #lyricsWindow

   Its own surface, namespaced .lyrics-window-*. It borrows .lyrics-scene and
   .player-lyrics, which are the lyric list's own styles and belong to the list
   rather than to a player, and adds nothing that either player's rules can see.
   =========================================================================== */

.lyrics-window {
  display: none;
}

@media (min-width:1001px) {
  /* A panel in the dock: it fills the dock's body — under the player, the
     height left in the pane; detached, the window's height, which resizes —
     and the list scrolls inside. The dock owns the box, the bar and the grip. */
  .lyrics-window:not([hidden]) {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
    overflow: hidden;
  }
}

/* The header: title, the follow control, close. Its grip, kicker, title and
   close chrome are the floating windows' shared rules (.float-window-*); only
   the three-column layout is this window's own. */

/* The panel's own tool row: the follow toggle, at the end, above the list. */

.lyrics-window-top {
  display: flex;
  justify-content: flex-end;
  padding: 10px 16px 0;
}

/* The follow control. Pressed by default — the words track the vocals — and it
   lifts as soon as the reader scrolls, so the button shows the state rather than
   just setting it. Pressing it again catches the words up to the song. */

.lyrics-window-follow {
  align-self: center;
  padding: 5px 10px;
  border: 1px solid #4a3f5c;
  border-radius: 999px;
  background: #241f2e;
  color: #d4baf7;
  font: 600 var(--fs-2xs)/1 Inter,system-ui,sans-serif;
  white-space: nowrap;
  cursor: pointer;
  transition: background .16s,color .16s,border-color .16s;
}

.lyrics-window-follow:hover {
  background: #2c2639;
}

/* Suspended: the reader has scrolled away and the words are staying put. */

/* .lyrics-follow-status, which this button also carries when the song is timed,
   sets its colour with !important and draws the live dot. Both are overridden
   here so the suspended state actually reads as suspended. */

.lyrics-window-follow.is-off {
  border-color: #35333c;
  background: #1a1920;
  color: var(--muted) !important;
}

.lyrics-window-follow.is-off:before {
  opacity: .35;
  animation: none;
}

/* The list scrolls inside the window, and it must ACTUALLY scroll: the follower
   advances by setting scrollTop, so a list whose box is as tall as its content
   has nowhere to scroll to. The active line then walks off the bottom and the
   lyrics look like they stopped after the first verse — which is exactly what
   `max-height: none; height: 100%` here caused, the content height becoming the
   box height.

   .player-lyrics is the scroll box, because that is the element handed to
   setupLyricsFollowing() and the one whose scrollTop it writes. The scene only
   gives it a bounded height to scroll within: flex:1 to take what the header
   leaves, min-height:0 so it may shrink below its content. */

/* The list is an ordinary scroll box. Its class is .lyrics-window-list, not
   .player-lyrics, on purpose: every transform-and-slot rule that animates the
   card's lyrics is scoped under .player-lyrics, and none of it applies here. The
   lines lay out as plain text, the box scrolls, and the window scrolls it to the
   active line itself — which is what lets a reader scroll it too. */

/* The same look as the card's lyrics tray — the slot height, the typography,
   the emphasis on the sung line and its neighbours, the sticky section pill —
   copied from the .player-lyrics rules that produce it, so the two read as one
   thing. The window's own advance() assigns the same classes the card's follower
   does (active, lyric-previous, lyric-next, -two, lyric-past, lyric-future), so
   these rules mean the same here.

   One deliberate difference: the tray hides everything beyond two lines from
   the sung one (opacity 0), because its stage only ever shows five slots. Here
   the list scrolls, so far lines stay dim but readable — a reader who scrolls
   away must have something to read. While following, the box centres the sung
   line, which shows exactly the tray's five-slot neighbourhood. */

/* The tray sizes the scene to exactly five slots. Here that is the starting
   height: the scene is the flex child that takes whatever height the window is
   pulled to, up or down, and the list scrolls inside it. The follower anchors
   the sung line at a fraction of the list's real height, so a taller list
   keeps it in place with more lines around it. */

.lyrics-window .lyrics-panel {
  /* The follower's anchor ancestor, and the flex link between the window and
     the scene: a plain block here would keep the scene at its basis whatever
     the window is pulled to. */
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  padding: 10px 16px 14px;
}

.lyrics-window .lyrics-scene {
  flex: 1 1 calc(var(--lyric-slot-height,64px)*5);
  min-height: 0;
}

.lyrics-window-list {
  --lyric-slot-height: 64px;
  /* Positioned, because the follower's cueTop() walks offsetParent up to its
     root: an unpositioned root is never reached and the walk overshoots. */
  position: relative;
  height: 100%;
  overflow: auto;
  /* The tray's edge fade (.lyrics-following), so lines dissolve at the top and
     bottom instead of clipping. */
  mask-image: linear-gradient(transparent,#000 16%,#000 84%,transparent);
  padding: 0 7px 0 2px;
  color: #e9e5ed;
  font-size: var(--fs-base);
  line-height: 1.7;
  white-space: pre-wrap;
  scrollbar-width: thin;
}

/* A five-slot tail, as the tray has, so the last lines can reach the centre. */

.lyrics-window-list:after {
  content: "";
  display: block;
  height: calc(var(--lyric-slot-height)*5);
  pointer-events: none;
}

.lyrics-window-list .section {
  position: relative;
  margin: 0;
}

/* Section names are not drawn inline; the sticky pill carries the section. */

.lyrics-window-list .section-name {
  display: none;
}

.lyrics-window-list .lyric-line {
  position: relative;
  z-index: 1;
  box-sizing: border-box;
  display: flex;
  height: var(--lyric-slot-height);
  min-height: var(--lyric-slot-height);
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  overflow: hidden;
  padding: 6px 10px;
  border-left: 2px solid transparent;
  color: #807986;
  opacity: .26;
  font-size: 1em;
  line-height: 1.18;
  transform-origin: left center;
  transform: translateY(8px) scale(.95);
  filter: blur(.7px);
  transition: color 1.8s cubic-bezier(.22,1,.36,1),opacity 1.8s cubic-bezier(.22,1,.36,1),border-color 1.8s cubic-bezier(.22,1,.36,1),text-shadow 1.8s cubic-bezier(.22,1,.36,1),font-size 1.8s cubic-bezier(.22,1,.36,1),transform 1.8s cubic-bezier(.22,1,.36,1),filter 1.8s cubic-bezier(.22,1,.36,1);
}

/* Far lines: the tray's transforms, but readable rather than hidden. */

.lyrics-window-list .lyric-line.lyric-past {
  transform: translateY(-6px) scale(.96);
  filter: blur(.5px);
}

.lyrics-window-list .lyric-line.lyric-future {
  transform: translateY(6px) scale(.96);
  filter: blur(.5px);
}

.lyrics-window-list .lyric-line.lyric-previous-two,
.lyrics-window-list .lyric-line.lyric-next-two {
  color: #938b99;
  opacity: .42;
  transform: translateY(0) scale(.96);
  filter: blur(.25px);
}

.lyrics-window-list .lyric-line.lyric-previous,
.lyrics-window-list .lyric-line.lyric-next {
  color: #bbb3c1;
  opacity: .68;
  transform: translateY(0) scale(.98);
  filter: blur(.2px);
}

/* The line being sung. */

.lyrics-window-list .lyric-line.active {
  color: #fff;
  opacity: 1;
  font-size: 1.25em;
  border-left-color: #caa7f2;
  text-shadow: 0 0 18px #c79bf655;
  transform: translateY(0) scale(1);
  filter: none;
}

.lyrics-window-list .lyric-line.active .lyric-translation {
  color: #cfbadf;
  opacity: .92;
}

/* The line the roll is carrying in: it wears the neighbour look until the roll
   settles, when the follower promotes it to active — the tray's hand-off. */

.lyrics-window-list .lyric-line.lyric-arriving {
  color: #bbb3c1;
  opacity: .68;
  transform: translateY(0) scale(.98);
  filter: none;
}

/* The outro: the follower rolls to the end and retires the pill. */

.lyrics-window-list.lyrics-outro .lyrics-section-sticky {
  opacity: 0;
  transform: translateY(-10px);
  pointer-events: none;
}

.lyrics-window-list .lyric-gap {
  display: block;
  height: .7em;
}

/* ===========================================================================
   Floating windows — shared chrome

   The lyrics window and the share window are siblings: one owner, and they
   must look alike, so their grip, kicker, title and close are one set of rules.
   Each window keeps its own position, size and header layout.
   =========================================================================== */

/* Dragged by the header, so the header is the grip. touch-action keeps a drag
   from scrolling the page behind it. */

.float-window-grip {
  cursor: grab;
  touch-action: none;
}

.float-window.dragging .float-window-grip {
  cursor: grabbing;
}

.float-window-kicker {
  font-size: var(--fs-2xs);
  letter-spacing: .12em;
  color: var(--muted);
}

.float-window-title strong {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  overflow-wrap: anywhere;
  font-size: var(--fs-sm);
  line-height: 1.3;
}

.float-window-close {
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--muted);
  font-size: var(--fs-lg);
  line-height: 1;
  cursor: pointer;
}

.float-window-close:hover {
  background: #221f29;
  color: #e6e0ee;
}

/* The panel bar: a raised title bar for the panels that dock under the player
   (the lyrics, the equalizer). Docked, it shows the dock's tabs and a detach
   control; detached, its own kicker and a dock control. One set of rules, so
   the panels are one look. */

.panel-top {
  display: grid;
  grid-template-columns: auto minmax(0,1fr) auto auto auto;
  gap: 10px;
  align-items: center;
  padding: 8px 10px 8px 14px;
  background: linear-gradient(180deg,#24202e,#1b1823);
  border-bottom: 1px solid #35333c;
  box-shadow: inset 0 1px 0 #ffffff0d;
}

.panel-top .panel-kicker {
  color: #b6b0c2;
}

.panel-title {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: var(--fs-sm);
  color: #e6e0ee;
}

.panel-tabs {
  display: inline-flex;
  gap: 2px;
  padding: 2px;
  border: 1px solid #35333c;
  border-radius: 999px;
  background: #15141a;
}

.panel-tab {
  height: 24px;
  padding: 0 10px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: #8f89a0;
  font-size: var(--fs-2xs);
  font-weight: 600;
  letter-spacing: .04em;
  cursor: pointer;
  transition: background .16s,color .16s;
}

.panel-tab:hover {
  color: #e6e0ee;
}

.panel-tab.active {
  background: #2d2440;
  color: #d9c9ff;
}

.panel-detach,
.panel-dock {
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: #b6b0c2;
  display: grid;
  place-items: center;
  cursor: pointer;
}

.panel-detach svg,
.panel-dock svg {
  width: 15px;
  height: 15px;
}

.panel-detach:hover,
.panel-dock:hover {
  background: #221f29;
  color: #e6e0ee;
}

/* The dock: one unit under the player, tabs and a body; the body takes the
   height left in the pane. Its bar is the tabs, then detach and close
   together at the end. */

.dock {
  display: none;
}

@media (min-width:1001px) {
  .dock:not([hidden]) {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
    margin-top: 12px;
    border: 1px solid var(--line);
    border-radius: 18px;
    background: linear-gradient(160deg,#17151c,#111016);
    overflow: hidden;
  }
}

/* The bars are flex rows, so a control that is hidden leaves no hole and the
   controls stay together at the end. */

.dock-top,
.frame-top {
  display: flex;
  align-items: center;
  gap: 8px;
}

.dock-top .panel-tabs {
  margin-right: auto;
}

.frame-top .panel-title {
  flex: 1;
}

.dock-body,
.frame-body {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* A floating frame: one detached panel in a window of its own — the song's
   title, dock and close — draggable and resizable. Each panel has a spot of
   its own to start from, so two frames do not land on each other. */

.float-frame {
  display: none;
}

@media (min-width:1001px) {
  .float-frame {
    position: fixed;
    z-index: 70;
    display: flex;
    flex-direction: column;
    border: 1px solid var(--line);
    border-radius: 18px;
    background: linear-gradient(160deg,#17151c,#111016);
    box-shadow: 0 24px 60px #000a;
    overflow: hidden;
    resize: both;
    min-width: 320px;
    min-height: 280px;
    max-width: calc(100vw - 48px);
    max-height: calc(100vh - 48px);
  }
  .float-frame.frame-lyrics {
    right: 24px;
    bottom: 24px;
    width: 340px;
    height: 460px;
  }
  /* The equalizer's content is a fixed instrument, so its frame is not
     resizable; the lyrics' and the tags' frames are. */
  .float-frame.frame-equalizer {
    left: calc(var(--sidebar-w,272px) + 24px);
    bottom: 24px;
    width: 440px;
    height: auto;
    resize: none;
  }
  .float-frame.frame-tags {
    left: calc(var(--sidebar-w,272px) + 24px);
    top: calc(var(--topbar-h,72px) + 12px);
    width: 420px;
    height: 480px;
  }
}

/* ===========================================================================
   The share window — #shareWindow

   Its own surface. It borrows .share-grid, .share-target, .share-bubble and
   .tray-status, which are the card's share controls' own unscoped styles —
   same controls, same meaning — and adds nothing a player's rules can see.
   =========================================================================== */

.share-window {
  display: none;
}

/* Desktop only, like the lyrics window, and parked above it by default so the
   two can be open together; both are draggable. */

@media (min-width:1001px) {
  .share-window:not([hidden]) {
    position: fixed;
    right: 24px;
    bottom: calc(52vh + 44px);
    z-index: 71;
    display: flex;
    flex-direction: column;
    gap: 12px;
    width: 340px;
    padding: 14px 16px;
    border: 1px solid var(--line);
    border-radius: 18px;
    background: linear-gradient(160deg,#17151c,#111016);
    box-shadow: 0 24px 60px #000a;
  }
}

/* The header: title, close. */

.share-window-top {
  display: grid;
  grid-template-columns: minmax(0,1fr) auto;
  gap: 10px;
  align-items: start;
}

.share-window .tray-status {
  min-height: 1.2em;
}
/* ===========================================================================
   The current player — #legacyWindow

   A floating window around the cloned card, on desktop only. Its chrome is the
   floating windows' shared set; the card inside keeps every rule it has. The
   tray-open rules that let the card's lyrics fill the pane name this window as
   a second host (60-desktop.css), so the tray fills the window the same way;
   what this file adds is the box and the flex chain from the window to the
   card, the counterpart of the pane's own chain.
   =========================================================================== */

.legacy-window {
  display: none;
}

@media (min-width:1001px) {
  .legacy-window:not([hidden]) {
    position: fixed;
    /* To the right of the spot the next player detaches to, so both can be out. */
    left: calc(var(--sidebar-w,272px) + 24px + 384px);
    top: calc(var(--topbar-h,72px) + 12px);
    z-index: 59;
    display: flex;
    flex-direction: column;
    gap: 10px;
    width: 392px;
    max-height: calc(100vh - var(--topbar-h,72px) - 36px);
    padding: 14px 16px;
    border: 1px solid var(--line);
    border-radius: 18px;
    background: linear-gradient(160deg,#17151c,#111016);
    box-shadow: 0 24px 60px #000a;
  }
  /* An open tray takes the slack, so the window has to have a height to give:
     the pane is a grid cell of a fixed height; the window takes the same. */
  .legacy-window.tray-open:not([hidden]) {
    height: calc(100vh - var(--topbar-h,72px) - 36px);
  }
}

.legacy-window-top {
  display: grid;
  grid-template-columns: minmax(0,1fr) auto;
  gap: 10px;
  align-items: start;
}

.legacy-window-card {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.legacy-window-card>.global-full-card {
  flex: 1;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
/* ===========================================================================
   The equalizer — #eqPanel

   Docked under the next player in the pane, or detached as a floating window
   in the shared chrome. Seven bands: a fader (a range input written
   vertically, its cap a mixing-desk fader) and a ladder in the meter's
   segmented stripe, green low, yellow, orange, red at the top, lit to the
   band's level. Its own classes throughout, eq-*; nothing here names a player.
   =========================================================================== */

.eq-panel {
  display: none;
}

@media (min-width:1001px) {
  /* A panel in the dock, which owns the box, the bar and the grip. */
  .eq-panel:not([hidden]) {
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }
}

/* The title bar is the shared .panel-top (float-windows.css). Docked, the
   panel is a little shorter — the pane's height is what it is — so the faders
   lose 20px and the paddings tighten. */

.dock .eq-column,
.dock .eq-fader,
.dock .eq-ladder {
  height: 130px;
}

.dock .eq-bands {
  padding-top: 12px;
}

.dock .eq-foot {
  padding: 8px 14px 12px;
}

/* The bands. */

.eq-bands {
  display: grid;
  grid-template-columns: repeat(7,minmax(0,1fr));
  gap: 6px;
  padding: 18px 14px 0;
}

.eq-band {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}

.eq-column {
  display: flex;
  align-items: flex-end;
  gap: 5px;
  height: 150px;
}

/* The fader: a range input written vertically, max at the top. The track is
   the input's own box; the cap is the thumb, drawn as a mixing-desk fader. */

.eq-fader {
  writing-mode: vertical-lr;
  direction: rtl;
  -webkit-appearance: none;
  appearance: none;
  width: 14px;
  height: 150px;
  margin: 0;
  padding: 0;
  border: 1px solid #35333c;
  border-radius: 7px;
  background: linear-gradient(#3a3446,#3a3446) no-repeat center / 2px calc(100% - 16px),#1c1922;
  cursor: pointer;
}

.eq-fader::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 24px;
  height: 28px;
  border: 0;
  border-radius: 6px;
  background: linear-gradient(#2a1f3d,#2a1f3d) no-repeat center / 14px 2px,linear-gradient(180deg,#cdbdf7 0%,#a98ff0 48%,#8a6fd6 52%,#6f57b8 100%);
  box-shadow: 0 5px 12px #000a,inset 0 1px 0 #ffffff66,inset 0 -1px 0 #00000055;
  cursor: grab;
}

.eq-fader::-moz-range-thumb {
  width: 24px;
  height: 28px;
  border: 0;
  border-radius: 6px;
  background: linear-gradient(#2a1f3d,#2a1f3d) no-repeat center / 14px 2px,linear-gradient(180deg,#cdbdf7 0%,#a98ff0 48%,#8a6fd6 52%,#6f57b8 100%);
  box-shadow: 0 5px 12px #000a,inset 0 1px 0 #ffffff66,inset 0 -1px 0 #00000055;
  cursor: grab;
}

.eq-fader:focus-visible {
  outline: 2px solid #9b7fe8;
  outline-offset: 2px;
}

/* The ladder: the meter's stripe, one segment per span, coloured by position
   and lit by level. */

.eq-ladder {
  display: flex;
  flex-direction: column-reverse;
  gap: 3px;
  width: 10px;
  height: 150px;
  padding: 4px 0;
  box-sizing: border-box;
}

.eq-ladder i {
  flex: 1;
  border-radius: 2px;
  background: #25232c;
  transition: background .08s;
}

.eq-ladder i.lit {
  background: var(--seg,#5dcaa5);
}

.eq-ladder i:nth-child(n+9) {
  --seg: #f2d16b;
}

.eq-ladder i:nth-child(n+12) {
  --seg: #ef9f27;
}

.eq-ladder i:nth-child(n+14) {
  --seg: #e24b4a;
}

.eq-label {
  font-size: var(--fs-2xs);
  font-variant-numeric: tabular-nums;
  color: #cfc9d8;
}

/* The gain readout: the same recessed chip the player's clock is. */

.eq-readout {
  min-height: 20px;
  padding: 0 6px;
  border: 1px solid #35333c;
  border-radius: 7px;
  background: linear-gradient(#1e1d23,#151419);
  box-shadow: inset 0 1px 0 #ffffff0a,0 4px 12px #0003;
  font-size: 10px;
  line-height: 20px;
  color: #cfc9d8;
  font-variant-numeric: tabular-nums;
}

.eq-foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  padding: 10px 14px 14px;
}

.eq-note {
  flex: 1;
  font-size: var(--fs-2xs);
  color: var(--muted);
  text-align: center;
}

/* The preset list: a select in the readout chip's dress. */

.eq-preset {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-2xs);
  color: #8f89a0;
}

.eq-preset-select {
  min-height: 28px;
  padding: 0 24px 0 8px;
  border: 1px solid #35333c;
  border-radius: 9px;
  background: linear-gradient(#1e1d23,#151419);
  color: #e6e0ee;
  font: inherit;
  font-size: var(--fs-2xs);
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238f89a0' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='m6 9 6 6 6-6'/></svg>"),linear-gradient(#1e1d23,#151419);
  background-repeat: no-repeat;
  background-position: right 6px center,0 0;
  background-size: 14px 14px,100% 100%;
}

.eq-preset-select:focus-visible {
  outline: 2px solid #9b7fe8;
  outline-offset: 2px;
}

.eq-reset {
  padding: 6px 12px;
  font-size: var(--fs-xs);
}
/* ===========================================================================
   The tags panel — #tagsWindow

   A panel in the dock, which owns the box, the bar and the grip: one add row
   at the top, then four groups of chips with a remove on each, then a status
   line. Its own classes, tags-*; the chips are the song card's .tag so a
   genre looks the same here as on the card.
   =========================================================================== */

.tags-window {
  display: none;
}

@media (min-width:1001px) {
  .tags-window:not([hidden]) {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
    overflow: hidden;
  }
}

/* The add row: a group, a field, Add. */

.tags-add-row {
  display: flex;
  gap: 6px;
  padding: 12px 16px 0;
}

.tags-group-select {
  height: 32px;
  padding: 0 24px 0 8px;
  border: 1px solid #35333c;
  border-radius: 9px;
  background: linear-gradient(#1e1d23,#151419);
  color: #e6e0ee;
  font: inherit;
  font-size: var(--fs-xs);
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238f89a0' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='m6 9 6 6 6-6'/></svg>"),linear-gradient(#1e1d23,#151419);
  background-repeat: no-repeat;
  background-position: right 6px center,0 0;
  background-size: 14px 14px,100% 100%;
}

.tags-add {
  flex: 1;
  min-width: 0;
  height: 32px;
  padding: 0 10px;
  border: 1px solid #35333c;
  border-radius: 9px;
  background: #15141a;
  color: #e6e0ee;
  font: inherit;
  font-size: var(--fs-xs);
}

.tags-add:focus-visible,
.tags-group-select:focus-visible {
  outline: 2px solid #9b7fe8;
  outline-offset: 1px;
}

.tags-add-button {
  height: 32px;
  padding: 0 12px;
  font-size: var(--fs-xs);
}

.tags-body {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 12px 16px 4px;
  overflow-y: auto;
  scrollbar-width: thin;
}

.tags-group {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.tags-group h4 {
  margin: 0;
  font-size: var(--fs-2xs);
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: #8f89a0;
}

.tags-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  min-height: 26px;
}

.tags-none {
  font-size: var(--fs-2xs);
  color: var(--muted);
  line-height: 26px;
}

.tags-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding-right: 4px;
}

.tags-remove {
  width: 16px;
  height: 16px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: #0006;
  color: inherit;
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
}

.tags-remove:hover {
  background: #000a;
}

.tags-status {
  min-height: 18px;
  padding: 4px 16px 10px;
  font-size: var(--fs-2xs);
  color: var(--muted);
}
