Retour au catalogue
Video Cinematic
Section video cinematique avec barres letterbox 21:9, bouton play avec cercle SVG progress, titre qui fade au play.
videocomplex Dark Responsive a11y
darkelegantelegantuniversalagencyportfoliofullscreen
Theme
"use client";
import { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { Play } from "lucide-react";
interface VideoCinematicProps {
title?: string;
subtitle?: string;
ctaLabel?: string;
duration?: string;
}
const EASE = [0.16, 1, 0.3, 1] as const;
const LETTERBOX = "12%";
export default function VideoCinematic({
title = "Decouvrez notre film",
subtitle = "Une experience visuelle unique",
ctaLabel = "Regarder",
duration = "2:34",
}: VideoCinematicProps) {
const [isPlaying, setIsPlaying] = useState(false);
const [progress, setProgress] = useState(0);
const circumference = 2 * Math.PI * 34;
const handlePlay = () => {
setIsPlaying(true);
setProgress(0);
const interval = setInterval(() => {
setProgress((prev) => {
if (prev >= 100) {
clearInterval(interval);
return 100;
}
return prev + 0.5;
});
}, 50);
};
return (
<section
style={{
position: "relative",
width: "100%",
minHeight: "100vh",
display: "flex",
alignItems: "center",
justifyContent: "center",
overflow: "hidden",
background: "#000",
}}
>
{/* Video background placeholder */}
<div
style={{
position: "absolute",
inset: 0,
background: `
radial-gradient(ellipse at center,
color-mix(in srgb, var(--color-accent) 6%, transparent),
transparent 60%
),
linear-gradient(180deg, #0a0a0a 0%, #111 50%, #0a0a0a 100%)
`,
}}
/>
{/* Letterbox bars — cinematic 21:9 */}
<div
aria-hidden
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
height: LETTERBOX,
background: "#000",
zIndex: 3,
}}
/>
<div
aria-hidden
style={{
position: "absolute",
bottom: 0,
left: 0,
right: 0,
height: LETTERBOX,
background: "#000",
zIndex: 3,
}}
/>
{/* Content */}
<div
style={{
position: "relative",
zIndex: 2,
textAlign: "center",
padding: "0 var(--container-padding-x)",
}}
>
{/* Title — fades out when playing */}
<AnimatePresence>
{!isPlaying && (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.6, ease: EASE }}
>
<h2
style={{
fontFamily: "var(--font-sans)",
fontSize: "clamp(2rem, 5vw, 4rem)",
fontWeight: 700,
lineHeight: 1.1,
letterSpacing: "-0.03em",
color: "#f5f5f5",
marginBottom: "0.75rem",
}}
>
{title}
</h2>
<p
style={{
fontSize: "1.125rem",
color: "rgba(255,255,255,0.5)",
marginBottom: "3rem",
}}
>
{subtitle}
</p>
</motion.div>
)}
</AnimatePresence>
{/* Play button with SVG progress ring */}
<motion.button
whileHover={{ scale: 1.06 }}
whileTap={{ scale: 0.95 }}
onClick={handlePlay}
style={{
position: "relative",
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
width: "88px",
height: "88px",
borderRadius: "50%",
background: "none",
border: "none",
cursor: "pointer",
}}
>
{/* Progress ring SVG */}
<svg
width="88"
height="88"
style={{
position: "absolute",
top: 0,
left: 0,
transform: "rotate(-90deg)",
}}
>
{/* Track */}
<circle
cx="44"
cy="44"
r="34"
fill="none"
stroke="rgba(255,255,255,0.15)"
strokeWidth="2"
/>
{/* Progress */}
<circle
cx="44"
cy="44"
r="34"
fill="none"
stroke="var(--color-accent)"
strokeWidth="2.5"
strokeLinecap="round"
strokeDasharray={circumference}
strokeDashoffset={circumference - (progress / 100) * circumference}
style={{ transition: "stroke-dashoffset 0.1s linear" }}
/>
</svg>
{/* Play icon */}
<div
style={{
width: "56px",
height: "56px",
borderRadius: "50%",
background: "rgba(255,255,255,0.1)",
backdropFilter: "blur(8px)",
display: "flex",
alignItems: "center",
justifyContent: "center",
border: "1px solid rgba(255,255,255,0.2)",
}}
>
<Play
style={{
width: 22,
height: 22,
color: "#fff",
marginLeft: 2,
}}
/>
</div>
</motion.button>
{/* Label & duration */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.4, duration: 0.5 }}
style={{
marginTop: "1.5rem",
display: "flex",
alignItems: "center",
justifyContent: "center",
gap: "0.75rem",
}}
>
<span
style={{
fontSize: "0.875rem",
fontWeight: 500,
color: "rgba(255,255,255,0.6)",
}}
>
{ctaLabel}
</span>
<span
style={{
fontSize: "0.75rem",
color: "rgba(255,255,255,0.3)",
padding: "0.2rem 0.5rem",
borderRadius: "var(--radius-sm)",
background: "rgba(255,255,255,0.06)",
}}
>
{duration}
</span>
</motion.div>
</div>
</section>
);
}