skills/premium-frontend-design/templates/hero-section.tsx

309 lines
8.4 KiB
TypeScript

/**
* Hero Section Template
*
* Patterns for creating impactful hero sections with:
* - 3D backgrounds (particle sphere, data globe, racing)
* - Massive typography with scramble effects
* - Animated CTAs
* - Status badges
*/
import React, { useMemo, useRef, useState, useEffect } from 'react';
import { Canvas, useFrame } from '@react-three/fiber';
import { Stars } from '@react-three/drei';
import { motion } from 'framer-motion';
import { ChevronRight, Zap } from 'lucide-react';
import * as THREE from 'three';
// ============================================
// TEXT SCRAMBLE HOOK
// ============================================
export const useTextScramble = (text: string, active: boolean = true) => {
const [output, setOutput] = useState('');
const chars = '!<>-_\\/[]{}—=+*^?#';
useEffect(() => {
if (!active) return;
let iteration = 0;
const interval = setInterval(() => {
setOutput(
text.split('').map((char, i) => {
if (char === ' ') return ' ';
if (i < iteration) return char;
return chars[Math.floor(Math.random() * chars.length)];
}).join('')
);
if (iteration >= text.length) clearInterval(interval);
iteration += 1/3;
}, 30);
return () => clearInterval(interval);
}, [text, active]);
return output;
};
// ============================================
// 3D BACKGROUNDS
// ============================================
// Particle Sphere Background
export const ParticleSphereBackground: React.FC<{
color?: string;
count?: number;
}> = ({ color = '#ff4d00', count = 3000 }) => {
const pointsRef = useRef<THREE.Points>(null);
const positions = useMemo(() => {
const pos = new Float32Array(count * 3);
for (let i = 0; i < count; i++) {
const theta = Math.random() * Math.PI * 2;
const phi = Math.acos(Math.random() * 2 - 1);
const r = 2;
pos[i * 3] = r * Math.sin(phi) * Math.cos(theta);
pos[i * 3 + 1] = r * Math.sin(phi) * Math.sin(theta);
pos[i * 3 + 2] = r * Math.cos(phi);
}
return pos;
}, [count]);
useFrame(() => {
if (pointsRef.current) {
pointsRef.current.rotation.y += 0.001;
}
});
return (
<points ref={pointsRef}>
<bufferGeometry>
<bufferAttribute attach="attributes-position" count={count} array={positions} itemSize={3} />
</bufferGeometry>
<pointsMaterial size={0.02} color={color} transparent opacity={0.8} />
</points>
);
};
// Starfield Scene
export const StarfieldScene: React.FC<{ particleColor?: string }> = ({
particleColor = '#ff4d00'
}) => (
<>
<ambientLight intensity={0.2} />
<pointLight position={[10, 10, 10]} intensity={0.5} color={particleColor} />
<ParticleSphereBackground color={particleColor} />
<Stars radius={100} depth={50} count={2000} factor={3} />
</>
);
// ============================================
// UI COMPONENTS
// ============================================
// Status Badge
export const StatusBadge: React.FC<{
text: string;
icon?: React.ReactNode;
color?: string;
pulse?: boolean;
}> = ({
text,
icon = <Zap className="w-4 h-4" />,
color = '#ff4d00',
pulse = true
}) => (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-white/5 border border-white/10"
>
{pulse && (
<div
className="w-2 h-2 rounded-full animate-pulse"
style={{ backgroundColor: color }}
/>
)}
<span style={{ color }}>{icon}</span>
<span
className="text-sm font-mono uppercase tracking-wider"
style={{ color }}
>
{text}
</span>
</motion.div>
);
// Animated CTA Button
export const CTAButton: React.FC<{
children: React.ReactNode;
onClick?: () => void;
variant?: 'primary' | 'secondary';
color?: string;
icon?: React.ReactNode;
}> = ({
children,
onClick,
variant = 'primary',
color = '#ff4d00',
icon = <ChevronRight className="w-5 h-5" />
}) => (
<motion.button
onClick={onClick}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className={`
px-8 py-4 font-mono uppercase tracking-wider rounded-lg
flex items-center justify-center gap-2 group
${variant === 'primary'
? 'text-black font-bold'
: 'bg-white/5 border border-white/10 text-white hover:bg-white/10'
}
`}
style={variant === 'primary' ? { backgroundColor: color } : undefined}
>
{children}
{icon && (
<span className="group-hover:translate-x-1 transition-transform">
{icon}
</span>
)}
</motion.button>
);
// ============================================
// HERO SECTION TEMPLATES
// ============================================
// Template 1: Cyberpunk Hero
export const CyberpunkHero: React.FC<{
title: string;
subtitle?: string;
badge?: string;
primaryCTA?: { text: string; onClick: () => void };
secondaryCTA?: { text: string; onClick: () => void };
accentColor?: string;
}> = ({
title,
subtitle,
badge = 'SYSTEM ONLINE',
primaryCTA,
secondaryCTA,
accentColor = '#ff4d00',
}) => {
const scrambledTitle = useTextScramble(title, true);
return (
<div className="relative min-h-screen flex items-center justify-center overflow-hidden">
{/* 3D Background */}
<div className="absolute inset-0">
<Canvas camera={{ position: [0, 0, 6] }}>
<StarfieldScene particleColor={accentColor} />
</Canvas>
</div>
{/* Scanlines */}
<div className="absolute inset-0 pointer-events-none z-10 scanlines" />
{/* Content */}
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }}
className="relative z-20 text-center max-w-4xl px-6"
>
<StatusBadge text={badge} color={accentColor} />
<h1 className="text-5xl md:text-7xl lg:text-8xl font-bold tracking-tighter mt-8 mb-6">
<span className="bg-gradient-to-r from-white via-zinc-300 to-zinc-500 bg-clip-text text-transparent">
{scrambledTitle}
</span>
</h1>
{subtitle && (
<p className="text-xl text-zinc-400 mb-10 max-w-2xl mx-auto">
{subtitle}
</p>
)}
<div className="flex flex-col sm:flex-row gap-4 justify-center">
{primaryCTA && (
<CTAButton onClick={primaryCTA.onClick} color={accentColor}>
{primaryCTA.text}
</CTAButton>
)}
{secondaryCTA && (
<CTAButton onClick={secondaryCTA.onClick} variant="secondary" icon={null}>
{secondaryCTA.text}
</CTAButton>
)}
</div>
</motion.div>
</div>
);
};
// Template 2: Minimal Hero (no 3D)
export const MinimalHero: React.FC<{
title: string;
highlight?: string;
subtitle?: string;
accentColor?: string;
}> = ({
title,
highlight,
subtitle,
accentColor = '#ff4d00',
}) => (
<div className="min-h-screen flex items-center justify-center px-6">
<div className="text-center max-w-4xl">
<motion.h1
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
className="text-5xl md:text-7xl font-bold tracking-tight mb-6"
>
<span className="text-white">{title}</span>
{highlight && (
<>
<br />
<span style={{ color: accentColor }}>{highlight}</span>
</>
)}
</motion.h1>
{subtitle && (
<motion.p
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
className="text-xl text-zinc-400"
>
{subtitle}
</motion.p>
)}
</div>
</div>
);
// ============================================
// USAGE EXAMPLE
// ============================================
/*
import { CyberpunkHero } from './hero-section';
function LandingPage() {
return (
<CyberpunkHero
title="ENGINEERING THE FUTURE"
subtitle="Next-generation AI infrastructure for modern applications."
badge="PLATFORM LIVE"
primaryCTA={{ text: 'Get Started', onClick: () => {} }}
secondaryCTA={{ text: 'Documentation', onClick: () => {} }}
accentColor="#00f3ff"
/>
);
}
*/
export default { CyberpunkHero, MinimalHero, StatusBadge, CTAButton };