import React, { useMemo } from 'react'; import { SeasonalOverlayProps } from '../types'; import { animBurst, animCoreFlash, animShimmer, animConfettiFall, animConfettiSway, animTwinkle, animSkyPulse, } from './NewYear.css'; /** * New Year overlay — a midnight celebration. Layered oklch gradients sink the * app into a deep navy night; fireworks bloom as expanding spark rings, a * champagne-gold shimmer sweeps across, confetti slivers tumble down, and * sparkle stars twinkle. All motion is transform/opacity only. * * Palette (oklch): midnight navy oklch(0.20 0.07 260), champagne gold * oklch(0.85 0.13 90), bursts in magenta oklch(0.7 0.22 350), cyan * oklch(0.8 0.15 200), and gold. * * RENDERING CONTRACT: the parent supplies a fixed inset:0 overflow:hidden * pointer-events:none container at the right z-index. We only return * absolutely-positioned aria-hidden children at low opacity — no z-index, * position:fixed, or pointer-events here — kept well below opaque so chat text * stays WCAG-AA legible. * * REDUCED MOTION: when `reduced`, render a static but gorgeous scene (a frozen * firework bloom mid-burst, scattered gold confetti, a still shimmer band) with * no `animation` at all. The settings preview always passes reduced=true. */ const BURST_HUES = [ // [ring oklch, core oklch] ['oklch(0.7 0.22 350)', 'oklch(0.88 0.14 350)'], // magenta ['oklch(0.8 0.15 200)', 'oklch(0.92 0.1 200)'], // cyan ['oklch(0.85 0.13 90)', 'oklch(0.95 0.09 95)'], // gold ['oklch(0.75 0.2 30)', 'oklch(0.9 0.12 40)'], // warm coral ] as const; const CONFETTI_COLORS = [ 'oklch(0.85 0.13 90)', // champagne gold 'oklch(0.7 0.22 350)', // magenta 'oklch(0.8 0.15 200)', // cyan 'oklch(0.9 0.06 90)', // pale gold 'oklch(0.78 0.18 30)', // coral ] as const; type Burst = { top: number; left: number; size: number; ring: string; core: string; duration: number; delay: number; }; type Confetto = { left: number; w: number; h: number; color: string; round: boolean; fallDur: number; swayDur: number; delay: number; }; type Star = { top: number; left: number; size: number; color: string; duration: number; delay: number; }; // Deterministic pseudo-random so the memoized scene is stable across renders. const rand = (seed: number) => { const x = Math.sin(seed * 12.9898 + 78.233) * 43758.5453; return x - Math.floor(x); }; // A four-point sparkle (gleam) as an inline SVG data-URI — CSP-safe, no assets. const sparkleUri = (color: string) => `url("data:image/svg+xml,${encodeURIComponent( ``, )}")`; export function NewYearOverlay({ reduced }: SeasonalOverlayProps) { const bursts = useMemo( () => // Bursts cluster in the upper two-thirds of the sky, away from typical text. Array.from({ length: 7 }, (_, i) => { const hue = BURST_HUES[i % BURST_HUES.length]; return { top: 8 + rand(i + 1) * 48, left: 8 + rand(i + 11) * 84, size: 130 + Math.floor(rand(i + 21) * 110), ring: hue[0], core: hue[1], duration: 6.5 + rand(i + 31) * 4, delay: rand(i + 41) * 9, }; }), [], ); const confetti = useMemo( () => Array.from({ length: 20 }, (_, i) => ({ left: rand(i + 101) * 100, w: 4 + Math.floor(rand(i + 111) * 4), h: 7 + Math.floor(rand(i + 121) * 7), color: CONFETTI_COLORS[i % CONFETTI_COLORS.length], round: i % 4 === 0, fallDur: 9 + rand(i + 131) * 7, swayDur: 3 + rand(i + 141) * 3, delay: rand(i + 151) * 10, })), [], ); const stars = useMemo( () => Array.from({ length: 9 }, (_, i) => ({ top: 4 + rand(i + 201) * 64, left: 4 + rand(i + 211) * 92, size: 8 + Math.floor(rand(i + 221) * 10), color: i % 2 === 0 ? 'oklch(0.85 0.13 90)' : 'oklch(0.92 0.06 200)', duration: 3 + rand(i + 231) * 3, delay: rand(i + 241) * 4, })), [], ); return ( <> {/* Midnight sky — layered oklch gradients for depth, with a faint breathe. */}