/* Interface inspirée de Pocket Casts : barre latérale fixe, grille de pochettes,
   lecteur permanent en bas d'écran. La couleur d'emphase est le mauve de
   jv.montreuillois.com, pour que les deux sites se répondent.

   Pas de règle `style` en ligne dans le HTML : la politique de sécurité
   (style-src 'self') les interdit. Les valeurs variables passent donc par des
   éléments qui les portent nativement — <progress value max>, <input range>. */

/* Deux thèmes, une seule liste de couleurs : light-dark() donne à chaque teinte
   sa valeur claire puis sa valeur sombre, et c'est `color-scheme` qui tranche.
   Sans réglage, il vaut `light dark` : le système décide. Le bouton de la barre
   latérale pose data-theme sur <html> et fige alors le choix — le serveur le
   relit dans un témoin pour le rendre dès la première image de la page, sans
   éclair de thème. Voir bascule-theme dans app.js et header.php.

   `color-scheme` sert aussi aux éléments natifs — barres de défilement, réglettes,
   champ de recherche — qui suivent le thème sans une ligne de plus. */
:root {
	color-scheme: light dark;

	--fond: light-dark(#fff, #0b0b0d);
	--laterale: light-dark(#f4f4f7, #000);
	--surface: light-dark(#f1f1f5, #17181c);
	--surface-haute: light-dark(#e6e6ee, #212328);
	--bordure: light-dark(#e1e1e9, #2a2c33);
	--separateur: light-dark(#e4e4ec, #1d1f25);
	--survol: light-dark(#eaeaf1, #131418);
	--texte: light-dark(#16171a, #f2f3f5);
	/* Le corps des descriptions : un ton en retrait du texte courant, sans tomber
	   dans le gris des mentions secondaires. */
	--texte-doux: light-dark(#3b3f47, #c8ccd3);
	--discret: light-dark(#62687a, #9096a0);
	--efface: light-dark(#6b7180, #5f6672);
	/* Mauve repris de jv.montreuillois.com. Le thème sombre en prend la déclinaison
	   claire, qui réclame un texte sombre quand elle sert de fond — d'où
	   --accent-texte, et l'inverse en thème clair. */
	--accent: light-dark(#7b2cbf, #c589f2);
	--accent-texte: light-dark(#fff, #17171b);
	--accent-voile: light-dark(rgba(123, 44, 191, .12), rgba(197, 137, 242, .16));
	--accent-halo: light-dark(rgba(123, 44, 191, .35), rgba(197, 137, 242, .45));
	--erreur: light-dark(#c0272d, #ff6b6b);
	--succes: light-dark(#0f7a52, #5ddba0);
	/* Aplat le plus contrasté du thème : le bouton de lecture principal, la poignée
	   des réglettes. Noir sur fond clair, blanc sur fond sombre. */
	--plein: light-dark(#16171a, #fff);
	--plein-texte: light-dark(#fff, #000);
	--plein-survol: light-dark(#33353c, #e6e6e6);
	/* Une ombre portée pèse plus lourd sur du blanc que sur du noir : elle s'allège
	   d'autant en thème clair. */
	--ombre: light-dark(rgba(0, 0, 0, .12), rgba(0, 0, 0, .35));
	--ombre-forte: light-dark(rgba(0, 0, 0, .22), rgba(0, 0, 0, .55));
	--voile-fond: light-dark(rgba(0, 0, 0, .35), rgba(0, 0, 0, .6));
	--rayon: 6px;
	--hauteur-lecteur: 92px;
	--largeur-laterale: 232px;

	/* Antenne du menu « Radios », en masque : le tracé est en noir opaque, seule sa
	   silhouette compte, la couleur vient du fond qu'il découpe. Même dessin que
	   icone('radio') dans src/partials/header.php. */
	--antenne: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round'><circle cx='12' cy='12' r='2.4'/><path d='M8.1 15.9a5.5 5.5 0 010-7.8'/><path d='M15.9 8.1a5.5 5.5 0 010 7.8'/><path d='M5.2 18.8a9.6 9.6 0 010-13.6'/><path d='M18.8 5.2a9.6 9.6 0 010 13.6'/></svg>");
}

/* Le choix explicite, posé sur <html> par le serveur puis par le bouton. */
:root[data-theme="clair"] { color-scheme: light; }
:root[data-theme="sombre"] { color-scheme: dark; }

* { box-sizing: border-box; }

html, body { height: 100%; }

body {
	margin: 0;
	background: var(--fond);
	color: var(--texte);
	font: 15px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
	-webkit-font-smoothing: antialiased;
}

a { color: inherit; }
h1, h2 { font-weight: 700; }

.discret { color: var(--discret); font-size: .8125rem; }
.alerte { color: var(--erreur); }
.succes { color: var(--succes); }
.vide { color: var(--discret); padding: 3rem 0; text-align: center; }
.vide a { color: var(--accent); }

svg { fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }

/* ------------------------------------------------------------------ Structure */

.application {
	display: grid;
	grid-template-columns: var(--largeur-laterale) 1fr;
	min-height: 100vh;
	padding-bottom: var(--hauteur-lecteur);
}

.laterale {
	position: fixed;
	/* S'arrête au-dessus du lecteur, qui occupe toute la largeur en bas d'écran.
	   Sans ça la barre passait dessous, et le lien vers le profil — ancré tout en
	   bas — devenait invisible. */
	inset: 0 auto var(--hauteur-lecteur) 0;
	width: var(--largeur-laterale);
	display: flex;
	flex-direction: column;
	gap: 1.5rem;
	padding: 1.25rem 1rem;
	background: var(--laterale);
	z-index: 30;
}

/* La partie qui défile quand les entrées ne tiennent pas dans la hauteur. Pas
   d'ascenseur : une barre grise en permanence le long du menu se voyait plus que
   le menu lui-même. À la place, le bord où il reste du contenu reçoit un chevron
   et s'estompe. Le fondu seul ne suffisait pas : quand la coupe tombe entre deux
   entrées, il n'a rien à estomper et le menu paraît complet.

   Le cadre porte les chevrons, en dehors de ce qui défile : ils restent en place
   et le fondu ne les efface pas au passage. */
.laterale-corps {
	position: relative;
	flex: 1;
	min-height: 0;
	display: flex;
	flex-direction: column;
}

/* Le fondu passe par un masque et non par un dégradé posé par-dessus : un masque
   s'applique à la boîte visible et ne défile pas avec le contenu, là où un enfant
   en `position: absolute` d'un conteneur qui défile suivrait le contenu. Il ne
   voile que ce qui est dedans, le fond de la barre restant sur `.laterale`.

   Les deux longueurs sont mises à jour par le script au fil du défilement ; à
   0 px, les arrêts se confondent et le masque est entièrement opaque. */
.laterale-defilement {
	flex: 1;
	min-height: 0;
	display: flex;
	flex-direction: column;
	gap: 1.5rem;
	overflow-y: auto;
	scrollbar-width: none;
	--fondu: linear-gradient(
		to bottom,
		transparent 0,
		#000 var(--fondu-haut),
		#000 calc(100% - var(--fondu-bas)),
		transparent 100%
	);
	-webkit-mask-image: var(--fondu);
	mask-image: var(--fondu);
	transition: --fondu-haut .15s linear, --fondu-bas .15s linear;
}

.laterale-defilement::-webkit-scrollbar { width: 0; height: 0; }

/* Purement indicatifs : `pointer-events: none` laisse le clic à l'entrée qui se
   trouve dessous, dont ils ne recouvrent que le bord déjà estompé. */
.fleche-defilement {
	position: absolute;
	left: 0;
	right: 0;
	height: 1.25rem;
	display: grid;
	place-items: center;
	color: var(--discret);
	opacity: 0;
	pointer-events: none;
	transition: opacity .15s ease;
}

.fleche-defilement .icone { width: 18px; height: 18px; flex: none; }
.fleche-defilement--haut { top: 0; transform: rotate(180deg); }
.fleche-defilement--bas { bottom: 0; }

.laterale-corps.deborde-haut .fleche-defilement--haut,
.laterale-corps.deborde-bas .fleche-defilement--bas { opacity: 1; }

/* Déclarées pour être animables : une variable non enregistrée est du texte pour
   le moteur de style, et sauterait d'une valeur à l'autre. */
@property --fondu-haut {
	syntax: '<length>';
	inherits: false;
	initial-value: 0px;
}

@property --fondu-bas {
	syntax: '<length>';
	inherits: false;
	initial-value: 0px;
}

.logo { width: 44px; height: 44px; display: block; }
/* Le tracé « S93 » du logo. Le même dessin existe en autonome dans assets/img/logo.svg,
   avec ses couleurs en dur : un favicon ne peut pas lire les variables du site.
   Changer la teinte d'accent demande donc de toucher aux deux fichiers. */
.logo svg { stroke: none; }
.logo-fond { fill: var(--accent); }
.logo-trace {
	fill: none;
	stroke: var(--accent-texte);
	stroke-width: 1.42;
	stroke-linecap: round;
	stroke-linejoin: round;
}

.menu { display: flex; flex-direction: column; gap: .125rem; }

.menu hr { border: 0; border-top: 1px solid var(--separateur); margin: 1rem .25rem; }

.menu a {
	display: flex;
	align-items: center;
	gap: .75rem;
	padding: .55rem .75rem;
	border-radius: var(--rayon);
	text-decoration: none;
	color: var(--discret);
	font-size: .9375rem;
}

.menu a:hover { color: var(--texte); background: var(--survol); }
.menu a.actif { color: var(--texte); font-weight: 600; }
.menu a.actif .icone { color: var(--accent); }

.icone { width: 20px; height: 20px; flex: 0 0 20px; }

/* ------------------------------------------------------------ Bascule de thème */

/* Un picto seul, sur le gabarit rond des .outil déjà présents partout ailleurs :
   il se glisse ainsi dans la ligne du compte sans la faire grandir de plus de
   quelques pixels — le retrait vertical de la ligne est réduit d'autant. */
.bascule-theme { flex: 0 0 auto; }

/* Un seul des deux états est rendu : `display: none` le retire aussi de ce que lit
   une synthèse vocale, si bien que le nom du bouton dit toujours ce qu'il fera.
   Deux points d'entrée pour le thème clair — le choix explicite, et à défaut la
   préférence du système. Attention à la spécificité : une règle sur `.bascule-theme
   > span` pèserait plus lourd que `.theme-sombre` et rendrait les deux à la fois. */
.theme-clair, .theme-sombre { display: grid; place-items: center; }
.theme-sombre { display: none; }

:root[data-theme="clair"] .theme-clair { display: none; }
:root[data-theme="clair"] .theme-sombre { display: grid; }

@media (prefers-color-scheme: light) {
	:root:not([data-theme="sombre"]) .theme-clair { display: none; }
	:root:not([data-theme="sombre"]) .theme-sombre { display: grid; }
}

/* Texte réservé aux synthèses vocales : présent pour elles, absent de l'écran.
   `display: none` ou `visibility: hidden` le retireraient des deux. */
.hors-ecran {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

.compte {
	margin-top: auto;
	display: flex;
	align-items: center;
	gap: .5rem;
	padding: .25rem .75rem;
	border-top: 1px solid var(--separateur);
}

.compte-nom {
	flex: 1;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	color: var(--discret);
	font-size: .8125rem;
	text-decoration: none;
}
.compte-nom:hover, .compte-nom.actif { color: var(--texte); }

/* Ciblé sur le formulaire de déconnexion, et non sur `.compte button` : la bascule
   de thème partage désormais cette ligne, et y aurait pris ce rouge au survol. */
.compte form button {
	background: none;
	border: none;
	color: var(--efface);
	font: inherit;
	font-size: .8125rem;
	padding: 0;
	cursor: pointer;
}
.compte form button:hover { color: var(--erreur); }

/* ---------------------------------------------------------------------- Profil */

/* ------------------------------------------------------- Profil et réglages */

/* La page est en deux temps : qui je suis et où j'en suis d'abord, en un bandeau
   qui se lit sans rien déplier ; les branchements techniques ensuite, en cartes.

   Le bandeau se réorganise à la place disponible, sans point de rupture : les
   deux blocs tiennent côte à côte tant qu'ils ont leur base — 20 rem pour
   l'identité, 18 rem pour les chiffres — et passent l'un sous l'autre en dessous.
   Le formulaire, lui, garde toujours sa ligne à lui. */
.profil-entete {
	display: flex;
	flex-wrap: wrap;
	gap: 1.5rem 3rem;
	align-items: center;
	max-width: 1500px;
	padding: 1.5rem;
	background: var(--surface);
	border: 1px solid var(--bordure);
	border-radius: 10px;
}

.profil-identite { flex: 0 1 20rem; display: flex; align-items: center; gap: 1rem; min-width: 0; }
.profil-identite p { margin: .15rem 0 0; }

/* L'initiale du nom, à défaut d'avoir jamais demandé une photo. */
.profil-pastille {
	flex: 0 0 56px;
	width: 56px;
	height: 56px;
	display: grid;
	place-items: center;
	border-radius: 50%;
	background: var(--accent-voile);
	color: var(--accent);
	font-size: 1.5rem;
	font-weight: 700;
}

.profil-nom { font-size: 1.25rem; margin: 0; }
.profil-role { color: var(--accent); }

/* L'identité ne grandit pas — une pastille et trois lignes courtes n'ont que faire
   d'une demi-page. Toute la place en trop va au bilan, où les chiffres se rangent
   d'autant mieux qu'ils ont de la largeur. */
.profil-bilan { flex: 1 1 26rem; min-width: 0; }

/* Les chiffres se lisent en diagonale : l'étiquette en petit au-dessus, la valeur
   en gros. La grille les aligne en colonnes régulières plutôt que de les laisser
   se tasser au gré de la longueur des étiquettes ; les chiffres tabulaires font le
   reste. */
.profil-chiffres {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
	gap: 1rem;
	margin: 0;
}

.profil-chiffres dt { color: var(--discret); font-size: .75rem; }
.profil-chiffres dd {
	margin: .1rem 0 0;
	font-size: 1.5rem;
	font-weight: 700;
	font-variant-numeric: tabular-nums;
}

/* Le second rang : le même dessin, à voix plus basse. Il détaille ce que le
   premier résume, et ne doit pas lui disputer le regard. */
.profil-chiffres--menus {
	grid-template-columns: repeat(auto-fit, minmax(6rem, 1fr));
	gap: .75rem 1rem;
	margin-top: 1rem;
	padding-top: 1rem;
	border-top: 1px solid var(--separateur);
}

.profil-chiffres--menus dd { font-size: 1.0625rem; color: var(--texte-doux); }

.profil-modifier { flex: 1 1 100%; border-top: 1px solid var(--separateur); padding-top: 1rem; }

.reglages {
	display: grid;
	/* `min()` plutôt que la seule largeur de base : sur un écran plus étroit que
	   22 rem, une piste de 22 rem déborderait au lieu de se réduire. */
	grid-template-columns: repeat(auto-fill, minmax(min(100%, 22rem), 1fr));
	gap: 1.25rem;
	align-items: start;
	max-width: 1500px;
}

.carte-reglage {
	background: var(--surface);
	border: 1px solid var(--bordure);
	border-radius: 10px;
	padding: 1.25rem 1.5rem 1.5rem;
}

.carte-reglage h2 { font-size: 1rem; margin: 0 0 1rem; }

/* Le respire des formulaires vaut aussi hors des cartes : celui de l'identité vit
   dans le bandeau du haut, replié. */
.carte-reglage .bouton, .repli-corps .bouton { margin-top: 1.25rem; }
.carte-reglage label, .repli-corps label { margin-top: 1rem; }
.carte-reglage .aide, .repli-corps .aide { margin: .5rem 0 0; }

/* Un formulaire qu'on ne remplit qu'une fois se replie : la carte montre alors ce
   qui est branché, et pas la manière de le brancher. Le triangle natif est
   remplacé par un chevron maison, pour tenir la même ligne que le reste. */
.repli > summary {
	display: flex;
	align-items: center;
	gap: .4rem;
	margin-top: 1rem;
	color: var(--discret);
	font-size: .875rem;
	cursor: pointer;
	list-style: none;
}

.repli > summary::-webkit-details-marker { display: none; }
.repli > summary:hover { color: var(--texte); }

.repli > summary::before {
	content: '';
	width: 7px;
	height: 7px;
	border-right: 1.5px solid currentColor;
	border-bottom: 1.5px solid currentColor;
	transform: rotate(-45deg);
	transition: transform .15s ease;
}

.repli[open] > summary::before { transform: rotate(45deg); }

.repli-corps { margin-top: .25rem; }
.repli-corps > :first-child { margin-top: 0; }
.repli-corps > .jetons { margin-top: .75rem; }

/* Le bandeau lui donne déjà son filet et son retrait : le sien ferait double. Et
   deux champs courts n'ont aucune raison de s'étirer sur toute sa largeur. */
.profil-modifier > summary { margin-top: 0; }
.profil-modifier .repli-corps { max-width: 26rem; }

.fiche-champs { margin: 0; display: grid; grid-template-columns: auto 1fr; gap: .4rem 1rem; }
.fiche-champs dt { color: var(--discret); font-size: .8125rem; }
.fiche-champs dd { margin: 0; font-size: .875rem; }

.jetons { list-style: none; margin: 0; padding: 0; display: grid; gap: .75rem; }
.jetons strong { display: block; font-size: .875rem; }

.colonne { grid-column: 2; display: flex; flex-direction: column; min-width: 0; }

.haut {
	position: sticky;
	top: 0;
	z-index: 20;
	display: flex;
	align-items: center;
	gap: 1.5rem;
	padding: 1rem 2rem;
	background: var(--fond);
}

.fleches { display: flex; gap: .25rem; }

.fleches button {
	width: 34px;
	height: 34px;
	display: grid;
	place-items: center;
	background: none;
	border: none;
	border-radius: 50%;
	color: var(--discret);
	cursor: pointer;
}
.fleches button:hover { color: var(--texte); background: var(--surface); }
.fleches svg { width: 20px; height: 20px; }

.recherche {
	margin-left: auto;
	display: flex;
	align-items: center;
	gap: .5rem;
	width: min(340px, 40vw);
	padding: .5rem .9rem;
	background: var(--surface);
	border-radius: 999px;
	color: var(--discret);
}

.recherche input {
	flex: 1;
	min-width: 0;
	background: none;
	border: none;
	color: var(--texte);
	font: inherit;
	font-size: .875rem;
}
.recherche input:focus { outline: none; }

/* ------------------------------------------------ Suggestions de la recherche */

/* La liste se pose sous le champ, qui lui sert d'ancre. */
.recherche--suggestions { position: relative; }

.suggestions {
	position: absolute;
	top: calc(100% + .5rem);
	left: 0;
	right: 0;
	z-index: 30;
	max-height: min(60vh, 26rem);
	overflow-y: auto;
	overscroll-behavior: contain;
	margin: 0;
	padding: .25rem;
	list-style: none;
	background: var(--surface-haute);
	border: 1px solid var(--bordure);
	border-radius: 12px;
	box-shadow: 0 14px 32px var(--ombre-forte);
}
.suggestions[hidden] { display: none; }

.suggestion {
	display: flex;
	align-items: center;
	gap: .625rem;
	padding: .375rem .5rem;
	border-radius: var(--rayon);
	cursor: pointer;
}

/* Le survol et le parcours au clavier mènent au même endroit : une seule marque
   pour les deux, sans quoi on ne saurait plus laquelle des deux lignes s'ouvrira. */
.suggestion:hover, .suggestion.visee { background: var(--accent-voile); }
.suggestion--termine .suggestion-titre { opacity: .55; }

.suggestion-vignette {
	width: 40px;
	height: 40px;
	flex: 0 0 40px;
	object-fit: cover;
	border-radius: 4px;
	background: var(--surface);
}

.suggestion-corps { min-width: 0; display: flex; flex-direction: column; gap: .0625rem; }

/* Une ligne chacun : la liste sert à reconnaître un épisode d'un coup d'œil, pas
   à le lire. Le titre entier attend sur sa fiche. */
.suggestion-titre,
.suggestion-meta {
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
.suggestion-titre { font-size: .8125rem; font-weight: 600; }
.suggestion-meta { font-size: .75rem; }

.vue { padding: 0 2rem 3rem; flex: 1; }
.vue--nue { padding: 0; }

.titre-vue { font-size: 1.75rem; margin: .5rem 0 1.5rem; }
.titre-section { font-size: 1rem; margin: 2.5rem 0 1rem; color: var(--discret); font-weight: 600; }
.sous-titre { margin: -1rem 0 1.5rem; }

/* Le décompte de la file et le bouton qui la vide tiennent sur la même ligne, le
   second poussé au bout — et passent l'un sous l'autre quand la largeur manque. */
.entete-file { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.entete-file p { margin: 0; }

/* --------------------------------------------------------- Grille de pochettes */

.grille {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
	gap: 1.125rem;
}

.carte {
	position: relative;
	display: block;
	aspect-ratio: 1;
	border-radius: 4px;
	overflow: hidden;
	background: var(--surface);
	transition: transform .12s ease;
}
.carte:hover { transform: translateY(-2px); }
.carte img { width: 100%; height: 100%; object-fit: cover; display: block; }

.carte-vide {
	display: grid;
	place-items: center;
	width: 100%;
	height: 100%;
	font-size: 1.75rem;
	font-weight: 700;
	color: var(--discret);
	text-transform: uppercase;
}

/* ------------------------------------------------------------------- Musique */

/* La grille des albums est celle des podcasts, à une différence près : une
   pochette de podcast se reconnaît, une pochette d'album beaucoup moins — le
   titre et l'artiste s'écrivent donc sous la case plutôt qu'en infobulle. */
.carte-legende { margin: .5rem 0 0; font-size: .8125rem; line-height: 1.3; }
.carte-legende strong { display: block; font-weight: 600; }
.carte-legende span { display: block; }

/* La sentinelle est un <li> comme les autres : dans une grille, elle prendrait la
   place d'une pochette. Elle tient donc toute la largeur, sous la dernière rangée. */
.grille .sentinelle { grid-column: 1 / -1; }

.onglets-serveurs { display: flex; gap: .5rem; flex-wrap: wrap; margin: -.5rem 0 1.5rem; }
.onglet {
	padding: .35rem .75rem;
	border: 1px solid var(--bordure);
	border-radius: 999px;
	font-size: .875rem;
	color: var(--discret);
}
.onglet--actif { color: var(--texte); border-color: var(--accent); }

.pagination { display: flex; gap: .75rem; justify-content: center; margin-top: 2rem; }

.morceaux { list-style: none; margin: 1.5rem 0 0; padding: 0; }

/* Toute la ligne joue, d'où le curseur sur elle et non sur le seul triangle. Les
   liens et le bouton qu'elle contient gardent le leur, plus précis. */
.morceau {
	display: flex;
	align-items: center;
	gap: .75rem;
	padding: .35rem .5rem;
	border-radius: 6px;
	cursor: pointer;
}
.morceau:hover { background: var(--surface-haute); }
.morceau--courant { background: var(--surface-haute); }
.morceau--courant .morceau-titre { color: var(--accent); font-weight: 600; }

/* Le rang cède la place au triangle de lecture au survol : la ligne entière est
   cliquable par son bouton, sans ajouter une colonne d'icônes toujours visible. */
.bouton-morceau {
	display: grid;
	place-items: center;
	width: 2rem;
	height: 2rem;
	flex: none;
	background: none;
	border: 0;
	color: var(--discret);
	cursor: pointer;
}
.bouton-morceau > * { grid-area: 1 / 1; }
.morceau-jouer { opacity: 0; width: 18px; height: 18px; }
.morceau:hover .morceau-rang,
.morceau--courant .morceau-rang { opacity: 0; }
.morceau:hover .morceau-jouer,
.morceau--courant .morceau-jouer { opacity: 1; color: var(--accent); }

.morceau-titre { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.morceau-artiste { flex: none; font-size: .8125rem; }
.morceau-duree { flex: none; font-variant-numeric: tabular-nums; font-size: .8125rem; }

/* Chanson trouvée par le filtre : le triangle remplace le rang, qui ne veut rien
   dire hors de son disque, et reste visible — au doigt il n'y a pas de survol
   pour le faire apparaître. Le nom du disque suit celui de l'artiste, et mène à
   lui : c'est souvent le disque qu'on cherchait. */
.morceau--trouve .morceau-jouer { opacity: 1; }
.morceau-artiste, .morceau-album { max-width: 12rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.morceau-album { flex: none; font-size: .8125rem; }
.morceau-album:hover { color: var(--texte); }

/* Une piste n'a ni favori, ni file d'attente, ni repères : elle n'existe que
   dans la médiathèque distante, sans ligne en base. La réglette et les sauts,
   eux, restent — contrairement au direct, une piste a une durée. */
.lecteur--musique #favori { display: none; }

/* Pastille d'angle : le triangle rouge du coin, avec le nombre d'épisodes non
   écoutés — le repère visuel principal de la grille. */
.pastille-angle {
	position: absolute;
	top: 0;
	right: 0;
	min-width: 40px;
	height: 40px;
	padding: 3px 5px 0 0;
	display: flex;
	align-items: flex-start;
	justify-content: flex-end;
	background: linear-gradient(to bottom left, var(--accent) 50%, transparent 50%);
	color: var(--accent-texte);
	font-size: .6875rem;
	font-weight: 700;
	line-height: 1;
}

/* Réorganisation par appui maintenu. Faute de poignée, c'est toute la case qui se
   saisit : `touch-action: manipulation` laisse le défilement au navigateur tant que
   le déplacement n'a pas commencé ; le blocage du défilement se fait ensuite en
   JavaScript, une fois le geste reconnu. La file d'attente, elle, a sa poignée et
   n'a donc rien à retirer au doigt sur le reste de la ligne. */
.grille[data-reorganisable] > li {
	touch-action: manipulation;
	-webkit-user-select: none;
	user-select: none;
}
.grille img { -webkit-user-drag: none; }

.grille > li.deplace { pointer-events: none; }
.grille > li.deplace .carte {
	transform: scale(.9);
	opacity: .85;
	outline: 2px solid var(--accent);
	outline-offset: 2px;
	box-shadow: 0 12px 28px var(--ombre-forte);
}

body.reorganisation { cursor: grabbing; }

/* Pendant le classement, le soulèvement au survol parasiterait le glissement des
   cases : seule celle qu'on tient doit bouger de sa propre initiative. */
body.reorganisation .grille > li:not(.deplace) .carte:hover { transform: none; }

/* Les cases se déplacent par transformation : c'est le JavaScript qui pose l'écart
   de départ, la transition fait le reste. Voir animerReorganisation(). */
.grille > li { transition: transform 180ms ease; }
.grille > li.deplace { transition: none; }

.aide-grille { margin-top: 1.75rem; }

.badge-erreur {
	position: absolute;
	inset: auto 0 0 0;
	/* Posé sur une pochette, jamais sur le fond de la page : ce voile reste noir
	   dans les deux thèmes, et le rouge garde donc sa déclinaison claire. */
	background: rgba(0, 0, 0, .78);
	color: #ff6b6b;
	font-size: .6875rem;
	text-align: center;
	padding: .25rem;
}

/* ------------------------------------------------ Filtres d'une liste d'épisodes */

/* Le champ prend la place restante, la bascule garde la sienne ; en dessous d'une
   certaine largeur, l'un passe sous l'autre. */
.filtres {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: .75rem;
	margin: 0 0 1.25rem;
}

/* La recherche du bandeau est calée à droite et bornée en largeur — celle-ci occupe
   ce qu'on lui laisse. */
.recherche--filtre { margin-left: 0; width: auto; flex: 1 1 16rem; }

.filtres .bouton { flex: 0 0 auto; }
.filtres .bouton.actif { background: var(--accent); color: var(--accent-texte); }

/* ----------------------------------------------------------- Listes d'épisodes */

.episodes { list-style: none; margin: 0; padding: 0; }

.episode {
	position: relative;
	display: flex;
	gap: 1rem;
	align-items: flex-start;
	padding: 1rem .75rem;
	border-bottom: 1px solid var(--bordure);
	border-radius: var(--rayon);
	transition: transform .16s ease, background-color .16s ease, box-shadow .16s ease;
}

/* Léger soulèvement au survol : assez pour désigner la ligne pointée, pas assez
   pour faire bouger la lecture d'une longue liste. */
.episode:hover {
	transform: translateY(-2px);
	background-color: var(--surface);
	box-shadow: 0 6px 18px var(--ombre);
	border-bottom-color: transparent;
}

/* La ligne qui quitte la liste — marquée écoutée dans « En écoute », où elle n'a
   plus sa place. Elle s'efface sur le côté avant d'être retirée, le temps qu'on
   voie qu'elle part plutôt que de la voir manquer.

   Sélecteur doublé pour peser autant que `:hover`, actif sous le curseur au
   moment même du clic — sans quoi le soulèvement du survol garderait la main sur
   `transform`. La durée est reprise dans app.js, qui retire la ligne à la fin. */
.episode.episode--sortante {
	opacity: 0;
	transform: translateX(-1rem);
	transition: opacity .2s ease, transform .2s ease;
	box-shadow: none;
	pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
	.episode { transition: background-color .16s ease; }
	.episode:hover { transform: none; box-shadow: none; }
}

/* Poignée de déplacement, à gauche de la ligne : le seul endroit par lequel elle se
   saisit, et le seul signe qui dise qu'elle se déplace. `touch-action: none` réserve
   le geste à la page — sans quoi le doigt ferait défiler la liste au lieu de prendre
   la ligne, l'attente de 420 ms de la grille n'ayant plus lieu d'être ici. */
.poignee {
	flex: 0 0 auto;
	align-self: center;
	display: grid;
	place-items: center;
	margin-left: -.25rem;
	color: var(--discret);
	cursor: grab;
	touch-action: none;
	-webkit-user-select: none;
	user-select: none;
}
.poignee:hover { color: var(--texte); }
.poignee .icone { width: 18px; height: 18px; flex: 0 0 18px; }

body.reorganisation .poignee { cursor: grabbing; }

/* Ligne saisie dans la file : les mêmes signes que la case tenue dans la grille —
   elle se resserre, se cerne de la couleur d'emphase et se détache par son ombre.
   `pointer-events: none` la rend transparente aux événements, ce dont dépend
   elementFromPoint() pour trouver la ligne qui se trouve dessous. */
.episodes[data-reorganisable] > li.deplace {
	pointer-events: none;
	transform: scale(.98);
	background-color: var(--surface);
	outline: 2px solid var(--accent);
	outline-offset: -1px;
	box-shadow: 0 12px 28px var(--ombre-forte);
	border-bottom-color: transparent;
}

/* Le déplacement des autres lignes est joué par transformation : le JavaScript pose
   l'écart de départ, la transition fait le reste (animerReorganisation). */
.episodes[data-reorganisable] > li { transition: transform 180ms ease; }
.episodes[data-reorganisable] > li.deplace { transition: none; }

/* Pendant le classement, le soulèvement au survol parasiterait le glissement. */
body.reorganisation .episodes > li:not(.deplace):hover { transform: none; box-shadow: none; }

/* Liseré d'avancement, au ras du bas de la carte : sa longueur dit d'un coup d'œil
   si on en est à 15 % ou à 90 %. Il se met à jour en direct pendant la lecture, et
   sa valeur vient de la base après un rechargement. */
progress.lisere {
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	width: 100%;
	height: 3px;
	margin: 0;
	border: none;
	border-radius: 0 2px 2px 0;
	background: transparent;
	appearance: none;
	pointer-events: none;
	z-index: 2;
}
progress.lisere::-webkit-progress-bar { background: transparent; }
progress.lisere::-webkit-progress-value { background: var(--accent); border-radius: 0 2px 2px 0; }
progress.lisere::-moz-progress-bar { background: var(--accent); border-radius: 0 2px 2px 0; }

.episode > *:not(.lisere) { position: relative; z-index: 1; }

.episode--termine .episode-titre, .episode--termine .episode-extrait { opacity: .55; }

/* Pastille de l'angle de la vignette : épisode jamais terminé. */
.episode-vignette { position: relative; flex: 0 0 56px; line-height: 0; }

.point-non-lu {
	position: absolute;
	top: -3px;
	right: -3px;
	width: 11px;
	height: 11px;
	border-radius: 50%;
	background: var(--accent);
	box-shadow: 0 0 0 2px var(--fond);
}
.episode:hover .point-non-lu { box-shadow: 0 0 0 2px var(--surface); }

.action-lu .coche { opacity: 0; transition: opacity .12s ease; }
.action-lu:hover .coche, .action-lu.actif .coche { opacity: 1; }

.vignette {
	width: 56px;
	height: 56px;
	flex: 0 0 56px;
	object-fit: cover;
	border-radius: 4px;
	background: var(--surface);
}
.vignette--vide { display: block; }

.episode-corps { flex: 1; min-width: 0; }
.episode-podcast { display: block; text-decoration: none; margin-bottom: .125rem; }
.episode-podcast:hover { color: var(--texte); }
.episode-titre { display: block; font-size: .9375rem; }

.episode-extrait {
	margin: .25rem 0 .375rem;
	display: -webkit-box;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
	overflow: hidden;
}

.episode-meta { display: flex; gap: 1rem; flex-wrap: wrap; align-items: center; }

.etiquette {
	border: 1px solid var(--bordure);
	border-radius: 999px;
	padding: .0625rem .5rem;
	font-size: .6875rem;
}

/* Instant visé par un lien partagé : il annonce pourquoi la fiche s'ouvre sur un
   bouton « Écouter à 12:30 » plutôt que sur une reprise ordinaire. */
.etiquette--vise { border-color: var(--accent); color: var(--accent); }

/* ----------------------------------------------------- Épisodes hors du flux */

/* Un épisode que le diffuseur a retiré et que l'application garde. Le signe est
   discret : c'est un épisode comme les autres, avec une provenance en moins. */
.etiquette--archive { border-style: dashed; }

/* Carton d'archive dans l'angle bas de la vignette. Le voile sombre reste sombre
   dans les deux thèmes : il est posé sur une pochette, pas sur le fond de la page. */
.marque-archive {
	position: absolute;
	right: -3px;
	bottom: -3px;
	display: grid;
	place-items: center;
	width: 20px;
	height: 20px;
	border-radius: 50%;
	background: rgba(0, 0, 0, .78);
	color: #fff;
	box-shadow: 0 0 0 2px var(--fond);
}
.marque-archive .icone { width: 12px; height: 12px; flex: 0 0 12px; stroke-width: 2.2; }
.episode:hover .marque-archive { box-shadow: 0 0 0 2px var(--surface); }

/* Sur la fiche, la place existe pour une phrase entière. */
.avis-archive {
	display: flex;
	align-items: flex-start;
	gap: .625rem;
	max-width: 60ch;
	margin: 1.25rem 0 0;
	padding: .75rem .875rem;
	border: 1px dashed var(--bordure);
	border-radius: var(--rayon);
	color: var(--discret);
	font-size: .8125rem;
	line-height: 1.5;
}
.avis-archive .icone { flex: 0 0 18px; width: 18px; height: 18px; margin-top: .0625rem; }

.episode-actions { display: flex; align-items: center; gap: .25rem; flex: 0 0 auto; }

/* Sur un téléphone, la vignette et les quatre boutons occupaient tant de largeur
   qu'il ne restait qu'une centaine de pixels au texte : un titre un peu long
   descendait sur dix lignes d'un ou deux mots, illisible. On passe donc sur deux
   rangées — vignette et texte en haut, boutons en dessous sur toute la largeur.
   Le texte y gagne environ le double d'espace, et les cibles tactiles s'agrandissent
   puisqu'elles ne disputent plus rien à personne. */
@media (max-width: 600px) {
	.episode {
		display: grid;
		grid-template-columns: 72px minmax(0, 1fr);
		column-gap: .875rem;
		row-gap: .5rem;
		align-items: start;
		padding: .875rem .5rem;
	}

	.episode-vignette { grid-column: 1; grid-row: 1; }
	.vignette { width: 72px; height: 72px; }

	.episode-corps { grid-column: 2; grid-row: 1; }

	/* Une poignée demande sa colonne, en tête de ligne : tout le reste se décale
	   d'un cran. Elle reste au milieu de la hauteur de la vignette, là où le pouce
	   la trouve sans viser. */
	.episodes[data-reorganisable] > .episode { grid-template-columns: 24px 72px minmax(0, 1fr); }
	.episodes[data-reorganisable] .poignee { grid-column: 1; grid-row: 1; margin-left: -.375rem; }
	.episodes[data-reorganisable] .episode-vignette { grid-column: 2; }
	.episodes[data-reorganisable] .episode-corps { grid-column: 3; }

	/* Le retrait négatif compense le creux du bouton rond : le premier picto tombe
	   ainsi à l'aplomb du bord gauche de la vignette. */
	.episode-actions {
		grid-column: 1 / -1;
		grid-row: 2;
		margin-left: -.5rem;
	}

	.episode-actions .outil { width: 40px; height: 40px; }
	.episode-actions .outil svg { width: 21px; height: 21px; }

	/* La lecture, action principale, s'isole à l'opposé des trois bascules : coin bas
	   droit, là où le pouce tombe sans effort. */
	.episode-actions .rond--lire { width: 40px; height: 40px; margin-left: auto; }
	.episode-actions .rond--lire svg { width: 19px; height: 19px; }

	/* Trois lignes suffisent à reconnaître un épisode, et le titre entier reste sur
	   sa fiche. Sans cette limite, un titre à rallonge occupe tout l'écran. */
	.episode-titre {
		display: -webkit-box;
		-webkit-line-clamp: 3;
		-webkit-box-orient: vertical;
		overflow: hidden;
	}

	.episode-meta { gap: .75rem; }
}

/* Fin de tranche : remplacée par la suite des épisodes quand elle approche du
   champ de vision. Sans JavaScript, c'est un lien vers la page suivante. */
.sentinelle {
	position: relative;
	list-style: none;
	display: grid;
	place-items: center;
	padding: 1.75rem 0;
}

.sentinelle a {
	padding: .4rem 1.1rem;
	border: 1px solid var(--bordure);
	border-radius: 999px;
	color: var(--discret);
	font-size: .8125rem;
	text-decoration: none;
}
.sentinelle a:hover { color: var(--texte); border-color: var(--discret); }

.sentinelle--active a { color: transparent; border-color: transparent; }
.sentinelle--active::after {
	content: "Chargement…";
	position: absolute;
	inset: 0;
	display: grid;
	place-items: center;
	color: var(--discret);
	font-size: .8125rem;
}

/* -------------------------------------------------------------------- Boutons */

.outil {
	width: 34px;
	height: 34px;
	display: grid;
	place-items: center;
	background: none;
	border: none;
	border-radius: 50%;
	color: var(--discret);
	cursor: pointer;
	text-decoration: none;
	position: relative;
}
.outil:hover { color: var(--texte); background: var(--surface-haute); }
.outil svg { width: 19px; height: 19px; }
/* Même effacement que .rond:disabled — le « − » de la vitesse au plus lent, le « + »
   au plus rapide. Le survol ne doit pas rallumer ce qui est éteint. */
.outil:disabled { opacity: .3; cursor: default; }
.outil:disabled:hover { background: none; color: var(--discret); }
.outil.actif { color: var(--accent); }
.outil.actif svg { fill: var(--accent); }

/* Le pictogramme de mise en file est fait de traits, pas de surfaces : le
   remplissage du cœur juste au-dessus l'épaissirait sans rien dire. Seule la
   couleur porte l'état — allumé, l'épisode est dans la file. */
.action-file.actif svg { fill: none; }

/* Battement à l'ajout, et à l'ajout seulement : le geste envoie l'épisode dans une
   file qu'on ne voit pas d'ici, il lui faut un accusé de réception. Le retrait n'en
   a pas besoin — la couleur s'éteint sous le doigt, c'est déjà une réponse.
   L'animation porte sur le pictogramme et non sur le bouton : la pastille du
   survol garde ainsi sa taille. */
@keyframes battement-file {
	0%   { transform: scale(1); }
	40%  { transform: scale(1.35); }
	100% { transform: scale(1); }
}

.action-file--ajoute svg { animation: battement-file .32s ease; }

.rond {
	width: 36px;
	height: 36px;
	display: grid;
	place-items: center;
	border: 1px solid var(--bordure);
	border-radius: 50%;
	background: none;
	color: var(--texte);
	cursor: pointer;
}
.rond:hover { background: var(--surface-haute); }
.rond svg { width: 17px; height: 17px; }
.rond--lire svg { fill: currentColor; stroke: none; margin-left: 2px; }

.rond--principal {
	width: 46px;
	height: 46px;
	border: none;
	background: var(--plein);
	color: var(--plein-texte);
}
.rond--principal:hover { background: var(--plein-survol); }
.rond--principal svg { width: 20px; height: 20px; fill: currentColor; stroke: none; }

.bouton {
	display: inline-block;
	padding: .5rem 1.1rem;
	border: none;
	border-radius: 999px;
	background: var(--accent);
	color: var(--accent-texte);
	font: inherit;
	font-size: .875rem;
	font-weight: 600;
	text-decoration: none;
	cursor: pointer;
}
.bouton--discret { background: var(--surface-haute); color: var(--texte); }

/* Bouton à pictogramme : le picto et le libellé sur une même ligne de base. */
.bouton--picto { display: inline-flex; align-items: center; gap: .4375rem; }
.bouton--picto .icone { width: 17px; height: 17px; flex: 0 0 17px; }

/* Le cœur se remplit quand l'épisode est en favori — même code visuel que dans les
   listes et dans le lecteur, où c'est .outil.actif qui s'en charge. */
.action-favori.actif .icone { fill: currentColor; }
.bouton--danger { background: none; color: var(--discret); border: 1px solid var(--bordure); }
.bouton--danger:hover { color: var(--erreur); border-color: var(--erreur); }

.pastille {
	position: absolute;
	top: 0;
	right: 0;
	min-width: 16px;
	height: 16px;
	padding: 0 4px;
	border-radius: 999px;
	background: var(--accent);
	color: var(--accent-texte);
	font-size: .625rem;
	font-weight: 700;
	line-height: 16px;
	text-align: center;
}

/* --------------------------------------------------------------- Fiche épisode */

.fiche-episode { display: flex; gap: 2rem; flex-wrap: wrap; align-items: flex-start; }

.fiche-episode-visuel { flex: 0 0 320px; max-width: 100%; }
.fiche-episode-visuel img,
.fiche-episode-visuel .carte-vide {
	width: 100%;
	aspect-ratio: 1;
	height: auto;
	object-fit: cover;
	border-radius: 10px;
	background: var(--surface);
}

.fiche-episode-corps { flex: 1; min-width: 300px; }
.fiche-episode-corps h1 { font-size: 1.5rem; margin: .25rem 0 .75rem; line-height: 1.25; }
.fiche-episode-meta { display: flex; gap: 1rem; align-items: center; flex-wrap: wrap; }

.fiche-episode-actions { display: flex; gap: .5rem; flex-wrap: wrap; margin: 1.5rem 0; }
.fiche-episode-actions .actif { color: var(--accent); border-color: var(--accent); }

.fiche-episode-description {
	font-size: .9375rem;
	line-height: 1.65;
	color: var(--texte-doux);
	max-width: 70ch;
}
/* Repères horaires cliquables dans une description. Assez visibles pour qu'on
   comprenne qu'ils mènent quelque part, assez discrets pour ne pas hacher le texte. */
.timecode {
	display: inline;
	padding: .05em .35em;
	border: none;
	border-radius: 4px;
	background: var(--accent-voile);
	color: var(--accent);
	font: inherit;
	font-variant-numeric: tabular-nums;
	cursor: pointer;
}
.timecode:hover { background: var(--accent); color: var(--accent-texte); }
.timecode:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.fiche-episode-description p { margin: 0 0 1em; }
.fiche-episode-description a { color: var(--accent); }
.fiche-episode-description ul, .fiche-episode-description ol { padding-left: 1.25rem; }
.fiche-episode-description blockquote {
	margin: 0 0 1em;
	padding-left: 1rem;
	border-left: 2px solid var(--bordure);
	color: var(--discret);
}

.episode-vignette { display: block; text-decoration: none; }
.lecteur-pochette-lien { display: block; flex: 0 0 auto; line-height: 0; }

/* --------------------------------------------------------------- Fiche podcast */

.fiche { display: flex; gap: 2rem; flex-wrap: wrap; align-items: flex-start; margin-bottom: 1rem; }
.fiche-pochette { width: 200px; height: 200px; object-fit: cover; border-radius: var(--rayon); }
.fiche-corps { flex: 1; min-width: 280px; }
.fiche-corps h1 { font-size: 1.5rem; margin: 0 0 .25rem; }
.fiche-description { font-size: .875rem; color: var(--texte-doux); max-width: 60ch; }
/* Le rythme de parution, entre la description et l'adresse du flux. Serré contre
   la description, dont il prolonge la présentation, et non contre l'adresse. */
.fiche-rythme { margin: -.5rem 0 0; }
/* Adresse du flux : une ligne, toujours une seule. Un flux privé porte un jeton
   qui peut faire trois cents caractères — coupé à l'écran, mais copié en entier,
   la troncature étant ici et non dans le HTML. */
.fiche-flux { display: flex; align-items: center; gap: .25rem; margin: 1rem 0 0; min-width: 0; }
.flux-adresse {
	flex: 1;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	font-size: .8125rem;
	color: var(--discret);
	background: var(--surface);
	border-radius: 4px;
	padding: .3rem .5rem;
}
.copier { flex: none; }
.copier--fait { color: var(--succes); }

/* Mention « Lien copié », posée après le bouton par app.js au premier clic. Une
   fois apparue elle garde sa place — `visibility` et non `display` — sinon
   l'adresse s'allongerait puis se raccourcirait à chaque copie, et la ligne
   sauterait sous l'œil. */
.copier-avis {
	flex: none;
	max-width: 40%;
	overflow: hidden;
	white-space: nowrap;
	font-size: .8125rem;
	color: var(--succes);
	opacity: 0;
	visibility: hidden;
	transition: opacity .15s ease;
}
.copier-avis--visible { opacity: 1; visibility: visible; }

.fiche-actions { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; margin-top: 1.25rem; }

/* ------------------------------------------------------------------ Formulaires */

.ajout { max-width: 620px; margin-bottom: 1rem; }
.ajout .ligne { display: flex; gap: .5rem; }
.ajout .aide { margin-top: .75rem; }

label { display: block; font-size: .8125rem; color: var(--discret); margin-bottom: .375rem; }

input[type="url"], input[type="text"], input[type="password"], input[type="search"] {
	width: 100%;
	padding: .6rem .9rem;
	background: var(--surface);
	border: 1px solid var(--bordure);
	border-radius: var(--rayon);
	color: var(--texte);
	font: inherit;
	font-size: .875rem;
}
/* Ciblé sur les champs de saisie : appliqué à tous les <input>, ce cadre entourait
   aussi les curseurs du lecteur d'un liseré rouge dès le premier clic. */
input[type="url"]:focus,
input[type="text"]:focus,
input[type="password"]:focus,
input[type="email"]:focus,
input[type="search"]:focus {
	outline: 2px solid var(--accent);
	outline-offset: -1px;
}

.liste-podcasts { list-style: none; margin: 0; padding: 0; }
.liste-podcast {
	display: flex;
	align-items: center;
	gap: .875rem;
	padding: .625rem .25rem;
	border-bottom: 1px solid var(--bordure);
	text-decoration: none;
}
.liste-podcast:hover { background: var(--surface); }
.liste-podcast-texte { display: flex; flex-direction: column; gap: .125rem; min-width: 0; }
.liste-podcast-texte strong { font-size: .9375rem; }

/* ------------------------------------------------------------------------ Radios */

/* Liste volontairement nue : une radio n'a ni pochette, ni durée, ni avancement.
   Un bouton de lecture, un nom, une croix pour la retirer — il n'y a rien d'autre
   à en dire, et lui donner l'allure d'un épisode serait mentir sur sa nature.

   D'où la mise en colonnes : une ligne de radio est courte, et l'étirer sur toute
   la largeur d'un écran de bureau laissait un vide entre le nom et le cœur. Les
   colonnes se font et se défont d'elles-mêmes selon la place — quatre sur un grand
   écran, une seule sur un téléphone — sans point de rupture à maintenir. */
.radios {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
	gap: 0 2rem;
}

.radio {
	display: flex;
	align-items: center;
	gap: .875rem;
	padding: .5rem .25rem;
	border-bottom: 1px solid var(--bordure);
}
.radio:hover { background: var(--surface); }

.radio-nom { flex: 1; min-width: 0; font-size: .9375rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* La radio à l'antenne se signale : nom à la couleur d'emphase, bouton cerclé.
   `--courante` veut dire chargée dans le lecteur, `--joue` qu'elle se lit vraiment —
   une radio en pause reste distinguée mais remontre une flèche de lecture. */
.radio--courante .radio-nom { color: var(--accent); font-weight: 600; }
.radio--courante .rond--lire { border-color: var(--accent); color: var(--accent); }

/* Les deux pictogrammes coexistent dans le bouton et c'est la classe de la ligne
   qui tranche : `hidden` n'a aucun effet sur un élément SVG. */
.radio .icone-arret { display: none; }
.radio--joue .icone-lecture { display: none; }
.radio--joue .icone-arret { display: block; margin-left: 0; } /* le décalage optique ne vaut que pour la flèche */

/* Le cœur, comme pour les épisodes : creux au repos, plein une fois retenu. */
.action-radio-favori.actif { color: var(--accent); }
.action-radio-favori.actif svg { fill: var(--accent); }

/* Renommer et retirer ne se montrent qu'au survol sur les pointeurs fins : sur une
   liste de quinze lignes, trente pictogrammes permanents attirent l'œil plus que les
   radios elles-mêmes. Le cœur, lui, porte un état et reste toujours visible. Au
   doigt, où il n'y a pas de survol, tout reste affiché. */
.radio-outils { display: flex; align-items: center; line-height: 0; }
.radio-outils .outil svg { width: 16px; height: 16px; }

@media (hover: hover) {
	.radio-outils .outil { opacity: 0; transition: opacity .14s ease; }
	.radio:hover .radio-outils .outil,
	.radio-outils .outil:focus-visible { opacity: 1; }
}

/* Champ de renommage : il prend la place exacte du nom, sans faire grandir la ligne
   — d'où la remise à plat de la hauteur héritée des champs de formulaire.
   Deux classes dans le sélecteur, et non `.radio-saisie` seul : `input[type="text"]`
   pèse autant qu'une classe et l'emporterait sinon selon l'ordre du fichier. */
.radio .radio-saisie {
	flex: 1;
	min-width: 0;
	padding: .25rem .5rem;
	font-size: .9375rem;
	background: var(--surface-haute);
	border-color: var(--accent);
}

/* --------------------------------------------------------------------- Connexion */

.connexion {
	max-width: 22rem;
	margin: 12vh auto;
	padding: 2rem;
	background: var(--surface);
	border-radius: 12px;
}
.connexion h1 { margin: 0 0 .25rem; font-size: 1.375rem; }
.connexion form { margin-top: 1.75rem; }
.connexion label { margin-top: 1rem; }
.connexion .bouton { width: 100%; margin-top: 1.5rem; text-align: center; }

/* Séparateur « ou » entre le mot de passe et le nuage : un filet interrompu par
   le mot, obtenu avec deux traits qui prennent chacun la place restante. */
.separateur-ou {
	display: flex;
	align-items: center;
	gap: .75rem;
	margin: 1.5rem 0 0;
	color: var(--discret);
	font-size: .8125rem;
}
.separateur-ou::before,
.separateur-ou::after {
	content: '';
	flex: 1;
	height: 1px;
	background: var(--bordure);
}

.bouton--nuage { background: var(--surface-haute); color: var(--texte); }

/* ------------------------------------------------- Page du morceau à l'antenne */

/* Même mise en page que la fiche d'un épisode, à laquelle elle répond : visuel à
   gauche, tout le reste dans une colonne à droite qui passe dessous quand la place
   manque. */
.fiche-direct { display: flex; gap: 2rem; flex-wrap: wrap; align-items: flex-start; }

.fiche-direct-pochette {
	flex: 0 0 320px;
	width: 320px;
	max-width: 100%;
	aspect-ratio: 1;
	display: grid;
	place-items: center;
	border-radius: 10px;
	background: var(--surface);
	overflow: hidden;
}
.fiche-direct-pochette img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Sans visuel, l'antenne du menu plutôt qu'un carré vide — comme dans le lecteur. */
.fiche-direct-pochette.sans-visuel::after {
	content: "";
	width: 128px;
	height: 128px;
	background: var(--accent);
	-webkit-mask: var(--antenne) center / contain no-repeat;
	mask: var(--antenne) center / contain no-repeat;
}

.fiche-direct-corps { flex: 1; min-width: 300px; }
.fiche-direct-corps h1 { font-size: 1.5rem; margin: .25rem 0 .375rem; line-height: 1.25; }
.fiche-direct-artiste { margin: 0; color: var(--texte-doux); font-size: 1.0625rem; }
.fiche-direct-corps .titre-section { margin: 2rem 0 .75rem; }

/* La pastille « En direct » du lecteur, réemployée telle quelle : même code visuel
   pour la même idée. Elle ne s'affiche là-bas que dans le lecteur en mode direct,
   d'où la remise en ligne ici. */
.fiche-direct-antenne { display: flex; align-items: center; gap: .75rem; margin: 0; }
.fiche-direct-antenne .direct-etat { display: inline-flex; }
.fiche-direct-antenne a { text-decoration: none; }
.fiche-direct-antenne a:hover { color: var(--texte); }

/* ------------------------------------------------------- Page de la piste jouée */

/* Même mise en page que la fiche d'un épisode et que celle du morceau à l'antenne :
   pochette à gauche, colonne de texte à droite qui passe dessous quand la place
   manque. Trois pages du même genre, un seul geste à apprendre. */
.fiche-piste { display: flex; gap: 2rem; flex-wrap: wrap; align-items: flex-start; }

.fiche-piste-pochette {
	flex: 0 0 320px;
	width: 320px;
	max-width: 100%;
	aspect-ratio: 1;
	border-radius: 10px;
	background: var(--surface);
	overflow: hidden;
}
.fiche-piste-pochette img { width: 100%; height: 100%; object-fit: cover; display: block; }

.fiche-piste-corps { flex: 1; min-width: 300px; }
.fiche-piste-corps h1 { font-size: 1.5rem; margin: .25rem 0 .375rem; line-height: 1.25; }
.fiche-piste-artiste { margin: 0 0 .25rem; color: var(--texte-doux); font-size: 1.0625rem; }
.fiche-piste-source { margin: 0; }
.fiche-piste-source a { text-decoration: none; }
.fiche-piste-source a:hover { color: var(--texte); }

.fiche-piste-champs { margin-top: 0; max-width: 34rem; }

/* Les onglets de la fiche reprennent les pastilles des serveurs de musique : même
   forme pour la même idée, choisir entre deux vues du même objet. */
.onglets-fiche { display: flex; gap: .5rem; margin: 1.5rem 0 1.25rem; }
.onglets-fiche .onglet { background: none; cursor: pointer; font: inherit; font-size: .875rem; }
.onglets-fiche .onglet[hidden] { display: none; }

/* Les paroles gardent leurs retours à la ligne, qui sont leur mise en page : c'est
   du texte brut, où chaque vers est une ligne et chaque couplet un blanc. */
.paroles-texte { white-space: pre-line; margin: 0; max-width: 46ch; line-height: 1.6; }
.paroles-source { margin: 1.25rem 0 0; font-size: .75rem; }

/* Un chemin de fichier se coupe n'importe où : il n'a pas d'espaces où le
   navigateur puisse revenir à la ligne, et déborderait de la colonne. */
.chemin { overflow-wrap: anywhere; font-size: .8125rem; color: var(--texte-doux); }

/* Le compte de pistes suit le nom du disque sur la même ligne, en retrait. */
.titre-section .discret { font-weight: 400; font-size: .8125rem; margin-left: .5rem; }

/* Au téléphone, la pochette prend la largeur et le texte passe dessous — le même
   pli que la page du morceau à l'antenne, qui a la même mise en page. */
@media (max-width: 600px) {
	.fiche-piste { gap: 1.25rem; }
	.fiche-piste-pochette { flex: 0 0 auto; width: 100%; }
	.fiche-piste-corps { min-width: 0; }
}

/* --------------------------------------------------------------- Liste de vidéos */

.videos { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }

.video {
	display: flex;
	gap: .875rem;
	align-items: center;
	padding: .375rem;
	border-radius: var(--rayon);
	text-decoration: none;
}
.video:hover { background: var(--surface-haute); }

.video-vignette { position: relative; flex: 0 0 120px; line-height: 0; }
.video-vignette img { width: 120px; height: 68px; object-fit: cover; border-radius: 4px; background: var(--surface-haute); }

/* La durée s'inscrit dans l'angle de la vignette, sur un voile qui reste sombre
   dans les deux thèmes : elle est posée sur une image, pas sur le fond de la page. */
.video-duree {
	position: absolute;
	right: 4px;
	bottom: 4px;
	padding: 0 .25rem;
	border-radius: 3px;
	background: rgba(0, 0, 0, .78);
	color: #fff;
	font-size: .6875rem;
	font-weight: 600;
	line-height: 1.4;
}

.video-corps { min-width: 0; display: flex; flex-direction: column; gap: .1875rem; }

.video-titre {
	font-size: .875rem;
	font-weight: 600;
	display: -webkit-box;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
	overflow: hidden;
}
.video-chaine { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.fiche-direct-suite { margin: 1rem 0 0; font-size: .8125rem; }
.fiche-direct-suite a { color: var(--accent); }
.fiche-direct-suite[hidden], .fiche-direct-suite a[hidden] { display: none; }

@media (max-width: 600px) {
	.fiche-direct { gap: 1.25rem; }
	.fiche-direct-pochette { flex: 0 0 auto; width: 100%; }
	.fiche-direct-pochette.sans-visuel::after { width: 96px; height: 96px; }
	.fiche-direct-corps { min-width: 0; }
	.video-vignette { flex: 0 0 96px; }
	.video-vignette img { width: 96px; height: 54px; }
}

/* ----------------------------------------------------------------------- Lecteur */

.lecteur {
	position: fixed;
	inset: auto 0 0 0;
	z-index: 40;
	height: var(--hauteur-lecteur);
	display: grid;
	grid-template-columns: minmax(180px, 1fr) auto minmax(220px, 2fr) auto;
	align-items: center;
	gap: 1.5rem;
	padding: 0 1.5rem calc(0px + env(safe-area-inset-bottom));
	background: light-dark(#fafafc, #0e0f12);
	border-top: 1px solid var(--bordure);
}

.lecteur[hidden] { display: none; }

.lecteur-piste { display: flex; align-items: center; gap: .875rem; min-width: 0; }
.lecteur-pochette { width: 60px; height: 60px; object-fit: cover; border-radius: 4px; background: var(--surface); }
.lecteur-infos { min-width: 0; }
.lecteur-infos strong, .lecteur-infos span {
	display: block;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	font-size: .875rem;
}

.lecteur-commandes { display: flex; align-items: center; gap: .75rem; }

/* ------------------------------------------------------ Lecteur en mode direct */

/* Une radio n'a ni durée, ni position, ni fin : la réglette, les deux minuteurs,
   les sauts de 10 et 30 secondes et le favori ne représentent plus rien. Ils
   s'effacent au profit d'une pastille, et ne laissent que ce qui a du sens sur un
   direct — lecture, pause et volume. */
.direct-etat { display: none; }

.lecteur--direct .piste,
.lecteur--direct .temps,
.lecteur--direct .rond--saut,
.lecteur--direct .rond--enchainer,
.lecteur--direct #favori { display: none; }

.lecteur--direct .direct-etat {
	display: inline-flex;
	align-items: center;
	gap: .5rem;
	font-size: .8125rem;
	font-weight: 600;
	color: var(--accent);
}

.direct-etat::before {
	content: "";
	width: 8px;
	height: 8px;
	border-radius: 50%;
	background: var(--accent);
	animation: battement 2s ease-in-out infinite;
}

@keyframes battement { 0%, 100% { opacity: 1; } 50% { opacity: .3; } }

@media (prefers-reduced-motion: reduce) {
	.direct-etat::before { animation: none; }
}

/* Une radio n'a pas de pochette : l'emplacement reçoit le pictogramme d'antenne du
   menu plutôt que de laisser un trou. Il est posé en masque et non en image de
   fond, ce qui lui garde la couleur de --accent : contrairement au logo, il suivra
   tout seul un changement de teinte. */
.lecteur--direct .lecteur-pochette { display: none; }

.lecteur--direct .lecteur-pochette-lien {
	display: grid;
	place-items: center;
	width: 60px;
	height: 60px;
	border-radius: 4px;
	background: var(--surface-haute);
}

.lecteur--direct .lecteur-pochette-lien::after {
	content: "";
	width: 28px;
	height: 28px;
	background: var(--accent);
	-webkit-mask: var(--antenne) center / contain no-repeat;
	mask: var(--antenne) center / contain no-repeat;
}

/* Quand le programme fournit un visuel — pochette du disque, affiche de l'émission —
   il prend la place du pictogramme, qui n'était là que pour combler le vide. */
.lecteur--direct.lecteur--visuel .lecteur-pochette-lien { background: none; }
.lecteur--direct.lecteur--visuel .lecteur-pochette-lien::after { display: none; }
.lecteur--direct.lecteur--visuel .lecteur-pochette { display: block; }

/* Sous-titre trop long : va-et-vient plutôt que troncature. La course et la durée
   sont posées par app.js, qui seul connaît le débordement réel ; les paliers laissent
   le texte lisible à l'arrêt aux deux extrémités. */
.defilant { display: inline-block; white-space: nowrap; }

.defile .defilant {
	animation: defilement var(--duree, 12s) ease-in-out infinite;
}

@keyframes defilement {
	0%, 18%   { transform: translateX(0); }
	50%, 68%  { transform: translateX(var(--course, 0)); }
	100%      { transform: translateX(0); }
}

@media (prefers-reduced-motion: reduce) {
	.defile .defilant { animation: none; }
}

/* Boutons de saut : le nombre de secondes écrit en toutes lettres. Les flèches
   circulaires qui le portaient se lisaient mal à cette taille, et le signe dit le
   sens du saut plus vite que le sens d'enroulement d'une boucle.
   Le vrai signe moins (U+2212) et les chiffres tabulaires donnent à « −10 » et
   « +30 » exactement la même largeur : les deux boutons restent symétriques. */
.rond--saut {
	width: 40px;
	height: 40px;
	font: inherit;
	font-size: .8125rem;
	font-weight: 700;
	font-variant-numeric: tabular-nums;
	letter-spacing: -.02em;
	line-height: 1;
}

/* Précédent et suivant : plus discrets que les sauts, qui restent les commandes
   du geste courant. Le picto est plein, comme celui du bouton de lecture. */
.rond--enchainer { width: 32px; height: 32px; border-color: transparent; color: var(--discret); }
.rond--enchainer svg { width: 15px; height: 15px; fill: currentColor; stroke: currentColor; }
.rond--enchainer:hover { color: var(--texte); }

/* Grisé plutôt que masqué : un bouton qui disparaît déplace ses voisins sous le
   doigt, et la place qu'il occupe dit qu'il pourra resservir. */
.rond:disabled { opacity: .3; cursor: default; }
.rond:disabled:hover { background: none; color: var(--discret); }

/* Masquage des pictogrammes alternés. L'attribut `hidden` n'existe pas sur
   SVGElement : le basculer en JavaScript n'a aucun effet. */
.cache { display: none !important; }

.lecteur-progression { display: flex; align-items: center; gap: .75rem; min-width: 0; }
.temps { font-size: .75rem; color: var(--discret); font-variant-numeric: tabular-nums; flex: 0 0 auto; }

.lecteur-outils { display: flex; align-items: center; gap: .375rem; }
.volume { display: flex; align-items: center; gap: .25rem; }

/* ------------------------------------------------------- Vitesse de lecture */

/* Deux boutons et la valeur entre eux. La valeur est elle-même un bouton : elle
   ramène à la normale d'un appui, ce qu'on cherche après avoir accéléré. */
.vitesse { display: flex; align-items: center; gap: .0625rem; }
.vitesse .outil { width: 28px; height: 28px; }
.vitesse .outil svg { width: 15px; height: 15px; }

.vitesse-valeur {
	min-width: 2.75rem;
	padding: .1875rem .25rem;
	background: none;
	border: none;
	border-radius: var(--rayon);
	color: var(--discret);
	font: inherit;
	font-size: .75rem;
	font-weight: 600;
	font-variant-numeric: tabular-nums;
	text-align: center;
	cursor: pointer;
}
.vitesse-valeur:hover { color: var(--texte); background: var(--surface-haute); }

/* Hors du pas normal, la valeur se signale : à 1,5× on aime savoir pourquoi la
   voix court, surtout en reprenant le lecteur le lendemain. */
.vitesse.actif .vitesse-valeur { color: var(--accent); }

/* Une radio arrive en temps réel : la vitesse n'a rien à y régler. */
.lecteur--direct .vitesse { display: none; }

/* ------------------------------------------------------- Minuterie de sommeil */

.minuterie { position: relative; display: flex; }

/* Temps restant dans l'angle du bouton : le même code visuel que le compteur de la
   file d'attente, deux boutons plus loin. Les minutes, puis les secondes sur la
   dernière — c'est là qu'on regarde vraiment. */
.minuterie-reste { font-size: .5625rem; }
.minuterie-reste[hidden] { display: none; }

/* Une minuterie armée passe à la couleur d'emphase, comme les autres outils actifs,
   mais sans le remplissage que `.outil.actif svg` leur donne : ce picto est un tracé
   — lune et cadran — et le remplir boucherait l'aiguille. */
#sommeil.actif svg { fill: none; }

/* Menu déroulant vers le haut : le lecteur est collé au bas de l'écran. */
.minuterie-menu {
	position: absolute;
	right: 0;
	bottom: calc(100% + .5rem);
	z-index: 50;
	min-width: 12.5rem;
	padding: .375rem;
	display: grid;
	gap: .0625rem;
	background: var(--surface-haute);
	border: 1px solid var(--bordure);
	border-radius: 10px;
	box-shadow: 0 12px 28px var(--ombre-forte);
}
.minuterie-menu[hidden] { display: none; }

.minuterie-menu button {
	padding: .5rem .75rem;
	background: none;
	border: none;
	border-radius: var(--rayon);
	color: var(--texte);
	font: inherit;
	font-size: .875rem;
	text-align: left;
	white-space: nowrap;
	cursor: pointer;
}

/* Le voile de la couleur d'emphase plutôt qu'un gris : sur ce fond déjà relevé, un
   survol gris ne se voit dans aucun des deux thèmes. */
.minuterie-menu button:hover { background: var(--accent-voile); color: var(--accent); }

.minuterie-arret { color: var(--erreur); }
.minuterie-arret[hidden] { display: none; }

/* Une radio n'a pas de fin : l'entrée n'a plus de sens en direct. */
.lecteur--direct .minuterie-fin { display: none; }

/* Piste : la réglette, les repères de chapitre et l'infobulle superposés. */
.piste { position: relative; flex: 1; min-width: 0; display: flex; align-items: center; }
.piste .barre { flex: 1; }

.reperes { position: absolute; inset: 0; pointer-events: none; }

.repere {
	position: absolute;
	top: 50%;
	width: 3px;
	height: 11px;
	margin-left: -1px;
	transform: translateY(-50%);
	border-radius: 1px;
	background: var(--texte);
	opacity: .6;
}

.bulle {
	position: absolute;
	bottom: 20px;
	transform: translateX(-50%);
	max-width: 22rem;
	padding: .3rem .6rem;
	border-radius: 6px;
	background: light-dark(#fff, #000);
	border: 1px solid var(--bordure);
	color: var(--texte);
	font-size: .75rem;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	pointer-events: none;
	z-index: 5;
}
.bulle[hidden] { display: none; }
.bulle .heure { color: var(--discret); margin-right: .4rem; font-variant-numeric: tabular-nums; }

/* Le chemin parcouru est coloré, ce qui reste est gris. Un dégradé à arrêt franc
   plutôt qu'un élément de plus : `input[type=range]` n'expose pas sa partie gauche
   de façon portable — Firefox a `::-moz-range-progress`, WebKit n'a rien
   d'équivalent —, et deux règles divergentes se seraient désaccordées au premier
   changement. `--avancement` est posé par dessinerProgression(), en CSSOM : la
   politique de sécurité du site interdit le style en ligne.

   La valeur par défaut vaut zéro : une réglette qu'aucun script n'a encore touchée
   est entièrement grise, comme avant. */
.barre {
	--avancement: 0%;
	flex: 1;
	min-width: 0;
	height: 4px;
	appearance: none;
	background: linear-gradient(
		to right,
		var(--accent) 0 var(--avancement),
		var(--bordure) var(--avancement) 100%
	);
	border-radius: 2px;
	cursor: pointer;
}

.barre--volume { flex: 0 0 80px; }

/* Aucun cadre au clic. Le repère de focus est réservé au clavier, et posé sur la
   poignée plutôt qu'autour de la piste, où il ferait une bande rouge disgracieuse. */
.barre:focus { outline: none; }
.barre:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 4px var(--accent-halo); }
.barre:focus-visible::-moz-range-thumb { box-shadow: 0 0 0 4px var(--accent-halo); }

.barre::-webkit-slider-thumb {
	appearance: none;
	width: 12px;
	height: 12px;
	border-radius: 50%;
	background: var(--plein);
	cursor: pointer;
}
.barre::-moz-range-thumb {
	width: 12px;
	height: 12px;
	border: none;
	border-radius: 50%;
	background: var(--plein);
	cursor: pointer;
}

audio { display: none; }

/* ------------------------------------------------------------------- Étroit */

/* Le bouton burger et le voile n'existent qu'en dessous de 900 px. */
.burger { display: none; }

.voile {
	position: fixed;
	inset: 0;
	z-index: 25;
	background: var(--voile-fond);
	opacity: 0;
	visibility: hidden;
	transition: opacity .2s ease, visibility .2s ease;
}

@media (max-width: 900px) {
	/* Une seule colonne, et le lecteur seul en bas : plus de rangée d'icônes
	   coincée entre le contenu et lui, la navigation passe par le tiroir. */
	.application { grid-template-columns: 1fr; padding-bottom: var(--hauteur-lecteur); }
	.colonne { grid-column: 1; }

	.laterale {
		width: 264px;
		max-width: 82vw;
		/* Le lecteur se replie sur deux lignes en dessous de 900 px : un peu plus
		   de marge que la hauteur nominale pour que le tiroir le dégage vraiment. */
		bottom: calc(var(--hauteur-lecteur) + 1rem);
		z-index: 30;
		transform: translateX(-100%);
		transition: transform .22s ease;
		border-right: 1px solid var(--bordure);
	}

	.menu a { padding: .7rem .75rem; } /* cibles tactiles un peu plus hautes */

	/* Au doigt, le picto du thème mérite les 40 px des autres cibles du tiroir. */
	.bascule-theme { width: 40px; height: 40px; }

	body.menu-ouvert .laterale { transform: none; }
	body.menu-ouvert .voile { opacity: 1; visibility: visible; }

	.burger {
		display: grid;
		place-items: center;
		width: 38px;
		height: 38px;
		flex: 0 0 38px;
		background: none;
		border: none;
		border-radius: 50%;
		color: var(--texte);
		cursor: pointer;
	}
	.burger svg { width: 22px; height: 22px; }

	/* Quatre pochettes par ligne, quelle que soit la largeur : demandé pour voir
	   plus de podcasts d'un coup d'œil, au prix de vignettes plus petites. */
	.grille { grid-template-columns: repeat(4, 1fr); gap: .5rem; }

	/* Trois pour les albums : une pochette de podcast se reconnaît à sa forme, un
	   album se lit — il faut la place du titre et de l'artiste sous la case. */
	.grille--albums { grid-template-columns: repeat(3, 1fr); }

	/* Trop de colonnes pour la largeur d'un téléphone : le disque s'efface, le nom
	   de l'artiste dit déjà l'essentiel et la fiche de l'album est à un clic. */
	.morceau-album { display: none; }

	.haut { padding: .625rem 1rem; gap: .5rem; }
	.fleches { display: none; } /* le geste de retour du navigateur suffit sur mobile */
	.vue { padding: 0 1rem 2rem; }
	.recherche { width: auto; flex: 1; margin-left: 0; }

	.lecteur {
		grid-template-columns: 1fr auto;
		grid-template-rows: auto auto;
		gap: .375rem 1rem;
		height: auto;
		padding: .625rem 1rem calc(.625rem + env(safe-area-inset-bottom));
	}
	/* La vitesse et la minuterie gardent leur place au bout de la seconde ligne :
	   c'est en marchant et au lit, donc sur un téléphone, qu'on s'en sert. Le volume
	   se règle par les touches de l'appareil, la file a son entrée dans le menu et le
	   favori vit sur la fiche — ces trois-là s'effacent. */
	.lecteur-progression { grid-column: 1; grid-row: 2; }
	.lecteur-outils { grid-column: 2; grid-row: 2; justify-content: flex-end; }
	.lecteur-outils > *:not(.minuterie):not(.vitesse) { display: none; }

	/* Cinq boutons ne tiennent pas sur la première ligne d'un téléphone : la
	   rangée en garde trois, et c'est le mode qui décide lesquels. Sur un épisode
	   on veut −10 et +30, les commandes du geste courant ; sur un album on veut
	   passer au morceau suivant, et reculer de dix secondes dans une chanson n'a
	   guère d'usage. « Suivant » sur la file d'attente reste donc au grand écran. */
	.rond--enchainer { display: none; }
	.lecteur--musique .rond--enchainer { display: grid; }
	.lecteur--musique .rond--saut { display: none; }

	/* Resserré : à trois commandes sur la ligne, chaque pixel repris va à la
	   réglette, qui n'a plus que le reste. */
	.vitesse .outil { width: 24px; height: 24px; }
	.vitesse-valeur { min-width: 2.25rem; padding: .1875rem .125rem; }
	.lecteur-pochette { width: 48px; height: 48px; }
	.lecteur--direct .lecteur-pochette-lien { width: 48px; height: 48px; }
	.lecteur--direct .lecteur-pochette-lien::after { width: 24px; height: 24px; }
}

@media (prefers-reduced-motion: reduce) {
	.laterale, .voile, .laterale-defilement, .fleche-defilement { transition: none; }
	.repli > summary::before { transition: none; }
}
