// Ported from bmo-preview (https://github.com/PageZ948/bmo-preview). Visuals kept 1:1. /** * SeasonalOverlay — renders ground-level seasonal decorations as an SVG group. * * Supported overlays: autumn-leaves, snow, cherry-blossoms. * Positions are deterministic (index-based). No Math.random(). * Cherry blossom petals in the air use a gentle drift animation * (garden-pollen-drift) — same keyframe, different colors. */ import { memo } from "react"; interface SeasonalOverlayProps { overlay: string; sceneWidth: number; sceneHeight: number; } /** Deterministic value in [0, range) for a given index + seed. */ function det(i: number, range: number, seed = 37): number { return ((i * seed + i * i * 13) % 1000) / 1000 * range; } function detRange(i: number, min: number, max: number, seed = 37): number { return min + det(i, max - min, seed); } // ---- Autumn leaves ---- const AUTUMN_COLORS = ["#f59e0b", "#ea580c", "#dc2626", "#92400e", "#d97706", "#b45309"]; function leafPath(): string { return "M0,-3 C1.5,-1.5 3,0 1.5,3 C0,4.5 -1.5,3 0,0 C-1.5,-1.5 -3,0 0,-3Z"; } function AutumnLeaves({ count, sceneWidth, sceneHeight, }: { count: number; sceneWidth: number; sceneHeight: number; }) { const groundMinY = sceneHeight * 0.60; const groundMaxY = sceneHeight * 0.70; return ( <> {Array.from({ length: count }, (_, i) => { const x = detRange(i, 10, sceneWidth - 10, 41); const y = detRange(i, groundMinY, groundMaxY, 59); const rotate = detRange(i, -45, 45, 73); const scale = detRange(i, 0.6, 1.2, 89); const color = AUTUMN_COLORS[i % AUTUMN_COLORS.length]; return ( ); })} ); } // ---- Snow ---- function SnowOverlay({ sceneWidth, sceneHeight, }: { sceneWidth: number; sceneHeight: number; }) { // Wavy ground cover path const groundY = sceneHeight * 0.63; const amplitude = 3; const segments = 12; const segW = sceneWidth / segments; let d = `M0,${groundY}`; for (let i = 0; i <= segments; i++) { const x = i * segW; const y = groundY + (i % 2 === 0 ? -amplitude : amplitude); if (i === 0) d += ` L${x},${y}`; else d += ` Q${x - segW / 2},${y + amplitude * 0.5} ${x},${y}`; } d += ` L${sceneWidth},${sceneHeight} L0,${sceneHeight} Z`; // Small snow pile ellipses near ground features const pilePositions = Array.from({ length: 8 }, (_, i) => ({ cx: detRange(i, 15, sceneWidth - 15, 43), cy: detRange(i, sceneHeight * 0.60, sceneHeight * 0.67, 61), rx: detRange(i, 6, 14, 73), ry: detRange(i, 2, 5, 89), })); return ( <> {/* Wavy ground cover */} {/* Snow piles */} {pilePositions.map((p, i) => ( ))} ); } // ---- Cherry blossoms ---- const BLOSSOM_COLORS = ["#fbb6ce", "#f9a8d4", "#f472b6", "#fbcfe8"]; function CherryBlossoms({ count, sceneWidth, sceneHeight, }: { count: number; sceneWidth: number; sceneHeight: number; }) { const airCount = Math.floor(count * 0.6); const groundCount = count - airCount; return ( <> {/* Air petals — animated drift */} {Array.from({ length: airCount }, (_, i) => { const cx = detRange(i, 5, sceneWidth - 5, 43); const cy = detRange(i, sceneHeight * 0.1, sceneHeight * 0.55, 59); const r = detRange(i, 1, 2.5, 71); const color = BLOSSOM_COLORS[i % BLOSSOM_COLORS.length]; const duration = detRange(i, 5, 10, 83); const delay = detRange(i, 0, 7, 97); return ( ); })} {/* Ground petals — static */} {Array.from({ length: groundCount }, (_, i) => { const cx = detRange(i, 10, sceneWidth - 10, 101); const cy = detRange(i, sceneHeight * 0.60, sceneHeight * 0.68, 113); const r = detRange(i, 0.8, 2, 127); const color = BLOSSOM_COLORS[i % BLOSSOM_COLORS.length]; return ( ); })} ); } // ---- Main component ---- export const SeasonalOverlay = memo(function SeasonalOverlay({ overlay, sceneWidth, sceneHeight, }: SeasonalOverlayProps) { if (overlay === "none" || !overlay) return null; if (overlay === "autumn-leaves") { return ( ); } if (overlay === "snow") { return ( ); } if (overlay === "cherry-blossoms") { return ( ); } return null; });