아 404 컴포넌트 날먹 개꿀이죠~

This commit is contained in:
imnyang 2025-05-08 16:40:17 +09:00
commit 14a04aa3e7
3 changed files with 218 additions and 4 deletions

View file

@ -1,8 +1,17 @@
import { CommitsGrid } from "@/components/commits-grid"
import { Button } from "@/components/ui/button";
import { useNavigate } from "react-router";
export default function NotFound() { export default function NotFound() {
const navigate = useNavigate();
return ( return (
<div> <div className="flex flex-col justify-center items-center h-screen gap-8">
<h1>404 - Not Found</h1> <CommitsGrid text="404" />
<p>The page you are looking for does not exist.</p> <p className="text-2xl">The page you are looking for does not exist.</p>
<Button variant="outline" onClick={() => navigate("/")}>
Go to Home
</Button>
</div> </div>
); );
} }

View file

@ -0,0 +1,179 @@
"use client"
import * as React from "react"
import { cn } from "@/lib/utils";
import type { CSSProperties } from "react";
export const CommitsGrid = ({ text }: { text: string }) => {
const cleanString = (str: string): string => {
const upperStr = str.toUpperCase();
const withoutAccents = upperStr
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "");
const allowedChars = Object.keys(letterPatterns);
return withoutAccents
.split("")
.filter((char) => allowedChars.includes(char))
.join("");
};
const generateHighlightedCells = (text: string) => {
const cleanedText = cleanString(text);
const width = Math.max(cleanedText.length * 6, 6) + 1;
let currentPosition = 1; // we start at 1 to leave space for the top border
const highlightedCells: number[] = [];
cleanedText
.toUpperCase()
.split("")
.forEach((char) => {
if (letterPatterns[char]) {
const pattern = letterPatterns[char].map((pos) => {
const row = Math.floor(pos / 50);
const col = pos % 50;
return (row + 1) * width + col + currentPosition;
});
highlightedCells.push(...pattern);
}
currentPosition += 6;
});
return {
cells: highlightedCells,
width,
height: 9, // 7+2 for the top and bottom borders
};
};
const {
cells: highlightedCells,
width: gridWidth,
height: gridHeight,
} = generateHighlightedCells(text);
const getRandomColor = () => {
const commitColors = [
"#48d55d",
"#016d32",
"#0d4429"
];
const randomIndex = Math.floor(Math.random() * commitColors.length);
return commitColors[randomIndex];
};
const getRandomDelay = () => `${(Math.random() * 0.6).toFixed(1)}s`;
const getRandomFlash = () => +(Math.random() < 0.3);
return (
<section
className="w-full max-w-xl bg-card border grid p-1.5 sm:p-3 gap-0.5 sm:gap-1 rounded-[10px] sm:rounded-[15px]"
style={{
gridTemplateColumns: `repeat(${gridWidth}, minmax(0, 1fr))`,
gridTemplateRows: `repeat(${gridHeight}, minmax(0, 1fr))`,
}}
>
{Array.from({ length: gridWidth * gridHeight }).map((_, index) => {
const isHighlighted = highlightedCells.includes(index);
const shouldFlash = !isHighlighted && getRandomFlash();
return (
<div
key={index}
className={cn(
`border h-full w-full aspect-square rounded-[4px] sm:rounded-[3px]`,
isHighlighted ? "animate-highlight" : "",
shouldFlash ? "animate-flash" : "",
!isHighlighted && !shouldFlash ? "bg-card" : ""
)}
style={
{
animationDelay: getRandomDelay(),
"--highlight": getRandomColor(),
} as CSSProperties
}
/>
);
})}
</section>
);
};
const letterPatterns: { [key: string]: number[] } = {
A: [
1, 2, 3, 50, 100, 150, 200, 250, 300, 54, 104, 154, 204, 254, 304, 151, 152,
153,
],
B: [
0, 1, 2, 3, 4, 50, 100, 150, 151, 200, 250, 300, 301, 302, 303, 304, 54,
104, 152, 153, 204, 254, 303,
],
C: [0, 1, 2, 3, 4, 50, 100, 150, 200, 250, 300, 301, 302, 303, 304],
D: [
0, 1, 2, 3, 50, 100, 150, 200, 250, 300, 301, 302, 54, 104, 154, 204, 254,
303,
],
E: [0, 1, 2, 3, 4, 50, 100, 150, 200, 250, 300, 301, 302, 303, 304, 151, 152],
F: [0, 1, 2, 3, 4, 50, 100, 150, 200, 250, 300, 151, 152, 153],
G: [
0, 1, 2, 3, 4, 50, 100, 150, 200, 250, 300, 301, 302, 303, 153, 204, 154,
304, 254,
],
H: [
0, 50, 100, 150, 200, 250, 300, 151, 152, 153, 4, 54, 104, 154, 204, 254,
304,
],
I: [0, 1, 2, 3, 4, 52, 102, 152, 202, 252, 300, 301, 302, 303, 304],
J: [0, 1, 2, 3, 4, 52, 102, 152, 202, 250, 252, 302, 300, 301],
K: [0, 4, 50, 100, 150, 200, 250, 300, 151, 152, 103, 54, 203, 254, 304],
L: [0, 50, 100, 150, 200, 250, 300, 301, 302, 303, 304],
M: [
0, 50, 100, 150, 200, 250, 300, 51, 102, 53, 4, 54, 104, 154, 204, 254, 304,
],
N: [
0, 50, 100, 150, 200, 250, 300, 51, 102, 153, 204, 4, 54, 104, 154, 204,
254, 304,
],
Ñ: [
0, 50, 100, 150, 200, 250, 300, 51, 102, 153, 204, 4, 54, 104, 154, 204,
254, 304,
],
O: [1, 2, 3, 50, 100, 150, 200, 250, 301, 302, 303, 54, 104, 154, 204, 254],
P: [0, 50, 100, 150, 200, 250, 300, 1, 2, 3, 54, 104, 151, 152, 153],
Q: [
1, 2, 3, 50, 100, 150, 200, 250, 301, 302, 54, 104, 154, 204, 202, 253, 304,
],
R: [
0, 50, 100, 150, 200, 250, 300, 1, 2, 3, 54, 104, 151, 152, 153, 204, 254,
304,
],
S: [1, 2, 3, 4, 50, 100, 151, 152, 153, 204, 254, 300, 301, 302, 303],
T: [0, 1, 2, 3, 4, 52, 102, 152, 202, 252, 302],
U: [0, 50, 100, 150, 200, 250, 301, 302, 303, 4, 54, 104, 154, 204, 254],
V: [0, 50, 100, 150, 200, 251, 302, 4, 54, 104, 154, 204, 253],
W: [
0, 50, 100, 150, 200, 250, 301, 152, 202, 252, 4, 54, 104, 154, 204, 254,
303,
],
X: [0, 50, 203, 254, 304, 4, 54, 152, 101, 103, 201, 250, 300],
Y: [0, 50, 101, 152, 202, 252, 302, 4, 54, 103],
Z: [0, 1, 2, 3, 4, 54, 103, 152, 201, 250, 300, 301, 302, 303, 304],
"0": [1, 2, 3, 50, 100, 150, 200, 250, 301, 302, 303, 54, 104, 154, 204, 254],
"1": [1, 52, 102, 152, 202, 252, 302, 0, 2, 300, 301, 302, 303, 304],
"2": [0, 1, 2, 3, 54, 104, 152, 153, 201, 250, 300, 301, 302, 303, 304],
"3": [0, 1, 2, 3, 54, 104, 152, 153, 204, 254, 300, 301, 302, 303],
"4": [0, 50, 100, 150, 4, 54, 104, 151, 152, 153, 154, 204, 254, 304],
"5": [0, 1, 2, 3, 4, 50, 100, 151, 152, 153, 204, 254, 300, 301, 302, 303],
"6": [
1, 2, 3, 50, 100, 150, 151, 152, 153, 200, 250, 301, 302, 204, 254, 303,
],
"7": [0, 1, 2, 3, 4, 54, 103, 152, 201, 250, 300],
"8": [
1, 2, 3, 50, 100, 151, 152, 153, 200, 250, 301, 302, 303, 54, 104, 204, 254,
],
"9": [1, 2, 3, 50, 100, 151, 152, 153, 154, 204, 254, 304, 54, 104],
" ": [],
};

View file

@ -142,3 +142,29 @@
@apply bg-background text-foreground; @apply bg-background text-foreground;
} }
} }
@theme {
--animate-highlight: highlight 0.6s ease forwards;
--animate-flash: flash 0.6s ease forwards;
@keyframes highlight {
0% {
background-color: transparent;
}
100% {
background-color: var(--highlight);
}
}
@keyframes flash {
0% {
background-color: hsl(var(--card));
}
50% {
background-color: var(--highlight);
}
100% {
background-color: hsl(var(--card));
}
}
}