404lab 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
Binary file
package/bin/index.js ADDED
@@ -0,0 +1,176 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "fs";
4
+ import path from "path";
5
+ import { fileURLToPath } from "url";
6
+
7
+ /**
8
+ * Resolve __dirname equivalent for ES modules
9
+ */
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+
13
+ /**
14
+ * Supported templates mapped to component names
15
+ */
16
+ const TEMPLATES = {
17
+ stoneage: "StoneAge",
18
+ amongus: "AmongUs",
19
+ blueglitch: "BlueGlitch",
20
+ modern: "ModernPage",
21
+ poet: "Poet",
22
+ retro: "RetroTv",
23
+ simple: "SimplePage",
24
+ snow: "Snow",
25
+ strangethings: "StrangerThings",
26
+ terminal: "Terminal404",
27
+ };
28
+
29
+ /**
30
+ * Read template content from templates directory
31
+ */
32
+ function getTemplateContent(componentName) {
33
+ const templatesDir = path.join(__dirname, "..", "templates");
34
+ const templatePath = path.join(templatesDir, `${componentName}.tsx`);
35
+
36
+ if (!fs.existsSync(templatePath)) {
37
+ throw new Error(`Template "${componentName}" not found`);
38
+ }
39
+
40
+ return fs.readFileSync(templatePath, "utf-8");
41
+ }
42
+
43
+ /**
44
+ * Create the 404 component file
45
+ */
46
+ function createComponentFile(componentName, templateContent) {
47
+ const componentDir = path.join(process.cwd(), "components", "404");
48
+ const componentPath = path.join(componentDir, `${componentName}.tsx`);
49
+
50
+ fs.mkdirSync(componentDir, { recursive: true });
51
+ fs.writeFileSync(componentPath, templateContent, "utf-8");
52
+
53
+ return componentPath;
54
+ }
55
+
56
+ /**
57
+ * Create or overwrite app/not-found.tsx
58
+ */
59
+ function createNotFoundPage(componentName) {
60
+ const appDir = path.join(process.cwd(), "app");
61
+ const notFoundPath = path.join(appDir, "not-found.tsx");
62
+
63
+ fs.mkdirSync(appDir, { recursive: true });
64
+
65
+ const content = `import ${componentName} from "@/components/404/${componentName}";
66
+
67
+ export default function NotFoundPage() {
68
+ return <${componentName} />;
69
+ }
70
+ `;
71
+
72
+ fs.writeFileSync(notFoundPath, content, "utf-8");
73
+
74
+ return notFoundPath;
75
+ }
76
+
77
+ /**
78
+ * Display help information
79
+ */
80
+ function showHelp() {
81
+ console.log(`
82
+ 404lab — Custom 404 Page Generator for Next.js (App Router)
83
+
84
+ Usage:
85
+ 404lab add <template> Generate a 404 page from a template
86
+ 404lab list List all available templates
87
+ 404lab --help Show this help message
88
+
89
+ Available Templates:
90
+ ${Object.keys(TEMPLATES).map(t => ` ${t}`).join("\n")}
91
+
92
+ Examples:
93
+ 404lab add stoneage
94
+ 404lab add retro
95
+
96
+ Generated Files:
97
+ components/404/<Template>.tsx
98
+ app/not-found.tsx (overwritten if it already exists)
99
+ `);
100
+ }
101
+
102
+ /**
103
+ * List available templates
104
+ */
105
+ function listTemplates() {
106
+ console.log("\nAvailable 404 Templates:\n");
107
+
108
+ Object.keys(TEMPLATES).forEach((name) => {
109
+ console.log(` ${name}`);
110
+ });
111
+
112
+ console.log("\nUsage: 404lab add <template>\n");
113
+ }
114
+
115
+ /**
116
+ * Main CLI entry point
117
+ */
118
+ function main() {
119
+ const args = process.argv.slice(2);
120
+
121
+ if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
122
+ showHelp();
123
+ process.exit(0);
124
+ }
125
+
126
+ if (args[0] === "list" || args[0] === "ls") {
127
+ listTemplates();
128
+ process.exit(0);
129
+ }
130
+
131
+ if (args[0] !== "add") {
132
+ console.error(`Error: Unknown command "${args[0]}"`);
133
+ console.log('Run "404lab --help" for usage information.');
134
+ process.exit(1);
135
+ }
136
+
137
+ if (!args[1]) {
138
+ console.error("Error: Template name is required.");
139
+ console.log("Usage: 404lab add <template>");
140
+ console.log("Available templates:", Object.keys(TEMPLATES).join(", "));
141
+ process.exit(1);
142
+ }
143
+
144
+ const templateKey = args[1].toLowerCase();
145
+ const componentName = TEMPLATES[templateKey];
146
+
147
+ if (!componentName) {
148
+ console.error(`Error: Template "${templateKey}" does not exist.`);
149
+ console.log("Available templates:", Object.keys(TEMPLATES).join(", "));
150
+ console.log('Run "404lab list" to view all templates.');
151
+ process.exit(1);
152
+ }
153
+
154
+ try {
155
+ console.log(`Installing 404 template: ${componentName}`);
156
+
157
+ const templateContent = getTemplateContent(componentName);
158
+
159
+ const componentPath = createComponentFile(componentName, templateContent);
160
+ console.log(`Created: ${path.relative(process.cwd(), componentPath)}`);
161
+
162
+ const notFoundPath = createNotFoundPage(componentName);
163
+ console.log(`Created: ${path.relative(process.cwd(), notFoundPath)}`);
164
+
165
+ console.log("\n404 page successfully installed.");
166
+ console.log("Next steps:");
167
+ console.log(" 1. Start your Next.js development server.");
168
+ console.log(" 2. Visit a non-existent route to view the 404 page.");
169
+ console.log(` 3. Customize components/404/${componentName}.tsx as needed.\n`);
170
+ } catch (error) {
171
+ console.error("Error generating files:", error.message);
172
+ process.exit(1);
173
+ }
174
+ }
175
+
176
+ main();
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "404lab",
3
+ "version": "1.0.0",
4
+ "description": "A CLI tool for generating beautiful custom 404 pages in Next.js projects",
5
+ "type": "module",
6
+ "bin": {
7
+ "404lab": "./bin/index.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "templates",
12
+ "README.md"
13
+ ],
14
+ "keywords": [
15
+ "nextjs",
16
+ "404",
17
+ "not-found",
18
+ "cli",
19
+ "generator",
20
+ "templates"
21
+ ],
22
+ "author": "Your Name <your.email@example.com>",
23
+ "license": "MIT",
24
+ "engines": {
25
+ "node": ">=16.0.0"
26
+ },
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/yourusername/404lab.git"
30
+ }
31
+ }
@@ -0,0 +1,111 @@
1
+ "use client";
2
+
3
+ import { useEffect } from "react";
4
+ import Image from "next/image";
5
+
6
+ export default function AmongUs() {
7
+ useEffect(() => {
8
+ const body = document.body;
9
+
10
+ const createStar = () => {
11
+ let right = Math.random() * 500;
12
+ const top = Math.random() * window.innerHeight;
13
+
14
+ const star = document.createElement("div");
15
+ star.className =
16
+ "star absolute w-[2px] h-[2px] bg-white animate-starTwinkle";
17
+ star.style.top = `${top}px`;
18
+ star.style.right = `${right}px`;
19
+
20
+ body.appendChild(star);
21
+
22
+ const runStar = setInterval(() => {
23
+ if (right >= window.innerWidth) {
24
+ star.remove();
25
+ clearInterval(runStar);
26
+ }
27
+ right += 3;
28
+ star.style.right = `${right}px`;
29
+ }, 10);
30
+ };
31
+
32
+ const starInterval = setInterval(createStar, 100);
33
+
34
+ return () => {
35
+ clearInterval(starInterval);
36
+ document.querySelectorAll(".star").forEach((s) => s.remove());
37
+ };
38
+ }, []);
39
+
40
+ return (
41
+ <div className="relative flex h-screen w-full items-center justify-center overflow-hidden bg-linear-to-t from-[#2e1753] via-[#1f1746] to-[#050819] font-['Tomorrow']">
42
+ <div className="absolute top-[10%] text-center text-white">
43
+ <div className="text-lg tracking-widest">ERROR</div>
44
+ <h1 className="text-6xl font-bold">404</h1>
45
+ <hr className="my-2 w-32 mx-auto opacity-40" />
46
+ <div className="tracking-widest">Page Not Found</div>
47
+ </div>
48
+
49
+ <div className="astronaut">
50
+ <Image
51
+ src="https://images.vexels.com/media/users/3/152639/isolated/preview/506b575739e90613428cdb399175e2c8-space-astronaut-cartoon-by-vexels.png"
52
+ alt="astronaut"
53
+ width={96}
54
+ height={96}
55
+ unoptimized
56
+ className="absolute top-[55%] w-24 h-auto animate-astronautFly"
57
+ />
58
+ </div>
59
+
60
+ <style jsx global>{`
61
+ @keyframes astronautFly {
62
+ 0% {
63
+ left: -100px;
64
+ }
65
+ 25% {
66
+ top: 50%;
67
+ transform: rotate(30deg);
68
+ }
69
+ 50% {
70
+ top: 55%;
71
+ transform: rotate(45deg);
72
+ }
73
+ 75% {
74
+ top: 60%;
75
+ transform: rotate(30deg);
76
+ }
77
+ 100% {
78
+ left: 110%;
79
+ transform: rotate(45deg);
80
+ }
81
+ }
82
+
83
+ @keyframes starTwinkle {
84
+ 0% {
85
+ background: rgba(255, 255, 255, 0.4);
86
+ }
87
+ 25% {
88
+ background: rgba(255, 255, 255, 0.8);
89
+ }
90
+ 50% {
91
+ background: rgba(255, 255, 255, 1);
92
+ }
93
+ 75% {
94
+ background: rgba(255, 255, 255, 0.8);
95
+ }
96
+ 100% {
97
+ background: rgba(255, 255, 255, 0.4);
98
+ }
99
+ }
100
+
101
+ .animate-astronautFly {
102
+ animation: astronautFly 6s linear infinite;
103
+ }
104
+
105
+ .animate-starTwinkle {
106
+ animation: starTwinkle 3s linear infinite;
107
+ }
108
+ `}</style>
109
+ </div>
110
+ );
111
+ }
@@ -0,0 +1,118 @@
1
+ "use client";
2
+
3
+ export default function BlueGlitch() {
4
+ return (
5
+ <main className="min-h-screen flex items-center justify-center bg-[#000084] text-[#bbb]">
6
+ <div className="notfound">
7
+ <div className="centered">
8
+ <span className="inverted">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>
9
+ </div>
10
+
11
+ <div className="centered">
12
+ <span className="inverted">&nbsp;4&nbsp;0&nbsp;4&nbsp;</span>
13
+ <span className="shadow">&nbsp;</span>
14
+ </div>
15
+
16
+ <div className="centered">
17
+ <span className="inverted">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>
18
+ <span className="shadow">&nbsp;</span>
19
+ </div>
20
+
21
+ <div className="centered">
22
+ &nbsp;<span className="shadow">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>
23
+ </div>
24
+
25
+ <div className="row">&nbsp;</div>
26
+ <div className="row">
27
+ A fatal exception 404 has occurred at C0DE:ABAD1DEA in 0xC0DEBA5E.
28
+ </div>
29
+ <div className="row">The current request will be terminated.</div>
30
+ <div className="row">&nbsp;</div>
31
+ <div className="row">* Press any key to return to the previous page.</div>
32
+ <div className="row">
33
+ * Press CTRL+R to restart your server. You will
34
+ </div>
35
+ <div className="row">
36
+ &nbsp;&nbsp;lose any unsaved information in all applications.
37
+ </div>
38
+ <div className="row">&nbsp;</div>
39
+
40
+ <div className="centered">
41
+ Please refresh the page...Or Return to safety&nbsp;
42
+ <span className="blink">█</span>
43
+ </div>
44
+ </div>
45
+
46
+ {/* ALL CSS INLINE */}
47
+ <style jsx>{`
48
+ * {
49
+ box-sizing: border-box;
50
+ }
51
+
52
+ .notfound {
53
+ width: 70ch;
54
+ height: 25ch;
55
+ background-color: #000084;
56
+ font-family: "more_perfect_dos_vgaregular", "Courier New", monospace;
57
+ font-size: 10px;
58
+ }
59
+
60
+ @media (min-width: 600px) {
61
+ .notfound {
62
+ font-size: 16px;
63
+ }
64
+ }
65
+
66
+ @media (min-width: 900px) {
67
+ .notfound {
68
+ font-size: 24px;
69
+ font-weight: 400;
70
+ }
71
+ }
72
+
73
+ .row {
74
+ text-align: left;
75
+ }
76
+
77
+ .centered {
78
+ text-align: center;
79
+ }
80
+
81
+ .inverted {
82
+ background-color: #bbb;
83
+ color: #000084;
84
+ }
85
+
86
+ .shadow {
87
+ background-color: #000;
88
+ color: #000084;
89
+ }
90
+
91
+ .blink {
92
+ animation: blink 0.8s infinite;
93
+ }
94
+
95
+ @keyframes blink {
96
+ 0% {
97
+ opacity: 0;
98
+ }
99
+ 49% {
100
+ opacity: 0;
101
+ }
102
+ 50% {
103
+ opacity: 1;
104
+ }
105
+ }
106
+
107
+ /* DOS VGA FONT */
108
+ @font-face {
109
+ font-family: "more_perfect_dos_vgaregular";
110
+ src: url("https://fonts.cdnfonts.com/s/17263/Perfect%20DOS%20VGA%20437.woff")
111
+ format("woff");
112
+ font-weight: normal;
113
+ font-style: normal;
114
+ }
115
+ `}</style>
116
+ </main>
117
+ );
118
+ }
@@ -0,0 +1,20 @@
1
+ import Link from "next/link";
2
+
3
+ export default function ModernPage() {
4
+ return (
5
+ <div className="flex min-h-screen items-center justify-center bg-background">
6
+ <div className="surface rounded-2xl p-8 text-center">
7
+ <h1 className="text-7xl font-extrabold tracking-tight">404</h1>
8
+ <p className="mt-2 text-muted">
9
+ This page could not be found.
10
+ </p>
11
+ <Link
12
+ href="/"
13
+ className="mt-6 inline-block rounded-lg bg-foreground px-4 py-2 text-background"
14
+ >
15
+ Back to home
16
+ </Link>
17
+ </div>
18
+ </div>
19
+ );
20
+ }
@@ -0,0 +1,70 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import Image from "next/image";
5
+
6
+ export default function Poet() {
7
+ return (
8
+ <main className="min-h-screen bg-[#777] flex items-center justify-center p-4">
9
+ <div className="max-w-5xl w-full border-2 border-black bg-black text-white">
10
+ <div className="bg-[#d0d0d0] text-black px-4 py-3 border-b-2 border-black font-mono">
11
+ <div className="text-xl font-bold">
12
+ 404 &raquo; Document Not Found
13
+ </div>
14
+ <div className="text-sm mt-1">
15
+ The requested document{' '}
16
+ <span className="font-semibold">&quot;/missing.pdf&quot;</span> does not exist.
17
+ </div>
18
+ <div className="mt-2 text-sm">
19
+ <Link href="/" className="underline font-semibold">
20
+ GO Back
21
+ </Link>{" "}
22
+ - or -{" "}
23
+ <Link href="/" className="underline font-semibold">
24
+ GO Home
25
+ </Link>
26
+ </div>
27
+ </div>
28
+
29
+ <div className="flex flex-col md:flex-row">
30
+ <div className="flex-1 p-6 font-serif leading-snug text-lg">
31
+ <p>Once upon a midnight dreary,</p>
32
+ <p>While I web surfed, weak and weary,</p>
33
+ <p>For pages long forgotten yore.</p>
34
+ <p>When I clicked my fav&apos;rite href,</p>
35
+ <p>Suddenly there came a warning,</p>
36
+ <p>and my heart was filled with mourning,</p>
37
+
38
+ <p className="mt-4 text-2xl">
39
+ Mourning for my dear{" "}
40
+ <span className="italic">&quot;/missing.pdf&quot;</span>,
41
+ </p>
42
+
43
+ <p className="mt-4">
44
+ &quot;Tis not possible!&quot; I muttered,
45
+ </p>
46
+
47
+ <p className="text-3xl mt-2">
48
+ &quot;Give thine pages, I implore!&quot;
49
+ </p>
50
+
51
+ <p className="text-4xl mt-6">
52
+ Quoth the server, <span className="font-bold">404</span>.
53
+ </p>
54
+ </div>
55
+
56
+ <div className="w-full md:w-[320px] bg-black flex items-center justify-center p-4">
57
+ <Image
58
+ src="/raven.png"
59
+ alt="Raven"
60
+ width={320}
61
+ height={420}
62
+ className="max-h-105 object-contain opacity-90"
63
+ priority
64
+ />
65
+ </div>
66
+ </div>
67
+ </div>
68
+ </main>
69
+ );
70
+ }
@@ -0,0 +1,162 @@
1
+ "use client";
2
+
3
+ import { useEffect, useRef } from "react";
4
+ import Image from "next/image";
5
+
6
+ export default function RetroTv() {
7
+ const canvasRef = useRef<HTMLCanvasElement>(null);
8
+
9
+ useEffect(() => {
10
+ const canvas = canvasRef.current;
11
+ if (!canvas) return;
12
+
13
+ const ctx = canvas.getContext("2d");
14
+ if (!ctx) return;
15
+
16
+ const WIDTH = 700;
17
+ const HEIGHT = 500;
18
+
19
+ canvas.width = WIDTH;
20
+ canvas.height = HEIGHT;
21
+
22
+ ctx.fillStyle = "white";
23
+ ctx.fillRect(0, 0, WIDTH, HEIGHT);
24
+
25
+ const imgData = ctx.getImageData(0, 0, WIDTH, HEIGHT);
26
+ const pix = imgData.data;
27
+
28
+ const flicker = setInterval(() => {
29
+ for (let i = 0; i < pix.length; i += 4) {
30
+ const color = Math.random() * 255 + 50;
31
+ pix[i] = color;
32
+ pix[i + 1] = color;
33
+ pix[i + 2] = color;
34
+ }
35
+ ctx.putImageData(imgData, 0, 0);
36
+ }, 30);
37
+
38
+ return () => clearInterval(flicker);
39
+ }, []);
40
+
41
+ return (
42
+ <div className="relative h-screen w-screen overflow-hidden bg-black">
43
+ <h1 className="absolute left-1/2 top-1/2 z-3 -translate-x-1/2 -translate-y-1/2 text-[200px] font-bold text-transparent glitch-text">
44
+ 404
45
+ </h1>
46
+
47
+ <div className="absolute inset-0 z-3 frame">
48
+ <div />
49
+ <div />
50
+ <div />
51
+ </div>
52
+
53
+ <div className="absolute inset-0 z-2 opacity-0 caps">
54
+ <Image
55
+ src="http://ademilter.com/caps.png"
56
+ alt="caps"
57
+ className="h-full w-full"
58
+ fill
59
+ />
60
+ </div>
61
+
62
+ <canvas
63
+ ref={canvasRef}
64
+ className="absolute left-0 top-0 z-1 h-full w-full"
65
+ />
66
+
67
+ <style jsx>{`
68
+ .glitch-text {
69
+ animation: glitch 2s linear infinite;
70
+ text-shadow: 0 0 30px rgba(0, 0, 0, 0.5);
71
+ }
72
+
73
+ @keyframes glitch {
74
+ 0% {
75
+ text-shadow: 0 0 30px rgba(0, 0, 0, 0.5);
76
+ }
77
+ 33% {
78
+ text-shadow: 0 0 10px rgba(0, 0, 0, 0.4);
79
+ }
80
+ 66% {
81
+ text-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
82
+ }
83
+ 100% {
84
+ text-shadow: 0 0 40px rgba(0, 0, 0, 0.8);
85
+ }
86
+ }
87
+
88
+ .caps {
89
+ animation: capsFlicker 8s linear infinite;
90
+ }
91
+
92
+ @keyframes capsFlicker {
93
+ 0% {
94
+ opacity: 0;
95
+ }
96
+ 10% {
97
+ opacity: 0.3;
98
+ }
99
+ 20% {
100
+ opacity: 0.1;
101
+ }
102
+ 30% {
103
+ opacity: 0.5;
104
+ }
105
+ 40% {
106
+ opacity: 0;
107
+ }
108
+ 50% {
109
+ opacity: 0.8;
110
+ }
111
+ 55% {
112
+ opacity: 0;
113
+ }
114
+ 100% {
115
+ opacity: 0;
116
+ }
117
+ }
118
+
119
+ .frame {
120
+ background: radial-gradient(
121
+ ellipse at center,
122
+ rgba(0, 0, 0, 0) 0%,
123
+ rgba(0, 0, 0, 0) 19%,
124
+ rgba(0, 0, 0, 0.9) 100%
125
+ );
126
+ }
127
+
128
+ .frame div {
129
+ position: absolute;
130
+ left: 0;
131
+ top: -20%;
132
+ width: 100%;
133
+ height: 20%;
134
+ background-color: rgba(0, 0, 0, 0.12);
135
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
136
+ animation: scanline 12s linear infinite;
137
+ }
138
+
139
+ .frame div:nth-child(1) {
140
+ animation-delay: 0s;
141
+ }
142
+
143
+ .frame div:nth-child(2) {
144
+ animation-delay: 4s;
145
+ }
146
+
147
+ .frame div:nth-child(3) {
148
+ animation-delay: 8s;
149
+ }
150
+
151
+ @keyframes scanline {
152
+ 0% {
153
+ top: -20%;
154
+ }
155
+ 100% {
156
+ top: 100%;
157
+ }
158
+ }
159
+ `}</style>
160
+ </div>
161
+ );
162
+ }