import React, { useMemo } from 'react'; import { SeasonalOverlayProps } from '../types'; import { animHeartRise, animHeartBob, animPetalTumble, animBokehBreathe, animBlushPulse, animSparkle, } from './Valentines.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) => { const x = Math.sin(seed * 127.1 + 311.7) * 43758.5453; return x - Math.floor(x); }; // Romantic oklch palette — rose, blush pink, warm red, soft cream. Kept // luminous and gentle so everything reads as soft ambient glow over chat. const ROSE = 'oklch(0.7 0.15 10)'; const BLUSH = 'oklch(0.9 0.06 350)'; const WARM_RED = 'oklch(0.6 0.18 20)'; const CREAM = 'oklch(0.96 0.03 60)'; const HEART_COLORS = [ROSE, BLUSH, WARM_RED, 'oklch(0.78 0.13 5)']; const PETAL_COLORS = [ 'oklch(0.66 0.16 12)', // rose 'oklch(0.74 0.13 6)', // lighter rose 'oklch(0.6 0.18 20)', // warm red ]; // Inline SVG (data-URI) so it is fully Tauri/CSP-safe — no external assets. // A soft heart with a gradient fill and a cream highlight glint. const heartSvg = (fill: string, glint: string) => { const svg = ` `; return `url("data:image/svg+xml,${svg.replace(/\n/g, '').replace(/#/g, '%23')}")`; }; // A single rose petal — a soft teardrop/ovate shape with an inner crease, // gently asymmetric so the tumble reads as a real petal. const petalSvg = (fill: string) => { const svg = ` `; return `url("data:image/svg+xml,${svg.replace(/\n/g, '').replace(/#/g, '%23')}")`; }; type Heart = { left: number; size: number; duration: number; delay: number; bobDuration: number; opacity: number; blur: number; image: string; restTop: number; // static resting position for reduced scene }; type Petal = { left: number; size: number; duration: number; delay: number; opacity: number; image: string; rotate: number; restTop: number; }; type Bokeh = { left: number; top: number; size: number; color: string; duration: number; delay: number; }; type Sparkle = { left: number; top: number; size: number; duration: number; delay: number; }; export function ValentinesOverlay({ reduced }: SeasonalOverlayProps) { // Three parallax bands of hearts: far (small/slow/dim) -> near (large/fast). const hearts = useMemo(() => { const bands = [ { count: 4, size: [12, 18], dur: [20, 26], op: [0.3, 0.5], blur: 0.8 }, { count: 4, size: [18, 26], dur: [15, 19], op: [0.5, 0.72], blur: 0.3 }, { count: 3, size: [26, 38], dur: [12, 15], op: [0.62, 0.85], blur: 0 }, ]; const out: Heart[] = []; 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); const fill = HEART_COLORS[Math.floor(r4 * HEART_COLORS.length) % HEART_COLORS.length]; out.push({ left: r1 * 96 + 2, 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] + 5), bobDuration: 5 + r2 * 5, opacity: b.op[0] + r3 * (b.op[1] - b.op[0]), blur: b.blur, image: heartSvg(fill, CREAM), restTop: 6 + r3 * 86, }); s += 1; } }); return out; }, []); // Drifting rose petals tumbling down — a gentle counter-motion to the hearts. const petals = useMemo(() => { const count = 8; const out: Petal[] = []; for (let i = 0; i < count; i += 1) { const r1 = rand(i + 40); const r2 = rand(i + 40.5); const r3 = rand(i + 40.9); const fill = PETAL_COLORS[i % PETAL_COLORS.length]; out.push({ left: r1 * 98, size: 9 + r2 * 9, duration: 14 + r3 * 9, delay: -r1 * 22, opacity: 0.45 + r2 * 0.35, image: petalSvg(fill), rotate: r3 * 360, restTop: 4 + r2 * 90, }); } return out; }, []); // Dreamy blush bokeh orbs scattered across the scene, softly breathing. const bokeh = useMemo(() => { const count = 7; const out: Bokeh[] = []; for (let i = 0; i < count; i += 1) { const r1 = rand(i + 70); const r2 = rand(i + 70.4); const r3 = rand(i + 70.8); out.push({ left: r1 * 94 + 3, top: r2 * 88 + 4, size: 70 + r3 * 130, color: i % 2 === 0 ? BLUSH : 'oklch(0.82 0.1 355)', duration: 9 + r3 * 7, delay: -r1 * 10, }); } return out; }, []); // Faint sparkle glints — sparse, never strobing. const sparkles = useMemo(() => { const count = 5; const out: Sparkle[] = []; for (let i = 0; i < count; i += 1) { const r1 = rand(i + 200); const r2 = rand(i + 200.5); const r3 = rand(i + 200.9); out.push({ left: r1 * 92 + 4, top: r2 * 80 + 6, size: 6 + r3 * 8, duration: 5 + r3 * 4, delay: -r1 * 9, }); } return out; }, []); return ( <> {/* Warm romantic ambient wash — layered radial + linear oklch gradients for depth. Low opacity so chat text stays legible (WCAG-AA). */}