Spaces:
Sleeping
Sleeping
| import React, { useEffect, useRef, useState } from 'react'; | |
| import { Link } from 'react-router-dom'; | |
| import MagneticButton from './MagneticButton.jsx'; | |
| import ImageFlex from './ImageFlex.jsx'; | |
| export default function Hero() { | |
| const mediaRef = useRef(null); | |
| const videoRef = useRef(null); | |
| const [mounted, setMounted] = useState(false); | |
| const [videoLoaded, setVideoLoaded] = useState(false); | |
| useEffect(() => { | |
| setMounted(true); | |
| // Check if video loads successfully | |
| if (videoRef.current) { | |
| const handleLoadedData = () => setVideoLoaded(true); | |
| const handleError = () => setVideoLoaded(false); | |
| videoRef.current.addEventListener('loadeddata', handleLoadedData); | |
| videoRef.current.addEventListener('error', handleError); | |
| return () => { | |
| if (videoRef.current) { | |
| videoRef.current.removeEventListener('loadeddata', handleLoadedData); | |
| videoRef.current.removeEventListener('error', handleError); | |
| } | |
| }; | |
| } | |
| }, []); | |
| useEffect(() => { | |
| if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; | |
| const onScroll = () => { | |
| const y = window.scrollY; | |
| const el = videoLoaded ? videoRef.current : mediaRef.current; | |
| if (!el) return; | |
| // Subtle parallax translate | |
| el.style.transform = `translateY(${Math.min(40, y * 0.15)}px) scale(1.05)`; | |
| }; | |
| window.addEventListener('scroll', onScroll, { passive: true }); | |
| return () => window.removeEventListener('scroll', onScroll); | |
| }, [videoLoaded]); | |
| return ( | |
| <section | |
| className="relative flex min-h-screen items-end overflow-hidden bg-slate-900 text-white mb-0" | |
| aria-label="Hero" | |
| style={{ zIndex: 1 }} | |
| > | |
| {/* Video background with image fallback */} | |
| <video | |
| ref={videoRef} | |
| autoPlay | |
| loop | |
| muted | |
| playsInline | |
| className={`absolute inset-0 h-full w-full object-cover will-change-transform ${ | |
| videoLoaded ? 'opacity-100' : 'opacity-0' | |
| }`} | |
| style={{ | |
| transform: mounted ? 'scale(1.05)' : undefined, | |
| filter: 'brightness(1.12) contrast(1.06)' | |
| }} | |
| aria-hidden="true" | |
| > | |
| <source src="/assets/hero-video.mp4" type="video/mp4" /> | |
| {/* TODO: replace with actual video path */} | |
| </video> | |
| <ImageFlex | |
| base="/assets/hero" /* TODO: replace */ | |
| alt="" | |
| className={`absolute inset-0 h-full w-full object-cover will-change-transform ${ | |
| videoLoaded ? 'opacity-0' : 'opacity-100' | |
| }`} | |
| style={{ | |
| transform: mounted ? 'scale(1.05)' : undefined, | |
| filter: 'brightness(1.12) contrast(1.06)' | |
| }} | |
| ref={mediaRef} | |
| /> | |
| <div className="absolute inset-0 bg-slate-900/40" aria-hidden="true"></div> | |
| {/* Overlay logo at top-right */} | |
| <div className="absolute right-0 top-0 z-10"> | |
| <div className="rounded-bl-xl bg-white/30 px-3 py-2 shadow-lg backdrop-blur-md ring-1 ring-white/20 md:px-4 md:py-3"> | |
| <ImageFlex | |
| base="/assets/logo" | |
| alt="Jade Infra" | |
| className="h-14 w-auto md:h-18 lg:h-22" | |
| /> | |
| </div> | |
| </div> | |
| {/* Heading with equal left and bottom margins regardless of media aspect */} | |
| <div className="absolute left-6 bottom-6 z-10 md:left-10 md:bottom-10 lg:left-16 lg:bottom-16"> | |
| <div className="max-w-3xl"> | |
| <h1 className="h1 text-white">Building Tomorrow with Quality and Trust</h1> | |
| </div> | |
| </div> | |
| </section> | |
| ); | |
| } |