Add christmas events

This commit is contained in:
Nomi Nonsense (Nonszy) 2025-12-25 16:53:09 +07:00
parent 4760f51095
commit 29d251459a
10 changed files with 230 additions and 24 deletions

16
package-lock.json generated
View File

@ -13,7 +13,7 @@
"@next/mdx": "^15.3.4",
"@types/mdx": "^2.0.13",
"clsx": "^2.1.1",
"dayjs": "^1.11.13",
"date-fns": "^4.1.0",
"next": "^14.2.35",
"react": "^18",
"react-dom": "^18"
@ -2112,11 +2112,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/dayjs": {
"version": "1.11.19",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz",
"integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==",
"license": "MIT"
"node_modules/date-fns": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/kossnocorp"
}
},
"node_modules/debug": {
"version": "4.4.0",

View File

@ -14,7 +14,7 @@
"@next/mdx": "^15.3.4",
"@types/mdx": "^2.0.13",
"clsx": "^2.1.1",
"dayjs": "^1.11.13",
"date-fns": "^4.1.0",
"next": "^14.2.35",
"react": "^18",
"react-dom": "^18"

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View File

@ -6,10 +6,12 @@ import HomeText from "@/components/texts/home.mdx"
import Link from "@/components/link";
import { FakeWindow, HomeWindows } from "@/components/windows";
import { WindowManagerProvider } from "@/hooks/window-manager";
import { SnowfallBackground } from "@/components/events/christmas";
export default function Home() {
return (<>
<main className="flex items-center pt-16 md:pt-24 pb-12 px-8 md:px-0 overflow-x-hidden">
<SnowfallBackground />
<WindowManagerProvider>
<FakeWindow windowText="Homepage">
<HomeWindows />

View File

@ -0,0 +1,168 @@
'use client'
import { getEvent } from '@/lib/utils';
import clsx from 'clsx';
import Image from 'next/image';
import { ReactNode, useEffect, useRef, useState } from 'react';
interface Snowflake {
x: number;
y: number;
radius: number;
speed: number;
sway: number;
swayOffset: number;
opacity: number;
}
interface ChristmasProps {
left: number;
top: number;
size: number;
className?: string;
flip?: boolean;
absolute?: boolean;
}
export const ChristmasExclusive = ({ children }: { children: ReactNode }) => {
const isItChristmas = getEvent()?.name == 'christmas';
if (isItChristmas) return children;
return null;
}
export const ChristmasHat: React.FC<ChristmasProps> = ({
left,
top,
size,
className,
flip
}) => {
return (
<ChristmasExclusive>
<div
className={clsx('absolute md:block hidden', flip && '-scale-x-100', className)}
style={{
left, top
}}
>
<Image
className='pointer-events-none'
src={'/images/event_santahat01.png'}
alt='Santa Hat'
height={size}
width={size}
/>
</div>
</ChristmasExclusive>
)
}
function SnowfallRawBackground() {
const canvasRef = useRef<HTMLCanvasElement>(null);
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
const snowflakesRef = useRef<Snowflake[]>([]);
const animationFrameRef = useRef<number>(0);
// Update dimensions on resize
useEffect(() => {
const updateDimensions = () => {
setDimensions({
width: window.innerWidth,
height: window.innerHeight,
});
};
updateDimensions();
window.addEventListener('resize', updateDimensions);
return () => window.removeEventListener('resize', updateDimensions);
}, []);
useEffect(() => {
if (!canvasRef.current) return;
const canvas = canvasRef.current;
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;
if (init) {
const initHeight = (dimensions.height / 1.1);
posy = Math.random() * initHeight + posy;
radius = (posy / (initHeight + posy) - 1) * -radius;
}
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.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;
}
ctx.beginPath();
ctx.arc(flake.x, flake.y, flake.radius, 0, Math.PI * 2);
ctx.fillStyle = `rgba(255, 255, 255, ${flake.opacity})`;
ctx.fill();
});
animationFrameRef.current = requestAnimationFrame(animate);
};
animationFrameRef.current = requestAnimationFrame(animate);
return () => cancelAnimationFrame(animationFrameRef.current);
}, [dimensions]);
return (
<canvas
ref={canvasRef}
className="fixed top-0 left-0 w-full h-full pointer-events-none"
style={{ zIndex: 0 }}
/>
);
}
export const SnowfallBackground = () => (
<ChristmasExclusive>
<SnowfallRawBackground />
</ChristmasExclusive>
)

View File

@ -0,0 +1 @@
'use client'

View File

@ -3,6 +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";
export const FakeWindow = ({
windowText, children
@ -10,7 +11,8 @@ export const FakeWindow = ({
windowText: string
children: React.ReactNode
}) => (
<div className="mx-auto w-[480px] md:w-[520px] md:border border-primary">
<div className="relative md:bg-background mx-auto w-[480px] md:w-[520px] md:border border-primary z-10">
<ChristmasHat size={180} left={-60} top={-80} />
<div className="p-1 pb-0">
<div className="hidden md:flex bg-primary p-2 justify-between text-background">
<div className="ms-1 pointer-events-none">
@ -45,13 +47,16 @@ export const HomeWindows = () => (
draggable
>
<FloatingLabel placeholder="This is Nola, my OC :3">
<Image
className=""
alt="Nola"
src="/images/nola.png"
width={200}
height={200}
/>
<div className="relative">
<ChristmasHat size={150} top={-40} left={70} flip />
<Image
className=""
alt="Nola"
src="/images/nola.png"
width={200}
height={200}
/>
</div>
</FloatingLabel>
</FakeRelativeWindow>
<FakeRelativeWindow

5
src/lib/types.ts Normal file
View File

@ -0,0 +1,5 @@
export type EventsDate = {
name: string,
start: number[],
end: number[]
}

View File

@ -1,8 +1,29 @@
'use client'
import dayjs from 'dayjs';
import { format, isWithinInterval } from "date-fns";
import type { EventsDate } from "./types";
const events: EventsDate[] = [
{
name: 'christmas',
start: [12, 20],
end: [12, 30]
},
]
export function getExpirationDate () {
const expirationDate = new Date("2026-02-15");
return dayjs(expirationDate).format("dddd, D MMMM YYYY");
return format(expirationDate, "dddd, D MMMM YYYY");
}
export function getEvent (): EventsDate | undefined {
return events.find((e) => {
const today = new Date(Date.now());
const year = today.getFullYear();
const start = new Date(year, e.start[0] - 1, e.start[1]);
const end = new Date(year, e.end[0] - 1, e.end[1]);
return isWithinInterval(today, { start, end })
})
}

View File

@ -320,10 +320,10 @@
dependencies:
source-map "^0.7.0"
"@next/swc-linux-x64-gnu@14.2.33":
"@next/swc-win32-x64-msvc@14.2.33":
version "14.2.33"
resolved "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.33.tgz"
integrity sha512-FnFn+ZBgsVMbGDsTqo8zsnRzydvsGV8vfiWwUo1LD8FTmPTdV+otGSWKc4LJec0oSexFnCYVO4hX8P8qQKaSlg==
resolved "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.33.tgz"
integrity sha512-nOjfZMy8B94MdisuzZo9/57xuFVLHJaDj5e/xrduJp9CV2/HrfxTRH2fbyLe+K9QT41WBLUd4iXX3R7jBp0EUg==
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
@ -1003,10 +1003,10 @@ data-view-byte-offset@^1.0.1:
es-errors "^1.3.0"
is-data-view "^1.0.1"
dayjs@^1.11.13:
version "1.11.19"
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz"
integrity sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==
date-fns@^4.1.0:
version "4.1.0"
resolved "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz"
integrity sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==
debug@^3.2.7:
version "3.2.7"