import React, { useMemo } from 'react'; import { SeasonalOverlayProps } from '../types'; import { animLanternBob, animLanternSway, animTasselSway, animGlowBreathe, animPetalFall, animPetalSway, animDragonDrift, animLacquerPulse, animEmberRise, } from './LunarNewYear.css'; // Deterministic pseudo-random so the scene is identical every mount (no React // state per frame). Large primes keep the distribution well spread. const rand = (seed: number): number => { const x = Math.sin(seed * 127.1 + 311.7) * 43758.5453; return x - Math.floor(x); }; // Core oklch palette — auspicious crimson/vermilion lanterns, imperial gold // trim and blossoms, over a deep lacquer-red ambient tint. Kept luminous and // gentle so everything reads as soft ambient glow, never solid paint. const CRIMSON = 'oklch(0.50 0.20 25)'; const VERMILION = 'oklch(0.58 0.21 30)'; const GOLD = 'oklch(0.82 0.14 85)'; const GOLD_HI = 'oklch(0.92 0.10 92)'; // A coiling dragon silhouette in imperial gold, rendered once as an inline SVG // data-URI so it costs a single GPU-composited layer (no DOM weight). The curve // is intentionally abstract and very subtle — a calligraphic ribbon-body with a // suggestion of a head, mane and tail arcing across the upper scene. const dragonUri = ((): string => { const svg = `` + `` + `` + `` + `` + `` + `` + `` + `` + // Sinuous body — a thick tapering serpentine ribbon. `` + // Inner highlight running along the body for a calligraphic sheen. `` + // Head + horn flourish at the leading end. `` + // Mane / whisker strokes flaring back from the head. `` + // Tail wisps. `` + ``; return `url("data:image/svg+xml;utf8,${encodeURIComponent(svg)}")`; })(); type Lantern = { left: number; top: number; scale: number; bobDuration: number; swayDuration: number; delay: number; opacity: number; }; type Petal = { left: number; size: number; duration: number; delay: number; swayDuration: number; opacity: number; blur: number; hue: number; }; type Ember = { left: number; bottom: number; size: number; duration: number; delay: number; }; // A single five-petal plum blossom (gold), inline SVG so each petal sliver is // one cheap element. Returned as a data-URI background painted on a square. const blossomUri = ((): string => { const petals = Array.from({ length: 5 }, (_, i) => { const a = (i * 72 * Math.PI) / 180; const cx = 16 + Math.cos(a - Math.PI / 2) * 8; const cy = 16 + Math.sin(a - Math.PI / 2) * 8; return ``; }).join(''); const svg = `` + `${petals}` + `` + ``; return `url("data:image/svg+xml;utf8,${encodeURIComponent(svg)}")`; })(); export function LunarNewYearOverlay({ reduced }: SeasonalOverlayProps) { // Paper lanterns strung across the upper third, gently staggered in depth. const lanterns = useMemo(() => { const slots = [ { left: 9, top: 7, scale: 1.0 }, { left: 27, top: 13, scale: 0.82 }, { left: 46, top: 6, scale: 1.12 }, { left: 64, top: 15, scale: 0.78 }, { left: 82, top: 9, scale: 0.95 }, { left: 92, top: 20, scale: 0.7 }, ]; return slots.map((s, i) => ({ left: s.left, top: s.top, scale: s.scale, bobDuration: 7 + rand(i + 1) * 4, swayDuration: 5.5 + rand(i + 4) * 3, delay: -rand(i + 7) * 6, opacity: 0.78 + rand(i + 2) * 0.18, })); }, []); // Drifting gold plum-blossom petals — two parallax bands (far small/dim/slow, // near large/bright/fast) for depth. const petals = useMemo(() => { const bands = [ { count: 9, size: [9, 14], dur: [15, 21], op: [0.4, 0.6], blur: 0.6 }, { count: 8, size: [15, 24], dur: [10, 14], op: [0.6, 0.85], blur: 0 }, ]; const out: Petal[] = []; let s = 1; bands.forEach((b) => { for (let i = 0; i < b.count; i += 1) { const r1 = rand(s); const r2 = rand(s + 0.37); const r3 = rand(s + 0.71); const r4 = rand(s + 0.91); out.push({ left: r1 * 100, size: b.size[0] + r2 * (b.size[1] - b.size[0]), duration: b.dur[0] + r3 * (b.dur[1] - b.dur[0]), delay: -r4 * (b.dur[1] + 4), swayDuration: 5 + r2 * 5, opacity: b.op[0] + r3 * (b.op[1] - b.op[0]), blur: b.blur, hue: 82 + r4 * 10, }); s += 1; } }); return out; }, []); // A few gold embers rising from the lanterns (motion scene only). const embers = useMemo( () => Array.from({ length: 7 }, (_, i) => ({ left: 8 + rand(i + 11) * 84, bottom: 8 + rand(i + 21) * 30, size: 1.6 + rand(i + 31) * 2.2, duration: 9 + rand(i + 41) * 6, delay: -rand(i + 51) * 12, })), [], ); return ( <> {/* Deep lacquer-red ambient wash — layered radial + linear oklch gradients for depth and a warm crimson lantern-glow from above. Low-opacity so chat text stays legible (WCAG-AA). */}