saas-forge 0.0.1
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.
- package/.eslintrc.js +10 -0
- package/.vscode/settings.json +3 -0
- package/README.md +31 -0
- package/apps/web/.env.example +1 -0
- package/apps/web/app/favicon.ico +0 -0
- package/apps/web/app/layout.tsx +23 -0
- package/apps/web/app/page.tsx +12 -0
- package/apps/web/components.json +20 -0
- package/apps/web/eslint.config.js +4 -0
- package/apps/web/hooks/.gitkeep +0 -0
- package/apps/web/next-env.d.ts +5 -0
- package/apps/web/next.config.mjs +6 -0
- package/apps/web/package.json +32 -0
- package/apps/web/postcss.config.mjs +1 -0
- package/apps/web/tsconfig.json +23 -0
- package/docs/CODE_OF_CONDUCT.md +128 -0
- package/docs/CONTRIBUTING.md +45 -0
- package/docs/ISSUE_TEMPLATE/bug_report.md +31 -0
- package/docs/ISSUE_TEMPLATE/feature_request.md +17 -0
- package/docs/SECURITY.md +9 -0
- package/docs/pull_request_template.md +24 -0
- package/package.json +38 -0
- package/packages/eslint-config/README.md +3 -0
- package/packages/eslint-config/base.js +32 -0
- package/packages/eslint-config/next.js +51 -0
- package/packages/eslint-config/package.json +26 -0
- package/packages/eslint-config/react-internal.js +41 -0
- package/packages/typescript-config/README.md +3 -0
- package/packages/typescript-config/base.json +20 -0
- package/packages/typescript-config/nextjs.json +13 -0
- package/packages/typescript-config/package.json +9 -0
- package/packages/typescript-config/react-library.json +8 -0
- package/packages/ui/components.json +20 -0
- package/packages/ui/eslint.config.js +4 -0
- package/packages/ui/package.json +86 -0
- package/packages/ui/postcss.config.mjs +6 -0
- package/packages/ui/src/components/.gitkeep +0 -0
- package/packages/ui/src/components/aceternity/3d-card.tsx +155 -0
- package/packages/ui/src/components/aceternity/3d-marquee.tsx +142 -0
- package/packages/ui/src/components/aceternity/Spotlight.tsx +57 -0
- package/packages/ui/src/components/aceternity/animated-testimonials.tsx +165 -0
- package/packages/ui/src/components/aceternity/animated-tooltip.tsx +92 -0
- package/packages/ui/src/components/aceternity/aurora-background.tsx +61 -0
- package/packages/ui/src/components/aceternity/background-beams.tsx +141 -0
- package/packages/ui/src/components/aceternity/background-gradient.tsx +72 -0
- package/packages/ui/src/components/aceternity/card-hover-effect.tsx +111 -0
- package/packages/ui/src/components/aceternity/compare.tsx +244 -0
- package/packages/ui/src/components/aceternity/container-scroll-animation.tsx +95 -0
- package/packages/ui/src/components/aceternity/lamp.tsx +104 -0
- package/packages/ui/src/components/aceternity/sparkles.tsx +434 -0
- package/packages/ui/src/components/aceternity/spotlight-new.tsx +128 -0
- package/packages/ui/src/components/mdx/Alert.tsx +14 -0
- package/packages/ui/src/components/mdx/Badge.tsx +10 -0
- package/packages/ui/src/components/mdx/blockquote.tsx +11 -0
- package/packages/ui/src/components/mdx/code.tsx +16 -0
- package/packages/ui/src/components/mdx/h1.tsx +18 -0
- package/packages/ui/src/components/mdx/h2.tsx +18 -0
- package/packages/ui/src/components/mdx/h3.tsx +18 -0
- package/packages/ui/src/components/mdx/hr.tsx +7 -0
- package/packages/ui/src/components/mdx/li.tsx +7 -0
- package/packages/ui/src/components/mdx/mdxComponents.tsx +25 -0
- package/packages/ui/src/components/mdx/ol.tsx +7 -0
- package/packages/ui/src/components/mdx/pre.tsx +13 -0
- package/packages/ui/src/components/mdx/tableOfContents.tsx +23 -0
- package/packages/ui/src/components/mdx/ul.tsx +7 -0
- package/packages/ui/src/components/mdx/ulNumbered.tsx +7 -0
- package/packages/ui/src/components/shadcn/accordion.tsx +66 -0
- package/packages/ui/src/components/shadcn/alert-dialog.tsx +157 -0
- package/packages/ui/src/components/shadcn/alert.tsx +66 -0
- package/packages/ui/src/components/shadcn/aspect-ratio.tsx +11 -0
- package/packages/ui/src/components/shadcn/avatar.tsx +53 -0
- package/packages/ui/src/components/shadcn/badge.tsx +46 -0
- package/packages/ui/src/components/shadcn/breadcrumb.tsx +109 -0
- package/packages/ui/src/components/shadcn/button-group.tsx +83 -0
- package/packages/ui/src/components/shadcn/button.tsx +60 -0
- package/packages/ui/src/components/shadcn/calendar.tsx +216 -0
- package/packages/ui/src/components/shadcn/card.tsx +92 -0
- package/packages/ui/src/components/shadcn/carousel.tsx +241 -0
- package/packages/ui/src/components/shadcn/chart.tsx +357 -0
- package/packages/ui/src/components/shadcn/checkbox.tsx +32 -0
- package/packages/ui/src/components/shadcn/collapsible.tsx +33 -0
- package/packages/ui/src/components/shadcn/command.tsx +184 -0
- package/packages/ui/src/components/shadcn/context-menu.tsx +252 -0
- package/packages/ui/src/components/shadcn/dialog.tsx +143 -0
- package/packages/ui/src/components/shadcn/drawer.tsx +135 -0
- package/packages/ui/src/components/shadcn/dropdown-menu.tsx +257 -0
- package/packages/ui/src/components/shadcn/empty.tsx +104 -0
- package/packages/ui/src/components/shadcn/field.tsx +248 -0
- package/packages/ui/src/components/shadcn/form.tsx +167 -0
- package/packages/ui/src/components/shadcn/hover-card.tsx +44 -0
- package/packages/ui/src/components/shadcn/input-group.tsx +170 -0
- package/packages/ui/src/components/shadcn/input-otp.tsx +77 -0
- package/packages/ui/src/components/shadcn/input.tsx +21 -0
- package/packages/ui/src/components/shadcn/item.tsx +193 -0
- package/packages/ui/src/components/shadcn/kbd.tsx +28 -0
- package/packages/ui/src/components/shadcn/label.tsx +24 -0
- package/packages/ui/src/components/shadcn/menubar.tsx +276 -0
- package/packages/ui/src/components/shadcn/native-select.tsx +48 -0
- package/packages/ui/src/components/shadcn/navigation-menu.tsx +168 -0
- package/packages/ui/src/components/shadcn/pagination.tsx +127 -0
- package/packages/ui/src/components/shadcn/popover.tsx +48 -0
- package/packages/ui/src/components/shadcn/progress.tsx +31 -0
- package/packages/ui/src/components/shadcn/radio-group.tsx +45 -0
- package/packages/ui/src/components/shadcn/resizable.tsx +56 -0
- package/packages/ui/src/components/shadcn/scroll-area.tsx +58 -0
- package/packages/ui/src/components/shadcn/select.tsx +187 -0
- package/packages/ui/src/components/shadcn/separator.tsx +28 -0
- package/packages/ui/src/components/shadcn/sheet.tsx +139 -0
- package/packages/ui/src/components/shadcn/sidebar.tsx +726 -0
- package/packages/ui/src/components/shadcn/skeleton.tsx +13 -0
- package/packages/ui/src/components/shadcn/slider.tsx +63 -0
- package/packages/ui/src/components/shadcn/sonner.tsx +40 -0
- package/packages/ui/src/components/shadcn/spinner.tsx +16 -0
- package/packages/ui/src/components/shadcn/switch.tsx +31 -0
- package/packages/ui/src/components/shadcn/table.tsx +116 -0
- package/packages/ui/src/components/shadcn/tabs.tsx +66 -0
- package/packages/ui/src/components/shadcn/textarea.tsx +18 -0
- package/packages/ui/src/components/shadcn/toggle-group.tsx +83 -0
- package/packages/ui/src/components/shadcn/toggle.tsx +47 -0
- package/packages/ui/src/components/shadcn/tooltip.tsx +61 -0
- package/packages/ui/src/hooks/.gitkeep +0 -0
- package/packages/ui/src/hooks/use-device.tsx +29 -0
- package/packages/ui/src/hooks/use-mobile.tsx +19 -0
- package/packages/ui/src/hooks/use-outside-click.tsx +23 -0
- package/packages/ui/src/lib/utils.ts +6 -0
- package/packages/ui/src/providers/theme-provider.tsx +18 -0
- package/packages/ui/src/styles/globals.css +65 -0
- package/packages/ui/src/styles/shadcn-blue.css +69 -0
- package/packages/ui/src/styles/shadcn-green.css +68 -0
- package/packages/ui/src/styles/shadcn-neutral.css +69 -0
- package/packages/ui/src/styles/shadcn-orange.css +68 -0
- package/packages/ui/src/styles/shadcn-red.css +68 -0
- package/packages/ui/src/styles/shadcn-rose.css +68 -0
- package/packages/ui/src/styles/shadcn-violet.css +68 -0
- package/packages/ui/src/styles/shadcn-yellow.css +68 -0
- package/packages/ui/src/typography/font.tsx +19 -0
- package/packages/ui/src/typography/fontFiles/Cyberdyne.woff2 +0 -0
- package/packages/ui/src/typography/fontFiles/GeistMonoVF.woff +0 -0
- package/packages/ui/src/typography/fontFiles/GeistVF.woff +0 -0
- package/packages/ui/tsconfig.json +11 -0
- package/packages/ui/tsconfig.lint.json +8 -0
- package/pnpm-workspace.yaml +3 -0
- package/scripts/cli.js +44 -0
- package/tsconfig.json +4 -0
- package/turbo.json +21 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import React, { useState, useEffect, useRef, useCallback } from "react";
|
|
3
|
+
import { AnimatePresence, motion } from "motion/react";
|
|
4
|
+
import { IconDotsVertical } from "@tabler/icons-react";
|
|
5
|
+
import { cn } from "../../lib/utils";
|
|
6
|
+
import { SparklesCore } from "./sparkles";
|
|
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
|
+
if (e.touches[0]) {
|
|
130
|
+
handleStart(e.touches[0].clientX);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
[handleStart, autoplay]
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const handleTouchEnd = useCallback(() => {
|
|
138
|
+
if (!autoplay) {
|
|
139
|
+
handleEnd();
|
|
140
|
+
}
|
|
141
|
+
}, [handleEnd, autoplay]);
|
|
142
|
+
|
|
143
|
+
const handleTouchMove = useCallback(
|
|
144
|
+
(e: React.TouchEvent) => {
|
|
145
|
+
if (!autoplay) {
|
|
146
|
+
if (e.touches[0]) {
|
|
147
|
+
handleMove(e.touches[0].clientX);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
[handleMove, autoplay]
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<div
|
|
156
|
+
ref={sliderRef}
|
|
157
|
+
className={cn("w-[400px] h-[400px] overflow-hidden", className)}
|
|
158
|
+
style={{
|
|
159
|
+
position: "relative",
|
|
160
|
+
cursor: slideMode === "drag" ? "grab" : "col-resize",
|
|
161
|
+
}}
|
|
162
|
+
onMouseMove={handleMouseMove}
|
|
163
|
+
onMouseLeave={mouseLeaveHandler}
|
|
164
|
+
onMouseEnter={mouseEnterHandler}
|
|
165
|
+
onMouseDown={handleMouseDown}
|
|
166
|
+
onMouseUp={handleMouseUp}
|
|
167
|
+
onTouchStart={handleTouchStart}
|
|
168
|
+
onTouchEnd={handleTouchEnd}
|
|
169
|
+
onTouchMove={handleTouchMove}
|
|
170
|
+
>
|
|
171
|
+
<AnimatePresence initial={false}>
|
|
172
|
+
<motion.div
|
|
173
|
+
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"
|
|
174
|
+
style={{
|
|
175
|
+
left: `${sliderXPercent}%`,
|
|
176
|
+
top: "0",
|
|
177
|
+
zIndex: 40,
|
|
178
|
+
}}
|
|
179
|
+
transition={{ duration: 0 }}
|
|
180
|
+
>
|
|
181
|
+
<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" />
|
|
182
|
+
<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" />
|
|
183
|
+
<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)]">
|
|
184
|
+
<MemoizedSparklesCore
|
|
185
|
+
background="transparent"
|
|
186
|
+
minSize={0.4}
|
|
187
|
+
maxSize={1}
|
|
188
|
+
particleDensity={1200}
|
|
189
|
+
className="w-full h-full"
|
|
190
|
+
particleColor="#FFFFFF"
|
|
191
|
+
/>
|
|
192
|
+
</div>
|
|
193
|
+
{showHandlebar && (
|
|
194
|
+
<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]">
|
|
195
|
+
<IconDotsVertical className="h-4 w-4 text-black" />
|
|
196
|
+
</div>
|
|
197
|
+
)}
|
|
198
|
+
</motion.div>
|
|
199
|
+
</AnimatePresence>
|
|
200
|
+
<div className="overflow-hidden w-full h-full relative z-20 pointer-events-none">
|
|
201
|
+
<AnimatePresence initial={false}>
|
|
202
|
+
{firstImage ? (
|
|
203
|
+
<motion.div
|
|
204
|
+
className={cn(
|
|
205
|
+
"absolute inset-0 z-20 rounded-2xl shrink-0 w-full h-full select-none overflow-hidden",
|
|
206
|
+
firstImageClassName
|
|
207
|
+
)}
|
|
208
|
+
style={{
|
|
209
|
+
clipPath: `inset(0 ${100 - sliderXPercent}% 0 0)`,
|
|
210
|
+
}}
|
|
211
|
+
transition={{ duration: 0 }}
|
|
212
|
+
>
|
|
213
|
+
<img
|
|
214
|
+
alt="first image"
|
|
215
|
+
src={firstImage}
|
|
216
|
+
className={cn(
|
|
217
|
+
"absolute inset-0 z-20 rounded-2xl shrink-0 w-full h-full select-none",
|
|
218
|
+
firstImageClassName
|
|
219
|
+
)}
|
|
220
|
+
draggable={false}
|
|
221
|
+
/>
|
|
222
|
+
</motion.div>
|
|
223
|
+
) : null}
|
|
224
|
+
</AnimatePresence>
|
|
225
|
+
</div>
|
|
226
|
+
|
|
227
|
+
<AnimatePresence initial={false}>
|
|
228
|
+
{secondImage ? (
|
|
229
|
+
<motion.img
|
|
230
|
+
className={cn(
|
|
231
|
+
"absolute top-0 left-0 z-[19] rounded-2xl w-full h-full select-none",
|
|
232
|
+
secondImageClassname
|
|
233
|
+
)}
|
|
234
|
+
alt="second image"
|
|
235
|
+
src={secondImage}
|
|
236
|
+
draggable={false}
|
|
237
|
+
/>
|
|
238
|
+
) : null}
|
|
239
|
+
</AnimatePresence>
|
|
240
|
+
</div>
|
|
241
|
+
);
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const MemoizedSparklesCore = React.memo(SparklesCore);
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import React, { useRef } from "react";
|
|
3
|
+
import { useScroll, useTransform, motion, MotionValue } from "framer-motion";
|
|
4
|
+
|
|
5
|
+
export const ContainerScroll = ({
|
|
6
|
+
titleComponent,
|
|
7
|
+
children,
|
|
8
|
+
}: {
|
|
9
|
+
titleComponent: string | React.ReactNode;
|
|
10
|
+
children: React.ReactNode;
|
|
11
|
+
}) => {
|
|
12
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
13
|
+
const { scrollYProgress } = useScroll({
|
|
14
|
+
target: containerRef,
|
|
15
|
+
});
|
|
16
|
+
const [isMobile, setIsMobile] = React.useState(false);
|
|
17
|
+
|
|
18
|
+
React.useEffect(() => {
|
|
19
|
+
const checkMobile = () => {
|
|
20
|
+
setIsMobile(window.innerWidth <= 768);
|
|
21
|
+
};
|
|
22
|
+
checkMobile();
|
|
23
|
+
window.addEventListener("resize", checkMobile);
|
|
24
|
+
return () => {
|
|
25
|
+
window.removeEventListener("resize", checkMobile);
|
|
26
|
+
};
|
|
27
|
+
}, []);
|
|
28
|
+
|
|
29
|
+
const scaleDimensions = () => {
|
|
30
|
+
return isMobile ? [0.7, 0.9] : [1.05, 1];
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const rotate = useTransform(scrollYProgress, [1, 0], [20, 0]);
|
|
34
|
+
const scale = useTransform(scrollYProgress, [0, 1], scaleDimensions());
|
|
35
|
+
const translate = useTransform(scrollYProgress, [0, 1], [0, -40]);
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div
|
|
39
|
+
className="h-[30rem] md:h-[40rem] flex items-center justify-center relative p-2 "
|
|
40
|
+
ref={containerRef}
|
|
41
|
+
>
|
|
42
|
+
<div
|
|
43
|
+
className=" w-full relative"
|
|
44
|
+
style={{
|
|
45
|
+
perspective: "1000px",
|
|
46
|
+
}}
|
|
47
|
+
>
|
|
48
|
+
<Header translate={translate} titleComponent={titleComponent} />
|
|
49
|
+
<Card rotate={rotate} translate={translate} scale={scale}>
|
|
50
|
+
{children}
|
|
51
|
+
</Card>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const Header = ({ translate, titleComponent }: any) => {
|
|
58
|
+
return (
|
|
59
|
+
<motion.div
|
|
60
|
+
style={{
|
|
61
|
+
translateY: translate,
|
|
62
|
+
}}
|
|
63
|
+
className="div max-w-5xl mx-auto text-center"
|
|
64
|
+
>
|
|
65
|
+
{titleComponent}
|
|
66
|
+
</motion.div>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const Card = ({
|
|
71
|
+
rotate,
|
|
72
|
+
scale,
|
|
73
|
+
children,
|
|
74
|
+
}: {
|
|
75
|
+
rotate: MotionValue<number>;
|
|
76
|
+
scale: MotionValue<number>;
|
|
77
|
+
translate: MotionValue<number>;
|
|
78
|
+
children: React.ReactNode;
|
|
79
|
+
}) => {
|
|
80
|
+
return (
|
|
81
|
+
<motion.div
|
|
82
|
+
style={{
|
|
83
|
+
rotateX: rotate,
|
|
84
|
+
scale,
|
|
85
|
+
boxShadow:
|
|
86
|
+
"0 0 #0000004d, 0 9px 20px #0000004a, 0 37px 37px #00000042, 0 84px 50px #00000026, 0 149px 60px #0000000a, 0 233px 65px #00000003",
|
|
87
|
+
}}
|
|
88
|
+
className="max-w-7xl mx-auto h-[30rem] md:h-[40rem] w-full border-4 border-[#6C6C6C] p-2 md:p-6 bg-[#222222] rounded-[30px] shadow-2xl"
|
|
89
|
+
>
|
|
90
|
+
<div className=" h-full w-full overflow-hidden rounded-2xl bg-gray-100 dark:bg-zinc-900 md:rounded-2xl md:p-4 ">
|
|
91
|
+
{children}
|
|
92
|
+
</div>
|
|
93
|
+
</motion.div>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { motion } from "motion/react";
|
|
4
|
+
import { cn } from "../../lib/utils";
|
|
5
|
+
|
|
6
|
+
export function LampDemo() {
|
|
7
|
+
return (
|
|
8
|
+
<LampContainer>
|
|
9
|
+
<motion.h1
|
|
10
|
+
initial={{ opacity: 0.5, y: 100 }}
|
|
11
|
+
whileInView={{ opacity: 1, y: 0 }}
|
|
12
|
+
transition={{
|
|
13
|
+
delay: 0.3,
|
|
14
|
+
duration: 0.8,
|
|
15
|
+
ease: "easeInOut",
|
|
16
|
+
}}
|
|
17
|
+
className="mt-8 bg-gradient-to-br from-slate-300 to-slate-500 py-4 bg-clip-text text-center text-4xl font-medium tracking-tight text-transparent md:text-7xl"
|
|
18
|
+
>
|
|
19
|
+
Build lamps <br /> the right way
|
|
20
|
+
</motion.h1>
|
|
21
|
+
</LampContainer>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const LampContainer = ({
|
|
26
|
+
children,
|
|
27
|
+
className,
|
|
28
|
+
}: {
|
|
29
|
+
children: React.ReactNode;
|
|
30
|
+
className?: string;
|
|
31
|
+
}) => {
|
|
32
|
+
return (
|
|
33
|
+
<div
|
|
34
|
+
className={cn(
|
|
35
|
+
"relative flex min-h-screen flex-col items-center justify-center overflow-hidden bg-slate-950 w-full rounded-md z-0",
|
|
36
|
+
className
|
|
37
|
+
)}
|
|
38
|
+
>
|
|
39
|
+
<div className="relative flex w-full flex-1 scale-y-125 items-center justify-center isolate z-0 ">
|
|
40
|
+
<motion.div
|
|
41
|
+
initial={{ opacity: 0.5, width: "15rem" }}
|
|
42
|
+
whileInView={{ opacity: 1, width: "30rem" }}
|
|
43
|
+
transition={{
|
|
44
|
+
delay: 0.3,
|
|
45
|
+
duration: 0.8,
|
|
46
|
+
ease: "easeInOut",
|
|
47
|
+
}}
|
|
48
|
+
style={{
|
|
49
|
+
backgroundImage: `conic-gradient(var(--conic-position), var(--tw-gradient-stops))`,
|
|
50
|
+
}}
|
|
51
|
+
className="absolute inset-auto right-1/2 h-56 overflow-visible w-[30rem] bg-gradient-conic from-cyan-500 via-transparent to-transparent text-white [--conic-position:from_70deg_at_center_top]"
|
|
52
|
+
>
|
|
53
|
+
<div className="absolute w-[100%] left-0 bg-slate-950 h-40 bottom-0 z-20 [mask-image:linear-gradient(to_top,white,transparent)]" />
|
|
54
|
+
<div className="absolute w-40 h-[100%] left-0 bg-slate-950 bottom-0 z-20 [mask-image:linear-gradient(to_right,white,transparent)]" />
|
|
55
|
+
</motion.div>
|
|
56
|
+
<motion.div
|
|
57
|
+
initial={{ opacity: 0.5, width: "15rem" }}
|
|
58
|
+
whileInView={{ opacity: 1, width: "30rem" }}
|
|
59
|
+
transition={{
|
|
60
|
+
delay: 0.3,
|
|
61
|
+
duration: 0.8,
|
|
62
|
+
ease: "easeInOut",
|
|
63
|
+
}}
|
|
64
|
+
style={{
|
|
65
|
+
backgroundImage: `conic-gradient(var(--conic-position), var(--tw-gradient-stops))`,
|
|
66
|
+
}}
|
|
67
|
+
className="absolute inset-auto left-1/2 h-56 w-[30rem] bg-gradient-conic from-transparent via-transparent to-cyan-500 text-white [--conic-position:from_290deg_at_center_top]"
|
|
68
|
+
>
|
|
69
|
+
<div className="absolute w-40 h-[100%] right-0 bg-slate-950 bottom-0 z-20 [mask-image:linear-gradient(to_left,white,transparent)]" />
|
|
70
|
+
<div className="absolute w-[100%] right-0 bg-slate-950 h-40 bottom-0 z-20 [mask-image:linear-gradient(to_top,white,transparent)]" />
|
|
71
|
+
</motion.div>
|
|
72
|
+
<div className="absolute top-1/2 h-48 w-full translate-y-12 scale-x-150 bg-slate-950 blur-2xl"></div>
|
|
73
|
+
<div className="absolute top-1/2 z-50 h-48 w-full bg-transparent opacity-10 backdrop-blur-md"></div>
|
|
74
|
+
<div className="absolute inset-auto z-50 h-36 w-[28rem] -translate-y-1/2 rounded-full bg-cyan-500 opacity-50 blur-3xl"></div>
|
|
75
|
+
<motion.div
|
|
76
|
+
initial={{ width: "8rem" }}
|
|
77
|
+
whileInView={{ width: "16rem" }}
|
|
78
|
+
transition={{
|
|
79
|
+
delay: 0.3,
|
|
80
|
+
duration: 0.8,
|
|
81
|
+
ease: "easeInOut",
|
|
82
|
+
}}
|
|
83
|
+
className="absolute inset-auto z-30 h-36 w-64 -translate-y-[6rem] rounded-full bg-cyan-400 blur-2xl"
|
|
84
|
+
></motion.div>
|
|
85
|
+
<motion.div
|
|
86
|
+
initial={{ width: "15rem" }}
|
|
87
|
+
whileInView={{ width: "30rem" }}
|
|
88
|
+
transition={{
|
|
89
|
+
delay: 0.3,
|
|
90
|
+
duration: 0.8,
|
|
91
|
+
ease: "easeInOut",
|
|
92
|
+
}}
|
|
93
|
+
className="absolute inset-auto z-50 h-0.5 w-[30rem] -translate-y-[7rem] bg-cyan-400 "
|
|
94
|
+
></motion.div>
|
|
95
|
+
|
|
96
|
+
<div className="absolute inset-auto z-40 h-44 w-full -translate-y-[12.5rem] bg-slate-950 "></div>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<div className="relative z-50 flex -translate-y-80 flex-col items-center px-5">
|
|
100
|
+
{children}
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
);
|
|
104
|
+
};
|