/*
 * style.css — Panneau du lecteur.
 *
 * TROIS tailles, pas deux. Le cadre d'une Activity vaut :
 *   · ~1550 × 735  en panneau de salon vocal (ordinateur) ;
 *   · ~300 × 180   en fenêtre flottante, quand on détache le lecteur ;
 *   · plein écran en portrait sur iOS et Android.
 * Une seule mise en page ne couvre pas les trois : la fenêtre flottante
 * n'affichait qu'une pochette géante et rien d'utilisable.
 *
 * L'iframe est transparente par défaut : le fond est peint explicitement,
 * sinon le panneau hérite du thème de l'hôte et devient illisible.
 */

:root {
  /* ════════════════════════════════════════════════════════════════════════
   * IDENTITÉ KIRBY
   *
   * Le rose #FF9EBD est la couleur du bot : elle ne se remplace pas. Tout le
   * système est construit AUTOUR d'elle, et c'est ce qui distingue ce panneau
   * d'un gris neutre sur lequel on aurait posé un accent.
   *
   * Le fond n'est donc pas un noir bleuté (#0b0b0e) mais un noir très
   * légèrement TEINTÉ vers le violet. L'écart est minime — la luminance
   * bouge à peine, les contrastes mesurés restent valables — mais il suffit
   * à ce que le rose se lise comme une intention plutôt que comme une tache
   * sur du gris. Les surfaces montent dans la même teinte, jamais en gris pur.
   * ════════════════════════════════════════════════════════════════════════ */

  --brand:       #FF9EBD;
  --brand-ink:   #2a0d16;                   /* texte posé SUR le rose */
  --brand-soft:  rgba(255, 158, 189, .16);
  --brand-dim:   rgba(255, 158, 189, .09);
  --brand-hover: #ffb3c9;                   /* survol : on éclaircit, jamais on ne sature */

  /* ── Surfaces ─────────────────────────────────────────────────────────── */
  --bg:        #0b0a0e;                     /* fond de page */
  --surface-1: #121016;                     /* ossature : rail, barre de lecture */
  --surface-2: #17151d;                     /* panneau posé sur le fond */
  --surface-3: #201d28;                     /* élément élevé : carte au repos */
  --surface:   rgba(255, 255, 255, .05);    /* voile, sur fond inconnu */
  --hover:     rgba(255, 255, 255, .07);
  --active:    rgba(255, 255, 255, .11);

  /* ── Traits ───────────────────────────────────────────────────────────── */
  --stroke: rgba(255, 255, 255, .08);
  --line:   rgba(255, 255, 255, .06);       /* séparation discrète */
  --edge:   rgba(255, 255, 255, .05);       /* filet de lumière en haut d'une surface */

  /* ── Texte ────────────────────────────────────────────────────────────── */
  --text: #f6f5f8;                          /* primaire */
  --dim:  #a4a1ae;                          /* secondaire */
  /*
   * MESURÉ au rendu réel (Chrome 131, WCAG sur le fond EFFECTIF de chaque
   * élément, couches semi-transparentes composées) : #6d707a donnait 3,98 sur
   * le fond de page, 3,90 dans la barre et 3,17 sur l'indice clavier — sous
   * les 4,5 exigés par AA. #8a8d97 est la valeur la plus SOMBRE qui passe
   * partout. Ne pas l'assombrir sans remesurer.
   */
  --faint: #8a8d97;                         /* tertiaire : métadonnées */

  /* ── États ────────────────────────────────────────────────────────────── */
  --ok:    #6ee7a8;
  --warn:  #ffd08a;
  --error: #ff8a8a;

  /* ── Espacement ───────────────────────────────────────────────────────── *
   * Une échelle, et rien en dehors. Avant, les marges allaient de 3 à 34 px
   * au jugé — d'où des respirations qui ne se répondaient jamais.            */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px; --s5: 20px;
  --s6: 24px; --s7: 32px; --s8: 40px; --s9: 48px; --s10: 64px;

  /* ── Rayons ───────────────────────────────────────────────────────────── *
   * Une hiérarchie : plus l'élément est grand, plus son rayon l'est. Un même
   * rayon partout aplatit la lecture.                                        */
  --r-xs: 6px; --r-sm: 10px; --r-md: 14px; --r-lg: 18px; --r-xl: 24px;
  --r-full: 999px;

  /* ── Profondeur ───────────────────────────────────────────────────────── *
   * Trois niveaux, jamais plus. Une ombre porte une élévation, pas un effet. */
  --e1: 0 1px 2px rgba(0,0,0,.30);
  --e2: 0 6px 18px -6px rgba(0,0,0,.55);
  --e3: 0 18px 44px -12px rgba(0,0,0,.70);
  /* Signature Kirby : un halo rosé TRÈS retenu, réservé à ce qui joue. */
  --glow: 0 10px 40px -14px rgba(255,158,189,.55);

  /* ── Typographie ──────────────────────────────────────────────────────── *
   * Mesuré avant : trois tailles sur tout l'accueil (16,8 / 12,9 / 11,1 px),
   * donc aucune hiérarchie lisible. Une vraie échelle, du titre de page à la
   * métadonnée.                                                              */
  --t-hero:  2.1rem;   /* titre de page */
  --t-h1:    1.5rem;   /* en-tête de fiche */
  --t-h2:    1.12rem;  /* titre de section */
  --t-body:  .92rem;   /* ligne de liste */
  --t-card:  .9rem;    /* titre de carte */
  --t-meta:  .78rem;   /* artiste, durée */
  --t-micro: .69rem;   /* intitulé de groupe, badge */

  --rail: 208px;

  --ease: cubic-bezier(.22, .68, .32, 1);
  --ease-out: cubic-bezier(.16, 1, .3, 1);

  /* Teintes de la pochette — app.js les réécrit, la marque reste le repli. */
  --amb-a: #FF9EBD;
  --amb-b: #8a5cff;
}

* { box-sizing: border-box; }

/*
 * Hauteur réservée par la barre de lecture, lue par le rail ET par les vues.
 * Déclarée ici pour n'exister qu'à UN endroit : trois règles la reprenaient
 * chacune avec sa propre valeur en dur.
 */
body { --bar-h: 0px; }
body:has(.mini:not([hidden])) { --bar-h: 76px; }
@media (max-width: 879px) { body:has(.mini:not([hidden])) { --bar-h: 64px; } }

/*
 * PIÈGE : `[hidden] { display: none }` vient de la feuille du NAVIGATEUR, donc
 * la moindre règle d'auteur posant un `display` la bat — et les quatre écrans
 * du panneau s'empilaient. Cette règle rend son sens à l'attribut.
 */
[hidden] { display: none !important; }

html, body {
  margin: 0; height: 100%;
  background: var(--bg); color: var(--text);
  font-family: "gg sans", "Noto Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 15px;
  overflow: hidden; overscroll-behavior: none;
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;   /* iOS : pas de flash gris au tap */
}

svg { width: 1em; height: 1em; display: block; fill: currentColor; }

::selection { background: var(--brand); color: var(--brand-ink); }

/* ══ Ambiance ══════════════════════════════════════════════════════════════ */

/*
 * Trois halos dérivant lentement, dont un TOUJOURS à la couleur du bot : la
 * pochette donne l'ambiance, la marque reste présente quelle que soit l'image.
 * La version précédente à 30 % d'opacité était invisible sur fond noir — d'où
 * ces valeurs franches.
 */
.ambient { position: fixed; inset: 0; z-index: -2; overflow: hidden; background: var(--bg); }

/*
 * PERFORMANCE — mesurée, pas supposée. La version précédente empilait trois
 * <span> de ~900 px avec `filter: blur(70px)`, animés en continu : le
 * navigateur refloutait cette surface À CHAQUE IMAGE.
 *
 *     tel quel ................. 15 i/s
 *     sans les halos flous ..... 60 i/s
 *     (grain et backdrop-filter : aucun effet mesurable)
 *
 * Un `radial-gradient` EST un halo doux, nativement, sans le moindre flou à
 * calculer : il est peint une fois puis mis en cache. Et l'animation ne porte
 * que sur `transform`, que le compositeur traite sans jamais repeindre.
 * Même rendu, quatre fois plus fluide.
 */
.blob {
  position: absolute; display: block; border-radius: 50%;
  will-change: transform;
  transition: background 1.4s var(--ease);
}
.blob-a {
  width: 92vmax; height: 92vmax; top: -34vmax; left: -26vmax;
  background: radial-gradient(closest-side, var(--amb-a) 0%, transparent 72%);
  opacity: .55; animation: drift-a 26s ease-in-out infinite alternate;
}
.blob-b {
  width: 84vmax; height: 84vmax; bottom: -34vmax; right: -24vmax;
  background: radial-gradient(closest-side, var(--amb-b) 0%, transparent 72%);
  opacity: .48; animation: drift-b 32s ease-in-out infinite alternate;
}
.blob-c {
  width: 64vmax; height: 64vmax; top: 18%; left: 30%;
  background: radial-gradient(closest-side, var(--brand) 0%, transparent 70%);
  opacity: .30; animation: drift-c 40s ease-in-out infinite alternate;
}

@keyframes drift-a { to { transform: translate3d(10vmax, 7vmax, 0) scale(1.12); } }
@keyframes drift-b { to { transform: translate3d(-9vmax, -6vmax, 0) scale(1.08); } }
@keyframes drift-c { to { transform: translate3d(-12vmax, 9vmax, 0) scale(.86); } }

/* Voile sombre : sans lui, le texte perd son contraste sur les halos vifs. */
.ambient::after {
  content: ''; position: absolute; inset: 0;
  background: radial-gradient(120% 90% at 50% 40%, rgba(11,11,14,.50) 0%, rgba(11,11,14,.84) 60%, #0b0b0e 100%);
}
/* Grain : casse les bandes de dégradé. Mesuré sans coût. */
.grain {
  position: absolute; inset: 0; opacity: .035; pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)'/%3E%3C/svg%3E");
}

@media (prefers-reduced-motion: reduce) {
  .blob { animation: none; }
  *, *::before, *::after { transition-duration: .01ms !important; animation-duration: .01ms !important; animation-iteration-count: 1 !important; }
}

/* ══ Écrans pleins ═════════════════════════════════════════════════════════ */

.screen {
  height: 100%; display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: 16px; text-align: center; padding: 24px; color: var(--dim);
  animation: fade-in .4s var(--ease);
}
.screen-icon { width: 44px; height: 44px; color: var(--brand); opacity: .75; }
.screen-icon.pulse { animation: breathe 3s ease-in-out infinite; }
.screen-title { color: var(--text); font-weight: 700; font-size: 1.1rem; margin: 0; letter-spacing: -.01em; }
.screen-sub { margin: 0; max-width: 38ch; line-height: 1.55; font-size: .92rem; }

@keyframes breathe { 50% { transform: scale(1.12); opacity: 1; } }
@keyframes fade-in { from { opacity: 0; transform: translateY(8px); } }

/* Trois points qui rebondissent — plus lisible qu'un anneau qui tourne. */
.loader { display: flex; gap: 7px; }
.loader span {
  width: 9px; height: 9px; border-radius: 50%; background: var(--brand);
  animation: bounce 1.1s ease-in-out infinite;
}
.loader span:nth-child(2) { animation-delay: .14s; }
.loader span:nth-child(3) { animation-delay: .28s; }
@keyframes bounce { 0%, 70%, 100% { transform: translateY(0); opacity: .45; } 35% { transform: translateY(-9px); opacity: 1; } }

code { background: var(--surface); border: 1px solid var(--stroke); padding: 2px 7px; border-radius: 7px; font-size: .9em; }

/* ══ Ossature ══════════════════════════════════════════════════════════════ */

#player {
  height: 100%; max-width: 1500px; margin: 0 auto;
  padding:
    calc(22px + env(safe-area-inset-top)) calc(22px + env(safe-area-inset-right))
    calc(22px + env(safe-area-inset-bottom)) calc(22px + env(safe-area-inset-left));
  display: grid; gap: 18px;
  grid-template-rows: auto minmax(0, 1fr);
  animation: fade-in .45s var(--ease);
}

@media (min-width: 880px) {
  #player {
    grid-template-columns: minmax(320px, 400px) minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr);
    column-gap: 40px;
  }
}

/* ══ Pochette et métadonnées ═══════════════════════════════════════════════ */

.stage { display: flex; flex-direction: column; gap: 18px; min-height: 0; }

.art-wrap { position: relative; align-self: center; width: 100%; max-width: 320px; }

.art {
  width: 100%; aspect-ratio: 1; object-fit: cover;
  border-radius: var(--r-lg); background: var(--surface);
  box-shadow: 0 24px 60px rgba(0,0,0,.6), 0 0 0 1px var(--stroke),
              0 0 70px -20px var(--brand);        /* halo rosé signature */
  animation: art-in .5s var(--ease);
}
@keyframes art-in { from { opacity: 0; transform: scale(.94); } }

.art-empty { display: grid; place-items: center; color: var(--faint); box-shadow: 0 0 0 1px var(--stroke); }
.art-empty svg { width: 32%; height: 32%; }

/* Égaliseur : signale la lecture d'un coup d'œil, même sans regarder le bouton. */
.bars {
  position: absolute; right: 12px; bottom: 12px;
  display: flex; align-items: flex-end; gap: 3px; height: 20px;
  padding: 7px 9px; border-radius: var(--r-full);
  background: rgba(0,0,0,.55); backdrop-filter: blur(10px);
  opacity: 0; transition: opacity .3s var(--ease);
}
.bars.on { opacity: 1; }
.bars span { width: 3px; height: 100%; background: var(--brand); border-radius: 2px; transform-origin: bottom; animation: eq .9s ease-in-out infinite; }
.bars span:nth-child(2) { animation-delay: .18s; }
.bars span:nth-child(3) { animation-delay: .36s; }
.bars span:nth-child(4) { animation-delay: .54s; }
@keyframes eq { 0%, 100% { transform: scaleY(.28); } 50% { transform: scaleY(1); } }

.meta { min-width: 0; text-align: center; }
.title {
  margin: 0; font-size: 1.32rem; font-weight: 800; line-height: 1.28; letter-spacing: -.02em;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.author { margin: 6px 0 0; color: var(--brand); font-size: .95rem; font-weight: 600; }
.requester { margin: 4px 0 0; color: var(--faint); font-size: .76rem; }
.author, .requester { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ══ Curseurs ══════════════════════════════════════════════════════════════ */

/*
 * La portion parcourue est peinte par un dégradé dont la césure suit `--fill`,
 * réécrite par app.js. Un `background-size` animé serait plus court mais
 * désolidariserait la poignée de la barre au redimensionnement.
 */
.range {
  -webkit-appearance: none; appearance: none;
  width: 100%; height: 5px; border-radius: var(--r-full); outline: none; cursor: pointer;
  background: linear-gradient(to right, var(--brand) 0 var(--fill, 0%), rgba(255,255,255,.14) var(--fill, 0%) 100%);
  transition: height .15s var(--ease);
}
.range:hover, .range:active { height: 8px; }

.range::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 15px; height: 15px; border-radius: 50%; background: #fff; border: none; cursor: pointer;
  box-shadow: 0 2px 8px rgba(0,0,0,.6);
  transform: scale(0); transition: transform .15s var(--ease);
}
.range:hover::-webkit-slider-thumb, .range:active::-webkit-slider-thumb, .range:focus-visible::-webkit-slider-thumb { transform: scale(1); }
.range::-moz-range-thumb { width: 15px; height: 15px; border-radius: 50%; background: #fff; border: none; box-shadow: 0 2px 8px rgba(0,0,0,.6); }
.range:disabled { opacity: .35; cursor: default; }
.range:disabled::-webkit-slider-thumb { transform: scale(0); }
.range:focus-visible { outline: 2px solid var(--brand); outline-offset: 5px; }

.times {
  display: flex; justify-content: space-between;
  font-size: .73rem; color: var(--faint); margin-top: 9px;
  font-variant-numeric: tabular-nums;
}

/* ══ Boutons ═══════════════════════════════════════════════════════════════ */

.controls { display: flex; align-items: center; justify-content: center; gap: 8px; }

.btn {
  border: none; background: none; color: var(--text); font: inherit;
  cursor: pointer; display: grid; place-items: center; position: relative;
  border-radius: var(--r-full);
  transition: background .16s var(--ease), color .16s var(--ease), transform .12s var(--ease), opacity .16s;
}
.btn:active:not(:disabled) { transform: scale(.9); }
.btn:disabled { opacity: .28; cursor: not-allowed; }
.btn:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; }

.btn-icon { width: 42px; height: 42px; font-size: 21px; color: var(--dim); }
.btn-icon:hover:not(:disabled) { color: var(--text); background: var(--hover); }
.btn-sm { width: 34px; height: 34px; font-size: 17px; }

/* Bouton principal : c'est lui qui porte la couleur du bot. */
.btn-play {
  width: 60px; height: 60px; font-size: 26px;
  background: var(--brand); color: var(--brand-ink);
  box-shadow: 0 8px 26px -6px var(--brand), 0 4px 14px rgba(0,0,0,.4);
}
.btn-play:hover:not(:disabled) { transform: scale(1.07); box-shadow: 0 12px 34px -6px var(--brand); }
.btn-play:active:not(:disabled) { transform: scale(.96); }

/* Bascule active : couleur du bot + pastille dessous. */
.btn-icon.on { color: var(--brand); }
.btn-icon.on::after {
  content: ''; position: absolute; bottom: 5px; left: 50%; translate: -50% 0;
  width: 4px; height: 4px; border-radius: 50%; background: var(--brand);
}

.btn-pill {
  padding: 10px 22px; font-size: .9rem; font-weight: 600;
  background: var(--brand); color: var(--brand-ink);
  box-shadow: 0 6px 20px -8px var(--brand);
}
.btn-pill:hover { transform: translateY(-1px); }

/* ══ Volume ════════════════════════════════════════════════════════════════ */

.volume { display: flex; align-items: center; gap: 8px; }
.range-vol { flex: 1; }
.vol-value { font-size: .73rem; color: var(--faint); min-width: 48px; text-align: right; font-variant-numeric: tabular-nums; }

/* ══ Onglets ═══════════════════════════════════════════════════════════════ */

.side { display: flex; flex-direction: column; min-height: 0; }

.tabs {
  display: flex; align-items: center; gap: 4px;
  border-bottom: 1px solid var(--stroke); padding-bottom: 12px; margin-bottom: 6px;
}
.tab {
  display: inline-flex; align-items: center; gap: 8px;
  border: none; background: none; cursor: pointer; font: inherit;
  color: var(--faint); font-size: .78rem; font-weight: 700;
  letter-spacing: .05em; text-transform: uppercase;
  padding: 9px 15px; border-radius: var(--r-full);
  transition: color .16s var(--ease), background .16s var(--ease);
}
.tab svg { width: 15px; height: 15px; }
.tab:hover { color: var(--dim); background: var(--surface); }
.tab.is-active { color: var(--brand-ink); background: var(--brand); }
.tab.is-active .pill { background: rgba(0,0,0,.22); color: var(--brand-ink); }
.tab:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

.pill { background: var(--brand-soft); color: var(--brand); border-radius: var(--r-full); padding: 2px 8px; font-size: .72rem; letter-spacing: 0; }
.listeners { margin-left: auto; font-size: .74rem; color: var(--faint); white-space: nowrap; }

.pane { flex: 1; min-height: 0; display: flex; flex-direction: column; animation: fade-in .3s var(--ease); }

/* ══ File d'attente ════════════════════════════════════════════════════════ */

.queue-list {
  list-style: none; margin: 0; padding: 6px 8px 6px 0;
  overflow-y: auto; min-height: 0; flex: 1;
  display: flex; flex-direction: column; gap: 2px;
}

.queue-item {
  display: flex; align-items: center; gap: 14px;
  padding: 10px 14px; border-radius: var(--r-sm);
  cursor: pointer; border: 1px solid transparent;
  transition: background .14s var(--ease), border-color .14s var(--ease);
  animation: row-in .32s var(--ease) backwards;
}
@keyframes row-in { from { opacity: 0; transform: translateX(-10px); } }

.queue-item:hover { background: var(--hover); border-color: var(--stroke); }
.queue-item:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.queue-item[aria-disabled="true"] { cursor: default; }
.queue-item[aria-disabled="true"]:hover { background: transparent; border-color: transparent; }

.q-index { color: var(--faint); font-size: .78rem; min-width: 2em; text-align: right; font-variant-numeric: tabular-nums; flex: 0 0 auto; transition: color .14s; }
.queue-item:not([aria-disabled="true"]):hover .q-index { color: var(--brand); }
.q-main { flex: 1; min-width: 0; }
.q-title { font-size: .9rem; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.q-author { font-size: .76rem; color: var(--faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 1px; }
.q-time { color: var(--faint); font-size: .76rem; font-variant-numeric: tabular-nums; flex: 0 0 auto; }

.queue-more { color: var(--faint); font-size: .77rem; margin: 12px 0 4px; text-align: center; }

/* ══ Paroles ═══════════════════════════════════════════════════════════════ */

/*
 * PIÈGE CORRIGÉ : le remplissage vertical valait `22%`, et un pourcentage de
 * `padding` se calcule sur la LARGEUR du bloc, jamais sur sa hauteur. Sur une
 * colonne de 1000 px, cela faisait 220 px de vide au-dessus de la première
 * ligne — le « gros espace ». Le centrage se règle désormais en pixels, calculé
 * par app.js sur la hauteur RÉELLE du cadre et refait à chaque redimensionnement.
 */
.lyrics {
  flex: 1; min-height: 0; overflow-y: auto;
  padding: 0 10px;
  scroll-behavior: smooth;
  mask-image: linear-gradient(to bottom, transparent, #000 12%, #000 88%, transparent);
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 12%, #000 88%, transparent);
}
@media (prefers-reduced-motion: reduce) { .lyrics { scroll-behavior: auto; } }

.ly-line {
  margin: 0; padding: 9px 14px; border-radius: var(--r-sm);
  font-size: 1.3rem; font-weight: 700; line-height: 1.36;
  color: var(--faint); opacity: .42;
  transition: color .35s var(--ease), opacity .35s var(--ease),
              transform .35s var(--ease), text-shadow .35s var(--ease);
  transform-origin: left center;
}
.ly-line.past { opacity: .25; }

/* La ligne chantée porte la couleur du bot, et elle seule. */
.ly-line.current {
  color: var(--brand); opacity: 1;
  transform: scale(1.055) translateX(4px);
  text-shadow: 0 0 34px rgba(255, 158, 189, .55);
}

/* Intermède musical : trois points qui pulsent, sinon une ligne resterait
   figée pendant vingt secondes de solo. */
.ly-line.gap { min-height: .8em; padding-block: 4px; }
.ly-line.gap.current { transform: none; }
.ly-line.gap.current::after {
  content: '● ● ●'; font-size: .46em; letter-spacing: .3em; color: var(--brand);
  animation: dots 1.5s ease-in-out infinite;
}
@keyframes dots { 0%, 100% { opacity: .3; } 50% { opacity: 1; } }

.ly-plain { white-space: pre-wrap; line-height: 1.8; color: var(--dim); font-size: .96rem; padding: 4px 14px; }
.ly-msg {
  color: var(--faint); text-align: center; padding: 44px 16px; line-height: 1.6;
  display: flex; flex-direction: column; align-items: center; gap: 14px;
}
.ly-msg svg { width: 34px; height: 34px; color: var(--brand); opacity: .55; }

/* ══ Message éphémère ══════════════════════════════════════════════════════ */

.toast {
  position: fixed; left: 50%; translate: -50% 0;
  bottom: calc(24px + env(safe-area-inset-bottom));
  background: rgba(18,18,22,.95); border: 1px solid var(--stroke);
  padding: 12px 20px; border-radius: var(--r-full); font-size: .85rem;
  box-shadow: 0 10px 36px rgba(0,0,0,.65);
  max-width: calc(100% - 44px); text-align: center;
  backdrop-filter: blur(14px); z-index: 10;
  animation: toast-in .26s var(--ease);
}
@keyframes toast-in { from { opacity: 0; translate: -50% 14px; } }

/* ══ Barres de défilement ══════════════════════════════════════════════════ */

.queue-list::-webkit-scrollbar, .lyrics::-webkit-scrollbar { width: 8px; }
.queue-list::-webkit-scrollbar-thumb, .lyrics::-webkit-scrollbar-thumb { background: rgba(255,255,255,.12); border-radius: var(--r-full); }
.queue-list::-webkit-scrollbar-thumb:hover, .lyrics::-webkit-scrollbar-thumb:hover { background: var(--brand-soft); }
.queue-list, .lyrics { scrollbar-width: thin; scrollbar-color: rgba(255,255,255,.14) transparent; }

/* ══ Mobile — iOS et Android, portrait ═════════════════════════════════════ */

@media (max-width: 879px) {
  #player { gap: 14px; padding-inline: 18px; }
  .art-wrap { max-width: min(66vw, 260px); }
  .title { font-size: 1.22rem; }
  .controls { gap: 6px; }
  /* Cibles tactiles : 44 px est le minimum lisible des guides iOS et Android. */
  .btn-icon { width: 46px; height: 46px; }
  .btn-play { width: 64px; height: 64px; }
  .range { height: 6px; }                      /* plus facile à saisir au doigt */
  .range::-webkit-slider-thumb { transform: scale(1); }   /* pas de survol au doigt */
  .tab { padding: 9px 13px; font-size: .74rem; }
  .tab span:not(.pill) { display: inline; }
  .ly-line { font-size: 1.12rem; padding: 8px 10px; }
  .queue-item { padding: 12px 12px; }          /* lignes plus hautes = tapables */
  .listeners { display: none; }                /* la largeur manque */
}

/* (seuil des libellés d'onglets déplacé à 359 px — voir plus bas) */

/* ══ Fenêtre flottante (~300 × 180) ════════════════════════════════════════ */

/*
 * Détaché, le lecteur tient dans une vignette. Tout ce qui n'est pas « quel
 * titre » et « lecture/pause/suivant » disparaît : la pochette passe en
 * miniature à gauche, la file et les paroles n'ont plus la place d'exister.
 */
@media (max-height: 320px) {
  #player {
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr);
    gap: 12px; padding: 12px;
  }
  .side, .bars, .volume, .times { display: none; }

  .stage { flex-direction: row; align-items: center; gap: 12px; grid-column: 1 / -1; }
  .art-wrap { width: 64px; max-width: 64px; flex: 0 0 auto; }
  .art { border-radius: var(--r-md); box-shadow: 0 6px 18px rgba(0,0,0,.5), 0 0 0 1px var(--stroke); animation: none; }

  .meta { text-align: left; flex: 1; min-width: 0; }
  .title { font-size: .95rem; -webkit-line-clamp: 1; }
  .author { font-size: .78rem; margin-top: 2px; }
  .requester { display: none; }

  .progress { position: absolute; left: 12px; right: 12px; bottom: 8px; }
  .controls { flex: 0 0 auto; gap: 2px; }
  .btn-icon { width: 34px; height: 34px; font-size: 17px; }
  .btn-play { width: 42px; height: 42px; font-size: 19px; box-shadow: none; }
  #shuffle, #loop { display: none; }
  .screen-sub, .screen-title { font-size: .82rem; }
}

/* ══════════════════════════════════════════════════════════════════════════ */
/* ══ Navigation et vues ═══════════════════════════════════════════════════ */
/* ══════════════════════════════════════════════════════════════════════════ */

/*
 * Le panneau n'est plus une seule page : accueil, recherche, bibliothèque et
 * lecteur cohabitent. La barre passe SUR LE CÔTÉ en large et EN BAS en étroit —
 * sur téléphone, le bas de l'écran est la seule zone atteignable au pouce.
 */
.nav {
  position: fixed; z-index: 5; display: flex;
  background: rgba(14,14,18,.86); backdrop-filter: blur(18px);
  border: 1px solid var(--stroke);
}
.nav-btn {
  flex: 1; display: flex; align-items: center; gap: var(--s3);
  border: none; background: none; cursor: pointer; font: inherit;
  color: var(--dim); font-size: .82rem; font-weight: 600;
  padding: 10px var(--s3); border-radius: var(--r-sm);
  transition: color .16s var(--ease), background .16s var(--ease);
}
.nav-btn svg { width: 19px; height: 19px; flex: 0 0 auto; opacity: .85; transition: opacity .16s var(--ease); }
.nav-btn:hover { color: var(--text); background: var(--hover); }
.nav-btn:hover svg { opacity: 1; }
.nav-btn.is-active { color: var(--brand); background: var(--brand-soft); }
.nav-btn.is-active svg { opacity: 1; }
.nav-btn:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }

/*
 * Large : barre latérale pleine hauteur.
 *
 * MESURÉ AVANT : la navigation était une pastille flottante de 172 × 190 px,
 * centrée verticalement (y = 355 sur 900). Les vues lui réservaient pourtant
 * 222 px de marge gauche SUR TOUTE LA HAUTEUR — à 1920 × 1080, 239 760 px²
 * réservés pour 32 680 px² occupés, soit 86 % de vide. Et comme elle ne
 * touchait aucun bord, elle se lisait comme une infobulle égarée plutôt que
 * comme la navigation du produit.
 *
 * Elle prend maintenant toute la hauteur, s'arrête net sur la barre de
 * lecture, et porte la marque — que le panneau authentifié n'affichait nulle
 * part.
 */
@media (min-width: 880px) {
  .nav {
    left: 0; top: 0; bottom: var(--bar-h); translate: none;
    flex-direction: column; gap: 2px;
    padding: var(--s5) var(--s3) calc(var(--s4) + env(safe-area-inset-bottom));
    padding-left: calc(var(--s3) + env(safe-area-inset-left));
    width: var(--rail); border-radius: 0;
    background: var(--surface-1); backdrop-filter: none;
    border: none; border-right: 1px solid var(--line);
  }
  .nav-btn { flex: 0 0 auto; }
  .view { padding-left: calc(var(--rail) + 28px + env(safe-area-inset-left)) !important; }
}

/* ── Marque et intitulé de groupe (barre latérale seulement) ─────────────── */

.nav-brand {
  display: none; align-items: center; gap: var(--s3);
  padding: var(--s1) var(--s3) var(--s6);
}
.nav-mark {
  width: 32px; height: 32px; flex: 0 0 auto; border-radius: 10px;
  display: grid; place-items: center;
  background: var(--brand); color: var(--brand-ink);
  box-shadow: var(--glow);
}
.nav-mark svg { width: 17px; height: 17px; }
.nav-word { font-size: 1rem; font-weight: 800; letter-spacing: -.025em; color: var(--text); }
.nav-word b { font-weight: 800; color: var(--brand); margin-left: .3em; }

.nav-group {
  display: none; margin: 0 0 var(--s2); padding-inline: var(--s3);
  font-size: var(--t-micro); font-weight: 800; letter-spacing: .13em;
  text-transform: uppercase; color: var(--faint);
}

@media (min-width: 880px) {
  .nav-brand, .nav-group { display: flex; }
  .nav-group { display: block; }

  /*
   * L'état actif se lisait par un simple fond rose pâle. Un repère de bord
   * dit « vous êtes ici » sans dépendre de la couleur seule — utile aussi à
   * qui distingue mal le rose du gris.
   */
  .nav-btn { position: relative; padding: 10px var(--s3); }
  .nav-btn.is-active::before {
    content: ''; position: absolute; left: calc(var(--s3) * -1); top: 50%; translate: 0 -50%;
    width: 3px; height: 18px; border-radius: 0 3px 3px 0; background: var(--brand);
    box-shadow: 0 0 12px -2px var(--brand);
  }

  /* Le groupe des playlists se détache du bloc de navigation. */
  .nav-group + .nav-pl, .nav-pl { margin-top: var(--s1); }
  #nav-pl-label { margin-top: var(--s5); padding-top: var(--s5); border-top: 1px solid var(--line); }
}

/* Étroit : barre en bas, libellés sous les icônes. */
@media (max-width: 879px) {
  .nav {
    left: 0; right: 0; bottom: 0; border-radius: 0;
    border-left: none; border-right: none; border-bottom: none;
    padding: 6px 6px calc(6px + env(safe-area-inset-bottom));
  }
  .nav-btn { flex-direction: column; gap: 3px; font-size: .68rem; padding: 8px 4px; }
  /*
   * PIÈGE CORRIGÉ. Cette règle porte !important (elle doit battre le padding
   * de base de `.view`), et une seconde règle plus bas tentait d'ajouter la
   * hauteur de la barre de lecture SANS !important : elle ne s'appliquait
   * jamais. Mesuré au rendu réel sur 390 × 844 : barre de 707 à 774 px,
   * contenu autorisé jusqu'à 766 px — les 59 derniers pixels de chaque vue
   * passaient SOUS la barre dès qu'un titre jouait.
   *
   * Une seule règle maintenant : `--bar-h` vaut 0 sans barre, 64 px avec.
   */
  .view { padding-bottom: calc(var(--bar-h) + 78px + env(safe-area-inset-bottom)) !important; }
}

/* En fenêtre flottante, la navigation n'a pas la place d'exister. */
@media (max-height: 320px) { .nav { display: none; } .view { padding-left: 12px !important; padding-bottom: 12px !important; } }

.view {
  position: absolute; inset: 0;
  display: flex; flex-direction: column;
  padding: calc(22px + env(safe-area-inset-top)) calc(22px + env(safe-area-inset-right))
           calc(22px + env(safe-area-inset-bottom)) calc(22px + env(safe-area-inset-left));
  animation: fade-in .3s var(--ease);
}
#player.view { position: absolute; }

.scroll { flex: 1; min-height: 0; overflow-y: auto; padding-right: 6px; }
.scroll::-webkit-scrollbar { width: 8px; }
.scroll::-webkit-scrollbar-thumb { background: rgba(255,255,255,.12); border-radius: var(--r-full); }
.scroll { scrollbar-width: thin; scrollbar-color: rgba(255,255,255,.14) transparent; }

/* ══ Sections et cartes ═══════════════════════════════════════════════════ */

.sec { margin-bottom: var(--s8); }
.sec-head {
  display: flex; align-items: baseline; gap: var(--s3); margin: 0 0 var(--s4);
  font-size: var(--t-h2); font-weight: 800; letter-spacing: -.02em;
}
.sec-note { font-size: var(--t-meta); font-weight: 500; color: var(--faint); letter-spacing: 0; }

/* Rangée horizontale de pochettes — le format « à découvrir ». */
.row {
  display: grid; grid-auto-flow: column; grid-auto-columns: 158px;
  gap: 14px; overflow-x: auto; padding-bottom: 8px;
  scroll-snap-type: x proximity;
}
.row::-webkit-scrollbar { height: 6px; }
.row::-webkit-scrollbar-thumb { background: rgba(255,255,255,.12); border-radius: var(--r-full); }

/*
 * Une carte est maintenant une SURFACE, pas une image posée sur le vide.
 * Mesuré avant : l'accueil n'avait qu'un seul niveau de surface, tout était
 * transparent sur le fond — d'où des pochettes qui flottaient sans que rien
 * ne dise où commence et où finit un élément cliquable.
 *
 * Empiler depuis le HAUT est explicite à dessein : la carte n'est plus un
 * <button> (elle en contient un), mais la règle protège aussi du piège
 * d'origine — un bouton centre verticalement son contenu, et les pochettes
 * d'une même rangée ne s'alignaient jamais quand les titres tenaient sur un
 * nombre de lignes différent.
 */
.card {
  scroll-snap-align: start; cursor: pointer; text-align: left; font: inherit; color: inherit;
  display: flex; flex-direction: column; align-items: stretch; justify-content: flex-start;
  padding: var(--s3);
  border-radius: var(--r-lg);
  background: var(--surface-3);
  border: 1px solid transparent;
  transition: background .18s var(--ease), transform .18s var(--ease-out),
              box-shadow .18s var(--ease), border-color .18s var(--ease);
  animation: row-in .34s var(--ease) backwards;
}
.card:hover {
  background: #262230;
  transform: translateY(-4px);
  box-shadow: var(--e3);
  border-color: var(--stroke);
}
.card:active { transform: translateY(-1px) scale(.988); }
.card:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; }

/* Conteneur de la pochette : c'est lui qui porte le bouton de lecture. */
.card-media {
  position: relative;
  border-radius: var(--r-md);
  overflow: hidden;
  margin-bottom: var(--s3);
  box-shadow: var(--e2);
  background: var(--surface-2);
}
.card-art {
  width: 100%; aspect-ratio: 1; object-fit: cover; display: grid; place-items: center;
  background: var(--surface-2); color: var(--faint);
  transition: transform .4s var(--ease-out);
}
.card-art svg { width: 34%; height: 34%; }
/* La pochette respire au survol — très peu, et sous le masque du conteneur. */
.card:hover .card-art { transform: scale(1.045); }

/*
 * Bouton de lecture.
 *
 * Il est TOUJOURS visible, et discret : verre sombre, glyphe blanc. Il ne
 * prend la couleur de la marque qu'au survol ou au clavier.
 *
 * POURQUOI PAS LE RÉVÉLER AU SURVOL, comme le font les lecteurs connus. Deux
 * raisons, dans cet ordre :
 *   · sur un écran tactile il n'y a pas de survol — le raccourci n'existerait
 *     tout simplement pas là où il sert le plus ;
 *   · et je ne peux pas le VÉRIFIER : Chrome sans tête annonce `hover: none`
 *     quoi qu'on lui demande, donc un rendu qui dépend du survol serait livré
 *     sans avoir jamais été vu.
 *
 * Trente-six pastilles roses d'un coup seraient illisibles : c'est la retenue
 * au repos qui rend le motif tenable, pas le fait de le cacher.
 */
.card-play {
  position: absolute; right: var(--s3); bottom: var(--s3);
  width: 38px; height: 38px; border-radius: 50%;
  display: grid; place-items: center; cursor: pointer;
  border: 1px solid rgba(255,255,255,.14);
  background: rgba(12,10,16,.62); color: #fff;
  backdrop-filter: blur(10px);
  box-shadow: var(--e1);
  transform: scale(.94);
  transition: background .16s var(--ease), color .16s var(--ease),
              transform .2s var(--ease-out), box-shadow .2s var(--ease), border-color .16s var(--ease);
}
.card-play svg { width: 15px; height: 15px; margin-left: 1px; }
.card:hover .card-play {
  background: var(--brand); color: var(--brand-ink); border-color: var(--brand);
  transform: scale(1); box-shadow: var(--e2), var(--glow);
}
/* `color` répété ici volontairement : la règle ne doit pas dépendre de
   `.card:hover .card-play` pour rester lisible si celle-ci bouge. */
.card-play:hover {
  background: var(--brand-hover) !important; color: var(--brand-ink) !important;
  border-color: var(--brand-hover) !important; transform: scale(1.08) !important;
}
.card-play:focus-visible {
  outline: 2px solid var(--brand); outline-offset: 2px;
  background: var(--brand); color: var(--brand-ink); transform: scale(1);
}
@media (hover: none) { .card-play { width: 40px; height: 40px; transform: none; } }

/* Ce qui joue se reconnaît sans lire : un liseré et un égaliseur. */
.card-media.is-current { box-shadow: var(--e2), 0 0 0 2px var(--brand); }
.card-eq {
  position: absolute; left: var(--s3); bottom: var(--s3);
  display: flex; align-items: flex-end; gap: 2px; height: 14px;
  padding: 5px 6px; border-radius: var(--r-xs);
  background: rgba(10,9,13,.78); backdrop-filter: blur(6px);
}
.card-eq i {
  width: 2.5px; height: 100%; border-radius: 2px; background: var(--brand);
  transform-origin: bottom; animation: eq .9s ease-in-out infinite;
}
.card-eq i:nth-child(2) { animation-delay: .2s; }
.card-eq i:nth-child(3) { animation-delay: .4s; }
@media (prefers-reduced-motion: reduce) {
  .card-eq i { animation: none; height: 60%; }
  .card, .card-art, .card-play { transition: none; }
  .card:hover { transform: none; }
  .card:hover .card-art { transform: none; }
}
.card-media.is-current + .card-title, .card-media.is-current ~ .card-title { color: var(--brand); }

.card-title {
  font-size: var(--t-card); font-weight: 600; line-height: 1.32;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.card-sub {
  font-size: var(--t-meta); color: var(--faint); margin-top: var(--s1);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Liste verticale — le format « résultats » et « bibliothèque ». */
.list { display: flex; flex-direction: column; gap: 2px; }
.item, .skel-row {
  display: flex; align-items: center; gap: var(--s3); width: 100%;
  padding: var(--s2) var(--s3); border-radius: var(--r-md);
  border: 1px solid transparent; background: none; cursor: pointer;
  font: inherit; color: inherit; text-align: left;
  transition: background .14s var(--ease), border-color .14s var(--ease);
  animation: row-in .3s var(--ease) backwards;
}
.item:hover { background: var(--surface-2); border-color: var(--line); }
.item:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.item-art {
  width: 46px; height: 46px; border-radius: var(--r-sm); object-fit: cover;
  background: var(--surface-2); flex: 0 0 auto; display: grid; place-items: center;
  color: var(--faint); box-shadow: var(--e1);
}
.item-art svg { width: 44%; height: 44%; }
.item-main { flex: 1; min-width: 0; }
.item-title { font-size: var(--t-body); font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.item-sub { font-size: var(--t-meta); color: var(--faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 1px; }
.item-end { color: var(--faint); font-size: var(--t-meta); font-variant-numeric: tabular-nums; flex: 0 0 auto; display: flex; align-items: center; gap: 6px; }
.item-end .btn { color: var(--faint); }
.item:hover .item-end .btn { color: var(--text); }
.btn.liked { color: var(--brand) !important; }

/*
 * État vide ou en échec : un constat, ce qu'on peut tenter, et le bouton pour
 * le faire. Même mise en forme partout — un état vide ne doit pas ressembler
 * à une page cassée.
 */
.empty {
  display: flex; flex-direction: column; align-items: center; gap: var(--s2);
  color: var(--faint); text-align: center;
  padding: var(--s7) var(--s4); line-height: 1.6; font-size: .88rem;
}
.empty-main { margin: 0; color: var(--dim); font-weight: 600; }
.empty-action { margin-top: var(--s2); }

/* Variante posée DANS le flux (démarrage à froid de l'accueil) : alignée à
   gauche, sans le rembourrage d'un écran vide. */
.empty-inline { align-items: flex-start; text-align: left; padding: 0 0 var(--s5); max-width: 62ch; }

/* ══ Recherche ════════════════════════════════════════════════════════════ */

.search-bar {
  display: flex; align-items: center; gap: var(--s3);
  background: var(--surface); border: 1px solid var(--stroke);
  border-radius: var(--r-full); padding: 0 14px; margin-bottom: 18px;
  transition: border-color .16s var(--ease), background .16s var(--ease);
}
.search-bar:focus-within { border-color: var(--brand); background: var(--hover); }
.search-icon { width: 18px; height: 18px; color: var(--faint); flex: 0 0 auto; }
.search-input {
  flex: 1; border: none; background: none; outline: none; color: var(--text);
  font: inherit; font-size: .95rem; padding: var(--s3) 0;
}
.search-input::placeholder { color: var(--faint); }
.search-input::-webkit-search-cancel-button { display: none; }

/* ══ Feuille d'actions ════════════════════════════════════════════════════ */

/*
 * Une seule feuille pour toutes les actions d'un titre (jouer, aimer, ajouter
 * à une playlist, copier le lien). Sur téléphone elle monte du bas, au pouce ;
 * sur grand écran elle se centre.
 */
.sheet {
  position: fixed; inset: 0; z-index: 20;
  background: rgba(0,0,0,.6); backdrop-filter: blur(6px);
  display: flex; align-items: flex-end; justify-content: center;
  animation: fade-in .18s var(--ease);
}
@media (min-width: 880px) { .sheet { align-items: center; } }

.sheet-card {
  width: 100%; max-width: 440px;
  background: #16161b; border: 1px solid var(--stroke);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  padding: 22px 20px calc(22px + env(safe-area-inset-bottom));
  box-shadow: 0 -10px 50px rgba(0,0,0,.7);
  animation: sheet-up .26s var(--ease);
}
@media (min-width: 880px) { .sheet-card { border-radius: var(--r-lg); animation: fade-in .2s var(--ease); } }
@keyframes sheet-up { from { transform: translateY(100%); } }

.sheet-title { margin: 0; font-size: 1.02rem; font-weight: 700; line-height: 1.35; }
.sheet-sub { margin: 4px 0 18px; font-size: .82rem; color: var(--faint); }
.sheet-actions { display: flex; flex-direction: column; gap: var(--s1); margin-bottom: var(--s4); }
.sheet-act {
  display: flex; align-items: center; gap: 14px; width: 100%;
  border: none; background: none; cursor: pointer; font: inherit; color: var(--text);
  padding: var(--s3); border-radius: var(--r-sm); text-align: left; font-size: var(--t-body);
  transition: background .14s var(--ease);
}
.sheet-act svg { width: 19px; height: 19px; color: var(--brand); flex: 0 0 auto; }
.sheet-act:hover { background: var(--hover); }
.sheet-act:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.sheet-act.danger { color: #ff8080; }
.sheet-act.danger svg { color: #ff8080; }
.sheet-close { width: 100%; }

/* Champ de saisie dans la feuille (nom d'une nouvelle playlist). */
.sheet-input {
  width: 100%; border: 1px solid var(--stroke); background: var(--surface);
  border-radius: var(--r-sm); padding: 12px 14px; color: var(--text);
  font: inherit; font-size: .92rem; outline: none; margin-bottom: 10px;
}
.sheet-input:focus { border-color: var(--brand); }

/* ══ Réglage du décalage des paroles ══════════════════════════════════════ */

/*
 * Les horodatages de LRCLIB sont calés sur l'audio STUDIO. Un clip YouTube
 * commence souvent par une intro, une dédicace ou un skit : toutes les lignes
 * arrivent alors en avance, du même écart. Aucun calcul ne peut deviner cette
 * durée — le réglage est donc manuel, et mémorisé par titre.
 */
.ly-tools {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 12px; margin-bottom: 8px;
  background: var(--surface); border: 1px solid var(--stroke);
  border-radius: var(--r-full); font-size: .78rem; color: var(--faint);
  align-self: flex-start;
}
.ly-tools-label { font-weight: 600; letter-spacing: .04em; text-transform: uppercase; font-size: .7rem; }
.ly-tools .btn { width: 28px; height: 28px; font-size: 17px; font-weight: 700; color: var(--brand); }
.ly-tools .btn:hover { background: var(--brand-soft); }
.ly-offset {
  min-width: 62px; text-align: center; color: var(--text);
  font-variant-numeric: tabular-nums; font-weight: 600;
}
.ly-reset {
  border: none; background: none; cursor: pointer; font: inherit;
  color: var(--faint); font-size: .74rem; text-decoration: underline;
  padding: 4px 6px; border-radius: 6px;
}
.ly-reset:hover { color: var(--brand); }
@media (max-width: 400px) { .ly-reset { display: none; } }

/* ══ Mobile : la scène se replie quand un panneau réclame la place ═════════ */

/*
 * Constat de la capture iPhone : pochette + titre + progression + contrôles +
 * volume + onglets occupaient 75 % de l'écran, et il ne restait QUE 150 px aux
 * paroles — la ligne chantée était coupée en bas. Sur un téléphone, le lecteur
 * et le karaoké ne peuvent pas coexister en pleine taille.
 *
 * Quand l'onglet Paroles est ouvert, la scène se replie donc en une barre
 * horizontale (vignette + titre + lecture/pause) et le texte prend tout le
 * reste. C'est le comportement des lecteurs qui affichent des paroles : la
 * pochette redevient un repère, pas le sujet.
 */
@media (max-width: 879px) {
  /* La file aussi manquait d'air : une pochette un peu plus petite dégage
     trois lignes supplémentaires sans dénaturer l'écran. */
  .art-wrap { max-width: min(56vw, 220px); }

  #player[data-pane="lyrics"] .stage { flex-direction: row; align-items: center; gap: 12px; }
  #player[data-pane="lyrics"] .art-wrap { width: 58px; max-width: 58px; flex: 0 0 auto; }
  #player[data-pane="lyrics"] .art { border-radius: var(--r-md); box-shadow: 0 6px 18px rgba(0,0,0,.5), 0 0 0 1px var(--stroke); }
  #player[data-pane="lyrics"] .bars { display: none; }

  #player[data-pane="lyrics"] .meta { text-align: left; flex: 1; min-width: 0; }
  #player[data-pane="lyrics"] .title { font-size: .95rem; -webkit-line-clamp: 1; }
  #player[data-pane="lyrics"] .author { font-size: .78rem; margin-top: 2px; }
  #player[data-pane="lyrics"] .requester { display: none; }

  /* Seul le bouton principal survit : passer un titre pendant qu'on lit les
     paroles est rare, revenir à la lecture ne l'est pas. */
  #player[data-pane="lyrics"] .progress,
  #player[data-pane="lyrics"] .volume,
  #player[data-pane="lyrics"] #shuffle,
  #player[data-pane="lyrics"] #loop,
  #player[data-pane="lyrics"] #prev { display: none; }
  #player[data-pane="lyrics"] .controls { flex: 0 0 auto; gap: 4px; }
  #player[data-pane="lyrics"] .btn-play { width: 46px; height: 46px; font-size: 20px; }
  #player[data-pane="lyrics"] #skip { width: 40px; height: 40px; }
  #player[data-pane="lyrics"] .ly-line { font-size: 1.24rem; }
}

/* Les libellés d'onglets ne disparaissent qu'en dessous d'un vrai petit écran :
   à 393 px (iPhone 15) ils tenaient très bien, et un compteur seul à côté d'une
   icône ne veut rien dire. */
@media (max-width: 359px) {
  .tab span:not(.pill) { display: none; }
  .vol-value { display: none; }
}

/* ══ Site autonome ════════════════════════════════════════════════════════ */

/*
 * Hors de Discord, la première chose vue n'est plus un lecteur mais une page
 * d'accueil : personne n'est encore identifié, et rien ne dit de quel serveur
 * on parle.
 */
.landing-mark {
  width: 86px; height: 86px; display: grid; place-items: center;
  border-radius: 26px; color: var(--brand-ink); background: var(--brand);
  box-shadow: 0 16px 46px -10px var(--brand);
  animation: breathe 4s ease-in-out infinite;
}
.landing-mark svg { width: 46px; height: 46px; }
.landing-title { margin: 6px 0 0; font-size: 1.7rem; font-weight: 800; letter-spacing: -.03em; color: var(--text); }
.landing-cta { margin-top: 6px; padding: 14px 30px; font-size: .98rem; }
.landing-note { margin: 0; max-width: 36ch; font-size: .78rem; color: var(--faint); line-height: 1.55; }

.guild-list { display: flex; flex-direction: column; gap: 6px; width: 100%; max-width: 420px; margin-top: 6px; }
.guild-btn {
  display: flex; align-items: center; gap: 14px; width: 100%;
  padding: 12px 16px; border-radius: var(--r-md);
  border: 1px solid var(--stroke); background: var(--surface);
  cursor: pointer; font: inherit; color: var(--text); text-align: left;
  transition: background .14s var(--ease), border-color .14s var(--ease), transform .1s var(--ease);
}
.guild-btn:hover { background: var(--hover); border-color: var(--brand); transform: translateX(3px); }
.guild-btn:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.guild-icon {
  width: 42px; height: 42px; border-radius: 13px; object-fit: cover; flex: 0 0 auto;
  background: var(--brand-soft); color: var(--brand);
  display: grid; place-items: center; font-weight: 800; font-size: 1rem;
}
.guild-name { font-weight: 600; font-size: .94rem; }

/* Le lecteur YouTube ne sert qu'au son : on le sort du flux sans le masquer
   avec `display:none`, ce qui empêcherait certains navigateurs de le lancer. */
.yt-mount { position: fixed; width: 1px; height: 1px; opacity: 0; pointer-events: none; left: -9999px; top: 0; }

/* Bandeau « tu écoutes ici, pas dans le vocal » — sans lui, on croit piloter
   le bot et on s'étonne que le salon reste muet. */
.web-badge {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: .7rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--brand); background: var(--brand-soft);
  padding: 5px 11px; border-radius: var(--r-full); margin-left: auto;
}


/* ── Note « écran éteint » (site, sur téléphone) ────────────────────────────
   Dite une fois, sous les commandes, et discrète : c'est une limite du système,
   pas une erreur de l'application. */
.bg-note {
  margin: 14px 0 0; padding: 9px 12px;
  font-size: .74rem; line-height: 1.45; color: var(--faint);
  background: var(--surface); border-radius: var(--r-sm);
  border-left: 2px solid var(--brand);
}

/* ── Rangées horizontales sur téléphone ────────────────────────────────────
   À 158px fixes, deux cartes remplissaient l'écran pile-poil : plus rien ne
   dépassait, et la rangée se lisait comme une grille de deux — le reste du
   contenu restait invisible. On la rétrécit pour qu'une troisième carte
   dépasse : ce débord est ce qui dit « ça défile ». */
@media (max-width: 560px) {
  .row { grid-auto-columns: clamp(126px, 38vw, 158px); gap: 10px; }
  .card { padding: 8px; }
  .sec { margin-bottom: 24px; }
}

/* ── Barre d'onglets sur téléphone ─────────────────────────────────────────
   « À suivre » et « Ce navigateur » se cassaient en deux lignes, ce qui
   déformait toute la barre. Les libellés ne se coupent plus, et le badge —
   dont la note juste au-dessus dit déjà la même chose — s'efface : à cette
   largeur, il ne fait que voler la place aux onglets. */
@media (max-width: 560px) {
  .tab { padding: 9px 12px; gap: 6px; white-space: nowrap; }
  .web-badge { display: none; }

  /* La barre de navigation flotte au-dessus du contenu : sans cette réserve,
     la dernière ligne de chaque panneau passe dessous et se coupe. */
  .pane { padding-bottom: 18px; }
  .empty { padding: 22px 16px; }
}

/* L'état vide d'un panneau se lit en haut, pas flottant au milieu du vide. */
#queue-empty { padding-top: var(--s6); }

/* En karaoké plein écran sur téléphone, l'en-tête se réduit à un mini-lecteur :
   la note y prenait un quart de la hauteur et repoussait les paroles. Elle a
   déjà été lue sur l'écran du lecteur. */
[data-pane="lyrics"] .bg-note { display: none; }

/* ── Pochette d'une playlist ───────────────────────────────────────────────
   Quatre vignettes en damier, ou une seule si la playlist est trop courte pour
   en fournir quatre. Le conteneur garde exactement la taille d'une pochette de
   titre : les lignes de la bibliothèque restent alignées. */
.item-art.is-mosaic {
  display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr;
  gap: 1px; overflow: hidden; padding: 0;
}
.item-art.is-mosaic img,
.item-art.is-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.item-art.is-cover { overflow: hidden; padding: 0; }

/* ── Listes étroites ───────────────────────────────────────────────────────
   Sous 420px, la durée et le titre se disputaient la largeur et le titre se
   coupait dès le nom de l'artiste (« Rick Astley - Never Gonn… »). La durée est
   la moins utile des deux : elle s'efface, le titre respire. */
@media (max-width: 420px) {
  .item-time { display: none; }
}

/* ── Choix de genre (accueil d'un serveur qui n'a rien écouté) ──────────────
   Une grille, pas une rangée qui défile : au démarrage à froid, tout doit être
   visible d'un coup. C'est le seul écran où la personne n'a aucun repère. */
.grid {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
}
/* La tuile de genre n'a pas de pochette à encadrer : c'est l'image ELLE-MÊME
   qui fait la carte. Elle se passe donc de la surface et du rembourrage que
   les autres cartes ont reçus. */
.genre-card { padding: 0; background: none; border: none; }
.genre-card:hover { background: none; box-shadow: var(--e3); }

.genre-art {
  position: relative; display: grid; place-items: center;
  aspect-ratio: 16 / 10; margin-bottom: 0;
  border-radius: var(--r-lg); overflow: hidden;
  box-shadow: var(--e2);
}
.genre-card:hover .genre-art { transform: none; }
/* Voile sombre : sur un dégradé clair, un libellé blanc devient illisible. */
.genre-art::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  background: linear-gradient(180deg, rgba(0,0,0,.05), rgba(0,0,0,.52));
}
.genre-label {
  position: relative; z-index: 1;
  font-weight: 800; font-size: 1rem; letter-spacing: -.01em;
  color: #fff; text-shadow: 0 2px 10px rgba(0,0,0,.55);
}

@media (max-width: 560px) {
  .grid { grid-template-columns: repeat(auto-fill, minmax(112px, 1fr)); gap: 10px; }
  .genre-label { font-size: .9rem; }
}

/* ══ Barre de lecture persistante ═════════════════════════════════════════ */

.mini {
  position: fixed; z-index: 6; left: 0; right: 0;
  display: flex; align-items: center; gap: 10px;
  padding: 10px 14px;
  background: rgba(14,14,18,.92); backdrop-filter: blur(18px);
  border-top: 1px solid var(--stroke);
  animation: mini-in .28s var(--ease);
}
@keyframes mini-in { from { transform: translateY(100%); } }

/* Le fil de progression coiffe la barre : il informe sans rien coûter en
   hauteur, dans un espace qui n'a de la place ni pour un chiffre ni pour une
   poignée. */
.mini-progress { position: absolute; top: 0; left: 0; right: 0; height: 2px; background: rgba(255,255,255,.10); }
.mini-progress span { display: block; height: 100%; width: 0; background: var(--brand); transition: width .25s linear; }

.mini-open {
  flex: 1; min-width: 0; display: flex; align-items: center; gap: var(--s3);
  border: none; background: none; cursor: pointer; font: inherit; color: inherit;
  padding: 0; text-align: left; border-radius: var(--r-sm);
}
.mini-open:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; }

.mini-art {
  width: 42px; height: 42px; flex: 0 0 auto; border-radius: 9px;
  object-fit: cover; background: var(--surface);
}
.mini-art-empty { display: grid; place-items: center; color: var(--faint); }
.mini-art-empty svg { width: 20px; height: 20px; }

/* min-width:0 sur le conteneur ET sur les lignes : sans les deux, un titre
   long pousse les commandes hors de l'écran au lieu de se couper. */
.mini-meta { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.mini-title, .mini-author { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mini-title  { font-size: .86rem; font-weight: 700; }
.mini-author { font-size: .76rem; color: var(--faint); }

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

/* Étroit : la barre se pose SUR la navigation du bas, pas dessous. */
@media (max-width: 879px) {
  .mini { bottom: calc(70px + env(safe-area-inset-bottom)); }
}

/*
 * Large : bandeau pleine largeur sous la barre latérale — la forme qu'a le
 * lecteur de tout produit musical. Les commandes sont CENTRÉES sur la page et
 * non collées à droite : c'est là que l'œil et la souris les cherchent.
 */
@media (min-width: 880px) {
  .mini {
    bottom: 0; height: var(--bar-h); padding: 0 var(--s6); gap: var(--s5);
    background: var(--surface-1); backdrop-filter: none;
    border-top: 1px solid var(--line);
    box-shadow: 0 -12px 32px -18px rgba(0,0,0,.9), inset 0 1px 0 var(--edge);
  }
  .mini-art { border-radius: var(--r-sm); box-shadow: var(--e2); }
  /* Le titre en cours porte la couleur de la marque : c'est LE repère. */
  .mini-title { font-size: .88rem; }
  .mini-open { flex: 0 1 320px; }
  .mini-art { width: 50px; height: 50px; }
  .mini-time { margin-left: auto; }

  /*
   * La position se lisait sur un FIL de 2 px courant sur toute la largeur, au
   * ras du bord haut de la barre — mesuré : 30 px de rose sur 1 440, juste
   * sous une bordure de 1 px. À ce format, on lit une bordure, pas une
   * progression.
   *
   * Sur grand écran, elle devient ce qu'elle est dans n'importe quel lecteur :
   * une piste, sous les commandes, au centre. Le fil du haut reste pour les
   * écrans étroits, où il n'y a pas la place.
   */
  .mini-controls { position: absolute; left: 50%; translate: -50% 0; top: 9px; gap: var(--s2); }
  .mini-progress {
    top: auto; bottom: 11px; left: 50%; right: auto; translate: -50% 0;
    width: min(440px, 32vw); height: 5px;
    border-radius: var(--r-full); overflow: hidden;
    background: rgba(255,255,255,.13);
  }
  .mini-progress span { border-radius: inherit; box-shadow: 0 0 12px -3px var(--brand); }
}

/* Position de lecture. Elle n'était lisible QUE sur la vue lecteur : depuis
   n'importe quelle autre page, on savait ce qui jouait, jamais où on en
   était. */
.mini-time {
  display: none; font-size: .74rem; color: var(--faint);
  font-variant-numeric: tabular-nums; white-space: nowrap; flex: 0 0 auto;
}
@media (min-width: 880px) { .mini-time { display: block; } }

/* « Précédent » n'existait que sur la vue lecteur, alors que l'action est
   réelle des deux côtés (bot et navigateur). Au doigt, la place manque :
   la barre garde lecture/pause et suivant, le reste est sur le lecteur. */
.mini-prev { display: none; }
@media (min-width: 880px) { .mini-prev { display: inline-flex; } }

/* Les vues réservent la hauteur de la barre : sans ça, leur dernière ligne
   passe dessous et devient inatteignable. */
body:has(.mini:not([hidden])) .view { padding-bottom: calc(var(--bar-h) + 24px); }
/* En étroit, le calcul vit dans la règle !important plus haut : la barre se
   pose SUR la navigation du bas, et les deux hauteurs y sont déjà réservées. */

/* La déconnexion ne doit pas se lire comme une entrée de bibliothèque parmi les
   autres : on la met à distance et en retrait. */
.logout-row { width: 100%; border: none; background: none; font: inherit; color: inherit; cursor: pointer; text-align: left; margin-top: 10px; }
.logout-row .item-title { color: var(--dim); }

/* ══ Recherche enrichie ══════════════════════════════════════════════════════
 *
 * La recherche ne rend plus une liste plate : un résultat mis en avant, puis
 * des titres, des albums, des artistes et des playlists. Tout ce bloc sert ces
 * quatre formes, et RIEN d'autre — les composants existants (.item, .card,
 * .row, .list) sont réutilisés tels quels partout où ils suffisaient.
 */

/* Filtres de source, sous la barre de recherche. */
.chips { display: flex; gap: 8px; margin: -8px 0 16px; overflow-x: auto; padding-bottom: 2px; scrollbar-width: none; }
.chips::-webkit-scrollbar { display: none; }
.chip {
  flex: 0 0 auto; padding: var(--s2) var(--s4); border-radius: var(--r-full);
  border: 1px solid var(--stroke); background: var(--surface); color: var(--dim);
  font: inherit; font-size: .8rem; font-weight: 600; cursor: pointer;
  transition: background .16s var(--ease), color .16s var(--ease), border-color .16s var(--ease);
}
.chip:hover { background: var(--hover); color: var(--text); }
.chip.is-active { background: var(--brand); border-color: var(--brand); color: var(--brand-ink); }
/* Sans cette règle, le contour par défaut du navigateur (1 px, sombre) était
   invisible sur le fond du panneau : au clavier, on ne savait plus où on était. */
.chip:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

/* Le résultat mis en avant. Grand, une seule fois, tout en haut. */
.top-card {
  display: flex; gap: 18px; align-items: center;
  padding: 16px; margin-bottom: 22px; border-radius: var(--r-lg);
  border: 1px solid var(--stroke);
  /* Les teintes ambiantes sont déjà réécrites par le lecteur : les réutiliser
     ici fait tenir le bloc avec le reste du panneau sans nouvelle variable. */
  background: linear-gradient(135deg, color-mix(in srgb, var(--amb-a) 16%, transparent), var(--surface) 65%);
  animation: row-in .34s var(--ease) backwards;
}
.top-art {
  width: 116px; height: 116px; flex: 0 0 auto; object-fit: cover;
  border-radius: var(--r-md); background: var(--surface);
  box-shadow: 0 12px 30px rgba(0,0,0,.5);
  display: grid; place-items: center; color: var(--faint);
}
.top-art svg { width: 34%; height: 34%; }
.round { border-radius: 50% !important; }

.top-main  { min-width: 0; flex: 1; }
.top-kind  { font-size: .68rem; font-weight: 800; letter-spacing: .1em; text-transform: uppercase; color: var(--brand); }
.top-title { font-size: 1.5rem; font-weight: 800; letter-spacing: -.02em; line-height: 1.15; margin: 4px 0 5px;
             overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.top-sub   { font-size: .85rem; color: var(--dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.top-actions { display: flex; gap: var(--s2); margin-top: var(--s3); flex-wrap: wrap; }
.top-actions .btn {
  width: auto; height: 38px; padding: 0 17px; gap: 8px;
  grid-auto-flow: column; background: var(--surface); border: 1px solid var(--stroke); font-size: .84rem; font-weight: 700;
}
.top-actions .btn:hover { background: var(--hover); }
.top-actions .btn svg { width: 17px; height: 17px; fill: currentColor; }
.btn-primary { background: var(--brand) !important; border-color: var(--brand) !important; color: var(--brand-ink) !important; }

/* Pastilles de provenance. Une carte fusionnée en porte DEUX : elle vient du
   catalogue Spotify et se joue depuis YouTube — le dire évite de faire croire
   à une source unique. */
.badges { display: flex; gap: var(--s2); margin-top: var(--s2); flex-wrap: wrap; }
.badge {
  font-size: .63rem; font-weight: 800; letter-spacing: .04em; text-transform: uppercase;
  padding: 3px 8px; border-radius: var(--r-full); border: 1px solid var(--stroke); color: var(--dim);
}
.badge-spotify    { color: #1ed760; border-color: rgba(30,215,96,.32);  background: rgba(30,215,96,.08); }
.badge-youtube    { color: #ff6b6b; border-color: rgba(255,107,107,.3); background: rgba(255,107,107,.08); }
.badge-soundcloud { color: #ff8a3d; border-color: rgba(255,138,61,.3);  background: rgba(255,138,61,.08); }
.badge-explicit   { color: var(--faint); }

/* Ce que chaque moteur a rendu, en pied de résultats : une recherche muette
   côté Spotify se voit ici au lieu de se deviner. */
.engines { margin: 18px 0 4px; font-size: .7rem; color: var(--faint); text-align: center; letter-spacing: .02em; }

/* Carte d'artiste : pochette ronde, sous-titre centré comme le nom. */
.card-artist { text-align: center; }
.card-artist .card-title { -webkit-line-clamp: 1; }

/* ── Fiche album / artiste / playlist ─────────────────────────────────────── */
.btn-back {
  width: auto; height: 34px; padding: 0 14px 0 10px; gap: 6px; grid-auto-flow: column;
  background: var(--surface); border: 1px solid var(--stroke); color: var(--dim);
  font-size: .8rem; font-weight: 600; margin-bottom: 16px;
}
.btn-back:hover { background: var(--hover); color: var(--text); }
.btn-back svg { width: 16px; height: 16px; fill: currentColor; }

.ent-head { display: flex; gap: 22px; align-items: flex-end; margin-bottom: 26px; }
.ent-art {
  width: 168px; height: 168px; flex: 0 0 auto; object-fit: cover;
  border-radius: var(--r-md); background: var(--surface);
  box-shadow: 0 18px 44px rgba(0,0,0,.55);
  display: grid; place-items: center; color: var(--faint);
}
.ent-art svg { width: 32%; height: 32%; }
.ent-meta  { min-width: 0; flex: 1; }
.ent-title { font-size: 2rem; font-weight: 900; letter-spacing: -.03em; line-height: 1.08; margin: 5px 0 7px; }
.ent-sub   { font-size: .86rem; color: var(--dim); }
.ent-warn  { margin-top: 9px; font-size: .74rem; color: #ffc46b; }

/* Écran étroit : la fiche passe en colonne, sinon la pochette mange tout. */
@media (max-width: 560px) {
  .top-card { gap: 13px; padding: 13px; }
  .top-art  { width: 84px; height: 84px; }
  .top-title { font-size: 1.14rem; }
  .ent-head { flex-direction: column; align-items: flex-start; gap: 14px; }
  .ent-art  { width: 128px; height: 128px; }
  .ent-title { font-size: 1.5rem; }
}

/* ══ Habillage v2 — ambiance, matière, mouvement ═════════════════════════════
 *
 * Tout ce bloc repose sur DEUX variables déjà calculées par app.js à partir de
 * la pochette en cours (`ambientFrom()` : --amb-a et --amb-b). Aucune couleur
 * n'est inventée ici : le panneau prend la teinte de ce qu'on écoute, et rien
 * ne bouge quand rien ne joue.
 *
 * Contrainte tenue partout : `prefers-reduced-motion` coupe TOUTES les
 * animations de ce bloc — une aurore qui dérive en fond est exactement ce que
 * ce réglage existe pour éteindre.
 */

/* ── Pas d'aurore ici, et c'est un CHOIX ──────────────────────────────────── */
/*
 * J'avais écrit un second calque de halos flous par-dessus celui-ci. Deux
 * raisons de l'avoir retiré, toutes deux déjà consignées plus haut dans ce
 * fichier :
 *
 *   1. `.ambient` fait DÉJÀ exactement ça — trois halos teintés par la pochette
 *      (--amb-a / --amb-b), la marque en troisième, voile sombre et grain.
 *   2. Mon calque utilisait `filter: blur(90px)` sur des éléments ANIMÉS :
 *      c'est précisément ce que ce dépôt a mesuré à **15 images par seconde**
 *      avant de le remplacer par des `radial-gradient` (60 i/s). Le refaire
 *      aurait annulé ce correctif sans que rien ne le signale.
 *
 * Et mes `@keyframes drift-a/b/c` portaient les mêmes noms que ceux de
 * `.ambient` : déclarés plus bas, ils AURAIENT ÉCRASÉ les siens en silence.
 * Si l'ambiance doit évoluer, elle évolue dans `.ambient`, pas à côté.
 */

/* ── Matière : la pochette derrière le lecteur ────────────────────────────── */
/*
 * La grande pochette est déjà là ; on en pose une COPIE floutée derrière la
 * scène. C'est ce qui donne au lecteur sa profondeur, et ça ne coûte aucun
 * téléchargement de plus : c'est la même image, déjà en cache.
 */
.stage { position: relative; }
.stage::before {
  content: ''; position: absolute; inset: -12% -20% auto; height: 62%;
  background: var(--stage-art) center/cover no-repeat;
  filter: blur(54px) saturate(160%); opacity: .34;
  border-radius: 50%; pointer-events: none; z-index: -1;
  transition: opacity .6s var(--ease);
}
body:not(.is-playing) .stage::before { opacity: 0; }

/* La pochette respire doucement pendant la lecture — l'amplitude est minuscule
   (2 %) : on veut que ce soit senti, pas remarqué. */
body.is-playing .art { animation: art-in .5s var(--ease), breathe 7s ease-in-out 1s infinite; }
@keyframes breathe { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.02); } }
@media (prefers-reduced-motion: reduce) {
  body.is-playing .art { animation: none; }
}

/*
 * Le bloc « relief au survol » d'une passe précédente vivait ICI et écrasait le
 * nouveau système de cartes, plus bas dans la cascade. Constaté en forçant
 * `:hover` par le protocole DevTools : la carte passait de sa surface opaque
 * (#201d28) à un voile `rgba(255,255,255,.07)` qui, composé sur le fond de
 * page, donne PLUS SOMBRE que le repos. Survoler une carte l'assombrissait.
 *
 * Il redéfinissait aussi `transition` sans `box-shadow` ni `border-color`, donc
 * l'élévation et le liseré apparaissaient d'un coup, et posait une lueur sur
 * `.card-art` — désormais enfermée dans `.card-media { overflow: hidden }`,
 * donc rognée et invisible.
 *
 * Seule la réaction à l'appui méritait d'être gardée : elle rejoint le système
 * de cartes plus haut.
 */

.top-card { position: relative; overflow: hidden; }
/* Voile teinté par la pochette, posé sur le résultat mis en avant. */
.top-card::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(120% 140% at 0% 0%, color-mix(in srgb, var(--amb-a) 22%, transparent), transparent 60%);
}
.top-card > * { position: relative; z-index: 1; }

/* ── Squelettes de chargement ─────────────────────────────────────────────── */
/*
 * « Recherche… » en texte ne dit rien de ce qui arrive. Un squelette dessine
 * la forme du résultat AVANT qu'il existe : l'attente paraît plus courte parce
 * que la page ne saute pas quand les données tombent.
 */
/*
 * MESURÉ : la silhouette faisait 64 px là où la ligne réelle en fait 68, et la
 * carte-silhouette 194 px contre 224 — soit 24 px de saut sur six résultats et
 * 30 px par rangée de l'accueil, à l'instant précis où les données arrivent.
 * C'est le moment où l'œil est le plus attentif.
 *
 * La silhouette PARTAGE désormais la géométrie de ce qu'elle remplace (cf. la
 * règle `.item, .skel-row` plus haut, et `.card-media` dans skeletonCards) :
 * elles ne peuvent plus diverger.
 */
.skel { display: flex; flex-direction: column; gap: 2px; }
.skel-row { cursor: default; pointer-events: none; }
.skel-art { width: 46px; height: 46px; border-radius: var(--r-sm); flex: 0 0 auto; }
.skel-lines { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--s2); }
.skel-l { height: 10px; border-radius: 5px; }
.skel-l.w70 { width: 70%; } .skel-l.w40 { width: 40%; }
.skel-art, .skel-l {
  background: linear-gradient(100deg, var(--surface) 30%, var(--hover) 50%, var(--surface) 70%);
  background-size: 220% 100%;
  animation: shimmer 1.25s linear infinite;
}
@keyframes shimmer { from { background-position: 180% 0; } to { background-position: -40% 0; } }
@media (prefers-reduced-motion: reduce) { .skel-art, .skel-l { animation: none; } }

/* ── Pastilles de provenance : les couleurs de chaque plateforme ──────────── */
.badge-deezer { color: #ff6b4a; border-color: rgba(255,107,74,.32); background: rgba(255,107,74,.08); }
.badge-itunes { color: #fa5a7c; border-color: rgba(250,90,124,.30); background: rgba(250,90,124,.08); }
.badge-radio  { color: #6fd3ff; border-color: rgba(111,211,255,.30); background: rgba(111,211,255,.08); }

/* ── Stations de radio ────────────────────────────────────────────────────── */
/*
 * Une station n'a pas de pochette : ses favicons viennent d'hôtes arbitraires
 * et ne sont jamais relayés (choix de sécurité, cf. services/music/radio.js).
 * Elle se reconnaît donc à sa vignette dédiée et à son point « en direct ».
 */
.item-art.is-station, .card-art.is-station, .top-art.is-station {
  display: grid; place-items: center;
  background: linear-gradient(140deg, color-mix(in srgb, var(--amb-a) 35%, #101014), #101014);
  color: var(--text);
}
.live-dot {
  display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  background: #ff4d4d; margin-right: 6px; vertical-align: middle;
  box-shadow: 0 0 0 0 rgba(255,77,77,.7); animation: pulse 1.8s ease-out infinite;
}
@keyframes pulse {
  0%   { box-shadow: 0 0 0 0 rgba(255,77,77,.6); }
  70%  { box-shadow: 0 0 0 8px rgba(255,77,77,0); }
  100% { box-shadow: 0 0 0 0 rgba(255,77,77,0); }
}
@media (prefers-reduced-motion: reduce) { .live-dot { animation: none; } }

/* ── Égaliseur sur la ligne en cours de lecture ───────────────────────────── */
.row-eq { display: inline-flex; align-items: flex-end; gap: 2px; height: 13px; margin-right: 8px; }
.row-eq i {
  width: 2.5px; height: 100%; border-radius: 2px; background: var(--brand);
  transform-origin: bottom; animation: eq .9s ease-in-out infinite;
}
.row-eq i:nth-child(2) { animation-delay: .2s; }
.row-eq i:nth-child(3) { animation-delay: .4s; }
.item.is-current .item-title { color: var(--brand); }
@media (prefers-reduced-motion: reduce) { .row-eq i { animation: none; height: 60%; } }

/* ── Pas de défilement de titre : l'ellipse fait mieux ────────────────────── */
/*
 * J'avais écrit un défilement au survol pour les titres trop longs. Retiré
 * avant livraison : pour garder `text-overflow: ellipsis`, le texte doit être
 * le contenu DIRECT de l'élément — dès qu'on l'enveloppe dans un <span> pour
 * l'animer, l'ellipse disparaît. Sur mobile, où il n'y a pas de survol, on
 * aurait donc échangé « … » contre un mot coupé net. Mauvais marché.
 */

/* ── Filtres de source : la teinte de la plateforme au survol ─────────────── */
.chip[data-source="deezer"]:hover     { border-color: rgba(255,107,74,.5);  color: #ff6b4a; }
.chip[data-source="spotify"]:hover    { border-color: rgba(30,215,96,.5);   color: #1ed760; }
.chip[data-source="itunes"]:hover     { border-color: rgba(250,90,124,.5);  color: #fa5a7c; }
.chip[data-source="youtube"]:hover    { border-color: rgba(255,107,107,.5); color: #ff6b6b; }
.chip[data-source="soundcloud"]:hover { border-color: rgba(255,138,61,.5);  color: #ff8a3d; }
.chip[data-source="radio"]:hover      { border-color: rgba(111,211,255,.5); color: #6fd3ff; }
.chip.is-active:hover { color: var(--brand-ink); }

/* ── Bandeau d'en-tête d'une fiche : dégradé tiré de la pochette ──────────── */
.ent-head { position: relative; }
.ent-head::before {
  content: ''; position: absolute; inset: -22px -22px -10px; z-index: -1;
  background: linear-gradient(180deg, color-mix(in srgb, var(--amb-a) 18%, transparent), transparent 85%);
  border-radius: var(--r-lg);
}

/* ══ Mission UI/UX — raccourci, historique, file manipulable ═════════════════
 *
 * Tout ce bloc n'utilise que `opacity`, `transform` et des couleurs : aucun
 * `filter` animé, aucun flou permanent. Le dépôt a déjà mesuré qu'un blur animé
 * faisait tomber le panneau à 15 images par seconde (voir plus haut) — la règle
 * vaut pour tout ce qu'on ajoute ensuite.
 */

/* Indice du raccourci, dans la barre de recherche. Masqué au tactile : sans
   clavier, annoncer « Ctrl K » est au mieux du bruit. */
.search-kbd {
  flex: 0 0 auto; font: inherit; font-size: .68rem; font-weight: 700; letter-spacing: .04em;
  color: var(--faint); background: var(--surface); border: 1px solid var(--stroke);
  border-radius: 6px; padding: 3px 7px; white-space: nowrap;
}
.search-bar:focus-within .search-kbd { opacity: 0; }
@media (hover: none), (max-width: 560px) { .search-kbd { display: none; } }

/* Recherches récentes : les mêmes pastilles que les filtres, mais sur
   plusieurs lignes — une liste d'historique n'a pas à défiler latéralement. */
.chips-wrap { flex-wrap: wrap; overflow: visible; }
.chips-wrap .chip { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Action discrète posée dans un titre de section (« Effacer »). */
.sec-action {
  margin-left: auto; width: auto; height: 26px; padding: 0 10px;
  font-size: .72rem; font-weight: 600; color: var(--faint);
  border: 1px solid var(--stroke); background: var(--surface);
}
.sec-action:hover { color: var(--text); background: var(--hover); }

/* ── File : monter, descendre, retirer ───────────────────────────────────── */
/*
 * Les commandes n'apparaissent qu'au survol ou au focus CLAVIER. Trois boutons
 * visibles en permanence sur chaque ligne transformeraient la file en tableau
 * de bord ; mais les cacher au clavier les rendrait inatteignables, d'où le
 * `:focus-within`. Sur tactile — pas de survol — elles restent affichées.
 */
.q-actions { display: flex; gap: 2px; flex: 0 0 auto; opacity: 0; transition: opacity .15s var(--ease); }
.queue-item:hover .q-actions,
.queue-item:focus-within .q-actions { opacity: 1; }
@media (hover: none) { .q-actions { opacity: 1; } }

.btn-xs {
  width: 26px; height: 26px; font-size: 11px; color: var(--faint);
  border-radius: var(--r-sm);
}
.btn-xs:hover { color: var(--text); background: var(--hover); }
/* Le retrait est la seule action destructrice de la ligne : elle se signale. */
.q-actions .btn-xs:last-child:hover { color: #ff6b6b; }

/* Bouton pleine largeur, pour déplier une liste (« Voir les 9 autres »). */
.btn-wide {
  width: 100%; height: 38px; margin-top: 12px;
  border: 1px solid var(--stroke); background: var(--surface);
  font-size: .82rem; font-weight: 600; color: var(--dim);
}
.btn-wide:hover { background: var(--hover); color: var(--text); }

/*
 * Silhouette d'une carte. Ses trois blocs reprennent les hauteurs MESURÉES de
 * la vraie carte — pochette carrée, titre sur deux lignes (36 px), sous-titre
 * (14 px + 4 px de marge) — pour que la rangée ne saute pas d'un pixel quand
 * les données arrivent.
 */
.skel-card { pointer-events: none; cursor: default; }
.skel-cover { width: 100%; height: auto; aspect-ratio: 1; border-radius: 0; }
.skel-title { height: 36px; display: flex; flex-direction: column; justify-content: space-between; }
.skel-sub { height: 14px; margin-top: var(--s1); }   /* hauteur réelle de .card-sub */
.skel-l.w50 { width: 50%; }

/* Seconde ligne d'un état vide : ce qu'on peut tenter, en retrait du constat. */
.empty-hint { display: inline-block; margin: 0; font-size: .8rem; color: var(--faint); }

/*
 * Écran étroit : la ligne de file doit rester LISIBLE avant d'être pilotable.
 *
 * Les trois commandes font 78 px à elles seules, et sur tactile elles sont
 * affichées en permanence (pas de survol pour les révéler). À 320 px, le titre
 * n'avait plus qu'une centaine de pixels. On resserre l'écart et les boutons,
 * sans descendre sous 24 px — en dessous, la cible devient trop petite pour le
 * pouce.
 */
@media (max-width: 480px) {
  .queue-item { gap: 8px; padding: 10px; }
  .q-actions { gap: 0; }
  .btn-xs { width: 24px; height: 24px; }
  /* Le numéro d'ordre passe en retrait : la position se devine, le titre non. */
  .q-index { min-width: 1.4em; font-size: .72rem; }
}

/*
 * Numéro de piste d'un album : posé AVANT la pochette, aligné en chiffres de
 * même largeur pour que la colonne reste droite de 1 à 99. Discret — c'est un
 * repère, pas une information qu'on vient chercher.
 */
.item-num {
  flex: 0 0 auto; min-width: 1.7em; text-align: right;
  color: var(--faint); font-size: .78rem; font-variant-numeric: tabular-nums;
}
@media (max-width: 480px) { .item-num { min-width: 1.3em; font-size: .72rem; } }

/*
 * Biographie d'artiste (TheAudioDB). Repliée par défaut : la page sert d'abord
 * à écouter. `grid-template-rows` plutôt qu'une hauteur en pixels — c'est la
 * seule façon d'animer l'ouverture d'un texte dont on ne connaît pas la
 * hauteur, sans la mesurer en JavaScript.
 */
.ent-bio {
  display: grid; grid-template-rows: 0fr; overflow: hidden;
  margin: 10px 0 0;
  transition: grid-template-rows .28s var(--ease);
}
.ent-bio.is-open { grid-template-rows: 1fr; }
/* L'enfant DOIT pouvoir être écrasé à zéro, sinon la rangée ne se referme pas. */
.ent-bio > p {
  min-height: 0; margin: 0;
  font-size: .84rem; line-height: 1.55; color: var(--dim);
  max-width: 62ch;
}
.ent-bio-toggle { margin: 12px 0 0; }
@media (prefers-reduced-motion: reduce) { .ent-bio { transition: none; } }

/* ══ Zones tactiles — mesurées au rendu réel ═════════════════════════════════
 *
 * Inspection headless à neuf tailles : les pastilles de filtre tombaient à
 * 30 px de haut, et `.btn-sm` à 30 px de large dès 320 px. Sous le pouce, une
 * cible de 30 px se rate — surtout sept pastilles alignées.
 *
 * On ne touche PAS à la taille du texte : seule la zone cliquable grandit.
 */
.chip { min-height: 36px; display: inline-flex; align-items: center; }

@media (hover: none) {
  /* Sans survol, la précision vient du doigt seul : on vise 42 px. */
  .chip { min-height: 42px; padding-inline: 16px; }
}

@media (max-width: 360px) {
  /* À 320 px, `.btn-sm` était écrasé à 30 px de large par la place restante.
     On lui rend une cible carrée plutôt que de le laisser s'aplatir. */
  .btn-sm { min-width: 34px; width: 34px; }
  .item { gap: 10px; padding: 9px 8px; }
}

/* ══════════════════════════════════════════════════════════════════════════ */
/* ══ Refonte visuelle du panneau authentifié ══════════════════════════════ */
/* ══════════════════════════════════════════════════════════════════════════ */
/*
 * Tout ce bloc répond à des défauts MESURÉS au rendu réel (Chrome 131 piloté
 * par le protocole DevTools, vraies données, vraies pochettes). Chaque règle
 * porte le chiffre qui l'a motivée.
 */

/* ── En-tête de l'accueil ──────────────────────────────────────────────────
 *
 * MESURÉ : l'accueil commençait par « Écoutés récemment », sans titre de page
 * ni marque, et n'offrait que TROIS tailles de texte (16,8 / 12,9 / 11,1 px).
 * Rien n'annonçait ce qu'était cette page ni par où commencer.
 */
.hero { margin: 2px 0 var(--s7); }

/* Le salut et l'entrée de recherche partagent une ligne : sur grand écran, la
   moitié droite de l'en-tête était vide. */
.hero-top { display: flex; align-items: flex-end; gap: var(--s6); flex-wrap: wrap; }
.hero-top > div { flex: 1 1 auto; min-width: 0; }
.hero-top .hero-cta { flex: 0 1 460px; margin: 0; }
@media (max-width: 879px) { .hero-top { display: block; } .hero-top .hero-cta { margin-top: var(--s3); } }

/* ── « Reprendre » ─────────────────────────────────────────────────────────
 *
 * Le bloc d'accroche de l'accueil. Sa pochette sert de fond, floutée et
 * assombrie — flou STATIQUE, peint une seule fois : animé, il tombe à 15 i/s.
 */
.resume {
  position: relative; overflow: hidden;
  display: flex; align-items: center; gap: var(--s6);
  padding: var(--s6);
  border-radius: var(--r-xl);
  border: 1px solid var(--stroke);
  background: var(--surface-2);
  box-shadow: var(--e3);
  isolation: isolate;
}
/*
 * PIÈGE : en `z-index: -1`, le fond flouté passait DERRIÈRE le
 * `background: var(--surface-2)` opaque de `.resume` — donc invisible. Les
 * trois couches sont maintenant empilées explicitement, au-dessus.
 */
.resume-bg {
  position: absolute; inset: -60px; z-index: 0;
  background-size: cover; background-position: center;
  filter: blur(48px) saturate(1.7);
  opacity: .85;
  transform: scale(1.2);            /* masque les bords délavés du flou */
}
/* Voile : sans lui, un titre blanc sur une pochette claire devient illisible. */
.resume::after {
  content: ''; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(100deg, rgba(11,10,14,.94) 22%, rgba(11,10,14,.58) 70%, rgba(11,10,14,.42));
}
.resume-art, .resume-main { position: relative; z-index: 2; }
.resume-art {
  width: 132px; height: 132px; flex: 0 0 auto;
  border-radius: var(--r-lg); object-fit: cover;
  box-shadow: var(--e3);
}
.resume-main { min-width: 0; flex: 1; }
.resume-kicker {
  margin: 0; font-size: var(--t-micro); font-weight: 800;
  letter-spacing: .14em; text-transform: uppercase; color: var(--brand);
}
.resume-title {
  margin: var(--s2) 0 var(--s1); font-size: var(--t-h1); font-weight: 800;
  letter-spacing: -.02em; line-height: 1.18; color: var(--text);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.resume-meta {
  margin: 0; font-size: var(--t-meta); color: var(--dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.resume-actions { display: flex; gap: var(--s2); margin-top: var(--s4); flex-wrap: wrap; }

/* Deux boutons, deux poids : l'action principale est pleine, l'autre en creux. */
.btn-solid, .btn-ghost {
  display: inline-flex; align-items: center; gap: var(--s2);
  min-height: 42px; padding: 0 var(--s5);
  border-radius: var(--r-full); cursor: pointer;
  font: inherit; font-size: var(--t-meta); font-weight: 700;
  transition: background .16s var(--ease), transform .16s var(--ease-out),
              border-color .16s var(--ease), color .16s var(--ease);
}
.btn-solid { border: none; background: var(--brand); color: var(--brand-ink); box-shadow: var(--glow); }
.btn-solid:hover { background: var(--brand-hover); transform: translateY(-1px); }
.btn-ghost { border: 1px solid var(--stroke); background: rgba(255,255,255,.04); color: var(--text); }
.btn-ghost:hover { background: var(--hover); border-color: var(--brand); }
.btn-solid svg, .btn-ghost svg { width: 16px; height: 16px; }
.btn-solid:focus-visible, .btn-ghost:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; }

@media (max-width: 879px) {
  .resume { flex-direction: column; align-items: flex-start; gap: var(--s4); padding: var(--s5); }
  .resume-art { width: 108px; height: 108px; }
  .resume::after { background: linear-gradient(180deg, rgba(11,10,14,.74), rgba(11,10,14,.94)); }
  .resume-actions { width: 100%; }
  .btn-solid, .btn-ghost { flex: 1 1 auto; justify-content: center; }
}
@media (prefers-reduced-motion: reduce) {
  .btn-solid, .btn-ghost { transition: none; }
  .btn-solid:hover { transform: none; }
}
.hero-title {
  margin: 0; font-size: var(--t-hero); font-weight: 800; letter-spacing: -.035em;
  line-height: 1.1; color: var(--text);
}
.hero-sub { margin: 6px 0 16px; font-size: .95rem; color: var(--dim); }

/* Point d'entrée vers la recherche. Il ouvre la vue Recherche et y pose le
   curseur — le même chemin que l'entrée du rail et que Ctrl+K. */
.hero-cta {
  display: flex; align-items: center; gap: 11px;
  width: 100%; max-width: 560px; min-height: 48px;
  padding: 0 14px; border-radius: var(--r-full);
  border: 1px solid var(--stroke); background: var(--surface-2);
  color: var(--faint); font: inherit; font-size: .9rem; text-align: left; cursor: pointer;
  transition: border-color .16s var(--ease), background .16s var(--ease), color .16s var(--ease);
}
.hero-cta:hover { border-color: var(--brand); color: var(--dim); background: var(--hover); }
.hero-cta:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; }
.hero-cta svg { width: 18px; height: 18px; flex: 0 0 auto; }
.hero-kbd {
  margin-left: auto; flex: 0 0 auto;
  font: inherit; font-size: .68rem; font-weight: 600; letter-spacing: .04em;
  color: var(--faint); background: var(--surface); border: 1px solid var(--stroke);
  border-radius: 6px; padding: 3px 7px;
}
/* Pas de clavier au doigt : l'indice n'aurait aucun sens. */
@media (hover: none) { .hero-kbd { display: none; } }
@media (max-width: 879px) {
  .hero { margin-bottom: 20px; }
  .hero-title { font-size: 1.55rem; }
  .hero-sub { font-size: .88rem; margin-bottom: 13px; }
}

/* ── Rangées défilables ────────────────────────────────────────────────────
 *
 * MESURÉ : 2 056 px de pochettes pour 1 218 px visibles — 838 px (41 %) hors
 * de l'écran, sans aucun indicateur, et inatteignables à la molette d'une
 * souris ordinaire. Les flèches ne sont posées que lorsqu'il reste vraiment
 * quelque chose de ce côté (cf. `scrollable()` dans app.js).
 */
.row-shell { position: relative; }

.row-nav {
  position: absolute; top: calc(50% - 22px); translate: 0 -50%; z-index: 2;
  width: 36px; height: 36px; border-radius: 50%;
  display: grid; place-items: center; cursor: pointer;
  border: 1px solid var(--stroke); background: rgba(16,16,20,.92);
  color: var(--text); backdrop-filter: blur(8px);
  opacity: 0; transition: opacity .16s var(--ease), background .16s var(--ease);
  box-shadow: 0 6px 20px rgba(0,0,0,.5);
}
.row-nav svg { width: 17px; height: 17px; }
.row-nav-prev { left: -10px; }
.row-nav-next { right: -10px; }
.row-nav-next svg { rotate: 180deg; }
.row-nav:hover { background: var(--brand); color: var(--brand-ink); border-color: var(--brand); }
.row-nav:focus-visible { opacity: 1; outline: 2px solid var(--brand); outline-offset: 2px; }
/* Elles se montrent à l'approche, comme les commandes d'un lecteur. */
.row-shell:hover .row-nav, .row-shell:focus-within .row-nav { opacity: 1; }

/* Au doigt, on fait glisser : une flèche prendrait la place d'une pochette
   pour un geste que personne n'utilise. */
@media (hover: none) { .row-nav { display: none; } }

/* ── Colonne « album » des listes larges ───────────────────────────────────
 *
 * MESURÉ : une ligne de résultat fait 1 212 px de large pour une pochette de
 * 46 px et ~350 px de texte — près de 850 px de vide avant la durée. L'album
 * et l'année étaient DÉJÀ dans les données, affichés nulle part.
 *
 * Sous 1 100 px la place manque : la colonne disparaît plutôt que d'écraser
 * le titre.
 */
.item-album {
  display: none; flex: 0 1 260px; min-width: 0;
  font-size: var(--t-meta); color: var(--faint);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
@media (min-width: 1100px) {
  .item-album { display: block; }
  /*
   * Titre et album se PARTAGENT la place restante. Sans ça, `.item-main`
   * (flex: 1) prenait tout — mesuré à 730 px pour un titre de 40 — et
   * repoussait l'album à 1 057 px, c'est-à-dire collé à la durée, avec le
   * même grand vide qu'avant au milieu de la ligne.
   */
  .item-main  { flex: 1 1 0; }
  .item-album { flex: 1 1 0; }
}

/* ── Profondeur : le panneau latéral du lecteur ────────────────────────────
 *
 * MESURÉ : sur 1 440 × 900, le volet droit du lecteur fait 778 × 856 px et
 * ne contenait, file vide, qu'une seule ligne de texte posée sur le fond de
 * page. Rien ne le délimitait — le vide ne se lisait pas comme un panneau
 * mais comme un bout de page oublié.
 */
@media (min-width: 880px) {
  .side {
    background: var(--surface-1); border: 1px solid var(--line);
    border-radius: var(--r-lg); padding: 12px 14px;
  }
  .tabs { border-bottom: 1px solid var(--line); padding-bottom: 10px; margin-bottom: 4px; }
}

/* ── États vides ───────────────────────────────────────────────────────────
 * Une seule forme partout : une phrase qui dit quoi faire, pas un constat.
 */
.empty { max-width: 42ch; margin-inline: auto; }

/* ── Barre de recherche : plus présente, alignée sur le reste ──────────────── */
.search-bar { background: var(--surface-2); }

/* ── Densité des lignes sur grand écran ────────────────────────────────────
 * Le corps de page passe de 1 218 à 1 320 px une fois le rail collé au bord :
 * on en profite pour aérer la ligne plutôt que pour l'étirer.
 */
@media (min-width: 1100px) {
  .item, .skel-row { padding: var(--s3) var(--s4); gap: var(--s4); }
}

/* ── Playlists dans la barre latérale ──────────────────────────────────────
 *
 * MESURÉ : sous « Lecteur », le rail laissait ~570 px de surface vide sur un
 * écran de 900 px. Ces entrées ouvrent une playlist réelle, elles ne sont pas
 * un décor de remplissage.
 */
.nav-pl { display: none; flex-direction: column; gap: 1px; min-height: 0; overflow-y: auto; }
.nav-pl::-webkit-scrollbar { width: 6px; }
.nav-pl::-webkit-scrollbar-thumb { background: rgba(255,255,255,.10); border-radius: var(--r-full); }
.nav-pl { scrollbar-width: thin; scrollbar-color: rgba(255,255,255,.12) transparent; }

.nav-pl-btn {
  display: flex; align-items: center; gap: 8px; width: 100%;
  border: none; background: none; cursor: pointer; font: inherit;
  color: var(--faint); font-size: .78rem; font-weight: 500;
  padding: 7px 12px; border-radius: var(--r-sm); text-align: left;
  transition: color .16s var(--ease), background .16s var(--ease);
}
.nav-pl-btn:hover { color: var(--text); background: var(--hover); }
.nav-pl-btn:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.nav-pl-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nav-pl-count { flex: 0 0 auto; font-size: .7rem; font-variant-numeric: tabular-nums; opacity: .75; }

/* Le rail n'existe qu'en large : en bas d'un téléphone, la place appartient
   aux quatre destinations. */
@media (min-width: 880px) {
  .nav-pl { display: flex; }
  /* Le groupe des playlists prend la hauteur restante et défile seul : sans
     ça, une longue liste poussait les destinations hors du rail. */
  .nav { overflow: hidden; }
}

/* ── Équilibre vertical du lecteur ─────────────────────────────────────────
 *
 * MESURÉ : sur 1 440 × 900, la colonne de gauche du lecteur fait 856 px de
 * haut et son contenu s'arrête à ~630 px — 280 px de vide SOUS le volume,
 * alors que la pochette était collée en haut. Centrer la colonne répartit ce
 * vide de part et d'autre au lieu de le laisser pendre.
 *
 * Uniquement en large : en étroit la scène occupe une rangée de grille à
 * hauteur automatique, où centrer ne changerait rien.
 */
@media (min-width: 880px) {
  .stage { justify-content: center; }
}

/* Réglage système « moins d'animation » : les flèches restent visibles au
   survol, elles cessent simplement de fondre. */
@media (prefers-reduced-motion: reduce) {
  .row-nav { transition: none; }
  .hero-cta { transition: none; }
  .nav-pl-btn { transition: none; }
}
