Spaces:
Sleeping
Sleeping
File size: 8,945 Bytes
35a92dd | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 | import React, { useRef, useEffect, useState, useMemo } from 'react';
interface HeroOverlayProps {
visible: boolean;
smoothScrollRef: React.MutableRefObject<number>;
shiftRef: React.MutableRefObject<number>;
shapeMode: string;
isMobile: boolean;
}
// --- OPTIMIZED ANIMATION COMPONENT ---
// Memoized to prevent unnecessary re-renders when parent state changes (like the clock)
const CinematicReveal = React.memo(({ text, delay = 0, visible, className }: { text: string; delay?: number; visible: boolean; className?: string }) => {
return (
<span className={`inline-block whitespace-nowrap ${className}`}>
{text.split('').map((char, i) => (
<span
key={i}
style={{
transitionDelay: `${delay + (i * 35)}ms`,
transitionDuration: '1000ms',
transitionTimingFunction: 'cubic-bezier(0.2, 0.65, 0.3, 0.9)',
}}
className={`inline-block transition-all will-change-transform ${
visible
? 'opacity-100 translate-y-0 filter-none scale-100'
: 'opacity-0 translate-y-[100%] blur-sm scale-110'
}`}
>
{char === " " ? "\u00A0" : char}
</span>
))}
</span>
);
});
export const HeroOverlay: React.FC<HeroOverlayProps> = ({ visible, smoothScrollRef, shiftRef, shapeMode, isMobile }) => {
// Refs for direct DOM manipulation (High Performance)
const contentRef = useRef<HTMLDivElement>(null);
const blurRef = useRef<HTMLDivElement>(null);
// State for Live Date
const [dateTime, setDateTime] = useState({ day: '', date: '' });
// --- LIVE DATE LOGIC ---
useEffect(() => {
const updateTime = () => {
const now = new Date();
// Format: "SATURDAY"
const day = now.toLocaleDateString('en-US', { weekday: 'long' }).toUpperCase();
// Format: "DEC 20 2025"
const date = now.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }).toUpperCase().replace(',', '');
setDateTime({ day, date });
};
updateTime();
// Update every minute (efficient, no need for second-by-second ticking here)
const timer = setInterval(updateTime, 60000);
return () => clearInterval(timer);
}, []);
const getStatus = () => {
if (shapeMode === 'triangle') return 'AWAITING INPUT...';
if (shapeMode === 'explode') return 'PROCESSING DATA...';
return 'SYSTEM ONLINE';
};
const getStatusColor = () => {
if (shapeMode === 'triangle') return 'text-red-500';
if (shapeMode === 'explode') return 'text-blue-400';
return 'text-emerald-500';
};
// --- SCROLL LOOP (GPU OPTIMIZED) ---
useEffect(() => {
let frameId: number;
const update = () => {
if (!visible) return;
const y = smoothScrollRef.current;
const h = window.innerHeight;
const processItem = (ref: React.RefObject<HTMLDivElement>, type: 'text' | 'bg', start: number, fade: number = 200) => {
if (!ref.current) return;
let opacity = 0;
let transY = 0;
let scale = 1;
// Logic: Everything fades out as you scroll down
if (y < start + fade) {
opacity = 1 - (y / (start + fade));
// Only move/scale the text layer, keep the background fixed so it just fades
if (type === 'text') {
transY = -y * 0.5;
scale = 1 - (y / (start + fade)) * 0.05;
}
} else {
opacity = 0;
}
ref.current.style.opacity = Math.max(0, opacity).toFixed(3);
if (type === 'text') {
ref.current.style.transform = `translate3d(0, ${transY.toFixed(3)}px, 0) scale(${scale.toFixed(3)})`;
}
};
// Apply to Content (Text): Fades and moves up
processItem(contentRef, 'text', 0, 0.8 * h);
// Apply to Blur Layer: Fades out slightly faster to reveal content below
processItem(blurRef, 'bg', 0, 0.7 * h);
// Globe Lateral Shifts Logic
const SHIFT = isMobile ? 0 : 5.5;
let tx = 0;
if (!isMobile) {
if (y > 0.8 * h && y < 2.0 * h) tx = SHIFT;
}
if (shiftRef) shiftRef.current = tx;
frameId = requestAnimationFrame(update);
};
update();
return () => cancelAnimationFrame(frameId);
}, [visible, smoothScrollRef, shiftRef, isMobile]);
return (
<div className={`fixed inset-0 z-20 pointer-events-none transition-opacity duration-1000 ${visible ? 'opacity-100' : 'opacity-0'}`}>
{/*
FULL SCREEN BLUR LAYER
- inset-0: Covers the whole screen.
- backdrop-blur-[2px]: Subtle glass effect.
- bg-black/5: Slight tint for readability.
- Fades out on scroll via blurRef.
*/}
<div
ref={blurRef}
className="absolute inset-0 bg-black/5 backdrop-blur-[2px] -z-10"
/>
{/* Hero Content Wrapper */}
<div ref={contentRef} className="absolute inset-0 flex flex-col justify-center px-6 md:px-12 pointer-events-none">
{/* Content Container - Left Aligned */}
<div className="w-full max-w-[95%] md:max-w-4xl mr-auto relative z-10 text-left">
{/* Live Date & Day Tags */}
<div className={`flex flex-row items-start justify-start gap-3 mb-8 md:mb-6 transition-opacity duration-1000 delay-500 ${visible ? 'opacity-50' : 'opacity-0'}`}>
<span className="text-[9px] md:text-[10px] font-mono border border-white/30 px-2 py-0.5 rounded-full uppercase tracking-widest text-white">
{dateTime.day || "LOADING..."}
</span>
<span className="text-[9px] md:text-[10px] font-mono border border-white/30 px-2 py-0.5 rounded-full uppercase tracking-widest text-white">
{dateTime.date || "..."}
</span>
</div>
{/* Main Headline */}
<h1 className="flex flex-col gap-1 md:gap-2 text-white/90 leading-[1.0] md:leading-[0.85]">
<span className="text-[11vw] md:text-[8rem] lg:text-[9rem] font-bold md:font-medium tracking-tighter mix-blend-screen overflow-hidden text-left">
<CinematicReveal text="DESIGNING" visible={visible} delay={100} />
</span>
<span className="text-[11vw] md:text-[8rem] lg:text-[9rem] font-serif italic text-red-500 mix-blend-screen -mt-2 md:-mt-4 overflow-hidden text-left">
<CinematicReveal text="INTELLIGENCE" visible={visible} delay={400} />
</span>
</h1>
{/* Subtext */}
<div className={`mt-8 md:mt-12 flex flex-col md:flex-row items-start justify-start gap-8 transition-all duration-1000 delay-[900ms] ${visible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-8'}`}>
<div className="h-[1px] w-12 md:w-24 bg-red-500/50 block mt-3"></div>
<p className="text-white/60 max-w-[85%] md:max-w-md font-mono text-xs md:text-sm leading-relaxed text-left mx-0">
We are an advanced R&D collective specializing in high-frequency neural networks and generative interface design.
<span className="block mt-4 text-white/40">
Building the cognitive architecture for the next web.
</span>
</p>
</div>
</div>
</div>
{/* Bottom Status Bar */}
<div className="absolute bottom-0 left-0 w-full p-6 md:p-12 z-40 transition-opacity duration-1000 delay-[1200ms]" style={{ opacity: visible ? 1 : 0 }}>
<div className="w-full border-t border-white/10 pt-4 flex flex-col md:flex-row justify-between items-end gap-4">
<div className="font-mono text-[9px] md:text-[10px] text-white/60 tracking-wider flex items-center gap-2">
<span className="text-red-500">➜</span>
<span>{shapeMode === 'triangle' ? 'EXEC: NEURAL_VOICE_PROTOCOL' : 'EXEC: TRIANGLE_MAIN_LOOP'}</span>
<span className="w-1.5 h-3 bg-red-500/80 animate-pulse ml-1"></span>
</div>
<div className="flex gap-6 font-mono text-[9px] text-white/30 tracking-widest uppercase md:pr-12">
<div>
<span className="block text-white/10 mb-1">System</span>
<span className={getStatusColor() + " animate-pulse"}>{getStatus()}</span>
</div>
<div className="hidden md:block">
<span className="block text-white/10 mb-1">Latency</span>
<span>12ms</span>
</div>
<div className="hidden md:block">
<span className="block text-white/10 mb-1">Build</span>
<span>v2.0.4-RC</span>
</div>
</div>
</div>
</div>
</div>
)
} |