Skip to content

Commit f3d2355

Browse files
committed
feat: custom 404 not found page for invalid routes
1 parent 39dbb53 commit f3d2355

File tree

2 files changed

+238
-0
lines changed

2 files changed

+238
-0
lines changed
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
defmodule AlgoraWeb.Component.NotFoundComponent do
2+
@moduledoc false
3+
use AlgoraWeb.Component
4+
5+
@doc "Renders a minimal modern 404 page like Next.js with black background"
6+
def not_found(assigns) do
7+
~H"""
8+
<!DOCTYPE html>
9+
<html lang="en">
10+
<head>
11+
<meta charset="UTF-8">
12+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
13+
<title>404 - Page Not Found</title>
14+
<style>
15+
* {
16+
margin: 0;
17+
padding: 0;
18+
box-sizing: border-box;
19+
}
20+
21+
body {
22+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
23+
background: #000;
24+
color: #fff;
25+
min-height: 100vh;
26+
display: flex;
27+
align-items: center;
28+
justify-content: center;
29+
padding: 1rem;
30+
overflow: hidden;
31+
}
32+
33+
.container {
34+
text-align: center;
35+
max-width: 500px;
36+
position: relative;
37+
z-index: 2;
38+
}
39+
40+
.error-code {
41+
font-size: clamp(4rem, 12vw, 8rem);
42+
font-weight: 800;
43+
line-height: 0.9;
44+
margin-bottom: 1.5rem;
45+
background: linear-gradient(135deg, #fff 0%, #a0a0a0 100%);
46+
-webkit-background-clip: text;
47+
-webkit-text-fill-color: transparent;
48+
background-clip: text;
49+
letter-spacing: -0.05em;
50+
position: relative;
51+
}
52+
53+
.error-code::before {
54+
content: '404';
55+
position: absolute;
56+
top: 0;
57+
left: 0;
58+
right: 0;
59+
background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.05) 100%);
60+
-webkit-background-clip: text;
61+
-webkit-text-fill-color: transparent;
62+
background-clip: text;
63+
filter: blur(2px);
64+
z-index: -1;
65+
}
66+
67+
.title {
68+
font-size: 1.5rem;
69+
font-weight: 600;
70+
margin-bottom: 0.75rem;
71+
color: #fff;
72+
letter-spacing: -0.025em;
73+
}
74+
75+
.description {
76+
font-size: 1rem;
77+
color: #888;
78+
margin-bottom: 2.5rem;
79+
line-height: 1.5;
80+
max-width: 400px;
81+
margin-left: auto;
82+
margin-right: auto;
83+
}
84+
85+
.home-button {
86+
display: inline-flex;
87+
align-items: center;
88+
gap: 0.5rem;
89+
padding: 0.75rem 1.5rem;
90+
background: #fff;
91+
color: #000;
92+
text-decoration: none;
93+
border-radius: 0.5rem;
94+
font-weight: 600;
95+
font-size: 0.875rem;
96+
transition: all 0.2s ease;
97+
border: 2px solid transparent;
98+
position: relative;
99+
overflow: hidden;
100+
}
101+
102+
.home-button::before {
103+
content: '';
104+
position: absolute;
105+
top: 0;
106+
left: -100%;
107+
width: 100%;
108+
height: 100%;
109+
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
110+
transition: left 0.6s ease;
111+
}
112+
113+
.home-button:hover {
114+
background: #f5f5f5;
115+
transform: translateY(-1px);
116+
box-shadow: 0 4px 12px rgba(255,255,255,0.1);
117+
}
118+
119+
.home-button:hover::before {
120+
left: 100%;
121+
}
122+
123+
.home-button:active {
124+
transform: translateY(0);
125+
}
126+
127+
.floating-elements {
128+
position: absolute;
129+
top: 0;
130+
left: 0;
131+
width: 100%;
132+
height: 100%;
133+
pointer-events: none;
134+
z-index: 1;
135+
}
136+
137+
.floating-dot {
138+
position: absolute;
139+
width: 4px;
140+
height: 4px;
141+
background: rgba(255,255,255,0.1);
142+
border-radius: 50%;
143+
animation: float 6s ease-in-out infinite;
144+
}
145+
146+
.floating-dot:nth-child(1) {
147+
top: 20%;
148+
left: 10%;
149+
animation-delay: 0s;
150+
}
151+
152+
.floating-dot:nth-child(2) {
153+
top: 60%;
154+
right: 15%;
155+
animation-delay: 1s;
156+
}
157+
158+
.floating-dot:nth-child(3) {
159+
bottom: 30%;
160+
left: 20%;
161+
animation-delay: 2s;
162+
}
163+
164+
.floating-dot:nth-child(4) {
165+
top: 40%;
166+
right: 30%;
167+
animation-delay: 3s;
168+
}
169+
170+
@keyframes float {
171+
0%, 100% {
172+
transform: translateY(0px) scale(1);
173+
opacity: 0;
174+
}
175+
50% {
176+
transform: translateY(-20px) scale(1.2);
177+
opacity: 1;
178+
}
179+
}
180+
181+
.glow {
182+
position: absolute;
183+
top: 50%;
184+
left: 50%;
185+
transform: translate(-50%, -50%);
186+
width: 300px;
187+
height: 300px;
188+
background: radial-gradient(circle, rgba(255,255,255,0.03) 0%, transparent 70%);
189+
border-radius: 50%;
190+
z-index: 1;
191+
}
192+
193+
@media (max-width: 640px) {
194+
.container {
195+
max-width: 90%;
196+
}
197+
198+
.title {
199+
font-size: 1.25rem;
200+
}
201+
202+
.description {
203+
font-size: 0.875rem;
204+
margin-bottom: 2rem;
205+
}
206+
}
207+
</style>
208+
</head>
209+
<body>
210+
<div class="floating-elements">
211+
<div class="floating-dot"></div>
212+
<div class="floating-dot"></div>
213+
<div class="floating-dot"></div>
214+
<div class="floating-dot"></div>
215+
<div class="glow"></div>
216+
</div>
217+
218+
<div class="container">
219+
<div class="error-code">404</div>
220+
<h1 class="title">This page could not be found</h1>
221+
<p class="description">
222+
Sorry, the page you're looking for doesn't exist or has been moved.
223+
</p>
224+
<a href="/" class="home-button">
225+
<span></span>
226+
Go back home
227+
</a>
228+
</div>
229+
</body>
230+
</html>
231+
"""
232+
end
233+
end

lib/algora_web/controllers/error_html.ex

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
defmodule AlgoraWeb.ErrorHTML do
22
use AlgoraWeb, :html
3+
alias AlgoraWeb.Component.NotFoundComponent
34

45
# If you want to customize your error pages,
56
# uncomment the embed_templates/1 call below
@@ -10,6 +11,10 @@ defmodule AlgoraWeb.ErrorHTML do
1011
#
1112
# embed_templates "error_html/*"
1213

14+
def render("404.html", assigns) do
15+
NotFoundComponent.not_found(assigns)
16+
end
17+
1318
# The default is to render a plain text page based on
1419
# the template name. For example, "404.html" becomes
1520
# "Not Found".

0 commit comments

Comments
 (0)