import React, { useMemo } from 'react'; import { SeasonalOverlayProps } from '../types'; import { animCosmosDrift, animNebulaA, animNebulaB, animGalaxySpin, animTwinkle, animStarPulse, animParallaxNear, animParallaxFar, animComet, } from './DeepSpace.css'; /** * Deep Space overlay — a cosmic, awe-inspiring ambient mode. Layered oklch * radial gradients build a deep violet void seeded with drifting magenta/cyan * nebula clouds and a faint distant galaxy spiral. A parallax starfield sits at * two depths (a dense field of tiny twinkling stars plus a handful of brighter * hero stars), and slow comet streaks cross the sky occasionally. * * Palette (oklch): deep cosmic violet oklch(0.2 0.12 300), nebula magenta * oklch(0.55 0.2 330), cyan oklch(0.75 0.13 200), starlight white * oklch(0.98 0.02 280). * * 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 still * nebula, a static starfield, a frozen galaxy and one frozen comet streak) with * no `animation` at all. The settings preview always passes reduced=true. */ const STAR_TINTS = [ 'oklch(0.98 0.02 280)', // starlight white 'oklch(0.9 0.07 230)', // cool cyan-white 'oklch(0.88 0.08 330)', // faint magenta-white ] as const; const HERO_TINTS = [ 'oklch(0.92 0.06 200)', // cyan starlight 'oklch(0.9 0.09 330)', // magenta starlight 'oklch(0.98 0.02 280)', // pure starlight ] as const; type Star = { top: number; left: number; size: number; color: string; duration: number; delay: number; staticOpacity: number; }; type HeroStar = Star; type Comet = { top: number; left: number; length: number; angle: 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 gleam (sparkle) as an inline SVG data-URI — CSP-safe, no assets. const gleamUri = (color: string) => `url("data:image/svg+xml,${encodeURIComponent( ``, )}")`; function makeStars(count: number, seedBase: number): Star[] { return Array.from({ length: count }, (_, i) => { const s = seedBase + i; return { top: rand(s + 1) * 100, left: rand(s + 101) * 100, size: 1 + Math.floor(rand(s + 201) * 2), // 1–2px tiny stars color: STAR_TINTS[i % STAR_TINTS.length], duration: 2.6 + rand(s + 301) * 3.4, delay: rand(s + 401) * 5, staticOpacity: 0.4 + rand(s + 501) * 0.55, }; }); } export function DeepSpaceOverlay({ reduced }: SeasonalOverlayProps) { // Two parallax depths. Far = dense + faint, Near = sparser + slightly larger. const farStars = useMemo(() => makeStars(40, 1000), []); const nearStars = useMemo(() => makeStars(22, 2000), []); const heroStars = useMemo( () => Array.from({ length: 6 }, (_, i) => { const s = 3000 + i; return { top: 6 + rand(s + 1) * 78, left: 6 + rand(s + 101) * 88, size: 9 + Math.floor(rand(s + 201) * 9), // 9–17px gleams color: HERO_TINTS[i % HERO_TINTS.length], duration: 4 + rand(s + 301) * 4, delay: rand(s + 401) * 5, staticOpacity: 0.85, }; }), [], ); const comets = useMemo( () => Array.from({ length: 3 }, (_, i) => { const s = 4000 + i; return { top: 8 + rand(s + 1) * 44, left: -10 + rand(s + 101) * 30, length: 120 + Math.floor(rand(s + 201) * 120), angle: 18 + rand(s + 301) * 16, // gentle downward diagonal color: i % 2 === 0 ? 'oklch(0.92 0.06 200)' : 'oklch(0.9 0.09 330)', duration: 7 + rand(s + 401) * 5, delay: 2 + i * 6 + rand(s + 501) * 4, }; }), [], ); return ( <> {/* Deep cosmic void — layered oklch radial gradients for depth. A barely perceptible drift gives the whole field life without distraction. */}