import React, { useMemo } from 'react'; import { SeasonalOverlayProps } from '../types'; import { animDoodleFloat, animConfettiTumble, animWobble, animRainbowDrift, animGoogly, animSparkle, } from './AprilFools.css'; // Deterministic pseudo-random so the scene is identical on every mount and the // reduced-motion preview thumbnail is stable. Large primes spread the values. const rand = (seed: number) => { const x = Math.sin(seed * 127.1 + 311.7) * 43758.5453; return x - Math.floor(x); }; // Bright-but-soft pastel rainbow in oklch. Kept luminous and gentle so the // doodles read as crayon pastel over chat without ever fighting the text. const PASTELS = [ 'oklch(0.85 0.12 20)', // pink 'oklch(0.88 0.12 90)', // butter yellow 'oklch(0.82 0.12 160)', // mint 'oklch(0.8 0.12 260)', // periwinkle 'oklch(0.84 0.12 320)', // lilac 'oklch(0.86 0.11 50)', // peach ]; // Inline-SVG data-URI doodle glyphs, drawn hand-sketch style (round caps, // open paths). `enc()` keeps them CSP-safe — no external assets, no base64. const enc = (svg: string) => `url("data:image/svg+xml,${encodeURIComponent(svg)}")`; // A single rough stroke wrapper helper for the glyph SVGs. const stroke = (color: string, body: string) => `${body}`; // Question mark — the playful "huh?" centerpiece doodle. const glyphQuestion = (c: string) => stroke( c, `` + ``, ); // Exclamation / "bang" — a surprised little doodle. const glyphBang = (c: string) => stroke(c, ``); // Squiggle — a loopy scribble that adds whimsy. const glyphSquiggle = (c: string) => stroke(c, ``); // Five-point doodle star (open-stroke, hand-drawn look). const glyphStar = (c: string) => stroke( c, ``, ); // A tiny heart doodle for extra grin. const glyphHeart = (c: string) => stroke(c, ``); const GLYPHS = [glyphQuestion, glyphBang, glyphSquiggle, glyphStar, glyphHeart, glyphQuestion]; type Doodle = { left: number; size: number; glyph: string; duration: number; delay: number; startTop: number; // used for the static (reduced) scatter opacity: number; }; type Confetti = { left: number; size: number; color: string; duration: number; delay: number; startTop: number; ratio: number; // chip aspect round: boolean; }; type Eye = { left: number; top: number; size: number; duration: number; delay: number; }; type Spark = { left: number; top: number; size: number; color: string; duration: number; delay: number; }; export function AprilFoolsOverlay({ reduced }: SeasonalOverlayProps) { // ~16 drifting doodles. Built once; per-element timing creates the variety. const doodles = useMemo(() => { const count = 16; const out: Doodle[] = []; for (let i = 0; i < count; i += 1) { const color = PASTELS[i % PASTELS.length]; out.push({ left: rand(i + 0.1) * 96 + 2, size: 18 + rand(i + 0.3) * 22, glyph: enc(GLYPHS[i % GLYPHS.length](color)), duration: 16 + rand(i + 0.5) * 12, delay: -rand(i + 0.7) * 26, startTop: rand(i + 0.9) * 92 + 4, opacity: 0.5 + rand(i + 0.2) * 0.32, }); } return out; }, []); // ~14 confetti chips in a couple of falling bands. const confetti = useMemo(() => { const count = 14; const out: Confetti[] = []; for (let i = 0; i < count; i += 1) { out.push({ left: rand(i + 3.1) * 98 + 1, size: 5 + rand(i + 3.3) * 6, color: PASTELS[(i + 2) % PASTELS.length], duration: 10 + rand(i + 3.5) * 9, delay: -rand(i + 3.7) * 18, startTop: rand(i + 3.9) * 96 + 2, ratio: 0.45 + rand(i + 3.2) * 0.8, round: rand(i + 3.6) > 0.6, }); } return out; }, []); // A few googly eyes peeking from corners/edges — the cheeky surprise. const eyes = useMemo(() => { const anchors = [ { left: 6, top: 12 }, { left: 90, top: 20 }, { left: 80, top: 82 }, { left: 14, top: 74 }, ]; return anchors.map((a, i) => ({ left: a.left, top: a.top, size: 22 + rand(i + 5.1) * 12, duration: 3 + rand(i + 5.3) * 2.5, delay: -rand(i + 5.5) * 3, })); }, []); // Sly winking sparkles scattered sparsely. const sparks = useMemo(() => { const count = 5; const out: Spark[] = []; for (let i = 0; i < count; i += 1) { out.push({ left: rand(i + 7.1) * 90 + 5, top: rand(i + 7.3) * 84 + 8, size: 12 + rand(i + 7.5) * 12, color: PASTELS[(i + 1) % PASTELS.length], duration: 4 + rand(i + 7.7) * 3, delay: -rand(i + 7.9) * 5, }); } return out; }, []); // Four-point glint used for the winking sparkles. const sparkGlint = (c: string) => enc( `` + ``, ); return ( <> {/* Soft pastel ambient wash — layered oklch radials for depth. Very low opacity so chat text keeps WCAG-AA contrast. */}