"use client"; import { Button } from "@/components/ui/Button"; import { Link } from "@/i18n/routing"; import type { HeroBanner } from "@/lib/demo-data"; import { cn } from "@/lib/utils"; import { useTranslations } from "next-intl"; import { useCallback, useEffect, useRef, useState } from "react"; function isInternalHref(href: string) { return href.startsWith("/") && !href.startsWith("//"); } function BannerLink({ href, children, }: { href: string; children: React.ReactNode; }) { if (!href) return null; if (isInternalHref(href)) { return {children}; } return ( {children} ); } export function HeroCarousel({ banners }: { banners: HeroBanner[] }) { const t = useTranslations("hero"); const meta = useTranslations("meta"); const slides = banners.length ? banners : []; const [index, setIndex] = useState(0); const [paused, setPaused] = useState(false); const [playingId, setPlayingId] = useState(null); const videoRefs = useRef>({}); const current = slides[index] ?? slides[0]; const count = slides.length; const autoplayBlocked = paused || Boolean(playingId); const stopVideos = useCallback(() => { for (const video of Object.values(videoRefs.current)) { if (!video) continue; video.pause(); video.currentTime = 0; } setPlayingId(null); }, []); const go = useCallback( (next: number) => { if (!count) return; stopVideos(); setIndex((next + count) % count); }, [count, stopVideos], ); useEffect(() => { if (count < 2 || autoplayBlocked) return; const reduce = document.documentElement.dataset.motion === "reduce" || window.matchMedia("(prefers-reduced-motion: reduce)").matches; if (reduce) return; const duration = Math.max(4000, current?.durationMs || 7000); const timer = window.setTimeout(() => go(index + 1), duration); return () => window.clearTimeout(timer); }, [count, autoplayBlocked, current?.durationMs, go, index]); const toggleVideo = useCallback( (slide: HeroBanner) => { if (!slide.video) return; const video = videoRefs.current[slide.id]; if (!video) return; if (playingId === slide.id && !video.paused) { video.pause(); setPlayingId(null); return; } void video.play().then(() => setPlayingId(slide.id)).catch(() => setPlayingId(null)); }, [playingId], ); if (!current) return null; return ( setPaused(true)} onMouseLeave={() => setPaused(false)} onFocusCapture={() => setPaused(true)} onBlurCapture={(event) => { if (!event.currentTarget.contains(event.relatedTarget as Node | null)) { setPaused(false); } }} > {slides.map((slide, i) => ( // eslint-disable-next-line @next/next/no-img-element ))} {current.eyebrow || t("brand")} {meta("fullName")} {current.title} {current.subtitle ? ( {current.subtitle} ) : null} {current.ctaPrimaryLabel ? ( {current.ctaPrimaryLabel} ) : null} {current.ctaSecondaryLabel ? ( {current.ctaSecondaryLabel} ) : null} {(current.phone || current.address) && ( {current.phone ? ( {current.phone} ) : null} {current.phone && current.address ? ( · ) : null} {current.address} )} {slides.map((slide, i) => { const active = i === index; const playing = playingId === slide.id && active; return ( {slide.video ? ( { videoRefs.current[slide.id] = node; }} src={slide.video} poster={slide.image} playsInline preload="metadata" className="absolute inset-0 h-full w-full max-w-none object-cover object-center" onEnded={() => setPlayingId(null)} /> ) : null} {/* eslint-disable-next-line @next/next/no-img-element */} {slide.video ? ( toggleVideo(slide)} > {playing ? null : ( ▶ )} ) : null} ); })} {count > 1 ? ( go(index - 1)} > ‹ {slides.map((slide, i) => ( go(i)} /> ))} go(index + 1)} > › ) : null} ); }
{current.eyebrow || t("brand")}
{meta("fullName")}
{current.subtitle}
{current.phone ? ( {current.phone} ) : null} {current.phone && current.address ? ( · ) : null} {current.address}