From 1c99d21c1979eeb05655099252d929d19224ace9 Mon Sep 17 00:00:00 2001 From: Simon <10131203+gaomeng1900@users.noreply.github.com> Date: Mon, 22 Dec 2025 21:39:27 +0800 Subject: [PATCH] feat(website): add shadcn and magicUI components --- .vscode/settings.json | 1 + eslint.config.js | 7 +- package-lock.json | 914 +++++++++++++++++- packages/website/components.json | 4 +- packages/website/package.json | 8 + packages/website/src/components/ui/alert.tsx | 60 ++ .../components/ui/animated-gradient-text.tsx | 37 + .../src/components/ui/animated-shiny-text.tsx | 38 + .../website/src/components/ui/aurora-text.tsx | 39 + packages/website/src/components/ui/badge.tsx | 37 + packages/website/src/components/ui/button.tsx | 60 ++ .../website/src/components/ui/highlighter.tsx | 92 ++ .../website/src/components/ui/hyper-text.tsx | 140 +++ packages/website/src/components/ui/kbd.tsx | 28 + .../website/src/components/ui/magic-card.tsx | 101 ++ .../src/components/ui/neon-gradient-card.tsx | 136 +++ .../website/src/components/ui/particles.tsx | 297 ++++++ .../website/src/components/ui/separator.tsx | 26 + packages/website/src/components/ui/sonner.tsx | 38 + .../src/components/ui/sparkles-text.tsx | 148 +++ .../website/src/components/ui/spinner.tsx | 16 + packages/website/src/components/ui/switch.tsx | 26 + .../src/components/ui/text-animate.tsx | 417 ++++++++ .../website/src/components/ui/tooltip.tsx | 55 ++ .../src/components/ui/typing-animation.tsx | 164 ++++ packages/website/src/index.css | 68 ++ 26 files changed, 2949 insertions(+), 8 deletions(-) create mode 100644 packages/website/src/components/ui/alert.tsx create mode 100644 packages/website/src/components/ui/animated-gradient-text.tsx create mode 100644 packages/website/src/components/ui/animated-shiny-text.tsx create mode 100644 packages/website/src/components/ui/aurora-text.tsx create mode 100644 packages/website/src/components/ui/badge.tsx create mode 100644 packages/website/src/components/ui/button.tsx create mode 100644 packages/website/src/components/ui/highlighter.tsx create mode 100644 packages/website/src/components/ui/hyper-text.tsx create mode 100644 packages/website/src/components/ui/kbd.tsx create mode 100644 packages/website/src/components/ui/magic-card.tsx create mode 100644 packages/website/src/components/ui/neon-gradient-card.tsx create mode 100644 packages/website/src/components/ui/particles.tsx create mode 100644 packages/website/src/components/ui/separator.tsx create mode 100644 packages/website/src/components/ui/sonner.tsx create mode 100644 packages/website/src/components/ui/sparkles-text.tsx create mode 100644 packages/website/src/components/ui/spinner.tsx create mode 100644 packages/website/src/components/ui/switch.tsx create mode 100644 packages/website/src/components/ui/text-animate.tsx create mode 100644 packages/website/src/components/ui/tooltip.tsx create mode 100644 packages/website/src/components/ui/typing-animation.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index 21e055f..8fc6933 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,6 +8,7 @@ "opensource", "qwen", "retryable", + "shadcn", "wouter" ], "markdownlint.config": { diff --git a/eslint.config.js b/eslint.config.js index c43fa4b..4317c2b 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -8,7 +8,12 @@ import globals from 'globals' import tseslint from 'typescript-eslint' export default defineConfig([ - globalIgnores(['**/dist', '**/test-pages', '**/node_modules']), + globalIgnores([ + '**/dist', + '**/test-pages', + '**/node_modules', + 'packages/website/src/components/ui', + ]), { plugins: { 'react-hooks': reactHooks, diff --git a/package-lock.json b/package-lock.json index 0d1610a..964f624 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1294,6 +1294,44 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", + "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.4" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1576,6 +1614,769 @@ "resolved": "packages/website", "link": true }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-arrow/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-arrow/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.8.tgz", + "integrity": "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", + "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", + "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "license": "MIT" + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.47", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.47.tgz", @@ -2761,7 +3562,7 @@ "version": "19.2.7", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", - "dev": true, + "devOptional": true, "license": "MIT", "peer": true, "dependencies": { @@ -2772,8 +3573,9 @@ "version": "19.2.3", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "dev": true, + "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -3767,7 +4569,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/dargs": { @@ -4508,6 +5310,33 @@ "dev": true, "license": "ISC" }, + "node_modules/framer-motion": { + "version": "12.23.26", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.26.tgz", + "integrity": "sha512-cPcIhgR42xBn1Uj+PzOyheMtZ73H927+uWPDVhUMqxy8UHt6Okavb6xIz9J/phFUHUj0OncR6UvMfJTXoc/LKA==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.23.23", + "motion-utils": "^12.23.6", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fs-extra": { "version": "11.3.2", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", @@ -5695,6 +6524,47 @@ "pathe": "^2.0.1" } }, + "node_modules/motion": { + "version": "12.23.26", + "resolved": "https://registry.npmjs.org/motion/-/motion-12.23.26.tgz", + "integrity": "sha512-Ll8XhVxY8LXMVYTCfme27WH2GjBrCIzY4+ndr5QKxsK+YwCtOi2B/oBi5jcIbik5doXuWT/4KKDOVAZJkeY5VQ==", + "license": "MIT", + "dependencies": { + "framer-motion": "^12.23.26", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/motion-dom": { + "version": "12.23.23", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz", + "integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.23.6" + } + }, + "node_modules/motion-utils": { + "version": "12.23.6", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz", + "integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -5741,6 +6611,16 @@ "dev": true, "license": "MIT" }, + "node_modules/next-themes": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", + "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/node-releases": { "version": "2.0.27", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", @@ -6050,8 +6930,8 @@ "version": "19.2.3", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", - "dev": true, "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -6215,6 +7095,12 @@ "fsevents": "~2.3.2" } }, + "node_modules/rough-notation": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/rough-notation/-/rough-notation-0.5.1.tgz", + "integrity": "sha512-ITHofTzm13cWFVfoGsh/4c/k2Mg8geKgBCwex71UZLnNuw403tCRjYPQ68jSAd37DMbZIePXPjDgY0XdZi9HPw==", + "license": "MIT" + }, "node_modules/rxjs": { "version": "7.8.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", @@ -6229,7 +7115,6 @@ "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "dev": true, "license": "MIT" }, "node_modules/semver": { @@ -6311,6 +7196,16 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, + "node_modules/sonner": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", + "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -6630,7 +7525,6 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, "license": "0BSD" }, "node_modules/tw-animate-css": { @@ -7275,9 +8169,17 @@ "name": "@page-agent/website", "version": "0.0.15", "dependencies": { + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-switch": "^1.2.6", + "@radix-ui/react-tooltip": "^1.2.8", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.562.0", + "motion": "^12.23.26", + "next-themes": "^0.4.6", + "rough-notation": "^0.5.1", + "sonner": "^2.0.7", "tailwind-merge": "^3.4.0" }, "devDependencies": { diff --git a/packages/website/components.json b/packages/website/components.json index 2b0833f..fec6d0e 100644 --- a/packages/website/components.json +++ b/packages/website/components.json @@ -18,5 +18,7 @@ "lib": "@/lib", "hooks": "@/hooks" }, - "registries": {} + "registries": { + "@magicui": "https://magicui.design/r/{name}.json" + } } diff --git a/packages/website/package.json b/packages/website/package.json index eee6b72..072c016 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -24,9 +24,17 @@ "wouter": "^3.8.1" }, "dependencies": { + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-switch": "^1.2.6", + "@radix-ui/react-tooltip": "^1.2.8", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.562.0", + "motion": "^12.23.26", + "next-themes": "^0.4.6", + "rough-notation": "^0.5.1", + "sonner": "^2.0.7", "tailwind-merge": "^3.4.0" } } diff --git a/packages/website/src/components/ui/alert.tsx b/packages/website/src/components/ui/alert.tsx new file mode 100644 index 0000000..c8f7862 --- /dev/null +++ b/packages/website/src/components/ui/alert.tsx @@ -0,0 +1,60 @@ +import { type VariantProps, cva } from 'class-variance-authority' +import * as React from 'react' + +import { cn } from '@/lib/utils' + +const alertVariants = cva( + 'relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current', + { + variants: { + variant: { + default: 'bg-card text-card-foreground', + destructive: + 'text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90', + }, + }, + defaultVariants: { + variant: 'default', + }, + } +) + +function Alert({ + className, + variant, + ...props +}: React.ComponentProps<'div'> & VariantProps) { + return ( +
+ ) +} + +function AlertTitle({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +function AlertDescription({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +export { Alert, AlertTitle, AlertDescription } diff --git a/packages/website/src/components/ui/animated-gradient-text.tsx b/packages/website/src/components/ui/animated-gradient-text.tsx new file mode 100644 index 0000000..8e2de7c --- /dev/null +++ b/packages/website/src/components/ui/animated-gradient-text.tsx @@ -0,0 +1,37 @@ +import { ComponentPropsWithoutRef } from 'react' + +import { cn } from '@/lib/utils' + +export interface AnimatedGradientTextProps extends ComponentPropsWithoutRef<'div'> { + speed?: number + colorFrom?: string + colorTo?: string +} + +export function AnimatedGradientText({ + children, + className, + speed = 1, + colorFrom = '#ffaa40', + colorTo = '#9c40ff', + ...props +}: AnimatedGradientTextProps) { + return ( + + {children} + + ) +} diff --git a/packages/website/src/components/ui/animated-shiny-text.tsx b/packages/website/src/components/ui/animated-shiny-text.tsx new file mode 100644 index 0000000..899baae --- /dev/null +++ b/packages/website/src/components/ui/animated-shiny-text.tsx @@ -0,0 +1,38 @@ +import { CSSProperties, ComponentPropsWithoutRef, FC } from 'react' + +import { cn } from '@/lib/utils' + +export interface AnimatedShinyTextProps extends ComponentPropsWithoutRef<'span'> { + shimmerWidth?: number +} + +export const AnimatedShinyText: FC = ({ + children, + className, + shimmerWidth = 100, + ...props +}) => { + return ( + + {children} + + ) +} diff --git a/packages/website/src/components/ui/aurora-text.tsx b/packages/website/src/components/ui/aurora-text.tsx new file mode 100644 index 0000000..9a537d0 --- /dev/null +++ b/packages/website/src/components/ui/aurora-text.tsx @@ -0,0 +1,39 @@ +import React, { memo } from 'react' + +interface AuroraTextProps { + children: React.ReactNode + className?: string + colors?: string[] + speed?: number +} + +export const AuroraText = memo( + ({ + children, + className = '', + colors = ['#FF0080', '#7928CA', '#0070F3', '#38bdf8'], + speed = 1, + }: AuroraTextProps) => { + const gradientStyle = { + backgroundImage: `linear-gradient(135deg, ${colors.join(', ')}, ${colors[0]})`, + WebkitBackgroundClip: 'text', + WebkitTextFillColor: 'transparent', + animationDuration: `${10 / speed}s`, + } + + return ( + + {children} + + + ) + } +) + +AuroraText.displayName = 'AuroraText' diff --git a/packages/website/src/components/ui/badge.tsx b/packages/website/src/components/ui/badge.tsx new file mode 100644 index 0000000..4072656 --- /dev/null +++ b/packages/website/src/components/ui/badge.tsx @@ -0,0 +1,37 @@ +import { Slot } from '@radix-ui/react-slot' +import { type VariantProps, cva } from 'class-variance-authority' +import * as React from 'react' + +import { cn } from '@/lib/utils' + +const badgeVariants = cva( + 'inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden', + { + variants: { + variant: { + default: 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90', + secondary: + 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90', + destructive: + 'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60', + outline: 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground', + }, + }, + defaultVariants: { + variant: 'default', + }, + } +) + +function Badge({ + className, + variant, + asChild = false, + ...props +}: React.ComponentProps<'span'> & VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot : 'span' + + return +} + +export { Badge, badgeVariants } diff --git a/packages/website/src/components/ui/button.tsx b/packages/website/src/components/ui/button.tsx new file mode 100644 index 0000000..3579cbd --- /dev/null +++ b/packages/website/src/components/ui/button.tsx @@ -0,0 +1,60 @@ +import { Slot } from '@radix-ui/react-slot' +import { type VariantProps, cva } from 'class-variance-authority' +import * as React from 'react' + +import { cn } from '@/lib/utils' + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + { + variants: { + variant: { + default: 'bg-primary text-primary-foreground hover:bg-primary/90', + destructive: + 'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60', + outline: + 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50', + secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', + ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50', + link: 'text-primary underline-offset-4 hover:underline', + }, + size: { + default: 'h-9 px-4 py-2 has-[>svg]:px-3', + sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5', + lg: 'h-10 rounded-md px-6 has-[>svg]:px-4', + icon: 'size-9', + 'icon-sm': 'size-8', + 'icon-lg': 'size-10', + }, + }, + defaultVariants: { + variant: 'default', + size: 'default', + }, + } +) + +function Button({ + className, + variant = 'default', + size = 'default', + asChild = false, + ...props +}: React.ComponentProps<'button'> & + VariantProps & { + asChild?: boolean + }) { + const Comp = asChild ? Slot : 'button' + + return ( + + ) +} + +export { Button, buttonVariants } diff --git a/packages/website/src/components/ui/highlighter.tsx b/packages/website/src/components/ui/highlighter.tsx new file mode 100644 index 0000000..b0651f5 --- /dev/null +++ b/packages/website/src/components/ui/highlighter.tsx @@ -0,0 +1,92 @@ +import { useInView } from 'motion/react' +import { useEffect, useRef } from 'react' +import type React from 'react' +import { annotate } from 'rough-notation' +import { type RoughAnnotation } from 'rough-notation/lib/model' + +type AnnotationAction = + | 'highlight' + | 'underline' + | 'box' + | 'circle' + | 'strike-through' + | 'crossed-off' + | 'bracket' + +interface HighlighterProps { + children: React.ReactNode + action?: AnnotationAction + color?: string + strokeWidth?: number + animationDuration?: number + iterations?: number + padding?: number + multiline?: boolean + isView?: boolean +} + +export function Highlighter({ + children, + action = 'highlight', + color = '#ffd1dc', + strokeWidth = 1.5, + animationDuration = 600, + iterations = 2, + padding = 2, + multiline = true, + isView = false, +}: HighlighterProps) { + const elementRef = useRef(null) + const annotationRef = useRef(null) + + const isInView = useInView(elementRef, { + once: true, + margin: '-10%', + }) + + // If isView is false, always show. If isView is true, wait for inView + const shouldShow = !isView || isInView + + useEffect(() => { + if (!shouldShow) return + + const element = elementRef.current + if (!element) return + + const annotationConfig = { + type: action, + color, + strokeWidth, + animationDuration, + iterations, + padding, + multiline, + } + + const annotation = annotate(element, annotationConfig) + + annotationRef.current = annotation + annotationRef.current.show() + + const resizeObserver = new ResizeObserver(() => { + annotation.hide() + annotation.show() + }) + + resizeObserver.observe(element) + resizeObserver.observe(document.body) + + return () => { + if (element) { + annotate(element, { type: action }).remove() + resizeObserver.disconnect() + } + } + }, [shouldShow, action, color, strokeWidth, animationDuration, iterations, padding, multiline]) + + return ( + + {children} + + ) +} diff --git a/packages/website/src/components/ui/hyper-text.tsx b/packages/website/src/components/ui/hyper-text.tsx new file mode 100644 index 0000000..10cf166 --- /dev/null +++ b/packages/website/src/components/ui/hyper-text.tsx @@ -0,0 +1,140 @@ +import { AnimatePresence, MotionProps, motion } from 'motion/react' +import { useEffect, useRef, useState } from 'react' + +import { cn } from '@/lib/utils' + +type CharacterSet = string[] | readonly string[] + +interface HyperTextProps extends MotionProps { + /** The text content to be animated */ + children: string + /** Optional className for styling */ + className?: string + /** Duration of the animation in milliseconds */ + duration?: number + /** Delay before animation starts in milliseconds */ + delay?: number + /** Component to render as - defaults to div */ + as?: React.ElementType + /** Whether to start animation when element comes into view */ + startOnView?: boolean + /** Whether to trigger animation on hover */ + animateOnHover?: boolean + /** Custom character set for scramble effect. Defaults to uppercase alphabet */ + characterSet?: CharacterSet +} + +const DEFAULT_CHARACTER_SET = Object.freeze( + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('') +) as readonly string[] + +const getRandomInt = (max: number): number => Math.floor(Math.random() * max) + +export function HyperText({ + children, + className, + duration = 800, + delay = 0, + as: Component = 'div', + startOnView = false, + animateOnHover = true, + characterSet = DEFAULT_CHARACTER_SET, + ...props +}: HyperTextProps) { + const MotionComponent = motion.create(Component, { + forwardMotionProps: true, + }) + + const [displayText, setDisplayText] = useState(() => children.split('')) + const [isAnimating, setIsAnimating] = useState(false) + const iterationCount = useRef(0) + const elementRef = useRef(null) + + const handleAnimationTrigger = () => { + if (animateOnHover && !isAnimating) { + iterationCount.current = 0 + setIsAnimating(true) + } + } + + // Handle animation start based on view or delay + useEffect(() => { + if (!startOnView) { + const startTimeout = setTimeout(() => { + setIsAnimating(true) + }, delay) + return () => clearTimeout(startTimeout) + } + + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setTimeout(() => { + setIsAnimating(true) + }, delay) + observer.disconnect() + } + }, + { threshold: 0.1, rootMargin: '-30% 0px -30% 0px' } + ) + + if (elementRef.current) { + observer.observe(elementRef.current) + } + + return () => observer.disconnect() + }, [delay, startOnView]) + + // Handle scramble animation + useEffect(() => { + if (!isAnimating) return + + const maxIterations = children.length + const startTime = performance.now() + let animationFrameId: number + + const animate = (currentTime: number) => { + const elapsed = currentTime - startTime + const progress = Math.min(elapsed / duration, 1) + + iterationCount.current = progress * maxIterations + + setDisplayText((currentText) => + currentText.map((letter, index) => + letter === ' ' + ? letter + : index <= iterationCount.current + ? children[index] + : characterSet[getRandomInt(characterSet.length)] + ) + ) + + if (progress < 1) { + animationFrameId = requestAnimationFrame(animate) + } else { + setIsAnimating(false) + } + } + + animationFrameId = requestAnimationFrame(animate) + + return () => cancelAnimationFrame(animationFrameId) + }, [children, duration, isAnimating, characterSet]) + + return ( + + + {displayText.map((letter, index) => ( + + {letter.toUpperCase()} + + ))} + + + ) +} diff --git a/packages/website/src/components/ui/kbd.tsx b/packages/website/src/components/ui/kbd.tsx new file mode 100644 index 0000000..b5468bd --- /dev/null +++ b/packages/website/src/components/ui/kbd.tsx @@ -0,0 +1,28 @@ +import { cn } from '@/lib/utils' + +function Kbd({ className, ...props }: React.ComponentProps<'kbd'>) { + return ( + + ) +} + +function KbdGroup({ className, ...props }: React.ComponentProps<'div'>) { + return ( + + ) +} + +export { Kbd, KbdGroup } diff --git a/packages/website/src/components/ui/magic-card.tsx b/packages/website/src/components/ui/magic-card.tsx new file mode 100644 index 0000000..b44dcf8 --- /dev/null +++ b/packages/website/src/components/ui/magic-card.tsx @@ -0,0 +1,101 @@ +import { motion, useMotionTemplate, useMotionValue } from 'motion/react' +import React, { useCallback, useEffect } from 'react' + +import { cn } from '@/lib/utils' + +interface MagicCardProps { + children?: React.ReactNode + className?: string + gradientSize?: number + gradientColor?: string + gradientOpacity?: number + gradientFrom?: string + gradientTo?: string +} + +export function MagicCard({ + children, + className, + gradientSize = 200, + gradientColor = '#262626', + gradientOpacity = 0.8, + gradientFrom = '#9E7AFF', + gradientTo = '#FE8BBB', +}: MagicCardProps) { + const mouseX = useMotionValue(-gradientSize) + const mouseY = useMotionValue(-gradientSize) + const reset = useCallback(() => { + mouseX.set(-gradientSize) + mouseY.set(-gradientSize) + }, [gradientSize, mouseX, mouseY]) + + const handlePointerMove = useCallback( + (e: React.PointerEvent) => { + const rect = e.currentTarget.getBoundingClientRect() + mouseX.set(e.clientX - rect.left) + mouseY.set(e.clientY - rect.top) + }, + [mouseX, mouseY] + ) + + useEffect(() => { + reset() + }, [reset]) + + useEffect(() => { + const handleGlobalPointerOut = (e: PointerEvent) => { + if (!e.relatedTarget) { + reset() + } + } + + const handleVisibility = () => { + if (document.visibilityState !== 'visible') { + reset() + } + } + + window.addEventListener('pointerout', handleGlobalPointerOut) + window.addEventListener('blur', reset) + document.addEventListener('visibilitychange', handleVisibility) + + return () => { + window.removeEventListener('pointerout', handleGlobalPointerOut) + window.removeEventListener('blur', reset) + document.removeEventListener('visibilitychange', handleVisibility) + } + }, [reset]) + + return ( +
+ +
+ +
{children}
+
+ ) +} diff --git a/packages/website/src/components/ui/neon-gradient-card.tsx b/packages/website/src/components/ui/neon-gradient-card.tsx new file mode 100644 index 0000000..cf62fd7 --- /dev/null +++ b/packages/website/src/components/ui/neon-gradient-card.tsx @@ -0,0 +1,136 @@ +import { CSSProperties, ReactElement, ReactNode, useEffect, useRef, useState } from 'react' + +import { cn } from '@/lib/utils' + +interface NeonColorsProps { + firstColor: string + secondColor: string +} + +interface NeonGradientCardProps extends React.HTMLAttributes { + /** + * @default
+ * @type ReactElement + * @description + * The component to be rendered as the card + * */ + as?: ReactElement + /** + * @default "" + * @type string + * @description + * The className of the card + */ + className?: string + + /** + * @default "" + * @type ReactNode + * @description + * The children of the card + * */ + children?: ReactNode + + /** + * @default 5 + * @type number + * @description + * The size of the border in pixels + * */ + borderSize?: number + + /** + * @default 20 + * @type number + * @description + * The size of the radius in pixels + * */ + borderRadius?: number + + /** + * @default "{ firstColor: '#ff00aa', secondColor: '#00FFF1' }" + * @type string + * @description + * The colors of the neon gradient + * */ + neonColors?: NeonColorsProps +} + +export const NeonGradientCard: React.FC = ({ + className, + children, + borderSize = 2, + borderRadius = 20, + neonColors = { + firstColor: '#ff00aa', + secondColor: '#00FFF1', + }, + ...props +}) => { + const containerRef = useRef(null) + const [dimensions, setDimensions] = useState({ width: 0, height: 0 }) + + useEffect(() => { + const updateDimensions = () => { + if (containerRef.current) { + const { offsetWidth, offsetHeight } = containerRef.current + setDimensions({ width: offsetWidth, height: offsetHeight }) + } + } + + updateDimensions() + window.addEventListener('resize', updateDimensions) + + return () => { + window.removeEventListener('resize', updateDimensions) + } + }, []) + + useEffect(() => { + if (containerRef.current) { + const { offsetWidth, offsetHeight } = containerRef.current + setDimensions({ width: offsetWidth, height: offsetHeight }) + } + }, [children]) + + return ( +
+
+ {children} +
+
+ ) +} diff --git a/packages/website/src/components/ui/particles.tsx b/packages/website/src/components/ui/particles.tsx new file mode 100644 index 0000000..9a13df6 --- /dev/null +++ b/packages/website/src/components/ui/particles.tsx @@ -0,0 +1,297 @@ +import React, { ComponentPropsWithoutRef, useEffect, useRef, useState } from 'react' + +import { cn } from '@/lib/utils' + +interface MousePosition { + x: number + y: number +} + +function MousePosition(): MousePosition { + const [mousePosition, setMousePosition] = useState({ + x: 0, + y: 0, + }) + + useEffect(() => { + const handleMouseMove = (event: MouseEvent) => { + setMousePosition({ x: event.clientX, y: event.clientY }) + } + + window.addEventListener('mousemove', handleMouseMove) + + return () => { + window.removeEventListener('mousemove', handleMouseMove) + } + }, []) + + return mousePosition +} + +interface ParticlesProps extends ComponentPropsWithoutRef<'div'> { + className?: string + quantity?: number + staticity?: number + ease?: number + size?: number + refresh?: boolean + color?: string + vx?: number + vy?: number +} + +function hexToRgb(hex: string): number[] { + hex = hex.replace('#', '') + + if (hex.length === 3) { + hex = hex + .split('') + .map((char) => char + char) + .join('') + } + + const hexInt = parseInt(hex, 16) + const red = (hexInt >> 16) & 255 + const green = (hexInt >> 8) & 255 + const blue = hexInt & 255 + return [red, green, blue] +} + +type Circle = { + x: number + y: number + translateX: number + translateY: number + size: number + alpha: number + targetAlpha: number + dx: number + dy: number + magnetism: number +} + +export const Particles: React.FC = ({ + className = '', + quantity = 100, + staticity = 50, + ease = 50, + size = 0.4, + refresh = false, + color = '#ffffff', + vx = 0, + vy = 0, + ...props +}) => { + const canvasRef = useRef(null) + const canvasContainerRef = useRef(null) + const context = useRef(null) + const circles = useRef([]) + const mousePosition = MousePosition() + const mouse = useRef<{ x: number; y: number }>({ x: 0, y: 0 }) + const canvasSize = useRef<{ w: number; h: number }>({ w: 0, h: 0 }) + const dpr = typeof window !== 'undefined' ? window.devicePixelRatio : 1 + const rafID = useRef(null) + const resizeTimeout = useRef(null) + + useEffect(() => { + if (canvasRef.current) { + context.current = canvasRef.current.getContext('2d') + } + initCanvas() + animate() + + const handleResize = () => { + if (resizeTimeout.current) { + clearTimeout(resizeTimeout.current) + } + resizeTimeout.current = setTimeout(() => { + initCanvas() + }, 200) + } + + window.addEventListener('resize', handleResize) + + return () => { + if (rafID.current != null) { + window.cancelAnimationFrame(rafID.current) + } + if (resizeTimeout.current) { + clearTimeout(resizeTimeout.current) + } + window.removeEventListener('resize', handleResize) + } + }, [color]) + + useEffect(() => { + onMouseMove() + }, [mousePosition.x, mousePosition.y]) + + useEffect(() => { + initCanvas() + }, [refresh]) + + const initCanvas = () => { + resizeCanvas() + drawParticles() + } + + const onMouseMove = () => { + if (canvasRef.current) { + const rect = canvasRef.current.getBoundingClientRect() + const { w, h } = canvasSize.current + const x = mousePosition.x - rect.left - w / 2 + const y = mousePosition.y - rect.top - h / 2 + const inside = x < w / 2 && x > -w / 2 && y < h / 2 && y > -h / 2 + if (inside) { + mouse.current.x = x + mouse.current.y = y + } + } + } + + const resizeCanvas = () => { + if (canvasContainerRef.current && canvasRef.current && context.current) { + canvasSize.current.w = canvasContainerRef.current.offsetWidth + canvasSize.current.h = canvasContainerRef.current.offsetHeight + + canvasRef.current.width = canvasSize.current.w * dpr + canvasRef.current.height = canvasSize.current.h * dpr + canvasRef.current.style.width = `${canvasSize.current.w}px` + canvasRef.current.style.height = `${canvasSize.current.h}px` + context.current.scale(dpr, dpr) + + // Clear existing particles and create new ones with exact quantity + circles.current = [] + for (let i = 0; i < quantity; i++) { + const circle = circleParams() + drawCircle(circle) + } + } + } + + const circleParams = (): Circle => { + const x = Math.floor(Math.random() * canvasSize.current.w) + const y = Math.floor(Math.random() * canvasSize.current.h) + const translateX = 0 + const translateY = 0 + const pSize = Math.floor(Math.random() * 2) + size + const alpha = 0 + const targetAlpha = parseFloat((Math.random() * 0.6 + 0.1).toFixed(1)) + const dx = (Math.random() - 0.5) * 0.1 + const dy = (Math.random() - 0.5) * 0.1 + const magnetism = 0.1 + Math.random() * 4 + return { + x, + y, + translateX, + translateY, + size: pSize, + alpha, + targetAlpha, + dx, + dy, + magnetism, + } + } + + const rgb = hexToRgb(color) + + const drawCircle = (circle: Circle, update = false) => { + if (context.current) { + const { x, y, translateX, translateY, size, alpha } = circle + context.current.translate(translateX, translateY) + context.current.beginPath() + context.current.arc(x, y, size, 0, 2 * Math.PI) + context.current.fillStyle = `rgba(${rgb.join(', ')}, ${alpha})` + context.current.fill() + context.current.setTransform(dpr, 0, 0, dpr, 0, 0) + + if (!update) { + circles.current.push(circle) + } + } + } + + const clearContext = () => { + if (context.current) { + context.current.clearRect(0, 0, canvasSize.current.w, canvasSize.current.h) + } + } + + const drawParticles = () => { + clearContext() + const particleCount = quantity + for (let i = 0; i < particleCount; i++) { + const circle = circleParams() + drawCircle(circle) + } + } + + const remapValue = ( + value: number, + start1: number, + end1: number, + start2: number, + end2: number + ): number => { + const remapped = ((value - start1) * (end2 - start2)) / (end1 - start1) + start2 + return remapped > 0 ? remapped : 0 + } + + const animate = () => { + clearContext() + circles.current.forEach((circle: Circle, i: number) => { + // Handle the alpha value + const edge = [ + circle.x + circle.translateX - circle.size, // distance from left edge + canvasSize.current.w - circle.x - circle.translateX - circle.size, // distance from right edge + circle.y + circle.translateY - circle.size, // distance from top edge + canvasSize.current.h - circle.y - circle.translateY - circle.size, // distance from bottom edge + ] + const closestEdge = edge.reduce((a, b) => Math.min(a, b)) + const remapClosestEdge = parseFloat(remapValue(closestEdge, 0, 20, 0, 1).toFixed(2)) + if (remapClosestEdge > 1) { + circle.alpha += 0.02 + if (circle.alpha > circle.targetAlpha) { + circle.alpha = circle.targetAlpha + } + } else { + circle.alpha = circle.targetAlpha * remapClosestEdge + } + circle.x += circle.dx + vx + circle.y += circle.dy + vy + circle.translateX += + (mouse.current.x / (staticity / circle.magnetism) - circle.translateX) / ease + circle.translateY += + (mouse.current.y / (staticity / circle.magnetism) - circle.translateY) / ease + + drawCircle(circle, true) + + // circle gets out of the canvas + if ( + circle.x < -circle.size || + circle.x > canvasSize.current.w + circle.size || + circle.y < -circle.size || + circle.y > canvasSize.current.h + circle.size + ) { + // remove the circle from the array + circles.current.splice(i, 1) + // create a new circle + const newCircle = circleParams() + drawCircle(newCircle) + } + }) + rafID.current = window.requestAnimationFrame(animate) + } + + return ( + + ) +} diff --git a/packages/website/src/components/ui/separator.tsx b/packages/website/src/components/ui/separator.tsx new file mode 100644 index 0000000..ae51953 --- /dev/null +++ b/packages/website/src/components/ui/separator.tsx @@ -0,0 +1,26 @@ +import * as SeparatorPrimitive from '@radix-ui/react-separator' +import * as React from 'react' + +import { cn } from '@/lib/utils' + +function Separator({ + className, + orientation = 'horizontal', + decorative = true, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Separator } diff --git a/packages/website/src/components/ui/sonner.tsx b/packages/website/src/components/ui/sonner.tsx new file mode 100644 index 0000000..21835dc --- /dev/null +++ b/packages/website/src/components/ui/sonner.tsx @@ -0,0 +1,38 @@ +import { + CircleCheckIcon, + InfoIcon, + Loader2Icon, + OctagonXIcon, + TriangleAlertIcon, +} from 'lucide-react' +import { useTheme } from 'next-themes' +import { Toaster as Sonner, type ToasterProps } from 'sonner' + +const Toaster = ({ ...props }: ToasterProps) => { + const { theme = 'system' } = useTheme() + + return ( + , + info: , + warning: , + error: , + loading: , + }} + style={ + { + '--normal-bg': 'var(--popover)', + '--normal-text': 'var(--popover-foreground)', + '--normal-border': 'var(--border)', + '--border-radius': 'var(--radius)', + } as React.CSSProperties + } + {...props} + /> + ) +} + +export { Toaster } diff --git a/packages/website/src/components/ui/sparkles-text.tsx b/packages/website/src/components/ui/sparkles-text.tsx new file mode 100644 index 0000000..4bf3b9a --- /dev/null +++ b/packages/website/src/components/ui/sparkles-text.tsx @@ -0,0 +1,148 @@ +import { motion } from 'motion/react' +import { CSSProperties, ReactElement, useEffect, useState } from 'react' + +import { cn } from '@/lib/utils' + +interface Sparkle { + id: string + x: string + y: string + color: string + delay: number + scale: number + lifespan: number +} + +const Sparkle: React.FC = ({ id, x, y, color, delay, scale }) => { + return ( + + + + ) +} + +interface SparklesTextProps { + /** + * @default
+ * @type ReactElement + * @description + * The component to be rendered as the text + * */ + as?: ReactElement + + /** + * @default "" + * @type string + * @description + * The className of the text + */ + className?: string + + /** + * @required + * @type ReactNode + * @description + * The content to be displayed + * */ + children: React.ReactNode + + /** + * @default 10 + * @type number + * @description + * The count of sparkles + * */ + sparklesCount?: number + + /** + * @default "{first: '#9E7AFF', second: '#FE8BBB'}" + * @type string + * @description + * The colors of the sparkles + * */ + colors?: { + first: string + second: string + } +} + +export const SparklesText: React.FC = ({ + children, + colors = { first: '#9E7AFF', second: '#FE8BBB' }, + className, + sparklesCount = 10, + ...props +}) => { + const [sparkles, setSparkles] = useState([]) + + useEffect(() => { + const generateStar = (): Sparkle => { + const starX = `${Math.random() * 100}%` + const starY = `${Math.random() * 100}%` + const color = Math.random() > 0.5 ? colors.first : colors.second + const delay = Math.random() * 2 + const scale = Math.random() * 1 + 0.3 + const lifespan = Math.random() * 10 + 5 + const id = `${starX}-${starY}-${Date.now()}` + return { id, x: starX, y: starY, color, delay, scale, lifespan } + } + + const initializeStars = () => { + const newSparkles = Array.from({ length: sparklesCount }, generateStar) + setSparkles(newSparkles) + } + + const updateStars = () => { + setSparkles((currentSparkles) => + currentSparkles.map((star) => { + if (star.lifespan <= 0) { + return generateStar() + } else { + return { ...star, lifespan: star.lifespan - 0.1 } + } + }) + ) + } + + initializeStars() + const interval = setInterval(updateStars, 100) + + return () => clearInterval(interval) + }, [colors.first, colors.second, sparklesCount]) + + return ( +
+ + {sparkles.map((sparkle) => ( + + ))} + {children} + +
+ ) +} diff --git a/packages/website/src/components/ui/spinner.tsx b/packages/website/src/components/ui/spinner.tsx new file mode 100644 index 0000000..b45047c --- /dev/null +++ b/packages/website/src/components/ui/spinner.tsx @@ -0,0 +1,16 @@ +import { Loader2Icon } from 'lucide-react' + +import { cn } from '@/lib/utils' + +function Spinner({ className, ...props }: React.ComponentProps<'svg'>) { + return ( + + ) +} + +export { Spinner } diff --git a/packages/website/src/components/ui/switch.tsx b/packages/website/src/components/ui/switch.tsx new file mode 100644 index 0000000..8c29424 --- /dev/null +++ b/packages/website/src/components/ui/switch.tsx @@ -0,0 +1,26 @@ +import * as SwitchPrimitive from '@radix-ui/react-switch' +import * as React from 'react' + +import { cn } from '@/lib/utils' + +function Switch({ className, ...props }: React.ComponentProps) { + return ( + + + + ) +} + +export { Switch } diff --git a/packages/website/src/components/ui/text-animate.tsx b/packages/website/src/components/ui/text-animate.tsx new file mode 100644 index 0000000..ae7ef9b --- /dev/null +++ b/packages/website/src/components/ui/text-animate.tsx @@ -0,0 +1,417 @@ +import { AnimatePresence, MotionProps, Variants, motion } from 'motion/react' +import { ElementType, memo } from 'react' + +import { cn } from '@/lib/utils' + +type AnimationType = 'text' | 'word' | 'character' | 'line' +type AnimationVariant = + | 'fadeIn' + | 'blurIn' + | 'blurInUp' + | 'blurInDown' + | 'slideUp' + | 'slideDown' + | 'slideLeft' + | 'slideRight' + | 'scaleUp' + | 'scaleDown' + +interface TextAnimateProps extends MotionProps { + /** + * The text content to animate + */ + children: string + /** + * The class name to be applied to the component + */ + className?: string + /** + * The class name to be applied to each segment + */ + segmentClassName?: string + /** + * The delay before the animation starts + */ + delay?: number + /** + * The duration of the animation + */ + duration?: number + /** + * Custom motion variants for the animation + */ + variants?: Variants + /** + * The element type to render + */ + as?: ElementType + /** + * How to split the text ("text", "word", "character") + */ + by?: AnimationType + /** + * Whether to start animation when component enters viewport + */ + startOnView?: boolean + /** + * Whether to animate only once + */ + once?: boolean + /** + * The animation preset to use + */ + animation?: AnimationVariant + /** + * Whether to enable accessibility features (default: true) + */ + accessible?: boolean +} + +const staggerTimings: Record = { + text: 0.06, + word: 0.05, + character: 0.03, + line: 0.06, +} + +const defaultContainerVariants = { + hidden: { opacity: 1 }, + show: { + opacity: 1, + transition: { + delayChildren: 0, + staggerChildren: 0.05, + }, + }, + exit: { + opacity: 0, + transition: { + staggerChildren: 0.05, + staggerDirection: -1, + }, + }, +} + +const defaultItemVariants: Variants = { + hidden: { opacity: 0 }, + show: { + opacity: 1, + }, + exit: { + opacity: 0, + }, +} + +const defaultItemAnimationVariants: Record< + AnimationVariant, + { container: Variants; item: Variants } +> = { + fadeIn: { + container: defaultContainerVariants, + item: { + hidden: { opacity: 0, y: 20 }, + show: { + opacity: 1, + y: 0, + transition: { + duration: 0.3, + }, + }, + exit: { + opacity: 0, + y: 20, + transition: { duration: 0.3 }, + }, + }, + }, + blurIn: { + container: defaultContainerVariants, + item: { + hidden: { opacity: 0, filter: 'blur(10px)' }, + show: { + opacity: 1, + filter: 'blur(0px)', + transition: { + duration: 0.3, + }, + }, + exit: { + opacity: 0, + filter: 'blur(10px)', + transition: { duration: 0.3 }, + }, + }, + }, + blurInUp: { + container: defaultContainerVariants, + item: { + hidden: { opacity: 0, filter: 'blur(10px)', y: 20 }, + show: { + opacity: 1, + filter: 'blur(0px)', + y: 0, + transition: { + y: { duration: 0.3 }, + opacity: { duration: 0.4 }, + filter: { duration: 0.3 }, + }, + }, + exit: { + opacity: 0, + filter: 'blur(10px)', + y: 20, + transition: { + y: { duration: 0.3 }, + opacity: { duration: 0.4 }, + filter: { duration: 0.3 }, + }, + }, + }, + }, + blurInDown: { + container: defaultContainerVariants, + item: { + hidden: { opacity: 0, filter: 'blur(10px)', y: -20 }, + show: { + opacity: 1, + filter: 'blur(0px)', + y: 0, + transition: { + y: { duration: 0.3 }, + opacity: { duration: 0.4 }, + filter: { duration: 0.3 }, + }, + }, + }, + }, + slideUp: { + container: defaultContainerVariants, + item: { + hidden: { y: 20, opacity: 0 }, + show: { + y: 0, + opacity: 1, + transition: { + duration: 0.3, + }, + }, + exit: { + y: -20, + opacity: 0, + transition: { + duration: 0.3, + }, + }, + }, + }, + slideDown: { + container: defaultContainerVariants, + item: { + hidden: { y: -20, opacity: 0 }, + show: { + y: 0, + opacity: 1, + transition: { duration: 0.3 }, + }, + exit: { + y: 20, + opacity: 0, + transition: { duration: 0.3 }, + }, + }, + }, + slideLeft: { + container: defaultContainerVariants, + item: { + hidden: { x: 20, opacity: 0 }, + show: { + x: 0, + opacity: 1, + transition: { duration: 0.3 }, + }, + exit: { + x: -20, + opacity: 0, + transition: { duration: 0.3 }, + }, + }, + }, + slideRight: { + container: defaultContainerVariants, + item: { + hidden: { x: -20, opacity: 0 }, + show: { + x: 0, + opacity: 1, + transition: { duration: 0.3 }, + }, + exit: { + x: 20, + opacity: 0, + transition: { duration: 0.3 }, + }, + }, + }, + scaleUp: { + container: defaultContainerVariants, + item: { + hidden: { scale: 0.5, opacity: 0 }, + show: { + scale: 1, + opacity: 1, + transition: { + duration: 0.3, + scale: { + type: 'spring', + damping: 15, + stiffness: 300, + }, + }, + }, + exit: { + scale: 0.5, + opacity: 0, + transition: { duration: 0.3 }, + }, + }, + }, + scaleDown: { + container: defaultContainerVariants, + item: { + hidden: { scale: 1.5, opacity: 0 }, + show: { + scale: 1, + opacity: 1, + transition: { + duration: 0.3, + scale: { + type: 'spring', + damping: 15, + stiffness: 300, + }, + }, + }, + exit: { + scale: 1.5, + opacity: 0, + transition: { duration: 0.3 }, + }, + }, + }, +} + +const TextAnimateBase = ({ + children, + delay = 0, + duration = 0.3, + variants, + className, + segmentClassName, + as: Component = 'p', + startOnView = true, + once = false, + by = 'word', + animation = 'fadeIn', + accessible = true, + ...props +}: TextAnimateProps) => { + const MotionComponent = motion.create(Component) + + let segments: string[] = [] + switch (by) { + case 'word': + segments = children.split(/(\s+)/) + break + case 'character': + segments = children.split('') + break + case 'line': + segments = children.split('\n') + break + case 'text': + default: + segments = [children] + break + } + + const finalVariants = variants + ? { + container: { + hidden: { opacity: 0 }, + show: { + opacity: 1, + transition: { + opacity: { duration: 0.01, delay }, + delayChildren: delay, + staggerChildren: duration / segments.length, + }, + }, + exit: { + opacity: 0, + transition: { + staggerChildren: duration / segments.length, + staggerDirection: -1, + }, + }, + }, + item: variants, + } + : animation + ? { + container: { + ...defaultItemAnimationVariants[animation].container, + show: { + ...defaultItemAnimationVariants[animation].container.show, + transition: { + delayChildren: delay, + staggerChildren: duration / segments.length, + }, + }, + exit: { + ...defaultItemAnimationVariants[animation].container.exit, + transition: { + staggerChildren: duration / segments.length, + staggerDirection: -1, + }, + }, + }, + item: defaultItemAnimationVariants[animation].item, + } + : { container: defaultContainerVariants, item: defaultItemVariants } + + return ( + + + {accessible && {children}} + {segments.map((segment, i) => ( + + {segment} + + ))} + + + ) +} + +// Export the memoized version +export const TextAnimate = memo(TextAnimateBase) diff --git a/packages/website/src/components/ui/tooltip.tsx b/packages/website/src/components/ui/tooltip.tsx new file mode 100644 index 0000000..7781c0e --- /dev/null +++ b/packages/website/src/components/ui/tooltip.tsx @@ -0,0 +1,55 @@ +import * as TooltipPrimitive from '@radix-ui/react-tooltip' +import * as React from 'react' + +import { cn } from '@/lib/utils' + +function TooltipProvider({ + delayDuration = 0, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function Tooltip({ ...props }: React.ComponentProps) { + return ( + + + + ) +} + +function TooltipTrigger({ ...props }: React.ComponentProps) { + return +} + +function TooltipContent({ + className, + sideOffset = 0, + children, + ...props +}: React.ComponentProps) { + return ( + + + {children} + + + + ) +} + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } diff --git a/packages/website/src/components/ui/typing-animation.tsx b/packages/website/src/components/ui/typing-animation.tsx new file mode 100644 index 0000000..f9898df --- /dev/null +++ b/packages/website/src/components/ui/typing-animation.tsx @@ -0,0 +1,164 @@ +import { MotionProps, motion, useInView } from 'motion/react' +import { useEffect, useMemo, useRef, useState } from 'react' + +import { cn } from '@/lib/utils' + +interface TypingAnimationProps extends MotionProps { + children?: string + words?: string[] + className?: string + duration?: number + typeSpeed?: number + deleteSpeed?: number + delay?: number + pauseDelay?: number + loop?: boolean + as?: React.ElementType + startOnView?: boolean + showCursor?: boolean + blinkCursor?: boolean + cursorStyle?: 'line' | 'block' | 'underscore' +} + +export function TypingAnimation({ + children, + words, + className, + duration = 100, + typeSpeed, + deleteSpeed, + delay = 0, + pauseDelay = 1000, + loop = false, + as: Component = 'span', + startOnView = true, + showCursor = true, + blinkCursor = true, + cursorStyle = 'line', + ...props +}: TypingAnimationProps) { + const MotionComponent = motion.create(Component, { + forwardMotionProps: true, + }) + + const [displayedText, setDisplayedText] = useState('') + const [currentWordIndex, setCurrentWordIndex] = useState(0) + const [currentCharIndex, setCurrentCharIndex] = useState(0) + const [phase, setPhase] = useState<'typing' | 'pause' | 'deleting'>('typing') + const elementRef = useRef(null) + const isInView = useInView(elementRef as React.RefObject, { + amount: 0.3, + once: true, + }) + + const wordsToAnimate = useMemo(() => words || (children ? [children] : []), [words, children]) + const hasMultipleWords = wordsToAnimate.length > 1 + + const typingSpeed = typeSpeed || duration + const deletingSpeed = deleteSpeed || typingSpeed / 2 + + const shouldStart = startOnView ? isInView : true + + useEffect(() => { + if (!shouldStart || wordsToAnimate.length === 0) return + + const timeoutDelay = + delay > 0 && displayedText === '' + ? delay + : phase === 'typing' + ? typingSpeed + : phase === 'deleting' + ? deletingSpeed + : pauseDelay + + const timeout = setTimeout(() => { + const currentWord = wordsToAnimate[currentWordIndex] || '' + const graphemes = Array.from(currentWord) + + switch (phase) { + case 'typing': + if (currentCharIndex < graphemes.length) { + setDisplayedText(graphemes.slice(0, currentCharIndex + 1).join('')) + setCurrentCharIndex(currentCharIndex + 1) + } else { + if (hasMultipleWords || loop) { + const isLastWord = currentWordIndex === wordsToAnimate.length - 1 + if (!isLastWord || loop) { + setPhase('pause') + } + } + } + break + + case 'pause': + setPhase('deleting') + break + + case 'deleting': + if (currentCharIndex > 0) { + setDisplayedText(graphemes.slice(0, currentCharIndex - 1).join('')) + setCurrentCharIndex(currentCharIndex - 1) + } else { + const nextIndex = (currentWordIndex + 1) % wordsToAnimate.length + setCurrentWordIndex(nextIndex) + setPhase('typing') + } + break + } + }, timeoutDelay) + + return () => clearTimeout(timeout) + }, [ + shouldStart, + phase, + currentCharIndex, + currentWordIndex, + displayedText, + wordsToAnimate, + hasMultipleWords, + loop, + typingSpeed, + deletingSpeed, + pauseDelay, + delay, + ]) + + const currentWordGraphemes = Array.from(wordsToAnimate[currentWordIndex] || '') + const isComplete = + !loop && + currentWordIndex === wordsToAnimate.length - 1 && + currentCharIndex >= currentWordGraphemes.length && + phase !== 'deleting' + + const shouldShowCursor = + showCursor && + !isComplete && + (hasMultipleWords || loop || currentCharIndex < currentWordGraphemes.length) + + const getCursorChar = () => { + switch (cursorStyle) { + case 'block': + return '▌' + case 'underscore': + return '_' + case 'line': + default: + return '|' + } + } + + return ( + + {displayedText} + {shouldShowCursor && ( + + {getCursorChar()} + + )} + + ) +} diff --git a/packages/website/src/index.css b/packages/website/src/index.css index a91ce92..4f99858 100644 --- a/packages/website/src/index.css +++ b/packages/website/src/index.css @@ -12,6 +12,8 @@ /* 主题色渐变 */ --theme-color-1: rgb(88, 192, 252); --theme-color-2: rgb(189, 69, 251); + + /* shadcn */ --radius: 0.625rem; --card: oklch(1 0 0); --card-foreground: oklch(0.145 0 0); @@ -236,6 +238,7 @@ td { display: none; /* Chrome, Safari and Opera */ } +/* shadcn */ @theme inline { --radius-sm: calc(var(--radius) - 4px); --radius-md: calc(var(--radius) - 2px); @@ -275,8 +278,72 @@ td { --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-border: var(--sidebar-border); --color-sidebar-ring: var(--sidebar-ring); + + /* magic ui */ + --animate-blink-cursor: blink-cursor 1.2s step-end infinite; + @keyframes blink-cursor { + 0%, + 49% { + opacity: 1; + } + 50%, + 100% { + opacity: 0; + } + } + --animate-aurora: aurora 8s ease-in-out infinite alternate; + @keyframes aurora { + 0% { + background-position: 0% 50%; + transform: rotate(-5deg) scale(0.9); + } + 25% { + background-position: 50% 100%; + transform: rotate(5deg) scale(1.1); + } + 50% { + background-position: 100% 50%; + transform: rotate(-3deg) scale(0.95); + } + 75% { + background-position: 50% 0%; + transform: rotate(3deg) scale(1.05); + } + 100% { + background-position: 0% 50%; + transform: rotate(-5deg) scale(0.9); + } + } + --animate-shiny-text: shiny-text 8s infinite; + @keyframes shiny-text { + 0%, + 90%, + 100% { + background-position: calc(-100% - var(--shiny-width)) 0; + } + 30%, + 60% { + background-position: calc(100% + var(--shiny-width)) 0; + } + } + --animate-gradient: gradient 8s linear infinite; + @keyframes gradient { + to { + background-position: var(--bg-size, 300%) 0; + } + } + --animate-background-position-spin: background-position-spin 3000ms infinite alternate; + @keyframes background-position-spin { + 0% { + background-position: top center; + } + 100% { + background-position: bottom center; + } + } } +/* shadcn dark mode */ .dark { --background: oklch(0.145 0 0); --foreground: oklch(0.985 0 0); @@ -311,6 +378,7 @@ td { --sidebar-ring: oklch(0.556 0 0); } +/* shadcn base */ @layer base { * { @apply border-border outline-ring/50;