:root {
  --bg: #0f1226;
  --panel: #171b33;
  --left: #f5b63d;   /* main gauche (piste 0) */
  --right: #3d9bf5;  /* main droite (piste 1) */
  --text: #eef1f8;
  --muted: #8890b5;
  --topbar-h: 56px;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--bg);
  color: var(--text);
  font: 15px/1.4 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  -webkit-user-select: none;
  user-select: none;
  overscroll-behavior: none;
  overflow-x: hidden;            /* jamais de scroll horizontal (header > 100% etc.) */
}

.view[hidden] { display: none !important; }
.empty, .offline-note { color: var(--muted); padding: 0 18px; }
.offline-note { color: var(--right); }

/* ===================== ACCUEIL ===================== */
#home { height: 100%; overflow-y: auto; padding-bottom: calc(40px + env(safe-area-inset-bottom)); }

/* index.html déclare viewport-fit=cover : le contenu s'étend DÉLIBÉRÉMENT sous les
   zones système (encoche, coins arrondis, barre home). Sans env(safe-area-inset-*),
   le header passe donc dessous et paraît « sortir du cadre » — surtout en PAYSAGE
   sur iPhone, où l'encoche mord sur un CÔTÉ. max() garde la marge normale sur les
   appareils sans encoche (l'inset y vaut 0). */
.home-head {
  position: sticky; top: 0; z-index: 5;
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 16px 18px;
  padding-top: max(16px, env(safe-area-inset-top));
  padding-left: max(18px, env(safe-area-inset-left));
  padding-right: max(18px, env(safe-area-inset-right));
  background: linear-gradient(#0f1226, #0f1226ee);
  backdrop-filter: blur(6px);
}
.brand { font-size: 22px; margin: 0; font-weight: 800; letter-spacing: .5px; }
#search {
  flex: 1 1 160px; min-width: 0;     /* peut rétrécir → pas de débordement du header */
  background: #0f1226; border: 1px solid #ffffff1f; color: var(--text);
  border-radius: 10px; padding: 10px 12px; font-size: 14px;
}
.genre-select {
  flex: 0 0 190px;
  max-width: 220px;
  background: #0f1226;
  border: 1px solid #ffffff1f;
  color: var(--text);
  border-radius: 10px;
  padding: 10px 12px;
  font-size: 14px;
}
.btn-primary {
  background: var(--left); color: #2a1c00; border: 0; border-radius: 10px;
  padding: 10px 16px; font-size: 14px; font-weight: 700; cursor: pointer; flex: 0 0 auto;
}
.btn-ghost {
  background: #ffffff14; color: var(--text); border: 0; border-radius: 10px;
  padding: 10px 16px; font-size: 14px; cursor: pointer;
}

/* Grille de cartes */
.track-grid {
  list-style: none; margin: 0 auto;
  padding: 8px 18px;
  padding-left: max(18px, env(safe-area-inset-left));
  padding-right: max(18px, env(safe-area-inset-right));
  display: grid; gap: 16px;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
}
/* Grands écrans / 4K : cartes plus grandes (sinon une nuée de vignettes minuscules),
   et grille centrée avec une largeur max pour ne pas s'étaler à l'infini. */
@media (min-width: 1600px) {
  .track-grid { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 20px; }
}
@media (min-width: 2200px) {
  .track-grid { max-width: 2100px; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 24px; }
}
/* Sentinelle du défilement infini : une ligne invisible en fin de grille dont
   l'entrée dans le viewport déclenche le chargement de la page suivante. Elle prend
   toute la largeur (grid-column: 1/-1) pour ne pas occuper une case de carte et
   décaler la grille d'un cran. Retirée du DOM dès qu'il n'y a plus de page. */
.grid-sentinel { grid-column: 1 / -1; height: 1px; list-style: none; pointer-events: none; }

/* --- Accueil en RANGÉES ---------------------------------------------------
   Une rangée = un titre cliquable + un ruban horizontal de 9 cartes. Le ruban
   défile en x (scroll-snap) au lieu de passer à la ligne : la hauteur de
   l'accueil reste bornée quel que soit le nombre de genres. */
.rows { padding: 8px 0 24px; }
.row-title {
  background: none; border: 0; color: var(--text); cursor: pointer;
  font-size: 18px; font-weight: 700; padding: 0; text-align: left;
}
.row-title:hover { text-decoration: underline; }
.row-all {
  margin-left: auto; background: none; border: 0; cursor: pointer;
  color: var(--muted); font-size: 13px; padding: 0; white-space: nowrap;
}
.row-all:hover { color: var(--text); }
/* La marge latérale vit sur la RANGÉE, pas sur le ruban : mise sur le ruban
   (conteneur de défilement) elle défile avec le contenu, et dès qu'on scrolle les
   cartes passaient sous le bord de l'écran — désalignées avec le titre. */
.home-row { margin-bottom: 26px; padding-left: max(18px, env(safe-area-inset-left)); padding-right: max(18px, env(safe-area-inset-right)); }
.home-row-head { display: flex; align-items: baseline; gap: 12px; padding-bottom: 8px; }
.row-strip {
  list-style: none; margin: 0;
  display: grid; grid-auto-flow: column; grid-auto-columns: 180px; gap: 16px;
  overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  padding: 4px 0 6px;
}
.row-strip > .card { scroll-snap-align: start; }
@media (min-width: 1600px) { .row-strip { grid-auto-columns: 220px; } }
@media (min-width: 2200px) { .row-strip { grid-auto-columns: 280px; gap: 24px; } }

/* --- Catégorie ouverte : en-tête + MODE LISTE ---------------------------- */
.cat-head {
  display: flex; align-items: center; gap: 12px;
  padding: 6px 18px 2px;
  padding-left: max(18px, env(safe-area-inset-left));
  padding-right: max(18px, env(safe-area-inset-right));
}
.cat-head h2 { margin: 0; font-size: 20px; }
/* Barre de tri de la liste. N'hérite PAS de .genre-select : sous 900 px celui-ci
   passe en pleine largeur (il vit dans le header), ce qui n'a pas de sens ici. */
.cat-sort {
  margin-left: auto; flex: 0 0 auto; max-width: 60vw;
  background: #0f1226; border: 1px solid #ffffff1f; color: var(--text);
  border-radius: 10px; padding: 8px 10px; font-size: 13px;
}
@media (pointer: coarse), (hover: none) { .cat-sort { min-height: 44px; font-size: 16px; } }
/* En liste, la carte devient une LIGNE : mêmes éléments, disposition en flex —
   pas de second gabarit de carte à maintenir. */
.track-grid.list { display: flex; flex-direction: column; gap: 8px; max-width: none; margin: 0; }
.track-grid.list .card { display: flex; align-items: center; gap: 12px; border-radius: 10px; }
.track-grid.list .card:hover { transform: none; }
.track-grid.list .card .cover { width: 56px; flex: 0 0 56px; height: 56px; }
.track-grid.list .card .cover .genre,
.track-grid.list .card .cover .levels { display: none; }
.track-grid.list .card .info { flex: 1; min-width: 0; padding: 8px 12px 8px 0; }
/* Boutons admin en liste : remis dans le FLUX, à droite de la ligne, et toujours
   visibles (pas de survol au doigt). `order` compense l'ordre du DOM, où ils sont
   écrits AVANT la pochette (nécessaire à leur superposition en mode grille). */
.track-grid.list .card .del,
.track-grid.list .card .addlvl,
.track-grid.list .card .edit-meta {
  position: static; opacity: 1; flex: 0 0 auto; margin-right: 8px;
}
.track-grid.list .card .edit-meta { order: 1; }
.track-grid.list .card .addlvl { order: 2; }
.track-grid.list .card .del { order: 3; }
@media (max-width: 700px) {
  .track-grid.list {
    padding-left: max(10px, env(safe-area-inset-left));
    padding-right: max(10px, env(safe-area-inset-right));
  }
}

.card { cursor: pointer; border-radius: 14px; overflow: hidden; background: var(--panel); transition: transform .12s, box-shadow .12s; }
.card:hover { transform: translateY(-3px); box-shadow: 0 10px 30px #00000066; }
/* CARRÉ (et non 16/9) : les pochettes générées sont au format album, avec l'artiste
   en haut et le titre en bas. En 16/9 avec background-size:cover, le crop mangeait
   les deux textes. Le dégradé reste le repli quand il n'y a pas de pochette. */
.card .cover {
  position: relative; aspect-ratio: 1/1; background: linear-gradient(135deg, #2a2f55, #14172e);
  background-size: cover; background-position: center;
}
/* La pochette est un <img> (et non plus une background-image) pour bénéficier de
   loading="lazy" : le CSS n'a pas d'équivalent, une background-image se télécharge
   même hors écran. object-fit:cover reproduit exactement le cadrage précédent.
   Le dégradé de .cover reste visible tant que l'image n'est pas chargée : il sert de
   placeholder, donc pas de carte blanche pendant le scroll. Les pastilles genre /
   niveaux / play sont en position:absolute, elles passent naturellement au-dessus. */
.card .cover img {
  display: block; width: 100%; height: 100%; object-fit: cover;
}
.card .cover .genre {
  position: absolute; top: 8px; left: 8px;
  background: #000000a8; color: #fff; font-size: 11px; padding: 3px 8px; border-radius: 999px;
}
.card .cover .levels {
  position: absolute; top: 8px; right: 8px;
  background: #4f7cffcc; color: #fff; font-size: 11px; padding: 3px 8px; border-radius: 999px;
}
.level-wrap { flex: 0 0 auto; }
.level-select {
  background: #ffffff14; color: var(--text, #fff); border: 1px solid #ffffff2e;
  border-radius: 8px; font-size: 12px; padding: 4px 8px; cursor: pointer;
}
.card .cover .play {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font-size: 34px; color: #ffffffe6; opacity: 0; transition: opacity .12s;
  text-shadow: 0 2px 12px #000;
}
.card:hover .cover .play { opacity: 1; }
.card .info { padding: 10px 12px; }
.card .info .t { font-weight: 600; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.card .info .a { color: var(--muted); font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.card .del {
  position: absolute; top: 8px; right: 8px; z-index: 2;
  background: #000000a8; border: 0; color: #fff; border-radius: 999px; width: 28px; height: 28px;
  cursor: pointer; opacity: 0; transition: opacity .12s; font-size: 14px;
}
.card:hover .del { opacity: 1; }
.card .del:hover { background: #c0344e; }

/* « Ajouter un niveau » : même pastille que la corbeille, placée juste à sa
   gauche (28 px + 6 px de gouttière). Vert = action constructive, pour ne pas
   la confondre avec la suppression au survol. */
.card .addlvl {
  position: absolute; top: 8px; right: 42px; z-index: 2;
  background: #000000a8; border: 0; color: #fff; border-radius: 999px; width: 28px; height: 28px;
  cursor: pointer; opacity: 0; transition: opacity .12s; font-size: 14px;
}
.card:hover .addlvl { opacity: 1; }
.card .addlvl:hover { background: #2f9e5e; }

/* Modifier les métadonnées : 3e pastille, à gauche de « ajouter un niveau »
   (28 px + 6 px de gouttière × 2 boutons = 76 px du bord droit). */
.card .edit-meta {
  position: absolute; top: 8px; right: 76px; z-index: 2;
  background: #000000a8; border: 0; color: #fff; border-radius: 999px; width: 28px; height: 28px;
  cursor: pointer; opacity: 0; transition: opacity .12s; font-size: 13px;
}
.card:hover .edit-meta { opacity: 1; }
.card .edit-meta:hover { background: #3d7ad6; }

/* Jobs en cours */
.jobs { padding: 0 18px; }
.jobs:empty { display: none; }
.job { background: var(--panel); border-radius: 10px; padding: 10px 12px; margin: 8px 0; font-size: 13px; color: var(--muted);
  display: grid; grid-template-columns: 1fr auto; column-gap: 10px; align-items: center; }
.job .label { grid-area: 1 / 1; }
.job .bar { grid-area: 2 / 1 / 2 / 3; height: 5px; background: #ffffff14; border-radius: 3px; margin-top: 7px; overflow: hidden; }
.job .bar > i { display: block; height: 100%; background: var(--left); width: 0; transition: width .3s; }
.job.failed { color: var(--right); }
.job-cancel { grid-area: 1 / 2; width: 26px; height: 26px; flex: 0 0 auto; border: none; border-radius: 7px;
  background: #ffffff12; color: var(--muted); font-size: 13px; line-height: 1; cursor: pointer; padding: 0; }
.job-cancel:hover { background: #b3453f; color: #fff; }
.job-cancel:focus-visible { outline: 2px solid var(--right); outline-offset: 2px; }

/* ===================== VOLET D'AJOUT ===================== */
.overlay {
  position: fixed; inset: 0; z-index: 40;
  background: #00000099; backdrop-filter: blur(4px);
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
.overlay[hidden] { display: none; }
/* Modale d'ajout : grandit avec l'écran (confortable sur 4K, plein sur mobile). */
.add-card {
  width: min(94vw, clamp(360px, 52vw, 760px));
  background: var(--panel); border-radius: 18px; padding: clamp(20px, 2.4vw, 34px);
  display: flex; flex-direction: column; gap: 14px;
}
.add-card h2 { margin: 0 0 4px; font-size: clamp(20px, 2vw, 26px); }
.add-card label { display: flex; flex-direction: column; gap: 6px; font-size: 13px; color: var(--muted); flex: 1; }
.add-card .row { display: flex; gap: 14px; }
/* Bascule source : Lien YouTube / Fichier (segmented control) */
.src-toggle { display: flex; gap: 8px; margin-bottom: 2px; }
.src-tab {
  flex: 1; padding: 12px 12px; border-radius: 10px; cursor: pointer;
  background: #ffffff14; color: var(--text); border: 1px solid #ffffff1f;
  font-size: clamp(14px, 1.1vw, 16px); font-weight: 600;
}
.src-tab.on { background: var(--left); color: #2a1c00; border-color: var(--left); }
.add-card label[hidden] { display: none; }
#add-file {
  padding: 12px; font-size: clamp(14px, 1.1vw, 16px); cursor: pointer;
}
#add-file::file-selector-button {
  margin-right: 12px; padding: 9px 14px; border: 0; border-radius: 9px;
  background: var(--left); color: #2a1c00; font-weight: 700; cursor: pointer; font-size: 14px;
}
.add-progress { height: 8px; border-radius: 999px; background: #ffffff1f; overflow: hidden; }
.add-progress[hidden] { display: none; }
.add-progress-bar {
  height: 100%; width: 0%; border-radius: 999px;
  background: linear-gradient(90deg, var(--left), var(--right, #3d9bf5));
  transition: width .18s ease;
}
.add-card input, .add-card select {
  background: #0f1226; border: 1px solid #ffffff1f; color: var(--text);
  border-radius: 10px; padding: 12px 12px; font-size: clamp(15px, 1.1vw, 17px);
}
.add-actions { display: flex; justify-content: flex-end; gap: 12px; margin-top: 8px; }
.add-actions .btn-primary, .add-actions .btn-ghost {
  padding: 12px 22px; font-size: clamp(15px, 1.1vw, 17px);
}
.login-card { max-width: 380px; }
.genres-card { max-width: 620px; }
.genres-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 8px;
}
.genres-row-rename {
  grid-template-columns: 1fr 1fr auto;
}
.genres-row button { white-space: nowrap; }

@media (max-width: 900px) {
  .genre-select {
    flex: 1 1 100%;
    max-width: none;
    order: 10;
  }
}

/* Au DOIGT : cibles d'au moins 44 px (recommandation Apple) et texte à 16 px —
   en dessous, iOS zoome automatiquement sur le champ au focus, ce qui décale
   toute la mise en page. Les contrôles du header étaient dimensionnés pour la
   souris (14 px / padding 10 px), donc trop petits sur mobile. */
/* `pointer: coarse` seul ne suffit pas : selon le contexte (iPadOS avec pointeur,
   navigateur qui se déclare « fine »), il peut ne pas matcher alors qu'on est bien
   au doigt. `hover: none` est un second signal, indépendant. */
@media (pointer: coarse), (hover: none) {
  #search, .genre-select, .btn-primary, .btn-ghost {
    min-height: 44px;
    font-size: 16px;
  }
  .home-head { gap: 10px; row-gap: 10px; }
  .brand { font-size: 20px; }
}

/* Résultat de partie + classement */
.board-card { max-width: 440px; }
.board-result { text-align: center; }
.big-score { font-size: 56px; font-weight: 800; line-height: 1; }
.big-score span { font-size: 18px; color: var(--muted); margin-left: 4px; }
.res-sub { color: var(--muted); margin: 6px 0 12px; }
.res-cancel { color: var(--right); margin: 6px 0 12px; font-size: 13px; }
#score-form { display: flex; gap: 8px; }
#score-form input { flex: 1; background: #0f1226; border: 1px solid #ffffff1f; color: var(--text); border-radius: 9px; padding: 10px; font-size: 14px; }
.board-list { list-style: none; counter-reset: rank; margin: 10px 0 0; padding: 0; max-height: 320px; overflow-y: auto; }
.board-list li { counter-increment: rank; display: flex; align-items: center; gap: 10px; padding: 8px 4px; border-bottom: 1px solid #ffffff10; }
.board-list li::before { content: counter(rank); width: 26px; text-align: center; color: var(--muted); font-variant-numeric: tabular-nums; }
.board-list li:nth-child(1)::before { content: "🥇"; }
.board-list li:nth-child(2)::before { content: "🥈"; }
.board-list li:nth-child(3)::before { content: "🥉"; }
.board-list .who { flex: 1; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.board-list .det { color: var(--muted); font-size: 12px; }

/* Suppression d'un genre depuis la modale d'administration. Désactivé (et non
   masqué) quand le genre est utilisé : l'utilisateur voit que l'action existe et
   pourquoi elle est bloquée, via le title. L'API refuse aussi (409). */
.genre-del {
  background: #ffffff14; border: 0; color: #fff; border-radius: 8px;
  width: 28px; height: 28px; cursor: pointer; font-size: 13px; flex: 0 0 auto;
}
.genre-del:hover:not(:disabled) { background: #c0344e; }
.genre-del:disabled { opacity: .3; cursor: not-allowed; }

/* ---- Style visuel d'un genre (pochettes générées) ----
   Ces trois axes sont FIGÉS par genre : c'est ce qui rend la grille lisible d'un coup
   d'œil. Ils sont inventés par un LLM côté Pi (la clé OpenAI n'est pas sur l'hébergement
   web), donc éditables ici : un style raté condamnerait toutes les pochettes du genre.
   Replié par défaut — la modale sert d'abord à ajouter/renommer un genre. */
/* .board-list plafonne à 320px (pensé pour un classement de scores). Avec un éditeur
   de style déplié — trois champs de texte — on n'y voyait plus qu'un axe à la fois.
   On desserre pour la modale des genres seulement, en restant borné par la hauteur de
   l'écran pour que la modale ne déborde jamais. */
.genres-card .board-list { max-height: min(60vh, 520px); }
.board-list li.genre-item { flex-wrap: wrap; align-items: flex-start; }
.genre-head { flex: 1; display: flex; align-items: center; gap: 10px; min-width: 0; }
.genre-style-toggle {
  background: #ffffff14; border: 0; color: #fff; border-radius: 8px;
  width: 28px; height: 28px; cursor: pointer; font-size: 13px; flex: 0 0 auto;
}
.genre-style-toggle:hover { background: #ffffff2a; }
/* Pastille d'état : dit SANS ouvrir l'éditeur si le genre a déjà sa signature.
   Sur une longue liste, c'est ce qui permet de repérer les genres en attente du Pi. */
.genre-style-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; }
.genre-style-dot.on { background: #3ddc97; }
.genre-style-dot.off { background: #ffffff33; }

.genre-style { flex: 0 0 100%; padding: 10px 0 6px 36px; }
.genre-style-field { display: block; margin-bottom: 8px; }
.genre-style-field > span { display: block; font-size: 12px; font-weight: 600; margin-bottom: 3px; }
.genre-style-field em { display: block; font-style: normal; font-weight: 400; color: var(--muted); font-size: 11px; }
.genre-style-field textarea {
  width: 100%; background: #0f1226; border: 1px solid #ffffff1f; color: var(--text);
  border-radius: 9px; padding: 8px; font: inherit; font-size: 13px; resize: vertical;
}
.genre-style-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.genre-style-actions button { font-size: 13px; padding: 7px 12px; }
.genre-style-hint, .genre-style-warn { color: var(--muted); font-size: 12px; margin: 0 0 8px; }
.genre-style-warn { color: #ffb347; }
.board-list .sc { font-weight: 800; min-width: 34px; text-align: right; }
.board-empty { color: var(--muted); padding: 12px 0; text-align: center; }
#btn-midi.on { background: var(--left); color: #2a1c00; }

/* ===================== JOUER ===================== */
/* Tout le lecteur vit DANS la zone sûre : le canvas (#stage, inset:0) se cale sur
   le padding-box, donc ni le clavier ni la barre du haut ne passent sous l'encoche
   ou la barre home. Le fond reste --bg, la bande ajoutée ne se voit pas. */
#player {
  position: fixed; inset: 0;
  background: var(--bg);
  padding-top: env(safe-area-inset-top);
  padding-bottom: env(safe-area-inset-bottom);
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
}
#stage { position: absolute; inset: 0; width: 100%; height: 100%; display: block; touch-action: none; }

#topbar {
  position: absolute; top: 0; left: 0; right: 0; height: var(--topbar-h);
  display: flex; align-items: center; gap: 12px; padding: 0 12px;
  background: linear-gradient(#0f1226ee, #0f122699); backdrop-filter: blur(6px); z-index: 10;
}
/* Au doigt, la barre de lecture doit rester atteignable sans viser : 64 px et des
   boutons de 44 px (les icônes étaient dimensionnées pour la souris). */
/* `pointer: coarse` seul ne suffit pas : selon le contexte (iPadOS avec pointeur,
   navigateur qui se déclare « fine »), il peut ne pas matcher alors qu'on est bien
   au doigt. `hover: none` est un second signal, indépendant. */
@media (pointer: coarse), (hover: none) {
  /* Barre DOUBLÉE en hauteur et CENTRÉE : plafonner la timeline (voir plus bas) a
     laissé le contenu tassé à gauche avec un grand vide à droite. justify-content
     center rééquilibre, et 128 px rendent les commandes confortables au doigt —
     64 px restaient petits sur un grand écran tactile. */
  #topbar { height: 128px; gap: 12px; padding: 0 12px; justify-content: center; }
  #topbar button { min-width: 64px; min-height: 64px; font-size: 26px; border-radius: 14px; }
  #topbar .timeline { height: 34px; }
  #topbar .clock { font-size: 19px; }
  #topbar .now-playing { font-size: 19px; }
  #topbar .level-select { font-size: 17px; padding: 10px 14px; }
  /* min-width:auto (défaut flex) empêche ces deux-là de descendre sous leur
     contenu minimal : la barre débordait alors que 7 boutons à 44 px, l'horloge
     et les gaps saturent déjà la largeur d'un iPhone en paysage. min-width:0 les
     rend réellement compressibles, ce sont eux qui doivent absorber. */
  /* La barre de progression prenait tout l'espace restant (~70 % de la largeur),
     ce qui étirait la barre pour rien. La plafonner libère la place des boutons
     ET permet de garder le titre affiché. Elle reste assez large pour viser une
     position à la volée. */
  #topbar .timeline { min-width: 0; flex: 1 1 auto; max-width: 42%; }
  #topbar .now-playing { min-width: 0; max-width: 14ch; }
}
/* Écrans très étroits (iPhone SE en paysage) avec la barre d'admin : même
   compressés, les boutons ne tiennent plus. Le titre saute en dernier recours —
   on vient de cliquer la pochette, on sait ce qu'on écoute ; un bouton coupé,
   lui, devient inatteignable. */
/* Écrans étroits : la barre GARDE ses 128 px de haut (c'est le confort demandé),
   mais des boutons à 64 px ne rentrent plus — mesuré, ça débordait de 1 px à
   720 et 21 px à 667. On resserre donc les boutons, pas la barre. */
@media (pointer: coarse) and (max-width: 860px), (hover: none) and (max-width: 860px) {
  #topbar button { min-width: 54px; min-height: 54px; font-size: 22px; }
  #topbar { gap: 8px; padding: 0 8px; }
}
@media (pointer: coarse) and (max-width: 720px), (hover: none) and (max-width: 720px) {
  #topbar .now-playing { display: none; }
  #topbar button { min-width: 50px; min-height: 50px; font-size: 21px; }
  #topbar { gap: 6px; }
}
#topbar button {
  background: #ffffff14; color: var(--text); border: 0; border-radius: 10px;
  width: 40px; height: 40px; font-size: 18px; cursor: pointer; flex: 0 0 auto;
}
#topbar button:active { background: #ffffff2a; }
.now-playing { flex: 0 1 auto; max-width: 22ch; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--muted); }
.timeline { position: relative; flex: 1 1 auto; height: 22px; background: #ffffff10; border-radius: 6px; overflow: hidden; cursor: pointer; }
.timeline-parts { position: absolute; inset: 0; }
.timeline-parts .part { position: absolute; top: 0; bottom: 0; border-right: 1px solid #ffffff33; font-size: 11px; color: #ffffffcc; padding-left: 4px; display: flex; align-items: center; }
.timeline-playhead { position: absolute; top: 0; bottom: 0; width: 4px; margin-left: -2px; border-radius: 2px; background: var(--text); left: 0; pointer-events: none; box-shadow: 0 0 6px #000a; }
.clock { font-variant-numeric: tabular-nums; color: var(--muted); flex: 0 0 auto; }

.edit-tools {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px;
  border-radius: 10px;
  background: #ffffff10;
}
.edit-tools[hidden] { display: none; }
.edit-tools button {
  width: 34px !important;
  height: 34px !important;
  font-size: 13px !important;
}
.edit-tools button.on,
#btn-edit.on {
  background: #7ef1a7 !important;
  color: #102216 !important;
}
.edit-tools select {
  height: 34px;
  border-radius: 8px;
  border: 1px solid #ffffff2e;
  background: #0f1226;
  color: var(--text);
  font-size: 12px;
  padding: 0 6px;
}
/* Groupe transpose : 4 boutons collés en un « stepper » (décale le C4). */
.edit-transpose {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 2px;
  border-radius: 8px;
  background: #ffffff14;
}
.edit-tools .edit-transpose button {
  width: auto !important;
  min-width: 34px;
  padding: 0 8px;
  font-variant-numeric: tabular-nums;
}

/* Vitesse (slider vertical en popover) */
.speed { position: relative; flex: 0 0 auto; }
.speed > button { min-width: 44px; font-size: 13px !important; font-variant-numeric: tabular-nums; }
.speed-pop {
  position: absolute; top: calc(100% + 8px); right: 0; background: var(--panel);
  border: 1px solid #ffffff1f; border-radius: 12px; padding: 12px 10px 8px;
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  box-shadow: 0 8px 24px #00000066; z-index: 25;
}
.speed-pop[hidden] { display: none; }
.speed-pop input[type="range"] {
  writing-mode: vertical-lr; direction: rtl;
  width: 24px; height: 140px; accent-color: var(--left);
}
.speed-pop #speed-val { color: var(--text); font-size: 12px; font-variant-numeric: tabular-nums; }

/* Au DOIGT : le slider vertical de 24 px de large est trop fin à saisir. On le passe
   HORIZONTAL et large, avec une piste de 44 px de haut (cible tactile Apple).
   ATTENTION : surtout PAS de position:fixed ici. Le #topbar porte un backdrop-filter,
   qui fait de lui le BLOC CONTENEUR des descendants position:fixed (comme transform).
   Un popover fixed se calerait donc sur la barre (tout en haut), pas sur le viewport →
   il partait hors zone et le slider devenait inatteignable (le bug tactile). On reste
   en position:absolute (héritée, comme le popover d'options qui, lui, marche) et on
   l'élargit simplement, ancré à droite sous le bouton. */
@media (pointer: coarse), (hover: none) {
  .speed-pop {
    left: auto; right: 0;
    width: min(82vw, 340px);
    padding: 16px 16px 12px;
  }
  .speed-pop input[type="range"] {
    writing-mode: horizontal-tb; direction: ltr;
    width: 100%; height: 44px;
    touch-action: none;   /* le drag horizontal reste au slider, pas au scroll */
  }
  .speed-pop #speed-val { font-size: 18px; }
}
/* Réglage admin : vitesse « normale » du morceau (persistée en DB) */
.track-speed-admin {
  margin-top: 8px; padding-top: 8px; border-top: 1px solid #ffffff1f;
  display: flex; flex-direction: column; align-items: center; gap: 5px; width: 100%;
}
.track-speed-admin[hidden] { display: none; }
.track-speed-admin label { color: var(--text); font-size: 11px; opacity: .8; text-align: center; }
.track-speed-row { display: flex; align-items: center; gap: 4px; }
.track-speed-admin input[type="number"] {
  width: 62px; padding: 4px 6px; font-size: 13px; text-align: center;
  background: #00000033; color: var(--text); border: 1px solid #ffffff2b; border-radius: 8px;
  font-variant-numeric: tabular-nums;
}
.track-speed-admin span { color: var(--text); font-size: 12px; }

/* Options (⚙) en popover */
.opts { position: relative; flex: 0 0 auto; }
.opts-pop {
  position: absolute; top: calc(100% + 8px); right: 0; min-width: 200px;
  background: var(--panel); border: 1px solid #ffffff1f; border-radius: 12px; padding: 10px 12px;
  display: flex; flex-direction: column; gap: 10px; box-shadow: 0 8px 24px #00000066; z-index: 25;
}
.opts-pop[hidden] { display: none; }
.opts-pop label { display: flex; align-items: center; gap: 9px; font-size: 14px; cursor: pointer; }
.opts-action {
  background: #ffffff14; color: var(--text); border: 0; border-radius: 9px;
  padding: 9px 10px; font-size: 14px; cursor: pointer; text-align: left;
  border-top: 1px solid #ffffff1f;
}
.opts-action:hover { background: #ffffff24; }
/* Le menu ⚙ vit dans #topbar, dont TOUS les boutons sont carrés (44 → 64 px au
   doigt) : sans cet override, « Télécharger le MIDI » était comprimé à 64×64 et
   son libellé débordait de la pastille. */
#topbar .opts-action { min-width: 0; min-height: 0; width: 100%; font-size: 14px; border-radius: 9px; }
.opts-pop { max-height: 70vh; overflow-y: auto; }
/* Au DOIGT : lignes et cases à cocher à taille de cible réelle (44 px mini). */
@media (pointer: coarse), (hover: none) {
  .opts-pop { min-width: 270px; gap: 2px; padding: 8px; }
  .opts-pop label { min-height: 48px; font-size: 17px; padding: 0 8px; gap: 14px; }
  .opts-pop input[type="checkbox"] { width: 24px; height: 24px; flex: 0 0 auto; }
  #topbar .opts-action { min-height: 48px; font-size: 17px; margin-top: 4px; }
}
.opts-pop input { width: 16px; height: 16px; accent-color: var(--left); }

.edit-context-menu {
  position: fixed;
  min-width: 170px;
  background: #171b33f0;
  border: 1px solid #ffffff1f;
  border-radius: 10px;
  padding: 6px;
  z-index: 45;
  box-shadow: 0 12px 30px #00000088;
  backdrop-filter: blur(6px);
}
.edit-context-menu[hidden] { display: none; }
.edit-context-menu button {
  width: 100%;
  border: 0;
  border-radius: 8px;
  padding: 8px 10px;
  background: transparent;
  color: var(--text);
  text-align: left;
  cursor: pointer;
  font-size: 13px;
}
.edit-context-menu button:hover {
  background: #ffffff14;
}
.edit-context-menu hr {
  margin: 6px 2px;
  border: 0;
  border-top: 1px solid #ffffff1f;
}

.edit-note-info {
  position: fixed;
  right: 14px;
  bottom: 14px;
  z-index: 44;
  padding: 8px 12px;
  border-radius: 10px;
  background: #000000cc;
  color: var(--text);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  border: 1px solid #ffffff22;
  pointer-events: none;
}
.edit-note-info[hidden] { display: none; }

.toast {
  position: fixed; bottom: 16px; left: 50%; transform: translateX(-50%);
  background: #000000cc; color: var(--text); padding: 10px 16px; border-radius: 10px;
  z-index: 50; font-size: 14px;
}
.toast[hidden] { display: none; }

/* --- Calibrage vidéo --- */
/* Calibrage : modale PLEIN ÉCRAN — image (canvas) à gauche qui prend tout l'espace,
   contrôles à droite. Grand écran/4K comme mobile paysage. */
.calib-card {
  width: 96vw; height: 94vh; max-width: none;
  background: var(--panel); border-radius: 16px; padding: 14px;
  box-sizing: border-box;
  display: flex; gap: 14px;
}
.calib-stage {
  flex: 1 1 auto; min-width: 0;
  display: flex; align-items: center; justify-content: center;
  background: #000; border-radius: 10px; overflow: hidden;
}
/* max-width/height 100% : le canvas se met à l'échelle du stage EN GARDANT son ratio,
   et sa boîte reste égale à l'image rendue → le mapping du clic (getBoundingClientRect)
   n'est pas faussé (pas de letterbox comme object-fit l'introduirait). */
.calib-canvas {
  max-width: 100%; max-height: 100%;
  display: block; border-radius: 6px; cursor: crosshair; background: #000;
}
.calib-side {
  flex: 0 0 300px; display: flex; flex-direction: column; gap: 12px;
  overflow-y: auto;
}
.calib-side h2 { margin: 0; font-size: 20px; }
.calib-hint { color: #aeb4d6; font-size: 0.9rem; margin: 0; }
.calib-res {
  font-size: 0.82rem; font-weight: 600; padding: 6px 10px; border-radius: 8px;
  font-variant-numeric: tabular-nums; border: 1px solid #ffffff1f;
}
.calib-res[hidden] { display: none; }
.calib-res.good { background: #12351f; color: #7ef1a7; border-color: #2e6a45; }
.calib-res.ok   { background: #2e2a12; color: #f5d97e; border-color: #6a5f2e; }
.calib-res.warn { background: #33260f; color: #f5b63d; border-color: #6a4f1a; }
.calib-res.bad  { background: #3a1717; color: #ff9c9c; border-color: #7a3535; }
.calib-modes { display: flex; flex-direction: column; gap: 8px; margin: 0; }
.calib-modes .btn-ghost, .calib-actions .btn-ghost, .calib-actions .btn-primary { width: 100%; }
.calib-points { display: flex; flex-wrap: wrap; gap: 12px; margin: 0; color: #e7eaf6; font-size: 0.9rem; }
.calib-points select { margin-left: 4px; }
/* Boutons collés en bas de la colonne ; Valider en tête (action principale). */
.calib-actions { margin-top: auto; display: flex; flex-direction: column; gap: 8px; }
/* Écran étroit / portrait : on empile (image en haut, contrôles en bas). */
@media (max-width: 720px), (max-aspect-ratio: 1/1) {
  .calib-card { flex-direction: column; width: 98vw; height: 96vh; }
  .calib-side { flex: 0 0 auto; }
  .calib-actions { margin-top: 8px; }
}

/* ===================== PAYSAGE UNIQUEMENT ===================== */
/* Le manifeste verrouille déjà l'orientation d'une PWA installée (Android). Ce garde-fou
   couvre les cas non verrouillables (onglet navigateur, iOS) : en portrait sur un appareil
   TACTILE, on masque l'app et on invite à pivoter. Desktop (pointer fin) jamais bloqué. */
.rotate-gate { display: none; }
@media (orientation: portrait) and (pointer: coarse) {
  .rotate-gate {
    display: flex; position: fixed; inset: 0; z-index: 10000;
    align-items: center; justify-content: center; text-align: center;
    padding: 24px; background: var(--bg); color: var(--text);
  }
  .rotate-inner { max-width: 320px; }
  .rotate-icon {
    width: 88px; height: 56px; margin: 0 auto 22px; border: 3px solid var(--right);
    border-radius: 10px; color: transparent; overflow: hidden;
    animation: rotate-hint 2.4s ease-in-out infinite;
  }
  .rotate-title { font-size: 1.25rem; font-weight: 700; margin: 0 0 6px; }
  .rotate-sub { color: var(--muted); font-size: 0.95rem; margin: 0; }
}
@keyframes rotate-hint {
  0%, 40% { transform: rotate(0deg); }
  60%, 100% { transform: rotate(-90deg); }
}
