Complete Next.js website with Docker containerization: - Next.js 14 with TypeScript and Tailwind CSS - Responsive design with modern UI components - Hero section, features showcase, testimonials - FAQ section with comprehensive content - Contact forms and newsletter signup - Docker production build with Nginx - Health checks and monitoring support - SEO optimization and performance tuning Ready for integration as git submodule in main CHORUS project. Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
498 lines
16 KiB
TypeScript
498 lines
16 KiB
TypeScript
'use client';
|
|
|
|
import React, { useEffect, useRef, useState } from 'react';
|
|
import { motion, useScroll, useTransform, useSpring, useInView } from 'framer-motion';
|
|
import { Typography, Space } from 'antd';
|
|
import { PlayIcon, ArrowRightIcon, ChevronDownIcon } from 'lucide-react';
|
|
import { PrimaryButton, SecondaryButton } from '@/components/ui/Button';
|
|
import { useReducedMotion } from '@/hooks/useReducedMotion';
|
|
|
|
const { Title, Paragraph } = Typography;
|
|
|
|
interface ParallaxLayerProps {
|
|
children: React.ReactNode;
|
|
speed: number;
|
|
className?: string;
|
|
}
|
|
|
|
const ParallaxLayer: React.FC<ParallaxLayerProps> = ({ children, speed, className = '' }) => {
|
|
const { scrollY } = useScroll();
|
|
const prefersReducedMotion = useReducedMotion();
|
|
|
|
const y = useTransform(scrollY, [0, 1000], [0, prefersReducedMotion ? 0 : speed * 100]);
|
|
const smoothY = useSpring(y, { stiffness: 100, damping: 30 });
|
|
|
|
return (
|
|
<motion.div
|
|
className={`${className} will-change-transform gpu-accelerated`}
|
|
style={{ y: prefersReducedMotion ? 0 : smoothY }}
|
|
>
|
|
{children}
|
|
</motion.div>
|
|
);
|
|
};
|
|
|
|
const FloatingElement: React.FC<{ delay: number; children: React.ReactNode }> = ({ delay, children }) => {
|
|
const prefersReducedMotion = useReducedMotion();
|
|
|
|
return (
|
|
<motion.div
|
|
className="will-change-transform gpu-accelerated"
|
|
animate={prefersReducedMotion ? {} : {
|
|
y: [-10, 10, -10],
|
|
rotate: [-2, 2, -2],
|
|
}}
|
|
transition={{
|
|
duration: prefersReducedMotion ? 0 : 6,
|
|
delay: prefersReducedMotion ? 0 : delay,
|
|
repeat: prefersReducedMotion ? 0 : Infinity,
|
|
ease: "easeInOut",
|
|
}}
|
|
>
|
|
{children}
|
|
</motion.div>
|
|
);
|
|
};
|
|
|
|
const GeometricPattern: React.FC = () => {
|
|
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
|
|
const prefersReducedMotion = useReducedMotion();
|
|
const heroRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
const handleMouseMove = (e: MouseEvent) => {
|
|
if (heroRef.current && !prefersReducedMotion) {
|
|
const rect = heroRef.current.getBoundingClientRect();
|
|
setMousePosition({
|
|
x: (e.clientX - rect.left) / rect.width,
|
|
y: (e.clientY - rect.top) / rect.height,
|
|
});
|
|
}
|
|
};
|
|
|
|
const heroElement = heroRef.current;
|
|
if (heroElement) {
|
|
heroElement.addEventListener('mousemove', handleMouseMove);
|
|
|
|
return () => {
|
|
heroElement.removeEventListener('mousemove', handleMouseMove);
|
|
};
|
|
}
|
|
}, [prefersReducedMotion]);
|
|
|
|
return (
|
|
<div ref={heroRef} className="absolute inset-0 overflow-hidden pointer-events-none" aria-hidden="true">
|
|
{/* Enhanced animated gradient background */}
|
|
<motion.div
|
|
className="absolute inset-0 opacity-30"
|
|
animate={prefersReducedMotion ? {} : {
|
|
background: [
|
|
'radial-gradient(circle at 20% 50%, rgba(0, 122, 255, 0.15) 0%, transparent 50%)',
|
|
'radial-gradient(circle at 80% 20%, rgba(48, 209, 88, 0.15) 0%, transparent 50%)',
|
|
'radial-gradient(circle at 40% 80%, rgba(0, 122, 255, 0.15) 0%, transparent 50%)',
|
|
'radial-gradient(circle at 60% 30%, rgba(48, 209, 88, 0.12) 0%, transparent 60%)',
|
|
],
|
|
}}
|
|
transition={{
|
|
duration: prefersReducedMotion ? 0 : 12,
|
|
repeat: prefersReducedMotion ? 0 : Infinity,
|
|
ease: "easeInOut",
|
|
}}
|
|
/>
|
|
|
|
{/* Particle system - responsive dots */}
|
|
{[...Array(16)].map((_, i) => (
|
|
<motion.div
|
|
key={`particle-${i}`}
|
|
className="absolute w-1 h-1 bg-white/20 rounded-full"
|
|
style={{
|
|
left: `${15 + (i * 5.5)}%`,
|
|
top: `${25 + Math.sin(i * 0.8) * 25}%`,
|
|
}}
|
|
animate={prefersReducedMotion ? {} : {
|
|
scale: [1, 1.8, 1],
|
|
opacity: [0.2, 0.7, 0.2],
|
|
x: mousePosition.x * (8 + i * 1.5),
|
|
y: mousePosition.y * (4 + i * 0.8),
|
|
}}
|
|
transition={{
|
|
duration: prefersReducedMotion ? 0 : 4 + i * 0.3,
|
|
repeat: prefersReducedMotion ? 0 : Infinity,
|
|
ease: "easeInOut",
|
|
}}
|
|
/>
|
|
))}
|
|
|
|
{/* Enhanced geometric shapes - hexagons */}
|
|
{[...Array(8)].map((_, i) => (
|
|
<motion.div
|
|
key={`hex-${i}`}
|
|
className="absolute w-6 h-6 border border-white/8"
|
|
style={{
|
|
left: `${8 + (i * 12)}%`,
|
|
top: `${15 + Math.cos(i * 0.6) * 35}%`,
|
|
clipPath: 'polygon(30% 0%, 70% 0%, 100% 50%, 70% 100%, 30% 100%, 0% 50%)',
|
|
}}
|
|
animate={prefersReducedMotion ? {} : {
|
|
rotate: [0, 360],
|
|
scale: [1, 1.3, 1],
|
|
opacity: [0.08, 0.25, 0.08],
|
|
}}
|
|
transition={{
|
|
duration: prefersReducedMotion ? 0 : 10 + i * 2.5,
|
|
repeat: prefersReducedMotion ? 0 : Infinity,
|
|
ease: "linear",
|
|
}}
|
|
/>
|
|
))}
|
|
|
|
{/* Grid pattern overlay */}
|
|
<motion.div
|
|
className="absolute inset-0 opacity-5"
|
|
style={{
|
|
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: '50px 50px',
|
|
}}
|
|
animate={prefersReducedMotion ? {} : {
|
|
opacity: [0.02, 0.08, 0.02],
|
|
}}
|
|
transition={{
|
|
duration: prefersReducedMotion ? 0 : 8,
|
|
repeat: prefersReducedMotion ? 0 : Infinity,
|
|
ease: "easeInOut",
|
|
}}
|
|
/>
|
|
|
|
{/* Additional floating circles for depth */}
|
|
{[...Array(4)].map((_, i) => (
|
|
<motion.div
|
|
key={`circle-${i}`}
|
|
className="absolute rounded-full border border-white/5"
|
|
style={{
|
|
width: `${60 + i * 40}px`,
|
|
height: `${60 + i * 40}px`,
|
|
left: `${20 + i * 20}%`,
|
|
top: `${10 + i * 15}%`,
|
|
}}
|
|
animate={prefersReducedMotion ? {} : {
|
|
scale: [1, 1.1, 1],
|
|
opacity: [0.03, 0.12, 0.03],
|
|
rotate: [0, 180, 360],
|
|
}}
|
|
transition={{
|
|
duration: prefersReducedMotion ? 0 : 15 + i * 5,
|
|
repeat: prefersReducedMotion ? 0 : Infinity,
|
|
ease: "easeInOut",
|
|
}}
|
|
/>
|
|
))}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const AnimatedText: React.FC<{ children: React.ReactNode; delay?: number }> = ({ children, delay = 0 }) => {
|
|
const ref = useRef<HTMLDivElement>(null);
|
|
const isInView = useInView(ref, { once: true });
|
|
|
|
return (
|
|
<motion.div
|
|
ref={ref}
|
|
initial={{ opacity: 0, y: 50 }}
|
|
animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 50 }}
|
|
transition={{
|
|
duration: 0.8,
|
|
delay,
|
|
ease: [0.21, 1.11, 0.81, 0.99], // Apple-style easing
|
|
}}
|
|
>
|
|
{children}
|
|
</motion.div>
|
|
);
|
|
};
|
|
|
|
const GradientText: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
|
<motion.span
|
|
className="text-gradient"
|
|
animate={{
|
|
backgroundPosition: ['0% 50%', '100% 50%', '0% 50%'],
|
|
}}
|
|
transition={{
|
|
duration: 5,
|
|
repeat: Infinity,
|
|
ease: "easeInOut",
|
|
}}
|
|
style={{
|
|
backgroundSize: '200% 200%',
|
|
}}
|
|
>
|
|
{children}
|
|
</motion.span>
|
|
);
|
|
|
|
const ScrollIndicator: React.FC = () => {
|
|
const prefersReducedMotion = useReducedMotion();
|
|
|
|
return (
|
|
<motion.div
|
|
className="absolute bottom-8 left-1/2 transform -translate-x-1/2 flex flex-col items-center text-white/60"
|
|
initial={{ opacity: 0, y: prefersReducedMotion ? 0 : 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ delay: prefersReducedMotion ? 0 : 2, duration: prefersReducedMotion ? 0.01 : 1 }}
|
|
role="button"
|
|
tabIndex={0}
|
|
aria-label="Scroll down to see more content"
|
|
onKeyDown={(e) => {
|
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
window.scrollBy({ top: window.innerHeight, behavior: 'smooth' });
|
|
}
|
|
}}
|
|
onClick={() => {
|
|
window.scrollBy({ top: window.innerHeight, behavior: 'smooth' });
|
|
}}
|
|
className="cursor-pointer focus:outline-none focus:ring-2 focus:ring-chorus-blue focus:ring-opacity-50 rounded-lg p-3"
|
|
>
|
|
<motion.div
|
|
animate={prefersReducedMotion ? {} : { y: [0, 8, 0] }}
|
|
transition={{
|
|
duration: prefersReducedMotion ? 0 : 2,
|
|
repeat: prefersReducedMotion ? 0 : Infinity,
|
|
ease: "easeInOut"
|
|
}}
|
|
>
|
|
<ChevronDownIcon size={24} />
|
|
</motion.div>
|
|
<span className="text-sm mt-2 tracking-wider">SCROLL</span>
|
|
</motion.div>
|
|
);
|
|
};
|
|
|
|
const EnhancedHero: React.FC = () => {
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
const { scrollY } = useScroll();
|
|
const opacity = useTransform(scrollY, [0, 500], [1, 0]);
|
|
const scale = useTransform(scrollY, [0, 500], [1, 0.95]);
|
|
const prefersReducedMotion = useReducedMotion();
|
|
|
|
// Staggered animation variants with reduced motion support
|
|
const containerVariants = {
|
|
hidden: {},
|
|
visible: {
|
|
transition: {
|
|
staggerChildren: prefersReducedMotion ? 0 : 0.2,
|
|
delayChildren: prefersReducedMotion ? 0 : 0.3,
|
|
},
|
|
},
|
|
};
|
|
|
|
const itemVariants = {
|
|
hidden: {
|
|
opacity: 0,
|
|
y: prefersReducedMotion ? 0 : 60,
|
|
scale: prefersReducedMotion ? 1 : 0.95,
|
|
},
|
|
visible: {
|
|
opacity: 1,
|
|
y: 0,
|
|
scale: 1,
|
|
transition: {
|
|
duration: prefersReducedMotion ? 0.01 : 0.8,
|
|
ease: [0.21, 1.11, 0.81, 0.99], // Apple cubic-bezier
|
|
},
|
|
},
|
|
};
|
|
|
|
const buttonVariants = {
|
|
hidden: {
|
|
opacity: 0,
|
|
y: prefersReducedMotion ? 0 : 30
|
|
},
|
|
visible: {
|
|
opacity: 1,
|
|
y: 0,
|
|
transition: {
|
|
duration: prefersReducedMotion ? 0.01 : 0.6,
|
|
ease: [0.21, 1.11, 0.81, 0.99],
|
|
},
|
|
},
|
|
hover: {
|
|
scale: prefersReducedMotion ? 1 : 1.05,
|
|
transition: {
|
|
duration: 0.2,
|
|
ease: "easeOut",
|
|
},
|
|
},
|
|
tap: {
|
|
scale: prefersReducedMotion ? 1 : 0.98,
|
|
transition: {
|
|
duration: 0.1,
|
|
},
|
|
},
|
|
};
|
|
|
|
return (
|
|
<section
|
|
ref={containerRef}
|
|
className="relative min-h-screen flex items-center justify-center overflow-hidden bg-gradient-to-br from-chorus-charcoal via-chorus-charcoal to-chorus-charcoal-dark"
|
|
aria-label="CHORUS Services Hero Section"
|
|
role="banner"
|
|
>
|
|
{/* Geometric Background Pattern */}
|
|
<GeometricPattern />
|
|
|
|
{/* Main Content with Parallax */}
|
|
<motion.div
|
|
className="relative z-10 text-center max-w-6xl mx-auto px-6"
|
|
style={{ opacity, scale }}
|
|
initial="hidden"
|
|
animate="visible"
|
|
variants={containerVariants}
|
|
>
|
|
{/* Background floating elements */}
|
|
<ParallaxLayer speed={-0.3} className="absolute -top-20 -left-20">
|
|
<FloatingElement delay={0}>
|
|
<div className="w-32 h-32 rounded-full border border-white/5 bg-gradient-to-r from-chorus-blue/10 to-chorus-green/10" />
|
|
</FloatingElement>
|
|
</ParallaxLayer>
|
|
|
|
<ParallaxLayer speed={-0.5} className="absolute -top-40 -right-32">
|
|
<FloatingElement delay={1}>
|
|
<div className="w-48 h-48 rounded-full border border-white/5 bg-gradient-to-l from-chorus-green/10 to-chorus-blue/10" />
|
|
</FloatingElement>
|
|
</ParallaxLayer>
|
|
|
|
{/* Main Headline */}
|
|
<motion.div variants={itemVariants} className="mb-8">
|
|
<Title
|
|
level={1}
|
|
className="text-4xl sm:text-5xl md:text-6xl lg:text-7xl xl:text-8xl font-bold mb-6 text-white leading-tight"
|
|
style={{ letterSpacing: '-0.02em' }}
|
|
role="heading"
|
|
aria-level={1}
|
|
>
|
|
<GradientText>CHORUS</GradientText> Services
|
|
</Title>
|
|
</motion.div>
|
|
|
|
{/* Subtitle */}
|
|
<motion.div variants={itemVariants} className="mb-12">
|
|
<Paragraph
|
|
className="text-lg sm:text-xl md:text-2xl text-gray-300 max-w-4xl mx-auto leading-relaxed"
|
|
role="heading"
|
|
aria-level={2}
|
|
>
|
|
Distributed AI Orchestration Without the Hallucinations
|
|
</Paragraph>
|
|
</motion.div>
|
|
|
|
{/* CTA Buttons */}
|
|
<motion.div variants={itemVariants}>
|
|
<Space size="large" className="flex flex-wrap justify-center gap-4">
|
|
<motion.div
|
|
variants={buttonVariants}
|
|
whileHover="hover"
|
|
whileTap="tap"
|
|
>
|
|
<PrimaryButton
|
|
size="large"
|
|
icon={<PlayIcon size={20} />}
|
|
className="text-lg px-8 py-4 h-auto min-w-[180px] shadow-2xl shadow-chorus-blue/25"
|
|
aria-label="Explore CHORUS Platform"
|
|
>
|
|
Explore Platform
|
|
</PrimaryButton>
|
|
</motion.div>
|
|
|
|
<motion.div
|
|
variants={buttonVariants}
|
|
whileHover="hover"
|
|
whileTap="tap"
|
|
>
|
|
<SecondaryButton
|
|
size="large"
|
|
icon={<ArrowRightIcon size={20} />}
|
|
className="text-lg px-8 py-4 h-auto min-w-[180px] backdrop-blur-sm"
|
|
aria-label="View CHORUS Documentation"
|
|
>
|
|
View Documentation
|
|
</SecondaryButton>
|
|
</motion.div>
|
|
</Space>
|
|
</motion.div>
|
|
|
|
{/* Stats or additional info */}
|
|
<motion.div
|
|
variants={itemVariants}
|
|
className="mt-16 grid grid-cols-1 sm:grid-cols-3 gap-8 max-w-2xl mx-auto"
|
|
>
|
|
{[
|
|
{ label: 'Components', value: '4+', color: 'chorus-blue' },
|
|
{ label: 'Uptime', value: '99.9%', color: 'chorus-green' },
|
|
{ label: 'Performance', value: '< 100ms', color: 'yellow-400' },
|
|
].map((stat, index) => (
|
|
<motion.div
|
|
key={stat.label}
|
|
className="text-center"
|
|
whileHover={{ scale: 1.05 }}
|
|
transition={{ duration: 0.2 }}
|
|
>
|
|
<motion.div
|
|
className={`text-2xl font-bold text-${stat.color} mb-2`}
|
|
animate={{
|
|
scale: [1, 1.1, 1],
|
|
}}
|
|
transition={{
|
|
duration: 2,
|
|
delay: index * 0.5 + 3,
|
|
repeat: Infinity,
|
|
repeatDelay: 5,
|
|
}}
|
|
>
|
|
{stat.value}
|
|
</motion.div>
|
|
<div className="text-gray-400 text-sm uppercase tracking-wider">
|
|
{stat.label}
|
|
</div>
|
|
</motion.div>
|
|
))}
|
|
</motion.div>
|
|
</motion.div>
|
|
|
|
{/* Scroll Indicator */}
|
|
<ScrollIndicator />
|
|
|
|
{/* Ambient lighting effects */}
|
|
<div className="absolute inset-0 pointer-events-none" aria-hidden="true">
|
|
<motion.div
|
|
className="absolute top-1/4 left-1/4 w-96 h-96 bg-chorus-blue/5 rounded-full blur-3xl"
|
|
animate={prefersReducedMotion ? {} : {
|
|
scale: [1, 1.2, 1],
|
|
opacity: [0.3, 0.5, 0.3],
|
|
}}
|
|
transition={{
|
|
duration: prefersReducedMotion ? 0 : 8,
|
|
repeat: prefersReducedMotion ? 0 : Infinity,
|
|
ease: "easeInOut",
|
|
}}
|
|
/>
|
|
<motion.div
|
|
className="absolute bottom-1/4 right-1/4 w-96 h-96 bg-chorus-green/5 rounded-full blur-3xl"
|
|
animate={prefersReducedMotion ? {} : {
|
|
scale: [1.2, 1, 1.2],
|
|
opacity: [0.5, 0.3, 0.5],
|
|
}}
|
|
transition={{
|
|
duration: prefersReducedMotion ? 0 : 8,
|
|
repeat: prefersReducedMotion ? 0 : Infinity,
|
|
ease: "easeInOut",
|
|
delay: prefersReducedMotion ? 0 : 2,
|
|
}}
|
|
/>
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export default EnhancedHero; |