feat: build interactive documentary page with audio player and chapter navigation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
consultoria-as
2026-02-22 04:07:22 +00:00
parent e7e58bba29
commit 279ab5e822
9 changed files with 443 additions and 0 deletions

View File

@@ -0,0 +1,111 @@
"use client";
import { useTranslations } from "next-intl";
interface AudioPlayerProps {
trackTitle: string | null;
isPlaying: boolean;
progress: number;
duration: number;
playbackRate: number;
continuousMode: boolean;
onToggle: () => void;
onSeek: (seconds: number) => void;
onChangeRate: (rate: number) => void;
onToggleContinuous: () => void;
}
const RATES = [0.5, 0.75, 1, 1.25, 1.5, 2];
function formatTime(seconds: number): string {
const m = Math.floor(seconds / 60);
const s = Math.floor(seconds % 60);
return `${m}:${s.toString().padStart(2, "0")}`;
}
export function AudioPlayer({
trackTitle,
isPlaying,
progress,
duration,
playbackRate,
continuousMode,
onToggle,
onSeek,
onChangeRate,
onToggleContinuous,
}: AudioPlayerProps) {
const t = useTranslations("audio");
if (!trackTitle) return null;
const progressPercent = duration > 0 ? (progress / duration) * 100 : 0;
return (
<div className="fixed bottom-0 left-0 right-0 z-50 bg-gray-900/95 backdrop-blur-sm border-t border-white/10">
<div className="max-w-7xl mx-auto px-4 py-3">
{/* Progress bar */}
<div
className="w-full h-1 bg-gray-700 rounded-full mb-3 cursor-pointer"
onClick={(e) => {
const rect = e.currentTarget.getBoundingClientRect();
const ratio = (e.clientX - rect.left) / rect.width;
onSeek(ratio * duration);
}}
>
<div
className="h-full bg-blue-500 rounded-full transition-all"
style={{ width: `${progressPercent}%` }}
/>
</div>
<div className="flex items-center gap-4">
{/* Play/Pause */}
<button
onClick={onToggle}
className="w-10 h-10 flex items-center justify-center bg-white rounded-full text-black hover:bg-gray-200 transition-colors"
aria-label={isPlaying ? t("pause") : t("play")}
>
{isPlaying ? "\u23F8" : "\u25B6"}
</button>
{/* Track info */}
<div className="flex-1 min-w-0">
<p className="text-sm text-white truncate">{trackTitle}</p>
<p className="text-xs text-gray-500">
{formatTime(progress)} / {formatTime(duration)}
</p>
</div>
{/* Speed selector */}
<div className="flex items-center gap-2">
<span className="text-xs text-gray-500">{t("speed")}:</span>
<select
value={playbackRate}
onChange={(e) => onChangeRate(Number(e.target.value))}
className="bg-gray-800 border border-white/10 rounded px-2 py-1 text-xs text-white"
>
{RATES.map((r) => (
<option key={r} value={r}>
{r}x
</option>
))}
</select>
</div>
{/* Continuous mode toggle */}
<button
onClick={onToggleContinuous}
className={`text-xs px-3 py-1 rounded border transition-colors ${
continuousMode
? "border-blue-500 text-blue-400"
: "border-white/10 text-gray-500 hover:text-white"
}`}
>
{continuousMode ? t("continuous_mode") : t("chapter_mode")}
</button>
</div>
</div>
</div>
);
}