Components
A high-end, immersive hero component featuring a 3D parallax image card, refractive glass overlays, and dynamic typography. Designed for photography portfolios or studio landing pages.
Loading preview...
import React, { useState, useEffect, useRef } from 'react';
const OpticStudio: React.FC = () => {
const [activeIndex, setActiveIndex] = useState(0);
const [mousePos, setMousePos] = useState({ x: 50, y: 50 });
const [tilt, setTilt] = useState({ x: 0, y: 0 });
const sceneRef = useRef<HTMLDivElement>(null);
const images = [
"https://images.unsplash.com/photo-1533154683836-84ea7a0bc310?auto=format&fit=crop&q=80&w=1200",
"https://images.unsplash.com/photo-1493238792000-8113da705763?auto=format&fit=crop&q=80&w=1200",
"https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&q=80&w=1200"
];
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
// Update global mouse percentage for grid/spotlight
const xPct = (e.clientX / window.innerWidth) * 100;
const yPct = (e.clientY / window.innerHeight) * 100;
setMousePos({ x: xPct, y: yPct });
// Calculate 3D Tilt
const xAxis = (window.innerWidth / 2 - e.clientX) / 25;
const yAxis = (window.innerHeight / 2 - e.clientY) / 25;
setTilt({ x: xAxis, y: yAxis });
};
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);
const resetTilt = () => setTilt({ x: 0, y: 0 });
return (
<div style={containerStyle}>
{/* Dynamic Styles Injection */}
<style>{`
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@900&family=JetBrains+Mono:wght@300;500&family=Playfair+Display:ital,wght@0,900;1,900&display=swap');
:root {
--bg: #050505;
--accent: #e0e0e0;
--primary: #ffffff;
--dim: #444;
--glass: rgba(255, 255, 255, 0.03);
--border: rgba(255, 255, 255, 0.1);
--font-main: 'Inter', sans-serif;
--font-serif: 'Playfair Display', serif;
--font-mono: 'JetBrains Mono', monospace;
}
@keyframes pulse {
0% { transform: translate(-50%, -50%) scale(1); opacity: 0.3; }
100% { transform: translate(-50%, -55%) scale(1.2); opacity: 0.5; }
}
.word-reveal {
display: block;
overflow: hidden;
animation: reveal 1s cubic-bezier(0.23, 1, 0.32, 1) forwards;
opacity: 0;
transform: translateY(50px);
}
@keyframes reveal {
to { opacity: 1; transform: translateY(0); }
}
`}</style>
{/* Background Layers */}
<div style={{
...opticGridStyle,
'--mouse-x': `${mousePos.x}%`,
'--mouse-y': `${mousePos.y}%`
} as React.CSSProperties} className="optic-grid-effect" />
<div style={ambientGlowStyle} />
{/* HUD Navigation */}
<div style={hudStyle}>
<div style={hudTopStyle}>
<div style={{ fontFamily: 'var(--font-mono)', fontWeight: 500, fontSize: '1.2rem', cursor: 'pointer', pointerEvents: 'auto' }}>
OPTIC // STUDIO
</div>
<nav style={navLinksStyle}>
{['ARCHIVE', 'EXHIBITIONS', 'PROCESS', 'STUDIO'].map(link => (
<a key={link} href={`#${link}`} style={navLinkItemStyle}>{link}</a>
))}
</nav>
</div>
<div style={viewfinderStyle} />
<div style={{ ...focusCornerStyle, top: '4rem', left: '4rem', borderRight: 'none', borderBottom: 'none' }} />
<div style={{ ...focusCornerStyle, top: '4rem', right: '4rem', borderLeft: 'none', borderBottom: 'none' }} />
<div style={{ ...focusCornerStyle, bottom: '4rem', left: '4rem', borderRight: 'none', borderTop: 'none' }} />
<div style={{ ...focusCornerStyle, bottom: '4rem', right: '4rem', borderLeft: 'none', borderTop: 'none' }} />
</div>
{/* Main Content */}
<main style={heroStyle}>
<div style={{ zIndex: 20 }}>
<h1 style={headlineStyle}>
<span className="word-reveal" style={{ animationDelay: '0.2s' }}>Moments</span>
<span className="word-reveal" style={{
animationDelay: '0.35s',
fontFamily: 'var(--font-serif)',
fontStyle: 'italic',
color: 'transparent',
WebkitTextStroke: '1px var(--primary)',
marginLeft: '10%'
}}>That</span>
<span className="word-reveal" style={{ animationDelay: '0.5s', marginTop: '-0.1em' }}>Breathe.</span>
</h1>
</div>
<div
ref={sceneRef}
style={heroRightStyle}
onMouseLeave={resetTilt}
>
<div style={{
...imageContainerStyle,
transform: `rotateY(${tilt.x}deg) rotateX(${tilt.y}deg)`
}}>
<img
src={images[activeIndex]}
alt="Cinematic"
style={mainImageStyle}
/>
<div style={{
...refractiveOverlayStyle,
maskImage: `radial-gradient(circle at ${mousePos.x}% ${mousePos.y}%, transparent 150px, black 300px)`,
WebkitMaskImage: `radial-gradient(circle at ${mousePos.x}% ${mousePos.y}%, transparent 150px, black 300px)`
}} />
<div style={{ ...dataLabelStyle, top: '10%', right: '-5%' }}>LOC: 35.6895° N, 139.6917° E</div>
<div style={{ ...dataLabelStyle, bottom: '20%', left: '-10%' }}>CAM: 50MM F/1.2 LENS</div>
<div style={{ ...dataLabelStyle, top: '40%', left: '-15%' }}>STORY: THE SILENT VIGIL</div>
</div>
</div>
</main>
{/* Footer Preview */}
<div style={scrollPreviewStyle}>
{images.map((img, i) => (
<div
key={i}
onClick={() => setActiveIndex(i)}
style={{
...previewBoxStyle,
width: activeIndex === i ? '100px' : '60px',
borderColor: activeIndex === i ? 'var(--primary)' : 'var(--border)'
}}
>
<img src={img} alt="preview" style={{ ...previewImgStyle, opacity: activeIndex === i ? 1 : 0.4 }} />
</div>
))}
</div>
</div>
);
};
// --- Styles Object ---
const containerStyle: React.CSSProperties = {
backgroundColor: '#050505',
color: '#ffffff',
fontFamily: "'Inter', sans-serif",
height: '100vh',
width: '100vw',
overflow: 'hidden',
position: 'relative'
};
const opticGridStyle: React.CSSProperties = {
position: 'fixed',
inset: 0,
backgroundImage: `linear-gradient(rgba(255,255,255,0.1) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.1) 1px, transparent 1px)`,
backgroundSize: '80px 80px',
backgroundPosition: 'center center',
opacity: 0.2,
zIndex: 1,
pointerEvents: 'none'
};
const ambientGlowStyle: React.CSSProperties = {
position: 'fixed',
width: '60vw',
height: '60vw',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
background: 'radial-gradient(circle, rgba(255,255,255,0.05) 0%, transparent 70%)',
filter: 'blur(100px)',
zIndex: 0,
animation: 'pulse 10s infinite alternate ease-in-out'
};
const heroStyle: React.CSSProperties = {
position: 'relative',
display: 'grid',
gridTemplateColumns: '1fr 1.2fr',
height: '100vh',
width: '100%',
zIndex: 10,
padding: '4rem',
alignItems: 'center'
};
const headlineStyle: React.CSSProperties = {
fontSize: 'clamp(4rem, 10vw, 12rem)',
lineHeight: 0.85,
fontWeight: 900,
textTransform: 'uppercase',
letterSpacing: '-0.04em',
pointerEvents: 'none'
};
const heroRightStyle: React.CSSProperties = {
position: 'relative',
height: '80vh',
perspective: '2000px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
};
const imageContainerStyle: React.CSSProperties = {
position: 'relative',
width: '80%',
height: '100%',
transformStyle: 'preserve-3d',
transition: 'transform 0.6s cubic-bezier(0.23, 1, 0.32, 1)'
};
const mainImageStyle: React.CSSProperties = {
width: '100%',
height: '100%',
objectFit: 'cover',
filter: 'grayscale(0.2) contrast(1.1)',
boxShadow: '0 50px 100px rgba(0,0,0,0.5)',
transition: 'opacity 0.3s ease'
};
const refractiveOverlayStyle: React.CSSProperties = {
position: 'absolute',
inset: '-20px',
backdropFilter: 'blur(0px)',
border: '1px solid rgba(255,255,255,0.1)',
zIndex: 25,
pointerEvents: 'none',
background: 'linear-gradient(135deg, rgba(255,255,255,0.05) 0%, transparent 100%)'
};
const dataLabelStyle: React.CSSProperties = {
position: 'absolute',
fontFamily: 'var(--font-mono)',
fontSize: '0.7rem',
textTransform: 'uppercase',
background: 'rgba(255, 255, 255, 0.03)',
backdropFilter: 'blur(10px)',
padding: '8px 12px',
border: '1px solid rgba(255, 255, 255, 0.1)',
letterSpacing: '0.1em',
display: 'flex',
alignItems: 'center',
gap: '10px',
zIndex: 40,
color: '#fff'
};
const hudStyle: React.CSSProperties = {
position: 'fixed',
inset: 0,
pointerEvents: 'none',
zIndex: 100,
padding: '2rem'
};
const hudTopStyle: React.CSSProperties = {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'flex-start'
};
const navLinksStyle: React.CSSProperties = {
display: 'flex',
gap: '3rem',
fontFamily: 'var(--font-mono)',
fontSize: '0.8rem',
pointerEvents: 'auto'
};
const navLinkItemStyle: React.CSSProperties = {
color: '#fff',
textDecoration: 'none',
opacity: 0.6
};
const focusCornerStyle: React.CSSProperties = {
position: 'absolute',
width: '40px',
height: '40px',
border: '1px solid #444'
};
const scrollPreviewStyle: React.CSSProperties = {
position: 'absolute',
bottom: '4rem',
left: '4rem',
display: 'flex',
gap: '1rem',
zIndex: 50
};
const previewBoxStyle: React.CSSProperties = {
height: '80px',
background: '#444',
position: 'relative',
overflow: 'hidden',
border: '1px solid rgba(255,255,255,0.1)',
transition: '0.5s cubic-bezier(0.23, 1, 0.32, 1)',
cursor: 'pointer'
};
const previewImgStyle: React.CSSProperties = {
width: '100%',
height: '100%',
objectFit: 'cover',
transition: '0.5s'
};
const viewfinderStyle: React.CSSProperties = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: '30px',
height: '30px',
border: '1px solid transparent'
};
export default OpticStudio;