Add christmas events
This commit is contained in:
parent
4760f51095
commit
29d251459a
16
package-lock.json
generated
16
package-lock.json
generated
@ -13,7 +13,7 @@
|
|||||||
"@next/mdx": "^15.3.4",
|
"@next/mdx": "^15.3.4",
|
||||||
"@types/mdx": "^2.0.13",
|
"@types/mdx": "^2.0.13",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"dayjs": "^1.11.13",
|
"date-fns": "^4.1.0",
|
||||||
"next": "^14.2.35",
|
"next": "^14.2.35",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18"
|
"react-dom": "^18"
|
||||||
@ -2112,11 +2112,15 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dayjs": {
|
"node_modules/date-fns": {
|
||||||
"version": "1.11.19",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz",
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
|
||||||
"integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==",
|
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/kossnocorp"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.4.0",
|
"version": "4.4.0",
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
"@next/mdx": "^15.3.4",
|
"@next/mdx": "^15.3.4",
|
||||||
"@types/mdx": "^2.0.13",
|
"@types/mdx": "^2.0.13",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"dayjs": "^1.11.13",
|
"date-fns": "^4.1.0",
|
||||||
"next": "^14.2.35",
|
"next": "^14.2.35",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18"
|
"react-dom": "^18"
|
||||||
|
|||||||
BIN
public/images/event_santahat01.png
Normal file
BIN
public/images/event_santahat01.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 55 KiB |
@ -6,10 +6,12 @@ import HomeText from "@/components/texts/home.mdx"
|
|||||||
import Link from "@/components/link";
|
import Link from "@/components/link";
|
||||||
import { FakeWindow, HomeWindows } from "@/components/windows";
|
import { FakeWindow, HomeWindows } from "@/components/windows";
|
||||||
import { WindowManagerProvider } from "@/hooks/window-manager";
|
import { WindowManagerProvider } from "@/hooks/window-manager";
|
||||||
|
import { SnowfallBackground } from "@/components/events/christmas";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (<>
|
return (<>
|
||||||
<main className="flex items-center pt-16 md:pt-24 pb-12 px-8 md:px-0 overflow-x-hidden">
|
<main className="flex items-center pt-16 md:pt-24 pb-12 px-8 md:px-0 overflow-x-hidden">
|
||||||
|
<SnowfallBackground />
|
||||||
<WindowManagerProvider>
|
<WindowManagerProvider>
|
||||||
<FakeWindow windowText="Homepage">
|
<FakeWindow windowText="Homepage">
|
||||||
<HomeWindows />
|
<HomeWindows />
|
||||||
|
|||||||
168
src/components/events/christmas.tsx
Normal file
168
src/components/events/christmas.tsx
Normal 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>
|
||||||
|
)
|
||||||
1
src/components/events/eventprops.tsx
Normal file
1
src/components/events/eventprops.tsx
Normal file
@ -0,0 +1 @@
|
|||||||
|
'use client'
|
||||||
@ -3,6 +3,7 @@ import { FloatingLabel } from "@/components/floating-label";
|
|||||||
import { FakeRelativeWindow, RestoreWindowsButton } from "./client-windows";
|
import { FakeRelativeWindow, RestoreWindowsButton } from "./client-windows";
|
||||||
import Image from "next/image"
|
import Image from "next/image"
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { ChristmasHat } from "./events/christmas";
|
||||||
|
|
||||||
export const FakeWindow = ({
|
export const FakeWindow = ({
|
||||||
windowText, children
|
windowText, children
|
||||||
@ -10,7 +11,8 @@ export const FakeWindow = ({
|
|||||||
windowText: string
|
windowText: string
|
||||||
children: React.ReactNode
|
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="p-1 pb-0">
|
||||||
<div className="hidden md:flex bg-primary p-2 justify-between text-background">
|
<div className="hidden md:flex bg-primary p-2 justify-between text-background">
|
||||||
<div className="ms-1 pointer-events-none">
|
<div className="ms-1 pointer-events-none">
|
||||||
@ -45,6 +47,8 @@ export const HomeWindows = () => (
|
|||||||
draggable
|
draggable
|
||||||
>
|
>
|
||||||
<FloatingLabel placeholder="This is Nola, my OC :3">
|
<FloatingLabel placeholder="This is Nola, my OC :3">
|
||||||
|
<div className="relative">
|
||||||
|
<ChristmasHat size={150} top={-40} left={70} flip />
|
||||||
<Image
|
<Image
|
||||||
className=""
|
className=""
|
||||||
alt="Nola"
|
alt="Nola"
|
||||||
@ -52,6 +56,7 @@ export const HomeWindows = () => (
|
|||||||
width={200}
|
width={200}
|
||||||
height={200}
|
height={200}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</FloatingLabel>
|
</FloatingLabel>
|
||||||
</FakeRelativeWindow>
|
</FakeRelativeWindow>
|
||||||
<FakeRelativeWindow
|
<FakeRelativeWindow
|
||||||
|
|||||||
5
src/lib/types.ts
Normal file
5
src/lib/types.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export type EventsDate = {
|
||||||
|
name: string,
|
||||||
|
start: number[],
|
||||||
|
end: number[]
|
||||||
|
}
|
||||||
@ -1,8 +1,29 @@
|
|||||||
'use client'
|
'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 () {
|
export function getExpirationDate () {
|
||||||
const expirationDate = new Date("2026-02-15");
|
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 })
|
||||||
|
})
|
||||||
}
|
}
|
||||||
14
yarn.lock
14
yarn.lock
@ -320,10 +320,10 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
source-map "^0.7.0"
|
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"
|
version "14.2.33"
|
||||||
resolved "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.33.tgz"
|
resolved "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.33.tgz"
|
||||||
integrity sha512-FnFn+ZBgsVMbGDsTqo8zsnRzydvsGV8vfiWwUo1LD8FTmPTdV+otGSWKc4LJec0oSexFnCYVO4hX8P8qQKaSlg==
|
integrity sha512-nOjfZMy8B94MdisuzZo9/57xuFVLHJaDj5e/xrduJp9CV2/HrfxTRH2fbyLe+K9QT41WBLUd4iXX3R7jBp0EUg==
|
||||||
|
|
||||||
"@nodelib/fs.scandir@2.1.5":
|
"@nodelib/fs.scandir@2.1.5":
|
||||||
version "2.1.5"
|
version "2.1.5"
|
||||||
@ -1003,10 +1003,10 @@ data-view-byte-offset@^1.0.1:
|
|||||||
es-errors "^1.3.0"
|
es-errors "^1.3.0"
|
||||||
is-data-view "^1.0.1"
|
is-data-view "^1.0.1"
|
||||||
|
|
||||||
dayjs@^1.11.13:
|
date-fns@^4.1.0:
|
||||||
version "1.11.19"
|
version "4.1.0"
|
||||||
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz"
|
resolved "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz"
|
||||||
integrity sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==
|
integrity sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==
|
||||||
|
|
||||||
debug@^3.2.7:
|
debug@^3.2.7:
|
||||||
version "3.2.7"
|
version "3.2.7"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user