Retour au catalogue
Video Comparison
Comparaison video cote a cote avec controles de lecture synchronises. Ideal pour avant/apres ou comparaisons de produits.
videomedium Both Responsive a11y
minimalcorporatesaasbeautyuniversalsplit
Theme
"use client";
import { useState } from "react";
import { motion } from "framer-motion";
import { Play, Pause, RotateCcw } from "lucide-react";
interface ComparisonVideo {
title: string;
label: string;
thumbnail: string;
duration: string;
}
interface VideoComparisonProps {
title?: string;
subtitle?: string;
leftVideo?: ComparisonVideo;
rightVideo?: ComparisonVideo;
}
const EASE = [0.16, 1, 0.3, 1] as const;
const defaultVideo: ComparisonVideo = {
title: "Video",
label: "Video",
thumbnail: "/api/placeholder/800/500",
duration: "0:00",
};
export default function VideoComparison({
title = "Comparez les resultats",
subtitle = "Avant / Apres",
leftVideo = defaultVideo,
rightVideo = defaultVideo,
}: VideoComparisonProps) {
const [isPlaying, setIsPlaying] = useState(false);
const [progress, setProgress] = useState(0);
const handlePlay = () => {
if (isPlaying) {
setIsPlaying(false);
return;
}
setIsPlaying(true);
setProgress(0);
const interval = setInterval(() => {
setProgress((p) => {
if (p >= 100) { clearInterval(interval); setIsPlaying(false); return 100; }
return p + 0.3;
});
}, 50);
};
const handleReset = () => {
setProgress(0);
setIsPlaying(false);
};
return (
<section style={{ paddingTop: "var(--section-padding-y, 6rem)", paddingBottom: "var(--section-padding-y, 6rem)", background: "var(--color-background)" }}>
<div style={{ maxWidth: "var(--container-max-width, 72rem)", margin: "0 auto", padding: "0 var(--container-padding-x, 1.5rem)" }}>
<motion.div initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ duration: 0.6, ease: EASE }} style={{ textAlign: "center", marginBottom: "3rem" }}>
<p style={{ fontSize: "0.8125rem", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.1em", color: "var(--color-accent)", marginBottom: "0.5rem" }}>{subtitle}</p>
<h2 style={{ fontFamily: "var(--font-sans)", fontSize: "clamp(2rem, 4vw, 3rem)", fontWeight: 700, letterSpacing: "-0.03em", color: "var(--color-foreground)" }}>{title}</h2>
</motion.div>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "1.5rem" }}>
{[leftVideo, rightVideo].map((video, idx) => (
<motion.div
key={idx}
initial={{ opacity: 0, x: idx === 0 ? -20 : 20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: idx * 0.1, ease: EASE }}
style={{ borderRadius: "var(--radius-xl, 1.5rem)", overflow: "hidden", border: "1px solid var(--color-border)", background: "var(--color-background-card)" }}
>
{/* Label badge */}
<div style={{ padding: "0.75rem 1.25rem", borderBottom: "1px solid var(--color-border)", display: "flex", alignItems: "center", justifyContent: "space-between" }}>
<span style={{ fontSize: "0.75rem", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.05em", color: idx === 0 ? "var(--color-foreground-muted)" : "var(--color-accent)" }}>{video.label}</span>
<span style={{ fontSize: "0.6875rem", color: "var(--color-foreground-muted)" }}>{video.duration}</span>
</div>
<div style={{ position: "relative", aspectRatio: "16/10" }}>
<img src={video.thumbnail} alt={video.title} style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
{/* Simulated progress overlay */}
<div style={{ position: "absolute", bottom: 0, left: 0, right: 0, height: "3px", background: "var(--color-border)" }}>
<motion.div
animate={{ width: `${progress}%` }}
style={{ height: "100%", background: "var(--color-accent)" }}
/>
</div>
</div>
<div style={{ padding: "1rem 1.25rem" }}>
<h3 style={{ fontSize: "1rem", fontWeight: 600, color: "var(--color-foreground)" }}>{video.title}</h3>
</div>
</motion.div>
))}
</div>
{/* Synchronized controls */}
<motion.div
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.3 }}
style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: "1rem", marginTop: "2rem" }}
>
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={handlePlay}
style={{ display: "inline-flex", alignItems: "center", gap: "0.5rem", padding: "0.75rem 2rem", borderRadius: "var(--radius-full, 9999px)", background: "var(--color-accent)", border: "none", cursor: "pointer", fontSize: "0.875rem", fontWeight: 600, color: "var(--color-background)" }}
>
{isPlaying ? <Pause style={{ width: 16, height: 16 }} /> : <Play style={{ width: 16, height: 16 }} />}
{isPlaying ? "Pause" : "Lire les deux"}
</motion.button>
<button
onClick={handleReset}
style={{ width: "40px", height: "40px", borderRadius: "50%", border: "1px solid var(--color-border)", background: "var(--color-background-card)", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center" }}
>
<RotateCcw style={{ width: 16, height: 16, color: "var(--color-foreground)" }} />
</button>
</motion.div>
</div>
</section>
);
}