diff --git a/app/globals.css b/app/globals.css index 4a8ef3b..edc11de 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,18 +1,16 @@ @import "tailwindcss"; -@custom-variant dark (&:where(.dark, .dark *)); +@plugin "tailwindcss-animate"; +@import "tw-animate-css"; -:root { - --background: #ffffff; - --foreground: #171717; -} +@custom-variant dark (&:where(.dark, .dark *)); @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); --font-sans: var(--font-inter); --font-display: var(--font-anton); - + /* Custom Colors */ --color-primary: #3B82F6; --color-background-light: #FDFDFD; @@ -22,21 +20,57 @@ --color-slush-purple: #9747FF; --color-slush-green: #00D68F; --color-slush-dark: #1A1A1A; - + /* Border Radius */ --radius: 0.5rem; --radius-large: 2rem; - + /* Animations */ --animate-marquee: marquee 25s linear infinite; --animate-marquee-reverse: marquee-reverse 25s linear infinite; --animate-float: float 6s ease-in-out infinite; + --color-sidebar-ring: var(--sidebar-ring); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar: var(--sidebar); + --color-chart-5: var(--chart-5); + --color-chart-4: var(--chart-4); + --color-chart-3: var(--chart-3); + --color-chart-2: var(--chart-2); + --color-chart-1: var(--chart-1); + --color-ring: var(--ring); + --color-input: var(--input); + --color-border: var(--border); + --color-destructive: var(--destructive); + --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-secondary-foreground: var(--secondary-foreground); + --color-secondary: var(--secondary); + --color-primary-foreground: var(--primary-foreground); + --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-card-foreground: var(--card-foreground); + --color-card: var(--card); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --radius-2xl: calc(var(--radius) + 8px); + --radius-3xl: calc(var(--radius) + 12px); + --radius-4xl: calc(var(--radius) + 16px); } @keyframes marquee { 0% { transform: translateX(0%); } + 100% { transform: translateX(-100%); } @@ -46,30 +80,25 @@ 0% { transform: translateX(-100%); } + 100% { transform: translateX(0%); } } @keyframes float { - 0%, 100% { + + 0%, + 100% { transform: translateY(0); } + 50% { transform: translateY(-20px); } } -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; - } -} - body { - background: var(--background); - color: var(--foreground); font-family: var(--font-inter), Arial, Helvetica, sans-serif; } @@ -165,3 +194,165 @@ body { background: #1f2937 !important; border-color: #374151 !important; } + +/* Cursor Hover Animation */ +.cursor-animation { + animation: cursorMove 3s ease-in-out infinite; +} + +.buy-button { + animation: buttonHover 3s ease-in-out infinite; +} + +@keyframes cursorMove { + 0% { + transform: translate(80px, 60px); + opacity: 1; + } + + 30% { + transform: translate(0px, 0px); + opacity: 1; + } + + 45% { + transform: translate(0px, 0px) scale(0.9); + opacity: 1; + } + + 55% { + transform: translate(0px, 0px) scale(1); + opacity: 1; + } + + 70% { + transform: translate(0px, 0px); + opacity: 1; + } + + 100% { + transform: translate(80px, 60px); + opacity: 1; + } +} + +@keyframes buttonHover { + 0% { + background-color: black; + transform: scale(1); + box-shadow: 4px 4px 0px 0px rgba(0, 0, 0, 0.3); + } + + 30% { + background-color: #FFD600; + color: black; + transform: scale(1.05); + box-shadow: 6px 6px 0px 0px rgba(0, 0, 0, 0.4); + } + + 45% { + background-color: #00D68F; + color: black; + transform: scale(0.95); + box-shadow: 2px 2px 0px 0px rgba(0, 0, 0, 0.2); + } + + 55% { + background-color: #00D68F; + color: black; + transform: scale(1.05); + box-shadow: 6px 6px 0px 0px rgba(0, 0, 0, 0.4); + } + + 70% { + background-color: #FFD600; + color: black; + transform: scale(1.05); + box-shadow: 6px 6px 0px 0px rgba(0, 0, 0, 0.4); + } + + 100% { + background-color: black; + color: white; + transform: scale(1); + box-shadow: 4px 4px 0px 0px rgba(0, 0, 0, 0.3); + } +} + +:root { + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} \ No newline at end of file diff --git a/bun.lock b/bun.lock index f2c2118..c098a97 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 1, "workspaces": { "": { "name": "datex", @@ -13,12 +14,18 @@ "@mysten/walrus": "^0.9.0", "@mysten/zksend": "^0.14.12", "@tanstack/react-query": "^5.90.18", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", "gsap": "^3.14.2", "jwt-decode": "^4.0.0", + "lucide-react": "^0.562.0", "motion": "^12.26.2", "next": "16.1.3", "react": "19.2.3", "react-dom": "19.2.3", + "tailwind-merge": "^3.4.0", + "tailwindcss-animate": "^1.0.7", + "tw-animate-css": "^1.4.0", }, "devDependencies": { "@tailwindcss/postcss": "^4", @@ -595,6 +602,8 @@ "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], + "client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="], "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], @@ -975,6 +984,8 @@ "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "lucide-react": ["lucide-react@0.562.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw=="], + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], @@ -1185,8 +1196,12 @@ "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], + "tailwind-merge": ["tailwind-merge@3.4.0", "", {}, "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g=="], + "tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="], + "tailwindcss-animate": ["tailwindcss-animate@1.0.7", "", { "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders" } }, "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA=="], + "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], "teeny-request": ["teeny-request@9.0.0", "", { "dependencies": { "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "node-fetch": "^2.6.9", "stream-events": "^1.0.5", "uuid": "^9.0.0" } }, "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g=="], @@ -1203,6 +1218,8 @@ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="], + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], "typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="], diff --git a/components.json b/components.json new file mode 100644 index 0000000..af71ad4 --- /dev/null +++ b/components.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "registries": { + "@react-bits": "https://reactbits.dev/r/{name}.json" + } +} diff --git a/components/CardSwap.tsx b/components/CardSwap.tsx new file mode 100644 index 0000000..f376760 --- /dev/null +++ b/components/CardSwap.tsx @@ -0,0 +1,220 @@ +import React, { + Children, + cloneElement, + forwardRef, + isValidElement, + ReactElement, + ReactNode, + RefObject, + useEffect, + useMemo, + useRef +} from 'react'; +import gsap from 'gsap'; + +export interface CardSwapProps { + width?: number | string; + height?: number | string; + cardDistance?: number; + verticalDistance?: number; + delay?: number; + pauseOnHover?: boolean; + onCardClick?: (idx: number) => void; + skewAmount?: number; + easing?: 'linear' | 'elastic'; + children: ReactNode; +} + +export interface CardProps extends React.HTMLAttributes { + customClass?: string; +} + +export const Card = forwardRef(({ customClass, ...rest }, ref) => ( +
+)); +Card.displayName = 'Card'; + +type CardRef = RefObject; +interface Slot { + x: number; + y: number; + z: number; + zIndex: number; +} + +const makeSlot = (i: number, distX: number, distY: number, total: number): Slot => ({ + x: i * distX, + y: -i * distY, + z: -i * distX * 1.5, + zIndex: total - i +}); + +const placeNow = (el: HTMLElement, slot: Slot, skew: number) => + gsap.set(el, { + x: slot.x, + y: slot.y, + z: slot.z, + xPercent: -50, + yPercent: -50, + skewY: skew, + transformOrigin: 'center center', + zIndex: slot.zIndex, + force3D: true + }); + +const CardSwap: React.FC = ({ + width = 500, + height = 400, + cardDistance = 60, + verticalDistance = 70, + delay = 5000, + pauseOnHover = false, + onCardClick, + skewAmount = 6, + easing = 'elastic', + children +}) => { + const config = + easing === 'elastic' + ? { + ease: 'elastic.out(0.6,0.9)', + durDrop: 2, + durMove: 2, + durReturn: 2, + promoteOverlap: 0.9, + returnDelay: 0.05 + } + : { + ease: 'power1.inOut', + durDrop: 0.8, + durMove: 0.8, + durReturn: 0.8, + promoteOverlap: 0.45, + returnDelay: 0.2 + }; + + const childArr = useMemo(() => Children.toArray(children) as ReactElement[], [children]); + const refs = useMemo(() => childArr.map(() => React.createRef()), [childArr.length]); + + const order = useRef(Array.from({ length: childArr.length }, (_, i) => i)); + + const tlRef = useRef(null); + const intervalRef = useRef(0); + const container = useRef(null); + + useEffect(() => { + const total = refs.length; + refs.forEach((r, i) => placeNow(r.current!, makeSlot(i, cardDistance, verticalDistance, total), skewAmount)); + + const swap = () => { + if (order.current.length < 2) return; + + const [front, ...rest] = order.current; + const elFront = refs[front].current!; + const tl = gsap.timeline(); + tlRef.current = tl; + + tl.to(elFront, { + y: '+=500', + duration: config.durDrop, + ease: config.ease + }); + + tl.addLabel('promote', `-=${config.durDrop * config.promoteOverlap}`); + rest.forEach((idx, i) => { + const el = refs[idx].current!; + const slot = makeSlot(i, cardDistance, verticalDistance, refs.length); + tl.set(el, { zIndex: slot.zIndex }, 'promote'); + tl.to( + el, + { + x: slot.x, + y: slot.y, + z: slot.z, + duration: config.durMove, + ease: config.ease + }, + `promote+=${i * 0.15}` + ); + }); + + const backSlot = makeSlot(refs.length - 1, cardDistance, verticalDistance, refs.length); + tl.addLabel('return', `promote+=${config.durMove * config.returnDelay}`); + tl.call( + () => { + gsap.set(elFront, { zIndex: backSlot.zIndex }); + }, + undefined, + 'return' + ); + tl.to( + elFront, + { + x: backSlot.x, + y: backSlot.y, + z: backSlot.z, + duration: config.durReturn, + ease: config.ease + }, + 'return' + ); + + tl.call(() => { + order.current = [...rest, front]; + }); + }; + + swap(); + intervalRef.current = window.setInterval(swap, delay); + + if (pauseOnHover) { + const node = container.current!; + const pause = () => { + tlRef.current?.pause(); + clearInterval(intervalRef.current); + }; + const resume = () => { + tlRef.current?.play(); + intervalRef.current = window.setInterval(swap, delay); + }; + node.addEventListener('mouseenter', pause); + node.addEventListener('mouseleave', resume); + return () => { + node.removeEventListener('mouseenter', pause); + node.removeEventListener('mouseleave', resume); + clearInterval(intervalRef.current); + }; + } + return () => clearInterval(intervalRef.current); + }, [cardDistance, verticalDistance, delay, pauseOnHover, skewAmount, easing]); + + const rendered = childArr.map((child, i) => + isValidElement(child) + ? cloneElement(child, { + key: i, + ref: refs[i], + style: { width, height, ...(child.props.style ?? {}) }, + onClick: e => { + child.props.onClick?.(e as React.MouseEvent); + onCardClick?.(i); + } + } as CardProps & React.RefAttributes) + : child + ); + + return ( +
+ {rendered} +
+ ); +}; + +export default CardSwap; diff --git a/components/FeaturesSection.tsx b/components/FeaturesSection.tsx index 2b99a49..13073f4 100644 --- a/components/FeaturesSection.tsx +++ b/components/FeaturesSection.tsx @@ -3,7 +3,7 @@ export default function FeaturesSection() {

- Your Shortcut to
+ Your Shortcut to get
Data

@@ -32,7 +32,7 @@ export default function FeaturesSection() { Coins
@@ -50,20 +50,33 @@ export default function FeaturesSection() { Execution

- Buy and sell insights in a few taps. Withdraw rewards in the - asset you choose. + Buy and sell datasets in a few taps. Verify dataset with simple + click.

- Dashboard UI -
- + + {/* Animated Cursor */} +
+ + + +
diff --git a/components/Navbar.tsx b/components/Navbar.tsx index 62dab1f..b8307fe 100644 --- a/components/Navbar.tsx +++ b/components/Navbar.tsx @@ -13,7 +13,7 @@ export default function Navbar() {
- + Home diff --git a/lib/utils.ts b/lib/utils.ts new file mode 100644 index 0000000..bd0c391 --- /dev/null +++ b/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/package.json b/package.json index 5c6c18a..e72a15a 100644 --- a/package.json +++ b/package.json @@ -18,12 +18,18 @@ "@mysten/walrus": "^0.9.0", "@mysten/zksend": "^0.14.12", "@tanstack/react-query": "^5.90.18", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.562.0", "gsap": "^3.14.2", "jwt-decode": "^4.0.0", "motion": "^12.26.2", "next": "16.1.3", "react": "19.2.3", - "react-dom": "19.2.3" + "react-dom": "19.2.3", + "tailwind-merge": "^3.4.0", + "tailwindcss-animate": "^1.0.7", + "tw-animate-css": "^1.4.0" }, "devDependencies": { "@tailwindcss/postcss": "^4", @@ -43,4 +49,4 @@ "sharp", "unrs-resolver" ] -} +} \ No newline at end of file diff --git a/public/seal.svg b/public/seal.svg new file mode 100644 index 0000000..823c934 --- /dev/null +++ b/public/seal.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/walrus.svg b/public/walrus.svg new file mode 100644 index 0000000..aa9c228 --- /dev/null +++ b/public/walrus.svg @@ -0,0 +1 @@ + \ No newline at end of file