16cebb6ec9
ci/woodpecker/push/woodpecker Pipeline was successful
Sur smartphone (< 640px) : panneau stats masqué par défaut, accessible via un bottom drawer animé (bouton ☰). PeriodSelector passe en flex-wrap avec padding tactile 44px. AnimationPlayer s'adapte à la largeur écran. Badge ville focus affiché directement sur la carte en mode mobile. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
110 lines
3.4 KiB
TypeScript
110 lines
3.4 KiB
TypeScript
import type { TimeFrame } from '../hooks/useAnimation';
|
||
|
||
interface AnimationPlayerProps {
|
||
frames: TimeFrame[];
|
||
currentIndex: number;
|
||
playing: boolean;
|
||
speed: 1 | 2 | 4;
|
||
onSeek: (i: number) => void;
|
||
onPlay: () => void;
|
||
onPause: () => void;
|
||
onSpeedChange: (s: 1 | 2 | 4) => void;
|
||
onClose: () => void;
|
||
}
|
||
|
||
export function AnimationPlayer({
|
||
frames,
|
||
currentIndex,
|
||
playing,
|
||
speed,
|
||
onSeek,
|
||
onPlay,
|
||
onPause,
|
||
onSpeedChange,
|
||
onClose,
|
||
}: AnimationPlayerProps) {
|
||
const frame = frames[currentIndex];
|
||
|
||
return (
|
||
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 z-[1001] w-[min(640px,calc(100vw-1rem))]">
|
||
<div className="bg-[#0a0b0f]/90 backdrop-blur-sm border border-[#2e2f3a] rounded-2xl px-4 py-3 flex flex-col gap-2.5 shadow-xl">
|
||
|
||
{/* Frame label + position */}
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-[#d4a843] text-sm font-medium">
|
||
{frame?.label ?? '—'}
|
||
</span>
|
||
<span className="text-[#4b5563] text-xs tabular-nums">
|
||
{currentIndex + 1} / {frames.length}
|
||
</span>
|
||
</div>
|
||
|
||
{/* Slider */}
|
||
<input
|
||
type="range"
|
||
min={0}
|
||
max={frames.length - 1}
|
||
value={currentIndex}
|
||
onChange={(e) => onSeek(Number(e.target.value))}
|
||
className="w-full h-1 accent-[#d4a843] cursor-pointer"
|
||
/>
|
||
|
||
{/* Controls row */}
|
||
<div className="flex flex-wrap items-center justify-between gap-y-2">
|
||
|
||
{/* Playback buttons */}
|
||
<div className="flex items-center gap-1">
|
||
<button
|
||
onClick={() => onSeek(Math.max(0, currentIndex - 1))}
|
||
className="px-2.5 py-1.5 text-[#6b7280] hover:text-white transition-colors text-sm"
|
||
title="Frame précédente"
|
||
>
|
||
◀◀
|
||
</button>
|
||
<button
|
||
onClick={playing ? onPause : onPlay}
|
||
className="px-4 py-1.5 bg-[#d4a843] text-[#0a0b0f] rounded-lg font-bold text-sm hover:bg-[#e8c060] transition-colors min-w-[52px] text-center shadow-[0_0_10px_rgba(212,168,67,0.3)]"
|
||
>
|
||
{playing ? '⏸' : '▶'}
|
||
</button>
|
||
<button
|
||
onClick={() => onSeek(Math.min(frames.length - 1, currentIndex + 1))}
|
||
className="px-2.5 py-1.5 text-[#6b7280] hover:text-white transition-colors text-sm"
|
||
title="Frame suivante"
|
||
>
|
||
▶▶
|
||
</button>
|
||
</div>
|
||
|
||
{/* Speed selector */}
|
||
<div className="flex items-center gap-1">
|
||
<span className="text-[#4b5563] text-xs mr-1">Vitesse</span>
|
||
{([1, 2, 4] as const).map((s) => (
|
||
<button
|
||
key={s}
|
||
onClick={() => onSpeedChange(s)}
|
||
className={`px-2 py-1 rounded text-xs font-medium transition-colors ${
|
||
speed === s
|
||
? 'bg-[#d4a843] text-[#0a0b0f]'
|
||
: 'text-[#6b7280] hover:text-[#d4a843] hover:bg-[#1a1b23]'
|
||
}`}
|
||
>
|
||
×{s}
|
||
</button>
|
||
))}
|
||
</div>
|
||
|
||
{/* Close */}
|
||
<button
|
||
onClick={onClose}
|
||
className="text-[#4b5563] hover:text-white transition-colors px-2 py-1 text-sm ml-2"
|
||
title="Quitter l'animation"
|
||
>
|
||
✕
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|