diff --git a/next.config.mjs b/next.config.mjs index 62a752a..962044b 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -6,8 +6,7 @@ const nextConfig = { remotePatterns: [ { protocol: 'https', - hostname: 'media.tenor.com', - pathname: '/1BCeG1aTiBAAAAAd/temptation-stairway-ena.gif' + hostname: 'media.tenor.com' } ] }, diff --git a/public/images/event_santahat01.png b/public/images/event_santahat01.png deleted file mode 100644 index 02d94ec..0000000 Binary files a/public/images/event_santahat01.png and /dev/null differ diff --git a/public/images/event_santahat1.png b/public/images/event_santahat1.png new file mode 100644 index 0000000..4705618 Binary files /dev/null and b/public/images/event_santahat1.png differ diff --git a/public/images/event_snowflake.png b/public/images/event_snowflake.png new file mode 100644 index 0000000..2d60ac5 Binary files /dev/null and b/public/images/event_snowflake.png differ diff --git a/src/components/client-windows.tsx b/src/components/client-windows.tsx index 47c30de..06c23f6 100644 --- a/src/components/client-windows.tsx +++ b/src/components/client-windows.tsx @@ -191,7 +191,7 @@ export const FakeRelativeWindow = ({ {/* Main window */}
{ return null; } -export const ChristmasHat: React.FC = ({ +export const ChristmasProperty: React.FC = ({ left, top, + img, size, className, flip @@ -46,12 +50,13 @@ export const ChristmasHat: React.FC = ({ left, top }} > - Santa Hat
@@ -63,8 +68,8 @@ function SnowfallRawBackground() { const [dimensions, setDimensions] = useState({ width: 0, height: 0 }); const snowflakesRef = useRef([]); const animationFrameRef = useRef(0); + const imageRef = useRef(null); - // Update dimensions on resize useEffect(() => { const updateDimensions = () => { setDimensions({ @@ -80,76 +85,105 @@ function SnowfallRawBackground() { }, []); useEffect(() => { - if (!canvasRef.current) return; - const canvas = canvasRef.current; + if (!canvas) return; const ctx = canvas.getContext('2d'); if (!ctx) return; canvas.width = dimensions.width; canvas.height = dimensions.height; - const createSnowflake = (init?: boolean): Snowflake => { - let posy = -10; - let radius = Math.random() * 4 + 2; - let speed = Math.random() * 1 + 0.5; - let sway = Math.random() * 0.5 + 0.2; + // Load the snowflake image using a plain Image so we can draw it to canvas reliably + const img = new Image(); + img.src = '/images/event_snowflake.png'; + let mounted = true; - if (init) { - const initHeight = (dimensions.height / 1.1); - posy = Math.random() * initHeight + posy; - radius = (posy / (initHeight + posy) - 1) * -radius; - } + const start = () => { + if (!mounted) return; + imageRef.current = img; - if (window.innerWidth < 768) { - radius /= 1.4; - } + const createSnowflake = (init?: boolean): Snowflake => { + let posy = -10; + let radius = Math.random() * 18 + 10; + let speed = Math.random() * 1 + 0.5; + let sway = Math.random() * 0.5 + 0.2; - return { - x: Math.random() * dimensions.width, - y: posy, - radius, - speed, - sway, - swayOffset: Math.random() * Math.PI * 2, - opacity: Math.random() * 0.5 + 0.5, - } - }; - - - const snowflakes: Snowflake[] = []; - for (let i = 0; i < 200; i++) { - snowflakes.push(createSnowflake(true)); - } - snowflakesRef.current = snowflakes; - - const animate = () => { - ctx.clearRect(0, 0, canvas.width, canvas.height); - - snowflakesRef.current.forEach((flake, index) => { - flake.y += flake.speed; - flake.x += Math.sin(flake.swayOffset + flake.y * 0.02) * flake.sway; - - const shrink = dimensions.height < 768 ? 0.001 : 0.005 - flake.radius = Math.max(0, flake.radius - shrink); - - if (flake.y > dimensions.height || flake.radius <= 0.5) { - snowflakesRef.current[index] = createSnowflake(); - return; + if (init) { + const initHeight = (dimensions.height / 1.1); + posy = Math.random() * initHeight + posy; + radius = (posy / (initHeight + posy) - 1) * -radius; } - ctx.beginPath(); - ctx.arc(flake.x, flake.y, flake.radius, 0, Math.PI * 2); - ctx.fillStyle = `rgba(255, 255, 255, ${flake.opacity})`; - ctx.fill(); - }); + if (window.innerWidth < 768) { + radius /= 1.4; + } + + return { + x: Math.random() * dimensions.width, + y: posy, + radius, + speed, + sway, + swayOffset: Math.random() * Math.PI * 2, + opacity: Math.random() * 0.5 + 0.2, + rotation: Math.random() * Math.PI * 2, + rotationSpeed: (Math.random() - 0.5) * 0.02, + } + }; + + const snowflakes: Snowflake[] = []; + for (let i = 0; i < 80; i++) { + snowflakes.push(createSnowflake(true)); + } + snowflakesRef.current = snowflakes; + + const animate = () => { + ctx.clearRect(0, 0, canvas.width, canvas.height); + + snowflakesRef.current.forEach((flake, index) => { + flake.y += flake.speed; + flake.x += Math.sin(flake.swayOffset + flake.y * 0.02) * flake.sway; + flake.rotation += flake.rotationSpeed; + + const shrink = dimensions.height < 768 ? 0.005 : 0.01; + flake.radius = Math.max(2, flake.radius - shrink); + + if (flake.y > dimensions.height || flake.radius <= 0.5) { + snowflakesRef.current[index] = createSnowflake(); + return; + } + + ctx.save(); + ctx.globalAlpha = flake.opacity; + ctx.translate(flake.x, flake.y); + ctx.rotate(flake.rotation); + + ctx.drawImage( + img, + -flake.radius / 2, + -flake.radius / 2, + flake.radius, + flake.radius + ); + + ctx.restore(); + }); + + animationFrameRef.current = requestAnimationFrame(animate); + }; animationFrameRef.current = requestAnimationFrame(animate); }; - animationFrameRef.current = requestAnimationFrame(animate); + if (img.complete) start(); + else img.onload = start; - return () => cancelAnimationFrame(animationFrameRef.current); + return () => { + mounted = false; + cancelAnimationFrame(animationFrameRef.current); + img.onload = null; + imageRef.current = null; + }; }, [dimensions]); return ( @@ -157,6 +191,7 @@ function SnowfallRawBackground() { ref={canvasRef} className="fixed top-0 left-0 w-full h-full pointer-events-none" style={{ zIndex: 0 }} + aria-hidden="true" /> ); } diff --git a/src/components/windows.tsx b/src/components/windows.tsx index b60c13c..e3cfcc3 100644 --- a/src/components/windows.tsx +++ b/src/components/windows.tsx @@ -3,7 +3,7 @@ import { FloatingLabel } from "@/components/floating-label"; import { FakeRelativeWindow, RestoreWindowsButton } from "./client-windows"; import Image from "next/image" import Link from "next/link"; -import { ChristmasHat } from "./events/christmas"; +import { ChristmasProperty } from "./events/christmas"; export const FakeWindow = ({ windowText, children @@ -12,7 +12,12 @@ export const FakeWindow = ({ children: React.ReactNode }) => (
- +
@@ -48,7 +53,7 @@ export const HomeWindows = () => ( >
- + Nola ( />