This commit implements a complete brand system overhaul including: TYPOGRAPHY SYSTEM: - Add Exo font family (Thin, Light, Regular, ExtraLight) as primary brand font - Implement SF Pro Display/Text hierarchy for UI components - Create comprehensive Typography component with all brand variants - Update all components to use new typography tokens DESIGN TOKEN SYSTEM: - Create complete design token system in theme/designTokens.ts - Define Carbon Black (#1a1a1a), Walnut Brown (#8B4513), Brushed Aluminum (#A8A8A8) palette - Implement CSS custom properties for consistent theming - Update Ant Design theme integration COMPONENT UPDATES: - Enhance Hero section with Exo Thin typography and improved layout - Update navigation with SF Pro font hierarchy - Redesign Button component with new variants and accessibility - Apply brand colors and typography across all showcase sections - Improve Footer with consistent brand application PERFORMANCE & ACCESSIBILITY: - Self-host Exo fonts for optimal loading performance - Implement proper font-display strategies - Add comprehensive accessibility audit documentation - Include responsive testing verification DOCUMENTATION: - Add brand system demo and implementation guides - Include QA testing reports and accessibility audits - Document design token usage and component patterns 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
256 lines
8.4 KiB
TypeScript
256 lines
8.4 KiB
TypeScript
'use client';
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
import { Layout, Drawer } from 'antd';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import { MenuIcon, XIcon, ArrowRightIcon } from 'lucide-react';
|
|
import Link from 'next/link';
|
|
import { usePathname } from 'next/navigation';
|
|
import { cn } from '@/utils/cn';
|
|
import { Button } from '@/components/ui/Button';
|
|
import { Typography } from '@/components/ui/Typography';
|
|
|
|
const { Header: AntHeader } = Layout;
|
|
|
|
interface NavigationItem {
|
|
key: string;
|
|
label: string;
|
|
href: string;
|
|
}
|
|
|
|
const navigationItems: NavigationItem[] = [
|
|
{ key: 'home', label: 'Home', href: '/' },
|
|
{ key: 'technical-specs', label: 'Technical Specs', href: '/technical-specs' },
|
|
];
|
|
|
|
export const Header: React.FC = () => {
|
|
const [isScrolled, setIsScrolled] = useState(false);
|
|
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
|
const pathname = usePathname();
|
|
|
|
// Handle scroll effect
|
|
useEffect(() => {
|
|
const handleScroll = () => {
|
|
setIsScrolled(window.scrollY > 20);
|
|
};
|
|
|
|
window.addEventListener('scroll', handleScroll);
|
|
return () => window.removeEventListener('scroll', handleScroll);
|
|
}, []);
|
|
|
|
// Handle mobile menu close
|
|
const handleMobileMenuClose = () => {
|
|
setIsMobileMenuOpen(false);
|
|
};
|
|
|
|
// Get active key based on current pathname
|
|
const getActiveKey = () => {
|
|
const item = navigationItems.find(item => item.href === pathname);
|
|
return item?.key || 'home';
|
|
};
|
|
|
|
// Mobile menu animation variants
|
|
const drawerVariants = {
|
|
closed: { opacity: 0 },
|
|
open: { opacity: 1 },
|
|
};
|
|
|
|
const menuItemVariants = {
|
|
closed: { x: -20, opacity: 0 },
|
|
open: (i: number) => ({
|
|
x: 0,
|
|
opacity: 1,
|
|
transition: { delay: i * 0.1 },
|
|
}),
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<AntHeader
|
|
className={cn(
|
|
'fixed top-0 left-0 right-0 z-50 transition-all duration-300 border-b',
|
|
isScrolled
|
|
? 'bg-chorus-charcoal/95 backdrop-blur-md border-gray-800 shadow-lg'
|
|
: 'bg-transparent border-transparent'
|
|
)}
|
|
style={{ height: '80px', padding: '0 24px' }}
|
|
>
|
|
<div className="max-w-7xl mx-auto h-full flex items-center justify-between">
|
|
{/* Logo */}
|
|
<motion.div
|
|
initial={{ opacity: 0, x: -20 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
transition={{ duration: 0.5 }}
|
|
className="flex items-center space-x-3"
|
|
>
|
|
<div className="w-10 h-10 bg-gradient-chorus rounded-lg flex items-center justify-center">
|
|
<span className="text-white font-bold text-xl">C</span>
|
|
</div>
|
|
<Typography.Display level={3} style={{ fontSize: '1.25rem', fontWeight: 100 }}>
|
|
CHORUS
|
|
</Typography.Display>
|
|
</motion.div>
|
|
|
|
{/* Desktop Navigation */}
|
|
<div className="hidden lg:flex items-center space-x-8">
|
|
{navigationItems.map((item, index) => (
|
|
<motion.div
|
|
key={item.key}
|
|
initial={{ opacity: 0, y: -10 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.5, delay: index * 0.1 }}
|
|
>
|
|
<Link
|
|
href={item.href}
|
|
className={cn(
|
|
'px-4 py-2 rounded-lg transition-all duration-200',
|
|
'hover:bg-white/10',
|
|
getActiveKey() === item.key
|
|
? 'text-white'
|
|
: 'text-gray-300 hover:text-white'
|
|
)}
|
|
>
|
|
<Typography.Interface
|
|
weight="medium"
|
|
color={getActiveKey() === item.key ? 'primary' : 'secondary'}
|
|
>
|
|
{item.label}
|
|
</Typography.Interface>
|
|
</Link>
|
|
</motion.div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Desktop CTA Buttons */}
|
|
<div className="hidden lg:flex items-center space-x-4">
|
|
<motion.div
|
|
initial={{ opacity: 0, x: 20 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
transition={{ duration: 0.5, delay: 0.3 }}
|
|
>
|
|
<Button variant="ghost" size="middle">
|
|
Sign In
|
|
</Button>
|
|
</motion.div>
|
|
<motion.div
|
|
initial={{ opacity: 0, x: 20 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
transition={{ duration: 0.5, delay: 0.4 }}
|
|
>
|
|
<Button
|
|
variant="primary"
|
|
size="middle"
|
|
icon={<ArrowRightIcon size={16} />}
|
|
>
|
|
Get Started
|
|
</Button>
|
|
</motion.div>
|
|
</div>
|
|
|
|
{/* Mobile Menu Button */}
|
|
<motion.button
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
transition={{ duration: 0.5 }}
|
|
className="lg:hidden p-2 text-white hover:bg-white/10 rounded-lg transition-colors"
|
|
onClick={() => setIsMobileMenuOpen(true)}
|
|
>
|
|
<MenuIcon size={24} />
|
|
</motion.button>
|
|
</div>
|
|
</AntHeader>
|
|
|
|
{/* Mobile Menu Drawer */}
|
|
<Drawer
|
|
title={null}
|
|
placement="right"
|
|
closable={false}
|
|
onClose={() => setIsMobileMenuOpen(false)}
|
|
open={isMobileMenuOpen}
|
|
width={320}
|
|
className="mobile-menu-drawer"
|
|
styles={{
|
|
body: { padding: 0, background: '#1a1a1a' },
|
|
header: { display: 'none' },
|
|
}}
|
|
>
|
|
<AnimatePresence>
|
|
{isMobileMenuOpen && (
|
|
<motion.div
|
|
initial="closed"
|
|
animate="open"
|
|
exit="closed"
|
|
variants={drawerVariants}
|
|
className="h-full bg-chorus-charcoal"
|
|
>
|
|
{/* Mobile Header */}
|
|
<div className="flex items-center justify-between p-6 border-b border-gray-800">
|
|
<div className="flex items-center space-x-3">
|
|
<div className="w-8 h-8 bg-gradient-chorus rounded-lg flex items-center justify-center">
|
|
<span className="text-white font-bold text-sm">C</span>
|
|
</div>
|
|
<Typography.Display level={4} style={{ fontSize: '1.125rem', fontWeight: 100 }}>
|
|
CHORUS
|
|
</Typography.Display>
|
|
</div>
|
|
<button
|
|
onClick={() => setIsMobileMenuOpen(false)}
|
|
className="p-2 text-white hover:bg-white/10 rounded-lg transition-colors"
|
|
>
|
|
<XIcon size={20} />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Mobile Navigation */}
|
|
<div className="p-6">
|
|
<nav className="space-y-2">
|
|
{navigationItems.map((item, index) => (
|
|
<motion.div
|
|
key={item.key}
|
|
custom={index}
|
|
variants={menuItemVariants}
|
|
>
|
|
<Link
|
|
href={item.href}
|
|
onClick={handleMobileMenuClose}
|
|
className={cn(
|
|
'block w-full text-left p-4 rounded-lg font-medium transition-all duration-200',
|
|
'hover:bg-white/10 hover:text-slate-400',
|
|
getActiveKey() === item.key
|
|
? 'text-slate-400 bg-slate-400/10'
|
|
: 'text-gray-300'
|
|
)}
|
|
>
|
|
{item.label}
|
|
</Link>
|
|
</motion.div>
|
|
))}
|
|
</nav>
|
|
|
|
{/* Mobile CTA Buttons */}
|
|
<div className="mt-8 space-y-4">
|
|
<Button variant="outline" fullWidth size="large">
|
|
Sign In
|
|
</Button>
|
|
<Button
|
|
variant="primary"
|
|
fullWidth
|
|
size="large"
|
|
icon={<ArrowRightIcon size={16} />}
|
|
>
|
|
Get Started
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</Drawer>
|
|
|
|
{/* Spacer to prevent content from going under fixed header */}
|
|
<div style={{ height: '80px' }} />
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default Header; |