viktor commited on
Commit
869a182
·
1 Parent(s): 637cbf7

feat. improve ui

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +35 -0
  2. .gitignore +42 -0
  3. Dockerfile +25 -0
  4. README.md +13 -0
  5. components.json +21 -0
  6. next.config.mjs +14 -0
  7. next.config.ts +7 -0
  8. package-lock.json +1627 -0
  9. package.json +72 -0
  10. pnpm-lock.yaml +0 -0
  11. postcss.config.mjs +8 -0
  12. public/placeholder-logo.png +0 -0
  13. public/placeholder-logo.svg +1 -0
  14. public/placeholder-user.jpg +0 -0
  15. public/placeholder.jpg +0 -0
  16. public/placeholder.svg +1 -0
  17. src/app/api/auth/login/route.ts +52 -0
  18. src/app/api/auth/logout/route.ts +18 -0
  19. src/app/api/generate-code/route.ts +123 -0
  20. src/app/api/improve-prompt/route.ts +88 -0
  21. src/app/api/login/route.ts +12 -0
  22. src/app/api/user/route.ts +42 -0
  23. src/app/globals.css +68 -0
  24. src/app/layout.tsx +34 -0
  25. src/app/page.tsx +15 -0
  26. src/components/app-container.tsx +250 -0
  27. src/components/auth-error-popup.tsx +40 -0
  28. src/components/code-editor.tsx +52 -0
  29. src/components/color-panel.tsx +196 -0
  30. src/components/header.tsx +37 -0
  31. src/components/logo.tsx +14 -0
  32. src/components/model-selector.tsx +72 -0
  33. src/components/preview.tsx +239 -0
  34. src/components/prompt-input.tsx +188 -0
  35. src/components/theme-provider.tsx +11 -0
  36. src/components/ui/accordion.tsx +58 -0
  37. src/components/ui/alert-dialog.tsx +141 -0
  38. src/components/ui/alert.tsx +59 -0
  39. src/components/ui/aspect-ratio.tsx +7 -0
  40. src/components/ui/avatar.tsx +50 -0
  41. src/components/ui/badge.tsx +36 -0
  42. src/components/ui/breadcrumb.tsx +115 -0
  43. src/components/ui/button.tsx +47 -0
  44. src/components/ui/calendar.tsx +66 -0
  45. src/components/ui/card.tsx +79 -0
  46. src/components/ui/carousel.tsx +262 -0
  47. src/components/ui/chart.tsx +365 -0
  48. src/components/ui/checkbox.tsx +30 -0
  49. src/components/ui/collapsible.tsx +11 -0
  50. src/components/ui/command.tsx +153 -0
.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ node_modules/
5
+ .pnp
6
+ .pnp.*
7
+ .yarn/*
8
+ !.yarn/patches
9
+ !.yarn/plugins
10
+ !.yarn/releases
11
+ !.yarn/versions
12
+
13
+ # testing
14
+ coverage/
15
+
16
+ # next.js
17
+ .next/
18
+ out/
19
+
20
+ # production
21
+ build/
22
+
23
+ # misc
24
+ .DS_Store
25
+ *.pem
26
+ .cursor/
27
+
28
+ # debug
29
+ npm-debug.log*
30
+ yarn-debug.log*
31
+ yarn-error.log*
32
+ .pnpm-debug.log*
33
+
34
+ # env files (can opt-in for committing if needed)
35
+ .env*
36
+
37
+ # vercel
38
+ .vercel
39
+
40
+ # typescript
41
+ *.tsbuildinfo
42
+ next-env.d.ts
Dockerfile ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dockerfile
2
+ # Use an official Node.js runtime as the base image
3
+ FROM node:22.1.0
4
+ USER root
5
+
6
+ RUN apt-get update \
7
+ && npm install -g pnpm
8
+
9
+ USER 1000
10
+ WORKDIR /usr/src/app
11
+ # Copy package.json and package-lock.json to the container
12
+ COPY --chown=1000 package.json package-lock.json pnpm-lock.yaml ./
13
+
14
+ # Copy the rest of the application files to the container
15
+ COPY --chown=1000 . .
16
+
17
+ RUN rm -rf node_modules .next .next-env.d.ts \
18
+ && pnpm install \
19
+ && pnpm build
20
+
21
+ # Expose the application port (assuming your app runs on port 3000)
22
+ EXPOSE 5001
23
+
24
+ # Start the application
25
+ CMD ["pnpm", "start"]
README.md ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Novita Vibe Coding
3
+ emoji: 🐨
4
+ colorFrom: yellow
5
+ colorTo: yellow
6
+ sdk: docker
7
+ pinned: false
8
+ license: mit
9
+ app_port: 5001
10
+ short_description: This is a demo showing the use of Novita LLMs to vibe coding
11
+ ---
12
+
13
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
components.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "default",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "tailwind.config.ts",
8
+ "css": "app/globals.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "aliases": {
14
+ "components": "@/components",
15
+ "utils": "@/lib/utils",
16
+ "ui": "@/components/ui",
17
+ "lib": "@/lib",
18
+ "hooks": "@/hooks"
19
+ },
20
+ "iconLibrary": "lucide"
21
+ }
next.config.mjs ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+ eslint: {
4
+ ignoreDuringBuilds: true,
5
+ },
6
+ typescript: {
7
+ ignoreBuildErrors: true,
8
+ },
9
+ images: {
10
+ unoptimized: true,
11
+ },
12
+ }
13
+
14
+ export default nextConfig
next.config.ts ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ import type { NextConfig } from "next";
2
+
3
+ const nextConfig: NextConfig = {
4
+ /* config options here */
5
+ };
6
+
7
+ export default nextConfig;
package-lock.json ADDED
@@ -0,0 +1,1627 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "novita-anysite",
3
+ "version": "0.1.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "novita-anysite",
9
+ "version": "0.1.0",
10
+ "dependencies": {
11
+ "next": "15.3.2",
12
+ "react": "^19.0.0",
13
+ "react-dom": "^19.0.0"
14
+ },
15
+ "devDependencies": {
16
+ "@tailwindcss/postcss": "^4",
17
+ "@types/node": "^20",
18
+ "@types/react": "^19",
19
+ "@types/react-dom": "^19",
20
+ "tailwindcss": "^4",
21
+ "typescript": "^5"
22
+ }
23
+ },
24
+ "node_modules/@alloc/quick-lru": {
25
+ "version": "5.2.0",
26
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
27
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
28
+ "dev": true,
29
+ "engines": {
30
+ "node": ">=10"
31
+ },
32
+ "funding": {
33
+ "url": "https://github.com/sponsors/sindresorhus"
34
+ }
35
+ },
36
+ "node_modules/@ampproject/remapping": {
37
+ "version": "2.3.0",
38
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
39
+ "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
40
+ "dev": true,
41
+ "dependencies": {
42
+ "@jridgewell/gen-mapping": "^0.3.5",
43
+ "@jridgewell/trace-mapping": "^0.3.24"
44
+ },
45
+ "engines": {
46
+ "node": ">=6.0.0"
47
+ }
48
+ },
49
+ "node_modules/@emnapi/runtime": {
50
+ "version": "1.4.3",
51
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz",
52
+ "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==",
53
+ "optional": true,
54
+ "dependencies": {
55
+ "tslib": "^2.4.0"
56
+ }
57
+ },
58
+ "node_modules/@img/sharp-darwin-arm64": {
59
+ "version": "0.34.1",
60
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.1.tgz",
61
+ "integrity": "sha512-pn44xgBtgpEbZsu+lWf2KNb6OAf70X68k+yk69Ic2Xz11zHR/w24/U49XT7AeRwJ0Px+mhALhU5LPci1Aymk7A==",
62
+ "cpu": [
63
+ "arm64"
64
+ ],
65
+ "optional": true,
66
+ "os": [
67
+ "darwin"
68
+ ],
69
+ "engines": {
70
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
71
+ },
72
+ "funding": {
73
+ "url": "https://opencollective.com/libvips"
74
+ },
75
+ "optionalDependencies": {
76
+ "@img/sharp-libvips-darwin-arm64": "1.1.0"
77
+ }
78
+ },
79
+ "node_modules/@img/sharp-darwin-x64": {
80
+ "version": "0.34.1",
81
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.1.tgz",
82
+ "integrity": "sha512-VfuYgG2r8BpYiOUN+BfYeFo69nP/MIwAtSJ7/Zpxc5QF3KS22z8Pvg3FkrSFJBPNQ7mmcUcYQFBmEQp7eu1F8Q==",
83
+ "cpu": [
84
+ "x64"
85
+ ],
86
+ "optional": true,
87
+ "os": [
88
+ "darwin"
89
+ ],
90
+ "engines": {
91
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
92
+ },
93
+ "funding": {
94
+ "url": "https://opencollective.com/libvips"
95
+ },
96
+ "optionalDependencies": {
97
+ "@img/sharp-libvips-darwin-x64": "1.1.0"
98
+ }
99
+ },
100
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
101
+ "version": "1.1.0",
102
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.1.0.tgz",
103
+ "integrity": "sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==",
104
+ "cpu": [
105
+ "arm64"
106
+ ],
107
+ "optional": true,
108
+ "os": [
109
+ "darwin"
110
+ ],
111
+ "funding": {
112
+ "url": "https://opencollective.com/libvips"
113
+ }
114
+ },
115
+ "node_modules/@img/sharp-libvips-darwin-x64": {
116
+ "version": "1.1.0",
117
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.1.0.tgz",
118
+ "integrity": "sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==",
119
+ "cpu": [
120
+ "x64"
121
+ ],
122
+ "optional": true,
123
+ "os": [
124
+ "darwin"
125
+ ],
126
+ "funding": {
127
+ "url": "https://opencollective.com/libvips"
128
+ }
129
+ },
130
+ "node_modules/@img/sharp-libvips-linux-arm": {
131
+ "version": "1.1.0",
132
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.1.0.tgz",
133
+ "integrity": "sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==",
134
+ "cpu": [
135
+ "arm"
136
+ ],
137
+ "optional": true,
138
+ "os": [
139
+ "linux"
140
+ ],
141
+ "funding": {
142
+ "url": "https://opencollective.com/libvips"
143
+ }
144
+ },
145
+ "node_modules/@img/sharp-libvips-linux-arm64": {
146
+ "version": "1.1.0",
147
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.1.0.tgz",
148
+ "integrity": "sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==",
149
+ "cpu": [
150
+ "arm64"
151
+ ],
152
+ "optional": true,
153
+ "os": [
154
+ "linux"
155
+ ],
156
+ "funding": {
157
+ "url": "https://opencollective.com/libvips"
158
+ }
159
+ },
160
+ "node_modules/@img/sharp-libvips-linux-ppc64": {
161
+ "version": "1.1.0",
162
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.1.0.tgz",
163
+ "integrity": "sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==",
164
+ "cpu": [
165
+ "ppc64"
166
+ ],
167
+ "optional": true,
168
+ "os": [
169
+ "linux"
170
+ ],
171
+ "funding": {
172
+ "url": "https://opencollective.com/libvips"
173
+ }
174
+ },
175
+ "node_modules/@img/sharp-libvips-linux-s390x": {
176
+ "version": "1.1.0",
177
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.1.0.tgz",
178
+ "integrity": "sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==",
179
+ "cpu": [
180
+ "s390x"
181
+ ],
182
+ "optional": true,
183
+ "os": [
184
+ "linux"
185
+ ],
186
+ "funding": {
187
+ "url": "https://opencollective.com/libvips"
188
+ }
189
+ },
190
+ "node_modules/@img/sharp-libvips-linux-x64": {
191
+ "version": "1.1.0",
192
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.1.0.tgz",
193
+ "integrity": "sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==",
194
+ "cpu": [
195
+ "x64"
196
+ ],
197
+ "optional": true,
198
+ "os": [
199
+ "linux"
200
+ ],
201
+ "funding": {
202
+ "url": "https://opencollective.com/libvips"
203
+ }
204
+ },
205
+ "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
206
+ "version": "1.1.0",
207
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.1.0.tgz",
208
+ "integrity": "sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==",
209
+ "cpu": [
210
+ "arm64"
211
+ ],
212
+ "optional": true,
213
+ "os": [
214
+ "linux"
215
+ ],
216
+ "funding": {
217
+ "url": "https://opencollective.com/libvips"
218
+ }
219
+ },
220
+ "node_modules/@img/sharp-libvips-linuxmusl-x64": {
221
+ "version": "1.1.0",
222
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.1.0.tgz",
223
+ "integrity": "sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==",
224
+ "cpu": [
225
+ "x64"
226
+ ],
227
+ "optional": true,
228
+ "os": [
229
+ "linux"
230
+ ],
231
+ "funding": {
232
+ "url": "https://opencollective.com/libvips"
233
+ }
234
+ },
235
+ "node_modules/@img/sharp-linux-arm": {
236
+ "version": "0.34.1",
237
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.1.tgz",
238
+ "integrity": "sha512-anKiszvACti2sGy9CirTlNyk7BjjZPiML1jt2ZkTdcvpLU1YH6CXwRAZCA2UmRXnhiIftXQ7+Oh62Ji25W72jA==",
239
+ "cpu": [
240
+ "arm"
241
+ ],
242
+ "optional": true,
243
+ "os": [
244
+ "linux"
245
+ ],
246
+ "engines": {
247
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
248
+ },
249
+ "funding": {
250
+ "url": "https://opencollective.com/libvips"
251
+ },
252
+ "optionalDependencies": {
253
+ "@img/sharp-libvips-linux-arm": "1.1.0"
254
+ }
255
+ },
256
+ "node_modules/@img/sharp-linux-arm64": {
257
+ "version": "0.34.1",
258
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.1.tgz",
259
+ "integrity": "sha512-kX2c+vbvaXC6vly1RDf/IWNXxrlxLNpBVWkdpRq5Ka7OOKj6nr66etKy2IENf6FtOgklkg9ZdGpEu9kwdlcwOQ==",
260
+ "cpu": [
261
+ "arm64"
262
+ ],
263
+ "optional": true,
264
+ "os": [
265
+ "linux"
266
+ ],
267
+ "engines": {
268
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
269
+ },
270
+ "funding": {
271
+ "url": "https://opencollective.com/libvips"
272
+ },
273
+ "optionalDependencies": {
274
+ "@img/sharp-libvips-linux-arm64": "1.1.0"
275
+ }
276
+ },
277
+ "node_modules/@img/sharp-linux-s390x": {
278
+ "version": "0.34.1",
279
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.1.tgz",
280
+ "integrity": "sha512-7s0KX2tI9mZI2buRipKIw2X1ufdTeaRgwmRabt5bi9chYfhur+/C1OXg3TKg/eag1W+6CCWLVmSauV1owmRPxA==",
281
+ "cpu": [
282
+ "s390x"
283
+ ],
284
+ "optional": true,
285
+ "os": [
286
+ "linux"
287
+ ],
288
+ "engines": {
289
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
290
+ },
291
+ "funding": {
292
+ "url": "https://opencollective.com/libvips"
293
+ },
294
+ "optionalDependencies": {
295
+ "@img/sharp-libvips-linux-s390x": "1.1.0"
296
+ }
297
+ },
298
+ "node_modules/@img/sharp-linux-x64": {
299
+ "version": "0.34.1",
300
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.1.tgz",
301
+ "integrity": "sha512-wExv7SH9nmoBW3Wr2gvQopX1k8q2g5V5Iag8Zk6AVENsjwd+3adjwxtp3Dcu2QhOXr8W9NusBU6XcQUohBZ5MA==",
302
+ "cpu": [
303
+ "x64"
304
+ ],
305
+ "optional": true,
306
+ "os": [
307
+ "linux"
308
+ ],
309
+ "engines": {
310
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
311
+ },
312
+ "funding": {
313
+ "url": "https://opencollective.com/libvips"
314
+ },
315
+ "optionalDependencies": {
316
+ "@img/sharp-libvips-linux-x64": "1.1.0"
317
+ }
318
+ },
319
+ "node_modules/@img/sharp-linuxmusl-arm64": {
320
+ "version": "0.34.1",
321
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.1.tgz",
322
+ "integrity": "sha512-DfvyxzHxw4WGdPiTF0SOHnm11Xv4aQexvqhRDAoD00MzHekAj9a/jADXeXYCDFH/DzYruwHbXU7uz+H+nWmSOQ==",
323
+ "cpu": [
324
+ "arm64"
325
+ ],
326
+ "optional": true,
327
+ "os": [
328
+ "linux"
329
+ ],
330
+ "engines": {
331
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
332
+ },
333
+ "funding": {
334
+ "url": "https://opencollective.com/libvips"
335
+ },
336
+ "optionalDependencies": {
337
+ "@img/sharp-libvips-linuxmusl-arm64": "1.1.0"
338
+ }
339
+ },
340
+ "node_modules/@img/sharp-linuxmusl-x64": {
341
+ "version": "0.34.1",
342
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.1.tgz",
343
+ "integrity": "sha512-pax/kTR407vNb9qaSIiWVnQplPcGU8LRIJpDT5o8PdAx5aAA7AS3X9PS8Isw1/WfqgQorPotjrZL3Pqh6C5EBg==",
344
+ "cpu": [
345
+ "x64"
346
+ ],
347
+ "optional": true,
348
+ "os": [
349
+ "linux"
350
+ ],
351
+ "engines": {
352
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
353
+ },
354
+ "funding": {
355
+ "url": "https://opencollective.com/libvips"
356
+ },
357
+ "optionalDependencies": {
358
+ "@img/sharp-libvips-linuxmusl-x64": "1.1.0"
359
+ }
360
+ },
361
+ "node_modules/@img/sharp-wasm32": {
362
+ "version": "0.34.1",
363
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.1.tgz",
364
+ "integrity": "sha512-YDybQnYrLQfEpzGOQe7OKcyLUCML4YOXl428gOOzBgN6Gw0rv8dpsJ7PqTHxBnXnwXr8S1mYFSLSa727tpz0xg==",
365
+ "cpu": [
366
+ "wasm32"
367
+ ],
368
+ "optional": true,
369
+ "dependencies": {
370
+ "@emnapi/runtime": "^1.4.0"
371
+ },
372
+ "engines": {
373
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
374
+ },
375
+ "funding": {
376
+ "url": "https://opencollective.com/libvips"
377
+ }
378
+ },
379
+ "node_modules/@img/sharp-win32-ia32": {
380
+ "version": "0.34.1",
381
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.1.tgz",
382
+ "integrity": "sha512-WKf/NAZITnonBf3U1LfdjoMgNO5JYRSlhovhRhMxXVdvWYveM4kM3L8m35onYIdh75cOMCo1BexgVQcCDzyoWw==",
383
+ "cpu": [
384
+ "ia32"
385
+ ],
386
+ "optional": true,
387
+ "os": [
388
+ "win32"
389
+ ],
390
+ "engines": {
391
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
392
+ },
393
+ "funding": {
394
+ "url": "https://opencollective.com/libvips"
395
+ }
396
+ },
397
+ "node_modules/@img/sharp-win32-x64": {
398
+ "version": "0.34.1",
399
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.1.tgz",
400
+ "integrity": "sha512-hw1iIAHpNE8q3uMIRCgGOeDoz9KtFNarFLQclLxr/LK1VBkj8nby18RjFvr6aP7USRYAjTZW6yisnBWMX571Tw==",
401
+ "cpu": [
402
+ "x64"
403
+ ],
404
+ "optional": true,
405
+ "os": [
406
+ "win32"
407
+ ],
408
+ "engines": {
409
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
410
+ },
411
+ "funding": {
412
+ "url": "https://opencollective.com/libvips"
413
+ }
414
+ },
415
+ "node_modules/@isaacs/fs-minipass": {
416
+ "version": "4.0.1",
417
+ "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
418
+ "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
419
+ "dev": true,
420
+ "dependencies": {
421
+ "minipass": "^7.0.4"
422
+ },
423
+ "engines": {
424
+ "node": ">=18.0.0"
425
+ }
426
+ },
427
+ "node_modules/@jridgewell/gen-mapping": {
428
+ "version": "0.3.8",
429
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
430
+ "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
431
+ "dev": true,
432
+ "dependencies": {
433
+ "@jridgewell/set-array": "^1.2.1",
434
+ "@jridgewell/sourcemap-codec": "^1.4.10",
435
+ "@jridgewell/trace-mapping": "^0.3.24"
436
+ },
437
+ "engines": {
438
+ "node": ">=6.0.0"
439
+ }
440
+ },
441
+ "node_modules/@jridgewell/resolve-uri": {
442
+ "version": "3.1.2",
443
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
444
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
445
+ "dev": true,
446
+ "engines": {
447
+ "node": ">=6.0.0"
448
+ }
449
+ },
450
+ "node_modules/@jridgewell/set-array": {
451
+ "version": "1.2.1",
452
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
453
+ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
454
+ "dev": true,
455
+ "engines": {
456
+ "node": ">=6.0.0"
457
+ }
458
+ },
459
+ "node_modules/@jridgewell/sourcemap-codec": {
460
+ "version": "1.5.0",
461
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
462
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
463
+ "dev": true
464
+ },
465
+ "node_modules/@jridgewell/trace-mapping": {
466
+ "version": "0.3.25",
467
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
468
+ "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
469
+ "dev": true,
470
+ "dependencies": {
471
+ "@jridgewell/resolve-uri": "^3.1.0",
472
+ "@jridgewell/sourcemap-codec": "^1.4.14"
473
+ }
474
+ },
475
+ "node_modules/@next/env": {
476
+ "version": "15.3.2",
477
+ "resolved": "https://registry.npmjs.org/@next/env/-/env-15.3.2.tgz",
478
+ "integrity": "sha512-xURk++7P7qR9JG1jJtLzPzf0qEvqCN0A/T3DXf8IPMKo9/6FfjxtEffRJIIew/bIL4T3C2jLLqBor8B/zVlx6g=="
479
+ },
480
+ "node_modules/@next/swc-darwin-arm64": {
481
+ "version": "15.3.2",
482
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.3.2.tgz",
483
+ "integrity": "sha512-2DR6kY/OGcokbnCsjHpNeQblqCZ85/1j6njYSkzRdpLn5At7OkSdmk7WyAmB9G0k25+VgqVZ/u356OSoQZ3z0g==",
484
+ "cpu": [
485
+ "arm64"
486
+ ],
487
+ "optional": true,
488
+ "os": [
489
+ "darwin"
490
+ ],
491
+ "engines": {
492
+ "node": ">= 10"
493
+ }
494
+ },
495
+ "node_modules/@next/swc-darwin-x64": {
496
+ "version": "15.3.2",
497
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.3.2.tgz",
498
+ "integrity": "sha512-ro/fdqaZWL6k1S/5CLv1I0DaZfDVJkWNaUU3un8Lg6m0YENWlDulmIWzV96Iou2wEYyEsZq51mwV8+XQXqMp3w==",
499
+ "cpu": [
500
+ "x64"
501
+ ],
502
+ "optional": true,
503
+ "os": [
504
+ "darwin"
505
+ ],
506
+ "engines": {
507
+ "node": ">= 10"
508
+ }
509
+ },
510
+ "node_modules/@next/swc-linux-arm64-gnu": {
511
+ "version": "15.3.2",
512
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.3.2.tgz",
513
+ "integrity": "sha512-covwwtZYhlbRWK2HlYX9835qXum4xYZ3E2Mra1mdQ+0ICGoMiw1+nVAn4d9Bo7R3JqSmK1grMq/va+0cdh7bJA==",
514
+ "cpu": [
515
+ "arm64"
516
+ ],
517
+ "optional": true,
518
+ "os": [
519
+ "linux"
520
+ ],
521
+ "engines": {
522
+ "node": ">= 10"
523
+ }
524
+ },
525
+ "node_modules/@next/swc-linux-arm64-musl": {
526
+ "version": "15.3.2",
527
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.3.2.tgz",
528
+ "integrity": "sha512-KQkMEillvlW5Qk5mtGA/3Yz0/tzpNlSw6/3/ttsV1lNtMuOHcGii3zVeXZyi4EJmmLDKYcTcByV2wVsOhDt/zg==",
529
+ "cpu": [
530
+ "arm64"
531
+ ],
532
+ "optional": true,
533
+ "os": [
534
+ "linux"
535
+ ],
536
+ "engines": {
537
+ "node": ">= 10"
538
+ }
539
+ },
540
+ "node_modules/@next/swc-linux-x64-gnu": {
541
+ "version": "15.3.2",
542
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.2.tgz",
543
+ "integrity": "sha512-uRBo6THWei0chz+Y5j37qzx+BtoDRFIkDzZjlpCItBRXyMPIg079eIkOCl3aqr2tkxL4HFyJ4GHDes7W8HuAUg==",
544
+ "cpu": [
545
+ "x64"
546
+ ],
547
+ "optional": true,
548
+ "os": [
549
+ "linux"
550
+ ],
551
+ "engines": {
552
+ "node": ">= 10"
553
+ }
554
+ },
555
+ "node_modules/@next/swc-linux-x64-musl": {
556
+ "version": "15.3.2",
557
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.2.tgz",
558
+ "integrity": "sha512-+uxFlPuCNx/T9PdMClOqeE8USKzj8tVz37KflT3Kdbx/LOlZBRI2yxuIcmx1mPNK8DwSOMNCr4ureSet7eyC0w==",
559
+ "cpu": [
560
+ "x64"
561
+ ],
562
+ "optional": true,
563
+ "os": [
564
+ "linux"
565
+ ],
566
+ "engines": {
567
+ "node": ">= 10"
568
+ }
569
+ },
570
+ "node_modules/@next/swc-win32-arm64-msvc": {
571
+ "version": "15.3.2",
572
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.3.2.tgz",
573
+ "integrity": "sha512-LLTKmaI5cfD8dVzh5Vt7+OMo+AIOClEdIU/TSKbXXT2iScUTSxOGoBhfuv+FU8R9MLmrkIL1e2fBMkEEjYAtPQ==",
574
+ "cpu": [
575
+ "arm64"
576
+ ],
577
+ "optional": true,
578
+ "os": [
579
+ "win32"
580
+ ],
581
+ "engines": {
582
+ "node": ">= 10"
583
+ }
584
+ },
585
+ "node_modules/@next/swc-win32-x64-msvc": {
586
+ "version": "15.3.2",
587
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.3.2.tgz",
588
+ "integrity": "sha512-aW5B8wOPioJ4mBdMDXkt5f3j8pUr9W8AnlX0Df35uRWNT1Y6RIybxjnSUe+PhM+M1bwgyY8PHLmXZC6zT1o5tA==",
589
+ "cpu": [
590
+ "x64"
591
+ ],
592
+ "optional": true,
593
+ "os": [
594
+ "win32"
595
+ ],
596
+ "engines": {
597
+ "node": ">= 10"
598
+ }
599
+ },
600
+ "node_modules/@swc/counter": {
601
+ "version": "0.1.3",
602
+ "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
603
+ "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="
604
+ },
605
+ "node_modules/@swc/helpers": {
606
+ "version": "0.5.15",
607
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
608
+ "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
609
+ "dependencies": {
610
+ "tslib": "^2.8.0"
611
+ }
612
+ },
613
+ "node_modules/@tailwindcss/node": {
614
+ "version": "4.1.6",
615
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.6.tgz",
616
+ "integrity": "sha512-ed6zQbgmKsjsVvodAS1q1Ld2BolEuxJOSyyNc+vhkjdmfNUDCmQnlXBfQkHrlzNmslxHsQU/bFmzcEbv4xXsLg==",
617
+ "dev": true,
618
+ "dependencies": {
619
+ "@ampproject/remapping": "^2.3.0",
620
+ "enhanced-resolve": "^5.18.1",
621
+ "jiti": "^2.4.2",
622
+ "lightningcss": "1.29.2",
623
+ "magic-string": "^0.30.17",
624
+ "source-map-js": "^1.2.1",
625
+ "tailwindcss": "4.1.6"
626
+ }
627
+ },
628
+ "node_modules/@tailwindcss/oxide": {
629
+ "version": "4.1.6",
630
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.6.tgz",
631
+ "integrity": "sha512-0bpEBQiGx+227fW4G0fLQ8vuvyy5rsB1YIYNapTq3aRsJ9taF3f5cCaovDjN5pUGKKzcpMrZst/mhNaKAPOHOA==",
632
+ "dev": true,
633
+ "hasInstallScript": true,
634
+ "dependencies": {
635
+ "detect-libc": "^2.0.4",
636
+ "tar": "^7.4.3"
637
+ },
638
+ "engines": {
639
+ "node": ">= 10"
640
+ },
641
+ "optionalDependencies": {
642
+ "@tailwindcss/oxide-android-arm64": "4.1.6",
643
+ "@tailwindcss/oxide-darwin-arm64": "4.1.6",
644
+ "@tailwindcss/oxide-darwin-x64": "4.1.6",
645
+ "@tailwindcss/oxide-freebsd-x64": "4.1.6",
646
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.6",
647
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.1.6",
648
+ "@tailwindcss/oxide-linux-arm64-musl": "4.1.6",
649
+ "@tailwindcss/oxide-linux-x64-gnu": "4.1.6",
650
+ "@tailwindcss/oxide-linux-x64-musl": "4.1.6",
651
+ "@tailwindcss/oxide-wasm32-wasi": "4.1.6",
652
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.1.6",
653
+ "@tailwindcss/oxide-win32-x64-msvc": "4.1.6"
654
+ }
655
+ },
656
+ "node_modules/@tailwindcss/oxide-android-arm64": {
657
+ "version": "4.1.6",
658
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.6.tgz",
659
+ "integrity": "sha512-VHwwPiwXtdIvOvqT/0/FLH/pizTVu78FOnI9jQo64kSAikFSZT7K4pjyzoDpSMaveJTGyAKvDjuhxJxKfmvjiQ==",
660
+ "cpu": [
661
+ "arm64"
662
+ ],
663
+ "dev": true,
664
+ "optional": true,
665
+ "os": [
666
+ "android"
667
+ ],
668
+ "engines": {
669
+ "node": ">= 10"
670
+ }
671
+ },
672
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
673
+ "version": "4.1.6",
674
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.6.tgz",
675
+ "integrity": "sha512-weINOCcqv1HVBIGptNrk7c6lWgSFFiQMcCpKM4tnVi5x8OY2v1FrV76jwLukfT6pL1hyajc06tyVmZFYXoxvhQ==",
676
+ "cpu": [
677
+ "arm64"
678
+ ],
679
+ "dev": true,
680
+ "optional": true,
681
+ "os": [
682
+ "darwin"
683
+ ],
684
+ "engines": {
685
+ "node": ">= 10"
686
+ }
687
+ },
688
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
689
+ "version": "4.1.6",
690
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.6.tgz",
691
+ "integrity": "sha512-3FzekhHG0ww1zQjQ1lPoq0wPrAIVXAbUkWdWM8u5BnYFZgb9ja5ejBqyTgjpo5mfy0hFOoMnMuVDI+7CXhXZaQ==",
692
+ "cpu": [
693
+ "x64"
694
+ ],
695
+ "dev": true,
696
+ "optional": true,
697
+ "os": [
698
+ "darwin"
699
+ ],
700
+ "engines": {
701
+ "node": ">= 10"
702
+ }
703
+ },
704
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
705
+ "version": "4.1.6",
706
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.6.tgz",
707
+ "integrity": "sha512-4m5F5lpkBZhVQJq53oe5XgJ+aFYWdrgkMwViHjRsES3KEu2m1udR21B1I77RUqie0ZYNscFzY1v9aDssMBZ/1w==",
708
+ "cpu": [
709
+ "x64"
710
+ ],
711
+ "dev": true,
712
+ "optional": true,
713
+ "os": [
714
+ "freebsd"
715
+ ],
716
+ "engines": {
717
+ "node": ">= 10"
718
+ }
719
+ },
720
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
721
+ "version": "4.1.6",
722
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.6.tgz",
723
+ "integrity": "sha512-qU0rHnA9P/ZoaDKouU1oGPxPWzDKtIfX7eOGi5jOWJKdxieUJdVV+CxWZOpDWlYTd4N3sFQvcnVLJWJ1cLP5TA==",
724
+ "cpu": [
725
+ "arm"
726
+ ],
727
+ "dev": true,
728
+ "optional": true,
729
+ "os": [
730
+ "linux"
731
+ ],
732
+ "engines": {
733
+ "node": ">= 10"
734
+ }
735
+ },
736
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
737
+ "version": "4.1.6",
738
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.6.tgz",
739
+ "integrity": "sha512-jXy3TSTrbfgyd3UxPQeXC3wm8DAgmigzar99Km9Sf6L2OFfn/k+u3VqmpgHQw5QNfCpPe43em6Q7V76Wx7ogIQ==",
740
+ "cpu": [
741
+ "arm64"
742
+ ],
743
+ "dev": true,
744
+ "optional": true,
745
+ "os": [
746
+ "linux"
747
+ ],
748
+ "engines": {
749
+ "node": ">= 10"
750
+ }
751
+ },
752
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
753
+ "version": "4.1.6",
754
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.6.tgz",
755
+ "integrity": "sha512-8kjivE5xW0qAQ9HX9reVFmZj3t+VmljDLVRJpVBEoTR+3bKMnvC7iLcoSGNIUJGOZy1mLVq7x/gerVg0T+IsYw==",
756
+ "cpu": [
757
+ "arm64"
758
+ ],
759
+ "dev": true,
760
+ "optional": true,
761
+ "os": [
762
+ "linux"
763
+ ],
764
+ "engines": {
765
+ "node": ">= 10"
766
+ }
767
+ },
768
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
769
+ "version": "4.1.6",
770
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.6.tgz",
771
+ "integrity": "sha512-A4spQhwnWVpjWDLXnOW9PSinO2PTKJQNRmL/aIl2U/O+RARls8doDfs6R41+DAXK0ccacvRyDpR46aVQJJCoCg==",
772
+ "cpu": [
773
+ "x64"
774
+ ],
775
+ "dev": true,
776
+ "optional": true,
777
+ "os": [
778
+ "linux"
779
+ ],
780
+ "engines": {
781
+ "node": ">= 10"
782
+ }
783
+ },
784
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
785
+ "version": "4.1.6",
786
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.6.tgz",
787
+ "integrity": "sha512-YRee+6ZqdzgiQAHVSLfl3RYmqeeaWVCk796MhXhLQu2kJu2COHBkqlqsqKYx3p8Hmk5pGCQd2jTAoMWWFeyG2A==",
788
+ "cpu": [
789
+ "x64"
790
+ ],
791
+ "dev": true,
792
+ "optional": true,
793
+ "os": [
794
+ "linux"
795
+ ],
796
+ "engines": {
797
+ "node": ">= 10"
798
+ }
799
+ },
800
+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
801
+ "version": "4.1.6",
802
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.6.tgz",
803
+ "integrity": "sha512-qAp4ooTYrBQ5pk5jgg54/U1rCJ/9FLYOkkQ/nTE+bVMseMfB6O7J8zb19YTpWuu4UdfRf5zzOrNKfl6T64MNrQ==",
804
+ "bundleDependencies": [
805
+ "@napi-rs/wasm-runtime",
806
+ "@emnapi/core",
807
+ "@emnapi/runtime",
808
+ "@tybys/wasm-util",
809
+ "@emnapi/wasi-threads",
810
+ "tslib"
811
+ ],
812
+ "cpu": [
813
+ "wasm32"
814
+ ],
815
+ "dev": true,
816
+ "optional": true,
817
+ "dependencies": {
818
+ "@emnapi/core": "^1.4.3",
819
+ "@emnapi/runtime": "^1.4.3",
820
+ "@emnapi/wasi-threads": "^1.0.2",
821
+ "@napi-rs/wasm-runtime": "^0.2.9",
822
+ "@tybys/wasm-util": "^0.9.0",
823
+ "tslib": "^2.8.0"
824
+ },
825
+ "engines": {
826
+ "node": ">=14.0.0"
827
+ }
828
+ },
829
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
830
+ "version": "4.1.6",
831
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.6.tgz",
832
+ "integrity": "sha512-nqpDWk0Xr8ELO/nfRUDjk1pc9wDJ3ObeDdNMHLaymc4PJBWj11gdPCWZFKSK2AVKjJQC7J2EfmSmf47GN7OuLg==",
833
+ "cpu": [
834
+ "arm64"
835
+ ],
836
+ "dev": true,
837
+ "optional": true,
838
+ "os": [
839
+ "win32"
840
+ ],
841
+ "engines": {
842
+ "node": ">= 10"
843
+ }
844
+ },
845
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
846
+ "version": "4.1.6",
847
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.6.tgz",
848
+ "integrity": "sha512-5k9xF33xkfKpo9wCvYcegQ21VwIBU1/qEbYlVukfEIyQbEA47uK8AAwS7NVjNE3vHzcmxMYwd0l6L4pPjjm1rQ==",
849
+ "cpu": [
850
+ "x64"
851
+ ],
852
+ "dev": true,
853
+ "optional": true,
854
+ "os": [
855
+ "win32"
856
+ ],
857
+ "engines": {
858
+ "node": ">= 10"
859
+ }
860
+ },
861
+ "node_modules/@tailwindcss/postcss": {
862
+ "version": "4.1.6",
863
+ "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.6.tgz",
864
+ "integrity": "sha512-ELq+gDMBuRXPJlpE3PEen+1MhnHAQQrh2zF0dI1NXOlEWfr2qWf2CQdr5jl9yANv8RErQaQ2l6nIFO9OSCVq/g==",
865
+ "dev": true,
866
+ "dependencies": {
867
+ "@alloc/quick-lru": "^5.2.0",
868
+ "@tailwindcss/node": "4.1.6",
869
+ "@tailwindcss/oxide": "4.1.6",
870
+ "postcss": "^8.4.41",
871
+ "tailwindcss": "4.1.6"
872
+ }
873
+ },
874
+ "node_modules/@types/node": {
875
+ "version": "20.17.47",
876
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.47.tgz",
877
+ "integrity": "sha512-3dLX0Upo1v7RvUimvxLeXqwrfyKxUINk0EAM83swP2mlSUcwV73sZy8XhNz8bcZ3VbsfQyC/y6jRdL5tgCNpDQ==",
878
+ "dev": true,
879
+ "dependencies": {
880
+ "undici-types": "~6.19.2"
881
+ }
882
+ },
883
+ "node_modules/@types/react": {
884
+ "version": "19.1.4",
885
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.4.tgz",
886
+ "integrity": "sha512-EB1yiiYdvySuIITtD5lhW4yPyJ31RkJkkDw794LaQYrxCSaQV/47y5o1FMC4zF9ZyjUjzJMZwbovEnT5yHTW6g==",
887
+ "dev": true,
888
+ "dependencies": {
889
+ "csstype": "^3.0.2"
890
+ }
891
+ },
892
+ "node_modules/@types/react-dom": {
893
+ "version": "19.1.5",
894
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.5.tgz",
895
+ "integrity": "sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==",
896
+ "dev": true,
897
+ "peerDependencies": {
898
+ "@types/react": "^19.0.0"
899
+ }
900
+ },
901
+ "node_modules/busboy": {
902
+ "version": "1.6.0",
903
+ "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
904
+ "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
905
+ "dependencies": {
906
+ "streamsearch": "^1.1.0"
907
+ },
908
+ "engines": {
909
+ "node": ">=10.16.0"
910
+ }
911
+ },
912
+ "node_modules/caniuse-lite": {
913
+ "version": "1.0.30001718",
914
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz",
915
+ "integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==",
916
+ "funding": [
917
+ {
918
+ "type": "opencollective",
919
+ "url": "https://opencollective.com/browserslist"
920
+ },
921
+ {
922
+ "type": "tidelift",
923
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
924
+ },
925
+ {
926
+ "type": "github",
927
+ "url": "https://github.com/sponsors/ai"
928
+ }
929
+ ]
930
+ },
931
+ "node_modules/chownr": {
932
+ "version": "3.0.0",
933
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
934
+ "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
935
+ "dev": true,
936
+ "engines": {
937
+ "node": ">=18"
938
+ }
939
+ },
940
+ "node_modules/client-only": {
941
+ "version": "0.0.1",
942
+ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
943
+ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
944
+ },
945
+ "node_modules/color": {
946
+ "version": "4.2.3",
947
+ "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
948
+ "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
949
+ "optional": true,
950
+ "dependencies": {
951
+ "color-convert": "^2.0.1",
952
+ "color-string": "^1.9.0"
953
+ },
954
+ "engines": {
955
+ "node": ">=12.5.0"
956
+ }
957
+ },
958
+ "node_modules/color-convert": {
959
+ "version": "2.0.1",
960
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
961
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
962
+ "optional": true,
963
+ "dependencies": {
964
+ "color-name": "~1.1.4"
965
+ },
966
+ "engines": {
967
+ "node": ">=7.0.0"
968
+ }
969
+ },
970
+ "node_modules/color-name": {
971
+ "version": "1.1.4",
972
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
973
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
974
+ "optional": true
975
+ },
976
+ "node_modules/color-string": {
977
+ "version": "1.9.1",
978
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
979
+ "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
980
+ "optional": true,
981
+ "dependencies": {
982
+ "color-name": "^1.0.0",
983
+ "simple-swizzle": "^0.2.2"
984
+ }
985
+ },
986
+ "node_modules/csstype": {
987
+ "version": "3.1.3",
988
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
989
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
990
+ "dev": true
991
+ },
992
+ "node_modules/detect-libc": {
993
+ "version": "2.0.4",
994
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
995
+ "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
996
+ "devOptional": true,
997
+ "engines": {
998
+ "node": ">=8"
999
+ }
1000
+ },
1001
+ "node_modules/enhanced-resolve": {
1002
+ "version": "5.18.1",
1003
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
1004
+ "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==",
1005
+ "dev": true,
1006
+ "dependencies": {
1007
+ "graceful-fs": "^4.2.4",
1008
+ "tapable": "^2.2.0"
1009
+ },
1010
+ "engines": {
1011
+ "node": ">=10.13.0"
1012
+ }
1013
+ },
1014
+ "node_modules/graceful-fs": {
1015
+ "version": "4.2.11",
1016
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
1017
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
1018
+ "dev": true
1019
+ },
1020
+ "node_modules/is-arrayish": {
1021
+ "version": "0.3.2",
1022
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
1023
+ "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
1024
+ "optional": true
1025
+ },
1026
+ "node_modules/jiti": {
1027
+ "version": "2.4.2",
1028
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
1029
+ "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==",
1030
+ "dev": true,
1031
+ "bin": {
1032
+ "jiti": "lib/jiti-cli.mjs"
1033
+ }
1034
+ },
1035
+ "node_modules/lightningcss": {
1036
+ "version": "1.29.2",
1037
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz",
1038
+ "integrity": "sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==",
1039
+ "dev": true,
1040
+ "dependencies": {
1041
+ "detect-libc": "^2.0.3"
1042
+ },
1043
+ "engines": {
1044
+ "node": ">= 12.0.0"
1045
+ },
1046
+ "funding": {
1047
+ "type": "opencollective",
1048
+ "url": "https://opencollective.com/parcel"
1049
+ },
1050
+ "optionalDependencies": {
1051
+ "lightningcss-darwin-arm64": "1.29.2",
1052
+ "lightningcss-darwin-x64": "1.29.2",
1053
+ "lightningcss-freebsd-x64": "1.29.2",
1054
+ "lightningcss-linux-arm-gnueabihf": "1.29.2",
1055
+ "lightningcss-linux-arm64-gnu": "1.29.2",
1056
+ "lightningcss-linux-arm64-musl": "1.29.2",
1057
+ "lightningcss-linux-x64-gnu": "1.29.2",
1058
+ "lightningcss-linux-x64-musl": "1.29.2",
1059
+ "lightningcss-win32-arm64-msvc": "1.29.2",
1060
+ "lightningcss-win32-x64-msvc": "1.29.2"
1061
+ }
1062
+ },
1063
+ "node_modules/lightningcss-darwin-arm64": {
1064
+ "version": "1.29.2",
1065
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.29.2.tgz",
1066
+ "integrity": "sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==",
1067
+ "cpu": [
1068
+ "arm64"
1069
+ ],
1070
+ "dev": true,
1071
+ "optional": true,
1072
+ "os": [
1073
+ "darwin"
1074
+ ],
1075
+ "engines": {
1076
+ "node": ">= 12.0.0"
1077
+ },
1078
+ "funding": {
1079
+ "type": "opencollective",
1080
+ "url": "https://opencollective.com/parcel"
1081
+ }
1082
+ },
1083
+ "node_modules/lightningcss-darwin-x64": {
1084
+ "version": "1.29.2",
1085
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.29.2.tgz",
1086
+ "integrity": "sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==",
1087
+ "cpu": [
1088
+ "x64"
1089
+ ],
1090
+ "dev": true,
1091
+ "optional": true,
1092
+ "os": [
1093
+ "darwin"
1094
+ ],
1095
+ "engines": {
1096
+ "node": ">= 12.0.0"
1097
+ },
1098
+ "funding": {
1099
+ "type": "opencollective",
1100
+ "url": "https://opencollective.com/parcel"
1101
+ }
1102
+ },
1103
+ "node_modules/lightningcss-freebsd-x64": {
1104
+ "version": "1.29.2",
1105
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.29.2.tgz",
1106
+ "integrity": "sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==",
1107
+ "cpu": [
1108
+ "x64"
1109
+ ],
1110
+ "dev": true,
1111
+ "optional": true,
1112
+ "os": [
1113
+ "freebsd"
1114
+ ],
1115
+ "engines": {
1116
+ "node": ">= 12.0.0"
1117
+ },
1118
+ "funding": {
1119
+ "type": "opencollective",
1120
+ "url": "https://opencollective.com/parcel"
1121
+ }
1122
+ },
1123
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
1124
+ "version": "1.29.2",
1125
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.29.2.tgz",
1126
+ "integrity": "sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==",
1127
+ "cpu": [
1128
+ "arm"
1129
+ ],
1130
+ "dev": true,
1131
+ "optional": true,
1132
+ "os": [
1133
+ "linux"
1134
+ ],
1135
+ "engines": {
1136
+ "node": ">= 12.0.0"
1137
+ },
1138
+ "funding": {
1139
+ "type": "opencollective",
1140
+ "url": "https://opencollective.com/parcel"
1141
+ }
1142
+ },
1143
+ "node_modules/lightningcss-linux-arm64-gnu": {
1144
+ "version": "1.29.2",
1145
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.29.2.tgz",
1146
+ "integrity": "sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==",
1147
+ "cpu": [
1148
+ "arm64"
1149
+ ],
1150
+ "dev": true,
1151
+ "optional": true,
1152
+ "os": [
1153
+ "linux"
1154
+ ],
1155
+ "engines": {
1156
+ "node": ">= 12.0.0"
1157
+ },
1158
+ "funding": {
1159
+ "type": "opencollective",
1160
+ "url": "https://opencollective.com/parcel"
1161
+ }
1162
+ },
1163
+ "node_modules/lightningcss-linux-arm64-musl": {
1164
+ "version": "1.29.2",
1165
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.29.2.tgz",
1166
+ "integrity": "sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==",
1167
+ "cpu": [
1168
+ "arm64"
1169
+ ],
1170
+ "dev": true,
1171
+ "optional": true,
1172
+ "os": [
1173
+ "linux"
1174
+ ],
1175
+ "engines": {
1176
+ "node": ">= 12.0.0"
1177
+ },
1178
+ "funding": {
1179
+ "type": "opencollective",
1180
+ "url": "https://opencollective.com/parcel"
1181
+ }
1182
+ },
1183
+ "node_modules/lightningcss-linux-x64-gnu": {
1184
+ "version": "1.29.2",
1185
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.29.2.tgz",
1186
+ "integrity": "sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==",
1187
+ "cpu": [
1188
+ "x64"
1189
+ ],
1190
+ "dev": true,
1191
+ "optional": true,
1192
+ "os": [
1193
+ "linux"
1194
+ ],
1195
+ "engines": {
1196
+ "node": ">= 12.0.0"
1197
+ },
1198
+ "funding": {
1199
+ "type": "opencollective",
1200
+ "url": "https://opencollective.com/parcel"
1201
+ }
1202
+ },
1203
+ "node_modules/lightningcss-linux-x64-musl": {
1204
+ "version": "1.29.2",
1205
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.29.2.tgz",
1206
+ "integrity": "sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==",
1207
+ "cpu": [
1208
+ "x64"
1209
+ ],
1210
+ "dev": true,
1211
+ "optional": true,
1212
+ "os": [
1213
+ "linux"
1214
+ ],
1215
+ "engines": {
1216
+ "node": ">= 12.0.0"
1217
+ },
1218
+ "funding": {
1219
+ "type": "opencollective",
1220
+ "url": "https://opencollective.com/parcel"
1221
+ }
1222
+ },
1223
+ "node_modules/lightningcss-win32-arm64-msvc": {
1224
+ "version": "1.29.2",
1225
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.29.2.tgz",
1226
+ "integrity": "sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==",
1227
+ "cpu": [
1228
+ "arm64"
1229
+ ],
1230
+ "dev": true,
1231
+ "optional": true,
1232
+ "os": [
1233
+ "win32"
1234
+ ],
1235
+ "engines": {
1236
+ "node": ">= 12.0.0"
1237
+ },
1238
+ "funding": {
1239
+ "type": "opencollective",
1240
+ "url": "https://opencollective.com/parcel"
1241
+ }
1242
+ },
1243
+ "node_modules/lightningcss-win32-x64-msvc": {
1244
+ "version": "1.29.2",
1245
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.29.2.tgz",
1246
+ "integrity": "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==",
1247
+ "cpu": [
1248
+ "x64"
1249
+ ],
1250
+ "dev": true,
1251
+ "optional": true,
1252
+ "os": [
1253
+ "win32"
1254
+ ],
1255
+ "engines": {
1256
+ "node": ">= 12.0.0"
1257
+ },
1258
+ "funding": {
1259
+ "type": "opencollective",
1260
+ "url": "https://opencollective.com/parcel"
1261
+ }
1262
+ },
1263
+ "node_modules/magic-string": {
1264
+ "version": "0.30.17",
1265
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
1266
+ "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
1267
+ "dev": true,
1268
+ "dependencies": {
1269
+ "@jridgewell/sourcemap-codec": "^1.5.0"
1270
+ }
1271
+ },
1272
+ "node_modules/minipass": {
1273
+ "version": "7.1.2",
1274
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
1275
+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
1276
+ "dev": true,
1277
+ "engines": {
1278
+ "node": ">=16 || 14 >=14.17"
1279
+ }
1280
+ },
1281
+ "node_modules/minizlib": {
1282
+ "version": "3.0.2",
1283
+ "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz",
1284
+ "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==",
1285
+ "dev": true,
1286
+ "dependencies": {
1287
+ "minipass": "^7.1.2"
1288
+ },
1289
+ "engines": {
1290
+ "node": ">= 18"
1291
+ }
1292
+ },
1293
+ "node_modules/mkdirp": {
1294
+ "version": "3.0.1",
1295
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
1296
+ "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
1297
+ "dev": true,
1298
+ "bin": {
1299
+ "mkdirp": "dist/cjs/src/bin.js"
1300
+ },
1301
+ "engines": {
1302
+ "node": ">=10"
1303
+ },
1304
+ "funding": {
1305
+ "url": "https://github.com/sponsors/isaacs"
1306
+ }
1307
+ },
1308
+ "node_modules/nanoid": {
1309
+ "version": "3.3.11",
1310
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
1311
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
1312
+ "funding": [
1313
+ {
1314
+ "type": "github",
1315
+ "url": "https://github.com/sponsors/ai"
1316
+ }
1317
+ ],
1318
+ "bin": {
1319
+ "nanoid": "bin/nanoid.cjs"
1320
+ },
1321
+ "engines": {
1322
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
1323
+ }
1324
+ },
1325
+ "node_modules/next": {
1326
+ "version": "15.3.2",
1327
+ "resolved": "https://registry.npmjs.org/next/-/next-15.3.2.tgz",
1328
+ "integrity": "sha512-CA3BatMyHkxZ48sgOCLdVHjFU36N7TF1HhqAHLFOkV6buwZnvMI84Cug8xD56B9mCuKrqXnLn94417GrZ/jjCQ==",
1329
+ "dependencies": {
1330
+ "@next/env": "15.3.2",
1331
+ "@swc/counter": "0.1.3",
1332
+ "@swc/helpers": "0.5.15",
1333
+ "busboy": "1.6.0",
1334
+ "caniuse-lite": "^1.0.30001579",
1335
+ "postcss": "8.4.31",
1336
+ "styled-jsx": "5.1.6"
1337
+ },
1338
+ "bin": {
1339
+ "next": "dist/bin/next"
1340
+ },
1341
+ "engines": {
1342
+ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
1343
+ },
1344
+ "optionalDependencies": {
1345
+ "@next/swc-darwin-arm64": "15.3.2",
1346
+ "@next/swc-darwin-x64": "15.3.2",
1347
+ "@next/swc-linux-arm64-gnu": "15.3.2",
1348
+ "@next/swc-linux-arm64-musl": "15.3.2",
1349
+ "@next/swc-linux-x64-gnu": "15.3.2",
1350
+ "@next/swc-linux-x64-musl": "15.3.2",
1351
+ "@next/swc-win32-arm64-msvc": "15.3.2",
1352
+ "@next/swc-win32-x64-msvc": "15.3.2",
1353
+ "sharp": "^0.34.1"
1354
+ },
1355
+ "peerDependencies": {
1356
+ "@opentelemetry/api": "^1.1.0",
1357
+ "@playwright/test": "^1.41.2",
1358
+ "babel-plugin-react-compiler": "*",
1359
+ "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
1360
+ "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
1361
+ "sass": "^1.3.0"
1362
+ },
1363
+ "peerDependenciesMeta": {
1364
+ "@opentelemetry/api": {
1365
+ "optional": true
1366
+ },
1367
+ "@playwright/test": {
1368
+ "optional": true
1369
+ },
1370
+ "babel-plugin-react-compiler": {
1371
+ "optional": true
1372
+ },
1373
+ "sass": {
1374
+ "optional": true
1375
+ }
1376
+ }
1377
+ },
1378
+ "node_modules/next/node_modules/postcss": {
1379
+ "version": "8.4.31",
1380
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
1381
+ "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
1382
+ "funding": [
1383
+ {
1384
+ "type": "opencollective",
1385
+ "url": "https://opencollective.com/postcss/"
1386
+ },
1387
+ {
1388
+ "type": "tidelift",
1389
+ "url": "https://tidelift.com/funding/github/npm/postcss"
1390
+ },
1391
+ {
1392
+ "type": "github",
1393
+ "url": "https://github.com/sponsors/ai"
1394
+ }
1395
+ ],
1396
+ "dependencies": {
1397
+ "nanoid": "^3.3.6",
1398
+ "picocolors": "^1.0.0",
1399
+ "source-map-js": "^1.0.2"
1400
+ },
1401
+ "engines": {
1402
+ "node": "^10 || ^12 || >=14"
1403
+ }
1404
+ },
1405
+ "node_modules/picocolors": {
1406
+ "version": "1.1.1",
1407
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
1408
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
1409
+ },
1410
+ "node_modules/postcss": {
1411
+ "version": "8.5.3",
1412
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
1413
+ "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
1414
+ "dev": true,
1415
+ "funding": [
1416
+ {
1417
+ "type": "opencollective",
1418
+ "url": "https://opencollective.com/postcss/"
1419
+ },
1420
+ {
1421
+ "type": "tidelift",
1422
+ "url": "https://tidelift.com/funding/github/npm/postcss"
1423
+ },
1424
+ {
1425
+ "type": "github",
1426
+ "url": "https://github.com/sponsors/ai"
1427
+ }
1428
+ ],
1429
+ "dependencies": {
1430
+ "nanoid": "^3.3.8",
1431
+ "picocolors": "^1.1.1",
1432
+ "source-map-js": "^1.2.1"
1433
+ },
1434
+ "engines": {
1435
+ "node": "^10 || ^12 || >=14"
1436
+ }
1437
+ },
1438
+ "node_modules/react": {
1439
+ "version": "19.1.0",
1440
+ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
1441
+ "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
1442
+ "engines": {
1443
+ "node": ">=0.10.0"
1444
+ }
1445
+ },
1446
+ "node_modules/react-dom": {
1447
+ "version": "19.1.0",
1448
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
1449
+ "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
1450
+ "dependencies": {
1451
+ "scheduler": "^0.26.0"
1452
+ },
1453
+ "peerDependencies": {
1454
+ "react": "^19.1.0"
1455
+ }
1456
+ },
1457
+ "node_modules/scheduler": {
1458
+ "version": "0.26.0",
1459
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
1460
+ "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="
1461
+ },
1462
+ "node_modules/semver": {
1463
+ "version": "7.7.2",
1464
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
1465
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
1466
+ "optional": true,
1467
+ "bin": {
1468
+ "semver": "bin/semver.js"
1469
+ },
1470
+ "engines": {
1471
+ "node": ">=10"
1472
+ }
1473
+ },
1474
+ "node_modules/sharp": {
1475
+ "version": "0.34.1",
1476
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.1.tgz",
1477
+ "integrity": "sha512-1j0w61+eVxu7DawFJtnfYcvSv6qPFvfTaqzTQ2BLknVhHTwGS8sc63ZBF4rzkWMBVKybo4S5OBtDdZahh2A1xg==",
1478
+ "hasInstallScript": true,
1479
+ "optional": true,
1480
+ "dependencies": {
1481
+ "color": "^4.2.3",
1482
+ "detect-libc": "^2.0.3",
1483
+ "semver": "^7.7.1"
1484
+ },
1485
+ "engines": {
1486
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
1487
+ },
1488
+ "funding": {
1489
+ "url": "https://opencollective.com/libvips"
1490
+ },
1491
+ "optionalDependencies": {
1492
+ "@img/sharp-darwin-arm64": "0.34.1",
1493
+ "@img/sharp-darwin-x64": "0.34.1",
1494
+ "@img/sharp-libvips-darwin-arm64": "1.1.0",
1495
+ "@img/sharp-libvips-darwin-x64": "1.1.0",
1496
+ "@img/sharp-libvips-linux-arm": "1.1.0",
1497
+ "@img/sharp-libvips-linux-arm64": "1.1.0",
1498
+ "@img/sharp-libvips-linux-ppc64": "1.1.0",
1499
+ "@img/sharp-libvips-linux-s390x": "1.1.0",
1500
+ "@img/sharp-libvips-linux-x64": "1.1.0",
1501
+ "@img/sharp-libvips-linuxmusl-arm64": "1.1.0",
1502
+ "@img/sharp-libvips-linuxmusl-x64": "1.1.0",
1503
+ "@img/sharp-linux-arm": "0.34.1",
1504
+ "@img/sharp-linux-arm64": "0.34.1",
1505
+ "@img/sharp-linux-s390x": "0.34.1",
1506
+ "@img/sharp-linux-x64": "0.34.1",
1507
+ "@img/sharp-linuxmusl-arm64": "0.34.1",
1508
+ "@img/sharp-linuxmusl-x64": "0.34.1",
1509
+ "@img/sharp-wasm32": "0.34.1",
1510
+ "@img/sharp-win32-ia32": "0.34.1",
1511
+ "@img/sharp-win32-x64": "0.34.1"
1512
+ }
1513
+ },
1514
+ "node_modules/simple-swizzle": {
1515
+ "version": "0.2.2",
1516
+ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
1517
+ "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
1518
+ "optional": true,
1519
+ "dependencies": {
1520
+ "is-arrayish": "^0.3.1"
1521
+ }
1522
+ },
1523
+ "node_modules/source-map-js": {
1524
+ "version": "1.2.1",
1525
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
1526
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
1527
+ "engines": {
1528
+ "node": ">=0.10.0"
1529
+ }
1530
+ },
1531
+ "node_modules/streamsearch": {
1532
+ "version": "1.1.0",
1533
+ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
1534
+ "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
1535
+ "engines": {
1536
+ "node": ">=10.0.0"
1537
+ }
1538
+ },
1539
+ "node_modules/styled-jsx": {
1540
+ "version": "5.1.6",
1541
+ "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
1542
+ "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
1543
+ "dependencies": {
1544
+ "client-only": "0.0.1"
1545
+ },
1546
+ "engines": {
1547
+ "node": ">= 12.0.0"
1548
+ },
1549
+ "peerDependencies": {
1550
+ "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0"
1551
+ },
1552
+ "peerDependenciesMeta": {
1553
+ "@babel/core": {
1554
+ "optional": true
1555
+ },
1556
+ "babel-plugin-macros": {
1557
+ "optional": true
1558
+ }
1559
+ }
1560
+ },
1561
+ "node_modules/tailwindcss": {
1562
+ "version": "4.1.6",
1563
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.6.tgz",
1564
+ "integrity": "sha512-j0cGLTreM6u4OWzBeLBpycK0WIh8w7kSwcUsQZoGLHZ7xDTdM69lN64AgoIEEwFi0tnhs4wSykUa5YWxAzgFYg==",
1565
+ "dev": true
1566
+ },
1567
+ "node_modules/tapable": {
1568
+ "version": "2.2.1",
1569
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
1570
+ "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
1571
+ "dev": true,
1572
+ "engines": {
1573
+ "node": ">=6"
1574
+ }
1575
+ },
1576
+ "node_modules/tar": {
1577
+ "version": "7.4.3",
1578
+ "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz",
1579
+ "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==",
1580
+ "dev": true,
1581
+ "dependencies": {
1582
+ "@isaacs/fs-minipass": "^4.0.0",
1583
+ "chownr": "^3.0.0",
1584
+ "minipass": "^7.1.2",
1585
+ "minizlib": "^3.0.1",
1586
+ "mkdirp": "^3.0.1",
1587
+ "yallist": "^5.0.0"
1588
+ },
1589
+ "engines": {
1590
+ "node": ">=18"
1591
+ }
1592
+ },
1593
+ "node_modules/tslib": {
1594
+ "version": "2.8.1",
1595
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
1596
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
1597
+ },
1598
+ "node_modules/typescript": {
1599
+ "version": "5.8.3",
1600
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
1601
+ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
1602
+ "dev": true,
1603
+ "bin": {
1604
+ "tsc": "bin/tsc",
1605
+ "tsserver": "bin/tsserver"
1606
+ },
1607
+ "engines": {
1608
+ "node": ">=14.17"
1609
+ }
1610
+ },
1611
+ "node_modules/undici-types": {
1612
+ "version": "6.19.8",
1613
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
1614
+ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
1615
+ "dev": true
1616
+ },
1617
+ "node_modules/yallist": {
1618
+ "version": "5.0.0",
1619
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
1620
+ "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
1621
+ "dev": true,
1622
+ "engines": {
1623
+ "node": ">=18"
1624
+ }
1625
+ }
1626
+ }
1627
+ }
package.json ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "my-v0-project",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev -p ${APP_PORT:-5001}",
7
+ "build": "next build",
8
+ "start": "next start -p ${APP_PORT:-5001}",
9
+ "lint": "next lint"
10
+ },
11
+ "dependencies": {
12
+ "@hookform/resolvers": "^3.9.1",
13
+ "@huggingface/hub": "^1.1.2",
14
+ "@huggingface/inference": "^3.13.1",
15
+ "@radix-ui/react-accordion": "1.2.2",
16
+ "@radix-ui/react-alert-dialog": "1.1.4",
17
+ "@radix-ui/react-aspect-ratio": "1.1.1",
18
+ "@radix-ui/react-avatar": "1.1.2",
19
+ "@radix-ui/react-checkbox": "1.1.3",
20
+ "@radix-ui/react-collapsible": "1.1.2",
21
+ "@radix-ui/react-context-menu": "2.2.4",
22
+ "@radix-ui/react-dialog": "1.1.4",
23
+ "@radix-ui/react-dropdown-menu": "2.1.4",
24
+ "@radix-ui/react-hover-card": "1.1.4",
25
+ "@radix-ui/react-label": "2.1.1",
26
+ "@radix-ui/react-menubar": "1.1.4",
27
+ "@radix-ui/react-navigation-menu": "1.2.3",
28
+ "@radix-ui/react-popover": "latest",
29
+ "@radix-ui/react-progress": "1.1.1",
30
+ "@radix-ui/react-radio-group": "1.2.2",
31
+ "@radix-ui/react-scroll-area": "1.2.2",
32
+ "@radix-ui/react-select": "2.1.4",
33
+ "@radix-ui/react-separator": "1.1.1",
34
+ "@radix-ui/react-slider": "1.2.2",
35
+ "@radix-ui/react-slot": "latest",
36
+ "@radix-ui/react-switch": "1.1.2",
37
+ "@radix-ui/react-tabs": "1.1.2",
38
+ "@radix-ui/react-toast": "1.2.4",
39
+ "@radix-ui/react-toggle": "1.1.1",
40
+ "@radix-ui/react-toggle-group": "1.1.1",
41
+ "@radix-ui/react-tooltip": "latest",
42
+ "autoprefixer": "^10.4.20",
43
+ "class-variance-authority": "^0.7.1",
44
+ "clsx": "^2.1.1",
45
+ "cmdk": "1.0.4",
46
+ "date-fns": "4.1.0",
47
+ "embla-carousel-react": "8.5.1",
48
+ "input-otp": "1.4.1",
49
+ "lucide-react": "^0.454.0",
50
+ "next": "15.2.4",
51
+ "next-themes": "^0.4.4",
52
+ "react": "^19",
53
+ "react-day-picker": "8.10.1",
54
+ "react-dom": "^19",
55
+ "react-hook-form": "^7.54.1",
56
+ "react-resizable-panels": "^2.1.7",
57
+ "recharts": "2.15.0",
58
+ "sonner": "^1.7.1",
59
+ "tailwind-merge": "^2.5.5",
60
+ "tailwindcss-animate": "^1.0.7",
61
+ "vaul": "^0.9.6",
62
+ "zod": "^3.24.1"
63
+ },
64
+ "devDependencies": {
65
+ "@types/node": "^22",
66
+ "@types/react": "^19",
67
+ "@types/react-dom": "^19",
68
+ "postcss": "^8",
69
+ "tailwindcss": "^3.4.17",
70
+ "typescript": "^5"
71
+ }
72
+ }
pnpm-lock.yaml ADDED
The diff for this file is too large to render. See raw diff
 
postcss.config.mjs ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('postcss-load-config').Config} */
2
+ const config = {
3
+ plugins: {
4
+ tailwindcss: {},
5
+ },
6
+ };
7
+
8
+ export default config;
public/placeholder-logo.png ADDED
public/placeholder-logo.svg ADDED
public/placeholder-user.jpg ADDED
public/placeholder.jpg ADDED
public/placeholder.svg ADDED
src/app/api/auth/login/route.ts ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NextRequest, NextResponse } from "next/server";
2
+
3
+ export const dynamic = "force-dynamic";
4
+
5
+ export async function GET(request: NextRequest) {
6
+ const searchParams = request.nextUrl.searchParams;
7
+ const code = searchParams.get("code");
8
+
9
+ if (!code) {
10
+ return NextResponse.redirect(new URL("/", request.url));
11
+ }
12
+
13
+ const port = process.env.APP_PORT || 3000;
14
+ const redirectUri = process.env.REDIRECT_URI || `http://localhost:${port}/auth/login`;
15
+ const Authorization = `Basic ${Buffer.from(
16
+ `${process.env.OAUTH_CLIENT_ID}:${process.env.OAUTH_CLIENT_SECRET}`
17
+ ).toString("base64")}`;
18
+
19
+ try {
20
+ const request_auth = await fetch("https://huggingface.co/oauth/token", {
21
+ method: "POST",
22
+ headers: {
23
+ "Content-Type": "application/x-www-form-urlencoded",
24
+ Authorization,
25
+ },
26
+ body: new URLSearchParams({
27
+ grant_type: "authorization_code",
28
+ code: code,
29
+ redirect_uri: redirectUri,
30
+ }),
31
+ });
32
+
33
+ const response = await request_auth.json();
34
+
35
+ if (!response.access_token) {
36
+ return NextResponse.redirect(new URL("/", request.url));
37
+ }
38
+
39
+ const res = NextResponse.redirect(new URL("/", request.url));
40
+ res.cookies.set("hf_token", response.access_token, {
41
+ httpOnly: false,
42
+ secure: true,
43
+ sameSite: "none",
44
+ maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
45
+ path: "/",
46
+ });
47
+
48
+ return res;
49
+ } catch (error) {
50
+ return NextResponse.redirect(new URL("/", request.url));
51
+ }
52
+ }
src/app/api/auth/logout/route.ts ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NextResponse } from "next/server";
2
+ import { cookies } from "next/headers";
3
+
4
+ export const dynamic = "force-dynamic";
5
+
6
+ export async function GET() {
7
+ const res = NextResponse.redirect(new URL("/", process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000"));
8
+
9
+ res.cookies.set("hf_token", "", {
10
+ httpOnly: false,
11
+ secure: true,
12
+ sameSite: "none",
13
+ maxAge: 0,
14
+ path: "/",
15
+ });
16
+
17
+ return res;
18
+ }
src/app/api/generate-code/route.ts ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { MODEL_CONFIG_CODE_GENERATION } from "@/lib/constants";
3
+ import {
4
+ getInferenceToken,
5
+ checkTokenLimit,
6
+ createInferenceClient,
7
+ createStreamResponse,
8
+ getInferenceOptions
9
+ } from "@/lib/inference-utils";
10
+
11
+ export const dynamic = "force-dynamic";
12
+ const NO_THINK_TAG = " /no_think";
13
+
14
+ export async function POST(request: NextRequest) {
15
+ try {
16
+ const { prompt, html, previousPrompt, colors, modelId } = await request.json();
17
+
18
+ if (!prompt) {
19
+ return NextResponse.json(
20
+ {
21
+ ok: false,
22
+ message: "Missing required fields",
23
+ },
24
+ { status: 400 }
25
+ );
26
+ }
27
+
28
+ // Find the model config by modelId or use the first one as default
29
+ const modelConfig = modelId
30
+ ? MODEL_CONFIG_CODE_GENERATION.find(config => config.id === modelId) || MODEL_CONFIG_CODE_GENERATION[0]
31
+ : MODEL_CONFIG_CODE_GENERATION[0];
32
+
33
+ // Get inference token
34
+ const tokenResult = await getInferenceToken(request);
35
+ if (tokenResult.error) {
36
+ return NextResponse.json(
37
+ {
38
+ ok: false,
39
+ openLogin: tokenResult.error.openLogin,
40
+ message: tokenResult.error.message,
41
+ },
42
+ { status: tokenResult.error.status }
43
+ );
44
+ }
45
+
46
+ // Calculate the estimated number of tokens used, this is not accurate.
47
+ let TOKENS_USED = prompt?.length || 0;
48
+ if (previousPrompt) TOKENS_USED += previousPrompt.length;
49
+ if (html) TOKENS_USED += html.length;
50
+
51
+ // Check token limit
52
+ const tokenLimitError = checkTokenLimit(TOKENS_USED, modelConfig);
53
+ if (tokenLimitError) {
54
+ return NextResponse.json(tokenLimitError, { status: 400 });
55
+ }
56
+
57
+ const actualNoThinkTag = modelConfig.default_enable_thinking ? NO_THINK_TAG : "";
58
+
59
+ // Use streaming response
60
+ return createStreamResponse(async (controller) => {
61
+ const client = createInferenceClient(tokenResult.token);
62
+ let completeResponse = "";
63
+
64
+ const messages = [
65
+ {
66
+ role: "system",
67
+ content: modelConfig.system_prompt,
68
+ },
69
+ ...(previousPrompt
70
+ ? [
71
+ {
72
+ role: "user",
73
+ content: previousPrompt,
74
+ },
75
+ ]
76
+ : []),
77
+ ...(html
78
+ ? [
79
+ {
80
+ role: "assistant",
81
+ content: `The current code is: ${html}.`,
82
+ },
83
+ ]
84
+ : []),
85
+ ...((colors && colors.length > 0)
86
+ ? [
87
+ {
88
+ role: "user",
89
+ content: `Use the following color palette in your UI design: ${colors.join(', ')}`,
90
+ },
91
+ ]
92
+ : []),
93
+ {
94
+ role: "user",
95
+ content: prompt + actualNoThinkTag,
96
+ },
97
+ ];
98
+
99
+ const chatCompletion = client.chatCompletionStream(
100
+ getInferenceOptions(modelConfig, messages, modelConfig.max_tokens)
101
+ );
102
+ const encoder = new TextEncoder();
103
+ for await (const chunk of chatCompletion) {
104
+ const content = chunk.choices[0]?.delta?.content;
105
+ if (content) {
106
+ controller.enqueue(encoder.encode(content));
107
+ completeResponse += content;
108
+ if (completeResponse.includes("</html>")) {
109
+ break;
110
+ }
111
+ }
112
+ }
113
+ });
114
+ } catch (error) {
115
+ return NextResponse.json(
116
+ {
117
+ ok: false,
118
+ message: error instanceof Error ? error.message : "Unknown error",
119
+ },
120
+ { status: 500 }
121
+ );
122
+ }
123
+ }
src/app/api/improve-prompt/route.ts ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { MODEL_CONFIG_PROMPT_IMPROVEMENT } from "@/lib/constants";
3
+ import {
4
+ getInferenceToken,
5
+ checkTokenLimit,
6
+ createInferenceClient,
7
+ createStreamResponse,
8
+ getInferenceOptions
9
+ } from "@/lib/inference-utils";
10
+
11
+ export const dynamic = "force-dynamic";
12
+
13
+ export async function POST(request: NextRequest) {
14
+ try {
15
+ const { prompt, provider } = await request.json();
16
+
17
+ if (!prompt) {
18
+ return NextResponse.json(
19
+ {
20
+ ok: false,
21
+ message: "Missing required fields",
22
+ },
23
+ { status: 400 }
24
+ );
25
+ }
26
+
27
+ // Get inference token
28
+ const tokenResult = await getInferenceToken(request);
29
+ if (tokenResult.error) {
30
+ return NextResponse.json(
31
+ {
32
+ ok: false,
33
+ openLogin: tokenResult.error.openLogin,
34
+ message: tokenResult.error.message,
35
+ },
36
+ { status: tokenResult.error.status }
37
+ );
38
+ }
39
+
40
+ let TOKENS_USED = prompt?.length || 0;
41
+
42
+ let modelConfig = MODEL_CONFIG_PROMPT_IMPROVEMENT;
43
+
44
+ // Check token limit
45
+ if (provider !== "auto") {
46
+ const tokenLimitError = checkTokenLimit(TOKENS_USED, modelConfig);
47
+ if (tokenLimitError) {
48
+ return NextResponse.json(tokenLimitError, { status: 400 });
49
+ }
50
+ }
51
+
52
+ // Use streaming response
53
+ return createStreamResponse(async (controller) => {
54
+ const client = createInferenceClient(tokenResult.token);
55
+
56
+ const messages = [
57
+ {
58
+ role: "system",
59
+ content: `You are an expert prompt engineer capable of enhancing prompts for generating precise HTML website development requests aimed at building visually stunning, intuitive UI/UX designs and fully functional components. KEEP IT CONCISE AND LESS THAN 256 TOKENS. GIVE ME THE IMPROVED PROMPT ONLY, NOTHING ELSE. DO NOT ENCLOSE THE PROMPT IN DOUBLE QUOTATION MARKS.`,
60
+ },
61
+ {
62
+ role: "user",
63
+ content: prompt,
64
+ },
65
+ ];
66
+
67
+ const chatCompletion = client.chatCompletionStream(
68
+ getInferenceOptions(modelConfig, messages, modelConfig.max_tokens)
69
+ );
70
+
71
+ const encoder = new TextEncoder();
72
+ for await (const chunk of chatCompletion) {
73
+ const content = chunk.choices[0]?.delta?.content;
74
+ if (content) {
75
+ controller.enqueue(encoder.encode(content));
76
+ }
77
+ }
78
+ });
79
+ } catch (error) {
80
+ return NextResponse.json(
81
+ {
82
+ ok: false,
83
+ message: error instanceof Error ? error.message : "Unknown error",
84
+ },
85
+ { status: 500 }
86
+ );
87
+ }
88
+ }
src/app/api/login/route.ts ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NextResponse } from "next/server";
2
+
3
+ export const dynamic = "force-dynamic";
4
+
5
+ export async function GET() {
6
+ const port = process.env.APP_PORT || 3000;
7
+ const redirectUri = process.env.REDIRECT_URI || `http://localhost:${port}/auth/login`;
8
+
9
+ const url = `https://huggingface.co/oauth/authorize?client_id=${process.env.OAUTH_CLIENT_ID}&redirect_uri=${redirectUri}&response_type=code&scope=openid%20profile%20write-repos%20manage-repos%20inference-api&prompt=consent&state=1234567890`;
10
+
11
+ return NextResponse.redirect(url);
12
+ }
src/app/api/user/route.ts ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { cookies } from "next/headers";
2
+ import { NextRequest, NextResponse } from "next/server";
3
+ import { checkUser } from "@/lib/middlewares/checkUser";
4
+
5
+ export const dynamic = "force-dynamic";
6
+
7
+ export async function GET(request: NextRequest) {
8
+ const checkResult = await checkUser(request);
9
+ if (checkResult) return checkResult;
10
+
11
+ const cookieStore = await cookies();
12
+ const hf_token = cookieStore.get("hf_token")?.value;
13
+
14
+ try {
15
+ const request_user = await fetch("https://huggingface.co/oauth/userinfo", {
16
+ headers: {
17
+ Authorization: `Bearer ${hf_token}`,
18
+ },
19
+ });
20
+
21
+ const user = await request_user.json();
22
+ return NextResponse.json(user);
23
+ } catch (error) {
24
+ const res = NextResponse.json(
25
+ {
26
+ ok: false,
27
+ message: error instanceof Error ? error.message : "Unknown error",
28
+ },
29
+ { status: 401 }
30
+ );
31
+
32
+ res.cookies.set("hf_token", "", {
33
+ httpOnly: false,
34
+ secure: true,
35
+ sameSite: "none",
36
+ maxAge: 0,
37
+ path: "/",
38
+ });
39
+
40
+ return res;
41
+ }
42
+ }
src/app/globals.css ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ @layer base {
6
+ :root {
7
+ --background: 40 10% 15.3%;
8
+ --foreground: 0 0% 98%;
9
+ --card: 40 10% 15.3%;
10
+ --card-foreground: 0 0% 98%;
11
+ --popover: 40 10% 15.3%;
12
+ --popover-foreground: 0 0% 98%;
13
+ --primary: 142.1 68.8% 49.4%;
14
+ --primary-foreground: 0 0% 100%;
15
+ --secondary: 40 5.9% 31.4%;
16
+ --secondary-foreground: 0 0% 98%;
17
+ --muted: 40 5.9% 31.4%;
18
+ --muted-foreground: 40 5% 64.9%;
19
+ --accent: 142.1 68.8% 49.4%;
20
+ --accent-foreground: 0 0% 100%;
21
+ --destructive: 0 62.8% 30.6%;
22
+ --destructive-foreground: 0 0% 98%;
23
+ --border: 40 5.9% 31.4%;
24
+ --input: 40 5.9% 31.4%;
25
+ --ring: 142.1 68.8% 49.4%;
26
+ --radius: 0.5rem;
27
+ }
28
+
29
+ .light {
30
+ --background: 0 0% 94.1%;
31
+ --foreground: 40 10% 15.3%;
32
+ --card: 0 0% 100%;
33
+ --card-foreground: 40 10% 15.3%;
34
+ --popover: 0 0% 100%;
35
+ --popover-foreground: 40 10% 15.3%;
36
+ --primary: 142.1 68.8% 49.4%;
37
+ --primary-foreground: 0 0% 100%;
38
+ --secondary: 40 4.8% 95.9%;
39
+ --secondary-foreground: 40 5.9% 10%;
40
+ --muted: 40 4.8% 95.9%;
41
+ --muted-foreground: 40 3.8% 46.1%;
42
+ --accent: 142.1 68.8% 49.4%;
43
+ --accent-foreground: 0 0% 100%;
44
+ --destructive: 0 84.2% 60.2%;
45
+ --destructive-foreground: 0 0% 98%;
46
+ --border: 40 5.9% 90%;
47
+ --input: 40 5.9% 90%;
48
+ --ring: 142.1 68.8% 49.4%;
49
+ }
50
+ }
51
+
52
+ @layer base {
53
+ * {
54
+ @apply border-border;
55
+ }
56
+ body {
57
+ @apply bg-background text-foreground;
58
+ }
59
+ }
60
+
61
+ .code-area {
62
+ background-color: #1f1f1f;
63
+ color: #f0f0ef;
64
+ }
65
+
66
+ .code-area pre {
67
+ font-family: "Menlo", "Monaco", "Courier New", monospace;
68
+ }
src/app/layout.tsx ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type React from "react"
2
+ import type { Metadata } from "next"
3
+ import { Inter } from "next/font/google"
4
+ import "./globals.css"
5
+ import { ThemeProvider } from "@/components/theme-provider"
6
+ import { TooltipProvider } from "@/components/ui/tooltip"
7
+ import { Toaster } from "sonner"
8
+ import { ModelProvider } from "@/lib/contexts/model-context"
9
+
10
+ const inter = Inter({ subsets: ["latin"] })
11
+
12
+ export const metadata: Metadata = {
13
+ title: "Novita AnySite",
14
+ description: "Create stunning websites with cutting-edge AI models powered by Novita AI.",
15
+ }
16
+
17
+ export default function RootLayout({
18
+ children,
19
+ }: Readonly<{
20
+ children: React.ReactNode
21
+ }>) {
22
+ return (
23
+ <html lang="en" suppressHydrationWarning>
24
+ <body className={inter.className} suppressHydrationWarning>
25
+ <ThemeProvider attribute="class" defaultTheme="dark" enableSystem disableTransitionOnChange>
26
+ <ModelProvider>
27
+ <TooltipProvider>{children}</TooltipProvider>
28
+ <Toaster richColors />
29
+ </ModelProvider>
30
+ </ThemeProvider>
31
+ </body>
32
+ </html>
33
+ )
34
+ }
src/app/page.tsx ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Metadata } from "next";
2
+ import { AppContainer } from "@/components/app-container";
3
+
4
+ export const metadata: Metadata = {
5
+ title: "DeepSite - Create AI-Generated Websites",
6
+ description: "Create beautiful websites using AI with DeepSite",
7
+ };
8
+
9
+ export default function Home() {
10
+ return (
11
+ <main className="flex min-h-screen flex-col">
12
+ <AppContainer />
13
+ </main>
14
+ );
15
+ }
src/components/app-container.tsx ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import { useState, useRef, useEffect } from "react"
4
+ import { CodeEditor } from "./code-editor"
5
+ import { Preview } from "./preview"
6
+ import { PromptInput } from "./prompt-input"
7
+ import { Header } from "./header"
8
+ import { VersionDropdown } from "./version-dropdown"
9
+ import { AuthErrorPopup } from "./auth-error-popup"
10
+ import { toast } from "sonner"
11
+ import { DEFAULT_HTML } from "@/lib/constants"
12
+ import { Version, PreviewRef } from "@/lib/types"
13
+ import { ErrorMessage } from "./ui/error-message"
14
+
15
+ export function AppContainer() {
16
+ const [code, setCode] = useState<string>("");
17
+ const [prompt, setPrompt] = useState<string>("");
18
+ const [loading, setLoading] = useState(false);
19
+ const [initialLoading, setInitialLoading] = useState(true);
20
+ const [showAuthError, setShowAuthError] = useState(false);
21
+ const [currentVersionId, setCurrentVersionId] = useState<string | null>(null);
22
+ const [generationError, setGenerationError] = useState<string | null>(null);
23
+ const [improveError, setImproveError] = useState<string | null>(null);
24
+ const previewRef = useRef<PreviewRef>(null);
25
+ const currentPromptRef = useRef<string>("");
26
+ // Add a ref to track the latest created version ID
27
+ const latestVersionIdRef = useRef<string | null>(null);
28
+
29
+ // Keep a reference to the current prompt for use in callbacks
30
+ useEffect(() => {
31
+ currentPromptRef.current = prompt;
32
+ }, [prompt]);
33
+
34
+ // Initialize current version ID from localStorage on mount
35
+ useEffect(() => {
36
+ setInitialLoading(true);
37
+
38
+ const storedVersions = localStorage.getItem('novita-versions');
39
+ if (storedVersions) {
40
+ const parsedVersions = JSON.parse(storedVersions) as Version[];
41
+ if (parsedVersions.length > 0) {
42
+ // Sort by creation time, newest first
43
+ const sortedVersions = parsedVersions.sort((a, b) => b.createdAt - a.createdAt);
44
+ const newestVersion = sortedVersions[0];
45
+
46
+ // Set the current version ID and also load its code and prompt
47
+ setCurrentVersionId(newestVersion.id);
48
+ latestVersionIdRef.current = newestVersion.id;
49
+ setCode(newestVersion.code);
50
+ setPrompt(newestVersion.prompt);
51
+ currentPromptRef.current = newestVersion.prompt;
52
+ } else {
53
+ // No versions found, set default code but don't create a version
54
+ setCode(DEFAULT_HTML);
55
+ setCurrentVersionId(null);
56
+ latestVersionIdRef.current = null;
57
+ }
58
+ } else {
59
+ // No versions in localStorage, set default code but don't create a version
60
+ setCode(DEFAULT_HTML);
61
+ setCurrentVersionId(null);
62
+ latestVersionIdRef.current = null;
63
+ localStorage.setItem('novita-versions', JSON.stringify([]));
64
+ }
65
+
66
+ // Finish loading after a short delay to ensure everything renders properly
67
+ setTimeout(() => {
68
+ setInitialLoading(false);
69
+ }, 100);
70
+ }, []);
71
+
72
+ const handlePromptSubmit = async (newPrompt: string, colors: string[]) => {
73
+ // Store the current prompt as previous prompt
74
+ const oldPrompt = currentPromptRef.current;
75
+
76
+ // Update prompt state
77
+ setPrompt(newPrompt);
78
+ currentPromptRef.current = newPrompt; // Update the ref immediately
79
+
80
+ // Clear code and preview when submit is pressed
81
+ setCode("");
82
+
83
+ // Create a new version immediately with empty code when submit is clicked
84
+ const newVersionId = saveVersionInitial(newPrompt);
85
+ setCurrentVersionId(newVersionId);
86
+
87
+ if (previewRef.current) {
88
+ // Pass both the new prompt and old prompt for context
89
+ await previewRef.current.generateCode(newPrompt, colors, oldPrompt);
90
+ }
91
+ }
92
+
93
+ // Handle code change and save version
94
+ const handleCodeChange = (newCode: string, save = false) => {
95
+ setCode(newCode);
96
+
97
+ // Save version when code changes and we're not loading
98
+ // This will now only be called once with the final code
99
+ if (save && currentPromptRef.current) {
100
+ // Make sure we're updating the latest version
101
+ updateVersionWithFinalCode(newCode);
102
+ }
103
+ }
104
+
105
+ // Explicitly save version when loading completes
106
+ const handleLoadingChange = (isLoading: boolean) => {
107
+ setLoading(isLoading);
108
+ }
109
+
110
+ // Function to create a new version initially with empty code
111
+ const saveVersionInitial = (promptToSave: string) => {
112
+ // Get existing versions
113
+ const storedVersions = localStorage.getItem('novita-versions');
114
+ let versions: Version[] = storedVersions ? JSON.parse(storedVersions) : [];
115
+
116
+ // Create new version with empty code
117
+ const newVersion: Version = {
118
+ id: Date.now().toString(),
119
+ code: "", // Empty code field initially
120
+ prompt: promptToSave,
121
+ createdAt: Date.now()
122
+ };
123
+
124
+ // Add to beginning (newest first)
125
+ versions = [newVersion, ...versions];
126
+
127
+ // Save to localStorage
128
+ localStorage.setItem('novita-versions', JSON.stringify(versions));
129
+
130
+ // Update current version ID
131
+ setCurrentVersionId(newVersion.id);
132
+ // Also update the latest version ID ref
133
+ latestVersionIdRef.current = newVersion.id;
134
+
135
+ return newVersion.id;
136
+ }
137
+
138
+ // Function to update existing version with final code
139
+ const updateVersionWithFinalCode = (finalCode: string) => {
140
+ // Get existing versions
141
+ const storedVersions = localStorage.getItem('novita-versions');
142
+ if (!storedVersions) return;
143
+
144
+ let versions: Version[] = JSON.parse(storedVersions);
145
+
146
+ if (!versions.length) return;
147
+
148
+ // Prioritize using the latestVersionIdRef, then fall back to currentVersionId, then to the most recent version
149
+ const versionIdToUpdate = latestVersionIdRef.current || currentVersionId || versions[0].id;
150
+
151
+ if (!versionIdToUpdate) return;
152
+
153
+ // Find and update the current version
154
+ const updatedVersions = versions.map(version => {
155
+ if (version.id === versionIdToUpdate) {
156
+ return { ...version, code: finalCode };
157
+ }
158
+ return version;
159
+ });
160
+
161
+ // Save to localStorage
162
+ localStorage.setItem('novita-versions', JSON.stringify(updatedVersions));
163
+
164
+ // Show a subtle toast notification when code generation completes
165
+ toast.success('Generated code saved', {
166
+ position: 'top-right',
167
+ duration: 2000,
168
+ });
169
+ }
170
+
171
+ // Handle selecting a version from the dropdown
172
+ const handleVersionSelect = (version: Version) => {
173
+ setCode(version.code);
174
+ setPrompt(version.prompt);
175
+ currentPromptRef.current = version.prompt;
176
+ setCurrentVersionId(version.id);
177
+ latestVersionIdRef.current = version.id;
178
+ }
179
+
180
+ // Handle clearing all versions
181
+ const handleClearAll = () => {
182
+ // Reset to default HTML
183
+ setCode(DEFAULT_HTML);
184
+ setPrompt('');
185
+ currentPromptRef.current = '';
186
+ setCurrentVersionId(null);
187
+ latestVersionIdRef.current = null;
188
+ }
189
+
190
+ return (
191
+ <div className="flex flex-col h-screen bg-novita-dark text-white">
192
+ <Header
193
+ onVersionSelect={handleVersionSelect}
194
+ currentVersion={currentVersionId || undefined}
195
+ onClearAll={handleClearAll}
196
+ />
197
+ <div className="flex flex-1 overflow-hidden relative">
198
+ <div className="w-1/3 border-r border-novita-gray/20 flex flex-col relative">
199
+ <CodeEditor code={code} isLoading={loading} />
200
+
201
+ <AuthErrorPopup
202
+ show={showAuthError}
203
+ onClose={() => setShowAuthError(false)}
204
+ />
205
+
206
+ <PromptInput
207
+ onSubmit={handlePromptSubmit}
208
+ isLoading={loading}
209
+ initialPrompt={prompt}
210
+ onImproveError={setImproveError}
211
+ />
212
+ </div>
213
+ <div className="w-2/3">
214
+ <Preview
215
+ ref={previewRef}
216
+ initialHtml={code}
217
+ onCodeChange={handleCodeChange}
218
+ onLoadingChange={handleLoadingChange}
219
+ onAuthErrorChange={setShowAuthError}
220
+ onErrorChange={setGenerationError}
221
+ currentVersion={currentVersionId || undefined}
222
+ />
223
+ </div>
224
+
225
+ {initialLoading && (
226
+ <div className="absolute inset-0 bg-novita-dark/80 backdrop-blur-sm flex items-center justify-center z-[999]">
227
+ <div className="p-4 text-center">
228
+ <div className="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-current border-r-transparent align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite]" role="status">
229
+ <span className="!absolute !-m-px !h-px !w-px !overflow-hidden !whitespace-nowrap !border-0 !p-0 ![clip:rect(0,0,0,0)]">Loading...</span>
230
+ </div>
231
+ <p className="mt-2">Preparing the workspace...</p>
232
+ </div>
233
+ </div>
234
+ )}
235
+ </div>
236
+
237
+ {/* 全局错误显示组件 */}
238
+ <ErrorMessage
239
+ message={improveError || generationError}
240
+ onClose={() => {
241
+ if (improveError) {
242
+ setImproveError(null);
243
+ } else if (generationError) {
244
+ setGenerationError(null);
245
+ }
246
+ }}
247
+ />
248
+ </div>
249
+ )
250
+ }
src/components/auth-error-popup.tsx ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react'
2
+
3
+ interface AuthErrorPopupProps {
4
+ show: boolean
5
+ authUrl?: string
6
+ onClose?: () => void
7
+ }
8
+
9
+ export function AuthErrorPopup({
10
+ show,
11
+ authUrl = '/api/auth/login',
12
+ onClose
13
+ }: AuthErrorPopupProps) {
14
+ if (!show) return null
15
+
16
+ return (
17
+ <div className="absolute right-0 bottom-20 mr-4 w-fit px-2.5 bg-gray-800 border border-gray-600 rounded-lg p-4 shadow-xl z-50">
18
+ {onClose && (
19
+ <button
20
+ onClick={onClose}
21
+ className="absolute top-1 right-1 text-gray-400 hover:text-white p-1 rounded-full hover:bg-gray-700 focus:outline-none"
22
+ aria-label="Close"
23
+ >
24
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
25
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
26
+ </svg>
27
+ </button>
28
+ )}
29
+ <div className="flex flex-col items-center">
30
+ <p className="mb-3 text-center">Login to continue</p>
31
+ <a href={authUrl} className="inline-block">
32
+ <img
33
+ src="https://huggingface.co/datasets/huggingface/badges/resolve/main/sign-in-with-huggingface-lg-dark.svg"
34
+ alt="Sign in with Hugging Face"
35
+ />
36
+ </a>
37
+ </div>
38
+ </div>
39
+ )
40
+ }
src/components/code-editor.tsx ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import { useEffect, useRef, useState } from "react";
4
+
5
+ interface CodeEditorProps {
6
+ code: string;
7
+ isLoading?: boolean;
8
+ }
9
+
10
+ export function CodeEditor({ code, isLoading = false }: CodeEditorProps) {
11
+ const containerRef = useRef<HTMLDivElement>(null);
12
+ const [isAtBottom, setIsAtBottom] = useState(true);
13
+
14
+ // Check if user is near bottom when scrolling
15
+ const handleScroll = () => {
16
+ if (!containerRef.current) return;
17
+
18
+ const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
19
+ // Consider "at bottom" if within 30px of the bottom
20
+ const isNearBottom = scrollHeight - scrollTop - clientHeight < 30;
21
+ setIsAtBottom(isNearBottom);
22
+ };
23
+
24
+ useEffect(() => {
25
+ const container = containerRef.current;
26
+ if (container) {
27
+ container.addEventListener('scroll', handleScroll);
28
+ return () => container.removeEventListener('scroll', handleScroll);
29
+ }
30
+ }, []);
31
+
32
+ useEffect(() => {
33
+ // Only auto-scroll if user was already at the bottom
34
+ if (isAtBottom && containerRef.current) {
35
+ containerRef.current.scrollTop = containerRef.current.scrollHeight;
36
+ }
37
+ }, [code, isLoading, isAtBottom]);
38
+
39
+ return (
40
+ <div className="flex-1 overflow-auto p-4">
41
+ <div
42
+ ref={containerRef}
43
+ className="code-area p-4 rounded-md font-mono text-sm h-full overflow-auto border border-novita-gray/20"
44
+ >
45
+ <pre className="whitespace-pre-wrap">
46
+ {code}
47
+ {isLoading && <span className="inline-block h-4 w-2 bg-white/70 animate-pulse"></span>}
48
+ </pre>
49
+ </div>
50
+ </div>
51
+ )
52
+ }
src/components/color-panel.tsx ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import { useState, useEffect, useRef } from "react"
4
+ import { Plus, X } from "lucide-react"
5
+ import { Tooltip, TooltipContent, TooltipTrigger, TooltipProvider } from "@/components/ui/tooltip"
6
+ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
7
+ import { cn } from "@/lib/utils"
8
+ import { Button } from "@/components/ui/button"
9
+
10
+ interface ColorCircleProps {
11
+ color: string
12
+ onClick?: () => void
13
+ onDelete?: () => void
14
+ className?: string
15
+ size?: "sm" | "md"
16
+ showDeleteIcon?: boolean
17
+ }
18
+
19
+ function ColorCircle({ color, onClick, onDelete, className, size = "sm", showDeleteIcon = false }: ColorCircleProps) {
20
+ const sizeClasses = {
21
+ sm: "w-5 h-5",
22
+ md: "w-6 h-6",
23
+ }
24
+
25
+ return (
26
+ <div className="relative group h-5">
27
+ <button
28
+ type="button"
29
+ onClick={onClick}
30
+ className={cn(
31
+ sizeClasses[size],
32
+ "rounded-full border border-white/20 transition-transform hover:scale-110 focus:outline-none focus:ring-2 focus:ring-white/30",
33
+ className,
34
+ )}
35
+ style={{ backgroundColor: color }}
36
+ aria-label={`Color ${color}`}
37
+ />
38
+ {showDeleteIcon && (
39
+ <button
40
+ type="button"
41
+ onClick={(e) => {
42
+ e.stopPropagation();
43
+ onDelete?.();
44
+ }}
45
+ className="absolute -top-1 -right-1 bg-novita-dark rounded-full w-3.5 h-3.5 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity"
46
+ aria-label={`Remove color ${color}`}
47
+ >
48
+ <X className="h-2 w-2 text-white" />
49
+ </button>
50
+ )}
51
+ </div>
52
+ )
53
+ }
54
+
55
+ const PRESET_COLORS = [
56
+ "#23d57c", // Novita green
57
+ "#f43f5e", // Rose
58
+ "#3b82f6", // Blue
59
+ "#eab308", // Yellow
60
+ "#8b5cf6", // Purple
61
+ "#ec4899", // Pink
62
+ "#06b6d4", // Cyan
63
+ "#ef4444", // Red
64
+ "#84cc16", // Lime
65
+ "#14b8a6", // Teal
66
+ "#f97316", // Orange
67
+ "#6366f1", // Indigo
68
+ ]
69
+
70
+ interface ColorPanelProps {
71
+ onColorsChange?: (colors: string[]) => void;
72
+ }
73
+
74
+ export function ColorPanel({ onColorsChange }: ColorPanelProps) {
75
+ const maxColors = 6
76
+ const [colors, setColors] = useState<string[]>([])
77
+ const [isPopoverOpen, setIsPopoverOpen] = useState(false)
78
+ const [selectedColor, setSelectedColor] = useState<string | null>(null)
79
+ const [isAddingDisabled, setIsAddingDisabled] = useState(false)
80
+ const debounceTimeoutRef = useRef<NodeJS.Timeout | null>(null)
81
+
82
+ useEffect(() => {
83
+ return () => {
84
+ if (debounceTimeoutRef.current) {
85
+ clearTimeout(debounceTimeoutRef.current)
86
+ }
87
+ }
88
+ }, [])
89
+
90
+ useEffect(() => {
91
+ // Call onColorsChange when colors change
92
+ onColorsChange?.(colors);
93
+ }, [colors, onColorsChange]);
94
+
95
+ const addColor = () => {
96
+ if (colors.length >= maxColors || !selectedColor || isAddingDisabled) return
97
+
98
+ if (!colors.includes(selectedColor)) {
99
+ setIsAddingDisabled(true)
100
+ setColors([...colors, selectedColor])
101
+ setSelectedColor(null)
102
+ setIsPopoverOpen(false)
103
+
104
+ // Debounce to prevent rapid clicking
105
+ debounceTimeoutRef.current = setTimeout(() => {
106
+ setIsAddingDisabled(false)
107
+ }, 500)
108
+ }
109
+ }
110
+
111
+ const selectColor = (color: string) => {
112
+ setSelectedColor(color)
113
+ }
114
+
115
+ const removeColor = (indexToRemove: number) => {
116
+ setColors(colors.filter((_, index) => index !== indexToRemove))
117
+ }
118
+
119
+ return (
120
+ <TooltipProvider>
121
+ <div className="absolute top-2.5 left-3 flex items-center gap-1.5 z-10">
122
+ {colors.map((color, index) => (
123
+ <ColorCircle
124
+ key={`${color}-${index}`}
125
+ color={color}
126
+ onClick={() => removeColor(index)}
127
+ onDelete={() => removeColor(index)}
128
+ showDeleteIcon={true}
129
+ />
130
+ ))}
131
+
132
+ {colors.length < maxColors && (
133
+ <Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
134
+ <Tooltip>
135
+ <TooltipTrigger asChild>
136
+ <PopoverTrigger asChild>
137
+ <button
138
+ type="button"
139
+ className="w-5 h-5 rounded-full bg-novita-gray/30 border border-novita-gray/50 flex items-center justify-center transition-transform hover:scale-110 hover:bg-novita-gray/40 focus:outline-none focus:ring-2 focus:ring-white/30"
140
+ aria-label="Add color"
141
+ >
142
+ <Plus className="h-2.5 w-2.5 text-white" />
143
+ </button>
144
+ </PopoverTrigger>
145
+ </TooltipTrigger>
146
+ <TooltipContent className="bg-novita-dark border-novita-gray/30 text-white">
147
+ <p>Choose Site Color palette</p>
148
+ </TooltipContent>
149
+ </Tooltip>
150
+ <PopoverContent
151
+ className="w-64 p-3 bg-novita-dark border-novita-gray/30 rounded-md shadow-lg"
152
+ align="start"
153
+ sideOffset={5}
154
+ >
155
+ <div className="space-y-3">
156
+ <h3 className="text-sm font-medium text-white">Color Picker</h3>
157
+ <div className="grid grid-cols-6 gap-2">
158
+ {PRESET_COLORS.map((color) => (
159
+ <ColorCircle
160
+ key={color}
161
+ color={color}
162
+ size="md"
163
+ onClick={() => selectColor(color)}
164
+ className={cn(
165
+ selectedColor === color && "ring-2 ring-white/50",
166
+ colors.includes(color) && "opacity-50"
167
+ )}
168
+ />
169
+ ))}
170
+ </div>
171
+ <div className="pt-2 border-t border-novita-gray/30">
172
+ <label className="block text-xs text-novita-gray mb-1.5">Custom color</label>
173
+ <input
174
+ type="color"
175
+ className="w-full h-8 bg-transparent border border-novita-gray/30 rounded cursor-pointer"
176
+ onChange={(e) => selectColor(e.target.value)}
177
+ value={selectedColor || "#ffffff"}
178
+ />
179
+ </div>
180
+ <div className="mt-3">
181
+ <Button
182
+ onClick={addColor}
183
+ disabled={!selectedColor || isAddingDisabled}
184
+ className="w-full h-6 bg-novita-white hover:bg-novita-gray/90 text-white rounded border border-novita-gray/90"
185
+ >
186
+ Add
187
+ </Button>
188
+ </div>
189
+ </div>
190
+ </PopoverContent>
191
+ </Popover>
192
+ )}
193
+ </div>
194
+ </TooltipProvider>
195
+ )
196
+ }
src/components/header.tsx ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+ import { Logo } from "./logo"
3
+ import { ModelSelector } from "./model-selector"
4
+ import { VersionDropdown } from "./version-dropdown"
5
+
6
+ export function Header({
7
+ onVersionSelect,
8
+ currentVersion,
9
+ onClearAll
10
+ }: {
11
+ onVersionSelect?: (version: any) => void,
12
+ currentVersion?: string,
13
+ onClearAll?: () => void
14
+ }) {
15
+ return (
16
+ <header className="border-b border-novita-gray/20 p-3 flex justify-between items-center bg-novita-dark">
17
+ <div className="flex items-center gap-3">
18
+ <div>
19
+ <div className="flex items-center gap-2">
20
+ <span className="text-l text-white">Build any site using</span>
21
+ <div className="flex items-center">
22
+ <ModelSelector />
23
+ {onVersionSelect && (
24
+ <VersionDropdown
25
+ onVersionSelect={onVersionSelect}
26
+ currentVersion={currentVersion}
27
+ onClearAll={onClearAll}
28
+ />
29
+ )}
30
+ </div>
31
+ </div>
32
+ <span className="text-sm text-white/40"><a href="https://novita.ai/models/llm" target="_blank" className="underline hover:text-white/70 transition-colors duration-200">Powered by novita.ai</a></span>
33
+ </div>
34
+ </div>
35
+ </header>
36
+ )
37
+ }
src/components/logo.tsx ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export function Logo() {
2
+ return (
3
+ <svg
4
+ width="32"
5
+ height="32"
6
+ viewBox="0 0 100 100"
7
+ fill="none"
8
+ xmlns="http://www.w3.org/2000/svg"
9
+ className="text-novita-green"
10
+ >
11
+ <path d="M50 0L100 50H50L0 0H50Z" fill="currentColor" />
12
+ </svg>
13
+ )
14
+ }
src/components/model-selector.tsx ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import { useState, useEffect, useRef } from 'react'
4
+ import { MODEL_CONFIG_CODE_GENERATION } from '@/lib/constants'
5
+ import { useModel } from '@/lib/contexts/model-context'
6
+ import { ChevronDown } from 'lucide-react'
7
+
8
+ export function ModelSelector() {
9
+ const { selectedModelIndex, setSelectedModelIndex } = useModel()
10
+ const [isOpen, setIsOpen] = useState(false)
11
+ const selectorRef = useRef<HTMLDivElement>(null)
12
+
13
+ const handleSelect = (index: number) => {
14
+ setSelectedModelIndex(index)
15
+ setIsOpen(false)
16
+ }
17
+
18
+ useEffect(() => {
19
+ const handleClickOutside = (event: MouseEvent) => {
20
+ if (selectorRef.current && !selectorRef.current.contains(event.target as Node)) {
21
+ setIsOpen(false)
22
+ }
23
+ }
24
+
25
+ if (isOpen) {
26
+ document.addEventListener('mousedown', handleClickOutside)
27
+ }
28
+
29
+ return () => {
30
+ document.removeEventListener('mousedown', handleClickOutside)
31
+ }
32
+ }, [isOpen])
33
+
34
+ return (
35
+ <div className="relative inline-block text-left" ref={selectorRef}>
36
+ <div
37
+ onClick={() => setIsOpen(!isOpen)}
38
+ className={`
39
+ cursor-pointer flex items-center justify-between gap-2
40
+ text-white/90 hover:text-white
41
+ px-3 py-1.5 rounded-md
42
+ border border-novita-gray/30 hover:border-novita-gray/60
43
+ bg-novita-gray/5 hover:bg-novita-gray/20
44
+ transition-all duration-200 ease-in-out
45
+ ${isOpen ? 'border-novita-gray/60 bg-novita-gray/20' : ''}
46
+ `}
47
+ >
48
+ <span className="text-sm">{MODEL_CONFIG_CODE_GENERATION[selectedModelIndex]?.id || 'Select model'}</span>
49
+ <ChevronDown className={`w-4 h-4 transition-transform duration-200 ${isOpen ? 'transform rotate-180' : ''}`} />
50
+ </div>
51
+
52
+ {isOpen && (
53
+ <div className="absolute z-40 mt-1 w-full origin-top-right rounded-md bg-novita-dark border border-novita-gray/20 shadow-lg">
54
+ <div className="py-1">
55
+ {MODEL_CONFIG_CODE_GENERATION.map((model, index) => (
56
+ <div
57
+ key={model.id}
58
+ onClick={() => handleSelect(index)}
59
+ className={`
60
+ px-4 py-2 text-xs cursor-pointer hover:bg-novita-gray/20
61
+ ${selectedModelIndex === index ? 'text-white bg-novita-gray/40' : 'text-white/70'}
62
+ `}
63
+ >
64
+ {model.id}
65
+ </div>
66
+ ))}
67
+ </div>
68
+ </div>
69
+ )}
70
+ </div>
71
+ )
72
+ }
src/components/preview.tsx ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import { useState, forwardRef, useImperativeHandle, useEffect, useRef } from "react"
4
+ import { DEFAULT_HTML } from "@/lib/constants"
5
+ import { PreviewRef, Version } from "@/lib/types"
6
+ import { MinimizeIcon, MaximizeIcon, DownloadIcon } from "./ui/fullscreen-icons"
7
+ import { useModel } from "@/lib/contexts/model-context"
8
+ import { cn } from "@/lib/utils"
9
+
10
+ interface PreviewProps {
11
+ initialHtml?: string;
12
+ onCodeChange?: (html: string, save?: boolean) => void;
13
+ onAuthErrorChange?: (show: boolean) => void;
14
+ onLoadingChange?: (loading: boolean) => void;
15
+ onErrorChange?: (error: string | null) => void;
16
+ currentVersion?: string;
17
+ }
18
+
19
+ export const Preview = forwardRef<PreviewRef, PreviewProps>(function Preview(
20
+ { initialHtml, onCodeChange, onAuthErrorChange, onLoadingChange, onErrorChange, currentVersion },
21
+ ref
22
+ ) {
23
+ const [html, setHtml] = useState<string>(initialHtml || "");
24
+ const [isFullscreen, setIsFullscreen] = useState(false);
25
+ const [loading, setLoading] = useState(false);
26
+ const [error, setError] = useState<string | null>(null);
27
+ const [showAuthError, setShowAuthError] = useState(false);
28
+ const { selectedModelId } = useModel();
29
+
30
+ // Update html when initialHtml changes
31
+ useEffect(() => {
32
+ if (initialHtml) {
33
+ setHtml(initialHtml);
34
+ }
35
+ }, [initialHtml]);
36
+
37
+ // Update parent component when error changes
38
+ useEffect(() => {
39
+ if (onErrorChange) {
40
+ onErrorChange(error);
41
+ }
42
+ }, [error, onErrorChange]);
43
+
44
+ useImperativeHandle(ref, () => ({
45
+ generateCode: async (prompt: string, colors: string[] = [], previousPrompt?: string) => {
46
+ await generateCode(prompt, colors, previousPrompt);
47
+ }
48
+ }));
49
+
50
+ const toggleFullscreen = () => {
51
+ setIsFullscreen(!isFullscreen);
52
+ };
53
+
54
+ const downloadHtml = () => {
55
+ if (!html) return;
56
+
57
+ // Get current version and generate filename
58
+ // If we have a currentVersion, use it; otherwise omit version part
59
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, 19);
60
+
61
+ // Load current version from localStorage if we have an ID
62
+ let versionLabel = "";
63
+ if (currentVersion) {
64
+ versionLabel = `-${currentVersion}`;
65
+ }
66
+
67
+ // Format the filename with or without version
68
+ const filename = `novita-anysite-generated${versionLabel}-${timestamp}.html`;
69
+
70
+ const blob = new Blob([html], { type: 'text/html' });
71
+ const url = window.URL.createObjectURL(blob);
72
+ const a = document.createElement('a');
73
+ a.href = url;
74
+ a.download = filename;
75
+ document.body.appendChild(a);
76
+ a.click();
77
+ window.URL.revokeObjectURL(url);
78
+ document.body.removeChild(a);
79
+ };
80
+
81
+ const generateCode = async (prompt: string, colors: string[] = [], previousPrompt?: string) => {
82
+ setLoading(true);
83
+ if (onLoadingChange) {
84
+ onLoadingChange(true);
85
+ }
86
+ setError(null);
87
+ setShowAuthError(false);
88
+ if (onAuthErrorChange) {
89
+ onAuthErrorChange(false);
90
+ }
91
+
92
+ // Clear HTML content when generation starts
93
+ setHtml("");
94
+
95
+ // Initialize generated code variable at function scope so it's accessible in finally block
96
+ let generatedCode = '';
97
+
98
+ try {
99
+ // Only include html in the request if it's not DEFAULT_HTML
100
+ const isDefaultHtml = initialHtml === DEFAULT_HTML;
101
+
102
+ const response = await fetch('/api/generate-code', {
103
+ method: 'POST',
104
+ headers: {
105
+ 'Content-Type': 'application/json',
106
+ },
107
+ body: JSON.stringify({
108
+ prompt,
109
+ html: isDefaultHtml ? undefined : html,
110
+ previousPrompt: isDefaultHtml ? undefined : previousPrompt,
111
+ colors,
112
+ modelId: selectedModelId
113
+ }),
114
+ });
115
+
116
+ if (!response.ok) {
117
+ // Check specifically for 403 error (authentication required)
118
+ if (response.status === 401 || response.status === 403) {
119
+ setShowAuthError(true);
120
+ if (onAuthErrorChange) {
121
+ onAuthErrorChange(true);
122
+ }
123
+ throw new Error('Authentication required');
124
+ }
125
+
126
+ const errorData = await response.json();
127
+ throw new Error(errorData.message || 'Failed to generate code');
128
+ }
129
+
130
+ // Handle streaming response
131
+ const reader = response.body?.getReader();
132
+ const decoder = new TextDecoder();
133
+ let lastRenderTime = 0;
134
+
135
+ if (reader) {
136
+ while (true) {
137
+ const { done, value } = await reader.read();
138
+ if (done) {
139
+ if (!generatedCode.includes('</html>')) {
140
+ generatedCode += '</html>';
141
+ }
142
+ const finalCode = generatedCode.match(
143
+ /<!DOCTYPE html>[\s\S]*<\/html>/
144
+ )?.[0];
145
+ if (finalCode) {
146
+ // Update state with the final code
147
+ setHtml(finalCode);
148
+ // Only call onCodeChange once with the final code
149
+ // Add a small delay to ensure all state updates have been applied
150
+ if (onCodeChange) {
151
+ setTimeout(() => {
152
+ onCodeChange(finalCode, true);
153
+ }, 50);
154
+ }
155
+ }
156
+ break;
157
+ }
158
+
159
+ const chunkText = decoder.decode(value, { stream: true });
160
+ let parsedChunk: any;
161
+ try {
162
+ // Try to parse as JSON
163
+ parsedChunk = JSON.parse(chunkText);
164
+ } catch (parseError) {
165
+ // If JSON parsing fails, treat it as plain text (backwards compatibility)
166
+ generatedCode += chunkText;
167
+ }
168
+ if (parsedChunk && parsedChunk.type === "error") {
169
+ throw new Error(parsedChunk.message || "An error occurred");
170
+ }
171
+ const newCode = generatedCode.match(/<!DOCTYPE html>[\s\S]*/)?.[0];
172
+ if (newCode) {
173
+ // Force-close the HTML tag so the iframe doesn't render half-finished markup
174
+ let partialDoc = newCode;
175
+ if (!partialDoc.endsWith("</html>")) {
176
+ partialDoc += "\n</html>";
177
+ }
178
+
179
+ // Throttle the re-renders to avoid flashing/flicker
180
+ const now = Date.now();
181
+ if (now - lastRenderTime > 200) {
182
+ // Update the UI with partial code, but don't call onCodeChange
183
+ setHtml(partialDoc);
184
+ if (onCodeChange) {
185
+ onCodeChange(partialDoc, false);
186
+ }
187
+
188
+ lastRenderTime = now;
189
+ }
190
+ }
191
+ }
192
+ }
193
+ } catch (err) {
194
+ const errorMessage = (err as Error).message || 'An error occurred while generating code';
195
+ setError(errorMessage);
196
+ if (onErrorChange) {
197
+ onErrorChange(errorMessage);
198
+ }
199
+ console.error('Error generating code:', err);
200
+ } finally {
201
+ setLoading(false);
202
+ if (onLoadingChange) {
203
+ onLoadingChange(false);
204
+ }
205
+ }
206
+ };
207
+
208
+ return (
209
+ <div className={`${isFullscreen ? 'fixed inset-0 z-10 bg-novita-dark' : 'h-full'} p-4`}>
210
+ <div className="bg-white text-black rounded-md h-full overflow-hidden relative isolation-auto">
211
+ <div className="absolute top-3 right-3 flex gap-2 z-[100]">
212
+ <button
213
+ onClick={downloadHtml}
214
+ className="bg-novita-gray/90 text-white p-2 rounded-md shadow-md hover:bg-novita-gray/70 transition-colors flex items-center justify-center"
215
+ aria-label="Download HTML"
216
+ title="Download HTML"
217
+ >
218
+ <DownloadIcon />
219
+ </button>
220
+ <button
221
+ onClick={toggleFullscreen}
222
+ className="bg-novita-gray/90 text-white p-2 rounded-md shadow-md hover:bg-novita-gray/70 transition-colors flex items-center justify-center"
223
+ aria-label={isFullscreen ? 'Exit Fullscreen' : 'Full Screen'}
224
+ title={isFullscreen ? 'Exit Fullscreen' : 'Full Screen'}
225
+ >
226
+ {isFullscreen ? <MinimizeIcon /> : <MaximizeIcon />}
227
+ </button>
228
+ </div>
229
+ <iframe
230
+ className={cn("relative z-10 w-full h-full select-none", {
231
+ "pointer-events-none": loading,
232
+ })}
233
+ srcDoc={html}
234
+ title="Preview"
235
+ />
236
+ </div>
237
+ </div>
238
+ )
239
+ });
src/components/prompt-input.tsx ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import type React from "react"
4
+
5
+ import { useState, useEffect } from "react"
6
+ import { Wand2, ArrowUp, Loader2, Maximize2, Minimize2 } from "lucide-react"
7
+ import { Button } from "@/components/ui/button"
8
+ import { Textarea } from "@/components/ui/textarea"
9
+ import { ColorPanel } from "./color-panel"
10
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
11
+ import { FullscreenToggle } from "./ui/fullscreen-toggle"
12
+
13
+ interface PromptInputProps {
14
+ onSubmit: (prompt: string, colors: string[]) => Promise<void>;
15
+ isLoading?: boolean;
16
+ initialPrompt?: string;
17
+ onImproveError?: (error: string | null) => void;
18
+ }
19
+
20
+ export function PromptInput({
21
+ onSubmit,
22
+ isLoading = false,
23
+ initialPrompt = "",
24
+ onImproveError
25
+ }: PromptInputProps) {
26
+ const [prompt, setPrompt] = useState(initialPrompt);
27
+ const [isImprovingPrompt, setIsImprovingPrompt] = useState(false);
28
+ const [isFullScreen, setIsFullScreen] = useState(false);
29
+ const [selectedColors, setSelectedColors] = useState<string[]>([]);
30
+ const [improveError, setImproveError] = useState<string | null>(null);
31
+
32
+ // Update prompt when initialPrompt changes
33
+ useEffect(() => {
34
+ setPrompt(initialPrompt);
35
+ }, [initialPrompt]);
36
+
37
+ // 当 improveError 改变时通知父组件
38
+ useEffect(() => {
39
+ if (onImproveError) {
40
+ onImproveError(improveError);
41
+ }
42
+ }, [improveError, onImproveError]);
43
+
44
+ const handleSubmit = async (e: React.FormEvent) => {
45
+ e.preventDefault();
46
+ if (prompt.trim() === '' || isLoading) return;
47
+
48
+ // Clear any previous errors
49
+ setImproveError(null);
50
+ await onSubmit(prompt, selectedColors);
51
+ }
52
+
53
+ const improvePrompt = async () => {
54
+ if (prompt.trim() === '' || isImprovingPrompt || isLoading) return;
55
+
56
+ // Clear previous errors
57
+ setImproveError(null);
58
+ setIsImprovingPrompt(true);
59
+
60
+ try {
61
+ const response = await fetch("/api/improve-prompt", {
62
+ method: "POST",
63
+ headers: {
64
+ "Content-Type": "application/json",
65
+ },
66
+ body: JSON.stringify({ prompt: prompt.trim() }),
67
+ })
68
+
69
+ if (!response.ok) {
70
+ const errorText = await response.text()
71
+ throw new Error(errorText || `Failed to improve prompt (${response.status})`);
72
+ }
73
+
74
+ if (!response.body) {
75
+ throw new Error("Response body is null");
76
+ }
77
+
78
+ // Handle streaming response
79
+ const reader = response.body.getReader();
80
+ let improvedPrompt = "";
81
+
82
+ let textDecoder = new TextDecoder();
83
+ while (true) {
84
+ const { done, value } = await reader.read();
85
+ if (done) break;
86
+
87
+ const chunkText = textDecoder.decode(value, { stream: true });
88
+ let parsedChunk: any;
89
+ try {
90
+ // Parse the JSON response
91
+ parsedChunk = JSON.parse(chunkText);
92
+ } catch (parseError) {
93
+ // If JSON parsing fails, treat it as plain text (backwards compatibility)
94
+ improvedPrompt += chunkText
95
+ setPrompt(improvedPrompt)
96
+ }
97
+ if (parsedChunk && parsedChunk.type === "error") {
98
+ throw new Error(parsedChunk.message || "An error occurred");
99
+ }
100
+ }
101
+ } catch (error) {
102
+ console.error("Error improving prompt:", error)
103
+ setImproveError(error instanceof Error ? error.message : "Failed to improve prompt")
104
+ } finally {
105
+ setIsImprovingPrompt(false)
106
+ }
107
+ }
108
+
109
+ const toggleFullScreen = () => {
110
+ setIsFullScreen(!isFullScreen)
111
+ }
112
+
113
+ const handleColorsChange = (colors: string[]) => {
114
+ setSelectedColors(colors);
115
+ }
116
+
117
+ const isPromptTooShort = prompt.length < 10
118
+
119
+ return (
120
+ <div className={`border-t border-novita-gray/20 p-4 relative transition-all duration-300 ease-in-out ${isFullScreen ? 'h-[50vh]' : ''}`}>
121
+ <div className="absolute top-1 right-1 z-10">
122
+ <FullscreenToggle
123
+ isFullScreen={isFullScreen}
124
+ onClick={toggleFullScreen}
125
+ />
126
+ </div>
127
+ <form onSubmit={handleSubmit} className="flex flex-col gap-4 h-full">
128
+ <div className={`relative ${isFullScreen ? 'h-full' : ''}`}>
129
+ <ColorPanel onColorsChange={handleColorsChange} />
130
+ <Textarea
131
+ value={prompt}
132
+ onChange={(e) => {
133
+ setPrompt(e.target.value)
134
+ // Clear error when user types
135
+ if (improveError) setImproveError(null)
136
+ }}
137
+ placeholder="Describe what site you want to build. E.g., Create a landing page with a hero section, features grid, and a contact form..."
138
+ className={`min-h-24 pr-20 pt-12 bg-novita-gray/20 border-novita-gray/30 text-white placeholder:text-novita-gray/70 resize-none ${isFullScreen ? 'h-full' : ''}`}
139
+ disabled={isLoading || isImprovingPrompt}
140
+ />
141
+ <div className="absolute bottom-3 right-3 flex gap-2">
142
+ <TooltipProvider>
143
+ <Tooltip>
144
+ <TooltipTrigger asChild>
145
+ <span>
146
+ <Button
147
+ type="button"
148
+ size="icon"
149
+ variant="outline"
150
+ className="h-8 w-8 bg-novita-gray/20 border-novita-gray/30 text-white hover:bg-novita-gray/30"
151
+ disabled={isPromptTooShort || isLoading || isImprovingPrompt}
152
+ onClick={improvePrompt}
153
+ >
154
+ {isImprovingPrompt ? (
155
+ <Loader2 className="h-4 w-4 animate-spin" />
156
+ ) : (
157
+ <Wand2 className="h-4 w-4" />
158
+ )}
159
+ <span className="sr-only">Magic wand</span>
160
+ </Button>
161
+ </span>
162
+ </TooltipTrigger>
163
+ {isPromptTooShort && (
164
+ <TooltipContent className="bg-novita-gray text-white">
165
+ <p>Your prompt is too simple, we can't improve it.</p>
166
+ </TooltipContent>
167
+ )}
168
+ </Tooltip>
169
+ </TooltipProvider>
170
+ <Button
171
+ type="submit"
172
+ size="icon"
173
+ className="h-8 w-8 bg-novita-green text-black hover:bg-novita-green/90"
174
+ disabled={prompt.trim() === "" || isLoading || isImprovingPrompt}
175
+ >
176
+ {isLoading ? (
177
+ <Loader2 className="h-4 w-4 animate-spin" />
178
+ ) : (
179
+ <ArrowUp className="h-4 w-4" />
180
+ )}
181
+ <span className="sr-only">Submit</span>
182
+ </Button>
183
+ </div>
184
+ </div>
185
+ </form>
186
+ </div>
187
+ )
188
+ }
src/components/theme-provider.tsx ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import {
5
+ ThemeProvider as NextThemesProvider,
6
+ type ThemeProviderProps,
7
+ } from 'next-themes'
8
+
9
+ export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
10
+ return <NextThemesProvider {...props}>{children}</NextThemesProvider>
11
+ }
src/components/ui/accordion.tsx ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as AccordionPrimitive from "@radix-ui/react-accordion"
5
+ import { ChevronDown } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ const Accordion = AccordionPrimitive.Root
10
+
11
+ const AccordionItem = React.forwardRef<
12
+ React.ElementRef<typeof AccordionPrimitive.Item>,
13
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
14
+ >(({ className, ...props }, ref) => (
15
+ <AccordionPrimitive.Item
16
+ ref={ref}
17
+ className={cn("border-b", className)}
18
+ {...props}
19
+ />
20
+ ))
21
+ AccordionItem.displayName = "AccordionItem"
22
+
23
+ const AccordionTrigger = React.forwardRef<
24
+ React.ElementRef<typeof AccordionPrimitive.Trigger>,
25
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
26
+ >(({ className, children, ...props }, ref) => (
27
+ <AccordionPrimitive.Header className="flex">
28
+ <AccordionPrimitive.Trigger
29
+ ref={ref}
30
+ className={cn(
31
+ "flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
32
+ className
33
+ )}
34
+ {...props}
35
+ >
36
+ {children}
37
+ <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
38
+ </AccordionPrimitive.Trigger>
39
+ </AccordionPrimitive.Header>
40
+ ))
41
+ AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
42
+
43
+ const AccordionContent = React.forwardRef<
44
+ React.ElementRef<typeof AccordionPrimitive.Content>,
45
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
46
+ >(({ className, children, ...props }, ref) => (
47
+ <AccordionPrimitive.Content
48
+ ref={ref}
49
+ className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
50
+ {...props}
51
+ >
52
+ <div className={cn("pb-4 pt-0", className)}>{children}</div>
53
+ </AccordionPrimitive.Content>
54
+ ))
55
+
56
+ AccordionContent.displayName = AccordionPrimitive.Content.displayName
57
+
58
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
src/components/ui/alert-dialog.tsx ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
5
+
6
+ import { cn } from "@/lib/utils"
7
+ import { buttonVariants } from "@/components/ui/button"
8
+
9
+ const AlertDialog = AlertDialogPrimitive.Root
10
+
11
+ const AlertDialogTrigger = AlertDialogPrimitive.Trigger
12
+
13
+ const AlertDialogPortal = AlertDialogPrimitive.Portal
14
+
15
+ const AlertDialogOverlay = React.forwardRef<
16
+ React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
17
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
18
+ >(({ className, ...props }, ref) => (
19
+ <AlertDialogPrimitive.Overlay
20
+ className={cn(
21
+ "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
22
+ className
23
+ )}
24
+ {...props}
25
+ ref={ref}
26
+ />
27
+ ))
28
+ AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
29
+
30
+ const AlertDialogContent = React.forwardRef<
31
+ React.ElementRef<typeof AlertDialogPrimitive.Content>,
32
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
33
+ >(({ className, ...props }, ref) => (
34
+ <AlertDialogPortal>
35
+ <AlertDialogOverlay />
36
+ <AlertDialogPrimitive.Content
37
+ ref={ref}
38
+ className={cn(
39
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
40
+ className
41
+ )}
42
+ {...props}
43
+ />
44
+ </AlertDialogPortal>
45
+ ))
46
+ AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
47
+
48
+ const AlertDialogHeader = ({
49
+ className,
50
+ ...props
51
+ }: React.HTMLAttributes<HTMLDivElement>) => (
52
+ <div
53
+ className={cn(
54
+ "flex flex-col space-y-2 text-center sm:text-left",
55
+ className
56
+ )}
57
+ {...props}
58
+ />
59
+ )
60
+ AlertDialogHeader.displayName = "AlertDialogHeader"
61
+
62
+ const AlertDialogFooter = ({
63
+ className,
64
+ ...props
65
+ }: React.HTMLAttributes<HTMLDivElement>) => (
66
+ <div
67
+ className={cn(
68
+ "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
69
+ className
70
+ )}
71
+ {...props}
72
+ />
73
+ )
74
+ AlertDialogFooter.displayName = "AlertDialogFooter"
75
+
76
+ const AlertDialogTitle = React.forwardRef<
77
+ React.ElementRef<typeof AlertDialogPrimitive.Title>,
78
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
79
+ >(({ className, ...props }, ref) => (
80
+ <AlertDialogPrimitive.Title
81
+ ref={ref}
82
+ className={cn("text-lg font-semibold", className)}
83
+ {...props}
84
+ />
85
+ ))
86
+ AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
87
+
88
+ const AlertDialogDescription = React.forwardRef<
89
+ React.ElementRef<typeof AlertDialogPrimitive.Description>,
90
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
91
+ >(({ className, ...props }, ref) => (
92
+ <AlertDialogPrimitive.Description
93
+ ref={ref}
94
+ className={cn("text-sm text-muted-foreground", className)}
95
+ {...props}
96
+ />
97
+ ))
98
+ AlertDialogDescription.displayName =
99
+ AlertDialogPrimitive.Description.displayName
100
+
101
+ const AlertDialogAction = React.forwardRef<
102
+ React.ElementRef<typeof AlertDialogPrimitive.Action>,
103
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
104
+ >(({ className, ...props }, ref) => (
105
+ <AlertDialogPrimitive.Action
106
+ ref={ref}
107
+ className={cn(buttonVariants(), className)}
108
+ {...props}
109
+ />
110
+ ))
111
+ AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
112
+
113
+ const AlertDialogCancel = React.forwardRef<
114
+ React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
115
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
116
+ >(({ className, ...props }, ref) => (
117
+ <AlertDialogPrimitive.Cancel
118
+ ref={ref}
119
+ className={cn(
120
+ buttonVariants({ variant: "outline" }),
121
+ "mt-2 sm:mt-0",
122
+ className
123
+ )}
124
+ {...props}
125
+ />
126
+ ))
127
+ AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
128
+
129
+ export {
130
+ AlertDialog,
131
+ AlertDialogPortal,
132
+ AlertDialogOverlay,
133
+ AlertDialogTrigger,
134
+ AlertDialogContent,
135
+ AlertDialogHeader,
136
+ AlertDialogFooter,
137
+ AlertDialogTitle,
138
+ AlertDialogDescription,
139
+ AlertDialogAction,
140
+ AlertDialogCancel,
141
+ }
src/components/ui/alert.tsx ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const alertVariants = cva(
7
+ "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-background text-foreground",
12
+ destructive:
13
+ "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ variant: "default",
18
+ },
19
+ }
20
+ )
21
+
22
+ const Alert = React.forwardRef<
23
+ HTMLDivElement,
24
+ React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
25
+ >(({ className, variant, ...props }, ref) => (
26
+ <div
27
+ ref={ref}
28
+ role="alert"
29
+ className={cn(alertVariants({ variant }), className)}
30
+ {...props}
31
+ />
32
+ ))
33
+ Alert.displayName = "Alert"
34
+
35
+ const AlertTitle = React.forwardRef<
36
+ HTMLParagraphElement,
37
+ React.HTMLAttributes<HTMLHeadingElement>
38
+ >(({ className, ...props }, ref) => (
39
+ <h5
40
+ ref={ref}
41
+ className={cn("mb-1 font-medium leading-none tracking-tight", className)}
42
+ {...props}
43
+ />
44
+ ))
45
+ AlertTitle.displayName = "AlertTitle"
46
+
47
+ const AlertDescription = React.forwardRef<
48
+ HTMLParagraphElement,
49
+ React.HTMLAttributes<HTMLParagraphElement>
50
+ >(({ className, ...props }, ref) => (
51
+ <div
52
+ ref={ref}
53
+ className={cn("text-sm [&_p]:leading-relaxed", className)}
54
+ {...props}
55
+ />
56
+ ))
57
+ AlertDescription.displayName = "AlertDescription"
58
+
59
+ export { Alert, AlertTitle, AlertDescription }
src/components/ui/aspect-ratio.tsx ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
4
+
5
+ const AspectRatio = AspectRatioPrimitive.Root
6
+
7
+ export { AspectRatio }
src/components/ui/avatar.tsx ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as AvatarPrimitive from "@radix-ui/react-avatar"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const Avatar = React.forwardRef<
9
+ React.ElementRef<typeof AvatarPrimitive.Root>,
10
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
11
+ >(({ className, ...props }, ref) => (
12
+ <AvatarPrimitive.Root
13
+ ref={ref}
14
+ className={cn(
15
+ "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
16
+ className
17
+ )}
18
+ {...props}
19
+ />
20
+ ))
21
+ Avatar.displayName = AvatarPrimitive.Root.displayName
22
+
23
+ const AvatarImage = React.forwardRef<
24
+ React.ElementRef<typeof AvatarPrimitive.Image>,
25
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
26
+ >(({ className, ...props }, ref) => (
27
+ <AvatarPrimitive.Image
28
+ ref={ref}
29
+ className={cn("aspect-square h-full w-full", className)}
30
+ {...props}
31
+ />
32
+ ))
33
+ AvatarImage.displayName = AvatarPrimitive.Image.displayName
34
+
35
+ const AvatarFallback = React.forwardRef<
36
+ React.ElementRef<typeof AvatarPrimitive.Fallback>,
37
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
38
+ >(({ className, ...props }, ref) => (
39
+ <AvatarPrimitive.Fallback
40
+ ref={ref}
41
+ className={cn(
42
+ "flex h-full w-full items-center justify-center rounded-full bg-muted",
43
+ className
44
+ )}
45
+ {...props}
46
+ />
47
+ ))
48
+ AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
49
+
50
+ export { Avatar, AvatarImage, AvatarFallback }
src/components/ui/badge.tsx ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const badgeVariants = cva(
7
+ "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default:
12
+ "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
13
+ secondary:
14
+ "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15
+ destructive:
16
+ "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
17
+ outline: "text-foreground",
18
+ },
19
+ },
20
+ defaultVariants: {
21
+ variant: "default",
22
+ },
23
+ }
24
+ )
25
+
26
+ export interface BadgeProps
27
+ extends React.HTMLAttributes<HTMLDivElement>,
28
+ VariantProps<typeof badgeVariants> {}
29
+
30
+ function Badge({ className, variant, ...props }: BadgeProps) {
31
+ return (
32
+ <div className={cn(badgeVariants({ variant }), className)} {...props} />
33
+ )
34
+ }
35
+
36
+ export { Badge, badgeVariants }
src/components/ui/breadcrumb.tsx ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { ChevronRight, MoreHorizontal } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const Breadcrumb = React.forwardRef<
8
+ HTMLElement,
9
+ React.ComponentPropsWithoutRef<"nav"> & {
10
+ separator?: React.ReactNode
11
+ }
12
+ >(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
13
+ Breadcrumb.displayName = "Breadcrumb"
14
+
15
+ const BreadcrumbList = React.forwardRef<
16
+ HTMLOListElement,
17
+ React.ComponentPropsWithoutRef<"ol">
18
+ >(({ className, ...props }, ref) => (
19
+ <ol
20
+ ref={ref}
21
+ className={cn(
22
+ "flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
23
+ className
24
+ )}
25
+ {...props}
26
+ />
27
+ ))
28
+ BreadcrumbList.displayName = "BreadcrumbList"
29
+
30
+ const BreadcrumbItem = React.forwardRef<
31
+ HTMLLIElement,
32
+ React.ComponentPropsWithoutRef<"li">
33
+ >(({ className, ...props }, ref) => (
34
+ <li
35
+ ref={ref}
36
+ className={cn("inline-flex items-center gap-1.5", className)}
37
+ {...props}
38
+ />
39
+ ))
40
+ BreadcrumbItem.displayName = "BreadcrumbItem"
41
+
42
+ const BreadcrumbLink = React.forwardRef<
43
+ HTMLAnchorElement,
44
+ React.ComponentPropsWithoutRef<"a"> & {
45
+ asChild?: boolean
46
+ }
47
+ >(({ asChild, className, ...props }, ref) => {
48
+ const Comp = asChild ? Slot : "a"
49
+
50
+ return (
51
+ <Comp
52
+ ref={ref}
53
+ className={cn("transition-colors hover:text-foreground", className)}
54
+ {...props}
55
+ />
56
+ )
57
+ })
58
+ BreadcrumbLink.displayName = "BreadcrumbLink"
59
+
60
+ const BreadcrumbPage = React.forwardRef<
61
+ HTMLSpanElement,
62
+ React.ComponentPropsWithoutRef<"span">
63
+ >(({ className, ...props }, ref) => (
64
+ <span
65
+ ref={ref}
66
+ role="link"
67
+ aria-disabled="true"
68
+ aria-current="page"
69
+ className={cn("font-normal text-foreground", className)}
70
+ {...props}
71
+ />
72
+ ))
73
+ BreadcrumbPage.displayName = "BreadcrumbPage"
74
+
75
+ const BreadcrumbSeparator = ({
76
+ children,
77
+ className,
78
+ ...props
79
+ }: React.ComponentProps<"li">) => (
80
+ <li
81
+ role="presentation"
82
+ aria-hidden="true"
83
+ className={cn("[&>svg]:w-3.5 [&>svg]:h-3.5", className)}
84
+ {...props}
85
+ >
86
+ {children ?? <ChevronRight />}
87
+ </li>
88
+ )
89
+ BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
90
+
91
+ const BreadcrumbEllipsis = ({
92
+ className,
93
+ ...props
94
+ }: React.ComponentProps<"span">) => (
95
+ <span
96
+ role="presentation"
97
+ aria-hidden="true"
98
+ className={cn("flex h-9 w-9 items-center justify-center", className)}
99
+ {...props}
100
+ >
101
+ <MoreHorizontal className="h-4 w-4" />
102
+ <span className="sr-only">More</span>
103
+ </span>
104
+ )
105
+ BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
106
+
107
+ export {
108
+ Breadcrumb,
109
+ BreadcrumbList,
110
+ BreadcrumbItem,
111
+ BreadcrumbLink,
112
+ BreadcrumbPage,
113
+ BreadcrumbSeparator,
114
+ BreadcrumbEllipsis,
115
+ }
src/components/ui/button.tsx ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const buttonVariants = cva(
8
+ "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
13
+ destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
14
+ outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
15
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
16
+ ghost: "hover:bg-accent hover:text-accent-foreground",
17
+ link: "text-primary underline-offset-4 hover:underline",
18
+ },
19
+ size: {
20
+ default: "h-10 px-4 py-2",
21
+ sm: "h-9 rounded-md px-3",
22
+ lg: "h-11 rounded-md px-8",
23
+ icon: "h-10 w-10",
24
+ },
25
+ },
26
+ defaultVariants: {
27
+ variant: "default",
28
+ size: "default",
29
+ },
30
+ },
31
+ )
32
+
33
+ export interface ButtonProps
34
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
35
+ VariantProps<typeof buttonVariants> {
36
+ asChild?: boolean
37
+ }
38
+
39
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
40
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
41
+ const Comp = asChild ? Slot : "button"
42
+ return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
43
+ },
44
+ )
45
+ Button.displayName = "Button"
46
+
47
+ export { Button, buttonVariants }
src/components/ui/calendar.tsx ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { ChevronLeft, ChevronRight } from "lucide-react"
5
+ import { DayPicker } from "react-day-picker"
6
+
7
+ import { cn } from "@/lib/utils"
8
+ import { buttonVariants } from "@/components/ui/button"
9
+
10
+ export type CalendarProps = React.ComponentProps<typeof DayPicker>
11
+
12
+ function Calendar({
13
+ className,
14
+ classNames,
15
+ showOutsideDays = true,
16
+ ...props
17
+ }: CalendarProps) {
18
+ return (
19
+ <DayPicker
20
+ showOutsideDays={showOutsideDays}
21
+ className={cn("p-3", className)}
22
+ classNames={{
23
+ months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
24
+ month: "space-y-4",
25
+ caption: "flex justify-center pt-1 relative items-center",
26
+ caption_label: "text-sm font-medium",
27
+ nav: "space-x-1 flex items-center",
28
+ nav_button: cn(
29
+ buttonVariants({ variant: "outline" }),
30
+ "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
31
+ ),
32
+ nav_button_previous: "absolute left-1",
33
+ nav_button_next: "absolute right-1",
34
+ table: "w-full border-collapse space-y-1",
35
+ head_row: "flex",
36
+ head_cell:
37
+ "text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
38
+ row: "flex w-full mt-2",
39
+ cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
40
+ day: cn(
41
+ buttonVariants({ variant: "ghost" }),
42
+ "h-9 w-9 p-0 font-normal aria-selected:opacity-100"
43
+ ),
44
+ day_range_end: "day-range-end",
45
+ day_selected:
46
+ "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
47
+ day_today: "bg-accent text-accent-foreground",
48
+ day_outside:
49
+ "day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground",
50
+ day_disabled: "text-muted-foreground opacity-50",
51
+ day_range_middle:
52
+ "aria-selected:bg-accent aria-selected:text-accent-foreground",
53
+ day_hidden: "invisible",
54
+ ...classNames,
55
+ }}
56
+ components={{
57
+ IconLeft: ({ ...props }) => <ChevronLeft className="h-4 w-4" />,
58
+ IconRight: ({ ...props }) => <ChevronRight className="h-4 w-4" />,
59
+ }}
60
+ {...props}
61
+ />
62
+ )
63
+ }
64
+ Calendar.displayName = "Calendar"
65
+
66
+ export { Calendar }
src/components/ui/card.tsx ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ const Card = React.forwardRef<
6
+ HTMLDivElement,
7
+ React.HTMLAttributes<HTMLDivElement>
8
+ >(({ className, ...props }, ref) => (
9
+ <div
10
+ ref={ref}
11
+ className={cn(
12
+ "rounded-lg border bg-card text-card-foreground shadow-sm",
13
+ className
14
+ )}
15
+ {...props}
16
+ />
17
+ ))
18
+ Card.displayName = "Card"
19
+
20
+ const CardHeader = React.forwardRef<
21
+ HTMLDivElement,
22
+ React.HTMLAttributes<HTMLDivElement>
23
+ >(({ className, ...props }, ref) => (
24
+ <div
25
+ ref={ref}
26
+ className={cn("flex flex-col space-y-1.5 p-6", className)}
27
+ {...props}
28
+ />
29
+ ))
30
+ CardHeader.displayName = "CardHeader"
31
+
32
+ const CardTitle = React.forwardRef<
33
+ HTMLDivElement,
34
+ React.HTMLAttributes<HTMLDivElement>
35
+ >(({ className, ...props }, ref) => (
36
+ <div
37
+ ref={ref}
38
+ className={cn(
39
+ "text-2xl font-semibold leading-none tracking-tight",
40
+ className
41
+ )}
42
+ {...props}
43
+ />
44
+ ))
45
+ CardTitle.displayName = "CardTitle"
46
+
47
+ const CardDescription = React.forwardRef<
48
+ HTMLDivElement,
49
+ React.HTMLAttributes<HTMLDivElement>
50
+ >(({ className, ...props }, ref) => (
51
+ <div
52
+ ref={ref}
53
+ className={cn("text-sm text-muted-foreground", className)}
54
+ {...props}
55
+ />
56
+ ))
57
+ CardDescription.displayName = "CardDescription"
58
+
59
+ const CardContent = React.forwardRef<
60
+ HTMLDivElement,
61
+ React.HTMLAttributes<HTMLDivElement>
62
+ >(({ className, ...props }, ref) => (
63
+ <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
64
+ ))
65
+ CardContent.displayName = "CardContent"
66
+
67
+ const CardFooter = React.forwardRef<
68
+ HTMLDivElement,
69
+ React.HTMLAttributes<HTMLDivElement>
70
+ >(({ className, ...props }, ref) => (
71
+ <div
72
+ ref={ref}
73
+ className={cn("flex items-center p-6 pt-0", className)}
74
+ {...props}
75
+ />
76
+ ))
77
+ CardFooter.displayName = "CardFooter"
78
+
79
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
src/components/ui/carousel.tsx ADDED
@@ -0,0 +1,262 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import useEmblaCarousel, {
5
+ type UseEmblaCarouselType,
6
+ } from "embla-carousel-react"
7
+ import { ArrowLeft, ArrowRight } from "lucide-react"
8
+
9
+ import { cn } from "@/lib/utils"
10
+ import { Button } from "@/components/ui/button"
11
+
12
+ type CarouselApi = UseEmblaCarouselType[1]
13
+ type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
14
+ type CarouselOptions = UseCarouselParameters[0]
15
+ type CarouselPlugin = UseCarouselParameters[1]
16
+
17
+ type CarouselProps = {
18
+ opts?: CarouselOptions
19
+ plugins?: CarouselPlugin
20
+ orientation?: "horizontal" | "vertical"
21
+ setApi?: (api: CarouselApi) => void
22
+ }
23
+
24
+ type CarouselContextProps = {
25
+ carouselRef: ReturnType<typeof useEmblaCarousel>[0]
26
+ api: ReturnType<typeof useEmblaCarousel>[1]
27
+ scrollPrev: () => void
28
+ scrollNext: () => void
29
+ canScrollPrev: boolean
30
+ canScrollNext: boolean
31
+ } & CarouselProps
32
+
33
+ const CarouselContext = React.createContext<CarouselContextProps | null>(null)
34
+
35
+ function useCarousel() {
36
+ const context = React.useContext(CarouselContext)
37
+
38
+ if (!context) {
39
+ throw new Error("useCarousel must be used within a <Carousel />")
40
+ }
41
+
42
+ return context
43
+ }
44
+
45
+ const Carousel = React.forwardRef<
46
+ HTMLDivElement,
47
+ React.HTMLAttributes<HTMLDivElement> & CarouselProps
48
+ >(
49
+ (
50
+ {
51
+ orientation = "horizontal",
52
+ opts,
53
+ setApi,
54
+ plugins,
55
+ className,
56
+ children,
57
+ ...props
58
+ },
59
+ ref
60
+ ) => {
61
+ const [carouselRef, api] = useEmblaCarousel(
62
+ {
63
+ ...opts,
64
+ axis: orientation === "horizontal" ? "x" : "y",
65
+ },
66
+ plugins
67
+ )
68
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false)
69
+ const [canScrollNext, setCanScrollNext] = React.useState(false)
70
+
71
+ const onSelect = React.useCallback((api: CarouselApi) => {
72
+ if (!api) {
73
+ return
74
+ }
75
+
76
+ setCanScrollPrev(api.canScrollPrev())
77
+ setCanScrollNext(api.canScrollNext())
78
+ }, [])
79
+
80
+ const scrollPrev = React.useCallback(() => {
81
+ api?.scrollPrev()
82
+ }, [api])
83
+
84
+ const scrollNext = React.useCallback(() => {
85
+ api?.scrollNext()
86
+ }, [api])
87
+
88
+ const handleKeyDown = React.useCallback(
89
+ (event: React.KeyboardEvent<HTMLDivElement>) => {
90
+ if (event.key === "ArrowLeft") {
91
+ event.preventDefault()
92
+ scrollPrev()
93
+ } else if (event.key === "ArrowRight") {
94
+ event.preventDefault()
95
+ scrollNext()
96
+ }
97
+ },
98
+ [scrollPrev, scrollNext]
99
+ )
100
+
101
+ React.useEffect(() => {
102
+ if (!api || !setApi) {
103
+ return
104
+ }
105
+
106
+ setApi(api)
107
+ }, [api, setApi])
108
+
109
+ React.useEffect(() => {
110
+ if (!api) {
111
+ return
112
+ }
113
+
114
+ onSelect(api)
115
+ api.on("reInit", onSelect)
116
+ api.on("select", onSelect)
117
+
118
+ return () => {
119
+ api?.off("select", onSelect)
120
+ }
121
+ }, [api, onSelect])
122
+
123
+ return (
124
+ <CarouselContext.Provider
125
+ value={{
126
+ carouselRef,
127
+ api: api,
128
+ opts,
129
+ orientation:
130
+ orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
131
+ scrollPrev,
132
+ scrollNext,
133
+ canScrollPrev,
134
+ canScrollNext,
135
+ }}
136
+ >
137
+ <div
138
+ ref={ref}
139
+ onKeyDownCapture={handleKeyDown}
140
+ className={cn("relative", className)}
141
+ role="region"
142
+ aria-roledescription="carousel"
143
+ {...props}
144
+ >
145
+ {children}
146
+ </div>
147
+ </CarouselContext.Provider>
148
+ )
149
+ }
150
+ )
151
+ Carousel.displayName = "Carousel"
152
+
153
+ const CarouselContent = React.forwardRef<
154
+ HTMLDivElement,
155
+ React.HTMLAttributes<HTMLDivElement>
156
+ >(({ className, ...props }, ref) => {
157
+ const { carouselRef, orientation } = useCarousel()
158
+
159
+ return (
160
+ <div ref={carouselRef} className="overflow-hidden">
161
+ <div
162
+ ref={ref}
163
+ className={cn(
164
+ "flex",
165
+ orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
166
+ className
167
+ )}
168
+ {...props}
169
+ />
170
+ </div>
171
+ )
172
+ })
173
+ CarouselContent.displayName = "CarouselContent"
174
+
175
+ const CarouselItem = React.forwardRef<
176
+ HTMLDivElement,
177
+ React.HTMLAttributes<HTMLDivElement>
178
+ >(({ className, ...props }, ref) => {
179
+ const { orientation } = useCarousel()
180
+
181
+ return (
182
+ <div
183
+ ref={ref}
184
+ role="group"
185
+ aria-roledescription="slide"
186
+ className={cn(
187
+ "min-w-0 shrink-0 grow-0 basis-full",
188
+ orientation === "horizontal" ? "pl-4" : "pt-4",
189
+ className
190
+ )}
191
+ {...props}
192
+ />
193
+ )
194
+ })
195
+ CarouselItem.displayName = "CarouselItem"
196
+
197
+ const CarouselPrevious = React.forwardRef<
198
+ HTMLButtonElement,
199
+ React.ComponentProps<typeof Button>
200
+ >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
201
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel()
202
+
203
+ return (
204
+ <Button
205
+ ref={ref}
206
+ variant={variant}
207
+ size={size}
208
+ className={cn(
209
+ "absolute h-8 w-8 rounded-full",
210
+ orientation === "horizontal"
211
+ ? "-left-12 top-1/2 -translate-y-1/2"
212
+ : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
213
+ className
214
+ )}
215
+ disabled={!canScrollPrev}
216
+ onClick={scrollPrev}
217
+ {...props}
218
+ >
219
+ <ArrowLeft className="h-4 w-4" />
220
+ <span className="sr-only">Previous slide</span>
221
+ </Button>
222
+ )
223
+ })
224
+ CarouselPrevious.displayName = "CarouselPrevious"
225
+
226
+ const CarouselNext = React.forwardRef<
227
+ HTMLButtonElement,
228
+ React.ComponentProps<typeof Button>
229
+ >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
230
+ const { orientation, scrollNext, canScrollNext } = useCarousel()
231
+
232
+ return (
233
+ <Button
234
+ ref={ref}
235
+ variant={variant}
236
+ size={size}
237
+ className={cn(
238
+ "absolute h-8 w-8 rounded-full",
239
+ orientation === "horizontal"
240
+ ? "-right-12 top-1/2 -translate-y-1/2"
241
+ : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
242
+ className
243
+ )}
244
+ disabled={!canScrollNext}
245
+ onClick={scrollNext}
246
+ {...props}
247
+ >
248
+ <ArrowRight className="h-4 w-4" />
249
+ <span className="sr-only">Next slide</span>
250
+ </Button>
251
+ )
252
+ })
253
+ CarouselNext.displayName = "CarouselNext"
254
+
255
+ export {
256
+ type CarouselApi,
257
+ Carousel,
258
+ CarouselContent,
259
+ CarouselItem,
260
+ CarouselPrevious,
261
+ CarouselNext,
262
+ }
src/components/ui/chart.tsx ADDED
@@ -0,0 +1,365 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as RechartsPrimitive from "recharts"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ // Format: { THEME_NAME: CSS_SELECTOR }
9
+ const THEMES = { light: "", dark: ".dark" } as const
10
+
11
+ export type ChartConfig = {
12
+ [k in string]: {
13
+ label?: React.ReactNode
14
+ icon?: React.ComponentType
15
+ } & (
16
+ | { color?: string; theme?: never }
17
+ | { color?: never; theme: Record<keyof typeof THEMES, string> }
18
+ )
19
+ }
20
+
21
+ type ChartContextProps = {
22
+ config: ChartConfig
23
+ }
24
+
25
+ const ChartContext = React.createContext<ChartContextProps | null>(null)
26
+
27
+ function useChart() {
28
+ const context = React.useContext(ChartContext)
29
+
30
+ if (!context) {
31
+ throw new Error("useChart must be used within a <ChartContainer />")
32
+ }
33
+
34
+ return context
35
+ }
36
+
37
+ const ChartContainer = React.forwardRef<
38
+ HTMLDivElement,
39
+ React.ComponentProps<"div"> & {
40
+ config: ChartConfig
41
+ children: React.ComponentProps<
42
+ typeof RechartsPrimitive.ResponsiveContainer
43
+ >["children"]
44
+ }
45
+ >(({ id, className, children, config, ...props }, ref) => {
46
+ const uniqueId = React.useId()
47
+ const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
48
+
49
+ return (
50
+ <ChartContext.Provider value={{ config }}>
51
+ <div
52
+ data-chart={chartId}
53
+ ref={ref}
54
+ className={cn(
55
+ "flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
56
+ className
57
+ )}
58
+ {...props}
59
+ >
60
+ <ChartStyle id={chartId} config={config} />
61
+ <RechartsPrimitive.ResponsiveContainer>
62
+ {children}
63
+ </RechartsPrimitive.ResponsiveContainer>
64
+ </div>
65
+ </ChartContext.Provider>
66
+ )
67
+ })
68
+ ChartContainer.displayName = "Chart"
69
+
70
+ const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
71
+ const colorConfig = Object.entries(config).filter(
72
+ ([_, config]) => config.theme || config.color
73
+ )
74
+
75
+ if (!colorConfig.length) {
76
+ return null
77
+ }
78
+
79
+ return (
80
+ <style
81
+ dangerouslySetInnerHTML={{
82
+ __html: Object.entries(THEMES)
83
+ .map(
84
+ ([theme, prefix]) => `
85
+ ${prefix} [data-chart=${id}] {
86
+ ${colorConfig
87
+ .map(([key, itemConfig]) => {
88
+ const color =
89
+ itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
90
+ itemConfig.color
91
+ return color ? ` --color-${key}: ${color};` : null
92
+ })
93
+ .join("\n")}
94
+ }
95
+ `
96
+ )
97
+ .join("\n"),
98
+ }}
99
+ />
100
+ )
101
+ }
102
+
103
+ const ChartTooltip = RechartsPrimitive.Tooltip
104
+
105
+ const ChartTooltipContent = React.forwardRef<
106
+ HTMLDivElement,
107
+ React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
108
+ React.ComponentProps<"div"> & {
109
+ hideLabel?: boolean
110
+ hideIndicator?: boolean
111
+ indicator?: "line" | "dot" | "dashed"
112
+ nameKey?: string
113
+ labelKey?: string
114
+ }
115
+ >(
116
+ (
117
+ {
118
+ active,
119
+ payload,
120
+ className,
121
+ indicator = "dot",
122
+ hideLabel = false,
123
+ hideIndicator = false,
124
+ label,
125
+ labelFormatter,
126
+ labelClassName,
127
+ formatter,
128
+ color,
129
+ nameKey,
130
+ labelKey,
131
+ },
132
+ ref
133
+ ) => {
134
+ const { config } = useChart()
135
+
136
+ const tooltipLabel = React.useMemo(() => {
137
+ if (hideLabel || !payload?.length) {
138
+ return null
139
+ }
140
+
141
+ const [item] = payload
142
+ const key = `${labelKey || item.dataKey || item.name || "value"}`
143
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
144
+ const value =
145
+ !labelKey && typeof label === "string"
146
+ ? config[label as keyof typeof config]?.label || label
147
+ : itemConfig?.label
148
+
149
+ if (labelFormatter) {
150
+ return (
151
+ <div className={cn("font-medium", labelClassName)}>
152
+ {labelFormatter(value, payload)}
153
+ </div>
154
+ )
155
+ }
156
+
157
+ if (!value) {
158
+ return null
159
+ }
160
+
161
+ return <div className={cn("font-medium", labelClassName)}>{value}</div>
162
+ }, [
163
+ label,
164
+ labelFormatter,
165
+ payload,
166
+ hideLabel,
167
+ labelClassName,
168
+ config,
169
+ labelKey,
170
+ ])
171
+
172
+ if (!active || !payload?.length) {
173
+ return null
174
+ }
175
+
176
+ const nestLabel = payload.length === 1 && indicator !== "dot"
177
+
178
+ return (
179
+ <div
180
+ ref={ref}
181
+ className={cn(
182
+ "grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
183
+ className
184
+ )}
185
+ >
186
+ {!nestLabel ? tooltipLabel : null}
187
+ <div className="grid gap-1.5">
188
+ {payload.map((item, index) => {
189
+ const key = `${nameKey || item.name || item.dataKey || "value"}`
190
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
191
+ const indicatorColor = color || item.payload.fill || item.color
192
+
193
+ return (
194
+ <div
195
+ key={item.dataKey}
196
+ className={cn(
197
+ "flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
198
+ indicator === "dot" && "items-center"
199
+ )}
200
+ >
201
+ {formatter && item?.value !== undefined && item.name ? (
202
+ formatter(item.value, item.name, item, index, item.payload)
203
+ ) : (
204
+ <>
205
+ {itemConfig?.icon ? (
206
+ <itemConfig.icon />
207
+ ) : (
208
+ !hideIndicator && (
209
+ <div
210
+ className={cn(
211
+ "shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
212
+ {
213
+ "h-2.5 w-2.5": indicator === "dot",
214
+ "w-1": indicator === "line",
215
+ "w-0 border-[1.5px] border-dashed bg-transparent":
216
+ indicator === "dashed",
217
+ "my-0.5": nestLabel && indicator === "dashed",
218
+ }
219
+ )}
220
+ style={
221
+ {
222
+ "--color-bg": indicatorColor,
223
+ "--color-border": indicatorColor,
224
+ } as React.CSSProperties
225
+ }
226
+ />
227
+ )
228
+ )}
229
+ <div
230
+ className={cn(
231
+ "flex flex-1 justify-between leading-none",
232
+ nestLabel ? "items-end" : "items-center"
233
+ )}
234
+ >
235
+ <div className="grid gap-1.5">
236
+ {nestLabel ? tooltipLabel : null}
237
+ <span className="text-muted-foreground">
238
+ {itemConfig?.label || item.name}
239
+ </span>
240
+ </div>
241
+ {item.value && (
242
+ <span className="font-mono font-medium tabular-nums text-foreground">
243
+ {item.value.toLocaleString()}
244
+ </span>
245
+ )}
246
+ </div>
247
+ </>
248
+ )}
249
+ </div>
250
+ )
251
+ })}
252
+ </div>
253
+ </div>
254
+ )
255
+ }
256
+ )
257
+ ChartTooltipContent.displayName = "ChartTooltip"
258
+
259
+ const ChartLegend = RechartsPrimitive.Legend
260
+
261
+ const ChartLegendContent = React.forwardRef<
262
+ HTMLDivElement,
263
+ React.ComponentProps<"div"> &
264
+ Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
265
+ hideIcon?: boolean
266
+ nameKey?: string
267
+ }
268
+ >(
269
+ (
270
+ { className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
271
+ ref
272
+ ) => {
273
+ const { config } = useChart()
274
+
275
+ if (!payload?.length) {
276
+ return null
277
+ }
278
+
279
+ return (
280
+ <div
281
+ ref={ref}
282
+ className={cn(
283
+ "flex items-center justify-center gap-4",
284
+ verticalAlign === "top" ? "pb-3" : "pt-3",
285
+ className
286
+ )}
287
+ >
288
+ {payload.map((item) => {
289
+ const key = `${nameKey || item.dataKey || "value"}`
290
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
291
+
292
+ return (
293
+ <div
294
+ key={item.value}
295
+ className={cn(
296
+ "flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
297
+ )}
298
+ >
299
+ {itemConfig?.icon && !hideIcon ? (
300
+ <itemConfig.icon />
301
+ ) : (
302
+ <div
303
+ className="h-2 w-2 shrink-0 rounded-[2px]"
304
+ style={{
305
+ backgroundColor: item.color,
306
+ }}
307
+ />
308
+ )}
309
+ {itemConfig?.label}
310
+ </div>
311
+ )
312
+ })}
313
+ </div>
314
+ )
315
+ }
316
+ )
317
+ ChartLegendContent.displayName = "ChartLegend"
318
+
319
+ // Helper to extract item config from a payload.
320
+ function getPayloadConfigFromPayload(
321
+ config: ChartConfig,
322
+ payload: unknown,
323
+ key: string
324
+ ) {
325
+ if (typeof payload !== "object" || payload === null) {
326
+ return undefined
327
+ }
328
+
329
+ const payloadPayload =
330
+ "payload" in payload &&
331
+ typeof payload.payload === "object" &&
332
+ payload.payload !== null
333
+ ? payload.payload
334
+ : undefined
335
+
336
+ let configLabelKey: string = key
337
+
338
+ if (
339
+ key in payload &&
340
+ typeof payload[key as keyof typeof payload] === "string"
341
+ ) {
342
+ configLabelKey = payload[key as keyof typeof payload] as string
343
+ } else if (
344
+ payloadPayload &&
345
+ key in payloadPayload &&
346
+ typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
347
+ ) {
348
+ configLabelKey = payloadPayload[
349
+ key as keyof typeof payloadPayload
350
+ ] as string
351
+ }
352
+
353
+ return configLabelKey in config
354
+ ? config[configLabelKey]
355
+ : config[key as keyof typeof config]
356
+ }
357
+
358
+ export {
359
+ ChartContainer,
360
+ ChartTooltip,
361
+ ChartTooltipContent,
362
+ ChartLegend,
363
+ ChartLegendContent,
364
+ ChartStyle,
365
+ }
src/components/ui/checkbox.tsx ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
5
+ import { Check } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ const Checkbox = React.forwardRef<
10
+ React.ElementRef<typeof CheckboxPrimitive.Root>,
11
+ React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
12
+ >(({ className, ...props }, ref) => (
13
+ <CheckboxPrimitive.Root
14
+ ref={ref}
15
+ className={cn(
16
+ "peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
17
+ className
18
+ )}
19
+ {...props}
20
+ >
21
+ <CheckboxPrimitive.Indicator
22
+ className={cn("flex items-center justify-center text-current")}
23
+ >
24
+ <Check className="h-4 w-4" />
25
+ </CheckboxPrimitive.Indicator>
26
+ </CheckboxPrimitive.Root>
27
+ ))
28
+ Checkbox.displayName = CheckboxPrimitive.Root.displayName
29
+
30
+ export { Checkbox }
src/components/ui/collapsible.tsx ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
4
+
5
+ const Collapsible = CollapsiblePrimitive.Root
6
+
7
+ const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
8
+
9
+ const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
10
+
11
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent }
src/components/ui/command.tsx ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { type DialogProps } from "@radix-ui/react-dialog"
5
+ import { Command as CommandPrimitive } from "cmdk"
6
+ import { Search } from "lucide-react"
7
+
8
+ import { cn } from "@/lib/utils"
9
+ import { Dialog, DialogContent } from "@/components/ui/dialog"
10
+
11
+ const Command = React.forwardRef<
12
+ React.ElementRef<typeof CommandPrimitive>,
13
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive>
14
+ >(({ className, ...props }, ref) => (
15
+ <CommandPrimitive
16
+ ref={ref}
17
+ className={cn(
18
+ "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
19
+ className
20
+ )}
21
+ {...props}
22
+ />
23
+ ))
24
+ Command.displayName = CommandPrimitive.displayName
25
+
26
+ const CommandDialog = ({ children, ...props }: DialogProps) => {
27
+ return (
28
+ <Dialog {...props}>
29
+ <DialogContent className="overflow-hidden p-0 shadow-lg">
30
+ <Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
31
+ {children}
32
+ </Command>
33
+ </DialogContent>
34
+ </Dialog>
35
+ )
36
+ }
37
+
38
+ const CommandInput = React.forwardRef<
39
+ React.ElementRef<typeof CommandPrimitive.Input>,
40
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
41
+ >(({ className, ...props }, ref) => (
42
+ <div className="flex items-center border-b px-3" cmdk-input-wrapper="">
43
+ <Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
44
+ <CommandPrimitive.Input
45
+ ref={ref}
46
+ className={cn(
47
+ "flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
48
+ className
49
+ )}
50
+ {...props}
51
+ />
52
+ </div>
53
+ ))
54
+
55
+ CommandInput.displayName = CommandPrimitive.Input.displayName
56
+
57
+ const CommandList = React.forwardRef<
58
+ React.ElementRef<typeof CommandPrimitive.List>,
59
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
60
+ >(({ className, ...props }, ref) => (
61
+ <CommandPrimitive.List
62
+ ref={ref}
63
+ className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
64
+ {...props}
65
+ />
66
+ ))
67
+
68
+ CommandList.displayName = CommandPrimitive.List.displayName
69
+
70
+ const CommandEmpty = React.forwardRef<
71
+ React.ElementRef<typeof CommandPrimitive.Empty>,
72
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
73
+ >((props, ref) => (
74
+ <CommandPrimitive.Empty
75
+ ref={ref}
76
+ className="py-6 text-center text-sm"
77
+ {...props}
78
+ />
79
+ ))
80
+
81
+ CommandEmpty.displayName = CommandPrimitive.Empty.displayName
82
+
83
+ const CommandGroup = React.forwardRef<
84
+ React.ElementRef<typeof CommandPrimitive.Group>,
85
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
86
+ >(({ className, ...props }, ref) => (
87
+ <CommandPrimitive.Group
88
+ ref={ref}
89
+ className={cn(
90
+ "overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
91
+ className
92
+ )}
93
+ {...props}
94
+ />
95
+ ))
96
+
97
+ CommandGroup.displayName = CommandPrimitive.Group.displayName
98
+
99
+ const CommandSeparator = React.forwardRef<
100
+ React.ElementRef<typeof CommandPrimitive.Separator>,
101
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
102
+ >(({ className, ...props }, ref) => (
103
+ <CommandPrimitive.Separator
104
+ ref={ref}
105
+ className={cn("-mx-1 h-px bg-border", className)}
106
+ {...props}
107
+ />
108
+ ))
109
+ CommandSeparator.displayName = CommandPrimitive.Separator.displayName
110
+
111
+ const CommandItem = React.forwardRef<
112
+ React.ElementRef<typeof CommandPrimitive.Item>,
113
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
114
+ >(({ className, ...props }, ref) => (
115
+ <CommandPrimitive.Item
116
+ ref={ref}
117
+ className={cn(
118
+ "relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
119
+ className
120
+ )}
121
+ {...props}
122
+ />
123
+ ))
124
+
125
+ CommandItem.displayName = CommandPrimitive.Item.displayName
126
+
127
+ const CommandShortcut = ({
128
+ className,
129
+ ...props
130
+ }: React.HTMLAttributes<HTMLSpanElement>) => {
131
+ return (
132
+ <span
133
+ className={cn(
134
+ "ml-auto text-xs tracking-widest text-muted-foreground",
135
+ className
136
+ )}
137
+ {...props}
138
+ />
139
+ )
140
+ }
141
+ CommandShortcut.displayName = "CommandShortcut"
142
+
143
+ export {
144
+ Command,
145
+ CommandDialog,
146
+ CommandInput,
147
+ CommandList,
148
+ CommandEmpty,
149
+ CommandGroup,
150
+ CommandItem,
151
+ CommandShortcut,
152
+ CommandSeparator,
153
+ }