143 lines
5.2 KiB
TypeScript
143 lines
5.2 KiB
TypeScript
import { Link } from 'react-router-dom';
|
|
import type { HeroVideoSectionPayload } from '@homaroma/types';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { getLocalizedField } from '../utils/localization';
|
|
|
|
interface DynamicHeroVideoProps {
|
|
payload: HeroVideoSectionPayload;
|
|
}
|
|
|
|
export default function DynamicHeroVideo({ payload }: DynamicHeroVideoProps) {
|
|
const { t, i18n } = useTranslation();
|
|
const lang = i18n.language;
|
|
|
|
const tagline = getLocalizedField(payload.tagline, lang);
|
|
const title = getLocalizedField(payload.title, lang);
|
|
const description = getLocalizedField(payload.description, lang);
|
|
const buttonText = getLocalizedField(payload.buttonText, lang);
|
|
const secondaryButtonText = getLocalizedField(payload.secondaryButtonText, lang);
|
|
|
|
const videoUrl = payload.videoUrl;
|
|
const posterImageUrl = payload.posterImageUrl;
|
|
const overlayOpacity = typeof payload.overlayOpacity === 'number' ? payload.overlayOpacity : 40;
|
|
const height = payload.height || 'large';
|
|
|
|
const focalPoint = typeof payload.focalPoint === 'object' && payload.focalPoint ? (payload.focalPoint as { x: string, y: string }) : undefined;
|
|
|
|
const getHeightClass = (h: string) => {
|
|
switch (h) {
|
|
case 'full':
|
|
return 'h-screen';
|
|
case 'medium':
|
|
return 'h-[60vh] min-h-[400px]';
|
|
case 'large':
|
|
default:
|
|
return 'h-[85vh] min-h-[550px]';
|
|
}
|
|
};
|
|
|
|
const renderTitle = (text: string) => {
|
|
if (!text) return null;
|
|
return text.split('[').map((part, i) => {
|
|
if (part.includes(']')) {
|
|
const [accent, rest] = part.split(']');
|
|
return (
|
|
<span key={i}>
|
|
<span className="text-pop-pink font-serif italic font-normal">
|
|
{accent}
|
|
</span>
|
|
{rest}
|
|
</span>
|
|
);
|
|
}
|
|
return <span key={i}>{part}</span>;
|
|
});
|
|
};
|
|
|
|
return (
|
|
<section
|
|
className={`relative w-full ${getHeightClass(height)} overflow-hidden flex items-center justify-center`}
|
|
style={{ backgroundColor: payload.backgroundColor || '#0B0F17', color: payload.textColor || '#FFFFFF' }}
|
|
>
|
|
{/* Background Video */}
|
|
{videoUrl ? (
|
|
<video
|
|
autoPlay={payload.autoplay !== false}
|
|
muted={payload.muted !== false}
|
|
loop={payload.loop !== false}
|
|
playsInline
|
|
poster={posterImageUrl}
|
|
className="absolute inset-0 w-full h-full object-cover z-0"
|
|
style={{ objectPosition: focalPoint ? `${focalPoint.x} ${focalPoint.y}` : undefined }}
|
|
>
|
|
<source src={videoUrl} type="video/mp4" />
|
|
<source src={videoUrl} type="video/webm" />
|
|
{posterImageUrl && (
|
|
<img
|
|
src={posterImageUrl}
|
|
alt="Hero fallback"
|
|
className="w-full h-full object-cover"
|
|
style={{ objectPosition: focalPoint ? `${focalPoint.x} ${focalPoint.y}` : undefined }}
|
|
/>
|
|
)}
|
|
</video>
|
|
) : posterImageUrl ? (
|
|
<img
|
|
src={posterImageUrl}
|
|
alt="Hero Poster"
|
|
className="absolute inset-0 w-full h-full object-cover z-0"
|
|
style={{ objectPosition: focalPoint ? `${focalPoint.x} ${focalPoint.y}` : undefined }}
|
|
/>
|
|
) : null}
|
|
|
|
{/* Customizable Overlay Opacity */}
|
|
<div
|
|
className="absolute inset-0 z-10 bg-black transition-opacity"
|
|
style={{ opacity: overlayOpacity / 100 }}
|
|
></div>
|
|
|
|
{/* Overlay Typography Content */}
|
|
<div className="relative z-20 mx-auto px-6 py-16 w-full max-w-5xl text-center space-y-6 flex flex-col items-center justify-center animate-fade-in">
|
|
{tagline && (
|
|
<p className="text-xs md:text-sm font-semibold uppercase tracking-[0.35em] text-pop-pink/90 drop-shadow-md">
|
|
{tagline}
|
|
</p>
|
|
)}
|
|
|
|
<h1 className="font-serif text-4xl md:text-6xl lg:text-7xl font-light leading-tight tracking-wide drop-shadow-lg text-white">
|
|
{renderTitle(title) || renderTitle(t('hero.defaultTitle'))}
|
|
</h1>
|
|
|
|
{description && (
|
|
<p className="max-w-2xl text-sm md:text-lg font-light leading-relaxed opacity-90 drop-shadow">
|
|
{description}
|
|
</p>
|
|
)}
|
|
|
|
{/* Dual Action Buttons */}
|
|
{(buttonText || secondaryButtonText) && (
|
|
<div className="flex flex-wrap items-center justify-center gap-4 pt-4">
|
|
{buttonText && payload.buttonLink && (
|
|
<Link
|
|
to={payload.buttonLink}
|
|
className="px-8 py-3.5 bg-white text-neutral-900 font-medium text-xs uppercase tracking-widest hover:bg-pop-pink hover:text-white transition-all shadow-lg rounded-sm"
|
|
>
|
|
{buttonText}
|
|
</Link>
|
|
)}
|
|
|
|
{secondaryButtonText && payload.secondaryButtonLink && (
|
|
<Link
|
|
to={payload.secondaryButtonLink}
|
|
className="px-8 py-3.5 bg-transparent border border-white/40 text-white font-medium text-xs uppercase tracking-widest hover:bg-white/10 hover:border-white transition-all rounded-sm backdrop-blur-sm"
|
|
>
|
|
{secondaryButtonText}
|
|
</Link>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|