shouldResumeCtxRef: si onPlay fire avant l'effet Web Audio,
ctx.resume() appelé dès que l'effet setup se termine.
autoPlay sur l'élément analyse pour démarrage sans attendre handleMainPlay.
Audio principal: URL directe, démarre instantanément comme avant.
Élément caché muet: proxy URL, Web Audio uniquement pour waveform.
Synchronisé sur play/pause/seek du principal.
Resume: savedPositionRef appliqué sur loadedmetadata pour survivre
au remontage audio après échec CORS. Analyser: fftSize 256,
smoothing 0.55. Simulation: rythme syllabique avec consonantes.
Remplace le signe binaire (>0.05?1:-1) par Math.sin() direct,
éliminant les sauts brusques aux zero-crossings. Réduit lerp rates
et fréquence spatiale pour oscillation plus douce.
Approche hybride CORS retry:
- Tentative 1: crossOrigin="anonymous" + createMediaElementSource
→ données de fréquence réelles si le serveur le permet
- Tentative 2 (si CORS bloque): sans crossOrigin, audio direct
→ son fonctionne, visualiseur simulé
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
createMediaElementSource détache l'audio de sa sortie par défaut.
Pour les ressources cross-origin sans CORS, le graphe Web Audio
produit du silence — aucun son.
- Retire createMediaElementSource et tout le pipeline Web Audio
- L'audio joue directement via l'élément <audio> (fiable)
- Le visualiseur utilise une animation simulée (plus de dépendance CORS)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
crossOrigin="anonymous" sur <audio> forçait le navigateur à exiger
les headers CORS des serveurs audio. Les hébergeurs sans
Access-Control-Allow-Origin bloquaient le chargement entièrement.
- Retire crossOrigin="anonymous" de l'élément <audio>
- Ajoute visualisation simulée dans l'egaliseur quand les données
de fréquence réelles sont indisponibles (audio cross-origin)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Remplacement du visualiseur horizontal (lignes de波形) par un égaliseur à barres verticales :
- Barres symétriques miroir (croissance depuis le centre vers haut et bas)
- Têtes de barres arrondies (arcTo)
- Dégradé cyan → indigo → cyan sur chaque barre
- Halo lumineux radial sur les barres hautes (>25%)
- Point lumineux au sommet des pics (>50%)
- Ligne de séparation centrale subtile
- Easing fluide entre les trames audio (lerp 0.35)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Extraction du composant MiniPlayer hors d'AudioPlayer pour stabiliser sa référence.
Avant: MiniPlayer était une fonction imbriquée recréée à chaque rendu (4x/sec avec
onTimeUpdate), React le traitait comme un nouveau type de composant → démontage/remontage →
animation slide-up rejouée → clignotement.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Ajout des variables CSS manquantes (--bg-primary, --bg-secondary, --radius-panel/--radius-card)
- Ajout des classes manquantes (premium-glass, neon-button, input-premium, shadow-glow-indigo, animate-aura)
- Fond du panneau latéral dashboard passé à --bg-elevated pour le décrocher du fond de page
- Fond solide sur le mini-player flottant + correction clignotement IntersectionObserver
- Ombres des cartes renforcées (shadow-card/shadow-card-hover)
- Contour de l'avatar profil amélioré (dégradé plus épais + glow + lettre en gradient)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- IntersectionObserver avec debounce 250ms pour éviter le clignotement
- Bouton play sur cartes: bg-black/85 text-white (plus de blanc sur blanc)
- Badge LU: fond vert solide (var(--accent-emerald))
- AudioVisualizer: forme d'onde horizontale style Stitch (canvas)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Barres plus pleines et arrondies, halo lumineux sur pics audio,
palette dark plus riche (indigo vibrant, cyan éclatant, bleu profond),
ombres et bordures plus marquées, gradients de fond plus immersifs.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Refonte du design system (CSS variables, composants glass/card/button/badge),
nouvelle page PodcastDetails avec route /podcast/:id,
lecteur audio immersif avec visualiseur audio,
animations, thème dark/light, responsive mobile/desktop.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace flat linear bars with a symmetric hill-shaped visualizer.
Bars now mirror from the center outward, with a smooth falloff
toward the edges and a mid-frequency boost for speech clarity.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace class-based animation toggle with inline animationPlayState
style. The transition-all class was conflicting with the CSS keyframe
animation, preventing the artwork from spinning when playing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace static CSS eq-bars with a real-time AudioVisualizer component
using the Web Audio API AnalyserNode. The visualizer is now centered
below the progress bar in the inline hero player, with 28 bars and
boosted mid-range frequencies for better speech visualization.
- AudioContext: wire AnalyserNode, expose getFrequencyData, add
crossOrigin="anonymous" and key on the audio element
- AudioPlayer (inline): remove static eq-bars, add AudioVisualizer
- New AudioVisualizer component: requestAnimationFrame loop,
smooth decay on pause, configurable bar count/height/gap
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- EpisodeCard: change 'Lu ?' → 'Marquer' so the toggle button no longer
shows a confusing question mark next to completed episodes
- Dashboard: fix episodes reappearing in 'Continuer l'écoute' after
navigation. handleToggleRead now does an optimistic update on BOTH
local state AND all TanStack Query cache pages (setQueriesData),
so the 'lu' state survives page navigation without a server roundtrip.
On error, cache is invalidated to revert to server truth.
- Dashboard: 'Découvrir' button now links to /add (discover new podcasts)
instead of /trending — was identical to 'Parcourir les tendances' button
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add .github/workflows/docker-publish.yml: builds Docker image on every
push to main and publishes to ghcr.io/r0m1k3/podcastic:latest automatically
- docker-compose.yml: replace local `build:` with `image: ghcr.io/r0m1k3/podcastic:latest`
so Unraid's "Update" button can pull the image directly from the registry
instead of failing with "no service selected"
- Remove deprecated `version: '3.8'` field
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- max-w-[1400px] mx-auto dans le wrapper global (App.tsx)
pour que les cartes ne soient pas immenses sur 2K/4K
- padding cohérent lg:px-8 sur toutes les pages
- suppression du max-w-5xl parasites sur AddPodcast
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Crée PodcastCard réutilisable avec le même design qu'EpisodeCard
(image carrée, auteur, titre, footer avec actions). Remplace les
3 designs inline différents par un seul composant cohérent.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Toutes les pages utilisent grid-cols-1 sm:2 lg:3 xl:4 gap-8
- Cadre gradient visible en permanence sur l'avatar profil
- Suppression du badge NOUVEAU sur les épisodes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- AudioPlayer is now purely presentational — no internal <audio> element or state.
All playback state (currentTime, duration, volume, speed, mute, error, isResuming)
comes from AudioContext, which holds the persistent <audio> at provider level.
This prevents audio from cutting when navigating between pages.
- userId is synced into AudioContext via setUserId() so progress saving/loading
works without the player needing to manage it locally.
- EpisodeCard: isCompleted now triggers at ≥90% listened (progressPercent >= 0.9)
in addition to the isCompleted flag, so the LU badge appears correctly.
Progress bar also hidden when ≥90%.
- Dashboard: episodesInProgress now excludes episodes at ≥90% listened,
removing them from the "Continuer l'écoute" section once effectively finished.
setUserId() called after user loads to wire context progress saving.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the old compact floating bar (h-88px rounded-2rem with expand
button) with the same clean mini-bar design as the Dashboard sticky
sub-bar — artwork + title + seeks + play + time + speed/volume/expand/
close + thin progress bar. The Dashboard hero inline player is kept.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Refresh podcast title/author/imageUrl/description on RSS sync (fixes
legacy 'Unknown Podcast' records)
- Always-visible Resync button on Library cards
- LU badge on completed episodes, NOUVEAU badge on <7-day-old episodes
- AudioPlayer gains mode='inline' that embeds in the Dashboard hero card
- Floating player suppressed on /dashboard to avoid dual audio
- Sticky mini-bar appears via IntersectionObserver when inline player
scrolls out of view (shares the same <audio> element)
- Fix: 'Reprendre' button called undefined setSelectedEpisode; now calls
playEpisode
- Add progress field to Episode TS interface
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
EpisodeCard and AudioPlayer now display episode.imageUrl when available,
falling back to the podcast image only if the episode has none.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
AudioPlayer:
- Supprime section vitesse de lecture
- -30s/+30s (compact et expanded), avec icônes RotateCcw/RotateCw
- Rewind compact passé de -15s à -30s
Thème:
- globals.css refait : animations slide-up/fade-in/reveal définies, glass card améliorée
- Background plus profond (#080d1a), glow/blur plus soignés
- Boutons neon-button avec hover translateY
Corrections:
- Supprime Settings2 (bouton sans action) dans Library
- Supprime stat "2.4k mins listened" inventée dans Navigation
- Stats Library passent de 3 à 2 colonnes (réelles uniquement)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- podcastController: createPodcastFromRss sans feedData (parse le RSS réel)
- Discover.tsx: stub vide (remplacé par /trending et /add)
- App.tsx + Navigation.tsx: conflits déjà résolus
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Abonner deux fois au même podcast retourne 200 au lieu de 409.
Tous les messages d'erreur traduits en français.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
result.podcast est un plain object (toJSON()), appeler .toJSON() dessus crashe.
Fix: refetch le podcast depuis MongoDB après création pour avoir un vrai Document Mongoose.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>