# Component Patterns ## Hydration Strategy | Directive | When to Use | Components | |-----------|-------------|------------| | `client:load` | Above-fold, needs immediate interaction | Navigation, Hero, Loader, CustomCursor | | `client:visible` | Below-fold, can wait until scrolled into view | About, Contact, Footer, GamesList, Marquee | | `client:idle` | Low priority, background loading | BlogSearch | ## GSAP Animation Patterns ### Required Setup in Every Animated Component ```typescript import { useEffect, useRef } from 'react'; import gsap from 'gsap'; import { ScrollTrigger } from 'gsap/ScrollTrigger'; gsap.registerPlugin(ScrollTrigger); export default function AnimatedComponent() { const sectionRef = useRef(null); useEffect(() => { // Always check reduced motion if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; // Always wrap in context for cleanup const ctx = gsap.context(() => { // animations here }); return () => ctx.revert(); }, []); } ``` ### SessionStorage One-Time Entrance For Hero and Loader — skip expensive animations on repeat visits: ```typescript const [isVisible, setIsVisible] = useState(() => { // Synchronous check before first render if (typeof window === 'undefined') return true; return !sessionStorage.getItem('{{STORAGE_KEY}}'); }); // In animation completion callback: sessionStorage.setItem('{{STORAGE_KEY}}', 'true'); setIsVisible(false); ``` ### ScrollTrigger Entrance Pattern For below-fold sections (About, Contact, GamesList): ```typescript gsap.context(() => { const tl = gsap.timeline({ scrollTrigger: { trigger: sectionRef.current, start: 'top 80%', toggleActions: 'play none none none', }, }); tl.from(leftRef.current, { x: -50, opacity: 0, duration: 0.8, ease: 'power2.out', }) .from(rightRef.current, { x: 50, opacity: 0, duration: 0.8, ease: 'power2.out', }, '-=0.6'); // Overlap by 0.6s for flowing feel }); ``` ### Parallax Pattern ```typescript gsap.to(bgRef.current, { yPercent: 20, ease: 'none', scrollTrigger: { trigger: sectionRef.current, start: 'top bottom', end: 'bottom top', scrub: true, }, }); ``` ### Idle Float Animation ```typescript gsap.to(elementRef.current, { y: 12, rotation: 1.5, duration: 3, ease: 'sine.inOut', yoyo: true, repeat: -1, }); ``` ### Animation Rules - **Transform-only**: Use x, y, scale, rotation, opacity. Never animate width, height, top, left, margin, padding. - **Stagger**: Use negative relative positioning (`'-=0.6'`) in timelines for overlapping reveals. - **Ease functions**: `power2.out` for entrances, `power3.inOut` for exits, `sine.inOut` for idle, `back.out(1.7)` for bouncy CTAs. ## Navigation Component Fixed header + fullscreen overlay menu: **Header Bar:** - Fixed position, z-50 - Transparent when at top, semi-opaque after scroll (useEffect with scroll listener) - Logo (left), optional center ticker, controls (right: audio toggle, menu button) - Top accent line: `h-[2px]` gradient with brand colors **Fullscreen Menu:** - Hidden by default (`display: none`), shown via GSAP - Background: layered gradients, noise texture, scanlines - Two-column: nav links (left) + info (right) - Nav links: indexed (01-06), description on hover (hidden mobile) - GSAP open sequence: scanline wipe → bg fade → links stagger → info slide - GSAP close: all fade 0.15s → bg fade 0.2s → `display: none` **Interactions:** - ESC to close (KeyboardEvent listener) - Body overflow hidden when open - Hash links scroll with GSAP ScrollToPlugin - Audio SFX via Web Audio API (optional — synthesized, no files needed) - 44px minimum touch targets on all links/buttons ## Hero Component Full viewport section: **Background Layers (bottom to top):** 1. Video/image (opacity 50%, object-cover) 2. Gradient overlays (bottom-to-top dark, radial center-fade) 3. Noise texture (mix-blend-overlay, opacity 3-5%) 4. Scanlines (optional) **Content:** - Centered: logo, tagline, 2 CTA buttons, optional stats ticker - Floating decorative elements (characters, icons) with idle float animations - Diagonal accent slash (optional, brand colored) **Entrance Animation (sessionStorage skip on repeat):** ``` 0.0s: Start 0.3s: Slash scaleX 0→1 (0.6s) 0.5s: Logo blur(20px) x:-120 → clear x:0 (1.0s) 0.7s: Character blur → clear (0.9s) 1.0s: Tagline clipPath reveal (0.7s) 1.2s: CTAs scale+y stagger (0.6s each, 0.12s gap) 1.5s: Ticker fade up (0.6s) ``` **Parallax (always active):** - Video: yPercent +20 on scroll - Character: yPercent -30 on scroll ## Loader Component (optional) One-time splash screen: ```typescript const [isVisible, setIsVisible] = useState(() => { if (typeof window === 'undefined') return true; return !sessionStorage.getItem('{{KEY}}-loader-seen'); }); if (!isVisible) return null; ``` - Progress bar: width 0→100% (1.5s) - Logo: opacity 0, scale 1.1, blur 10px → visible (0.5s) - Container: yPercent 0→-100 (0.8s, power3.inOut) - Safety timeout: 5s max - Click anywhere to skip - On complete: `sessionStorage.setItem`, `setIsVisible(false)` ## CustomCursor Component (optional) ```typescript export default function CustomCursor() { const cursorRef = useRef(null); useEffect(() => { if (window.matchMedia('(pointer: coarse)').matches) return; const onMove = (e: MouseEvent) => { gsap.to(cursorRef.current, { x: e.clientX, y: e.clientY, duration: 0.1, ease: 'power2.out', }); }; window.addEventListener('mousemove', onMove); return () => window.removeEventListener('mousemove', onMove); }, []); return
; } ``` Styled via global.css `#custom-cursor` rules. ## Contact Component Two-column layout: **Left Column:** - Section label (accent color, pixel font) - Heading with gradient text on key word - Contact email link - Social icons row with hover color transitions - Optional location tagline **Right Column (Form):** - Fields: name, email, subject, message - Honeypot: `_honey` (hidden, aria-hidden, tabIndex -1) - Submit button: brand bg, white text, pixel shadow, hover translate 2px - Status states: idle → sending (disabled) → success (cyan border box) → error (primary border box) **GSAP entrance**: ScrollTrigger, left x:-50, right x:+50, staggered. ## Footer Component Multi-column grid: ``` ┌─────────────────────────────────────────────────────────┐ │ Brand │ SITEMAP │ LEGAL │ CTA │ │ Logo │ Home │ Privacy │ Steam │ │ Name │ Product │ Terms │ Button │ │ Tagline │ About │ Press │ │ │ Socials │ Blog │ │ │ │ │ Contact │ │ │ ├─────────────────────────────────────────────────────────┤ │ © 2024 Company. Est. 20XX. │ └─────────────────────────────────────────────────────────┘ ``` - Background: `--color-darker` (#050505) - Text: white/35 base, white on hover, 200ms transition - Grid: 1 col mobile → 2 cols tablet → 4 cols desktop ## External API Integration Pattern Build-time data fetching (e.g., Steam API): ```typescript // src/lib/steam.ts interface SteamData { app: SteamAppDetails | null; reviews: SteamReviewSummary | null; players: number | null; news: SteamNewsItem[]; version: string | null; } async function fetchJson(url: string): Promise { try { const res = await fetch(url); if (!res.ok) return null; return await res.json() as T; } catch { return null; } } export async function fetchAllData(id: number): Promise { const [app, reviews, players, news] = await Promise.all([ fetchAppDetails(id), fetchReviewSummary(id), fetchCurrentPlayers(id), fetchNews(id), ]); return { app, reviews, players, news, version: parseVersionFromNews(news) }; } ``` Key principles: - Every fetch returns `T | null` — never throws - Use `Promise.all` for parallel fetching - Aggregate into single data object with nullable fields - Builds never break if external API is down - Wire into pages via frontmatter `await` ## Accessibility Checklist Every component should have: - `aria-label` on icon-only buttons - 44px minimum touch targets (w-11 h-11, or py-3 px-3 -mx-3) - Keyboard navigation (ESC to close overlays, Tab order) - `focus-visible` outline styles - Semantic HTML (`