/* =============================================================================
   Podcast Manager — feuille de style du site public

   Cette feuille ne déclare AUCUNE couleur, aucune police, aucun rayon : elle
   consomme les jetons de tokens.css, chargé avant elle par tous les points
   d'entrée. Le site public suit donc prefers-color-scheme, sans réglage ni
   cookie — ces pages tournent sous APP_SKIP_SESSION et ne doivent pas ouvrir de
   session pour un choix d'apparence.

   Les teintes translucides d'origine supposaient toutes un fond sombre. Le
   lecteur collant en concentrait l'essentiel : un rgba(255,255,255,0.09) y
   était un voile clair sur du charbon, et disparaît sur du blanc. Chacune est
   devenue un jeton qui bascule avec le thème, choisi d'après son RÔLE — fond,
   filet ou texte — et non d'après son opacité seule.

   Un test le vérifie : tests/styles.php refuse tout littéral de couleur ici.
   ============================================================================= */

/* Garde-fou : aucune image ne doit déborder de son conteneur, même si une règle
   spécifique manque ou si le navigateur sert une version périmée de ce fichier.
   Sans cela, une pochette de 3000 px s'affiche en taille réelle. */
img { max-width: 100%; height: auto; }
* { box-sizing: border-box; }

/* Sans cette ligne, les contrôles natifs (menu déroulant ouvert, barre de
   défilement) restent rendus en clair par le navigateur même quand le reste de
   la page suit un système réglé en sombre : le popup d'un <select> apparaîtrait
   alors en clair au-dessus d'une page sombre. "light dark" laisse le
   navigateur choisir lui-même, exactement comme prefers-color-scheme. */
html { color-scheme: light dark; }

body {
    margin: 0;
    background: var(--paper);
    background-image:
        radial-gradient(circle at 15% 0%, var(--signal-wash), transparent 45%),
        radial-gradient(circle at 90% 30%, var(--good-wash), transparent 40%);
    color: var(--ink);
    font-family: var(--f-body);
    line-height: 1.6;
    min-height: 100vh;
}

a { color: var(--signal); }

/* Lien d'évitement : hors de l'écran tant qu'il n'a pas le focus, il apparaît
   à la première tabulation. On ne l'escamote PAS avec display:none, qui le
   retirerait aussi de l'ordre de tabulation — il ne servirait alors à rien. */
.skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 300;
    padding: 10px 16px;
    background: var(--signal);
    color: var(--signal-ink);
    font-weight: 600;
    border-radius: 0 0 var(--r) 0;
    text-decoration: none;
}
.skip-link:focus {
    left: 0;
}

.wrap { max-width: 780px; margin: 0 auto; padding: 48px 20px 120px; }
/* Largeur élargie pour les pages en grille : à 780 px, deux colonnes de cartes
   seraient trop serrées pour que la pochette gagne en présence. */
.wrap--wide { max-width: 1060px; }

.hero {
    display: flex;
    gap: 26px;
    align-items: flex-start;
    margin-bottom: 40px;
    flex-wrap: wrap;
}
.hero img {
    width: 168px; height: 168px;
    border-radius: var(--r);
    object-fit: cover;
    background: var(--sheet);
    border: 1px solid var(--rule);
    flex-shrink: 0;
}
.hero .meta { flex: 1; min-width: 240px; }
.hero h1 {
    font-family: var(--f-display);
    font-size: 2rem;
    margin: 0 0 8px;
    letter-spacing: -0.01em;
}
.hero .subtitle { color: var(--muted); margin: 0 0 14px; font-size: 1.02rem; }
.hero .description { color: var(--muted); font-size: 0.92rem; max-width: 56ch; }

.platform-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 18px; }
.platform-pill {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 7px 14px;
    border-radius: 999px;
    background: var(--raised);
    border: 1px solid var(--rule-strong);
    color: var(--ink);
    font-size: 0.82rem;
    font-weight: 500;
    text-decoration: none;
    transition: background-color 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
/* Un fond, pas un filtre : brightness() éclaircit, et sur une page claire cela
   pousse la pastille vers le blanc de la feuille — le survol disparaît. */
.platform-pill:hover { background: var(--signal-soft); border-color: var(--signal-line); text-decoration: none; }
.platform-pill.rss { background: var(--signal); color: var(--signal-ink); border-color: var(--signal); }
/* Après .rss, sinon la variante pleine gagnerait l'ordre du fichier et la
   pastille RSS serait la seule à ne rien faire au survol. */
.platform-pill.rss:hover { background: var(--signal-ink); color: var(--signal); border-color: var(--signal); }

.section-title {
    font-family: var(--f-display);
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--faint);
    margin: 36px 0 16px;
}

/* L'ancien bloc .episode-card a été retiré : son balisage interne
   (.row/.info/.tag/.excerpt) n'existe plus depuis que les listes
   passent par includes/episode-card.php. Les styles de la carte sont
   désormais regroupés plus bas, dans la section « Architecture ».  */


.episode-detail {
    background: var(--sheet);
    border: 1px solid var(--rule);
    border-radius: var(--r);
    padding: 28px 26px;
}
.episode-detail .tag { font-family: var(--f-mono); font-size: 0.78rem; color: var(--faint); margin-bottom: 10px; }
.episode-detail h1 { font-family: var(--f-display); font-size: 1.6rem; margin: 0 0 16px; }
.episode-detail audio { width: 100%; margin-bottom: 22px; }
.episode-detail .content { color: var(--muted); font-size: 0.95rem; }
.episode-detail .content p { margin: 0 0 1em; }

/* Page d'épisode : deux colonnes au-delà de 860px, une colonne en dessous.
   L'ordre du DOM suit l'ordre visuel des deux colonnes — aucun grid-area,
   voir la contrainte globale du lot sur l'ordre de lecture. */
.episode-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 300px;
    gap: 28px;
    align-items: start;
}
/* Un enfant de grille garde min-width: auto par defaut : sous 860px, la
   colonne unique ne peut alors pas retrecir sous le contenu le plus large
   (le lecteur, une pastille de plateforme non coupee), et deborde. */
.episode-layout > * {
    min-width: 0;
}
.episode-side {
    display: flex;
    flex-direction: column;
    gap: 20px;
}
.episode-side-block {
    background: var(--sheet);
    border: 1px solid var(--rule);
    border-radius: var(--r);
    padding: 18px 20px;
}
/* .section-title porte un margin-top pensé pour une page entière ; en premier
   enfant d'une carte déjà paddée, il ne fusionne pas avec le padding et
   ouvre un vide au-dessus du titre. */
.episode-side-block .section-title:first-child {
    margin-top: 0;
}
.platform-row--stacked {
    flex-direction: column;
    align-items: stretch;
}
.platform-row--stacked .platform-pill {
    justify-content: flex-start;
}
/* Le margin-top de .platform-row s'ajoute ici au margin-bottom du titre
   au-dessus : dans une carte de 18px de padding, l'écart devient disproportionné. */
.episode-side-block .platform-row {
    margin-top: 0;
}
.guest-card {
    display: flex;
    align-items: center;
    gap: 12px;
    text-decoration: none;
    color: inherit;
}
.guest-card + .guest-card { margin-top: 14px; }
.guest-card-photo {
    width: 44px;
    height: 44px;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
    background: var(--sunk);
}
.guest-card-photo--empty { display: block; }
.guest-card-name { display: block; font-weight: 600; font-size: 0.92rem; }
.guest-card-role { display: block; color: var(--muted); font-size: 0.82rem; }

@media (max-width: 860px) {
    .episode-layout { grid-template-columns: 1fr; }
}

.back-link { display: inline-block; margin-bottom: 24px; color: var(--muted); font-size: 0.88rem; text-decoration: none; }
.back-link:hover { color: var(--signal); }
.chapters { margin: 20px 0 24px; }
.chapters-title { font-size: 1rem; margin: 0 0 10px; }
.chapters ol { list-style: none; margin: 0; padding: 0; }
.chapters li { margin: 0; }
.chapter-jump {
    display: flex;
    align-items: baseline;
    gap: 12px;
    width: 100%;
    text-align: left;
    background: none;
    border: none;
    border-radius: var(--r);
    padding: 8px 10px;
    cursor: pointer;
    color: inherit;
    font: inherit;
    transition: background 0.15s;
}
.chapter-jump:hover { background: var(--signal-soft); }
.chapter-jump.active { background: var(--signal-line); }
.chapter-time { color: var(--signal); font-size: 0.85rem; min-width: 52px; }
.chapter-title { flex: 1; }

/* Masqués par défaut : sans JavaScript, ces boutons ne font rien (le clic
   n'est jamais câblé) — les afficher inertes serait une affordance morte.
   C'est le script qui les révèle en posant is-js sur le conteneur, juste
   avant de câbler le clic (voir episode.php, $extraBody). */
.companion-tabs { display: none; gap: 4px; margin-bottom: 14px; }
[data-episode-companion].is-js .companion-tabs { display: flex; }
.companion-tab {
    padding: 8px 16px;
    border: 1px solid var(--rule);
    background: var(--sheet);
    color: var(--muted);
    border-radius: var(--r);
    cursor: pointer;
    font: inherit;
}
.companion-tab.is-active {
    background: var(--signal-soft);
    color: var(--ink);
    border-color: var(--signal-line);
}
/* Visibles par défaut : sans JavaScript, les deux vues restent lisibles.
   C'est SEULEMENT une fois le script exécuté, via la classe is-js posée sur
   le conteneur, que la vue inactive se masque. Vérifié sur le dépôt : aucun
   mécanisme no-js/is-js n'existe ailleurs (includes/public-header.php n'en
   pose aucun) — ne pas en supposer un, cette règle est autosuffisante. */
.companion-pane { display: block; }
[data-episode-companion].is-js .companion-pane:not(.is-active) { display: none; }
/* Titre visible de secours (sans JS) pour chaque vue, quand .companion-tabs
   est masqué : une fois l'onglet actif révélé par le script, son libellé
   porte déjà cette information, d'où le retrait ici. */
.companion-pane-title { font-size: 1rem; margin: 0 0 10px; }
[data-episode-companion].is-js .companion-pane-title { display: none; }

.transcript-search {
    margin-bottom: 18px;
}
.transcript-search-form {
    display: grid;
    gap: 8px;
}
.transcript-search-row {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
}
.transcript-search-input {
    flex: 1 1 320px;
    min-width: 0;
    border: 1px solid var(--rule);
    border-radius: var(--r);
    padding: 10px 12px;
    background: var(--sheet);
    color: var(--ink);
    font: inherit;
}
.transcript-search-button,
.transcript-search-clear {
    border: 1px solid var(--rule);
    border-radius: var(--r);
    background: var(--sheet);
    color: var(--ink);
    padding: 10px 14px;
    cursor: pointer;
    font: inherit;
}
.transcript-search-button:hover,
.transcript-search-clear:hover {
    border-color: var(--signal);
    color: var(--signal);
}
.transcript-search-results {
    color: var(--faint);
    font-size: 0.88rem;
    margin: 0;
}
.local-playlist-manager {
    margin-top: 18px;
    padding: 14px;
    border: 1px solid var(--rule);
    border-radius: var(--r);
    /* 0,05 tombait juste sous la plage de --signal-soft ; c'était bien un lavis
       de fond de bloc, et le jeton le plus discret de la famille lui va. */
    background: var(--signal-soft);
}
.local-playlist-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(180px, 240px);
    gap: 10px;
    align-items: center;
}
.local-playlist-row .field-input,
.local-playlist-row .field-select {
    width: 100%;
    min-width: 0;
    border: 1px solid var(--rule);
    border-radius: var(--r);
    padding: 10px 12px;
    background: var(--sheet);
    color: var(--ink);
    font: inherit;
}
.local-playlist-row .field-input::placeholder {
    color: var(--faint);
}
.local-playlist-manager .field-label {
    display: block;
    font-size: 0.9rem;
    color: var(--faint);
    margin-bottom: 8px;
}
/* .transcript-body (transcript.php, page dédiée) et .transcript-text
   (episode.php, vue compagnon) affichent le même contenu (voir
   TranscriptFormatter::renderHtml) : même style, seule la portée change. */
.transcript-body, .transcript-text { line-height: 1.75; color: var(--ink); }
.transcript-body p, .transcript-text p { margin: 0 0 1em; }
.transcript-body a, .transcript-text a { color: var(--signal); }
.transcript-paragraph {
    cursor: pointer;
    border-left: 2px solid transparent;
    padding-left: 12px;
    transition: background 0.15s, border-color 0.15s;
}
.transcript-paragraph.match {
    background: var(--signal-soft);
}
/* Même convention que .chapter-jump plus haut : --signal-soft au survol et pour
   les résultats trouvés, --signal-line pour le paragraphe en cours de lecture.
   Sans cet écart, pendant une recherche, tous les paragraphes trouvés et celui
   qu'on écoute avaient exactement le même fond. */
.transcript-paragraph.active {
    border-color: var(--signal);
    background: var(--signal-line);
}
.transcript-paragraph.selected {
    box-shadow: inset 0 0 0 1px var(--signal);
}
.transcript-paragraph:hover {
    background: var(--signal-soft);
    border-color: var(--signal);
}
.transcript-paragraph:focus-visible {
    outline: 2px dashed var(--signal);
    outline-offset: 4px;
}
mark {
    background: var(--signal-line);
    color: inherit;
}

footer.site-footer {
    text-align: center;
    color: var(--faint);
    font-size: 0.78rem;
    margin-top: 60px;
}
footer.site-footer a { color: var(--faint); }

.btn,
.btn-sm,
.btn-primary,
.btn-danger,
.btn-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    border: 1px solid transparent;
    border-radius: 999px;
    padding: 10px 16px;
    font: inherit;
    text-decoration: none;
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease, transform 0.15s ease;
}

/* brightness(1.05) était écrit pour un fond sombre ; sur la page claire, il
   éclaircit vers le blanc et ne signale plus rien. Chaque famille de bouton
   porte donc son propre fond, en jeton — sur un fond clair on ASSOMBRIT ou on
   inverse, on n'éclaircit pas. */
.btn:hover,
.btn-sm:hover,
.btn-link:hover {
    background: var(--signal-soft);
    border-color: var(--signal-line);
    color: var(--ink);
}
/* Inversion pour les deux boutons pleins : il n'existe pas de teinte plus
   soutenue que --signal ni que --live, et l'inversion garde un contraste franc
   dans les deux thèmes (7,9:1 et 6,3:1 au pire). */
.btn-primary:hover {
    background: var(--signal-ink);
    color: var(--signal);
    border-color: var(--signal);
}
/* .btn-danger part de --live-soft : le filtre l'y délavait jusqu'à 3,54:1
   sous son propre texte rouge. Le fond plein règle le contraste au lieu de le
   grignoter. */
.btn-danger:hover {
    background: var(--live);
    color: var(--sheet);
    border-color: var(--live);
}

.btn:active,
.btn-sm:active,
.btn-primary:active,
.btn-danger:active,
.btn-link:active {
    transform: translateY(1px);
}

.btn-primary {
    background: var(--signal);
    color: var(--signal-ink);
    border-color: transparent;
}

.btn-danger {
    background: var(--live-soft);
    color: var(--live);
    border-color: var(--live-line);
}

.btn-sm {
    padding: 8px 12px;
    font-size: 0.83rem;
}

.btn-link {
    background: transparent;
    color: var(--ink);
    border-color: var(--signal-line);
}

.empty-state { text-align: center; padding: 60px 20px; color: var(--muted); }

@media (max-width: 560px) {
    .hero { flex-direction: column; align-items: center; text-align: center; }
    .hero .description { max-width: none; }
    /* Ne vise que la variante en ligne : .platform-row--stacked a un axe
       principal vertical, où justify-content ne produit aucun effet. */
    .platform-row:not(.platform-row--stacked) { justify-content: center; }
}

/* =============================================================================
   Architecture des pages publiques (v1.19.0)
   Navigation, listes d'épisodes, pagination, recherche, page « À propos ».
   ============================================================================= */

/* --- Navigation du site --- */
.site-nav {
    display: flex; align-items: center; justify-content: space-between;
    gap: 16px; flex-wrap: wrap;
    padding: 14px 0 18px; margin-bottom: 24px;
    border-bottom: 1px solid var(--rule);
}
.site-nav-brand { display: flex; align-items: center; gap: 10px; font-weight: 600; text-decoration: none; color: inherit; }
.site-nav-brand img { width: 34px; height: 34px; border-radius: var(--r); object-fit: cover; }
.site-nav-links { display: flex; gap: 4px; flex-wrap: wrap; }
.site-nav-links a {
    padding: 6px 12px; border-radius: 999px; text-decoration: none;
    color: var(--muted); font-size: 0.92rem; transition: background .15s, color .15s;
}
.site-nav-links a:hover { background: var(--signal-soft); color: var(--ink); }
.site-nav-links a.active { background: var(--signal-soft); color: var(--signal); font-weight: 600; }

/* --- Titres de page --- */
.page-title { margin: 0 0 6px; }
.page-lede { color: var(--muted); margin: 0 0 22px; }

/* --- Liste et cartes d'épisode --- */
.episode-list { display: flex; flex-direction: column; gap: 18px; }

/* Grille de découverte (accueil d'une émission).
   Ailleurs — archive, recherche, sujet, invité — la liste verticale reste
   préférable : on y BALAYE du texte, alors qu'ici on choisit à la pochette.
   auto-fill plutôt qu'un nombre de colonnes fixe : la grille se réduit d'
   elle-même à une colonne sur mobile, sans point de rupture à maintenir. */
.episode-list--poster {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(272px, 1fr));
    gap: 18px;
}
/* Dans la colonne latérale de la page épisode (300px, jamais assez pour
   272px + le rembourrage de la carte), la grille de découverte déborderait :
   une seule colonne y est la seule mise en page possible. */
.episode-side-block .episode-list--poster {
    grid-template-columns: 1fr;
}
/* La carte reste le même balisage : seule sa direction change, la pochette
   passant donc au-dessus du texte sans variante de gabarit à maintenir. */
.episode-list--poster .episode-card { flex-direction: column; gap: 12px; }
.episode-list--poster .episode-card-cover { flex: none; width: 100%; }
.episode-list--poster .episode-card-cover img {
    width: 100%;
    height: auto;
    /* Rapport imposé : la place de la pochette est réservée avant même son
       chargement, donc la grille ne sursaute pas quand les images arrivent. */
    aspect-ratio: 1 / 1;
}
.episode-list--poster .episode-card-body { width: 100%; }
.episode-list--poster .episode-card-body h3 { font-size: 1rem; }
/* Carte étroite : le libellé du bouton suffit, la durée reste lisible à côté. */
.episode-list--poster .episode-play { padding: 0 14px 0 12px; }
/* Aux largeurs de colonne les plus faibles, la place restante peut descendre
   sous ce qu'exigent 24 barres. On les laisse alors s'amincir plutôt que de
   les laisser déborder : un débordement masqué couperait la FIN de la forme
   d'onde, donnant à lire un épisode plus court qu'il n'est. */
.episode-list--poster .waveform { gap: 1px; }
.episode-list--poster .waveform span { min-width: 1px; }
.episode-card {
    display: flex; gap: 16px; align-items: flex-start;
    padding: 16px; border: 1px solid var(--rule); border-radius: var(--r);
    transition: border-color .15s;
}
/* Les bordures de SURVOL prennent --signal plein, comme .podcast-card:hover et
   .offer-card:hover plus bas : --signal-line ne change que la teinte, à
   luminance quasi égale à --rule, alors que ces bordures portent une
   affordance — elles disent « ceci réagit au clic ». --signal-line reste pour
   les décors et les états qui ont déjà un autre porteur. */
.episode-card:hover { border-color: var(--signal); }
.episode-card-cover { flex: 0 0 96px; }
.episode-card-cover img { width: 96px; height: 96px; border-radius: var(--r); object-fit: cover; display: block; }
.episode-card-body { flex: 1; min-width: 0; }
.episode-card-body h3 { margin: 4px 0 6px; font-size: 1.05rem; }
.episode-card-body h3 a { text-decoration: none; }
.episode-card-body h3 a:hover { text-decoration: underline; }
.episode-card-meta {
    display: flex; gap: 10px; flex-wrap: wrap; align-items: center;
    font-size: 0.78rem; color: var(--faint);
}
.episode-card-marker { color: var(--signal); font-weight: 600; }
.episode-card-badge {
    background: var(--signal-soft); color: var(--signal);
    border-radius: 999px; padding: 2px 9px; font-weight: 600;
}
.episode-card-explicit {
    border: 1px solid currentColor; border-radius: var(--r);
    padding: 0 4px; font-size: 0.68rem; font-weight: 700;
}
.episode-card-subtitle, .episode-card-snippet { margin: 0 0 10px; color: var(--muted); font-size: 0.9rem; line-height: 1.6; }
/* Repli sans JavaScript uniquement (voir le <noscript> de la carte). */
.episode-card audio { width: 100%; margin-top: 4px; }
.episode-card-transcript { display: inline-block; margin-top: 8px; font-size: 0.85rem; }

/* --- Bouton « Écouter » d'une carte d'épisode ---
   Remplace le lecteur natif du navigateur, qui jurait sur fond sombre et
   différait d'un navigateur à l'autre. Alimente le lecteur du site. */
.episode-card-listen {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-top: 10px;
}
.episode-play {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    /* 44 px de haut : cible tactile confortable, sans quoi le bouton est
       difficile à viser au pouce sur mobile. */
    min-height: 44px;
    padding: 0 18px 0 14px;
    border-radius: 999px;
    border: 1px solid var(--rule-strong);
    background: var(--raised);
    color: var(--ink);
    font-family: inherit;
    font-size: 0.86rem;
    font-weight: 500;
    cursor: pointer;
    transition: background-color .2s ease, border-color .2s ease, transform .1s ease;
}
/* Survol : le FOND reste un palier de surface — le bouton au repos est déjà sur
   --raised, --sunk le décale sans changer de famille. La BORDURE, elle, prend
   --signal plein : c'est l'action principale des cartes, sur une cible de
   44 px, et cela l'aligne sur le :focus-visible de la ligne suivante. */
.episode-play:hover { background: var(--sunk); border-color: var(--signal); }
.episode-play:focus-visible { outline: 2px solid var(--signal); outline-offset: 2px; }
.episode-play:active { transform: scale(.97); }

/* L'épisode en cours se repère d'un coup d'œil dans une longue liste. */
.episode-play.is-current {
    background: var(--signal-soft);
    border-color: var(--signal-line);
    color: var(--signal);
}

.episode-play-icon {
    display: inline-flex;
    width: 22px; height: 22px;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: var(--signal);
    color: var(--signal-ink);
    flex-shrink: 0;
}
.episode-play-icon svg { width: 14px; height: 14px; display: block; }
/* Une seule des deux icônes est visible : jouer, ou mettre en pause. */
.episode-play .episode-play-icon-pause { display: none; }
.episode-play.is-playing .episode-play-icon-play { display: none; }
.episode-play.is-playing .episode-play-icon-pause { display: block; }

/* Mise en mémoire tampon : l'icône cède la place à un anneau qui tourne.
   Un épisode pèse souvent des dizaines de mégaoctets, et sans ce repère le clic
   semblait sans effet le temps du téléchargement. */
.episode-play.is-loading .episode-play-icon svg { display: none; }
.episode-play.is-loading .episode-play-icon {
    /* Anneau ambre sur fond transparent : le cercle plein disparaissant, une
       teinte sombre s'y confondrait avec le bouton. */
    border: 2px solid var(--signal);
    border-top-color: transparent;
    background: transparent;
    animation: episode-play-spin .7s linear infinite;
}
@keyframes episode-play-spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
    /* Rotation supprimée, mais l'anneau reste : le repère visuel ne doit pas
       disparaître pour qui a désactivé les animations — c'est une information,
       pas une décoration. Le libellé « Chargement… » l'accompagne de toute façon. */
    .episode-play.is-loading .episode-play-icon { animation: none; }
}

.episode-play-time {
    color: var(--faint);
    font-family: var(--f-mono);
    font-size: 0.78rem;
    font-variant-numeric: tabular-nums;
}

/* --- Reprise d'écoute ---
   Liseré d'avancement sous la ligne d'écoute, posé par le lecteur à partir des
   positions gardées dans le navigateur. Il double une information déjà donnée
   en toutes lettres par le bouton (« Reprendre à 18:24 ») : la couleur n'est
   donc jamais le seul porteur du sens. */
.episode-progress {
    display: block;
    height: 3px;
    margin-top: 8px;
    border-radius: var(--r);
    background: var(--rule);
    overflow: hidden;
}
.episode-progress > span {
    display: block;
    height: 100%;
    border-radius: var(--r);
    background: var(--signal);
}
/* Épisode terminé : teinte apaisée, pour le distinguer d'une écoute en cours
   sans le faire ressortir comme une action à reprendre. */
.episode-progress.is-done > span { background: var(--good); }

/* --- Forme d'onde ---
   Barres proportionnelles à l'amplitude réelle de l'épisode (voir
   src/Waveform.php). Purement décorative pour un lecteur d'écran : la durée est
   déjà annoncée à côté, en texte. */
.waveform {
    display: flex;
    align-items: center;
    gap: 2px;
    height: 26px;
    flex: 1;
    min-width: 0;
    overflow: hidden;
}
.waveform span {
    flex: 1 1 0;
    min-width: 2px;
    max-width: 4px;
    border-radius: var(--r);
    background: var(--rule-strong);
    transition: background-color .2s ease;
}
/* L'épisode en cours colore sa forme d'onde : le repère se lit d'un coup d'œil
   dans une liste, sans avoir à chercher quel bouton est actif. La classe est
   posée par le lecteur sur le conteneur (voir sticky-player.js). */
.episode-card-listen.is-current .waveform span { background: var(--signal-line); }

@media (prefers-reduced-motion: reduce) {
    .waveform span { transition: none; }
}

@media (max-width: 560px) {
    /* Sur mobile, la ligne d'écoute n'a pas la largeur d'y tenir le bouton, la
       forme d'onde ET la durée. Plutôt que de masquer le relief — c'est
       pourtant là que se fait l'essentiel de l'écoute — on le renvoie sur sa
       propre ligne, où il dispose de toute la largeur de la carte. */
    .episode-card-listen { flex-wrap: wrap; }
    .waveform {
        order: 3;
        flex: 1 0 100%;
        height: 22px;
        margin-top: 2px;
    }
}

@media (prefers-reduced-motion: reduce) {
    .episode-play { transition: none; }
    .episode-play:active { transform: none; }
}

@media (max-width: 560px) {
    .episode-card { flex-direction: column; }
    .episode-card-cover, .episode-card-cover img { width: 100%; height: auto; flex: none; }
}

/* --- Vote « pour / contre » d'un épisode --- */
/* Colonne compacte façon Reddit : les deux flèches encadrent le score, qui est
   la seule information réellement lue. Le score reste lisible même à 0, car un
   compteur absent laisserait croire que la fonction ne marche pas. */
.vote-bar {
    display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
    margin: 22px 0 6px; padding: 14px 16px;
    border: 1px solid var(--rule); border-radius: var(--r);
}
.vote-bar-title { margin: 0; font-weight: 600; font-size: 0.95rem; }
.vote-widget { display: inline-flex; align-items: center; gap: 6px; }
.vote-btn {
    display: inline-flex; align-items: center; justify-content: center;
    width: 34px; height: 34px; padding: 0;
    border: 1px solid var(--rule); border-radius: var(--r);
    background: transparent; color: var(--muted); cursor: pointer;
    transition: color .15s, border-color .15s, background .15s;
}
.vote-btn svg { width: 18px; height: 18px; fill: currentColor; }
/* --text n'a jamais existé dans aucun thème : la couleur de survol tombait donc
   sur l'héritage. C'est bien la couleur de texte appuyée qui était visée. */
.vote-btn:hover { border-color: var(--signal); color: var(--ink); }
.vote-up.is-active { color: var(--good); border-color: var(--good-line); background: var(--good-soft); }
.vote-down.is-active { color: var(--live); border-color: var(--live-line); background: var(--live-soft); }
.vote-score { min-width: 2.4em; text-align: center; font-weight: 700; font-variant-numeric: tabular-nums; }
.vote-hint { margin: 0; font-size: 0.82rem; color: var(--faint); }

/* --- Épisodes recommandés ---
   Un enfant flex de .episode-side comme les deux autres cartes de la
   colonne : l'espacement vient de leur gap commun, pas d'une marge propre. */

/* Réservé aux lecteurs d'écran : le libellé d'un bouton purement graphique doit
   exister dans le document, pas seulement dans un title. */
.sr-only {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* --- Filtre par saison --- */
.season-filter { display: flex; gap: 6px; flex-wrap: wrap; margin: 0 0 22px; }
.season-filter a {
    padding: 5px 12px; border-radius: 999px; border: 1px solid var(--rule);
    text-decoration: none; color: var(--muted); font-size: 0.88rem;
}
.season-filter a:hover { border-color: var(--signal); }
.season-filter a.active { background: var(--signal-soft); border-color: transparent; color: var(--signal); font-weight: 600; }
.season-count { opacity: .6; font-size: 0.8rem; margin-left: 3px; }

/* --- Pagination --- */
.pagination {
    display: flex; align-items: center; justify-content: space-between;
    gap: 12px; flex-wrap: wrap; margin: 30px 0 10px;
}
.pagination-pages { display: flex; gap: 4px; align-items: center; }
.pagination-pages a, .pagination-pages .current {
    padding: 5px 11px; border-radius: var(--r); text-decoration: none; font-size: 0.9rem;
}
.pagination-pages a { color: var(--muted); }
.pagination-pages a:hover { background: var(--signal-soft); }
.pagination-pages .current { background: var(--signal-soft); color: var(--signal); font-weight: 600; }
.pagination-gap { color: var(--faint); padding: 0 2px; }
.pagination-step { text-decoration: none; font-size: 0.9rem; }
.pagination-step.disabled { color: var(--faint); opacity: .45; }

/* --- Navigation entre épisodes --- */
.episode-nav {
    display: grid; grid-template-columns: 1fr 1fr; gap: 14px;
    margin: 30px 0 10px;
}
.episode-nav a {
    display: flex; flex-direction: column; gap: 4px;
    padding: 14px; border: 1px solid var(--rule); border-radius: var(--r);
    text-decoration: none; transition: border-color .15s;
}
.episode-nav a:hover { border-color: var(--signal); }
.episode-nav-next { text-align: right; }
.episode-nav-label { font-size: 0.78rem; color: var(--signal); }
.episode-nav-title { color: var(--ink); font-size: 0.92rem; }
@media (max-width: 560px) { .episode-nav { grid-template-columns: 1fr; } }

/* --- Recherche --- */
.search-form { display: flex; gap: 8px; margin: 0 0 22px; flex-wrap: wrap; }
.search-form input[type=search] {
    flex: 1; min-width: 200px; padding: 10px 14px;
    border: 1px solid var(--rule); border-radius: var(--r);
    background: transparent; color: var(--ink); font: inherit;
}
.search-form input[type=search]:focus { outline: 2px solid var(--signal); outline-offset: 1px; }
.search-form button {
    padding: 10px 18px; border: none; border-radius: var(--r); cursor: pointer;
    background: var(--signal); color: var(--signal-ink); font: inherit; font-weight: 600;
}
.search-count { color: var(--faint); font-size: 0.88rem; margin: 0 0 16px; }
mark { background: var(--signal-line); color: inherit; padding: 0 2px; border-radius: var(--r); }

/* --- Page « À propos » --- */
.about-grid { display: grid; grid-template-columns: 200px 1fr; gap: 26px; align-items: start; margin-bottom: 34px; }
.about-cover { width: 100%; border-radius: var(--r); display: block; }
.about-facts { display: grid; grid-template-columns: auto 1fr; gap: 6px 16px; margin: 18px 0 0; font-size: 0.9rem; }
.about-facts dt { color: var(--faint); }
.about-facts dd { margin: 0; }
@media (max-width: 640px) { .about-grid { grid-template-columns: 1fr; } .about-cover { max-width: 220px; } }

/* --- Divers --- */
.see-all { margin: 26px 0 0; }
.btn-link {
    display: inline-block; padding: 9px 18px; border-radius: var(--r);
    border: 1px solid var(--rule); text-decoration: none; font-size: 0.92rem;
}
.btn-link:hover { border-color: var(--signal); }

/* --- Pictogrammes des plateformes d'écoute --- */
.platform-logo {
    width: 17px; height: 17px;
    /* Bornes DURES : un logo téléversé peut avoir des dimensions intrinsèques
       énormes (SVG en 1000 px, PNG haute résolution). width/height suffisent en
       théorie, mais max-* garantit qu'aucune surcharge ni cascade inattendue ne
       puisse le laisser s'étendre. */
    max-width: 17px; max-height: 17px;
    object-fit: contain;
    flex-shrink: 0;
    display: block;
}
/* Le SVG hérite de la couleur du texte : il s'adapte donc au fond de la
   pastille, y compris sur la variante accentuée (.platform-pill.rss). */
svg.platform-logo { color: currentColor; }
/* Variante image (logo officiel déposé dans assets/img/platforms/) : on la
   laisse dans ses couleurs d'origine, sans la teinter. */
img.platform-logo { object-fit: contain; }

/* --- Pages légales (confidentialité, mentions) --- */
.legal { max-width: 68ch; line-height: 1.75; }
.legal h2 {
    font-size: 1.05rem; margin: 32px 0 10px;
    padding-top: 18px; border-top: 1px solid var(--rule);
}
.legal h2:first-of-type { border-top: 0; padding-top: 0; margin-top: 20px; }
.legal p, .legal li { color: var(--muted); }
.legal ul { padding-left: 20px; }
.legal li { margin-bottom: 6px; }
.legal table { width: 100%; border-collapse: collapse; margin: 14px 0 6px; font-size: 0.88rem; }
.legal th, .legal td {
    text-align: left; vertical-align: top;
    padding: 9px 10px; border-bottom: 1px solid var(--rule);
}
.legal th { color: var(--ink); font-weight: 600; }
.legal td { color: var(--muted); }
@media (max-width: 620px) {
    .legal table, .legal thead, .legal tbody, .legal tr, .legal th, .legal td { display: block; }
    .legal thead { display: none; }
    .legal tr { border-bottom: 1px solid var(--rule-strong); padding: 8px 0; }
    .legal td { border: 0; padding: 3px 0; }
    .legal td::before { content: attr(data-label); display: block; color: var(--faint); font-size: 0.75rem; }
}

/* --- Blocs repliables (page d'épisode) --- */

/* Chapitres : <details> natif, stylé pour ne pas ressembler à un widget brut. */
.foldable > .foldable-summary {
    display: flex; align-items: center; gap: 10px;
    cursor: pointer; list-style: none;
    padding: 10px 0; margin: 0;
    user-select: none;
}
.foldable > .foldable-summary::-webkit-details-marker { display: none; }
/* Chevron dessiné en CSS, qui pivote à l'ouverture. */
.foldable > .foldable-summary::before {
    content: '';
    width: 8px; height: 8px;
    border-right: 2px solid var(--signal);
    border-bottom: 2px solid var(--signal);
    transform: rotate(-45deg);
    transition: transform .18s;
    flex: 0 0 auto;
}
.foldable[open] > .foldable-summary::before { transform: rotate(45deg); }
.foldable > .foldable-summary:hover .chapters-title { color: var(--signal); }
.foldable-count {
    font-size: 0.75rem; color: var(--faint);
    background: var(--sunk);
    padding: 1px 8px; border-radius: 999px;
}
.foldable .chapters-title { margin: 0; }

/* Description : hauteur limitée tant qu'elle n'est pas dépliée.
   Le texte reste entier dans le document — seul l'affichage est écourté. */
.content.is-clamped {
    max-height: 420px;
    overflow: hidden;
    position: relative;
}
/* Dégradé de fin, pour signaler que le texte continue. */
.content.is-clamped::after {
    content: '';
    position: absolute; left: 0; right: 0; bottom: 0;
    height: 90px;
    background: linear-gradient(to bottom, transparent, var(--paper));
    pointer-events: none;
}
.content.is-expanded { max-height: none; }

.foldable-more {
    display: inline-flex; align-items: center; gap: 6px;
    margin: 10px 0 4px;
    padding: 7px 16px;
    border: 1px solid var(--rule-strong);
    border-radius: 999px;
    background: transparent;
    color: var(--signal);
    font: inherit; font-size: 0.85rem;
    cursor: pointer;
}
.foldable-more:hover { border-color: var(--signal); }

/* --- Liseré de progression de navigation ---
   Les liens internes sont suivis en arrière-plan : le navigateur n'affiche donc
   pas sa propre barre de chargement. Ce liseré rétablit le repère. Il progresse
   sans jamais atteindre 100 % — la durée réelle est inconnue — puis se complète
   d'un coup à l'arrivée de la page. */
.page-progress {
    position: fixed;
    top: 0; left: 0;
    height: 2px;
    width: 0;
    background: var(--signal);
    z-index: 200;
    opacity: 0;
    pointer-events: none;
}
.page-progress.is-active {
    opacity: 1;
    /* Course longue et ralentissante : donne le sentiment d'un travail en
       cours sans mentir sur une échéance qu'on ne connaît pas. */
    width: 92%;
    transition: width 8s cubic-bezier(.05,.7,.2,1);
}
.page-progress.is-done {
    width: 100%;
    opacity: 0;
    transition: width .18s ease, opacity .35s ease .12s;
}

@media (prefers-reduced-motion: reduce) {
    /* On garde l'information, sans la course : le liseré s'affiche plein. */
    .page-progress.is-active { width: 100%; transition: none; }
    .page-progress.is-done { transition: opacity .2s ease; }
}

/* Sticky player en bas de page */
.sticky-player {
    position: fixed;
    width: min(720px, calc(100% - 16px));
    left: 50%;
    transform: translateX(-50%);
    bottom: 12px;
    z-index: 15;
    /* Le dégradé est conservé : ses deux arrêts n'étaient qu'un même ton, l'un
       légèrement plus sombre que l'autre. --sheet puis --raised garde ce modelé
       diagonal SANS faire atterrir l'arrêt final sur la couleur exacte de la
       page : avec --paper, le coin inférieur droit de la barre se confondait
       avec le fond en thème clair.
       La translucidité, elle, disparaît : aucun jeton ne peut la porter sans
       redevenir un blanc ou un noir figé. */
    background: linear-gradient(135deg, var(--sheet), var(--raised));
    /* Le contour d'un conteneur n'est pas un accent : --signal-line n'était
       qu'à 1,45:1 de la page. */
    border: 1px solid var(--rule-strong);
    border-radius: var(--r);
    padding: 9px 11px;
    /* Exception d'ombre assumée, pas un reste de la refonte en filets : ce
       bandeau est une VRAIE superposition (position fixe, au-dessus du
       contenu de page, tâche 5), et son halo est réglé en --scrim pour
       rester lisible en thème clair. Ne pas uniformiser sur var(--shadow),
       qui l'aplatirait. */
    box-shadow: 0 10px 28px var(--scrim);
    display: none;
    /* Plus de backdrop-filter : le fond est désormais opaque, donc le flou
       n'était plus visible — mais il forçait toujours une couche de composition
       et un instantané du fond à chaque peinture, sur un élément fixe présent
       sur toutes les pages publiques, mobile compris. */
    font-size: 13px;
}
.sticky-player.is-visible {
    display: block;
    animation: slideUp 0.3s cubic-bezier(0.2, 0.8, 0.2, 1) forwards;
}

@keyframes slideUp {
    from {
        opacity: 0;
        transform: translateX(-50%) translateY(12px);
    }
    to {
        opacity: 1;
        transform: translateX(-50%) translateY(0);
    }
}

/* Barre unique façon « pilule » : tous les contrôles sur une seule ligne */
#sticky-player {
    max-width: 640px !important;
    padding: 7px 12px 5px !important;
    border-radius: 999px !important;
    font-size: 12px;
}
.sticky-player__button svg { width: 17px; height: 17px; display: block; }
/* Ensure inline mode still takes full width when embedded */
.sticky-player.inline { width: 100% !important; transform: none !important; left: auto !important; }

.sticky-player__inner {
    width: 100%;
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}
.sticky-player__meta {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 1px;
}
.sticky-player__cover {
    width: 34px;
    height: 34px;
    border-radius: var(--r);
    object-fit: cover;
    background: var(--sheet);
    display: block;
    flex: 0 0 auto;
    box-shadow: 0 2px 8px var(--scrim);
}
/* Le titre défile horizontalement quand il dépasse : le lecteur est étroit,
   une ellipse cacherait justement la fin du nom d'épisode. */
.sticky-player__marquee {
    /* Valeurs de repli des deux variables du défilement, déclarées ICI plutôt
       qu'en repli de chaque var() : sticky-player.js les pose en style en ligne
       sur ce même élément, donc sa déclaration l'emporte. Avant que le script
       ne tourne — ou s'il ne tourne pas — le titre garde une durée valide. */
    --marquee-duration: 18s;
    --marquee-shift: 0px;
    overflow: hidden;
    min-width: 0;
    width: 100%;
}
.sticky-player__marquee-track {
    display: flex;
    width: max-content;
    gap: 40px;
    will-change: transform;
}
.sticky-player__marquee.is-scrolling {
    /* Dégradé sur les bords : le texte semble entrer et sortir de la barre.
       `black` n'est PAS une couleur de thème ici : dans un masque, seul le canal
       alpha compte, et noir opaque veut dire « montre ce qu'il y a dessous ».
       Y mettre un jeton casserait le fondu sans rien rendre plus lisible. */
    -webkit-mask-image: linear-gradient(90deg, transparent, black 12px, black calc(100% - 12px), transparent);
    mask-image: linear-gradient(90deg, transparent, black 12px, black calc(100% - 12px), transparent);
}
.sticky-player__marquee.is-scrolling .sticky-player__marquee-track {
    animation: stickyMarquee var(--marquee-duration) linear infinite;
}
.sticky-player:hover .sticky-player__marquee.is-scrolling .sticky-player__marquee-track {
    animation-play-state: paused;
}
@keyframes stickyMarquee {
    from { transform: translateX(0); }
    to { transform: translateX(calc(-1 * var(--marquee-shift))); }
}
@media (prefers-reduced-motion: reduce) {
    .sticky-player__marquee.is-scrolling .sticky-player__marquee-track {
        animation: none;
    }
}
.sticky-player__title {
    font-size: 0.84rem;
    white-space: nowrap;
    font-weight: 600;
    color: var(--ink);
    line-height: 1.3;
}
.sticky-player__status {
    color: var(--faint);
    font-size: 0.71rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    line-height: 1.2;
}
.sticky-player__controls {
    display: flex;
    align-items: center;
    gap: 2px;
    flex: 0 0 auto;
    flex-wrap: nowrap;
}
.sticky-player__group {
    display: flex;
    gap: 2px;
    align-items: center;
}
.sticky-player__button,
.sticky-player-share button {
    border: none;
    background: transparent;
    color: var(--ink);
    border-radius: 999px;
    padding: 5px;
    cursor: pointer;
    font: inherit;
    min-height: 30px;
    min-width: 30px;
    display: flex;
    align-items: center;
    justify-content: center;
    text-decoration: none;
    transition: background 0.2s ease, color 0.2s ease, transform 0.2s ease;
}
.sticky-player__button:hover,
.sticky-player__button:focus-visible {
    /* Voile de survol = un palier de SURFACE, pas un filet : --sunk, et non
       --rule, même si l'opacité d'origine tombait dans la plage des filets. */
    background: var(--sunk);
    color: var(--signal);
}
.sticky-player__button:active {
    transform: scale(0.94);
}
.sticky-player__button.is-active {
    background: var(--signal-soft);
    color: var(--signal);
    box-shadow: inset 0 0 0 1px var(--signal-line);
}
.sticky-player__button--play svg { width: 21px; height: 21px; }
.sticky-player__speed,
.sticky-player__volume {
    position: relative;
    display: flex;
    align-items: center;
}
.sticky-player__speed-toggle {
    font-size: 0.78rem;
    font-weight: 700;
    letter-spacing: -0.2px;
    padding: 5px 7px;
    min-width: 34px;
}
/* Les quatre panneaux flottants du lecteur (vitesse, minuteur, volume,
   newsletter) et le menu de partage prennent --raised, et non --sheet : ils se
   posent SUR la barre, qui est déjà en --sheet. --raised est le palier prévu
   pour une surface soulevée au-dessus d'une autre, dans les deux thèmes.
   Leur contour prend --rule-strong et non --rule : ils s'ouvrent AU-DESSUS de
   la barre, donc sur le contenu de page, où --raised n'est qu'à 1,02:1 de
   --paper — sans un filet franc, le panneau n'aurait plus de bord du tout. */
.sticky-player__speed-menu {
    position: absolute;
    bottom: calc(100% + 8px);
    left: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 5px;
    background: var(--raised);
    border: 1px solid var(--rule-strong);
    border-radius: var(--r);
    box-shadow: var(--shadow);
    z-index: 30;
}
.sticky-player__speed-menu[hidden] { display: none; }
.sticky-player__speed-menu button {
    border: none;
    background: transparent;
    color: var(--ink);
    font: inherit;
    font-size: 0.75rem;
    font-weight: 600;
    padding: 5px 12px;
    border-radius: var(--r);
    cursor: pointer;
    text-align: left;
}
/* --signal-soft, et non --sunk : ces lignes de menu ne changent PAS d'encre au
   survol, le fond est donc leur seul retour visuel. Un palier de surface ne se
   voit pas sur --raised. L'état coché reste au-dessus, en --signal plein. */
.sticky-player__speed-menu button:hover {
    background: var(--signal-soft);
}
.sticky-player__speed-menu button[aria-checked="true"] {
    background: var(--signal);
    color: var(--signal-ink);
}
.sticky-player__sleep {
    position: relative;
    display: flex;
    align-items: center;
}
.sticky-player__sleep-menu {
    position: absolute;
    bottom: calc(100% + 8px);
    right: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 5px;
    background: var(--raised);
    border: 1px solid var(--rule-strong);
    border-radius: var(--r);
    box-shadow: var(--shadow);
    z-index: 30;
    min-width: 150px;
}
.sticky-player__sleep-menu[hidden] { display: none; }
.sticky-player__sleep-menu button {
    border: none;
    background: transparent;
    color: var(--ink);
    font: inherit;
    font-size: 0.75rem;
    font-weight: 600;
    padding: 5px 12px;
    border-radius: var(--r);
    cursor: pointer;
    text-align: left;
}
.sticky-player__sleep-menu button:hover {
    background: var(--signal-soft);
}
.sticky-player__sleep-menu button[aria-checked="true"] {
    background: var(--signal);
    color: var(--signal-ink);
}
/* Minuteur actif : le bouton se distingue au repos, comme la vitesse ≠ ×1. */
[data-player-action="sleep"].is-active { color: var(--signal); }
.sticky-player__volume-panel {
    position: absolute;
    bottom: calc(100% + 8px);
    right: 0;
    padding: 8px 10px;
    background: var(--raised);
    border: 1px solid var(--rule-strong);
    border-radius: var(--r);
    box-shadow: var(--shadow);
    z-index: 30;
}
.sticky-player__volume-panel[hidden] { display: none; }
.sticky-player__volume-range { width: 110px; accent-color: var(--signal); }
.sticky-player__button.is-muted .sticky-player__volume-wave { opacity: 0.25; }
.sticky-player__newsletter { position: relative; display: flex; align-items: center; }
.sticky-player__newsletter-panel {
    position: absolute;
    bottom: calc(100% + 8px);
    right: 0;
    width: 240px;
    padding: 10px 12px;
    background: var(--raised);
    border: 1px solid var(--rule-strong);
    border-radius: var(--r);
    box-shadow: var(--shadow);
    z-index: 30;
}
.sticky-player__newsletter-panel[hidden] { display: none; }
.sticky-player__newsletter-text { margin: 0 0 8px; font-size: 0.78rem; line-height: 1.4; color: var(--ink); }
.sticky-player__newsletter-panel form { display: flex; flex-direction: column; gap: 6px; }
.sticky-player__newsletter-panel input[type="email"] {
    padding: 7px 9px;
    border-radius: var(--r);
    border: 1px solid var(--rule);
    background: var(--sunk);
    color: var(--ink);
    font: inherit;
    font-size: 0.78rem;
}
.sticky-player__newsletter-panel button[type="submit"] {
    padding: 6px 10px;
    border: none;
    border-radius: var(--r);
    background: var(--signal);
    color: var(--signal-ink);
    font-weight: 600;
    font-size: 0.78rem;
    cursor: pointer;
}
.sticky-player__newsletter-success { margin: 6px 0 0; font-size: 0.76rem; color: var(--good); }
.sticky-player__newsletter-dismiss {
    display: block;
    margin: 8px 0 0;
    padding: 0;
    border: none;
    background: transparent;
    color: var(--faint);
    font-size: 0.7rem;
    text-decoration: underline;
    cursor: pointer;
}
.sticky-player__actions {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: 1px;
    flex: 0 0 auto;
}
.sticky-player__progress {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 3px 6px 0;
    color: var(--faint);
    font-size: 0.66rem;
    font-variant-numeric: tabular-nums;
}
.sticky-player__current,
.sticky-player__duration {
    flex: 0 0 auto;
    opacity: 0.55;
    transition: opacity 0.2s ease;
}
.sticky-player:hover .sticky-player__current,
.sticky-player:hover .sticky-player__duration,
.sticky-player:focus-within .sticky-player__current,
.sticky-player:focus-within .sticky-player__duration {
    opacity: 1;
}
.sticky-player__progress-bar {
    position: relative;
    flex: 1 1 auto;
    border: none;
    background: var(--sunk);
    border-radius: var(--r);
    height: 3px;
    padding: 0;
    cursor: pointer;
    overflow: hidden;
    transition: height 0.2s ease, background 0.2s ease;
}
.sticky-player__progress-bar:hover {
    /* Un cran AU-DESSUS du repos : --rule contraste plus que --sunk dans les
       deux thèmes, ce qui rend le survol perceptible sur 3 px de haut. */
    background: var(--rule);
    height: 5px;
}
/* --- Barre de lecture en forme d'onde ---
   Quand l'épisode a été mesuré, la barre plate cède la place à son relief réel.
   Le bouton, son clic et ses attributs ARIA ne changent pas : seul le visuel
   diffère, donc la façon de s'en servir reste la même. */
.sticky-player__progress-bar.has-wave {
    height: 26px;
    background: transparent;
    border-radius: 0;
    overflow: hidden;
}
.sticky-player__progress-bar.has-wave:hover {
    height: 26px;
    background: transparent;
}
/* Le remplissage plat n'a plus lieu d'être quand les barres portent la position. */
.sticky-player__progress-bar.has-wave .sticky-player__progress-fill { display: none; }

.player-wave {
    display: flex;
    align-items: center;
    gap: 1px;
    width: 100%;
    height: 100%;
    pointer-events: none; /* le clic revient au bouton, qui gère le déplacement */
}
.player-wave span {
    flex: 1 1 0;
    min-width: 1px;
    border-radius: var(--r);
    /* Même jeton que .waveform des cartes (ex-`--line`) : les deux formes
       d'onde du site doivent avoir le même gris au repos, sans quoi la barre du
       lecteur paraîtrait d'une autre matière que celle des cartes. */
    background: var(--rule-strong);
}
.player-wave span.is-played { background: var(--signal); }

.sticky-player__progress-fill {
    display: block;
    width: 0%;
    height: 100%;
    /* Le dégradé disparaît, l'aplat reste. 0,8 ne tombe dans aucune plage de la
       table : --signal-line laverait la tête de lecture (un bleu très pâle en
       clair) et --signal répéterait le premier arrêt. Sur 3 px de haut, une
       rampe de 12 % de luminance n'apportait rien ; l'aplat garde la position
       de lecture nette, ce qui est la seule chose que cette barre doit dire. */
    background: var(--signal);
    transition: width 0.1s linear;
    border-radius: var(--r);
    box-shadow: 0 0 4px var(--signal-line);
}
/* Menu de partage ouvert par le bouton « ⋯ » */
.sticky-player-share {
    position: absolute;
    right: 12px;
    bottom: calc(100% - 6px);
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 230px;
    padding: 6px;
    background: var(--raised);
    border: 1px solid var(--rule-strong);
    border-radius: var(--r);
    box-shadow: var(--shadow);
    z-index: 30;
}
.sticky-player-share[hidden] { display: none; }
.sticky-player-share button {
    justify-content: flex-start;
    border-radius: var(--r);
    padding: 7px 10px;
    font-size: 0.76rem;
    min-height: 32px;
    text-align: left;
}
.sticky-player-share button[hidden] { display: none; }
.sticky-player-share button:hover {
    background: var(--signal-soft);
}
.sticky-player-share__link {
    width: 100%;
    background: var(--sunk);
    border: 1px solid var(--rule);
    border-radius: var(--r);
    color: var(--ink);
    font: inherit;
    font-size: 0.72rem;
    padding: 6px 8px;
    margin-bottom: 3px;
}
.sticky-player-share__feedback {
    margin: 2px 4px 0;
    min-height: 0;
    color: var(--signal);
    font-size: 0.7rem;
}
.sticky-player-share__feedback:empty { display: none; }

@media (max-width: 640px) {
    .sticky-player {
        width: calc(100% - 14px);
        bottom: 9px;
        padding: 8px 10px;
        border-radius: var(--r);
        font-size: 11px;
    }
    #sticky-player {
        padding: 7px 10px 5px !important;
        border-radius: 999px !important;
    }
    .sticky-player__inner {
        flex-wrap: wrap;
        gap: 6px;
    }
    .sticky-player__meta {
        order: -1;
        flex: 1 1 100%;
    }
    .sticky-player__cover {
        order: -2;
        width: 32px;
        height: 32px;
        border-radius: var(--r);
    }
    .sticky-player__controls {
        flex: 1 1 auto;
    }
    .sticky-player__actions {
        flex: 0 0 auto;
    }
    .sticky-player__title {
        font-size: 0.8rem;
    }
    .sticky-player__status {
        font-size: 0.68rem;
    }
    .sticky-player-share {
        left: 12px;
        right: 12px;
        min-width: 0;
    }
}

/* Inline mode: when the player is inserted in-page to replace the native audio */
.sticky-player.inline {
    position: static !important;
    width: 100% !important;
    left: auto !important;
    transform: none !important;
    bottom: auto !important;
    margin: 0 0 16px 0;
    box-shadow: 0 8px 24px var(--scrim);
    /* ensure absolute indicator is positioned relative to player when inline */
    position: relative !important;
}
.sticky-player.inline .sticky-player__cover {
    width: 40px;
    height: 40px;
}
.sticky-player.inline .sticky-player__controls {
    display: flex;
    align-items: center;
    gap: 4px;
}

/* Attach indicator */
.attach-indicator {
    position: absolute;
    top: -8px;
    right: -8px;
    background: var(--signal);
    color: var(--signal-ink);
    padding: 4px 10px;
    font-size: 0.7rem;
    font-weight: 600;
    border-radius: 999px;
    box-shadow: 0 6px 18px var(--scrim);
    transform-origin: center;
    opacity: 0;
    transform: scale(0.8) translateY(-4px);
    transition: opacity 220ms ease, transform 260ms cubic-bezier(.2, .9, .2, 1);
    pointer-events: none;
    letter-spacing: 0.3px;
}
.sticky-player.attached .attach-indicator {
    opacity: 1;
    transform: scale(1) translateY(0);
}

/* Small border pulse when attaching */
.sticky-player.attached {
    /* Les deux ombres CUMULENT : le halo ambré remplaçait l'ombre portée de la
       règle de base, si bien que le lecteur perdait son détachement au moment
       précis où il devait le plus se voir. */
    /* Exception d'ombre assumée (voir .sticky-player plus haut) : ce halo
       d'état signale un moment transitoire réel — le lecteur qui se
       rattache — sur une superposition déjà flottante. Ne pas uniformiser
       sur var(--shadow), qui est un aplat générique sans ce réglage. */
    box-shadow: 0 10px 28px var(--scrim), 0 14px 36px var(--signal-soft);
    transition: box-shadow 320ms ease;
}
.sticky-player.moving {
    /* Même exception assumée : halo d'état sur la même superposition
       flottante, pas un oubli de conversion vers var(--shadow). */
    box-shadow: 0 20px 46px var(--signal-line);
}
.sticky-player.moving .attach-indicator {
    opacity: 1;
    transform: scale(1.05) translateY(0);
}

/* Filtres d'archive des épisodes */
.episode-filters {
    display: grid;
    gap: 12px;
    margin: 0 0 18px;
}
.episode-filters-row {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    align-items: flex-end;
}
.episode-filters label {
    display: flex;
    flex-direction: column;
    gap: 6px;
    font-size: 0.92rem;
    color: var(--faint);
}
.episode-filters select,
.episode-filters button {
    min-width: 160px;
    border: 1px solid var(--rule);
    border-radius: var(--r);
    padding: 10px 14px;
    background: var(--sheet);
    color: var(--ink);
    font: inherit;
}
.episode-filters button {
    background: var(--signal);
    color: var(--signal-ink);
    border-color: transparent;
}
/* Même inversion que .btn-primary, dont ce bouton reprend le fond plein. */
.episode-filters button:hover {
    background: var(--signal-ink);
    color: var(--signal);
    border-color: var(--signal);
}
@media (max-width: 640px) {
    .episode-filters-row {
        flex-direction: column;
        align-items: stretch;
    }
    .episode-filters select,
    .episode-filters button { width: 100%; }
}

/* ---------------------------------------------------------------------------
   Pages de PLATEFORME : annuaire des podcasts et offres d'abonnement.
   (Chrome fourni par includes/platform-header.php, réutilise .wrap / .site-nav.)
   --------------------------------------------------------------------------- */
.platform-hero {
    text-align: center;
    padding: 32px 0 8px;
}
.platform-hero h1 {
    font-family: var(--f-display);
    font-size: clamp(1.8rem, 4vw, 2.6rem);
    margin: 0 0 8px;
}
.platform-hero-lede {
    color: var(--muted);
    font-size: 1.05rem;
    margin: 0;
}
.platform-hero-lede a { color: var(--signal); }
.platform-hero-links {
    display: flex;
    justify-content: center;
    gap: 12px;
    flex-wrap: wrap;
    margin: 20px 0 0;
}

/* Annuaire : grille de podcasts. */
.podcast-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 18px;
    margin: 24px 0 40px;
}
.podcast-card {
    display: flex;
    flex-direction: column;
    background: var(--sheet);
    border: 1px solid var(--rule);
    border-radius: var(--r);
    overflow: hidden;
    text-decoration: none;
    color: inherit;
    transition: border-color .15s ease, transform .15s ease;
}
.podcast-card:hover,
.podcast-card:focus-visible {
    border-color: var(--signal);
    transform: translateY(-2px);
}
.podcast-card-cover {
    width: 100%;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    display: block;
    background: var(--sunk);
}
.podcast-card-cover--empty {
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 2.4rem;
    opacity: .5;
}
.podcast-card-body { padding: 14px 16px; }
.podcast-card-title {
    font-family: var(--f-display);
    font-size: 1.05rem;
    margin: 0 0 4px;
}
.podcast-card-subtitle {
    color: var(--muted);
    font-size: .88rem;
    margin: 0 0 8px;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.podcast-card-count {
    color: var(--faint);
    font-size: .8rem;
}

/* Annuaire : recherche et pagination. */
.directory-search {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    margin: 20px 0 4px;
}
.directory-search input[type="search"] {
    flex: 1 1 240px;
    min-width: 0;
    padding: 10px 14px;
    background: var(--sunk);
    border: 1px solid var(--rule);
    border-radius: var(--r);
    color: inherit;
    font: inherit;
}
.directory-search input[type="search"]:focus-visible {
    outline: none;
    border-color: var(--signal);
}
.directory-pager {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 16px;
    margin: 8px 0 40px;
}
.directory-pager-status {
    color: var(--muted);
    font-size: .9rem;
}
.btn-disabled {
    opacity: .45;
    pointer-events: none;
    cursor: default;
}

/* Offres : cartes de tarif. */
.offers-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 18px;
    margin: 24px 0 16px;
}
.offer-card {
    display: flex;
    flex-direction: column;
    gap: 14px;
    background: var(--sheet);
    border: 1px solid var(--rule);
    border-radius: var(--r);
    padding: 26px;
    transition: border-color .15s ease, transform .15s ease;
}
.offer-card:hover {
    border-color: var(--signal);
    transform: translateY(-2px);
}
.offer-card-name {
    font-family: var(--f-display);
    font-size: 1.3rem;
    margin: 0;
}
.offer-card-price strong {
    font-family: var(--f-display);
    font-size: 1.9rem;
}
.offer-card-per { color: var(--faint); }
.offer-card-features {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
    flex: 1 1 auto;
}
.offer-card-features li {
    padding-left: 22px;
    position: relative;
    color: var(--muted);
    font-size: .92rem;
}
.offer-card-features li::before {
    content: "✓";
    position: absolute;
    left: 0;
    color: var(--good);
    font-weight: 700;
}
.offer-card-closed {
    color: var(--faint);
    font-size: .88rem;
    text-align: center;
}
.offers-note {
    color: var(--faint);
    font-size: .85rem;
    text-align: center;
    max-width: 46rem;
    margin: 8px auto 40px;
}

/* Accueil : mise en avant des points forts et témoignage optionnel. */
.platform-section-head {
    text-align: center;
    max-width: 34rem;
    margin: 48px auto 20px;
}
.platform-section-head h2 {
    font-family: var(--f-display);
    font-size: clamp(1.3rem, 3vw, 1.7rem);
    margin: 0;
}
.value-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
    gap: 18px;
    margin: 0 0 16px;
}
.value-card {
    background: var(--sheet);
    border: 1px solid var(--rule);
    border-radius: var(--r);
    padding: 22px;
}
.value-card h3 {
    font-family: var(--f-display);
    font-size: 1.05rem;
    margin: 0 0 8px;
}
.value-card p {
    color: var(--muted);
    font-size: .92rem;
    margin: 0;
}
.testimonial {
    max-width: 42rem;
    margin: 0 auto 16px;
    background: var(--sheet);
    border: 1px solid var(--rule);
    border-radius: var(--r);
    padding: 30px 34px;
    text-align: center;
}
.testimonial blockquote {
    margin: 0 0 14px;
    font-family: var(--f-display);
    font-size: 1.15rem;
    color: var(--ink);
}
.testimonial cite {
    display: block;
    font-style: normal;
    color: var(--faint);
    font-size: .88rem;
}

/* Page Fonctionnalités : catégories de fonctionnalités du produit. */
.feature-category {
    padding: 36px 0;
    border-top: 1px solid var(--rule);
}
.feature-category:first-of-type { border-top: none; padding-top: 12px; }
.feature-category h2 {
    font-family: var(--f-display);
    font-size: 1.3rem;
    margin: 0 0 20px;
}
.feature-list {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: 16px 28px;
    margin: 0;
    padding: 0;
}
.feature-list li {
    list-style: none;
    padding-left: 24px;
    position: relative;
}
.feature-list li::before {
    content: "✓";
    position: absolute;
    left: 0;
    color: var(--good);
    font-weight: 700;
}
.feature-list li strong {
    display: block;
    font-size: .96rem;
    margin-bottom: 2px;
}
.feature-list li span {
    color: var(--muted);
    font-size: .86rem;
}

/* Page Comparatif : tableau face à un autre hébergeur, sans le nommer. */
.compare-table-wrap { overflow-x: auto; margin: 24px 0 12px; }
.compare-table {
    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
    background: var(--sheet);
    border: 1px solid var(--rule);
    border-radius: var(--r);
    overflow: hidden;
}
.compare-table th, .compare-table td {
    padding: 14px 18px;
    text-align: left;
    border-bottom: 1px solid var(--rule);
    font-size: .92rem;
}
.compare-table thead th {
    font-family: var(--f-display);
    font-weight: 600;
    background: var(--raised);
}
.compare-table thead th.us {
    color: var(--signal-ink);
    background: var(--signal);
}
.compare-table tbody tr:last-child td { border-bottom: none; }
.compare-table td.us {
    background: var(--signal-soft);
    font-weight: 600;
}
.compare-table td.us::before {
    content: "✓ ";
    color: var(--good);
    font-weight: 700;
}
.compare-table td.other { color: var(--muted); }
.compare-note {
    color: var(--faint);
    font-size: .85rem;
    max-width: 46rem;
    margin: 4px 0 40px;
}
