Florin Bobiș commited on
Commit
6528c1e
1 Parent(s): 6a37227
package-lock.json CHANGED
@@ -12,6 +12,7 @@
12
  "@radix-ui/react-dropdown-menu": "^2.1.1",
13
  "@radix-ui/react-icons": "^1.3.0",
14
  "@radix-ui/react-slot": "^1.1.0",
 
15
  "@tsparticles/engine": "^3.5.0",
16
  "@tsparticles/react": "^3.0.0",
17
  "@tsparticles/slim": "^3.5.0",
@@ -23,6 +24,7 @@
23
  "next-themes": "^0.3.0",
24
  "react": "^18",
25
  "react-dom": "^18",
 
26
  "tailwind-merge": "^2.5.2",
27
  "tailwindcss-animate": "^1.0.7"
28
  },
@@ -1081,6 +1083,32 @@
1081
  "tslib": "^2.4.0"
1082
  }
1083
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1084
  "node_modules/@tsparticles/basic": {
1085
  "version": "3.5.0",
1086
  "resolved": "https://registry.npmjs.org/@tsparticles/basic/-/basic-3.5.0.tgz",
@@ -5547,6 +5575,12 @@
5547
  "url": "https://github.com/sponsors/isaacs"
5548
  }
5549
  },
 
 
 
 
 
 
5550
  "node_modules/source-map-js": {
5551
  "version": "1.2.1",
5552
  "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
 
12
  "@radix-ui/react-dropdown-menu": "^2.1.1",
13
  "@radix-ui/react-icons": "^1.3.0",
14
  "@radix-ui/react-slot": "^1.1.0",
15
+ "@tabler/icons-react": "^3.17.0",
16
  "@tsparticles/engine": "^3.5.0",
17
  "@tsparticles/react": "^3.0.0",
18
  "@tsparticles/slim": "^3.5.0",
 
24
  "next-themes": "^0.3.0",
25
  "react": "^18",
26
  "react-dom": "^18",
27
+ "simplex-noise": "^4.0.3",
28
  "tailwind-merge": "^2.5.2",
29
  "tailwindcss-animate": "^1.0.7"
30
  },
 
1083
  "tslib": "^2.4.0"
1084
  }
1085
  },
1086
+ "node_modules/@tabler/icons": {
1087
+ "version": "3.17.0",
1088
+ "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-3.17.0.tgz",
1089
+ "integrity": "sha512-sCSfAQ0w93KSnSL7tS08n73CdIKpuHP8foeLMWgDKiZaCs8ZE//N3ytazCk651ZtruTtByI3b+ZDj7nRf+hHvA==",
1090
+ "license": "MIT",
1091
+ "funding": {
1092
+ "type": "github",
1093
+ "url": "https://github.com/sponsors/codecalm"
1094
+ }
1095
+ },
1096
+ "node_modules/@tabler/icons-react": {
1097
+ "version": "3.17.0",
1098
+ "resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-3.17.0.tgz",
1099
+ "integrity": "sha512-Ndm9Htv7KpIU1PYYrzs5EMhyA3aZGcgaxUp9Q1XOxcRZ+I0X+Ub2WS5f4bkRyDdL1s0++k2T9XRgmg2pG113sw==",
1100
+ "license": "MIT",
1101
+ "dependencies": {
1102
+ "@tabler/icons": "3.17.0"
1103
+ },
1104
+ "funding": {
1105
+ "type": "github",
1106
+ "url": "https://github.com/sponsors/codecalm"
1107
+ },
1108
+ "peerDependencies": {
1109
+ "react": ">= 16"
1110
+ }
1111
+ },
1112
  "node_modules/@tsparticles/basic": {
1113
  "version": "3.5.0",
1114
  "resolved": "https://registry.npmjs.org/@tsparticles/basic/-/basic-3.5.0.tgz",
 
5575
  "url": "https://github.com/sponsors/isaacs"
5576
  }
5577
  },
5578
+ "node_modules/simplex-noise": {
5579
+ "version": "4.0.3",
5580
+ "resolved": "https://registry.npmjs.org/simplex-noise/-/simplex-noise-4.0.3.tgz",
5581
+ "integrity": "sha512-qSE2I4AngLQG7BXqoZj51jokT4WUXe8mOBrvfOXpci8+6Yu44+/dD5zqDpOx3Ux792eamTd2lLcI8jqFntk/lg==",
5582
+ "license": "MIT"
5583
+ },
5584
  "node_modules/source-map-js": {
5585
  "version": "1.2.1",
5586
  "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
package.json CHANGED
@@ -13,6 +13,7 @@
13
  "@radix-ui/react-dropdown-menu": "^2.1.1",
14
  "@radix-ui/react-icons": "^1.3.0",
15
  "@radix-ui/react-slot": "^1.1.0",
 
16
  "@tsparticles/engine": "^3.5.0",
17
  "@tsparticles/react": "^3.0.0",
18
  "@tsparticles/slim": "^3.5.0",
@@ -24,6 +25,7 @@
24
  "next-themes": "^0.3.0",
25
  "react": "^18",
26
  "react-dom": "^18",
 
27
  "tailwind-merge": "^2.5.2",
28
  "tailwindcss-animate": "^1.0.7"
29
  },
 
13
  "@radix-ui/react-dropdown-menu": "^2.1.1",
14
  "@radix-ui/react-icons": "^1.3.0",
15
  "@radix-ui/react-slot": "^1.1.0",
16
+ "@tabler/icons-react": "^3.17.0",
17
  "@tsparticles/engine": "^3.5.0",
18
  "@tsparticles/react": "^3.0.0",
19
  "@tsparticles/slim": "^3.5.0",
 
25
  "next-themes": "^0.3.0",
26
  "react": "^18",
27
  "react-dom": "^18",
28
+ "simplex-noise": "^4.0.3",
29
  "tailwind-merge": "^2.5.2",
30
  "tailwindcss-animate": "^1.0.7"
31
  },
public/segment-after.jpg ADDED
public/segment-before.jpg ADDED
src/app/page.tsx CHANGED
@@ -1,7 +1,9 @@
1
  import Footer from "@/components/footer";
2
  import Header from "@/components/header";
 
3
  import { ContainerScroll } from "@/components/ui/container-scroll-animation";
4
- import { SparklesCore } from "@/components/ui/sparkles";
 
5
  import Image from "next/image";
6
 
7
  export default function Home() {
@@ -9,42 +11,12 @@ export default function Home() {
9
  <div className="flex min-h-screen w-full flex-col">
10
  <Header />
11
  <main className="flex min-h-[calc(100vh_-_theme(spacing.16))] flex-1 flex-col">
12
- <div className="h-screen w-full bg-black flex flex-col items-center justify-center overflow-hidden rounded-md">
13
- <h1 className="md:text-7xl text-3xl lg:text-9xl font-bold text-center text-white relative z-20">
14
- ATOM
15
- </h1>
16
- <div className="w-[40rem] h-40 relative">
17
- {/* Gradients */}
18
- <div className="absolute inset-x-20 top-0 bg-gradient-to-r from-transparent via-indigo-500 to-transparent h-[2px] w-3/4 blur-sm" />
19
- <div className="absolute inset-x-20 top-0 bg-gradient-to-r from-transparent via-indigo-500 to-transparent h-px w-3/4" />
20
- <div className="absolute inset-x-60 top-0 bg-gradient-to-r from-transparent via-sky-500 to-transparent h-[5px] w-1/4 blur-sm" />
21
- <div className="absolute inset-x-60 top-0 bg-gradient-to-r from-transparent via-sky-500 to-transparent h-px w-1/4" />
22
-
23
- {/* Core component */}
24
- <SparklesCore
25
- background="transparent"
26
- minSize={0.4}
27
- maxSize={1}
28
- particleDensity={1200}
29
- className="w-full h-full"
30
- particleColor="#FFFFFF"
31
- />
32
-
33
- <h3 className="mt-[-9rem] pb-1 md:text-xl text-sm lg:text-3xl bg-clip-text text-transparent bg-gradient-to-b from-gray-800 to-gray-500 dark:from-neutral-200 dark:to-neutral-600 text-center font-sans font-bold relative z-20">
34
- Your one-stop shop
35
- <br />
36
- for gravity-free design...
37
- </h3>
38
-
39
- {/* Radial Gradient to prevent sharp edges */}
40
- <div className="absolute inset-0 w-full h-full bg-black [mask-image:radial-gradient(350px_200px_at_top,transparent_20%,white)]"></div>
41
- </div>
42
- </div>
43
- <div className="flex flex-col overflow-hidden bg-black">
44
  <ContainerScroll
45
  titleComponent={
46
  <>
47
- <h1 className="text-4xl font-semibold text-black dark:text-white">
48
  Discover the power of <br />
49
  <span className="text-4xl md:text-[6rem] font-bold mt-1 leading-none">
50
  Microgravity
@@ -62,7 +34,22 @@ export default function Home() {
62
  draggable={false}
63
  />
64
  </ContainerScroll>
65
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  </main>
67
  <Footer />
68
  </div>
 
1
  import Footer from "@/components/footer";
2
  import Header from "@/components/header";
3
+ import { Compare } from "@/components/ui/compare";
4
  import { ContainerScroll } from "@/components/ui/container-scroll-animation";
5
+ import { Cover } from "@/components/ui/cover";
6
+ import WelcomeSection from "@/components/welcome-section";
7
  import Image from "next/image";
8
 
9
  export default function Home() {
 
11
  <div className="flex min-h-screen w-full flex-col">
12
  <Header />
13
  <main className="flex min-h-[calc(100vh_-_theme(spacing.16))] flex-1 flex-col">
14
+ <WelcomeSection />
15
+ <section className="flex flex-col overflow-hidden bg-background dark:bg-black">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  <ContainerScroll
17
  titleComponent={
18
  <>
19
+ <h1 className="p-2 text-4xl font-semibold bg-clip-text text-transparent text-center bg-gradient-to-b from-neutral-900 to-neutral-700 dark:from-neutral-600 dark:to-white">
20
  Discover the power of <br />
21
  <span className="text-4xl md:text-[6rem] font-bold mt-1 leading-none">
22
  Microgravity
 
34
  draggable={false}
35
  />
36
  </ContainerScroll>
37
+ </section>
38
+ <section className="flex flex-col overflow-hidden bg-background dark:bg-black h-[calc(100vh_-_theme(spacing.16))] items-center justify-center">
39
+ <h1 className="text-4xl md:text-4xl lg:text-6xl font-semibold max-w-7xl mx-auto text-center mt-6 relative z-20 py-6 bg-clip-text text-transparent bg-gradient-to-b from-neutral-800 via-neutral-700 to-neutral-700 dark:from-neutral-800 dark:via-white dark:to-white">
40
+ Get amazing insights <br /> at <Cover>warp speed</Cover>
41
+ </h1>
42
+ <div className="p-4 border rounded-3xl dark:bg-neutral-900 bg-neutral-100 border-neutral-200 dark:border-neutral-800 px-4">
43
+ <Compare
44
+ firstImage="/segment-before.jpg"
45
+ secondImage="/segment-after.jpg"
46
+ firstImageClassName="object-cover object-left-top"
47
+ secondImageClassname="object-cover object-left-top"
48
+ className="h-[250px] w-[200px] md:h-[500px] md:w-[500px]"
49
+ slideMode="hover"
50
+ />
51
+ </div>
52
+ </section>
53
  </main>
54
  <Footer />
55
  </div>
src/components/ui/compare.tsx ADDED
@@ -0,0 +1,240 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+ import React, { useState, useEffect, useRef, useCallback } from "react";
3
+ import { SparklesCore } from "@/components/ui/sparkles";
4
+ import { AnimatePresence, motion } from "framer-motion";
5
+ import { cn } from "@/lib/utils";
6
+ import { IconDotsVertical } from "@tabler/icons-react";
7
+
8
+ interface CompareProps {
9
+ firstImage?: string;
10
+ secondImage?: string;
11
+ className?: string;
12
+ firstImageClassName?: string;
13
+ secondImageClassname?: string;
14
+ initialSliderPercentage?: number;
15
+ slideMode?: "hover" | "drag";
16
+ showHandlebar?: boolean;
17
+ autoplay?: boolean;
18
+ autoplayDuration?: number;
19
+ }
20
+ export const Compare = ({
21
+ firstImage = "",
22
+ secondImage = "",
23
+ className,
24
+ firstImageClassName,
25
+ secondImageClassname,
26
+ initialSliderPercentage = 50,
27
+ slideMode = "hover",
28
+ showHandlebar = true,
29
+ autoplay = false,
30
+ autoplayDuration = 5000,
31
+ }: CompareProps) => {
32
+ const [sliderXPercent, setSliderXPercent] = useState(initialSliderPercentage);
33
+ const [isDragging, setIsDragging] = useState(false);
34
+
35
+ const sliderRef = useRef<HTMLDivElement>(null);
36
+
37
+ const [isMouseOver, setIsMouseOver] = useState(false);
38
+
39
+ const autoplayRef = useRef<NodeJS.Timeout | null>(null);
40
+
41
+ const startAutoplay = useCallback(() => {
42
+ if (!autoplay) return;
43
+
44
+ const startTime = Date.now();
45
+ const animate = () => {
46
+ const elapsedTime = Date.now() - startTime;
47
+ const progress =
48
+ (elapsedTime % (autoplayDuration * 2)) / autoplayDuration;
49
+ const percentage = progress <= 1 ? progress * 100 : (2 - progress) * 100;
50
+
51
+ setSliderXPercent(percentage);
52
+ autoplayRef.current = setTimeout(animate, 16); // ~60fps
53
+ };
54
+
55
+ animate();
56
+ }, [autoplay, autoplayDuration]);
57
+
58
+ const stopAutoplay = useCallback(() => {
59
+ if (autoplayRef.current) {
60
+ clearTimeout(autoplayRef.current);
61
+ autoplayRef.current = null;
62
+ }
63
+ }, []);
64
+
65
+ useEffect(() => {
66
+ startAutoplay();
67
+ return () => stopAutoplay();
68
+ }, [startAutoplay, stopAutoplay]);
69
+
70
+ function mouseEnterHandler() {
71
+ setIsMouseOver(true);
72
+ stopAutoplay();
73
+ }
74
+
75
+ function mouseLeaveHandler() {
76
+ setIsMouseOver(false);
77
+ if (slideMode === "hover") {
78
+ setSliderXPercent(initialSliderPercentage);
79
+ }
80
+ if (slideMode === "drag") {
81
+ setIsDragging(false);
82
+ }
83
+ startAutoplay();
84
+ }
85
+
86
+ const handleStart = useCallback(
87
+ (clientX: number) => {
88
+ if (slideMode === "drag") {
89
+ setIsDragging(true);
90
+ }
91
+ },
92
+ [slideMode]
93
+ );
94
+
95
+ const handleEnd = useCallback(() => {
96
+ if (slideMode === "drag") {
97
+ setIsDragging(false);
98
+ }
99
+ }, [slideMode]);
100
+
101
+ const handleMove = useCallback(
102
+ (clientX: number) => {
103
+ if (!sliderRef.current) return;
104
+ if (slideMode === "hover" || (slideMode === "drag" && isDragging)) {
105
+ const rect = sliderRef.current.getBoundingClientRect();
106
+ const x = clientX - rect.left;
107
+ const percent = (x / rect.width) * 100;
108
+ requestAnimationFrame(() => {
109
+ setSliderXPercent(Math.max(0, Math.min(100, percent)));
110
+ });
111
+ }
112
+ },
113
+ [slideMode, isDragging]
114
+ );
115
+
116
+ const handleMouseDown = useCallback(
117
+ (e: React.MouseEvent) => handleStart(e.clientX),
118
+ [handleStart]
119
+ );
120
+ const handleMouseUp = useCallback(() => handleEnd(), [handleEnd]);
121
+ const handleMouseMove = useCallback(
122
+ (e: React.MouseEvent) => handleMove(e.clientX),
123
+ [handleMove]
124
+ );
125
+
126
+ const handleTouchStart = useCallback(
127
+ (e: React.TouchEvent) => {
128
+ if (!autoplay) {
129
+ handleStart(e.touches[0].clientX);
130
+ }
131
+ },
132
+ [handleStart, autoplay]
133
+ );
134
+
135
+ const handleTouchEnd = useCallback(() => {
136
+ if (!autoplay) {
137
+ handleEnd();
138
+ }
139
+ }, [handleEnd, autoplay]);
140
+
141
+ const handleTouchMove = useCallback(
142
+ (e: React.TouchEvent) => {
143
+ if (!autoplay) {
144
+ handleMove(e.touches[0].clientX);
145
+ }
146
+ },
147
+ [handleMove, autoplay]
148
+ );
149
+
150
+ return (
151
+ <div
152
+ ref={sliderRef}
153
+ className={cn("w-[400px] h-[400px] overflow-hidden", className)}
154
+ style={{
155
+ position: "relative",
156
+ cursor: slideMode === "drag" ? "grab" : "col-resize",
157
+ }}
158
+ onMouseMove={handleMouseMove}
159
+ onMouseLeave={mouseLeaveHandler}
160
+ onMouseEnter={mouseEnterHandler}
161
+ onMouseDown={handleMouseDown}
162
+ onMouseUp={handleMouseUp}
163
+ onTouchStart={handleTouchStart}
164
+ onTouchEnd={handleTouchEnd}
165
+ onTouchMove={handleTouchMove}
166
+ >
167
+ <AnimatePresence initial={false}>
168
+ <motion.div
169
+ className="h-full w-px absolute top-0 m-auto z-30 bg-gradient-to-b from-transparent from-[5%] to-[95%] via-indigo-500 to-transparent"
170
+ style={{
171
+ left: `${sliderXPercent}%`,
172
+ top: "0",
173
+ zIndex: 40,
174
+ }}
175
+ transition={{ duration: 0 }}
176
+ >
177
+ <div className="w-36 h-full [mask-image:radial-gradient(100px_at_left,white,transparent)] absolute top-1/2 -translate-y-1/2 left-0 bg-gradient-to-r from-indigo-400 via-transparent to-transparent z-20 opacity-50" />
178
+ <div className="w-10 h-1/2 [mask-image:radial-gradient(50px_at_left,white,transparent)] absolute top-1/2 -translate-y-1/2 left-0 bg-gradient-to-r from-cyan-400 via-transparent to-transparent z-10 opacity-100" />
179
+ <div className="w-10 h-3/4 top-1/2 -translate-y-1/2 absolute -right-10 [mask-image:radial-gradient(100px_at_left,white,transparent)]">
180
+ <MemoizedSparklesCore
181
+ background="transparent"
182
+ minSize={0.4}
183
+ maxSize={1}
184
+ particleDensity={1200}
185
+ className="w-full h-full"
186
+ particleColor="#FFFFFF"
187
+ />
188
+ </div>
189
+ {showHandlebar && (
190
+ <div className="h-5 w-5 rounded-md top-1/2 -translate-y-1/2 bg-white z-30 -right-2.5 absolute flex items-center justify-center shadow-[0px_-1px_0px_0px_#FFFFFF40]">
191
+ <IconDotsVertical className="h-4 w-4 text-black" />
192
+ </div>
193
+ )}
194
+ </motion.div>
195
+ </AnimatePresence>
196
+ <div className="overflow-hidden w-full h-full relative z-20 pointer-events-none">
197
+ <AnimatePresence initial={false}>
198
+ {firstImage ? (
199
+ <motion.div
200
+ className={cn(
201
+ "absolute inset-0 z-20 rounded-2xl flex-shrink-0 w-full h-full select-none overflow-hidden",
202
+ firstImageClassName
203
+ )}
204
+ style={{
205
+ clipPath: `inset(0 ${100 - sliderXPercent}% 0 0)`,
206
+ }}
207
+ transition={{ duration: 0 }}
208
+ >
209
+ <img
210
+ alt="first image"
211
+ src={firstImage}
212
+ className={cn(
213
+ "absolute inset-0 z-20 rounded-2xl flex-shrink-0 w-full h-full select-none",
214
+ firstImageClassName
215
+ )}
216
+ draggable={false}
217
+ />
218
+ </motion.div>
219
+ ) : null}
220
+ </AnimatePresence>
221
+ </div>
222
+
223
+ <AnimatePresence initial={false}>
224
+ {secondImage ? (
225
+ <motion.img
226
+ className={cn(
227
+ "absolute top-0 left-0 z-[19] rounded-2xl w-full h-full select-none",
228
+ secondImageClassname
229
+ )}
230
+ alt="second image"
231
+ src={secondImage}
232
+ draggable={false}
233
+ />
234
+ ) : null}
235
+ </AnimatePresence>
236
+ </div>
237
+ );
238
+ };
239
+
240
+ const MemoizedSparklesCore = React.memo(SparklesCore);
src/components/ui/cover.tsx ADDED
@@ -0,0 +1,228 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+ import React, { useEffect, useId, useState } from "react";
3
+ import { AnimatePresence, motion } from "framer-motion";
4
+ import { useRef } from "react";
5
+ import { cn } from "@/lib/utils";
6
+ import { SparklesCore } from "@/components/ui/sparkles";
7
+
8
+ export const Cover = ({
9
+ children,
10
+ className,
11
+ }: {
12
+ children?: React.ReactNode;
13
+ className?: string;
14
+ }) => {
15
+ const [hovered, setHovered] = useState(false);
16
+
17
+ const ref = useRef<HTMLDivElement>(null);
18
+
19
+ const [containerWidth, setContainerWidth] = useState(0);
20
+ const [beamPositions, setBeamPositions] = useState<number[]>([]);
21
+
22
+ useEffect(() => {
23
+ if (ref.current) {
24
+ setContainerWidth(ref.current?.clientWidth ?? 0);
25
+
26
+ const height = ref.current?.clientHeight ?? 0;
27
+ const numberOfBeams = Math.floor(height / 10); // Adjust the divisor to control the spacing
28
+ const positions = Array.from(
29
+ { length: numberOfBeams },
30
+ (_, i) => (i + 1) * (height / (numberOfBeams + 1))
31
+ );
32
+ setBeamPositions(positions);
33
+ }
34
+ }, [ref.current]);
35
+
36
+ return (
37
+ <div
38
+ onMouseEnter={() => setHovered(true)}
39
+ onMouseLeave={() => setHovered(false)}
40
+ ref={ref}
41
+ className="relative hover:bg-neutral-900 group/cover inline-block dark:bg-neutral-900 bg-neutral-100 px-2 py-2 transition duration-200 rounded-sm"
42
+ >
43
+ <AnimatePresence>
44
+ {hovered && (
45
+ <motion.div
46
+ initial={{ opacity: 0 }}
47
+ animate={{ opacity: 1 }}
48
+ exit={{ opacity: 0 }}
49
+ transition={{
50
+ opacity: {
51
+ duration: 0.2,
52
+ },
53
+ }}
54
+ className="h-full w-full overflow-hidden absolute inset-0"
55
+ >
56
+ <motion.div
57
+ animate={{
58
+ translateX: ["-50%", "0%"],
59
+ }}
60
+ transition={{
61
+ translateX: {
62
+ duration: 10,
63
+ ease: "linear",
64
+ repeat: Infinity,
65
+ },
66
+ }}
67
+ className="w-[200%] h-full flex"
68
+ >
69
+ <SparklesCore
70
+ background="transparent"
71
+ minSize={0.4}
72
+ maxSize={1}
73
+ particleDensity={500}
74
+ className="w-full h-full"
75
+ particleColor="#FFFFFF"
76
+ />
77
+ <SparklesCore
78
+ background="transparent"
79
+ minSize={0.4}
80
+ maxSize={1}
81
+ particleDensity={500}
82
+ className="w-full h-full"
83
+ particleColor="#FFFFFF"
84
+ />
85
+ </motion.div>
86
+ </motion.div>
87
+ )}
88
+ </AnimatePresence>
89
+ {beamPositions.map((position, index) => (
90
+ <Beam
91
+ key={index}
92
+ hovered={hovered}
93
+ duration={Math.random() * 2 + 1}
94
+ delay={Math.random() * 2 + 1}
95
+ width={containerWidth}
96
+ style={{
97
+ top: `${position}px`,
98
+ }}
99
+ />
100
+ ))}
101
+ <motion.span
102
+ key={String(hovered)}
103
+ animate={{
104
+ scale: hovered ? 0.8 : 1,
105
+ x: hovered ? [0, -30, 30, -30, 30, 0] : 0,
106
+ y: hovered ? [0, 30, -30, 30, -30, 0] : 0,
107
+ }}
108
+ exit={{
109
+ filter: "none",
110
+ scale: 1,
111
+ x: 0,
112
+ y: 0,
113
+ }}
114
+ transition={{
115
+ duration: 0.2,
116
+ x: {
117
+ duration: 0.2,
118
+ repeat: Infinity,
119
+ repeatType: "loop",
120
+ },
121
+ y: {
122
+ duration: 0.2,
123
+ repeat: Infinity,
124
+ repeatType: "loop",
125
+ },
126
+ scale: {
127
+ duration: 0.2,
128
+ },
129
+ filter: {
130
+ duration: 0.2,
131
+ },
132
+ }}
133
+ className={cn(
134
+ "dark:text-white inline-block text-neutral-900 relative z-20 group-hover/cover:text-white transition duration-200",
135
+ className
136
+ )}
137
+ >
138
+ {children}
139
+ </motion.span>
140
+ <CircleIcon className="absolute -right-[2px] -top-[2px]" />
141
+ <CircleIcon className="absolute -bottom-[2px] -right-[2px]" delay={0.4} />
142
+ <CircleIcon className="absolute -left-[2px] -top-[2px]" delay={0.8} />
143
+ <CircleIcon className="absolute -bottom-[2px] -left-[2px]" delay={1.6} />
144
+ </div>
145
+ );
146
+ };
147
+
148
+ export const Beam = ({
149
+ className,
150
+ delay,
151
+ duration,
152
+ hovered,
153
+ width = 600,
154
+ ...svgProps
155
+ }: {
156
+ className?: string;
157
+ delay?: number;
158
+ duration?: number;
159
+ hovered?: boolean;
160
+ width?: number;
161
+ } & React.ComponentProps<typeof motion.svg>) => {
162
+ const id = useId();
163
+
164
+ return (
165
+ <motion.svg
166
+ width={width ?? "600"}
167
+ height="1"
168
+ viewBox={`0 0 ${width ?? "600"} 1`}
169
+ fill="none"
170
+ xmlns="http://www.w3.org/2000/svg"
171
+ className={cn("absolute inset-x-0 w-full", className)}
172
+ {...svgProps}
173
+ >
174
+ <motion.path
175
+ d={`M0 0.5H${width ?? "600"}`}
176
+ stroke={`url(#svgGradient-${id})`}
177
+ />
178
+
179
+ <defs>
180
+ <motion.linearGradient
181
+ id={`svgGradient-${id}`}
182
+ key={String(hovered)}
183
+ gradientUnits="userSpaceOnUse"
184
+ initial={{
185
+ x1: "0%",
186
+ x2: hovered ? "-10%" : "-5%",
187
+ y1: 0,
188
+ y2: 0,
189
+ }}
190
+ animate={{
191
+ x1: "110%",
192
+ x2: hovered ? "100%" : "105%",
193
+ y1: 0,
194
+ y2: 0,
195
+ }}
196
+ transition={{
197
+ duration: hovered ? 0.5 : duration ?? 2,
198
+ ease: "linear",
199
+ repeat: Infinity,
200
+ delay: hovered ? Math.random() * (1 - 0.2) + 0.2 : 0,
201
+ repeatDelay: hovered ? Math.random() * (2 - 1) + 1 : delay ?? 1,
202
+ }}
203
+ >
204
+ <stop stopColor="#2EB9DF" stopOpacity="0" />
205
+ <stop stopColor="#3b82f6" />
206
+ <stop offset="1" stopColor="#3b82f6" stopOpacity="0" />
207
+ </motion.linearGradient>
208
+ </defs>
209
+ </motion.svg>
210
+ );
211
+ };
212
+
213
+ export const CircleIcon = ({
214
+ className,
215
+ delay,
216
+ }: {
217
+ className?: string;
218
+ delay?: number;
219
+ }) => {
220
+ return (
221
+ <div
222
+ className={cn(
223
+ `pointer-events-none animate-pulse group-hover/cover:hidden group-hover/cover:opacity-100 group h-2 w-2 rounded-full bg-neutral-600 dark:bg-white opacity-20 group-hover/cover:bg-white`,
224
+ className
225
+ )}
226
+ ></div>
227
+ );
228
+ };
src/components/ui/shooting-stars.tsx ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+ import { cn } from "@/lib/utils";
3
+ import React, { useEffect, useState, useRef } from "react";
4
+
5
+ interface ShootingStar {
6
+ id: number;
7
+ x: number;
8
+ y: number;
9
+ angle: number;
10
+ scale: number;
11
+ speed: number;
12
+ distance: number;
13
+ }
14
+
15
+ interface ShootingStarsProps {
16
+ minSpeed?: number;
17
+ maxSpeed?: number;
18
+ minDelay?: number;
19
+ maxDelay?: number;
20
+ starColor?: string;
21
+ trailColor?: string;
22
+ starWidth?: number;
23
+ starHeight?: number;
24
+ className?: string;
25
+ }
26
+
27
+ const getRandomStartPoint = () => {
28
+ const side = Math.floor(Math.random() * 4);
29
+ const offset = Math.random() * window.innerWidth;
30
+
31
+ switch (side) {
32
+ case 0:
33
+ return { x: offset, y: 0, angle: 45 };
34
+ case 1:
35
+ return { x: window.innerWidth, y: offset, angle: 135 };
36
+ case 2:
37
+ return { x: offset, y: window.innerHeight, angle: 225 };
38
+ case 3:
39
+ return { x: 0, y: offset, angle: 315 };
40
+ default:
41
+ return { x: 0, y: 0, angle: 45 };
42
+ }
43
+ };
44
+ export const ShootingStars: React.FC<ShootingStarsProps> = ({
45
+ minSpeed = 10,
46
+ maxSpeed = 30,
47
+ minDelay = 1200,
48
+ maxDelay = 4200,
49
+ starColor = "#9E00FF",
50
+ trailColor = "#2EB9DF",
51
+ starWidth = 10,
52
+ starHeight = 1,
53
+ className,
54
+ }) => {
55
+ const [star, setStar] = useState<ShootingStar | null>(null);
56
+ const svgRef = useRef<SVGSVGElement>(null);
57
+
58
+ useEffect(() => {
59
+ const createStar = () => {
60
+ const { x, y, angle } = getRandomStartPoint();
61
+ const newStar: ShootingStar = {
62
+ id: Date.now(),
63
+ x,
64
+ y,
65
+ angle,
66
+ scale: 1,
67
+ speed: Math.random() * (maxSpeed - minSpeed) + minSpeed,
68
+ distance: 0,
69
+ };
70
+ setStar(newStar);
71
+
72
+ const randomDelay = Math.random() * (maxDelay - minDelay) + minDelay;
73
+ setTimeout(createStar, randomDelay);
74
+ };
75
+
76
+ createStar();
77
+
78
+ return () => {};
79
+ }, [minSpeed, maxSpeed, minDelay, maxDelay]);
80
+
81
+ useEffect(() => {
82
+ const moveStar = () => {
83
+ if (star) {
84
+ setStar((prevStar) => {
85
+ if (!prevStar) return null;
86
+ const newX =
87
+ prevStar.x +
88
+ prevStar.speed * Math.cos((prevStar.angle * Math.PI) / 180);
89
+ const newY =
90
+ prevStar.y +
91
+ prevStar.speed * Math.sin((prevStar.angle * Math.PI) / 180);
92
+ const newDistance = prevStar.distance + prevStar.speed;
93
+ const newScale = 1 + newDistance / 100;
94
+ if (
95
+ newX < -20 ||
96
+ newX > window.innerWidth + 20 ||
97
+ newY < -20 ||
98
+ newY > window.innerHeight + 20
99
+ ) {
100
+ return null;
101
+ }
102
+ return {
103
+ ...prevStar,
104
+ x: newX,
105
+ y: newY,
106
+ distance: newDistance,
107
+ scale: newScale,
108
+ };
109
+ });
110
+ }
111
+ };
112
+
113
+ const animationFrame = requestAnimationFrame(moveStar);
114
+ return () => cancelAnimationFrame(animationFrame);
115
+ }, [star]);
116
+
117
+ return (
118
+ <svg
119
+ ref={svgRef}
120
+ className={cn("w-full h-full absolute inset-0", className)}
121
+ >
122
+ {star && (
123
+ <rect
124
+ key={star.id}
125
+ x={star.x}
126
+ y={star.y}
127
+ width={starWidth * star.scale}
128
+ height={starHeight}
129
+ fill="url(#gradient)"
130
+ transform={`rotate(${star.angle}, ${
131
+ star.x + (starWidth * star.scale) / 2
132
+ }, ${star.y + starHeight / 2})`}
133
+ />
134
+ )}
135
+ <defs>
136
+ <linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="100%">
137
+ <stop offset="0%" style={{ stopColor: trailColor, stopOpacity: 0 }} />
138
+ <stop
139
+ offset="100%"
140
+ style={{ stopColor: starColor, stopOpacity: 1 }}
141
+ />
142
+ </linearGradient>
143
+ </defs>
144
+ </svg>
145
+ );
146
+ };
src/components/ui/stars-background.tsx ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+ import { cn } from "@/lib/utils";
3
+ import React, {
4
+ useState,
5
+ useEffect,
6
+ useRef,
7
+ RefObject,
8
+ useCallback,
9
+ } from "react";
10
+
11
+ interface StarProps {
12
+ x: number;
13
+ y: number;
14
+ radius: number;
15
+ opacity: number;
16
+ twinkleSpeed: number | null;
17
+ }
18
+
19
+ interface StarBackgroundProps {
20
+ starDensity?: number;
21
+ allStarsTwinkle?: boolean;
22
+ twinkleProbability?: number;
23
+ minTwinkleSpeed?: number;
24
+ maxTwinkleSpeed?: number;
25
+ className?: string;
26
+ }
27
+
28
+ export const StarsBackground: React.FC<StarBackgroundProps> = ({
29
+ starDensity = 0.00015,
30
+ allStarsTwinkle = true,
31
+ twinkleProbability = 0.7,
32
+ minTwinkleSpeed = 0.5,
33
+ maxTwinkleSpeed = 1,
34
+ className,
35
+ }) => {
36
+ const [stars, setStars] = useState<StarProps[]>([]);
37
+ const canvasRef: RefObject<HTMLCanvasElement> =
38
+ useRef<HTMLCanvasElement>(null);
39
+
40
+ const generateStars = useCallback(
41
+ (width: number, height: number): StarProps[] => {
42
+ const area = width * height;
43
+ const numStars = Math.floor(area * starDensity);
44
+ return Array.from({ length: numStars }, () => {
45
+ const shouldTwinkle =
46
+ allStarsTwinkle || Math.random() < twinkleProbability;
47
+ return {
48
+ x: Math.random() * width,
49
+ y: Math.random() * height,
50
+ radius: Math.random() * 0.05 + 0.5,
51
+ opacity: Math.random() * 0.5 + 0.5,
52
+ twinkleSpeed: shouldTwinkle
53
+ ? minTwinkleSpeed +
54
+ Math.random() * (maxTwinkleSpeed - minTwinkleSpeed)
55
+ : null,
56
+ };
57
+ });
58
+ },
59
+ [
60
+ starDensity,
61
+ allStarsTwinkle,
62
+ twinkleProbability,
63
+ minTwinkleSpeed,
64
+ maxTwinkleSpeed,
65
+ ]
66
+ );
67
+
68
+ useEffect(() => {
69
+ const updateStars = () => {
70
+ if (canvasRef.current) {
71
+ const canvas = canvasRef.current;
72
+ const ctx = canvas.getContext("2d");
73
+ if (!ctx) return;
74
+
75
+ const { width, height } = canvas.getBoundingClientRect();
76
+ canvas.width = width;
77
+ canvas.height = height;
78
+ setStars(generateStars(width, height));
79
+ }
80
+ };
81
+
82
+ updateStars();
83
+
84
+ const resizeObserver = new ResizeObserver(updateStars);
85
+ if (canvasRef.current) {
86
+ resizeObserver.observe(canvasRef.current);
87
+ }
88
+
89
+ return () => {
90
+ if (canvasRef.current) {
91
+ resizeObserver.unobserve(canvasRef.current);
92
+ }
93
+ };
94
+ }, [
95
+ starDensity,
96
+ allStarsTwinkle,
97
+ twinkleProbability,
98
+ minTwinkleSpeed,
99
+ maxTwinkleSpeed,
100
+ generateStars,
101
+ ]);
102
+
103
+ useEffect(() => {
104
+ const canvas = canvasRef.current;
105
+ if (!canvas) return;
106
+
107
+ const ctx = canvas.getContext("2d");
108
+ if (!ctx) return;
109
+
110
+ let animationFrameId: number;
111
+
112
+ const render = () => {
113
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
114
+ stars.forEach((star) => {
115
+ ctx.beginPath();
116
+ ctx.arc(star.x, star.y, star.radius, 0, Math.PI * 2);
117
+ ctx.fillStyle = `rgba(255, 255, 255, ${star.opacity})`;
118
+ ctx.fill();
119
+
120
+ if (star.twinkleSpeed !== null) {
121
+ star.opacity =
122
+ 0.5 +
123
+ Math.abs(Math.sin((Date.now() * 0.001) / star.twinkleSpeed) * 0.5);
124
+ }
125
+ });
126
+
127
+ animationFrameId = requestAnimationFrame(render);
128
+ };
129
+
130
+ render();
131
+
132
+ return () => {
133
+ cancelAnimationFrame(animationFrameId);
134
+ };
135
+ }, [stars]);
136
+
137
+ return (
138
+ <canvas
139
+ ref={canvasRef}
140
+ className={cn("h-full w-full absolute inset-0", className)}
141
+ />
142
+ );
143
+ };
src/components/welcome-section.tsx ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { useTheme } from "next-themes";
4
+ import { ShootingStars } from "./ui/shooting-stars";
5
+ import { SparklesCore } from "./ui/sparkles";
6
+ import { StarsBackground } from "./ui/stars-background";
7
+
8
+ const WelcomeSection = () => {
9
+ const { theme } = useTheme();
10
+ return (
11
+ <section className="h-screen w-full bg-background dark:bg-black flex flex-col items-center justify-center overflow-hidden rounded-md">
12
+ <h1
13
+ className="md:text-7xl text-3xl lg:text-9xl font-bold text-center text-white relative z-20
14
+ bg-clip-text text-transparent bg-gradient-to-b from-neutral-900 to-neutral-700 dark:from-neutral-600 dark:to-white"
15
+ >
16
+ ATOM
17
+ </h1>
18
+ <div className="w-[40rem] h-40 relative">
19
+ {/* Gradients */}
20
+ <div className="absolute inset-x-20 top-0 bg-gradient-to-r from-transparent via-indigo-500 to-transparent h-[2px] w-3/4 blur-sm" />
21
+ <div className="absolute inset-x-20 top-0 bg-gradient-to-r from-transparent via-indigo-500 to-transparent h-px w-3/4" />
22
+ <div className="absolute inset-x-60 top-0 bg-gradient-to-r from-transparent via-sky-500 to-transparent h-[5px] w-1/4 blur-sm" />
23
+ <div className="absolute inset-x-60 top-0 bg-gradient-to-r from-transparent via-sky-500 to-transparent h-px w-1/4" />
24
+
25
+ {/* Core component */}
26
+ <SparklesCore
27
+ background="transparent"
28
+ minSize={0.4}
29
+ maxSize={1}
30
+ particleDensity={1200}
31
+ className="w-full h-full"
32
+ particleColor="#FFFFFF"
33
+ />
34
+
35
+ {theme == "dark" &&
36
+ <h3 className="mt-[-9rem] pb-1 md:text-xl text-sm lg:text-3xl bg-clip-text text-transparent bg-gradient-to-b from-gray-800 to-gray-500 dark:from-neutral-200 dark:to-neutral-600 text-center font-sans font-bold relative z-20">
37
+ Your one-stop shop
38
+ <br />
39
+ for gravity-free design...
40
+ </h3>
41
+ }
42
+
43
+ {theme === "dark" && (
44
+ /* Radial Gradient to prevent sharp edges */
45
+ <div className="absolute inset-0 w-full h-full bg-black [mask-image:radial-gradient(350px_200px_at_top,transparent_20%,white)]"></div>
46
+ )}
47
+ </div>
48
+ <ShootingStars minSpeed={3} maxSpeed={12} />
49
+ <StarsBackground starDensity={0.0005} />
50
+ </section>
51
+ );
52
+ };
53
+
54
+ export default WelcomeSection;