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,164 +1,216 @@
1
1
  "use client";
2
2
 
3
- import { useEffect, useRef } from "react";
4
- import Image from "next/image";
3
+ import { useEffect, useRef, useState } from "react";
4
+ import Link from "next/link";
5
+ import { motion, AnimatePresence } from "framer-motion";
6
+ import { cn } from "@/components/ui/cn";
5
7
 
6
- const RetroTv = () => {
8
+ const RetroTv = ({ className }: { className?: string }) => {
7
9
  const canvasRef = useRef<HTMLCanvasElement>(null);
10
+ const [isOn, setIsOn] = useState(false);
11
+ const [noiseIntensity, setNoiseIntensity] = useState(0.8);
12
+ const [channel, setChannel] = useState(404);
13
+
14
+ useEffect(() => {
15
+ const timer = setTimeout(() => setIsOn(true), 500);
16
+ return () => clearTimeout(timer);
17
+ }, []);
8
18
 
9
19
  useEffect(() => {
10
20
  const canvas = canvasRef.current;
11
- if (!canvas) return;
21
+ if (!canvas || !isOn) return;
12
22
 
13
23
  const ctx = canvas.getContext("2d");
14
24
  if (!ctx) return;
15
25
 
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;
26
+ let animationFrame: number;
27
+
28
+ const resize = () => {
29
+ canvas.width = canvas.offsetWidth;
30
+ canvas.height = canvas.offsetHeight;
31
+ };
32
+ resize();
33
+ window.addEventListener("resize", resize);
34
+
35
+ const render = () => {
36
+ const w = canvas.width;
37
+ const h = canvas.height;
38
+ const imageData = ctx.createImageData(w, h);
39
+ const data = imageData.data;
40
+
41
+ for (let i = 0; i < data.length; i += 4) {
42
+ const val = Math.random() * 255 * noiseIntensity;
43
+ data[i] = val;
44
+ data[i + 1] = val;
45
+ data[i + 2] = val;
46
+ data[i + 3] = 255;
34
47
  }
35
- ctx.putImageData(imgData, 0, 0);
36
- }, 30);
37
48
 
38
- return () => clearInterval(flicker);
39
- }, []);
49
+ ctx.putImageData(imageData, 0, 0);
50
+ animationFrame = requestAnimationFrame(render);
51
+ };
40
52
 
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
- />
53
+ render();
66
54
 
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
- }
55
+ return () => {
56
+ cancelAnimationFrame(animationFrame);
57
+ window.removeEventListener("resize", resize);
58
+ };
59
+ }, [isOn, noiseIntensity]);
87
60
 
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
- );
61
+ return (
62
+ <div
63
+ className={cn(
64
+ "min-h-screen bg-[#1a1a1a] flex items-center justify-center p-4 sm:p-8 font-mono overflow-hidden select-none",
65
+ className
66
+ )}
67
+ >
68
+ <style jsx global>{`
69
+ .crt-curve {
70
+ position: relative;
71
+ overflow: hidden;
72
+ background: #000;
126
73
  }
127
-
128
- .frame div {
74
+
75
+ .crt-curve::after {
76
+ content: " ";
77
+ display: block;
129
78
  position: absolute;
79
+ top: 0;
130
80
  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;
81
+ bottom: 0;
82
+ right: 0;
83
+ background: linear-gradient(rgba(18, 16, 16, 0.1) 50%, rgba(0, 0, 0, 0.2) 50%), linear-gradient(90deg, rgba(255, 0, 0, 0.04), rgba(0, 255, 0, 0.01), rgba(0, 0, 255, 0.04));
84
+ z-index: 2;
85
+ background-size: 100% 4px, 6px 100%;
86
+ pointer-events: none;
137
87
  }
138
88
 
139
- .frame div:nth-child(1) {
140
- animation-delay: 0s;
89
+ .crt-curve::before {
90
+ content: " ";
91
+ display: block;
92
+ position: absolute;
93
+ top: 0;
94
+ left: 0;
95
+ bottom: 0;
96
+ right: 0;
97
+ background: radial-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.3) 100%), linear-gradient(to bottom, rgba(18, 16, 16, 0) 0%, rgba(18, 16, 16, 0.1) 50%, rgba(18, 16, 16, 0) 100%);
98
+ z-index: 2;
99
+ pointer-events: none;
141
100
  }
142
101
 
143
- .frame div:nth-child(2) {
144
- animation-delay: 4s;
102
+ .tv-frame {
103
+ box-shadow:
104
+ inset 0 0 40px rgba(0,0,0,0.8),
105
+ 0 0 100px rgba(0,0,0,0.5),
106
+ 0 20px 50px rgba(0,0,0,0.4);
145
107
  }
146
108
 
147
- .frame div:nth-child(3) {
148
- animation-delay: 8s;
109
+ @keyframes scanline {
110
+ 0% { transform: translateY(-100%); }
111
+ 100% { transform: translateY(100%); }
149
112
  }
150
113
 
151
- @keyframes scanline {
152
- 0% {
153
- top: -20%;
154
- }
155
- 100% {
156
- top: 100%;
157
- }
114
+ .scanline-overlay {
115
+ width: 100%;
116
+ height: 100px;
117
+ background: linear-gradient(to bottom, transparent 0%, rgba(255,255,255,0.03) 50%, transparent 100%);
118
+ position: absolute;
119
+ top: 0;
120
+ left: 0;
121
+ animation: scanline 8s linear infinite;
122
+ z-index: 3;
158
123
  }
159
124
  `}</style>
125
+
126
+ <div className="relative w-full max-w-4xl flex flex-col items-center">
127
+ <div className="w-full bg-[#2a2a2a] p-4 sm:p-8 rounded-[3rem] border-8 border-[#333] tv-frame">
128
+ <div className="aspect-[4/3] w-full bg-black rounded-[2rem] overflow-hidden crt-curve border-4 border-black box-content relative">
129
+ <AnimatePresence>
130
+ {!isOn && (
131
+ <motion.div
132
+ initial={{ scaleY: 1 }}
133
+ exit={{ scaleY: 0, opacity: 0 }}
134
+ transition={{ duration: 0.1 }}
135
+ className="absolute inset-0 bg-black z-[100] flex items-center justify-center"
136
+ />
137
+ )}
138
+ </AnimatePresence>
139
+
140
+ {isOn && (
141
+ <>
142
+ <canvas ref={canvasRef} className="absolute inset-0 w-full h-full opacity-30" />
143
+ <div className="scanline-overlay" />
144
+
145
+ <div className="absolute inset-0 flex flex-col items-center justify-center z-10">
146
+ <motion.div
147
+ initial={{ scale: 0.8, opacity: 0 }}
148
+ animate={{ scale: 1, opacity: 1 }}
149
+ className="text-white text-center"
150
+ >
151
+ <h1 className="text-[6rem] sm:text-[10rem] font-bold tracking-tighter mix-blend-difference">
152
+ 404
153
+ </h1>
154
+ <div className="px-4 py-2 bg-white text-black font-bold text-xl uppercase skew-x-[-12deg]">
155
+ No Signal
156
+ </div>
157
+ </motion.div>
158
+ </div>
159
+
160
+ <div className="absolute top-6 left-6 z-20 bg-green-500/80 text-black px-3 py-1 text-sm font-bold rounded-sm animate-pulse">
161
+ CH {channel}
162
+ </div>
163
+ </>
164
+ )}
165
+ </div>
166
+
167
+ <div className="grid grid-cols-2 sm:grid-cols-4 gap-4 mt-8 px-4">
168
+ <div className="flex flex-col gap-2">
169
+ <label className="text-[10px] uppercase text-white/40 tracking-widest">Intensity</label>
170
+ <input
171
+ type="range"
172
+ min="0" max="1" step="0.01"
173
+ value={noiseIntensity}
174
+ onChange={(e) => setNoiseIntensity(parseFloat(e.target.value))}
175
+ className="w-full h-1 bg-white/10 appearance-none rounded-full accent-white"
176
+ />
177
+ </div>
178
+
179
+ <button
180
+ onClick={() => setChannel(prev => (prev === 404 ? 13 : 404))}
181
+ className="px-4 py-2 bg-[#333] hover:bg-[#444] text-white/60 text-xs rounded-md transition-colors border-b-2 border-black"
182
+ >
183
+ CHANNEL
184
+ </button>
185
+
186
+ <button
187
+ onClick={() => setIsOn(!isOn)}
188
+ className={cn(
189
+ "px-4 py-2 text-xs rounded-md transition-all border-b-2 border-black font-bold",
190
+ isOn ? "bg-red-900 text-red-100" : "bg-green-900 text-green-100"
191
+ )}
192
+ >
193
+ {isOn ? "POWER OFF" : "POWER ON"}
194
+ </button>
195
+
196
+ <Link
197
+ href="/"
198
+ className="px-4 py-2 bg-white text-black text-xs rounded-md font-bold text-center hover:bg-gray-200 transition-colors"
199
+ >
200
+ HOME
201
+ </Link>
202
+ </div>
203
+ </div>
204
+
205
+ <div className="w-1/2 h-4 sm:h-8 bg-[#222] rounded-b-[2rem] mx-auto mt-[-4px] z-[-1]" />
206
+
207
+ <div className="mt-12 text-white/10 text-xs tracking-[0.5em] uppercase pointer-events-none">
208
+ Solid State Electronics // Model 404-X
209
+ </div>
210
+ </div>
160
211
  </div>
161
212
  );
162
213
  };
163
214
 
164
215
  export default RetroTv;
216
+
@@ -1,25 +1,69 @@
1
1
  "use client";
2
2
 
3
+ import { motion } from "framer-motion";
3
4
  import Link from "next/link";
5
+ import { cn } from "@/components/ui/cn";
4
6
 
5
- const SimplePage = () => {
7
+ const SimplePage = ({ className }: { className?: string }) => {
6
8
  return (
7
- <main className="flex min-h-screen items-center justify-center bg-black text-white">
8
- <div className="w-full max-w-[420px] rounded-2xl border border-neutral-800 bg-neutral-950 px-8 py-10 text-center">
9
- <h1 className="text-6xl font-bold tracking-tight">404</h1>
10
- <h2 className="mt-2 text-sm font-medium">Page not found</h2>
11
- <p className="mt-3 text-sm text-neutral-400">
12
- The page you&apos;re looking for doesn&apos;t exist or was moved.
13
- </p>
14
- <Link
15
- href="/"
16
- className="mt-8 inline-block rounded-xl bg-white px-6 py-3 text-sm font-semibold text-black transition hover:opacity-90"
17
- >
18
- Go back home
19
- </Link>
20
- </div>
21
- </main>
9
+ <div
10
+ className={cn(
11
+ "min-h-screen bg-white dark:bg-[#050505] flex items-center justify-center p-6 font-sans relative",
12
+ className
13
+ )}
14
+ >
15
+ <div className="absolute inset-0 z-0 bg-[radial-gradient(circle_at_center,_transparent_0%,_rgba(0,0,0,0.02)_100%)] pointer-events-none" />
16
+
17
+ <motion.div
18
+ initial={{ opacity: 0, y: 10 }}
19
+ animate={{ opacity: 1, y: 0 }}
20
+ transition={{ duration: 0.6, ease: [0.22, 1, 0.36, 1] }}
21
+ className="relative z-10 w-full max-w-[480px] text-center"
22
+ >
23
+ <div className="mb-12">
24
+ <motion.h1
25
+ initial={{ scale: 0.95 }}
26
+ animate={{ scale: 1 }}
27
+ className="text-[120px] md:text-[160px] font-black tracking-tighter text-black dark:text-white leading-none select-none"
28
+ >
29
+ 404
30
+ </motion.h1>
31
+ <div className="h-px w-24 bg-black/5 dark:bg-white/5 mx-auto mt-4" />
32
+ </div>
33
+
34
+ <div className="space-y-6">
35
+ <h2 className="text-2xl font-semibold text-black dark:text-white tracking-tight">
36
+ Something went sideways.
37
+ </h2>
38
+ <p className="text-gray-500 dark:text-gray-400 text-lg leading-relaxed max-w-sm mx-auto font-medium">
39
+ The page you requested is currently unavailable.
40
+ It may have been moved or doesn&apos;t exist.
41
+ </p>
42
+ </div>
43
+
44
+ <div className="mt-16 flex flex-col gap-4">
45
+ <Link
46
+ href="/"
47
+ className="w-full bg-black dark:bg-white text-white dark:text-black font-bold py-5 rounded-2xl hover:opacity-90 transition-all active:scale-[0.98] shadow-2xl shadow-black/10 text-lg"
48
+ >
49
+ Return to Safety
50
+ </Link>
51
+ <button className="w-full text-gray-400 dark:text-gray-500 font-bold py-4 hover:text-black dark:hover:text-white transition-colors">
52
+ Contact Support
53
+ </button>
54
+ </div>
55
+
56
+ <div className="mt-24 pt-12 border-t border-black/[0.03] dark:border-white/[0.03] flex justify-between items-center text-[10px] text-gray-300 dark:text-gray-600 font-bold uppercase tracking-[0.2em]">
57
+ <span>© 2024 NOTFOUND</span>
58
+ <div className="flex gap-4">
59
+ <span>Status 404</span>
60
+ <span>Uptime 99.9%</span>
61
+ </div>
62
+ </div>
63
+ </motion.div>
64
+ </div>
22
65
  );
23
66
  };
24
67
 
25
68
  export default SimplePage;
69
+