404lab 2.0.1 → 2.0.2

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.
@@ -1,210 +1,144 @@
1
1
  "use client";
2
2
 
3
- import { useEffect, useRef } from "react";
3
+ import { useEffect, useRef, useState } from "react";
4
4
  import Link from "next/link";
5
+ import { motion, AnimatePresence } from "framer-motion";
6
+ import { cn } from "@/components/ui/cn";
5
7
 
6
- const Snow = () => {
7
- const canvasRef = useRef<HTMLCanvasElement | null>(null);
8
+ const Snow = ({ className }: { className?: string }) => {
9
+ const canvasRef = useRef<HTMLCanvasElement>(null);
10
+ const [isHovered, setIsHovered] = useState(false);
8
11
 
9
12
  useEffect(() => {
10
- const canvas = canvasRef.current!;
11
- const ctx = canvas.getContext("2d")!;
12
- let width = 0;
13
- let height = 0;
14
-
15
- interface Particle {
16
- x: number;
17
- y: number;
18
- dx: number;
19
- dy: number;
20
- }
21
-
22
- let particles: Particle[] = [];
23
-
24
- const reset = (p: Particle) => {
25
- p.y = Math.random() * height;
26
- p.x = Math.random() * width;
27
- p.dx = Math.random() - 0.5;
28
- p.dy = Math.random() * 0.5 + 0.5;
13
+ const canvas = canvasRef.current;
14
+ if (!canvas) return;
15
+ const ctx = canvas.getContext("2d");
16
+ if (!ctx) return;
17
+
18
+ let animationFrame: number;
19
+ let particles: { x: number; y: number; size: number; speed: number; opacity: number }[] = [];
20
+
21
+ const resize = () => {
22
+ canvas.width = window.innerWidth;
23
+ canvas.height = window.innerHeight;
24
+ init();
29
25
  };
30
26
 
31
- function createParticles(count: number) {
27
+ const init = () => {
32
28
  particles = [];
29
+ const count = Math.floor((canvas.width * canvas.height) / 8000);
33
30
  for (let i = 0; i < count; i++) {
34
- const p: Particle = { x: 0, y: 0, dx: 0, dy: 0 };
35
- reset(p);
36
- particles.push(p);
31
+ particles.push({
32
+ x: Math.random() * canvas.width,
33
+ y: Math.random() * canvas.height,
34
+ size: Math.random() * 3 + 1,
35
+ speed: Math.random() * 1 + 0.5,
36
+ opacity: Math.random() * 0.5 + 0.3
37
+ });
37
38
  }
38
- }
39
-
40
- function resize() {
41
- width = window.innerWidth;
42
- height = window.innerHeight;
43
- canvas.width = width;
44
- canvas.height = height;
45
- createParticles(Math.floor((width * height) / 10000));
46
- }
47
-
48
- function animate() {
49
- ctx.clearRect(0, 0, width, height);
50
- ctx.fillStyle = "#f6f9fa";
51
-
52
- particles.forEach((p) => {
53
- p.y += p.dy;
54
- p.x += p.dx;
55
-
56
- if (p.y > height) p.y = 0;
57
- if (p.x > width) {
58
- reset(p);
59
- p.y = 0;
60
- }
39
+ };
40
+
41
+ const draw = () => {
42
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
43
+ ctx.fillStyle = "white";
61
44
 
45
+ particles.forEach(p => {
46
+ ctx.globalAlpha = p.opacity;
62
47
  ctx.beginPath();
63
- ctx.arc(p.x, p.y, 5, 0, Math.PI * 2);
48
+ ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
64
49
  ctx.fill();
50
+
51
+ p.y += p.speed;
52
+ p.x += Math.sin(p.y / 50) * 0.5;
53
+
54
+ if (p.y > canvas.height) {
55
+ p.y = -10;
56
+ p.x = Math.random() * canvas.width;
57
+ }
65
58
  });
66
59
 
67
- requestAnimationFrame(animate);
68
- }
60
+ animationFrame = requestAnimationFrame(draw);
61
+ };
69
62
 
70
- resize();
71
- animate();
72
63
  window.addEventListener("resize", resize);
73
- return () => window.removeEventListener("resize", resize);
64
+ resize();
65
+ draw();
66
+
67
+ return () => {
68
+ window.removeEventListener("resize", resize);
69
+ cancelAnimationFrame(animationFrame);
70
+ };
74
71
  }, []);
75
72
 
76
73
  return (
77
- <div className="content">
78
- <canvas ref={canvasRef} id="snow" className="snow" />
79
-
80
- <div className="main-text">
81
- <h1>
82
- Aw jeez.
83
- <br />
84
- That page has gone missing.
74
+ <div
75
+ className={cn(
76
+ "min-h-screen flex flex-col items-center justify-center overflow-hidden bg-gradient-to-b from-[#b7d1e5] via-[#e8f2f6] to-white relative",
77
+ className
78
+ )}
79
+ >
80
+ <canvas ref={canvasRef} className="absolute inset-0 z-10 pointer-events-none opacity-60" />
81
+
82
+ <div className="absolute inset-0 z-0 bg-[radial-gradient(circle_at_center,_transparent_0%,_rgba(255,255,255,0.4)_100%)]" />
83
+
84
+ <motion.main
85
+ initial={{ opacity: 0, y: 20 }}
86
+ animate={{ opacity: 1, y: 0 }}
87
+ className="z-20 text-center px-4"
88
+ >
89
+ <h1 className="text-4xl md:text-7xl font-bold text-[#5d7399] mb-4 tracking-tight">
90
+ Frozen in Time.
85
91
  </h1>
86
- <Link href="/" className="home-link">
87
- Hitch a ride back home.
92
+ <p className="text-[#5d7399]/70 text-lg md:text-2xl mb-12 max-w-2xl mx-auto font-medium">
93
+ The page you are looking for has been buried under a heavy snowfall.
94
+ Let&apos;s get you somewhere warmer.
95
+ </p>
96
+
97
+ <Link
98
+ href="/"
99
+ className="inline-flex items-center px-8 py-4 bg-white text-[#5d7399] font-bold rounded-full shadow-lg shadow-blue-200/50 hover:shadow-xl hover:scale-105 active:scale-95 transition-all text-lg border border-blue-50"
100
+ >
101
+ Hitch a ride back home
88
102
  </Link>
103
+ </motion.main>
104
+
105
+ <div className="absolute bottom-0 w-full h-[30vh] z-10">
106
+ <svg viewBox="0 0 1440 320" className="absolute bottom-0 w-full h-full drop-shadow-[-20px_-20px_40px_rgba(255,255,255,0.5)]">
107
+ <path fill="#f8f9fa" d="M0,192L48,197.3C96,203,192,213,288,192C384,171,480,117,576,117.3C672,117,768,171,864,197.3C960,224,1056,224,1152,202.7C1248,181,1344,139,1392,117.3L1440,96L1440,320L1392,320C1344,320,1248,320,1152,320C1056,320,960,320,864,320C768,320,672,320,576,320C480,320,384,320,288,320C192,320,96,320,48,320L0,320Z"></path>
108
+ </svg>
109
+
110
+ <motion.div
111
+ className="absolute bottom-[10%] left-1/2 -translate-x-1/2 z-20"
112
+ onMouseEnter={() => setIsHovered(true)}
113
+ onMouseLeave={() => setIsHovered(false)}
114
+ >
115
+ <div className="relative group cursor-pointer">
116
+ <motion.h2
117
+ className="text-8xl md:text-[12rem] font-black text-[#6b85b2] opacity-20 select-none tracking-tighter"
118
+ animate={isHovered ? { scale: 1.05, opacity: 0.3 } : {}}
119
+ >
120
+ 404
121
+ </motion.h2>
122
+
123
+ <motion.div
124
+ className="absolute -right-12 -top-12 w-16 h-16 pointer-events-none"
125
+ animate={isHovered ? { rotate: [0, -20, 10, 0], x: [0, -5, 5, 0] } : {}}
126
+ >
127
+ <div className="w-1 h-20 bg-[#dd4040]/20 absolute left-1/2 -translate-x-1/2 top-0" />
128
+ <div className="w-10 h-8 bg-[#dd4040] absolute bottom-0 left-0 rounded-sm" />
129
+ </motion.div>
130
+ </div>
131
+ </motion.div>
89
132
  </div>
90
133
 
91
- <div className="ground">
92
- <div className="mound">
93
- <div className="mound_text">404</div>
94
- <div className="mound_spade" />
95
- </div>
134
+ <div className="absolute top-10 right-10 flex gap-4 opacity-20">
135
+ {[1,2,3].map(i => (
136
+ <div key={i} className="w-12 h-1 bg-[#5d7399] rounded-full" />
137
+ ))}
96
138
  </div>
97
-
98
- <style jsx>{`
99
- .content {
100
- height: 100vh;
101
- position: relative;
102
- background: linear-gradient(to bottom, #bbcfe1 0%, #e8f2f6 80%);
103
- overflow: hidden;
104
- font-family: "Dosis", sans-serif;
105
- font-size: 32px;
106
- font-weight: 500;
107
- color: #5d7399;
108
- }
109
-
110
- .snow {
111
- position: absolute;
112
- inset: 0;
113
- pointer-events: none;
114
- z-index: 20;
115
- }
116
-
117
- .main-text {
118
- padding: 20vh 20px 0;
119
- text-align: center;
120
- line-height: 2em;
121
- font-size: 5vh;
122
- }
123
-
124
- .home-link {
125
- font-size: 0.6em;
126
- color: inherit;
127
- text-decoration: none;
128
- opacity: 0.6;
129
- border-bottom: 1px dashed rgba(93, 115, 153, 0.5);
130
- }
131
-
132
- .home-link:hover {
133
- opacity: 1;
134
- }
135
-
136
- .ground {
137
- position: absolute;
138
- bottom: 0;
139
- width: 100%;
140
- height: 160px;
141
- background: #f6f9fa;
142
- box-shadow: 0 0 10px 10px #f6f9fa;
143
- }
144
-
145
- .mound {
146
- margin-top: -80px;
147
- font-size: 180px;
148
- font-weight: 800;
149
- text-align: center;
150
- color: #dd4040;
151
- position: relative;
152
- pointer-events: none;
153
- }
154
-
155
- .mound::before {
156
- content: "";
157
- position: absolute;
158
- width: 600px;
159
- height: 200px;
160
- left: 50%;
161
- top: 50px;
162
- transform: translateX(-50%);
163
- border-radius: 100%;
164
- background: linear-gradient(to bottom, #d0deea, #f6f9fa 60px);
165
- z-index: 1;
166
- }
167
-
168
- .mound_text {
169
- transform: rotate(6deg);
170
- position: relative;
171
- z-index: 2;
172
- }
173
-
174
- .mound_spade {
175
- width: 35px;
176
- height: 30px;
177
- position: absolute;
178
- right: 50%;
179
- top: 42%;
180
- margin-right: -250px;
181
- background: #dd4040;
182
- transform: rotate(35deg);
183
- }
184
-
185
- .mound_spade::before {
186
- content: "";
187
- position: absolute;
188
- width: 40%;
189
- height: 30px;
190
- bottom: 100%;
191
- left: 50%;
192
- transform: translateX(-50%);
193
- background: #dd4040;
194
- }
195
-
196
- .mound_spade::after {
197
- content: "";
198
- position: absolute;
199
- width: 100%;
200
- height: 30px;
201
- top: -55px;
202
- border: 10px solid #dd4040;
203
- border-radius: 4px 4px 20px 20px;
204
- }
205
- `}</style>
206
139
  </div>
207
140
  );
208
141
  };
209
142
 
210
143
  export default Snow;
144
+
@@ -1,38 +1,106 @@
1
+ "use client";
2
+
3
+ import { motion } from "framer-motion";
1
4
  import Link from "next/link";
5
+ import { cn } from "@/components/ui/cn";
2
6
 
3
- const StoneAge = () => {
7
+ const StoneAge = ({ className }: { className?: string }) => {
4
8
  return (
5
- <section className="min-h-screen bg-white flex items-center justify-center font-[Arvo]">
6
- <div className="w-full max-w-5xl px-4">
7
- <div className="text-center">
8
- <div
9
- className="h-100 bg-center bg-no-repeat flex items-center justify-center"
10
- style={{
11
- backgroundImage:
12
- "url(https://cdn.dribbble.com/users/285475/screenshots/2083086/dribbble_1.gif)",
13
- }}
14
- ></div>
15
-
16
- <div className="-mt-12">
17
- <h3 className="text-2xl md:text-3xl font-semibold mb-2">
18
- Looks like you&apos;re lost
19
- </h3>
20
-
21
- <p className="text-gray-600 mb-5">
22
- The page you are looking for is not available!
23
- </p>
9
+ <div
10
+ className={cn(
11
+ "min-h-screen bg-[#fcf8f0] flex flex-col items-center justify-center p-8 overflow-hidden relative font-serif",
12
+ className
13
+ )}
14
+ >
15
+ <style jsx global>{`
16
+ @import url('https://fonts.googleapis.com/css2?family=Arvo:wght@400;700&display=swap');
17
+
18
+ .stone-age-font {
19
+ font-family: 'Arvo', serif;
20
+ }
21
+
22
+ .paper-texture {
23
+ background-image: url("https://www.transparenttextures.com/patterns/natural-paper.png");
24
+ }
25
+
26
+ .rock-shadow {
27
+ filter: drop-shadow(0 20px 30px rgba(0,0,0,0.1));
28
+ }
29
+ `}</style>
30
+
31
+ <div className="absolute inset-0 paper-texture opacity-40 pointer-events-none" />
32
+
33
+ <motion.div
34
+ animate={{ y: [0, -10, 0] }}
35
+ transition={{ duration: 6, repeat: Infinity, ease: "easeInOut" }}
36
+ className="absolute top-20 right-[15%] opacity-10 select-none"
37
+ >
38
+ <svg width="200" height="200" viewBox="0 0 200 200">
39
+ <path d="M40 160 L80 40 L120 180 L160 80 L180 160 Z" fill="#4a3b2a" />
40
+ </svg>
41
+ </motion.div>
42
+
43
+ <motion.div
44
+ animate={{ y: [0, -15, 0] }}
45
+ transition={{ duration: 8, repeat: Infinity, ease: "easeInOut", delay: 1 }}
46
+ className="absolute bottom-20 left-[10%] opacity-10 select-none"
47
+ >
48
+ <svg width="250" height="250" viewBox="0 0 200 200">
49
+ <path d="M50 180 L100 20 L150 160 Z" fill="#4a3b2a" />
50
+ </svg>
51
+ </motion.div>
24
52
 
53
+ <motion.div
54
+ initial={{ opacity: 0, y: 30 }}
55
+ animate={{ opacity: 1, y: 0 }}
56
+ className="relative z-10 w-full max-w-4xl flex flex-col items-center text-center"
57
+ >
58
+ <div className="relative mb-8">
59
+ <motion.div
60
+ animate={{ rotate: [-2, 2, -2] }}
61
+ transition={{ duration: 4, repeat: Infinity, ease: "easeInOut" }}
62
+ className="relative z-20"
63
+ >
64
+ <img
65
+ src="https://cdn.dribbble.com/users/285475/screenshots/2083086/dribbble_1.gif"
66
+ alt="Stone Age Illustration"
67
+ className="w-full max-w-[450px] h-auto rock-shadow rounded-3xl"
68
+ />
69
+ </motion.div>
70
+
71
+ <div className="absolute -bottom-6 left-1/2 -translate-x-1/2 w-[80%] h-6 bg-black opacity-[0.05] blur-xl rounded-full" />
72
+ </div>
73
+
74
+ <div className="stone-age-font">
75
+ <h1 className="text-4xl md:text-6xl font-black text-[#4a3b2a] mb-4 uppercase tracking-tighter">
76
+ Prehistoric 404
77
+ </h1>
78
+ <p className="text-[#6b5844] text-xl md:text-2xl mb-12 max-w-xl mx-auto font-medium">
79
+ This endpoint hasn&apos;t been discovered yet. It&apos;s still in the early Jurassic.
80
+ </p>
81
+
82
+ <div className="flex flex-col sm:flex-row gap-6 items-center justify-center">
25
83
  <Link
26
84
  href="/"
27
- className="inline-block bg-[#39ac31] text-white px-5 py-2 rounded-md hover:opacity-90 transition"
85
+ className="px-10 py-4 bg-[#4a3b2a] text-[#fcf8f0] font-bold rounded-xl hover:bg-[#5c4a36] transition-all transform hover:-translate-y-1 hover:shadow-xl active:scale-95 text-lg"
28
86
  >
29
- Go to Home
87
+ Back to Future
30
88
  </Link>
89
+ <button className="px-10 py-4 border-2 border-[#4a3b2a]/20 text-[#4a3b2a] font-bold rounded-xl hover:bg-[#4a3b2a]/5 transition-all text-lg">
90
+ Explore History
91
+ </button>
31
92
  </div>
32
93
  </div>
94
+ </motion.div>
95
+
96
+ <div className="absolute bottom-10 w-full px-8 flex justify-between items-center text-[#4a3b2a]/20 font-bold uppercase tracking-[0.2em] text-[10px] sm:text-xs">
97
+ <span>Pleistocene Era</span>
98
+ <div className="h-px flex-1 mx-8 bg-[#4a3b2a]/10" />
99
+ <span>No Connection Found</span>
33
100
  </div>
34
- </section>
101
+ </div>
35
102
  );
36
103
  };
37
104
 
38
105
  export default StoneAge;
106
+