Skip to content

Commit c3ba4d8

Browse files
nullcoderclaudeClaude
authored
feat: add humorous ghost-themed 404 page (#102)
* feat: implement global keyboard shortcuts (#72) - Add useGlobalShortcuts hook for managing keyboard shortcuts - Implement KeyboardShortcutsHelp component with help dialog - Add Separator component from shadcn/ui - Integrate keyboard shortcuts help in Header (Cmd+/ to open) - Add comprehensive tests for keyboard shortcuts functionality - Create demo page for keyboard shortcuts Keyboard shortcuts implemented: - Cmd/Ctrl + / : Open help dialog - Cmd/Ctrl + K : Create new gist (navigation) - Cmd/Ctrl + S : Save gist (when applicable) - Cmd/Ctrl + Shift + C : Copy share link - Escape : Close modals/dialogs 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: update TODO and tracking docs for completed keyboard shortcuts * feat: add humorous ghost-themed 404 page - Create custom 404 page with floating ghost animation - Add glitch effect to 404 text for visual interest - Include ghost-themed humor and messaging - Add responsive design with mobile support - Create clear call-to-action buttons (Home, Create New Gist) - Add CSS animations (float and glitch effects) - Include tests for 404 page component - Create demo page to showcase 404 features The 404 page features: - Animated floating ghost icon - Glitch effect on 404 text - Ghost puns and humor - Dark/light theme support - Responsive layout - Clear navigation options 🤖 Generated with Claude Code Co-Authored-By: Claude <claude@ghostpaste.dev> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <claude@ghostpaste.dev>
1 parent 4d751c2 commit c3ba4d8

File tree

4 files changed

+303
-0
lines changed

4 files changed

+303
-0
lines changed

app/demo/404/page.tsx

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
"use client";
2+
3+
import Link from "next/link";
4+
import { Button } from "@/components/ui/button";
5+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
6+
import NotFound from "@/app/not-found";
7+
import { ExternalLink, Ghost } from "lucide-react";
8+
9+
export default function NotFoundDemo() {
10+
return (
11+
<div className="container mx-auto max-w-6xl space-y-8 px-4 py-12">
12+
<div className="space-y-4 text-center">
13+
<h1 className="text-4xl font-bold">404 Page Demo</h1>
14+
<p className="text-muted-foreground text-lg">
15+
Preview of our ghost-themed 404 error page
16+
</p>
17+
</div>
18+
19+
<Card>
20+
<CardHeader>
21+
<CardTitle className="flex items-center gap-2">
22+
<Ghost className="h-5 w-5" />
23+
404 Page Features
24+
</CardTitle>
25+
</CardHeader>
26+
<CardContent className="space-y-4">
27+
<ul className="space-y-2 text-sm">
28+
<li className="flex items-start gap-2">
29+
<span className="text-muted-foreground">👻</span>
30+
<span>Animated floating ghost icon</span>
31+
</li>
32+
<li className="flex items-start gap-2">
33+
<span className="text-muted-foreground"></span>
34+
<span>Glitch effect on the 404 text</span>
35+
</li>
36+
<li className="flex items-start gap-2">
37+
<span className="text-muted-foreground">🎨</span>
38+
<span>Ghost-themed humor and messaging</span>
39+
</li>
40+
<li className="flex items-start gap-2">
41+
<span className="text-muted-foreground">🌓</span>
42+
<span>Dark/light theme support</span>
43+
</li>
44+
<li className="flex items-start gap-2">
45+
<span className="text-muted-foreground">📱</span>
46+
<span>Responsive design for all devices</span>
47+
</li>
48+
<li className="flex items-start gap-2">
49+
<span className="text-muted-foreground">🔗</span>
50+
<span>Clear call-to-action buttons</span>
51+
</li>
52+
</ul>
53+
54+
<div className="space-y-3 border-t pt-4">
55+
<p className="text-muted-foreground text-sm">
56+
Try visiting a non-existent page to see it in action:
57+
</p>
58+
<div className="flex flex-wrap gap-2">
59+
<Button asChild variant="outline" size="sm">
60+
<Link href="/this-page-does-not-exist" target="_blank">
61+
<ExternalLink className="mr-2 h-3 w-3" />
62+
/this-page-does-not-exist
63+
</Link>
64+
</Button>
65+
<Button asChild variant="outline" size="sm">
66+
<Link href="/g/fake-gist-id" target="_blank">
67+
<ExternalLink className="mr-2 h-3 w-3" />
68+
/g/fake-gist-id
69+
</Link>
70+
</Button>
71+
</div>
72+
</div>
73+
</CardContent>
74+
</Card>
75+
76+
<Card className="overflow-hidden">
77+
<CardHeader>
78+
<CardTitle>Preview</CardTitle>
79+
</CardHeader>
80+
<CardContent className="p-0">
81+
<div className="overflow-hidden rounded-lg border">
82+
<NotFound />
83+
</div>
84+
</CardContent>
85+
</Card>
86+
87+
<Card>
88+
<CardHeader>
89+
<CardTitle>Animation Details</CardTitle>
90+
</CardHeader>
91+
<CardContent className="space-y-4">
92+
<div>
93+
<h3 className="mb-2 font-semibold">Ghost Float Animation</h3>
94+
<pre className="bg-muted overflow-x-auto rounded-lg p-3 text-sm">
95+
{`@keyframes float {
96+
0%, 100% {
97+
transform: translateY(0) scale(1);
98+
opacity: 0.5;
99+
}
100+
50% {
101+
transform: translateY(-20px) scale(1.05);
102+
opacity: 0.7;
103+
}
104+
}`}
105+
</pre>
106+
</div>
107+
108+
<div>
109+
<h3 className="mb-2 font-semibold">404 Glitch Effect</h3>
110+
<pre className="bg-muted overflow-x-auto rounded-lg p-3 text-sm">
111+
{`@keyframes glitch {
112+
/* RGB shadow effect that shifts */
113+
text-shadow:
114+
0.02em 0 0 rgba(255, 0, 0, 0.75),
115+
-0.02em -0 0 rgba(0, 255, 0, 0.75),
116+
0.025em 0.05em 0 rgba(0, 0, 255, 0.75);
117+
}`}
118+
</pre>
119+
</div>
120+
</CardContent>
121+
</Card>
122+
</div>
123+
);
124+
}

app/globals.css

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,3 +198,64 @@
198198
@apply bg-background text-foreground;
199199
}
200200
}
201+
202+
/* Custom animations for 404 page */
203+
@keyframes float {
204+
0%,
205+
100% {
206+
transform: translateY(0) scale(1);
207+
opacity: 0.5;
208+
}
209+
50% {
210+
transform: translateY(-20px) scale(1.05);
211+
opacity: 0.7;
212+
}
213+
}
214+
215+
@keyframes glitch {
216+
0%,
217+
100% {
218+
text-shadow:
219+
0.02em 0 0 rgba(255, 0, 0, 0.75),
220+
-0.02em -0 0 rgba(0, 255, 0, 0.75),
221+
0.025em 0.05em 0 rgba(0, 0, 255, 0.75);
222+
}
223+
14% {
224+
text-shadow:
225+
0.02em 0 0 rgba(255, 0, 0, 0.75),
226+
-0.02em -0 0 rgba(0, 255, 0, 0.75),
227+
0.025em 0.05em 0 rgba(0, 0, 255, 0.75);
228+
}
229+
15% {
230+
text-shadow:
231+
-0.02em -0.025em 0 rgba(255, 0, 0, 0.75),
232+
0.025em 0.025em 0 rgba(0, 255, 0, 0.75),
233+
-0.05em -0.05em 0 rgba(0, 0, 255, 0.75);
234+
}
235+
49% {
236+
text-shadow:
237+
-0.02em -0.025em 0 rgba(255, 0, 0, 0.75),
238+
0.025em 0.025em 0 rgba(0, 255, 0, 0.75),
239+
-0.05em -0.05em 0 rgba(0, 0, 255, 0.75);
240+
}
241+
50% {
242+
text-shadow:
243+
0.025em 0.05em 0 rgba(255, 0, 0, 0.75),
244+
0.05em 0 0 rgba(0, 255, 0, 0.75),
245+
0 -0.05em 0 rgba(0, 0, 255, 0.75);
246+
}
247+
99% {
248+
text-shadow:
249+
0.025em 0.05em 0 rgba(255, 0, 0, 0.75),
250+
0.05em 0 0 rgba(0, 255, 0, 0.75),
251+
0 -0.05em 0 rgba(0, 0, 255, 0.75);
252+
}
253+
}
254+
255+
.animate-float {
256+
animation: float 3s ease-in-out infinite;
257+
}
258+
259+
.animate-glitch {
260+
animation: glitch 2s ease-in-out infinite alternate-reverse;
261+
}

app/not-found.test.tsx

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { render, screen } from "@testing-library/react";
2+
import { describe, it, expect } from "vitest";
3+
import NotFound from "./not-found";
4+
5+
describe("NotFound", () => {
6+
it("renders 404 text", () => {
7+
render(<NotFound />);
8+
expect(screen.getByText("404")).toBeInTheDocument();
9+
});
10+
11+
it("renders main heading", () => {
12+
render(<NotFound />);
13+
expect(screen.getByText("This page has been ghosted!")).toBeInTheDocument();
14+
});
15+
16+
it("renders description text", () => {
17+
render(<NotFound />);
18+
expect(
19+
screen.getByText(
20+
/The gist you're looking for has vanished into the digital ether/
21+
)
22+
).toBeInTheDocument();
23+
});
24+
25+
it("renders ghost icon", () => {
26+
render(<NotFound />);
27+
// Look for the ghost SVG by its container
28+
const ghostContainer = screen.getByText("404").previousElementSibling;
29+
expect(ghostContainer).toHaveClass("animate-float");
30+
});
31+
32+
it("renders home button with correct link", () => {
33+
render(<NotFound />);
34+
const homeLink = screen.getByRole("link", { name: /go home/i });
35+
expect(homeLink).toBeInTheDocument();
36+
expect(homeLink).toHaveAttribute("href", "/");
37+
});
38+
39+
it("renders create new gist button with correct link", () => {
40+
render(<NotFound />);
41+
const createLink = screen.getByRole("link", { name: /create new gist/i });
42+
expect(createLink).toBeInTheDocument();
43+
expect(createLink).toHaveAttribute("href", "/create");
44+
});
45+
46+
it("renders easter egg text", () => {
47+
render(<NotFound />);
48+
expect(
49+
screen.getByText(/Just like that person who never texted back.../)
50+
).toBeInTheDocument();
51+
});
52+
53+
it("applies correct animation classes", () => {
54+
render(<NotFound />);
55+
const heading = screen.getByText("404");
56+
expect(heading).toHaveClass("animate-glitch");
57+
});
58+
59+
it("has proper layout classes", () => {
60+
const { container } = render(<NotFound />);
61+
const mainContainer = container.firstChild;
62+
expect(mainContainer).toHaveClass("min-h-[80vh]");
63+
expect(mainContainer).toHaveClass("flex-col");
64+
expect(mainContainer).toHaveClass("items-center");
65+
expect(mainContainer).toHaveClass("justify-center");
66+
});
67+
});

app/not-found.tsx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import Link from "next/link";
2+
import { Button } from "@/components/ui/button";
3+
import { Ghost, Home, Plus } from "lucide-react";
4+
5+
export default function NotFound() {
6+
return (
7+
<div className="container mx-auto flex min-h-[80vh] flex-col items-center justify-center px-4 text-center">
8+
{/* Floating Ghost */}
9+
<div className="animate-float mb-8">
10+
<Ghost className="text-muted-foreground/50 h-32 w-32" />
11+
</div>
12+
13+
{/* 404 Text with Glitch Effect */}
14+
<h1 className="animate-glitch mb-4 text-8xl font-bold tracking-tighter">
15+
404
16+
</h1>
17+
18+
{/* Main Message */}
19+
<h2 className="mb-2 text-3xl font-semibold">
20+
This page has been ghosted!
21+
</h2>
22+
23+
{/* Subtext */}
24+
<p className="text-muted-foreground mb-8 max-w-md text-lg">
25+
The gist you&apos;re looking for has vanished into the digital ether.
26+
Maybe it expired, or perhaps it never existed at all...
27+
</p>
28+
29+
{/* Action Buttons */}
30+
<div className="flex flex-col gap-4 sm:flex-row">
31+
<Button asChild size="lg">
32+
<Link href="/">
33+
<Home className="mr-2 h-4 w-4" />
34+
Go Home
35+
</Link>
36+
</Button>
37+
<Button asChild variant="outline" size="lg">
38+
<Link href="/create">
39+
<Plus className="mr-2 h-4 w-4" />
40+
Create New Gist
41+
</Link>
42+
</Button>
43+
</div>
44+
45+
{/* Fun Easter Egg Text */}
46+
<p className="text-muted-foreground/60 mt-12 text-sm">
47+
👻 Just like that person who never texted back...
48+
</p>
49+
</div>
50+
);
51+
}

0 commit comments

Comments
 (0)