Retour au catalogue
Event Live Stream
Section d'embed de live stream avec chat lateral, indicateur en direct et infos du speaker.
eventmedium Both Responsive a11y
boldcorporateeventeducationsplit
Theme
"use client";
import { useState } from "react";
import { motion } from "framer-motion";
import { Radio, Users, MessageCircle, Send, Play } from "lucide-react";
interface ChatMessage {
author: string;
message: string;
time: string;
}
interface Speaker {
name: string;
role: string;
avatarUrl: string;
}
interface EventLiveStreamProps {
title?: string;
currentTalk?: string;
speaker?: Speaker;
viewerCount?: number;
embedPlaceholder?: string;
chatMessages?: ChatMessage[];
}
const EASE = [0.16, 1, 0.3, 1] as const;
export default function EventLiveStream({
title = "En direct",
currentTalk = "Presentation",
speaker,
viewerCount = 0,
embedPlaceholder = "/api/placeholder/800/450",
chatMessages = [],
}: EventLiveStreamProps) {
const [chatInput, setChatInput] = useState("");
return (
<section
style={{
paddingTop: "var(--section-padding-y)",
paddingBottom: "var(--section-padding-y)",
background: "var(--color-background)",
}}
>
<div
style={{
maxWidth: "var(--container-max-width)",
margin: "0 auto",
padding: "0 var(--container-padding-x)",
}}
>
{/* Header with live indicator */}
<motion.div
initial={{ opacity: 0, y: 16 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, ease: EASE }}
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
flexWrap: "wrap",
gap: "1rem",
marginBottom: "1.5rem",
}}
>
<div style={{ display: "flex", alignItems: "center", gap: "0.75rem" }}>
<motion.div
animate={{ opacity: [1, 0.4, 1] }}
transition={{ duration: 1.5, repeat: Infinity }}
style={{
display: "flex",
alignItems: "center",
gap: "0.375rem",
padding: "0.375rem 0.75rem",
borderRadius: "var(--radius-full)",
background: "color-mix(in srgb, var(--color-accent) 15%, transparent)",
}}
>
<Radio style={{ width: 14, height: 14, color: "var(--color-accent)" }} />
<span
style={{
fontSize: "0.75rem",
fontWeight: 700,
color: "var(--color-accent)",
textTransform: "uppercase",
}}
>
{title}
</span>
</motion.div>
<h2
style={{
fontSize: "clamp(1.125rem, 2vw, 1.5rem)",
fontWeight: 700,
color: "var(--color-foreground)",
}}
>
{currentTalk}
</h2>
</div>
<div style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}>
<Users style={{ width: 14, height: 14, color: "var(--color-foreground-muted)" }} />
<span style={{ fontSize: "0.8125rem", color: "var(--color-foreground-muted)", fontVariantNumeric: "tabular-nums" }}>
{viewerCount.toLocaleString("fr-FR")} spectateurs
</span>
</div>
</motion.div>
{/* Main content: Video + Chat */}
<div
style={{
display: "grid",
gridTemplateColumns: "1fr 320px",
gap: "1.5rem",
}}
>
{/* Video area */}
<motion.div
initial={{ opacity: 0, scale: 0.98 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.5, ease: EASE }}
>
<div
style={{
position: "relative",
aspectRatio: "16/9",
borderRadius: "var(--radius-lg)",
overflow: "hidden",
background: "var(--color-background-alt)",
border: "1px solid var(--color-border)",
}}
>
<img
src={embedPlaceholder}
alt="Stream"
style={{ width: "100%", height: "100%", objectFit: "cover" }}
/>
{/* Play overlay */}
<div
style={{
position: "absolute",
inset: 0,
display: "flex",
alignItems: "center",
justifyContent: "center",
background: "color-mix(in srgb, var(--color-foreground) 30%, transparent)",
}}
>
<div
style={{
width: 64,
height: 64,
borderRadius: "50%",
background: "var(--color-accent)",
display: "flex",
alignItems: "center",
justifyContent: "center",
cursor: "pointer",
}}
>
<Play
style={{
width: 28,
height: 28,
color: "var(--color-background)",
marginLeft: 4,
}}
/>
</div>
</div>
</div>
{/* Speaker info */}
{speaker && (
<div
style={{
display: "flex",
alignItems: "center",
gap: "0.75rem",
marginTop: "1rem",
padding: "1rem",
borderRadius: "var(--radius-md)",
background: "var(--color-background-card)",
border: "1px solid var(--color-border)",
}}
>
<img
src={speaker.avatarUrl}
alt={speaker.name}
style={{
width: 40,
height: 40,
borderRadius: "50%",
objectFit: "cover",
}}
/>
<div>
<p
style={{
fontSize: "0.875rem",
fontWeight: 700,
color: "var(--color-foreground)",
}}
>
{speaker.name}
</p>
<p
style={{
fontSize: "0.75rem",
color: "var(--color-foreground-muted)",
}}
>
{speaker.role}
</p>
</div>
</div>
)}
</motion.div>
{/* Chat panel */}
<motion.div
initial={{ opacity: 0, x: 16 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.1, ease: EASE }}
style={{
display: "flex",
flexDirection: "column",
borderRadius: "var(--radius-lg)",
border: "1px solid var(--color-border)",
background: "var(--color-background-card)",
overflow: "hidden",
}}
>
{/* Chat header */}
<div
style={{
display: "flex",
alignItems: "center",
gap: "0.5rem",
padding: "0.875rem 1rem",
borderBottom: "1px solid var(--color-border)",
}}
>
<MessageCircle style={{ width: 16, height: 16, color: "var(--color-accent)" }} />
<span style={{ fontSize: "0.8125rem", fontWeight: 700, color: "var(--color-foreground)" }}>
Chat en direct
</span>
</div>
{/* Messages */}
<div
style={{
flex: 1,
overflow: "auto",
padding: "0.75rem",
display: "flex",
flexDirection: "column",
gap: "0.625rem",
maxHeight: 400,
}}
>
{chatMessages.map((msg, i) => (
<motion.div
key={i}
initial={{ opacity: 0, x: -8 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3, delay: 0.05 * i }}
style={{ display: "flex", gap: "0.5rem", alignItems: "flex-start" }}
>
<div style={{ flex: 1 }}>
<div style={{ display: "flex", alignItems: "baseline", gap: "0.375rem" }}>
<span
style={{
fontSize: "0.75rem",
fontWeight: 700,
color: "var(--color-accent)",
}}
>
{msg.author}
</span>
<span
style={{
fontSize: "0.625rem",
color: "var(--color-foreground-muted)",
}}
>
{msg.time}
</span>
</div>
<p
style={{
fontSize: "0.8125rem",
color: "var(--color-foreground)",
lineHeight: 1.4,
}}
>
{msg.message}
</p>
</div>
</motion.div>
))}
</div>
{/* Chat input */}
<div
style={{
display: "flex",
alignItems: "center",
gap: "0.5rem",
padding: "0.75rem",
borderTop: "1px solid var(--color-border)",
}}
>
<input
value={chatInput}
onChange={(e) => setChatInput(e.target.value)}
placeholder="Envoyer un message..."
style={{
flex: 1,
padding: "0.5rem 0.75rem",
borderRadius: "var(--radius-md)",
border: "1px solid var(--color-border)",
background: "var(--color-background)",
color: "var(--color-foreground)",
fontSize: "0.8125rem",
}}
/>
<button
style={{
width: 36,
height: 36,
borderRadius: "var(--radius-md)",
background: "var(--color-accent)",
border: "none",
display: "flex",
alignItems: "center",
justifyContent: "center",
cursor: "pointer",
flexShrink: 0,
}}
>
<Send style={{ width: 14, height: 14, color: "var(--color-background)" }} />
</button>
</div>
</motion.div>
</div>
</div>
</section>
);
}