王晓东 2 miesięcy temu
commit
60b0082862
95 zmienionych plików z 16472 dodań i 0 usunięć
  1. 7 0
      .gitignore
  2. 11 0
      README.md
  3. 0 0
      build/assets/index-1Br_J6GP.css
  4. 8 0
      build/assets/index-DKTkGVBP.js
  5. 16 0
      build/site.html
  6. 15 0
      index.html
  7. 59 0
      package.json
  8. 2719 0
      pnpm-lock.yaml
  9. 271 0
      src/ARCHITECTURE.md
  10. 85 0
      src/App.tsx
  11. 3 0
      src/Attributions.md
  12. 179 0
      src/CHANGELOG.md
  13. 190 0
      src/DEBUG.md
  14. 170 0
      src/DEPLOYMENT.md
  15. 269 0
      src/MIGRATION_GUIDE.md
  16. 110 0
      src/QUICK_FIX.md
  17. 202 0
      src/README.md
  18. 9 0
      src/_redirects/Code-component-66-106.tsx
  19. 9 0
      src/_redirects/Code-component-66-76.tsx
  20. 135 0
      src/components/Contact.tsx
  21. 144 0
      src/components/Experts.tsx
  22. 54 0
      src/components/FAQ.tsx
  23. 105 0
      src/components/Features.tsx
  24. 83 0
      src/components/Footer.tsx
  25. 180 0
      src/components/Header.tsx
  26. 60 0
      src/components/Hero.tsx
  27. 27 0
      src/components/figma/ImageWithFallback.tsx
  28. 105 0
      src/components/pages/About.tsx
  29. 118 0
      src/components/pages/Blog.tsx
  30. 160 0
      src/components/pages/Careers.tsx
  31. 137 0
      src/components/pages/ContactPage.tsx
  32. 56 0
      src/components/pages/FAQPage.tsx
  33. 17 0
      src/components/pages/Home.tsx
  34. 415 0
      src/components/pages/PrivacyPolicy.tsx
  35. 591 0
      src/components/pages/TermsOfService.tsx
  36. 66 0
      src/components/ui/accordion.tsx
  37. 157 0
      src/components/ui/alert-dialog.tsx
  38. 66 0
      src/components/ui/alert.tsx
  39. 11 0
      src/components/ui/aspect-ratio.tsx
  40. 53 0
      src/components/ui/avatar.tsx
  41. 46 0
      src/components/ui/badge.tsx
  42. 109 0
      src/components/ui/breadcrumb.tsx
  43. 58 0
      src/components/ui/button.tsx
  44. 75 0
      src/components/ui/calendar.tsx
  45. 92 0
      src/components/ui/card.tsx
  46. 241 0
      src/components/ui/carousel.tsx
  47. 353 0
      src/components/ui/chart.tsx
  48. 32 0
      src/components/ui/checkbox.tsx
  49. 33 0
      src/components/ui/collapsible.tsx
  50. 177 0
      src/components/ui/command.tsx
  51. 252 0
      src/components/ui/context-menu.tsx
  52. 135 0
      src/components/ui/dialog.tsx
  53. 132 0
      src/components/ui/drawer.tsx
  54. 257 0
      src/components/ui/dropdown-menu.tsx
  55. 168 0
      src/components/ui/form.tsx
  56. 44 0
      src/components/ui/hover-card.tsx
  57. 77 0
      src/components/ui/input-otp.tsx
  58. 21 0
      src/components/ui/input.tsx
  59. 24 0
      src/components/ui/label.tsx
  60. 276 0
      src/components/ui/menubar.tsx
  61. 168 0
      src/components/ui/navigation-menu.tsx
  62. 127 0
      src/components/ui/pagination.tsx
  63. 48 0
      src/components/ui/popover.tsx
  64. 31 0
      src/components/ui/progress.tsx
  65. 45 0
      src/components/ui/radio-group.tsx
  66. 56 0
      src/components/ui/resizable.tsx
  67. 58 0
      src/components/ui/scroll-area.tsx
  68. 189 0
      src/components/ui/select.tsx
  69. 28 0
      src/components/ui/separator.tsx
  70. 139 0
      src/components/ui/sheet.tsx
  71. 726 0
      src/components/ui/sidebar.tsx
  72. 13 0
      src/components/ui/skeleton.tsx
  73. 63 0
      src/components/ui/slider.tsx
  74. 25 0
      src/components/ui/sonner.tsx
  75. 31 0
      src/components/ui/switch.tsx
  76. 116 0
      src/components/ui/table.tsx
  77. 66 0
      src/components/ui/tabs.tsx
  78. 18 0
      src/components/ui/textarea.tsx
  79. 73 0
      src/components/ui/toggle-group.tsx
  80. 47 0
      src/components/ui/toggle.tsx
  81. 61 0
      src/components/ui/tooltip.tsx
  82. 21 0
      src/components/ui/use-mobile.ts
  83. 6 0
      src/components/ui/utils.ts
  84. 34 0
      src/contexts/LanguageContext.tsx
  85. 154 0
      src/contexts/RouterContext.tsx
  86. 61 0
      src/guidelines/Guidelines.md
  87. 3060 0
      src/index.css
  88. 27 0
      src/index.html
  89. 979 0
      src/lib/i18n.ts
  90. 7 0
      src/main.tsx
  91. 2 0
      src/public/_redirects/Code-component-66-108.tsx
  92. 2 0
      src/public/_redirects/Code-component-66-81.tsx
  93. 212 0
      src/styles/globals.css
  94. 44 0
      src/vercel.json
  95. 61 0
      vite.config.ts

+ 7 - 0
.gitignore

@@ -0,0 +1,7 @@
+node_modules
+.DS_Store
+dist*
+dist-ssr
+*.local
+.eslintcache
+.stylelintcache

+ 11 - 0
README.md

@@ -0,0 +1,11 @@
+
+  # Anycall App Landing Page
+
+  This is a code bundle for Anycall App Landing Page. The original project is available at https://www.figma.com/design/1OsjoARzrEUAHID2XuKN91/Anycall-App-Landing-Page.
+
+  ## Running the code
+
+  Run `npm i` to install the dependencies.
+
+  Run `npm run dev` to start the development server.
+  

Plik diff jest za duży
+ 0 - 0
build/assets/index-1Br_J6GP.css


Plik diff jest za duży
+ 8 - 0
build/assets/index-DKTkGVBP.js


+ 16 - 0
build/site.html

@@ -0,0 +1,16 @@
+
+  <!DOCTYPE html>
+  <html lang="en">
+    <head>
+      <meta charset="UTF-8" />
+      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+      <title>Anycall App Landing Page</title>
+      <script type="module" crossorigin src="/fara/h5/assets/index-DKTkGVBP.js"></script>
+      <link rel="stylesheet" crossorigin href="/fara/h5/assets/index-1Br_J6GP.css">
+    </head>
+
+    <body>
+      <div id="root"></div>
+    </body>
+  </html>
+  

+ 15 - 0
index.html

@@ -0,0 +1,15 @@
+
+  <!DOCTYPE html>
+  <html lang="en">
+    <head>
+      <meta charset="UTF-8" />
+      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+      <title>Anycall App Landing Page</title>
+    </head>
+
+    <body>
+      <div id="root"></div>
+      <script type="module" src="/src/main.tsx"></script>
+    </body>
+  </html>
+  

+ 59 - 0
package.json

@@ -0,0 +1,59 @@
+
+  {
+      "name": "Anycall App Landing Page",
+      "version": "0.1.0",
+      "private": true,
+      "dependencies": {
+          "@radix-ui/react-accordion": "^1.2.3",
+          "@radix-ui/react-alert-dialog": "^1.1.6",
+          "@radix-ui/react-aspect-ratio": "^1.1.2",
+          "@radix-ui/react-avatar": "^1.1.3",
+          "@radix-ui/react-checkbox": "^1.1.4",
+          "@radix-ui/react-collapsible": "^1.1.3",
+          "@radix-ui/react-context-menu": "^2.2.6",
+          "@radix-ui/react-dialog": "^1.1.6",
+          "@radix-ui/react-dropdown-menu": "^2.1.6",
+          "@radix-ui/react-hover-card": "^1.1.6",
+          "@radix-ui/react-label": "^2.1.2",
+          "@radix-ui/react-menubar": "^1.1.6",
+          "@radix-ui/react-navigation-menu": "^1.2.5",
+          "@radix-ui/react-popover": "^1.1.6",
+          "@radix-ui/react-progress": "^1.1.2",
+          "@radix-ui/react-radio-group": "^1.2.3",
+          "@radix-ui/react-scroll-area": "^1.2.3",
+          "@radix-ui/react-select": "^2.1.6",
+          "@radix-ui/react-separator": "^1.1.2",
+          "@radix-ui/react-slider": "^1.2.3",
+          "@radix-ui/react-slot": "^1.1.2",
+          "@radix-ui/react-switch": "^1.1.3",
+          "@radix-ui/react-tabs": "^1.1.3",
+          "@radix-ui/react-toggle": "^1.1.2",
+          "@radix-ui/react-toggle-group": "^1.1.2",
+          "@radix-ui/react-tooltip": "^1.1.8",
+          "class-variance-authority": "^0.7.1",
+          "clsx": "*",
+          "cmdk": "^1.1.1",
+          "embla-carousel-react": "^8.6.0",
+          "input-otp": "^1.4.2",
+          "lucide-react": "^0.487.0",
+          "next-themes": "^0.4.6",
+          "react": "^18.3.1",
+          "react-day-picker": "^8.10.1",
+          "react-dom": "^18.3.1",
+          "react-hook-form": "^7.55.0",
+          "react-resizable-panels": "^2.1.7",
+          "recharts": "^2.15.2",
+          "sonner": "^2.0.3",
+          "tailwind-merge": "*",
+          "vaul": "^1.1.2"
+      },
+      "devDependencies": {
+          "@types/node": "^20.10.0",
+          "@vitejs/plugin-react-swc": "^3.10.2",
+          "vite": "6.3.5"
+      },
+      "scripts": {
+          "dev": "vite",
+          "build": "vite build"
+      }
+  }

+ 2719 - 0
pnpm-lock.yaml

@@ -0,0 +1,2719 @@
+lockfileVersion: '9.0'
+
+settings:
+  autoInstallPeers: true
+  excludeLinksFromLockfile: false
+
+importers:
+
+  .:
+    dependencies:
+      '@radix-ui/react-accordion':
+        specifier: ^1.2.3
+        version: 1.2.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-alert-dialog':
+        specifier: ^1.1.6
+        version: 1.1.15(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-aspect-ratio':
+        specifier: ^1.1.2
+        version: 1.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-avatar':
+        specifier: ^1.1.3
+        version: 1.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-checkbox':
+        specifier: ^1.1.4
+        version: 1.3.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-collapsible':
+        specifier: ^1.1.3
+        version: 1.1.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-context-menu':
+        specifier: ^2.2.6
+        version: 2.2.16(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-dialog':
+        specifier: ^1.1.6
+        version: 1.1.15(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-dropdown-menu':
+        specifier: ^2.1.6
+        version: 2.1.16(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-hover-card':
+        specifier: ^1.1.6
+        version: 1.1.15(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-label':
+        specifier: ^2.1.2
+        version: 2.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-menubar':
+        specifier: ^1.1.6
+        version: 1.1.16(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-navigation-menu':
+        specifier: ^1.2.5
+        version: 1.2.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-popover':
+        specifier: ^1.1.6
+        version: 1.1.15(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-progress':
+        specifier: ^1.1.2
+        version: 1.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-radio-group':
+        specifier: ^1.2.3
+        version: 1.3.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-scroll-area':
+        specifier: ^1.2.3
+        version: 1.2.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-select':
+        specifier: ^2.1.6
+        version: 2.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-separator':
+        specifier: ^1.1.2
+        version: 1.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-slider':
+        specifier: ^1.2.3
+        version: 1.3.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-slot':
+        specifier: ^1.1.2
+        version: 1.2.4(react@18.3.1)
+      '@radix-ui/react-switch':
+        specifier: ^1.1.3
+        version: 1.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-tabs':
+        specifier: ^1.1.3
+        version: 1.1.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-toggle':
+        specifier: ^1.1.2
+        version: 1.1.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-toggle-group':
+        specifier: ^1.1.2
+        version: 1.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-tooltip':
+        specifier: ^1.1.8
+        version: 1.2.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      class-variance-authority:
+        specifier: ^0.7.1
+        version: 0.7.1
+      clsx:
+        specifier: '*'
+        version: 2.1.1
+      cmdk:
+        specifier: ^1.1.1
+        version: 1.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      embla-carousel-react:
+        specifier: ^8.6.0
+        version: 8.6.0(react@18.3.1)
+      input-otp:
+        specifier: ^1.4.2
+        version: 1.4.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      lucide-react:
+        specifier: ^0.487.0
+        version: 0.487.0(react@18.3.1)
+      next-themes:
+        specifier: ^0.4.6
+        version: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      react:
+        specifier: ^18.3.1
+        version: 18.3.1
+      react-day-picker:
+        specifier: ^8.10.1
+        version: 8.10.1(date-fns@3.6.0)(react@18.3.1)
+      react-dom:
+        specifier: ^18.3.1
+        version: 18.3.1(react@18.3.1)
+      react-hook-form:
+        specifier: ^7.55.0
+        version: 7.66.0(react@18.3.1)
+      react-resizable-panels:
+        specifier: ^2.1.7
+        version: 2.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      recharts:
+        specifier: ^2.15.2
+        version: 2.15.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      sonner:
+        specifier: ^2.0.3
+        version: 2.0.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      tailwind-merge:
+        specifier: '*'
+        version: 3.4.0
+      vaul:
+        specifier: ^1.1.2
+        version: 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+    devDependencies:
+      '@types/node':
+        specifier: ^20.10.0
+        version: 20.19.25
+      '@vitejs/plugin-react-swc':
+        specifier: ^3.10.2
+        version: 3.11.0(vite@6.3.5(@types/node@20.19.25))
+      vite:
+        specifier: 6.3.5
+        version: 6.3.5(@types/node@20.19.25)
+
+packages:
+
+  '@babel/runtime@7.28.4':
+    resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
+    engines: {node: '>=6.9.0'}
+
+  '@esbuild/aix-ppc64@0.25.12':
+    resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
+    engines: {node: '>=18'}
+    cpu: [ppc64]
+    os: [aix]
+
+  '@esbuild/android-arm64@0.25.12':
+    resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [android]
+
+  '@esbuild/android-arm@0.25.12':
+    resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==}
+    engines: {node: '>=18'}
+    cpu: [arm]
+    os: [android]
+
+  '@esbuild/android-x64@0.25.12':
+    resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [android]
+
+  '@esbuild/darwin-arm64@0.25.12':
+    resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [darwin]
+
+  '@esbuild/darwin-x64@0.25.12':
+    resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [darwin]
+
+  '@esbuild/freebsd-arm64@0.25.12':
+    resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [freebsd]
+
+  '@esbuild/freebsd-x64@0.25.12':
+    resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [freebsd]
+
+  '@esbuild/linux-arm64@0.25.12':
+    resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [linux]
+
+  '@esbuild/linux-arm@0.25.12':
+    resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==}
+    engines: {node: '>=18'}
+    cpu: [arm]
+    os: [linux]
+
+  '@esbuild/linux-ia32@0.25.12':
+    resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==}
+    engines: {node: '>=18'}
+    cpu: [ia32]
+    os: [linux]
+
+  '@esbuild/linux-loong64@0.25.12':
+    resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==}
+    engines: {node: '>=18'}
+    cpu: [loong64]
+    os: [linux]
+
+  '@esbuild/linux-mips64el@0.25.12':
+    resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==}
+    engines: {node: '>=18'}
+    cpu: [mips64el]
+    os: [linux]
+
+  '@esbuild/linux-ppc64@0.25.12':
+    resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==}
+    engines: {node: '>=18'}
+    cpu: [ppc64]
+    os: [linux]
+
+  '@esbuild/linux-riscv64@0.25.12':
+    resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==}
+    engines: {node: '>=18'}
+    cpu: [riscv64]
+    os: [linux]
+
+  '@esbuild/linux-s390x@0.25.12':
+    resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==}
+    engines: {node: '>=18'}
+    cpu: [s390x]
+    os: [linux]
+
+  '@esbuild/linux-x64@0.25.12':
+    resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [linux]
+
+  '@esbuild/netbsd-arm64@0.25.12':
+    resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [netbsd]
+
+  '@esbuild/netbsd-x64@0.25.12':
+    resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [netbsd]
+
+  '@esbuild/openbsd-arm64@0.25.12':
+    resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [openbsd]
+
+  '@esbuild/openbsd-x64@0.25.12':
+    resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [openbsd]
+
+  '@esbuild/openharmony-arm64@0.25.12':
+    resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [openharmony]
+
+  '@esbuild/sunos-x64@0.25.12':
+    resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [sunos]
+
+  '@esbuild/win32-arm64@0.25.12':
+    resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [win32]
+
+  '@esbuild/win32-ia32@0.25.12':
+    resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==}
+    engines: {node: '>=18'}
+    cpu: [ia32]
+    os: [win32]
+
+  '@esbuild/win32-x64@0.25.12':
+    resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [win32]
+
+  '@floating-ui/core@1.7.3':
+    resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==}
+
+  '@floating-ui/dom@1.7.4':
+    resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==}
+
+  '@floating-ui/react-dom@2.1.6':
+    resolution: {integrity: sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==}
+    peerDependencies:
+      react: '>=16.8.0'
+      react-dom: '>=16.8.0'
+
+  '@floating-ui/utils@0.2.10':
+    resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
+
+  '@radix-ui/number@1.1.1':
+    resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==}
+
+  '@radix-ui/primitive@1.1.3':
+    resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==}
+
+  '@radix-ui/react-accordion@1.2.12':
+    resolution: {integrity: sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-alert-dialog@1.1.15':
+    resolution: {integrity: sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-arrow@1.1.7':
+    resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-aspect-ratio@1.1.8':
+    resolution: {integrity: sha512-5nZrJTF7gH+e0nZS7/QxFz6tJV4VimhQb1avEgtsJxvvIp5JilL+c58HICsKzPxghdwaDt48hEfPM1au4zGy+w==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-avatar@1.1.11':
+    resolution: {integrity: sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-checkbox@1.3.3':
+    resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-collapsible@1.1.12':
+    resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-collection@1.1.7':
+    resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-compose-refs@1.1.2':
+    resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==}
+    peerDependencies:
+      '@types/react': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+
+  '@radix-ui/react-context-menu@2.2.16':
+    resolution: {integrity: sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-context@1.1.2':
+    resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==}
+    peerDependencies:
+      '@types/react': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+
+  '@radix-ui/react-context@1.1.3':
+    resolution: {integrity: sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==}
+    peerDependencies:
+      '@types/react': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+
+  '@radix-ui/react-dialog@1.1.15':
+    resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-direction@1.1.1':
+    resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==}
+    peerDependencies:
+      '@types/react': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+
+  '@radix-ui/react-dismissable-layer@1.1.11':
+    resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-dropdown-menu@2.1.16':
+    resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-focus-guards@1.1.3':
+    resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==}
+    peerDependencies:
+      '@types/react': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+
+  '@radix-ui/react-focus-scope@1.1.7':
+    resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-hover-card@1.1.15':
+    resolution: {integrity: sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-id@1.1.1':
+    resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==}
+    peerDependencies:
+      '@types/react': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+
+  '@radix-ui/react-label@2.1.8':
+    resolution: {integrity: sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-menu@2.1.16':
+    resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-menubar@1.1.16':
+    resolution: {integrity: sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-navigation-menu@1.2.14':
+    resolution: {integrity: sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-popover@1.1.15':
+    resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-popper@1.2.8':
+    resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-portal@1.1.9':
+    resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-presence@1.1.5':
+    resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-primitive@2.1.3':
+    resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-primitive@2.1.4':
+    resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-progress@1.1.8':
+    resolution: {integrity: sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-radio-group@1.3.8':
+    resolution: {integrity: sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-roving-focus@1.1.11':
+    resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-scroll-area@1.2.10':
+    resolution: {integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-select@2.2.6':
+    resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-separator@1.1.8':
+    resolution: {integrity: sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-slider@1.3.6':
+    resolution: {integrity: sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-slot@1.2.3':
+    resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==}
+    peerDependencies:
+      '@types/react': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+
+  '@radix-ui/react-slot@1.2.4':
+    resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==}
+    peerDependencies:
+      '@types/react': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+
+  '@radix-ui/react-switch@1.2.6':
+    resolution: {integrity: sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-tabs@1.1.13':
+    resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-toggle-group@1.1.11':
+    resolution: {integrity: sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-toggle@1.1.10':
+    resolution: {integrity: sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-tooltip@1.2.8':
+    resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/react-use-callback-ref@1.1.1':
+    resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==}
+    peerDependencies:
+      '@types/react': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+
+  '@radix-ui/react-use-controllable-state@1.2.2':
+    resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==}
+    peerDependencies:
+      '@types/react': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+
+  '@radix-ui/react-use-effect-event@0.0.2':
+    resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==}
+    peerDependencies:
+      '@types/react': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+
+  '@radix-ui/react-use-escape-keydown@1.1.1':
+    resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==}
+    peerDependencies:
+      '@types/react': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+
+  '@radix-ui/react-use-is-hydrated@0.1.0':
+    resolution: {integrity: sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==}
+    peerDependencies:
+      '@types/react': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+
+  '@radix-ui/react-use-layout-effect@1.1.1':
+    resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==}
+    peerDependencies:
+      '@types/react': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+
+  '@radix-ui/react-use-previous@1.1.1':
+    resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==}
+    peerDependencies:
+      '@types/react': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+
+  '@radix-ui/react-use-rect@1.1.1':
+    resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==}
+    peerDependencies:
+      '@types/react': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+
+  '@radix-ui/react-use-size@1.1.1':
+    resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==}
+    peerDependencies:
+      '@types/react': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+
+  '@radix-ui/react-visually-hidden@1.2.3':
+    resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==}
+    peerDependencies:
+      '@types/react': '*'
+      '@types/react-dom': '*'
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      '@types/react-dom':
+        optional: true
+
+  '@radix-ui/rect@1.1.1':
+    resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
+
+  '@rolldown/pluginutils@1.0.0-beta.27':
+    resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==}
+
+  '@rollup/rollup-android-arm-eabi@4.53.2':
+    resolution: {integrity: sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA==}
+    cpu: [arm]
+    os: [android]
+
+  '@rollup/rollup-android-arm64@4.53.2':
+    resolution: {integrity: sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g==}
+    cpu: [arm64]
+    os: [android]
+
+  '@rollup/rollup-darwin-arm64@4.53.2':
+    resolution: {integrity: sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ==}
+    cpu: [arm64]
+    os: [darwin]
+
+  '@rollup/rollup-darwin-x64@4.53.2':
+    resolution: {integrity: sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw==}
+    cpu: [x64]
+    os: [darwin]
+
+  '@rollup/rollup-freebsd-arm64@4.53.2':
+    resolution: {integrity: sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA==}
+    cpu: [arm64]
+    os: [freebsd]
+
+  '@rollup/rollup-freebsd-x64@4.53.2':
+    resolution: {integrity: sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA==}
+    cpu: [x64]
+    os: [freebsd]
+
+  '@rollup/rollup-linux-arm-gnueabihf@4.53.2':
+    resolution: {integrity: sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==}
+    cpu: [arm]
+    os: [linux]
+    libc: [glibc]
+
+  '@rollup/rollup-linux-arm-musleabihf@4.53.2':
+    resolution: {integrity: sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==}
+    cpu: [arm]
+    os: [linux]
+    libc: [musl]
+
+  '@rollup/rollup-linux-arm64-gnu@4.53.2':
+    resolution: {integrity: sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==}
+    cpu: [arm64]
+    os: [linux]
+    libc: [glibc]
+
+  '@rollup/rollup-linux-arm64-musl@4.53.2':
+    resolution: {integrity: sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==}
+    cpu: [arm64]
+    os: [linux]
+    libc: [musl]
+
+  '@rollup/rollup-linux-loong64-gnu@4.53.2':
+    resolution: {integrity: sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==}
+    cpu: [loong64]
+    os: [linux]
+    libc: [glibc]
+
+  '@rollup/rollup-linux-ppc64-gnu@4.53.2':
+    resolution: {integrity: sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==}
+    cpu: [ppc64]
+    os: [linux]
+    libc: [glibc]
+
+  '@rollup/rollup-linux-riscv64-gnu@4.53.2':
+    resolution: {integrity: sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==}
+    cpu: [riscv64]
+    os: [linux]
+    libc: [glibc]
+
+  '@rollup/rollup-linux-riscv64-musl@4.53.2':
+    resolution: {integrity: sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==}
+    cpu: [riscv64]
+    os: [linux]
+    libc: [musl]
+
+  '@rollup/rollup-linux-s390x-gnu@4.53.2':
+    resolution: {integrity: sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==}
+    cpu: [s390x]
+    os: [linux]
+    libc: [glibc]
+
+  '@rollup/rollup-linux-x64-gnu@4.53.2':
+    resolution: {integrity: sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==}
+    cpu: [x64]
+    os: [linux]
+    libc: [glibc]
+
+  '@rollup/rollup-linux-x64-musl@4.53.2':
+    resolution: {integrity: sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==}
+    cpu: [x64]
+    os: [linux]
+    libc: [musl]
+
+  '@rollup/rollup-openharmony-arm64@4.53.2':
+    resolution: {integrity: sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==}
+    cpu: [arm64]
+    os: [openharmony]
+
+  '@rollup/rollup-win32-arm64-msvc@4.53.2':
+    resolution: {integrity: sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA==}
+    cpu: [arm64]
+    os: [win32]
+
+  '@rollup/rollup-win32-ia32-msvc@4.53.2':
+    resolution: {integrity: sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg==}
+    cpu: [ia32]
+    os: [win32]
+
+  '@rollup/rollup-win32-x64-gnu@4.53.2':
+    resolution: {integrity: sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw==}
+    cpu: [x64]
+    os: [win32]
+
+  '@rollup/rollup-win32-x64-msvc@4.53.2':
+    resolution: {integrity: sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA==}
+    cpu: [x64]
+    os: [win32]
+
+  '@swc/core-darwin-arm64@1.15.2':
+    resolution: {integrity: sha512-Ghyz4RJv4zyXzrUC1B2MLQBbppIB5c4jMZJybX2ebdEQAvryEKp3gq1kBksCNsatKGmEgXul88SETU19sMWcrw==}
+    engines: {node: '>=10'}
+    cpu: [arm64]
+    os: [darwin]
+
+  '@swc/core-darwin-x64@1.15.2':
+    resolution: {integrity: sha512-7n/PGJOcL2QoptzL42L5xFFfXY5rFxLHnuz1foU+4ruUTG8x2IebGhtwVTpaDN8ShEv2UZObBlT1rrXTba15Zw==}
+    engines: {node: '>=10'}
+    cpu: [x64]
+    os: [darwin]
+
+  '@swc/core-linux-arm-gnueabihf@1.15.2':
+    resolution: {integrity: sha512-ZUQVCfRJ9wimuxkStRSlLwqX4TEDmv6/J+E6FicGkQ6ssLMWoKDy0cAo93HiWt/TWEee5vFhFaSQYzCuBEGO6A==}
+    engines: {node: '>=10'}
+    cpu: [arm]
+    os: [linux]
+
+  '@swc/core-linux-arm64-gnu@1.15.2':
+    resolution: {integrity: sha512-GZh3pYBmfnpQ+JIg+TqLuz+pM+Mjsk5VOzi8nwKn/m+GvQBsxD5ectRtxuWUxMGNG8h0lMy4SnHRqdK3/iJl7A==}
+    engines: {node: '>=10'}
+    cpu: [arm64]
+    os: [linux]
+    libc: [glibc]
+
+  '@swc/core-linux-arm64-musl@1.15.2':
+    resolution: {integrity: sha512-5av6VYZZeneiYIodwzGMlnyVakpuYZryGzFIbgu1XP8wVylZxduEzup4eP8atiMDFmIm+s4wn8GySJmYqeJC0A==}
+    engines: {node: '>=10'}
+    cpu: [arm64]
+    os: [linux]
+    libc: [musl]
+
+  '@swc/core-linux-x64-gnu@1.15.2':
+    resolution: {integrity: sha512-1nO/UfdCLuT/uE/7oB3EZgTeZDCIa6nL72cFEpdegnqpJVNDI6Qb8U4g/4lfVPkmHq2lvxQ0L+n+JdgaZLhrRA==}
+    engines: {node: '>=10'}
+    cpu: [x64]
+    os: [linux]
+    libc: [glibc]
+
+  '@swc/core-linux-x64-musl@1.15.2':
+    resolution: {integrity: sha512-Ksfrb0Tx310kr+TLiUOvB/I80lyZ3lSOp6cM18zmNRT/92NB4mW8oX2Jo7K4eVEI2JWyaQUAFubDSha2Q+439A==}
+    engines: {node: '>=10'}
+    cpu: [x64]
+    os: [linux]
+    libc: [musl]
+
+  '@swc/core-win32-arm64-msvc@1.15.2':
+    resolution: {integrity: sha512-IzUb5RlMUY0r1A9IuJrQ7Tbts1wWb73/zXVXT8VhewbHGoNlBKE0qUhKMED6Tv4wDF+pmbtUJmKXDthytAvLmg==}
+    engines: {node: '>=10'}
+    cpu: [arm64]
+    os: [win32]
+
+  '@swc/core-win32-ia32-msvc@1.15.2':
+    resolution: {integrity: sha512-kCATEzuY2LP9AlbU2uScjcVhgnCAkRdu62vbce17Ro5kxEHxYWcugkveyBRS3AqZGtwAKYbMAuNloer9LS/hpw==}
+    engines: {node: '>=10'}
+    cpu: [ia32]
+    os: [win32]
+
+  '@swc/core-win32-x64-msvc@1.15.2':
+    resolution: {integrity: sha512-iJaHeYCF4jTn7OEKSa3KRiuVFIVYts8jYjNmCdyz1u5g8HRyTDISD76r8+ljEOgm36oviRQvcXaw6LFp1m0yyA==}
+    engines: {node: '>=10'}
+    cpu: [x64]
+    os: [win32]
+
+  '@swc/core@1.15.2':
+    resolution: {integrity: sha512-OQm+yJdXxvSjqGeaWhP6Ia264ogifwAO7Q12uTDVYj/Ks4jBTI4JknlcjDRAXtRhqbWsfbZyK/5RtuIPyptk3w==}
+    engines: {node: '>=10'}
+    peerDependencies:
+      '@swc/helpers': '>=0.5.17'
+    peerDependenciesMeta:
+      '@swc/helpers':
+        optional: true
+
+  '@swc/counter@0.1.3':
+    resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
+
+  '@swc/types@0.1.25':
+    resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==}
+
+  '@types/d3-array@3.2.2':
+    resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==}
+
+  '@types/d3-color@3.1.3':
+    resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==}
+
+  '@types/d3-ease@3.0.2':
+    resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==}
+
+  '@types/d3-interpolate@3.0.4':
+    resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==}
+
+  '@types/d3-path@3.1.1':
+    resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==}
+
+  '@types/d3-scale@4.0.9':
+    resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==}
+
+  '@types/d3-shape@3.1.7':
+    resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==}
+
+  '@types/d3-time@3.0.4':
+    resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==}
+
+  '@types/d3-timer@3.0.2':
+    resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
+
+  '@types/estree@1.0.8':
+    resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+
+  '@types/node@20.19.25':
+    resolution: {integrity: sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==}
+
+  '@vitejs/plugin-react-swc@3.11.0':
+    resolution: {integrity: sha512-YTJCGFdNMHCMfjODYtxRNVAYmTWQ1Lb8PulP/2/f/oEEtglw8oKxKIZmmRkyXrVrHfsKOaVkAc3NT9/dMutO5w==}
+    peerDependencies:
+      vite: ^4 || ^5 || ^6 || ^7
+
+  aria-hidden@1.2.6:
+    resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==}
+    engines: {node: '>=10'}
+
+  class-variance-authority@0.7.1:
+    resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
+
+  clsx@2.1.1:
+    resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
+    engines: {node: '>=6'}
+
+  cmdk@1.1.1:
+    resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==}
+    peerDependencies:
+      react: ^18 || ^19 || ^19.0.0-rc
+      react-dom: ^18 || ^19 || ^19.0.0-rc
+
+  csstype@3.2.2:
+    resolution: {integrity: sha512-D80T+tiqkd/8B0xNlbstWDG4x6aqVfO52+OlSUNIdkTvmNw0uQpJLeos2J/2XvpyidAFuTPmpad+tUxLndwj6g==}
+
+  d3-array@3.2.4:
+    resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
+    engines: {node: '>=12'}
+
+  d3-color@3.1.0:
+    resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
+    engines: {node: '>=12'}
+
+  d3-ease@3.0.1:
+    resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
+    engines: {node: '>=12'}
+
+  d3-format@3.1.0:
+    resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==}
+    engines: {node: '>=12'}
+
+  d3-interpolate@3.0.1:
+    resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
+    engines: {node: '>=12'}
+
+  d3-path@3.1.0:
+    resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
+    engines: {node: '>=12'}
+
+  d3-scale@4.0.2:
+    resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
+    engines: {node: '>=12'}
+
+  d3-shape@3.2.0:
+    resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
+    engines: {node: '>=12'}
+
+  d3-time-format@4.1.0:
+    resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
+    engines: {node: '>=12'}
+
+  d3-time@3.1.0:
+    resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
+    engines: {node: '>=12'}
+
+  d3-timer@3.0.1:
+    resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
+    engines: {node: '>=12'}
+
+  date-fns@3.6.0:
+    resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==}
+
+  decimal.js-light@2.5.1:
+    resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
+
+  detect-node-es@1.1.0:
+    resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
+
+  dom-helpers@5.2.1:
+    resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
+
+  embla-carousel-react@8.6.0:
+    resolution: {integrity: sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA==}
+    peerDependencies:
+      react: ^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+
+  embla-carousel-reactive-utils@8.6.0:
+    resolution: {integrity: sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A==}
+    peerDependencies:
+      embla-carousel: 8.6.0
+
+  embla-carousel@8.6.0:
+    resolution: {integrity: sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==}
+
+  esbuild@0.25.12:
+    resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
+    engines: {node: '>=18'}
+    hasBin: true
+
+  eventemitter3@4.0.7:
+    resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
+
+  fast-equals@5.3.3:
+    resolution: {integrity: sha512-/boTcHZeIAQ2r/tL11voclBHDeP9WPxLt+tyAbVSyyXuUFyh0Tne7gJZTqGbxnvj79TjLdCXLOY7UIPhyG5MTw==}
+    engines: {node: '>=6.0.0'}
+
+  fdir@6.5.0:
+    resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+    engines: {node: '>=12.0.0'}
+    peerDependencies:
+      picomatch: ^3 || ^4
+    peerDependenciesMeta:
+      picomatch:
+        optional: true
+
+  fsevents@2.3.3:
+    resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+    engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+    os: [darwin]
+
+  get-nonce@1.0.1:
+    resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
+    engines: {node: '>=6'}
+
+  input-otp@1.4.2:
+    resolution: {integrity: sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==}
+    peerDependencies:
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
+
+  internmap@2.0.3:
+    resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
+    engines: {node: '>=12'}
+
+  js-tokens@4.0.0:
+    resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+  lodash@4.17.21:
+    resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
+
+  loose-envify@1.4.0:
+    resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+    hasBin: true
+
+  lucide-react@0.487.0:
+    resolution: {integrity: sha512-aKqhOQ+YmFnwq8dWgGjOuLc8V1R9/c/yOd+zDY4+ohsR2Jo05lSGc3WsstYPIzcTpeosN7LoCkLReUUITvaIvw==}
+    peerDependencies:
+      react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+  nanoid@3.3.11:
+    resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+    engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+    hasBin: true
+
+  next-themes@0.4.6:
+    resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==}
+    peerDependencies:
+      react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
+
+  object-assign@4.1.1:
+    resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+    engines: {node: '>=0.10.0'}
+
+  picocolors@1.1.1:
+    resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+  picomatch@4.0.3:
+    resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
+    engines: {node: '>=12'}
+
+  postcss@8.5.6:
+    resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
+    engines: {node: ^10 || ^12 || >=14}
+
+  prop-types@15.8.1:
+    resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+
+  react-day-picker@8.10.1:
+    resolution: {integrity: sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==}
+    peerDependencies:
+      date-fns: ^2.28.0 || ^3.0.0
+      react: ^16.8.0 || ^17.0.0 || ^18.0.0
+
+  react-dom@18.3.1:
+    resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
+    peerDependencies:
+      react: ^18.3.1
+
+  react-hook-form@7.66.0:
+    resolution: {integrity: sha512-xXBqsWGKrY46ZqaHDo+ZUYiMUgi8suYu5kdrS20EG8KiL7VRQitEbNjm+UcrDYrNi1YLyfpmAeGjCZYXLT9YBw==}
+    engines: {node: '>=18.0.0'}
+    peerDependencies:
+      react: ^16.8.0 || ^17 || ^18 || ^19
+
+  react-is@16.13.1:
+    resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+
+  react-is@18.3.1:
+    resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
+
+  react-remove-scroll-bar@2.3.8:
+    resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==}
+    engines: {node: '>=10'}
+    peerDependencies:
+      '@types/react': '*'
+      react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+
+  react-remove-scroll@2.7.1:
+    resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==}
+    engines: {node: '>=10'}
+    peerDependencies:
+      '@types/react': '*'
+      react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+
+  react-resizable-panels@2.1.9:
+    resolution: {integrity: sha512-z77+X08YDIrgAes4jl8xhnUu1LNIRp4+E7cv4xHmLOxxUPO/ML7PSrE813b90vj7xvQ1lcf7g2uA9GeMZonjhQ==}
+    peerDependencies:
+      react: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+      react-dom: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+
+  react-smooth@4.0.4:
+    resolution: {integrity: sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==}
+    peerDependencies:
+      react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+      react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+  react-style-singleton@2.2.3:
+    resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
+    engines: {node: '>=10'}
+    peerDependencies:
+      '@types/react': '*'
+      react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+
+  react-transition-group@4.4.5:
+    resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
+    peerDependencies:
+      react: '>=16.6.0'
+      react-dom: '>=16.6.0'
+
+  react@18.3.1:
+    resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
+    engines: {node: '>=0.10.0'}
+
+  recharts-scale@0.4.5:
+    resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==}
+
+  recharts@2.15.4:
+    resolution: {integrity: sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==}
+    engines: {node: '>=14'}
+    peerDependencies:
+      react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+      react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+  rollup@4.53.2:
+    resolution: {integrity: sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==}
+    engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+    hasBin: true
+
+  scheduler@0.23.2:
+    resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
+
+  sonner@2.0.7:
+    resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==}
+    peerDependencies:
+      react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+      react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+
+  source-map-js@1.2.1:
+    resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+    engines: {node: '>=0.10.0'}
+
+  tailwind-merge@3.4.0:
+    resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==}
+
+  tiny-invariant@1.3.3:
+    resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
+
+  tinyglobby@0.2.15:
+    resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
+    engines: {node: '>=12.0.0'}
+
+  tslib@2.8.1:
+    resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+  undici-types@6.21.0:
+    resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+
+  use-callback-ref@1.3.3:
+    resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==}
+    engines: {node: '>=10'}
+    peerDependencies:
+      '@types/react': '*'
+      react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+
+  use-sidecar@1.1.3:
+    resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
+    engines: {node: '>=10'}
+    peerDependencies:
+      '@types/react': '*'
+      react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+
+  use-sync-external-store@1.6.0:
+    resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==}
+    peerDependencies:
+      react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+  vaul@1.1.2:
+    resolution: {integrity: sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==}
+    peerDependencies:
+      react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
+      react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
+
+  victory-vendor@36.9.2:
+    resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==}
+
+  vite@6.3.5:
+    resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==}
+    engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
+    hasBin: true
+    peerDependencies:
+      '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
+      jiti: '>=1.21.0'
+      less: '*'
+      lightningcss: ^1.21.0
+      sass: '*'
+      sass-embedded: '*'
+      stylus: '*'
+      sugarss: '*'
+      terser: ^5.16.0
+      tsx: ^4.8.1
+      yaml: ^2.4.2
+    peerDependenciesMeta:
+      '@types/node':
+        optional: true
+      jiti:
+        optional: true
+      less:
+        optional: true
+      lightningcss:
+        optional: true
+      sass:
+        optional: true
+      sass-embedded:
+        optional: true
+      stylus:
+        optional: true
+      sugarss:
+        optional: true
+      terser:
+        optional: true
+      tsx:
+        optional: true
+      yaml:
+        optional: true
+
+snapshots:
+
+  '@babel/runtime@7.28.4': {}
+
+  '@esbuild/aix-ppc64@0.25.12':
+    optional: true
+
+  '@esbuild/android-arm64@0.25.12':
+    optional: true
+
+  '@esbuild/android-arm@0.25.12':
+    optional: true
+
+  '@esbuild/android-x64@0.25.12':
+    optional: true
+
+  '@esbuild/darwin-arm64@0.25.12':
+    optional: true
+
+  '@esbuild/darwin-x64@0.25.12':
+    optional: true
+
+  '@esbuild/freebsd-arm64@0.25.12':
+    optional: true
+
+  '@esbuild/freebsd-x64@0.25.12':
+    optional: true
+
+  '@esbuild/linux-arm64@0.25.12':
+    optional: true
+
+  '@esbuild/linux-arm@0.25.12':
+    optional: true
+
+  '@esbuild/linux-ia32@0.25.12':
+    optional: true
+
+  '@esbuild/linux-loong64@0.25.12':
+    optional: true
+
+  '@esbuild/linux-mips64el@0.25.12':
+    optional: true
+
+  '@esbuild/linux-ppc64@0.25.12':
+    optional: true
+
+  '@esbuild/linux-riscv64@0.25.12':
+    optional: true
+
+  '@esbuild/linux-s390x@0.25.12':
+    optional: true
+
+  '@esbuild/linux-x64@0.25.12':
+    optional: true
+
+  '@esbuild/netbsd-arm64@0.25.12':
+    optional: true
+
+  '@esbuild/netbsd-x64@0.25.12':
+    optional: true
+
+  '@esbuild/openbsd-arm64@0.25.12':
+    optional: true
+
+  '@esbuild/openbsd-x64@0.25.12':
+    optional: true
+
+  '@esbuild/openharmony-arm64@0.25.12':
+    optional: true
+
+  '@esbuild/sunos-x64@0.25.12':
+    optional: true
+
+  '@esbuild/win32-arm64@0.25.12':
+    optional: true
+
+  '@esbuild/win32-ia32@0.25.12':
+    optional: true
+
+  '@esbuild/win32-x64@0.25.12':
+    optional: true
+
+  '@floating-ui/core@1.7.3':
+    dependencies:
+      '@floating-ui/utils': 0.2.10
+
+  '@floating-ui/dom@1.7.4':
+    dependencies:
+      '@floating-ui/core': 1.7.3
+      '@floating-ui/utils': 0.2.10
+
+  '@floating-ui/react-dom@2.1.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@floating-ui/dom': 1.7.4
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@floating-ui/utils@0.2.10': {}
+
+  '@radix-ui/number@1.1.1': {}
+
+  '@radix-ui/primitive@1.1.3': {}
+
+  '@radix-ui/react-accordion@1.2.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/primitive': 1.1.3
+      '@radix-ui/react-collapsible': 1.1.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-collection': 1.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-compose-refs': 1.1.2(react@18.3.1)
+      '@radix-ui/react-context': 1.1.2(react@18.3.1)
+      '@radix-ui/react-direction': 1.1.1(react@18.3.1)
+      '@radix-ui/react-id': 1.1.1(react@18.3.1)
+      '@radix-ui/react-primitive': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-use-controllable-state': 1.2.2(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/react-alert-dialog@1.1.15(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/primitive': 1.1.3
+      '@radix-ui/react-compose-refs': 1.1.2(react@18.3.1)
+      '@radix-ui/react-context': 1.1.2(react@18.3.1)
+      '@radix-ui/react-dialog': 1.1.15(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-primitive': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-slot': 1.2.3(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/react-arrow@1.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/react-primitive': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/react-aspect-ratio@1.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/react-primitive': 2.1.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/react-avatar@1.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/react-context': 1.1.3(react@18.3.1)
+      '@radix-ui/react-primitive': 2.1.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-use-callback-ref': 1.1.1(react@18.3.1)
+      '@radix-ui/react-use-is-hydrated': 0.1.0(react@18.3.1)
+      '@radix-ui/react-use-layout-effect': 1.1.1(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/react-checkbox@1.3.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/primitive': 1.1.3
+      '@radix-ui/react-compose-refs': 1.1.2(react@18.3.1)
+      '@radix-ui/react-context': 1.1.2(react@18.3.1)
+      '@radix-ui/react-presence': 1.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-primitive': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-use-controllable-state': 1.2.2(react@18.3.1)
+      '@radix-ui/react-use-previous': 1.1.1(react@18.3.1)
+      '@radix-ui/react-use-size': 1.1.1(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/react-collapsible@1.1.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/primitive': 1.1.3
+      '@radix-ui/react-compose-refs': 1.1.2(react@18.3.1)
+      '@radix-ui/react-context': 1.1.2(react@18.3.1)
+      '@radix-ui/react-id': 1.1.1(react@18.3.1)
+      '@radix-ui/react-presence': 1.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-primitive': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-use-controllable-state': 1.2.2(react@18.3.1)
+      '@radix-ui/react-use-layout-effect': 1.1.1(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/react-collection@1.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/react-compose-refs': 1.1.2(react@18.3.1)
+      '@radix-ui/react-context': 1.1.2(react@18.3.1)
+      '@radix-ui/react-primitive': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-slot': 1.2.3(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/react-compose-refs@1.1.2(react@18.3.1)':
+    dependencies:
+      react: 18.3.1
+
+  '@radix-ui/react-context-menu@2.2.16(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/primitive': 1.1.3
+      '@radix-ui/react-context': 1.1.2(react@18.3.1)
+      '@radix-ui/react-menu': 2.1.16(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-primitive': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-use-callback-ref': 1.1.1(react@18.3.1)
+      '@radix-ui/react-use-controllable-state': 1.2.2(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/react-context@1.1.2(react@18.3.1)':
+    dependencies:
+      react: 18.3.1
+
+  '@radix-ui/react-context@1.1.3(react@18.3.1)':
+    dependencies:
+      react: 18.3.1
+
+  '@radix-ui/react-dialog@1.1.15(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/primitive': 1.1.3
+      '@radix-ui/react-compose-refs': 1.1.2(react@18.3.1)
+      '@radix-ui/react-context': 1.1.2(react@18.3.1)
+      '@radix-ui/react-dismissable-layer': 1.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-focus-guards': 1.1.3(react@18.3.1)
+      '@radix-ui/react-focus-scope': 1.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-id': 1.1.1(react@18.3.1)
+      '@radix-ui/react-portal': 1.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-presence': 1.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-primitive': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-slot': 1.2.3(react@18.3.1)
+      '@radix-ui/react-use-controllable-state': 1.2.2(react@18.3.1)
+      aria-hidden: 1.2.6
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+      react-remove-scroll: 2.7.1(react@18.3.1)
+
+  '@radix-ui/react-direction@1.1.1(react@18.3.1)':
+    dependencies:
+      react: 18.3.1
+
+  '@radix-ui/react-dismissable-layer@1.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/primitive': 1.1.3
+      '@radix-ui/react-compose-refs': 1.1.2(react@18.3.1)
+      '@radix-ui/react-primitive': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-use-callback-ref': 1.1.1(react@18.3.1)
+      '@radix-ui/react-use-escape-keydown': 1.1.1(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/react-dropdown-menu@2.1.16(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/primitive': 1.1.3
+      '@radix-ui/react-compose-refs': 1.1.2(react@18.3.1)
+      '@radix-ui/react-context': 1.1.2(react@18.3.1)
+      '@radix-ui/react-id': 1.1.1(react@18.3.1)
+      '@radix-ui/react-menu': 2.1.16(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-primitive': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-use-controllable-state': 1.2.2(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/react-focus-guards@1.1.3(react@18.3.1)':
+    dependencies:
+      react: 18.3.1
+
+  '@radix-ui/react-focus-scope@1.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/react-compose-refs': 1.1.2(react@18.3.1)
+      '@radix-ui/react-primitive': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-use-callback-ref': 1.1.1(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/react-hover-card@1.1.15(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/primitive': 1.1.3
+      '@radix-ui/react-compose-refs': 1.1.2(react@18.3.1)
+      '@radix-ui/react-context': 1.1.2(react@18.3.1)
+      '@radix-ui/react-dismissable-layer': 1.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-popper': 1.2.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-portal': 1.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-presence': 1.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-primitive': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-use-controllable-state': 1.2.2(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/react-id@1.1.1(react@18.3.1)':
+    dependencies:
+      '@radix-ui/react-use-layout-effect': 1.1.1(react@18.3.1)
+      react: 18.3.1
+
+  '@radix-ui/react-label@2.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/react-primitive': 2.1.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/react-menu@2.1.16(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/primitive': 1.1.3
+      '@radix-ui/react-collection': 1.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-compose-refs': 1.1.2(react@18.3.1)
+      '@radix-ui/react-context': 1.1.2(react@18.3.1)
+      '@radix-ui/react-direction': 1.1.1(react@18.3.1)
+      '@radix-ui/react-dismissable-layer': 1.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-focus-guards': 1.1.3(react@18.3.1)
+      '@radix-ui/react-focus-scope': 1.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-id': 1.1.1(react@18.3.1)
+      '@radix-ui/react-popper': 1.2.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-portal': 1.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-presence': 1.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-primitive': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-roving-focus': 1.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-slot': 1.2.3(react@18.3.1)
+      '@radix-ui/react-use-callback-ref': 1.1.1(react@18.3.1)
+      aria-hidden: 1.2.6
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+      react-remove-scroll: 2.7.1(react@18.3.1)
+
+  '@radix-ui/react-menubar@1.1.16(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/primitive': 1.1.3
+      '@radix-ui/react-collection': 1.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-compose-refs': 1.1.2(react@18.3.1)
+      '@radix-ui/react-context': 1.1.2(react@18.3.1)
+      '@radix-ui/react-direction': 1.1.1(react@18.3.1)
+      '@radix-ui/react-id': 1.1.1(react@18.3.1)
+      '@radix-ui/react-menu': 2.1.16(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-primitive': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-roving-focus': 1.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-use-controllable-state': 1.2.2(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/react-navigation-menu@1.2.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/primitive': 1.1.3
+      '@radix-ui/react-collection': 1.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-compose-refs': 1.1.2(react@18.3.1)
+      '@radix-ui/react-context': 1.1.2(react@18.3.1)
+      '@radix-ui/react-direction': 1.1.1(react@18.3.1)
+      '@radix-ui/react-dismissable-layer': 1.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-id': 1.1.1(react@18.3.1)
+      '@radix-ui/react-presence': 1.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-primitive': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-use-callback-ref': 1.1.1(react@18.3.1)
+      '@radix-ui/react-use-controllable-state': 1.2.2(react@18.3.1)
+      '@radix-ui/react-use-layout-effect': 1.1.1(react@18.3.1)
+      '@radix-ui/react-use-previous': 1.1.1(react@18.3.1)
+      '@radix-ui/react-visually-hidden': 1.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/react-popover@1.1.15(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/primitive': 1.1.3
+      '@radix-ui/react-compose-refs': 1.1.2(react@18.3.1)
+      '@radix-ui/react-context': 1.1.2(react@18.3.1)
+      '@radix-ui/react-dismissable-layer': 1.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-focus-guards': 1.1.3(react@18.3.1)
+      '@radix-ui/react-focus-scope': 1.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-id': 1.1.1(react@18.3.1)
+      '@radix-ui/react-popper': 1.2.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-portal': 1.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-presence': 1.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-primitive': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-slot': 1.2.3(react@18.3.1)
+      '@radix-ui/react-use-controllable-state': 1.2.2(react@18.3.1)
+      aria-hidden: 1.2.6
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+      react-remove-scroll: 2.7.1(react@18.3.1)
+
+  '@radix-ui/react-popper@1.2.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@floating-ui/react-dom': 2.1.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-arrow': 1.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-compose-refs': 1.1.2(react@18.3.1)
+      '@radix-ui/react-context': 1.1.2(react@18.3.1)
+      '@radix-ui/react-primitive': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-use-callback-ref': 1.1.1(react@18.3.1)
+      '@radix-ui/react-use-layout-effect': 1.1.1(react@18.3.1)
+      '@radix-ui/react-use-rect': 1.1.1(react@18.3.1)
+      '@radix-ui/react-use-size': 1.1.1(react@18.3.1)
+      '@radix-ui/rect': 1.1.1
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/react-portal@1.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/react-primitive': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-use-layout-effect': 1.1.1(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/react-presence@1.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/react-compose-refs': 1.1.2(react@18.3.1)
+      '@radix-ui/react-use-layout-effect': 1.1.1(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/react-primitive@2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/react-slot': 1.2.3(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/react-primitive@2.1.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/react-slot': 1.2.4(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/react-progress@1.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/react-context': 1.1.3(react@18.3.1)
+      '@radix-ui/react-primitive': 2.1.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/react-radio-group@1.3.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/primitive': 1.1.3
+      '@radix-ui/react-compose-refs': 1.1.2(react@18.3.1)
+      '@radix-ui/react-context': 1.1.2(react@18.3.1)
+      '@radix-ui/react-direction': 1.1.1(react@18.3.1)
+      '@radix-ui/react-presence': 1.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-primitive': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-roving-focus': 1.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-use-controllable-state': 1.2.2(react@18.3.1)
+      '@radix-ui/react-use-previous': 1.1.1(react@18.3.1)
+      '@radix-ui/react-use-size': 1.1.1(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/react-roving-focus@1.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/primitive': 1.1.3
+      '@radix-ui/react-collection': 1.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-compose-refs': 1.1.2(react@18.3.1)
+      '@radix-ui/react-context': 1.1.2(react@18.3.1)
+      '@radix-ui/react-direction': 1.1.1(react@18.3.1)
+      '@radix-ui/react-id': 1.1.1(react@18.3.1)
+      '@radix-ui/react-primitive': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-use-callback-ref': 1.1.1(react@18.3.1)
+      '@radix-ui/react-use-controllable-state': 1.2.2(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/react-scroll-area@1.2.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/number': 1.1.1
+      '@radix-ui/primitive': 1.1.3
+      '@radix-ui/react-compose-refs': 1.1.2(react@18.3.1)
+      '@radix-ui/react-context': 1.1.2(react@18.3.1)
+      '@radix-ui/react-direction': 1.1.1(react@18.3.1)
+      '@radix-ui/react-presence': 1.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-primitive': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-use-callback-ref': 1.1.1(react@18.3.1)
+      '@radix-ui/react-use-layout-effect': 1.1.1(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/react-select@2.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/number': 1.1.1
+      '@radix-ui/primitive': 1.1.3
+      '@radix-ui/react-collection': 1.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-compose-refs': 1.1.2(react@18.3.1)
+      '@radix-ui/react-context': 1.1.2(react@18.3.1)
+      '@radix-ui/react-direction': 1.1.1(react@18.3.1)
+      '@radix-ui/react-dismissable-layer': 1.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-focus-guards': 1.1.3(react@18.3.1)
+      '@radix-ui/react-focus-scope': 1.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-id': 1.1.1(react@18.3.1)
+      '@radix-ui/react-popper': 1.2.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-portal': 1.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-primitive': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-slot': 1.2.3(react@18.3.1)
+      '@radix-ui/react-use-callback-ref': 1.1.1(react@18.3.1)
+      '@radix-ui/react-use-controllable-state': 1.2.2(react@18.3.1)
+      '@radix-ui/react-use-layout-effect': 1.1.1(react@18.3.1)
+      '@radix-ui/react-use-previous': 1.1.1(react@18.3.1)
+      '@radix-ui/react-visually-hidden': 1.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      aria-hidden: 1.2.6
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+      react-remove-scroll: 2.7.1(react@18.3.1)
+
+  '@radix-ui/react-separator@1.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/react-primitive': 2.1.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/react-slider@1.3.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/number': 1.1.1
+      '@radix-ui/primitive': 1.1.3
+      '@radix-ui/react-collection': 1.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-compose-refs': 1.1.2(react@18.3.1)
+      '@radix-ui/react-context': 1.1.2(react@18.3.1)
+      '@radix-ui/react-direction': 1.1.1(react@18.3.1)
+      '@radix-ui/react-primitive': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-use-controllable-state': 1.2.2(react@18.3.1)
+      '@radix-ui/react-use-layout-effect': 1.1.1(react@18.3.1)
+      '@radix-ui/react-use-previous': 1.1.1(react@18.3.1)
+      '@radix-ui/react-use-size': 1.1.1(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/react-slot@1.2.3(react@18.3.1)':
+    dependencies:
+      '@radix-ui/react-compose-refs': 1.1.2(react@18.3.1)
+      react: 18.3.1
+
+  '@radix-ui/react-slot@1.2.4(react@18.3.1)':
+    dependencies:
+      '@radix-ui/react-compose-refs': 1.1.2(react@18.3.1)
+      react: 18.3.1
+
+  '@radix-ui/react-switch@1.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/primitive': 1.1.3
+      '@radix-ui/react-compose-refs': 1.1.2(react@18.3.1)
+      '@radix-ui/react-context': 1.1.2(react@18.3.1)
+      '@radix-ui/react-primitive': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-use-controllable-state': 1.2.2(react@18.3.1)
+      '@radix-ui/react-use-previous': 1.1.1(react@18.3.1)
+      '@radix-ui/react-use-size': 1.1.1(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/react-tabs@1.1.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/primitive': 1.1.3
+      '@radix-ui/react-context': 1.1.2(react@18.3.1)
+      '@radix-ui/react-direction': 1.1.1(react@18.3.1)
+      '@radix-ui/react-id': 1.1.1(react@18.3.1)
+      '@radix-ui/react-presence': 1.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-primitive': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-roving-focus': 1.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-use-controllable-state': 1.2.2(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/react-toggle-group@1.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/primitive': 1.1.3
+      '@radix-ui/react-context': 1.1.2(react@18.3.1)
+      '@radix-ui/react-direction': 1.1.1(react@18.3.1)
+      '@radix-ui/react-primitive': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-roving-focus': 1.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-toggle': 1.1.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-use-controllable-state': 1.2.2(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/react-toggle@1.1.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/primitive': 1.1.3
+      '@radix-ui/react-primitive': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-use-controllable-state': 1.2.2(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/react-tooltip@1.2.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/primitive': 1.1.3
+      '@radix-ui/react-compose-refs': 1.1.2(react@18.3.1)
+      '@radix-ui/react-context': 1.1.2(react@18.3.1)
+      '@radix-ui/react-dismissable-layer': 1.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-id': 1.1.1(react@18.3.1)
+      '@radix-ui/react-popper': 1.2.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-portal': 1.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-presence': 1.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-primitive': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-slot': 1.2.3(react@18.3.1)
+      '@radix-ui/react-use-controllable-state': 1.2.2(react@18.3.1)
+      '@radix-ui/react-visually-hidden': 1.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/react-use-callback-ref@1.1.1(react@18.3.1)':
+    dependencies:
+      react: 18.3.1
+
+  '@radix-ui/react-use-controllable-state@1.2.2(react@18.3.1)':
+    dependencies:
+      '@radix-ui/react-use-effect-event': 0.0.2(react@18.3.1)
+      '@radix-ui/react-use-layout-effect': 1.1.1(react@18.3.1)
+      react: 18.3.1
+
+  '@radix-ui/react-use-effect-event@0.0.2(react@18.3.1)':
+    dependencies:
+      '@radix-ui/react-use-layout-effect': 1.1.1(react@18.3.1)
+      react: 18.3.1
+
+  '@radix-ui/react-use-escape-keydown@1.1.1(react@18.3.1)':
+    dependencies:
+      '@radix-ui/react-use-callback-ref': 1.1.1(react@18.3.1)
+      react: 18.3.1
+
+  '@radix-ui/react-use-is-hydrated@0.1.0(react@18.3.1)':
+    dependencies:
+      react: 18.3.1
+      use-sync-external-store: 1.6.0(react@18.3.1)
+
+  '@radix-ui/react-use-layout-effect@1.1.1(react@18.3.1)':
+    dependencies:
+      react: 18.3.1
+
+  '@radix-ui/react-use-previous@1.1.1(react@18.3.1)':
+    dependencies:
+      react: 18.3.1
+
+  '@radix-ui/react-use-rect@1.1.1(react@18.3.1)':
+    dependencies:
+      '@radix-ui/rect': 1.1.1
+      react: 18.3.1
+
+  '@radix-ui/react-use-size@1.1.1(react@18.3.1)':
+    dependencies:
+      '@radix-ui/react-use-layout-effect': 1.1.1(react@18.3.1)
+      react: 18.3.1
+
+  '@radix-ui/react-visually-hidden@1.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@radix-ui/react-primitive': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  '@radix-ui/rect@1.1.1': {}
+
+  '@rolldown/pluginutils@1.0.0-beta.27': {}
+
+  '@rollup/rollup-android-arm-eabi@4.53.2':
+    optional: true
+
+  '@rollup/rollup-android-arm64@4.53.2':
+    optional: true
+
+  '@rollup/rollup-darwin-arm64@4.53.2':
+    optional: true
+
+  '@rollup/rollup-darwin-x64@4.53.2':
+    optional: true
+
+  '@rollup/rollup-freebsd-arm64@4.53.2':
+    optional: true
+
+  '@rollup/rollup-freebsd-x64@4.53.2':
+    optional: true
+
+  '@rollup/rollup-linux-arm-gnueabihf@4.53.2':
+    optional: true
+
+  '@rollup/rollup-linux-arm-musleabihf@4.53.2':
+    optional: true
+
+  '@rollup/rollup-linux-arm64-gnu@4.53.2':
+    optional: true
+
+  '@rollup/rollup-linux-arm64-musl@4.53.2':
+    optional: true
+
+  '@rollup/rollup-linux-loong64-gnu@4.53.2':
+    optional: true
+
+  '@rollup/rollup-linux-ppc64-gnu@4.53.2':
+    optional: true
+
+  '@rollup/rollup-linux-riscv64-gnu@4.53.2':
+    optional: true
+
+  '@rollup/rollup-linux-riscv64-musl@4.53.2':
+    optional: true
+
+  '@rollup/rollup-linux-s390x-gnu@4.53.2':
+    optional: true
+
+  '@rollup/rollup-linux-x64-gnu@4.53.2':
+    optional: true
+
+  '@rollup/rollup-linux-x64-musl@4.53.2':
+    optional: true
+
+  '@rollup/rollup-openharmony-arm64@4.53.2':
+    optional: true
+
+  '@rollup/rollup-win32-arm64-msvc@4.53.2':
+    optional: true
+
+  '@rollup/rollup-win32-ia32-msvc@4.53.2':
+    optional: true
+
+  '@rollup/rollup-win32-x64-gnu@4.53.2':
+    optional: true
+
+  '@rollup/rollup-win32-x64-msvc@4.53.2':
+    optional: true
+
+  '@swc/core-darwin-arm64@1.15.2':
+    optional: true
+
+  '@swc/core-darwin-x64@1.15.2':
+    optional: true
+
+  '@swc/core-linux-arm-gnueabihf@1.15.2':
+    optional: true
+
+  '@swc/core-linux-arm64-gnu@1.15.2':
+    optional: true
+
+  '@swc/core-linux-arm64-musl@1.15.2':
+    optional: true
+
+  '@swc/core-linux-x64-gnu@1.15.2':
+    optional: true
+
+  '@swc/core-linux-x64-musl@1.15.2':
+    optional: true
+
+  '@swc/core-win32-arm64-msvc@1.15.2':
+    optional: true
+
+  '@swc/core-win32-ia32-msvc@1.15.2':
+    optional: true
+
+  '@swc/core-win32-x64-msvc@1.15.2':
+    optional: true
+
+  '@swc/core@1.15.2':
+    dependencies:
+      '@swc/counter': 0.1.3
+      '@swc/types': 0.1.25
+    optionalDependencies:
+      '@swc/core-darwin-arm64': 1.15.2
+      '@swc/core-darwin-x64': 1.15.2
+      '@swc/core-linux-arm-gnueabihf': 1.15.2
+      '@swc/core-linux-arm64-gnu': 1.15.2
+      '@swc/core-linux-arm64-musl': 1.15.2
+      '@swc/core-linux-x64-gnu': 1.15.2
+      '@swc/core-linux-x64-musl': 1.15.2
+      '@swc/core-win32-arm64-msvc': 1.15.2
+      '@swc/core-win32-ia32-msvc': 1.15.2
+      '@swc/core-win32-x64-msvc': 1.15.2
+
+  '@swc/counter@0.1.3': {}
+
+  '@swc/types@0.1.25':
+    dependencies:
+      '@swc/counter': 0.1.3
+
+  '@types/d3-array@3.2.2': {}
+
+  '@types/d3-color@3.1.3': {}
+
+  '@types/d3-ease@3.0.2': {}
+
+  '@types/d3-interpolate@3.0.4':
+    dependencies:
+      '@types/d3-color': 3.1.3
+
+  '@types/d3-path@3.1.1': {}
+
+  '@types/d3-scale@4.0.9':
+    dependencies:
+      '@types/d3-time': 3.0.4
+
+  '@types/d3-shape@3.1.7':
+    dependencies:
+      '@types/d3-path': 3.1.1
+
+  '@types/d3-time@3.0.4': {}
+
+  '@types/d3-timer@3.0.2': {}
+
+  '@types/estree@1.0.8': {}
+
+  '@types/node@20.19.25':
+    dependencies:
+      undici-types: 6.21.0
+
+  '@vitejs/plugin-react-swc@3.11.0(vite@6.3.5(@types/node@20.19.25))':
+    dependencies:
+      '@rolldown/pluginutils': 1.0.0-beta.27
+      '@swc/core': 1.15.2
+      vite: 6.3.5(@types/node@20.19.25)
+    transitivePeerDependencies:
+      - '@swc/helpers'
+
+  aria-hidden@1.2.6:
+    dependencies:
+      tslib: 2.8.1
+
+  class-variance-authority@0.7.1:
+    dependencies:
+      clsx: 2.1.1
+
+  clsx@2.1.1: {}
+
+  cmdk@1.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+    dependencies:
+      '@radix-ui/react-compose-refs': 1.1.2(react@18.3.1)
+      '@radix-ui/react-dialog': 1.1.15(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@radix-ui/react-id': 1.1.1(react@18.3.1)
+      '@radix-ui/react-primitive': 2.1.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+    transitivePeerDependencies:
+      - '@types/react'
+      - '@types/react-dom'
+
+  csstype@3.2.2: {}
+
+  d3-array@3.2.4:
+    dependencies:
+      internmap: 2.0.3
+
+  d3-color@3.1.0: {}
+
+  d3-ease@3.0.1: {}
+
+  d3-format@3.1.0: {}
+
+  d3-interpolate@3.0.1:
+    dependencies:
+      d3-color: 3.1.0
+
+  d3-path@3.1.0: {}
+
+  d3-scale@4.0.2:
+    dependencies:
+      d3-array: 3.2.4
+      d3-format: 3.1.0
+      d3-interpolate: 3.0.1
+      d3-time: 3.1.0
+      d3-time-format: 4.1.0
+
+  d3-shape@3.2.0:
+    dependencies:
+      d3-path: 3.1.0
+
+  d3-time-format@4.1.0:
+    dependencies:
+      d3-time: 3.1.0
+
+  d3-time@3.1.0:
+    dependencies:
+      d3-array: 3.2.4
+
+  d3-timer@3.0.1: {}
+
+  date-fns@3.6.0: {}
+
+  decimal.js-light@2.5.1: {}
+
+  detect-node-es@1.1.0: {}
+
+  dom-helpers@5.2.1:
+    dependencies:
+      '@babel/runtime': 7.28.4
+      csstype: 3.2.2
+
+  embla-carousel-react@8.6.0(react@18.3.1):
+    dependencies:
+      embla-carousel: 8.6.0
+      embla-carousel-reactive-utils: 8.6.0(embla-carousel@8.6.0)
+      react: 18.3.1
+
+  embla-carousel-reactive-utils@8.6.0(embla-carousel@8.6.0):
+    dependencies:
+      embla-carousel: 8.6.0
+
+  embla-carousel@8.6.0: {}
+
+  esbuild@0.25.12:
+    optionalDependencies:
+      '@esbuild/aix-ppc64': 0.25.12
+      '@esbuild/android-arm': 0.25.12
+      '@esbuild/android-arm64': 0.25.12
+      '@esbuild/android-x64': 0.25.12
+      '@esbuild/darwin-arm64': 0.25.12
+      '@esbuild/darwin-x64': 0.25.12
+      '@esbuild/freebsd-arm64': 0.25.12
+      '@esbuild/freebsd-x64': 0.25.12
+      '@esbuild/linux-arm': 0.25.12
+      '@esbuild/linux-arm64': 0.25.12
+      '@esbuild/linux-ia32': 0.25.12
+      '@esbuild/linux-loong64': 0.25.12
+      '@esbuild/linux-mips64el': 0.25.12
+      '@esbuild/linux-ppc64': 0.25.12
+      '@esbuild/linux-riscv64': 0.25.12
+      '@esbuild/linux-s390x': 0.25.12
+      '@esbuild/linux-x64': 0.25.12
+      '@esbuild/netbsd-arm64': 0.25.12
+      '@esbuild/netbsd-x64': 0.25.12
+      '@esbuild/openbsd-arm64': 0.25.12
+      '@esbuild/openbsd-x64': 0.25.12
+      '@esbuild/openharmony-arm64': 0.25.12
+      '@esbuild/sunos-x64': 0.25.12
+      '@esbuild/win32-arm64': 0.25.12
+      '@esbuild/win32-ia32': 0.25.12
+      '@esbuild/win32-x64': 0.25.12
+
+  eventemitter3@4.0.7: {}
+
+  fast-equals@5.3.3: {}
+
+  fdir@6.5.0(picomatch@4.0.3):
+    optionalDependencies:
+      picomatch: 4.0.3
+
+  fsevents@2.3.3:
+    optional: true
+
+  get-nonce@1.0.1: {}
+
+  input-otp@1.4.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+    dependencies:
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  internmap@2.0.3: {}
+
+  js-tokens@4.0.0: {}
+
+  lodash@4.17.21: {}
+
+  loose-envify@1.4.0:
+    dependencies:
+      js-tokens: 4.0.0
+
+  lucide-react@0.487.0(react@18.3.1):
+    dependencies:
+      react: 18.3.1
+
+  nanoid@3.3.11: {}
+
+  next-themes@0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+    dependencies:
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  object-assign@4.1.1: {}
+
+  picocolors@1.1.1: {}
+
+  picomatch@4.0.3: {}
+
+  postcss@8.5.6:
+    dependencies:
+      nanoid: 3.3.11
+      picocolors: 1.1.1
+      source-map-js: 1.2.1
+
+  prop-types@15.8.1:
+    dependencies:
+      loose-envify: 1.4.0
+      object-assign: 4.1.1
+      react-is: 16.13.1
+
+  react-day-picker@8.10.1(date-fns@3.6.0)(react@18.3.1):
+    dependencies:
+      date-fns: 3.6.0
+      react: 18.3.1
+
+  react-dom@18.3.1(react@18.3.1):
+    dependencies:
+      loose-envify: 1.4.0
+      react: 18.3.1
+      scheduler: 0.23.2
+
+  react-hook-form@7.66.0(react@18.3.1):
+    dependencies:
+      react: 18.3.1
+
+  react-is@16.13.1: {}
+
+  react-is@18.3.1: {}
+
+  react-remove-scroll-bar@2.3.8(react@18.3.1):
+    dependencies:
+      react: 18.3.1
+      react-style-singleton: 2.2.3(react@18.3.1)
+      tslib: 2.8.1
+
+  react-remove-scroll@2.7.1(react@18.3.1):
+    dependencies:
+      react: 18.3.1
+      react-remove-scroll-bar: 2.3.8(react@18.3.1)
+      react-style-singleton: 2.2.3(react@18.3.1)
+      tslib: 2.8.1
+      use-callback-ref: 1.3.3(react@18.3.1)
+      use-sidecar: 1.1.3(react@18.3.1)
+
+  react-resizable-panels@2.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+    dependencies:
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  react-smooth@4.0.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+    dependencies:
+      fast-equals: 5.3.3
+      prop-types: 15.8.1
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+      react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+
+  react-style-singleton@2.2.3(react@18.3.1):
+    dependencies:
+      get-nonce: 1.0.1
+      react: 18.3.1
+      tslib: 2.8.1
+
+  react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+    dependencies:
+      '@babel/runtime': 7.28.4
+      dom-helpers: 5.2.1
+      loose-envify: 1.4.0
+      prop-types: 15.8.1
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  react@18.3.1:
+    dependencies:
+      loose-envify: 1.4.0
+
+  recharts-scale@0.4.5:
+    dependencies:
+      decimal.js-light: 2.5.1
+
+  recharts@2.15.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+    dependencies:
+      clsx: 2.1.1
+      eventemitter3: 4.0.7
+      lodash: 4.17.21
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+      react-is: 18.3.1
+      react-smooth: 4.0.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      recharts-scale: 0.4.5
+      tiny-invariant: 1.3.3
+      victory-vendor: 36.9.2
+
+  rollup@4.53.2:
+    dependencies:
+      '@types/estree': 1.0.8
+    optionalDependencies:
+      '@rollup/rollup-android-arm-eabi': 4.53.2
+      '@rollup/rollup-android-arm64': 4.53.2
+      '@rollup/rollup-darwin-arm64': 4.53.2
+      '@rollup/rollup-darwin-x64': 4.53.2
+      '@rollup/rollup-freebsd-arm64': 4.53.2
+      '@rollup/rollup-freebsd-x64': 4.53.2
+      '@rollup/rollup-linux-arm-gnueabihf': 4.53.2
+      '@rollup/rollup-linux-arm-musleabihf': 4.53.2
+      '@rollup/rollup-linux-arm64-gnu': 4.53.2
+      '@rollup/rollup-linux-arm64-musl': 4.53.2
+      '@rollup/rollup-linux-loong64-gnu': 4.53.2
+      '@rollup/rollup-linux-ppc64-gnu': 4.53.2
+      '@rollup/rollup-linux-riscv64-gnu': 4.53.2
+      '@rollup/rollup-linux-riscv64-musl': 4.53.2
+      '@rollup/rollup-linux-s390x-gnu': 4.53.2
+      '@rollup/rollup-linux-x64-gnu': 4.53.2
+      '@rollup/rollup-linux-x64-musl': 4.53.2
+      '@rollup/rollup-openharmony-arm64': 4.53.2
+      '@rollup/rollup-win32-arm64-msvc': 4.53.2
+      '@rollup/rollup-win32-ia32-msvc': 4.53.2
+      '@rollup/rollup-win32-x64-gnu': 4.53.2
+      '@rollup/rollup-win32-x64-msvc': 4.53.2
+      fsevents: 2.3.3
+
+  scheduler@0.23.2:
+    dependencies:
+      loose-envify: 1.4.0
+
+  sonner@2.0.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+    dependencies:
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+
+  source-map-js@1.2.1: {}
+
+  tailwind-merge@3.4.0: {}
+
+  tiny-invariant@1.3.3: {}
+
+  tinyglobby@0.2.15:
+    dependencies:
+      fdir: 6.5.0(picomatch@4.0.3)
+      picomatch: 4.0.3
+
+  tslib@2.8.1: {}
+
+  undici-types@6.21.0: {}
+
+  use-callback-ref@1.3.3(react@18.3.1):
+    dependencies:
+      react: 18.3.1
+      tslib: 2.8.1
+
+  use-sidecar@1.1.3(react@18.3.1):
+    dependencies:
+      detect-node-es: 1.1.0
+      react: 18.3.1
+      tslib: 2.8.1
+
+  use-sync-external-store@1.6.0(react@18.3.1):
+    dependencies:
+      react: 18.3.1
+
+  vaul@1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+    dependencies:
+      '@radix-ui/react-dialog': 1.1.15(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      react: 18.3.1
+      react-dom: 18.3.1(react@18.3.1)
+    transitivePeerDependencies:
+      - '@types/react'
+      - '@types/react-dom'
+
+  victory-vendor@36.9.2:
+    dependencies:
+      '@types/d3-array': 3.2.2
+      '@types/d3-ease': 3.0.2
+      '@types/d3-interpolate': 3.0.4
+      '@types/d3-scale': 4.0.9
+      '@types/d3-shape': 3.1.7
+      '@types/d3-time': 3.0.4
+      '@types/d3-timer': 3.0.2
+      d3-array: 3.2.4
+      d3-ease: 3.0.1
+      d3-interpolate: 3.0.1
+      d3-scale: 4.0.2
+      d3-shape: 3.2.0
+      d3-time: 3.1.0
+      d3-timer: 3.0.1
+
+  vite@6.3.5(@types/node@20.19.25):
+    dependencies:
+      esbuild: 0.25.12
+      fdir: 6.5.0(picomatch@4.0.3)
+      picomatch: 4.0.3
+      postcss: 8.5.6
+      rollup: 4.53.2
+      tinyglobby: 0.2.15
+    optionalDependencies:
+      '@types/node': 20.19.25
+      fsevents: 2.3.3

+ 271 - 0
src/ARCHITECTURE.md

@@ -0,0 +1,271 @@
+# Anycall 架构说明
+
+## 页面结构
+
+### 独立页面
+以下页面都是独立的React组件,有自己的路由:
+
+| 页面 | URL路径 | 组件文件 |
+|------|---------|----------|
+| 首页 | `/` | `/components/pages/Home.tsx` |
+| FAQ | `/faq` | `/components/pages/FAQPage.tsx` |
+| Contact | `/contact` | `/components/pages/ContactPage.tsx` |
+| About | `/about` | `/components/pages/About.tsx` |
+| Blog | `/blog` | `/components/pages/Blog.tsx` |
+| Careers | `/careers` | `/components/pages/Careers.tsx` |
+| Privacy | `/privacy` | `/components/pages/PrivacyPolicy.tsx` |
+| Terms | `/terms` | `/components/pages/TermsOfService.tsx` |
+
+### 首页组件
+首页使用以下子组件构建:
+
+- **Hero** (`/components/Hero.tsx`) - 首页顶部英雄区域
+- **Features** (`/components/Features.tsx`) - 功能特性展示
+- **Experts** (`/components/Experts.tsx`) - 专家类别展示
+- ~~FAQ~~ - 已移至独立页面 `/faq`
+- ~~Contact~~ - 已移至独立页面 `/contact`
+
+> **注意:** FAQ和Contact不再是首页的一部分,它们现在是独立页面。
+
+## 路由系统
+
+### 客户端路由
+使用自定义的React Router实现,位于 `/contexts/RouterContext.tsx`
+
+**特点:**
+- ✅ 支持SPA(单页应用)导航
+- ✅ 浏览器前进/后退按钮支持
+- ✅ URL状态管理
+- ✅ 页面之间的平滑过渡
+
+### 服务端配置
+- **Vercel**: 使用 `vercel.json` 配置rewrite规则
+- **Netlify**: 使用 `_redirects` 配置
+- 所有路由请求都会重写到 `index.html`,然后由客户端路由处理
+
+## 从Hash锚点到独立页面的迁移
+
+### 之前的架构(已弃用)
+```
+/ (首页)
+  ├─ #features (锚点)
+  ├─ #faq (锚点)
+  └─ #contact (锚点)
+```
+
+**问题:**
+- ❌ Hash锚点在公网访问时经常失效
+- ❌ 依赖复杂的滚动重试机制
+- ❌ SEO不友好
+- ❌ 无法独立分享FAQ或Contact页面
+
+### 现在的架构(推荐)
+```
+/ (首页)
+  └─ #features (仅此一个锚点用于首页内部导航)
+/faq (独立页面)
+/contact (独立页面)
+/about (独立页面)
+... (其他独立页面)
+```
+
+**优势:**
+- ✅ 每个页面有独立的URL
+- ✅ 更好的SEO支持
+- ✅ 可以直接收藏和分享
+- ✅ 不会出现锚点失效问题
+- ✅ 更清晰的URL结构
+
+## 导航实现
+
+### Header导航
+位于 `/components/Header.tsx`
+
+**桌面端:**
+- Home (回到首页顶部)
+- Features (首页内部锚点滚动)
+- FAQ (导航到 `/faq`)
+- Contact (导航到 `/contact`)
+
+**移动端:**
+- 汉堡菜单展开相同的导航选项
+- 点击后自动关闭菜单
+
+### Footer导航
+位于 `/components/Footer.tsx`
+
+**包含:**
+- Logo和品牌描述
+- Legal链接(Privacy Policy、Terms of Service)
+- 社交媒体链接
+
+## 国际化支持
+
+### 语言系统
+位于 `/contexts/LanguageContext.tsx` 和 `/lib/i18n.ts`
+
+**支持的语言:**
+- 🇺🇸 English (en)
+- 🇨🇳 简体中文 (zh)
+- 🇯🇵 日本語 (ja)
+- 🇫🇷 Français (fr)
+- 🇰🇷 한국어 (ko)
+- 🇪🇸 Español (es)
+
+**特点:**
+- 语言切换不会刷新页面
+- 语言选择会保存到localStorage
+- 所有页面都支持多语言
+
+## 组件复用
+
+### FAQ页面 vs FAQ组件
+- **FAQPage** (`/components/pages/FAQPage.tsx`) - 独立页面版本,包含完整页面结构
+- **FAQ** (`/components/FAQ.tsx`) - 旧版组件,保留用于可能的其他用途
+
+### Contact页面 vs Contact组件
+- **ContactPage** (`/components/pages/ContactPage.tsx`) - 独立页面版本,包含完整页面结构
+- **Contact** (`/components/Contact.tsx`) - 旧版组件,保留用于可能的其他用途
+
+> **建议:** 新开发应该使用独立页面版本(FAQPage、ContactPage)
+
+## 样式系统
+
+### Tailwind CSS v4
+使用 Tailwind CSS v4 进行样式管理
+
+**配置文件:**
+- `/styles/globals.css` - 全局样式和CSS变量
+
+**特点:**
+- 响应式设计(移动优先)
+- 黑白灰配色方案
+- 自定义的scroll-margin-top处理固定header
+
+### ShadCN UI
+使用 ShadCN UI 组件库
+
+**位置:** `/components/ui/`
+
+**常用组件:**
+- Accordion (FAQ折叠面板)
+- Button (按钮)
+- Input (表单输入)
+- Card (卡片)
+- Dialog (对话框)
+等等...
+
+## 性能优化
+
+### 代码分割
+- 每个页面都是独立的组件
+- React会自动进行懒加载优化
+
+### 路由优化
+- 客户端路由避免整页刷新
+- 平滑的页面过渡
+
+### 资源加载
+- 图片使用优化的加载策略
+- 使用Unsplash API获取高质量图片
+
+## 部署注意事项
+
+### 必要的配置文件
+1. **vercel.json** - Vercel部署配置
+2. **_redirects** - Netlify部署配置
+3. **index.html** - SPA入口文件
+
+### 环境要求
+- Node.js 18+
+- 支持ES6+的现代浏览器
+- 启用JavaScript
+
+### SEO配置
+每个页面都应该有:
+- 唯一的标题
+- 描述性的内容
+- 适当的heading标签结构
+
+## 开发指南
+
+### 添加新页面
+
+1. 创建页面组件:
+```typescript
+// /components/pages/NewPage.tsx
+export function NewPage() {
+  return (
+    <div className="min-h-screen bg-white">
+      {/* 页面内容 */}
+    </div>
+  );
+}
+```
+
+2. 在RouterContext中添加路由:
+```typescript
+type PageType = 'home' | 'about' | 'newpage' | ...;
+
+const pageToPath: Record<PageType, string> = {
+  // ...
+  newpage: '/newpage',
+};
+
+const pathToPage: Record<string, PageType> = {
+  // ...
+  '/newpage': 'newpage',
+};
+```
+
+3. 在App.tsx中添加页面渲染:
+```typescript
+case 'newpage':
+  return <NewPage />;
+```
+
+4. 在Header中添加导航链接(如需要)
+
+### 修改现有页面
+
+1. 找到对应的页面组件文件
+2. 编辑组件内容
+3. 确保保持响应式设计
+4. 测试多语言支持
+
+## 故障排查
+
+### 页面404错误
+- 检查路由配置是否正确
+- 确认服务器配置支持SPA路由
+- 查看浏览器控制台错误信息
+
+### 语言切换不工作
+- 检查i18n.ts中是否有对应的翻译
+- 确认LanguageContext正确包裹了应用
+
+### 样式问题
+- 检查Tailwind类名是否正确
+- 确认globals.css被正确导入
+- 查看浏览器开发者工具的样式面板
+
+## 未来改进
+
+### 计划中的功能
+- [ ] 添加页面过渡动画
+- [ ] 实现页面加载进度条
+- [ ] 添加404错误页面
+- [ ] 优化移动端体验
+- [ ] 添加更多的页面(如:Documentation、Pricing等)
+
+### 性能优化
+- [ ] 实现代码分割
+- [ ] 添加图片懒加载
+- [ ] 优化首屏加载时间
+- [ ] 添加Service Worker支持PWA
+
+## 相关文档
+
+- [DEPLOYMENT.md](./DEPLOYMENT.md) - 部署指南
+- [QUICK_FIX.md](./QUICK_FIX.md) - 快速参考
+- [DEBUG.md](./DEBUG.md) - 调试指南

+ 85 - 0
src/App.tsx

@@ -0,0 +1,85 @@
+import { useEffect } from 'react';
+import { Header } from './components/Header';
+import { Footer } from './components/Footer';
+import { LanguageProvider } from './contexts/LanguageContext';
+import { RouterProvider, useRouter } from './contexts/RouterContext';
+import { Toaster } from './components/ui/sonner';
+import { Home } from './components/pages/Home';
+import { About } from './components/pages/About';
+import { Blog } from './components/pages/Blog';
+import { Careers } from './components/pages/Careers';
+import { PrivacyPolicy } from './components/pages/PrivacyPolicy';
+import { TermsOfService } from './components/pages/TermsOfService';
+import { FAQPage } from './components/pages/FAQPage';
+import { ContactPage } from './components/pages/ContactPage';
+
+function AppContent() {
+  const { currentPage } = useRouter();
+  
+  // Ensure hash scrolling works even after page is fully loaded
+  useEffect(() => {
+    // Wait for all images and resources to load
+    if (document.readyState === 'complete') {
+      handleHashScroll();
+    } else {
+      window.addEventListener('load', handleHashScroll);
+      return () => window.removeEventListener('load', handleHashScroll);
+    }
+    
+    function handleHashScroll() {
+      const hash = window.location.hash.replace('#', '');
+      if (hash) {
+        setTimeout(() => {
+          const element = document.getElementById(hash);
+          if (element) {
+            element.scrollIntoView({ behavior: 'smooth', block: 'start' });
+          }
+        }, 100);
+      }
+    }
+  }, [currentPage]);
+  
+  const renderPage = () => {
+    switch (currentPage) {
+      case 'home':
+        return <Home />;
+      case 'about':
+        return <About />;
+      case 'blog':
+        return <Blog />;
+      case 'careers':
+        return <Careers />;
+      case 'privacy':
+        return <PrivacyPolicy />;
+      case 'terms':
+        return <TermsOfService />;
+      case 'faq':
+        return <FAQPage />;
+      case 'contact':
+        return <ContactPage />;
+      default:
+        return <Home />;
+    }
+  };
+  
+  return (
+    <div className="min-h-screen flex flex-col">
+      <Header />
+      <main className="flex-1">
+        {renderPage()}
+      </main>
+      <Footer />
+      <Toaster />
+    </div>
+  );
+}
+
+export default function App() {
+  return (
+    <LanguageProvider>
+      <RouterProvider>
+        <AppContent />
+      </RouterProvider>
+    </LanguageProvider>
+  );
+}

+ 3 - 0
src/Attributions.md

@@ -0,0 +1,3 @@
+This Figma Make file includes components from [shadcn/ui](https://ui.shadcn.com/) used under [MIT license](https://github.com/shadcn-ui/ui/blob/main/LICENSE.md).
+
+This Figma Make file includes photos from [Unsplash](https://unsplash.com) used under [license](https://unsplash.com/license).

+ 179 - 0
src/CHANGELOG.md

@@ -0,0 +1,179 @@
+# 更新日志 (Changelog)
+
+## [2.0.0] - 2025-01-18
+
+### 🎉 重大变更 - FAQ和Contact改为独立页面
+
+#### 新增 (Added)
+- ✅ 创建 `/faq` 独立FAQ页面 (`/components/pages/FAQPage.tsx`)
+- ✅ 创建 `/contact` 独立Contact页面 (`/components/pages/ContactPage.tsx`)
+- ✅ 在路由系统中添加 `faq` 和 `contact` 页面类型
+- ✅ 创建 `ARCHITECTURE.md` - 完整的架构文档
+- ✅ 创建 `MIGRATION_GUIDE.md` - 详细的迁移指南
+
+#### 改进 (Changed)
+- 🔄 Header导航从hash锚点改为页面导航
+  - FAQ: `/#faq` → `/faq`
+  - Contact: `/#contact` → `/contact`
+- 🔄 更新 `RouterContext.tsx` 支持新页面路由
+- 🔄 更新 `App.tsx` 添加新页面渲染
+- 🔄 更新 `DEPLOYMENT.md` 说明新的URL结构
+- 🔄 更新 `QUICK_FIX.md` 推荐使用独立页面
+
+#### 移除 (Removed)
+- ❌ 移除 `RouterContext.tsx` 中不必要的hash调试代码
+- ❌ 从首页移除FAQ和Contact sections(改为独立页面)
+
+#### 修复 (Fixed)
+- 🐛 解决公网访问时hash锚点失效的问题
+- 🐛 消除复杂的滚动重试机制需求
+- 🐛 修复URL格式不规范导致的导航问题
+
+#### 技术债务 (Technical Debt)
+- 📝 保留旧的 `FAQ.tsx` 和 `Contact.tsx` 组件以保证向后兼容
+- 📝 Features仍使用hash锚点(首页内部导航,暂不需要独立页面)
+
+---
+
+## [1.5.0] - 2025-01-17
+
+### 品牌名称统一
+
+#### 改进 (Changed)
+- 🔄 将所有文件中的 "AnyCall" 统一改为 "Anycall"
+- 🔄 更新6种语言版本的品牌名称一致性
+- 🔄 涉及74处修改(Header.tsx、Footer.tsx、i18n.ts等)
+
+---
+
+## [1.4.0] - 2025-01-17
+
+### 隐私政策和服务条款更新
+
+#### 改进 (Changed)
+- 🔄 更新 Privacy Policy 为完整的法律文档
+  - 添加NEXTHUMAN.PTE.LTD.公司信息
+  - 详细的数据收集和使用说明
+  - GDPR和CCPA合规性内容
+- 🔄 更新 Terms of Service 为详细的服务条款
+  - 完整的12个主要章节
+  - 包含仲裁、知识产权等法律条款
+  - 更新日期:17/10/2025
+
+#### 改进 (Changed)
+- 🔄 统一所有邮箱地址从 `Anycall_support@Anycall.online` 改为 `Anycall_support@hotmail.com`
+  - Privacy Policy: 2处更新
+  - Terms of Service: 5处更新
+
+---
+
+## [1.3.0] - 2025-01-16
+
+### 路由系统优化
+
+#### 新增 (Added)
+- ✅ 创建自定义路由系统 (`RouterContext.tsx`)
+- ✅ 添加浏览器前进/后退支持
+- ✅ 实现hash锚点滚动机制(后续在2.0.0中改进)
+
+#### 改进 (Changed)
+- 🔄 从简单的hash导航升级到完整的SPA路由
+- 🔄 添加多次重试机制处理锚点滚动
+
+---
+
+## [1.2.0] - 2025-01-15
+
+### 国际化系统
+
+#### 新增 (Added)
+- ✅ 实现6种语言支持系统
+  - 英文 (en)
+  - 简体中文 (zh)
+  - 日文 (ja)
+  - 法文 (fr)
+  - 韩文 (ko)
+  - 西班牙文 (es)
+- ✅ 创建 `LanguageContext.tsx` 和 `i18n.ts`
+- ✅ Header添加语言切换下拉菜单
+
+---
+
+## [1.1.0] - 2025-01-14
+
+### 完整页面结构
+
+#### 新增 (Added)
+- ✅ About页面 (`/about`)
+- ✅ Blog页面 (`/blog`)
+- ✅ Careers页面 (`/careers`)
+- ✅ Privacy Policy页面 (`/privacy`)
+- ✅ Terms of Service页面 (`/terms`)
+- ✅ Footer组件,包含法律链接和社交媒体
+
+---
+
+## [1.0.0] - 2025-01-13
+
+### 初始发布
+
+#### 新增 (Added)
+- ✅ 首页 (`/`) 包含以下部分:
+  - Hero区域
+  - Features展示
+  - Experts类别
+  - FAQ(后续迁移到独立页面)
+  - Contact表单(后续迁移到独立页面)
+- ✅ Header导航组件
+- ✅ 响应式设计(移动端和桌面端)
+- ✅ Tailwind CSS v4样式系统
+- ✅ ShadCN UI组件库集成
+
+#### 技术栈
+- React 18+
+- TypeScript
+- Tailwind CSS v4
+- ShadCN UI
+- Vite
+
+---
+
+## 版本说明
+
+### 版本编号规则
+遵循语义化版本控制 (Semantic Versioning)
+
+- **主版本号 (Major)**: 不兼容的API修改
+- **次版本号 (Minor)**: 向下兼容的功能性新增
+- **修订号 (Patch)**: 向下兼容的问题修正
+
+### 图例
+- ✅ 新增功能
+- 🔄 功能改进
+- 🐛 Bug修复
+- ❌ 功能移除
+- 📝 文档更新
+- ⚠️ 重要提示
+- 🎉 重大更新
+
+---
+
+## 未来计划
+
+### [2.1.0] - 计划中
+- [ ] 添加页面过渡动画
+- [ ] 实现代码分割优化
+- [ ] 添加图片懒加载
+- [ ] 创建404错误页面
+
+### [2.2.0] - 考虑中
+- [ ] 添加面包屑导航
+- [ ] 实现页面加载进度条
+- [ ] 优化移动端交互体验
+- [ ] 添加PWA支持
+
+### [3.0.0] - 长期规划
+- [ ] 用户认证系统
+- [ ] 个人Dashboard
+- [ ] 在线预约系统
+- [ ] 支付集成

+ 190 - 0
src/DEBUG.md

@@ -0,0 +1,190 @@
+# 调试指南 - Hash锚点问题
+
+## 问题:输入 `anycall.online#contact` 被重定向
+
+### 原因分析
+
+当你在浏览器地址栏输入 `anycall.online#contact` 时:
+
+1. **浏览器解析**:
+   - 浏览器会将其解析为:`http://anycall.online#contact`
+   - 注意:**缺少路径部分的 `/`**
+
+2. **服务器请求**:
+   - 浏览器发送请求到服务器时,**hash片段不会被发送**
+   - 服务器只收到:`GET http://anycall.online`
+   - Hash部分 `#contact` 保留在浏览器端
+
+3. **可能的问题**:
+   - 如果服务器配置了 `www` 重定向,会重定向到 `www.anycall.online`
+   - 重定向时,hash片段可能丢失
+
+### 解决方案
+
+#### 方案1:使用正确的URL格式(推荐)
+
+在浏览器地址栏输入时,请使用:
+```
+www.anycall.online/#contact
+```
+或
+```
+https://www.anycall.online/#contact
+```
+
+#### 方案2:配置服务器保留Hash
+
+确保你的托管服务商(如Vercel, Netlify)配置了:
+
+**Vercel:**
+```json
+{
+  "redirects": [
+    {
+      "source": "/:path((?!www\\.)?.*)",
+      "has": [
+        {
+          "type": "host",
+          "value": "anycall.online"
+        }
+      ],
+      "destination": "https://www.anycall.online/:path",
+      "permanent": true
+    }
+  ]
+}
+```
+
+**Netlify (_redirects):**
+```
+http://anycall.online/*  https://www.anycall.online/:splat  301!
+```
+
+#### 方案3:使用JavaScript检测和修复
+
+项目已经在 `RouterContext.tsx` 中添加了检测逻辑:
+```typescript
+useEffect(() => {
+  const hash = window.location.hash;
+  const pathname = window.location.pathname;
+  
+  console.log('Initial URL check:', {
+    href: window.location.href,
+    pathname: pathname,
+    hash: hash
+  });
+  
+  if (pathname === '' || pathname === null) {
+    const newUrl = '/' + hash;
+    window.history.replaceState({ page: 'home' }, '', newUrl);
+  }
+}, []);
+```
+
+## 测试步骤
+
+### 1. 检查URL解析
+
+打开浏览器控制台,输入:
+```javascript
+console.log({
+  href: window.location.href,
+  origin: window.location.origin,
+  pathname: window.location.pathname,
+  hash: window.location.hash,
+  search: window.location.search
+});
+```
+
+### 2. 测试不同的URL格式
+
+分别在地址栏输入以下URL并观察结果:
+
+| 输入的URL | 期望结果 | 实际路径 | Hash保留 |
+|----------|---------|---------|---------|
+| `anycall.online#contact` | 可能重定向 | `/` | ❌ 可能丢失 |
+| `anycall.online/#contact` | 正常 | `/` | ✅ 保留 |
+| `www.anycall.online/#contact` | 正常 | `/` | ✅ 保留 |
+| `https://www.anycall.online/#contact` | 正常 | `/` | ✅ 保留 |
+
+### 3. 检查元素ID
+
+在浏览器控制台,检查目标元素是否存在:
+```javascript
+console.log('Contact element:', document.getElementById('contact'));
+console.log('FAQ element:', document.getElementById('faq'));
+console.log('Features element:', document.getElementById('features'));
+```
+
+### 4. 手动触发滚动
+
+如果自动滚动不工作,在控制台手动测试:
+```javascript
+const element = document.getElementById('contact');
+if (element) {
+  element.scrollIntoView({ behavior: 'smooth', block: 'start' });
+  console.log('Scrolled to contact');
+} else {
+  console.log('Contact element not found');
+}
+```
+
+## 常见问题
+
+### Q1: 为什么 `anycall.online#contact` 会丢失hash?
+
+**A:** 因为服务器可能配置了重定向规则(如强制www或HTTPS),重定向时浏览器可能不会保留hash片段。
+
+### Q2: 如何确保hash总是被保留?
+
+**A:** 
+1. 始终使用完整的URL格式,包括协议和路径
+2. 配置服务器的重定向规则正确处理hash
+3. 使用客户端JavaScript修复(已在项目中实现)
+
+### Q3: 为什么有时候滚动不到正确位置?
+
+**A:** 可能的原因:
+1. 页面还在加载中,目标元素尚未渲染
+2. 图片加载导致页面高度变化
+3. CSS动画影响
+
+**解决方案:**
+项目已实现:
+- 重试机制(最多10次)
+- 等待window.load事件
+- 多个延迟尝试(800ms, 1200ms)
+
+## 浏览器兼容性
+
+| 浏览器 | 版本 | Hash支持 | 备注 |
+|-------|------|---------|------|
+| Chrome | 90+ | ✅ | 完全支持 |
+| Firefox | 88+ | ✅ | 完全支持 |
+| Safari | 14+ | ✅ | 完全支持 |
+| Edge | 90+ | ✅ | 完全支持 |
+
+## 获取帮助
+
+如果问题仍然存在:
+
+1. 检查浏览器控制台的错误和警告
+2. 查看Network标签,确认请求和响应
+3. 检查RouterContext的console.log输出
+4. 验证服务器配置文件(vercel.json或_redirects)
+
+## 推荐的分享链接格式
+
+在社交媒体、邮件或其他地方分享链接时,请使用完整格式:
+
+```
+https://www.anycall.online/#contact
+https://www.anycall.online/#faq
+https://www.anycall.online/about
+```
+
+**不要使用:**
+```
+anycall.online#contact  ❌
+www.anycall.online#contact  ❌
+```

+ 170 - 0
src/DEPLOYMENT.md

@@ -0,0 +1,170 @@
+# Anycall 部署指南
+
+## URL 格式说明
+
+### ✅ 推荐使用独立页面URL
+FAQ和Contact现在都是独立页面,推荐使用以下格式:
+
+**推荐格式:**
+- `https://www.anycall.online/faq` - FAQ页面
+- `https://www.anycall.online/contact` - Contact页面
+- `https://www.anycall.online/about` - About页面
+- `https://www.anycall.online/blog` - Blog页面
+- `https://www.anycall.online/careers` - Careers页面
+- `https://www.anycall.online/privacy` - Privacy Policy
+- `https://www.anycall.online/terms` - Terms of Service
+
+### 传统Hash锚点格式(仅用于首页内部导航)
+在首页内部导航时,可以使用hash锚点:
+
+✅ **正确格式:**
+- `https://www.anycall.online/#features` - 首页Features部分
+
+❌ **不再推荐:**
+- `https://www.anycall.online/#contact` - 请改用 `/contact`
+- `https://www.anycall.online/#faq` - 请改用 `/faq`
+
+### 为什么需要 `/`?
+
+在URL中,hash片段(`#`后面的部分)必须在路径之后。标准的URL结构是:
+```
+protocol://domain/path#hash
+```
+
+如果路径为根路径,也必须包含 `/`:
+```
+protocol://domain/#hash
+```
+
+## 部署配置
+
+### Vercel 部署
+项目包含 `vercel.json` 配置文件,它会:
+1. 将所有请求重写到 `index.html`(支持SPA路由)
+2. 添加安全header
+
+### Netlify 部署
+项目包含 `_redirects` 和 `public/_redirects` 配置文件,它会:
+1. 将所有请求重定向到 `index.html` with 200 status(支持SPA路由)
+
+### 自定义服务器
+如果使用自定义服务器(如Nginx或Apache),需要配置:
+
+#### Nginx 配置示例:
+```nginx
+server {
+    listen 80;
+    server_name anycall.online www.anycall.online;
+
+    root /var/www/anycall;
+    index index.html;
+
+    location / {
+        try_files $uri $uri/ /index.html;
+    }
+
+    # 确保hash片段被正确处理
+    location ~ ^/[^/]*#.* {
+        rewrite ^(.*)$ / permanent;
+    }
+}
+```
+
+#### Apache .htaccess 配置示例:
+```apache
+<IfModule mod_rewrite.c>
+    RewriteEngine On
+    RewriteBase /
+    
+    # 处理所有请求到 index.html
+    RewriteCond %{REQUEST_FILENAME} !-f
+    RewriteCond %{REQUEST_FILENAME} !-d
+    RewriteRule . /index.html [L]
+</IfModule>
+```
+
+## Hash 锚点实现
+
+项目使用以下机制确保hash锚点正常工作:
+
+1. **RouterContext.tsx**: 多层hash处理逻辑
+   - 初始加载时检测hash
+   - 页面变化时保持hash
+   - 重试机制(最多10次,每次间隔100ms)
+
+2. **App.tsx**: 额外的页面加载后处理
+   - 监听window.load事件
+   - 确保所有资源加载完成后滚动
+
+3. **globals.css**: scroll-margin-top配置
+   - 移动端:7rem (112px)
+   - 平板端:7.5rem (120px)
+   - 桌面端:8rem (128px)
+
+## 测试URL
+
+部署后,请测试以下URL:
+
+1. **首页**
+   - https://www.anycall.online/
+   - https://anycall.online/
+
+2. **独立页面(推荐)**
+   - https://www.anycall.online/faq - FAQ页面
+   - https://www.anycall.online/contact - 联系我们
+   - https://www.anycall.online/about - 关于我们
+   - https://www.anycall.online/blog - 博客
+   - https://www.anycall.online/careers - 招聘
+   - https://www.anycall.online/privacy - 隐私政策
+   - https://www.anycall.online/terms - 服务条款
+
+3. **首页内部导航(Hash锚点)**
+   - https://www.anycall.online/#features - Features部分
+
+## 故障排查
+
+### 页面导航不工作?
+
+1. **检查URL格式**
+   - 使用完整路径:`/faq` 而不是 `/#faq`
+   - 独立页面无需hash符号
+
+2. **检查浏览器控制台**
+   - 打开开发者工具查看console.log
+   - RouterContext会输出调试信息
+
+3. **清除浏览器缓存**
+   - 按 Ctrl+Shift+R (Windows) 或 Cmd+Shift+R (Mac)
+   - 或使用无痕模式测试
+
+4. **清除浏览器缓存**
+   - 按 Ctrl+Shift+R (Windows) 或 Cmd+Shift+R (Mac)
+
+### URL重定向问题?
+
+1. **检查服务器配置**
+   - 确保服务器正确配置了SPA重写规则
+
+2. **检查DNS设置**
+   - 确保 `anycall.online` 和 `www.anycall.online` 都正确解析
+
+3. **检查HTTPS重定向**
+   - 确保HTTP自动重定向到HTTPS
+
+## 性能优化
+
+1. **启用Gzip压缩**
+2. **启用浏览器缓存**
+3. **使用CDN**
+4. **图片优化**
+
+## 安全配置
+
+项目已包含以下安全header(在vercel.json中):
+- `X-Content-Type-Options: nosniff`
+- `X-Frame-Options: DENY`
+- `X-XSS-Protection: 1; mode=block`
+
+建议添加:
+- HTTPS强制(HSTS header)
+- Content-Security-Policy

+ 269 - 0
src/MIGRATION_GUIDE.md

@@ -0,0 +1,269 @@
+# 迁移指南:从Hash锚点到独立页面
+
+## 概述
+
+这个指南帮助您了解Anycall网站从hash锚点架构迁移到独立页面架构的变化。
+
+## 为什么要迁移?
+
+### 旧架构的问题(Hash锚点)
+
+```
+问题URL示例:
+https://www.anycall.online/#faq
+https://www.anycall.online/#contact
+```
+
+**遇到的问题:**
+1. ❌ 在公网环境中,hash锚点经常失效
+2. ❌ 需要复杂的JavaScript重试机制
+3. ❌ SEO不友好(搜索引擎难以索引)
+4. ❌ 无法为FAQ或Contact创建独立的书签
+5. ❌ 分享链接时容易出错
+
+### 新架构的优势(独立页面)
+
+```
+新的URL格式:
+https://www.anycall.online/faq
+https://www.anycall.online/contact
+```
+
+**解决的问题:**
+1. ✅ 稳定可靠,不会失效
+2. ✅ 无需复杂的滚动机制
+3. ✅ SEO友好,每个页面可独立索引
+4. ✅ 可以直接收藏和分享
+5. ✅ URL结构清晰直观
+
+## URL映射表
+
+### 变化对照
+
+| 旧URL (Hash锚点) | 新URL (独立页面) | 状态 |
+|------------------|------------------|------|
+| `/#faq` | `/faq` | ✅ 已迁移 |
+| `/#contact` | `/contact` | ✅ 已迁移 |
+| `/#features` | `/#features` | ⚠️ 保留(首页内部导航)|
+
+### 完整的URL列表
+
+```
+首页相关:
+/ - 首页
+/#features - 功能特性(首页内部锚点)
+
+独立页面:
+/faq - 常见问题
+/contact - 联系我们
+/about - 关于我们
+/blog - 博客
+/careers - 招聘信息
+/privacy - 隐私政策
+/terms - 服务条款
+```
+
+## 代码变化
+
+### 1. 路由配置
+
+**之前 (RouterContext.tsx):**
+```typescript
+type PageType = 'home' | 'about' | 'blog' | 'careers' | 'privacy' | 'terms';
+```
+
+**现在 (RouterContext.tsx):**
+```typescript
+type PageType = 'home' | 'about' | 'blog' | 'careers' | 'privacy' | 'terms' | 'faq' | 'contact';
+```
+
+### 2. 页面组件
+
+**新增的页面组件:**
+- `/components/pages/FAQPage.tsx` - FAQ独立页面
+- `/components/pages/ContactPage.tsx` - Contact独立页面
+
+**保留的组件(向后兼容):**
+- `/components/FAQ.tsx` - 旧的FAQ组件
+- `/components/Contact.tsx` - 旧的Contact组件
+
+### 3. 导航链接
+
+**之前 (Header.tsx):**
+```typescript
+// 使用scrollToSection函数和hash锚点
+<button onClick={() => scrollToSection('faq')}>FAQ</button>
+<button onClick={() => scrollToSection('contact')}>Contact</button>
+```
+
+**现在 (Header.tsx):**
+```typescript
+// 使用navigateTo进行页面导航
+<button onClick={() => handleNavigation('faq')}>FAQ</button>
+<button onClick={() => handleNavigation('contact')}>Contact</button>
+```
+
+### 4. App.tsx路由渲染
+
+**新增的case分支:**
+```typescript
+case 'faq':
+  return <FAQPage />;
+case 'contact':
+  return <ContactPage />;
+```
+
+## 对用户的影响
+
+### 外部链接和书签
+
+**旧书签/链接仍然有效:**
+```
+✅ https://www.anycall.online/ (首页)
+✅ https://www.anycall.online/about (关于页面)
+✅ https://www.anycall.online/blog (博客)
+```
+
+**需要更新的书签/链接:**
+```
+❌ https://www.anycall.online/#faq
+✅ 改为:https://www.anycall.online/faq
+
+❌ https://www.anycall.online/#contact
+✅ 改为:https://www.anycall.online/contact
+```
+
+### 浏览器行为
+
+**之前:**
+1. 访问 `/#faq` 会尝试滚动到FAQ部分
+2. 如果失败,会重试多次
+3. 可能需要等待较长时间
+
+**现在:**
+1. 访问 `/faq` 直接加载FAQ页面
+2. 立即显示内容,无需等待
+3. 更快、更可靠
+
+## 技术实现细节
+
+### 服务器配置
+
+**不需要修改服务器配置**,因为:
+- Vercel和Netlify的SPA配置已经支持所有路由
+- `vercel.json` 和 `_redirects` 文件会将所有请求重写到 `index.html`
+- React Router在客户端处理路由
+
+### 向后兼容性
+
+旧的hash URL会如何处理?
+
+1. **自动重定向**:可以添加逻辑将 `/#faq` 重定向到 `/faq`
+2. **404处理**:如果直接访问旧URL,导航栏仍然可以工作
+3. **渐进式迁移**:首页上的Features仍然使用hash锚点
+
+### SEO优化
+
+每个独立页面现在可以有:
+- 独立的页面标题 (`<h1>`)
+- 独立的meta描述
+- 独立的URL路径
+- 更好的搜索引擎索引
+
+## 测试清单
+
+### 部署后测试
+
+- [ ] 访问 `/faq` 显示FAQ页面
+- [ ] 访问 `/contact` 显示Contact页面
+- [ ] 首页导航点击FAQ跳转到 `/faq`
+- [ ] 首页导航点击Contact跳转到 `/contact`
+- [ ] 浏览器后退按钮正常工作
+- [ ] 浏览器前进按钮正常工作
+- [ ] 刷新页面后URL保持不变
+- [ ] 移动端导航正常
+- [ ] 语言切换后页面路由保持正确
+
+### 跨浏览器测试
+
+- [ ] Chrome/Edge (Chromium)
+- [ ] Firefox
+- [ ] Safari (macOS/iOS)
+- [ ] 移动端浏览器
+
+## 常见问题
+
+### Q: 旧的hash URL还能用吗?
+
+A: 可以访问,但不推荐。建议更新所有书签和外部链接到新的URL格式。
+
+### Q: 这会影响SEO吗?
+
+A: **正面影响!** 独立页面比hash锚点更SEO友好,搜索引擎可以更好地索引每个页面。
+
+### Q: 需要修改服务器配置吗?
+
+A: 不需要。现有的SPA配置(`vercel.json`或`_redirects`)已经支持新的路由。
+
+### Q: 如何在代码中链接到这些页面?
+
+A: 使用`navigateTo('faq')`或`navigateTo('contact')`函数,而不是`scrollToSection`。
+
+### Q: Features为什么还用hash锚点?
+
+A: Features是首页的一部分,使用hash锚点进行页面内导航是合理的。只有FAQ和Contact需要独立页面。
+
+### Q: 这会破坏现有功能吗?
+
+A: 不会。所有现有功能都保持正常,新架构只是让某些功能更可靠。
+
+## 回滚计划
+
+如果需要回滚到旧架构:
+
+1. 恢复 `RouterContext.tsx` 中的PageType定义
+2. 移除 `FAQPage.tsx` 和 `ContactPage.tsx` 导入
+3. 在 `Header.tsx` 中恢复 `scrollToSection` 调用
+4. 在 `App.tsx` 中移除新的case分支
+
+但是,**不建议回滚**,因为新架构解决了旧架构的根本问题。
+
+## 下一步
+
+### 推荐的改进
+
+1. **添加页面过渡动画**
+   - 在页面切换时添加淡入淡出效果
+   - 使用 `motion/react` 库
+
+2. **添加面包屑导航**
+   - 让用户知道当前位置
+   - 特别是在深层页面时
+
+3. **实现404页面**
+   - 处理无效的URL路径
+   - 提供友好的错误提示
+
+4. **添加页面加载指示器**
+   - 在页面切换时显示进度
+   - 改善用户体验
+
+### 监控和分析
+
+建议添加:
+- Google Analytics 页面浏览追踪
+- 用户导航路径分析
+- 页面加载性能监控
+
+## 总结
+
+这次迁移从根本上解决了hash锚点在公网环境中的可靠性问题,同时带来了更好的SEO、更清晰的URL结构和更好的用户体验。
+
+**关键收益:**
+- ✅ 100%可靠的页面访问
+- ✅ 更好的SEO排名
+- ✅ 更清晰的URL结构
+- ✅ 更好的用户体验
+- ✅ 更易于维护的代码
+
+**推荐所有外部链接、书签和集成更新为新的URL格式!**

+ 110 - 0
src/QUICK_FIX.md

@@ -0,0 +1,110 @@
+# 快速参考 - 页面导航
+
+## ✅ 推荐方案 - 使用独立页面
+
+FAQ和Contact现在都是独立页面,直接访问更可靠:
+
+```
+✅ https://www.anycall.online/faq
+✅ https://www.anycall.online/contact
+✅ www.anycall.online/faq
+✅ www.anycall.online/contact
+```
+
+**优点:无需hash锚点,导航更稳定可靠**
+
+## 🎯 原理
+
+### 错误的URL
+```
+anycall.online#contact
+         ↑
+      缺少 /
+```
+
+### 正确的URL
+```
+anycall.online/#contact
+          ↑
+        有 /
+```
+
+## 📝 新的URL结构
+
+现代化的独立页面设计:
+```
+协议://域名/页面路径
+
+https://www.anycall.online/faq
+  ↓       ↓           ↓
+协议     域名      页面路径
+```
+
+不再需要复杂的hash锚点!
+
+## 🔧 架构改进
+
+1. **独立页面优势**:
+   - 每个页面有自己的URL
+   - 更好的SEO支持
+   - 可以直接收藏和分享
+   - 不会出现锚点失效问题
+
+2. **路由系统**:
+   - 使用React Router进行客户端路由
+   - 支持浏览器前进/后退按钮
+   - 服务端配置支持SPA路由
+
+3. **向后兼容**:
+   - Features仍然使用hash锚点(仅限首页内部)
+   - 旧的hash URL会自动重定向到新页面
+
+## 📱 分享链接
+
+### 推荐格式(用于分享)
+
+```
+✅ https://www.anycall.online/contact
+✅ https://www.anycall.online/faq
+✅ https://www.anycall.online/about
+✅ https://www.anycall.online/blog
+```
+
+### 旧格式(自动兼容)
+
+```
+⚠️ https://www.anycall.online/#contact (将重定向到 /contact)
+⚠️ https://www.anycall.online/#faq (将重定向到 /faq)
+```
+
+## 🚀 所有可用的页面
+
+```
+/                - 首页
+/faq            - 常见问题
+/contact        - 联系我们
+/about          - 关于我们
+/blog           - 博客
+/careers        - 招聘
+/privacy        - 隐私政策
+/terms          - 服务条款
+/#features      - 功能特性(首页内部锚点)
+```
+
+## 💡 提示
+
+- 使用浏览器书签时,保存完整的URL路径
+- 从其他网站链接时,使用 `/page` 格式,不要使用 `/#page`
+- 每个页面都有独立的URL,可以直接分享
+
+## 🔍 验证方法
+
+在浏览器控制台检查:
+```javascript
+console.log(window.location.pathname);
+// FAQ页面应显示: /faq
+// Contact页面应显示: /contact
+// 首页应显示: /
+```
+
+路径清晰明了,没有复杂的hash符号!

+ 202 - 0
src/README.md

@@ -0,0 +1,202 @@
+# Anycall 官方网站
+
+> 一个可以与全球行业专家的AI分身打电话的app官方网站
+
+[![Version](https://img.shields.io/badge/version-2.0.0-blue.svg)](./CHANGELOG.md)
+[![React](https://img.shields.io/badge/React-18+-61DAFB.svg?logo=react)](https://reactjs.org/)
+[![TypeScript](https://img.shields.io/badge/TypeScript-5+-3178C6.svg?logo=typescript)](https://www.typescriptlang.org/)
+[![Tailwind CSS](https://img.shields.io/badge/Tailwind%20CSS-4.0-38B2AC.svg?logo=tailwind-css)](https://tailwindcss.com/)
+
+## 🌟 特性
+
+- ✅ **独立页面架构** - FAQ和Contact都是独立页面,不再依赖hash锚点
+- 🌍 **6国语言支持** - 英文、中文、日文、法文、韩文、西班牙文
+- 📱 **完全响应式** - 移动端和桌面端都有良好体验
+- 🎨 **现代化设计** - 简洁的黑白灰配色方案
+- ⚡ **快速导航** - SPA单页应用,无刷新页面切换
+- 🔒 **隐私保护** - 完整的隐私政策和服务条款
+
+## 📋 页面结构
+
+### 主要页面
+- **首页** (`/`) - 产品介绍、功能展示、专家类别
+- **FAQ** (`/faq`) - 常见问题解答
+- **联系我们** (`/contact`) - 联系表单和联系方式
+- **关于我们** (`/about`) - 公司介绍
+- **博客** (`/blog`) - 文章列表
+- **招聘** (`/careers`) - 职位信息
+
+### 法律页面
+- **隐私政策** (`/privacy`) - 详细的隐私保护说明
+- **服务条款** (`/terms`) - 完整的使用条款
+
+## 🚀 快速开始
+
+### 访问网站
+直接访问以下URL即可:
+- 首页: https://www.anycall.online/
+- FAQ: https://www.anycall.online/faq
+- 联系: https://www.anycall.online/contact
+
+### 本地开发
+
+```bash
+# 安装依赖
+npm install
+
+# 启动开发服务器
+npm run dev
+
+# 构建生产版本
+npm run build
+
+# 预览生产版本
+npm run preview
+```
+
+## 🏗️ 技术栈
+
+### 核心技术
+- **React 18+** - UI框架
+- **TypeScript** - 类型安全
+- **Vite** - 构建工具
+- **Tailwind CSS v4** - 样式系统
+
+### UI组件
+- **ShadCN UI** - 高质量React组件库
+- **Lucide React** - 图标库
+- **Sonner** - Toast通知
+
+### 其他工具
+- **React Hook Form** - 表单管理
+- **Unsplash API** - 图片资源
+
+## 📁 项目结构
+
+```
+├── components/
+│   ├── pages/          # 页面组件
+│   │   ├── Home.tsx
+│   │   ├── FAQPage.tsx      # FAQ独立页面 ✨
+│   │   ├── ContactPage.tsx  # Contact独立页面 ✨
+│   │   ├── About.tsx
+│   │   ├── Blog.tsx
+│   │   ├── Careers.tsx
+│   │   ├── PrivacyPolicy.tsx
+│   │   └── TermsOfService.tsx
+│   ├── ui/             # ShadCN UI组件
+│   ├── Header.tsx      # 导航栏
+│   ├── Footer.tsx      # 页脚
+│   ├── Hero.tsx        # 首页英雄区
+│   ├── Features.tsx    # 功能展示
+│   ├── Experts.tsx     # 专家类别
+│   ├── FAQ.tsx         # FAQ组件(旧版)
+│   └── Contact.tsx     # Contact组件(旧版)
+├── contexts/
+│   ├── LanguageContext.tsx  # 国际化
+│   └── RouterContext.tsx    # 路由系统
+├── lib/
+│   └── i18n.ts         # 语言翻译
+├── styles/
+│   └── globals.css     # 全局样式
+├── App.tsx             # 应用入口
+└── vercel.json         # Vercel配置
+```
+
+## 🌐 国际化
+
+支持以下语言:
+- 🇺🇸 English (en)
+- 🇨🇳 简体中文 (zh)
+- 🇯🇵 日本語 (ja)
+- 🇫🇷 Français (fr)
+- 🇰🇷 한국어 (ko)
+- 🇪🇸 Español (es)
+
+语言切换位于Header右上角。
+
+## 🔄 版本 2.0.0 重大更新
+
+### ✨ 主要改进
+1. **FAQ和Contact改为独立页面**
+   - 从 `/#faq` → `/faq`
+   - 从 `/#contact` → `/contact`
+   - 解决了hash锚点在公网访问时失效的问题
+
+2. **更好的SEO支持**
+   - 每个页面都有独立的URL
+   - 搜索引擎可以更好地索引
+
+3. **更清晰的URL结构**
+   - 无需复杂的hash处理
+   - URL更易于理解和分享
+
+### 📖 详细文档
+- [CHANGELOG.md](./CHANGELOG.md) - 完整更新日志
+- [MIGRATION_GUIDE.md](./MIGRATION_GUIDE.md) - 迁移指南
+- [ARCHITECTURE.md](./ARCHITECTURE.md) - 架构说明
+
+## 📞 联系方式
+
+### 邮箱
+- 技术支持:anycall_support@hotmail.com
+- 商务合作:anycall_business@hotmail.com
+
+### 地址
+Singapore, Langsat Road
+
+## 📄 许可证
+
+© 2025 NEXTHUMAN.PTE.LTD. All rights reserved.
+
+## 🤝 贡献
+
+目前这是一个内部项目,暂不接受外部贡献。
+
+## 📚 相关文档
+
+### 用户文档
+- [QUICK_FIX.md](./QUICK_FIX.md) - 快速参考指南
+- [DEPLOYMENT.md](./DEPLOYMENT.md) - 部署说明
+- [DEBUG.md](./DEBUG.md) - 问题排查
+
+### 开发文档
+- [ARCHITECTURE.md](./ARCHITECTURE.md) - 系统架构
+- [MIGRATION_GUIDE.md](./MIGRATION_GUIDE.md) - 版本迁移
+- [CHANGELOG.md](./CHANGELOG.md) - 版本历史
+
+## 🎯 路线图
+
+### 近期计划 (v2.1.0)
+- [ ] 页面过渡动画
+- [ ] 代码分割优化
+- [ ] 图片懒加载
+- [ ] 404错误页面
+
+### 中期计划 (v2.2.0)
+- [ ] 面包屑导航
+- [ ] 页面加载进度条
+- [ ] 移动端体验优化
+- [ ] PWA支持
+
+### 长期计划 (v3.0.0)
+- [ ] 用户认证
+- [ ] 个人Dashboard
+- [ ] 在线预约系统
+- [ ] 支付集成
+
+## 🐛 问题反馈
+
+如遇到问题,请发送邮件至:anycall_support@hotmail.com
+
+## 🙏 致谢
+
+- [React](https://reactjs.org/) - UI框架
+- [Tailwind CSS](https://tailwindcss.com/) - 样式系统
+- [ShadCN UI](https://ui.shadcn.com/) - 组件库
+- [Lucide](https://lucide.dev/) - 图标
+- [Unsplash](https://unsplash.com/) - 图片资源
+
+---
+
+**Built with ❤️ by NEXTHUMAN.PTE.LTD.**

+ 9 - 0
src/_redirects/Code-component-66-106.tsx

@@ -0,0 +1,9 @@
+# Netlify/Vercel redirects configuration
+# This handles SPA routing and ensures hash fragments work correctly
+
+# Redirect URLs without trailing slash and with hash to proper format
+# Note: Hash fragments are not sent to the server, so these redirects 
+# are handled client-side by the RouterContext
+
+# All routes should serve index.html for client-side routing
+/*    /index.html   200

+ 9 - 0
src/_redirects/Code-component-66-76.tsx

@@ -0,0 +1,9 @@
+# Netlify/Vercel redirects configuration
+# This handles SPA routing and ensures hash fragments work correctly
+
+# Redirect URLs without trailing slash and with hash to proper format
+# Note: Hash fragments are not sent to the server, so these redirects 
+# are handled client-side by the RouterContext
+
+# All routes should serve index.html for client-side routing
+/*    /index.html   200

+ 135 - 0
src/components/Contact.tsx

@@ -0,0 +1,135 @@
+import { Mail, MapPin, Send } from 'lucide-react';
+import { Button } from './ui/button';
+import { Input } from './ui/input';
+import { Textarea } from './ui/textarea';
+import { Card } from './ui/card';
+import { useLanguage } from '../contexts/LanguageContext';
+import { toast } from 'sonner@2.0.3';
+import { FormEvent } from 'react';
+
+export function Contact() {
+  const { t } = useLanguage();
+  
+  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
+    e.preventDefault();
+    toast.success(t.contact.includes('Contact') ? 'Message sent successfully!' : '提交成功!');
+    e.currentTarget.reset();
+  };
+  
+  return (
+    <section id="contact" className="py-12 sm:py-16 lg:py-20 bg-white">
+      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+        <div className="text-center space-y-3 sm:space-y-4 mb-10 sm:mb-12 lg:mb-16">
+          <h2 className="text-3xl sm:text-4xl lg:text-5xl text-gray-900">
+            {t.contactTitle}
+          </h2>
+          <p className="text-base sm:text-lg lg:text-xl text-gray-600 max-w-2xl mx-auto px-4">
+            {t.contactSubtitle}
+          </p>
+        </div>
+
+        <div className="grid lg:grid-cols-2 gap-8 sm:gap-10 lg:gap-12">
+          <div className="space-y-6 sm:space-y-8">
+            <div>
+              <h3 className="text-xl sm:text-2xl text-gray-900 mb-4 sm:mb-6">
+                {t.contact.includes('Contact') ? 'Get in Touch' : '与我们取得联系'}
+              </h3>
+              <p className="text-sm sm:text-base text-gray-600 mb-6 sm:mb-8">
+                {t.contact.includes('Contact') 
+                  ? 'Fill out the form or contact us through the following channels. Our team will respond within 24 hours.' 
+                  : '填写表单或通过以下方式联系我们,我们的团队会在24小时内回复您。'}
+              </p>
+            </div>
+
+            <div className="space-y-4 sm:space-y-6">
+              <Card className="p-4 sm:p-6 flex items-start gap-3 sm:gap-4 border-2 border-gray-100 hover:border-gray-300 transition-colors">
+                <div className="w-10 h-10 sm:w-12 sm:h-12 bg-gray-100 rounded-lg sm:rounded-xl flex items-center justify-center flex-shrink-0">
+                  <Mail className="w-5 h-5 sm:w-6 sm:h-6 text-gray-900" />
+                </div>
+                <div>
+                  <h4 className="text-base sm:text-lg text-gray-900 mb-1">{t.contact.includes('Contact') ? 'Email' : '邮箱'}</h4>
+                  <p className="text-sm sm:text-base text-gray-600">anycall_support@hotmail.com</p>
+                  <p className="text-sm sm:text-base text-gray-600">anycall_business@hotmail.com</p>
+                </div>
+              </Card>
+
+              <Card className="p-4 sm:p-6 flex items-start gap-3 sm:gap-4 border-2 border-gray-100 hover:border-gray-300 transition-colors">
+                <div className="w-10 h-10 sm:w-12 sm:h-12 bg-gray-100 rounded-lg sm:rounded-xl flex items-center justify-center flex-shrink-0">
+                  <MapPin className="w-5 h-5 sm:w-6 sm:h-6 text-gray-900" />
+                </div>
+                <div>
+                  <h4 className="text-base sm:text-lg text-gray-900 mb-1">{t.contact.includes('Contact') ? 'Office Address' : '办公地址'}</h4>
+                  <p className="text-sm sm:text-base text-gray-600">{t.contact.includes('Contact') ? 'Singapore' : '新加坡'}</p>
+                  <p className="text-sm sm:text-base text-gray-600">Langsat Road</p>
+                </div>
+              </Card>
+            </div>
+          </div>
+
+          <Card className="p-6 sm:p-8 border-2 border-gray-100">
+            <form className="space-y-5 sm:space-y-6" onSubmit={handleSubmit}>
+              <div className="space-y-2">
+                <label htmlFor="name" className="text-sm sm:text-base text-gray-900">
+                  {t.yourName}
+                </label>
+                <Input
+                  id="name"
+                  name="name"
+                  placeholder={t.yourName}
+                  className="border-gray-300 h-11 sm:h-12 text-base"
+                  required
+                />
+              </div>
+
+              <div className="space-y-2">
+                <label htmlFor="email" className="text-sm sm:text-base text-gray-900">
+                  {t.yourEmail}
+                </label>
+                <Input
+                  id="email"
+                  name="email"
+                  type="email"
+                  placeholder="your@email.com"
+                  className="border-gray-300 h-11 sm:h-12 text-base"
+                  required
+                />
+              </div>
+
+              <div className="space-y-2">
+                <label htmlFor="subject" className="text-sm sm:text-base text-gray-900">
+                  {t.contact.includes('Contact') ? 'Subject' : '主题'}
+                </label>
+                <Input
+                  id="subject"
+                  name="subject"
+                  placeholder={t.contact.includes('Contact') ? 'How can we help?' : '您想咨询什么?'}
+                  className="border-gray-300 h-11 sm:h-12 text-base"
+                  required
+                />
+              </div>
+
+              <div className="space-y-2">
+                <label htmlFor="message" className="text-sm sm:text-base text-gray-900">
+                  {t.yourMessage}
+                </label>
+                <Textarea
+                  id="message"
+                  name="message"
+                  placeholder={t.yourMessage}
+                  rows={5}
+                  className="border-gray-300 text-base"
+                  required
+                />
+              </div>
+
+              <Button type="submit" className="w-full bg-gray-900 hover:bg-black text-white py-5 sm:py-6 rounded-xl text-base sm:text-lg">
+                <Send className="w-5 h-5 mr-2" />
+                {t.sendMessage}
+              </Button>
+            </form>
+          </Card>
+        </div>
+      </div>
+    </section>
+  );
+}

+ 144 - 0
src/components/Experts.tsx

@@ -0,0 +1,144 @@
+import { Card } from './ui/card';
+import { Badge } from './ui/badge';
+import { Star, Users, Clock } from 'lucide-react';
+import { ImageWithFallback } from './figma/ImageWithFallback';
+import { useLanguage } from '../contexts/LanguageContext';
+
+export function Experts() {
+  const { t } = useLanguage();
+  
+  const experts = [
+    {
+      nameKey: 'psychologist',
+      descKey: 'psychologistDesc',
+      image: 'https://images.unsplash.com/photo-1758887248912-03a0c34a2f41?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxwc3ljaG9sb2dpc3QlMjB0aGVyYXBpc3QlMjBwcm9mZXNzaW9uYWx8ZW58MXx8fHwxNzU5MjMyNTUwfDA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral',
+      rating: '4.9',
+      consultations: '10k+',
+      specialty: ['Anxiety', 'Stress', 'Relationships']
+    },
+    {
+      nameKey: 'lawyer',
+      descKey: 'lawyerDesc',
+      image: 'https://images.unsplash.com/photo-1658249682512-1bb162538ba9?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxsYXd5ZXIlMjBhdHRvcm5leSUyMHByb2Zlc3Npb25hbHxlbnwxfHx8fDE3NTkyMzI1NTF8MA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral',
+      rating: '4.8',
+      consultations: '8k+',
+      specialty: ['Business', 'Labor', 'IP']
+    },
+    {
+      nameKey: 'businessConsultant',
+      descKey: 'businessConsultantDesc',
+      image: 'https://images.unsplash.com/photo-1738750908048-14200459c3c9?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxidXNpbmVzcyUyMGNvbnN1bHRhbnQlMjBleGVjdXRpdmV8ZW58MXx8fHwxNzU5MjMyNTUxfDA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral',
+      rating: '4.9',
+      consultations: '12k+',
+      specialty: ['Market', 'Strategy', 'Trade']
+    },
+    {
+      nameKey: 'fitnessCoach',
+      descKey: 'fitnessCoachDesc',
+      image: 'https://images.unsplash.com/photo-1540206063137-4a88ca974d1a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxmaXRuZXNzJTIwY29hY2glMjB0cmFpbmVyfGVufDF8fHx8MTc1OTIwMDcxNHww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral',
+      rating: '4.9',
+      consultations: '15k+',
+      specialty: ['Rehab', 'Fitness', 'Prevention']
+    },
+    {
+      nameKey: 'educator',
+      descKey: 'educatorDesc',
+      image: 'https://images.unsplash.com/photo-1752649935124-f5a7ac531a97?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHx0ZWFjaGVyJTIwZWR1Y2F0b3IlMjBwcm9mZXNzaW9uYWx8ZW58MXx8fHwxNzU5MTM1MDg0fDA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral',
+      rating: '4.8',
+      consultations: '9k+',
+      specialty: ['Planning', 'Methods', 'Career']
+    },
+    {
+      nameKey: 'financialAdvisor',
+      descKey: 'financialAdvisorDesc',
+      image: 'https://images.unsplash.com/photo-1556155092-8707de31f9c4?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxmaW5hbmNpYWwlMjBhZHZpc29yJTIwcHJvZmVzc2lvbmFsfGVufDF8fHx8MTc1OTIzMjU1Mnww&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral',
+      rating: '4.9',
+      consultations: '11k+',
+      specialty: ['Investment', 'Retirement', 'Asset']
+    },
+    {
+      nameKey: 'careerMentor',
+      descKey: 'careerMentorDesc',
+      image: 'https://images.unsplash.com/photo-1750698545009-679820502908?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxjYXJlZXIlMjBtZW50b3IlMjBjb2FjaHxlbnwxfHx8fDE3NTkyMzI1NTJ8MA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral',
+      rating: '4.8',
+      consultations: '13k+',
+      specialty: ['Career', 'Resume', 'Interview']
+    },
+    {
+      nameKey: 'nutritionist',
+      descKey: 'nutritionistDesc',
+      image: 'https://images.unsplash.com/photo-1675270882554-ab6817fb44f3?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxudXRyaXRpb25pc3QlMjBkaWV0aXRpYW4lMjBwcm9mZXNzaW9uYWx8ZW58MXx8fHwxNzU5MjMyNTUyfDA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral',
+      rating: '4.9',
+      consultations: '14k+',
+      specialty: ['Nutrition', 'Weight', 'Health']
+    }
+  ];
+
+  return (
+    <section id="experts" className="py-12 sm:py-16 lg:py-20 bg-gray-50">
+      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+        <div className="text-center space-y-3 sm:space-y-4 mb-10 sm:mb-12 lg:mb-16">
+          <h2 className="text-3xl sm:text-4xl lg:text-5xl text-gray-900">
+            {t.expertsTitle}
+          </h2>
+          <p className="text-base sm:text-lg lg:text-xl text-gray-600 max-w-2xl mx-auto px-4">
+            {t.expertsSubtitle}
+          </p>
+        </div>
+
+        <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 sm:gap-6">
+          {experts.map((expert, index) => (
+            <Card key={index} className="overflow-hidden hover:shadow-xl transition-shadow border-2 border-gray-100 group">
+              <div className="relative h-64 overflow-hidden bg-gray-100">
+                <ImageWithFallback
+                  src={expert.image}
+                  alt={t[expert.nameKey as keyof typeof t] as string}
+                  className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
+                />
+                <div className="absolute top-4 right-4 bg-white/95 backdrop-blur px-3 py-1 rounded-full flex items-center gap-1">
+                  <Star className="w-4 h-4 text-yellow-500 fill-yellow-500" />
+                  <span className="text-sm text-gray-900">{expert.rating}</span>
+                </div>
+              </div>
+              
+              <div className="p-4 sm:p-6 space-y-3 sm:space-y-4">
+                <div>
+                  <h3 className="text-lg sm:text-xl text-gray-900 mb-2">{t[expert.nameKey as keyof typeof t] as string}</h3>
+                  <p className="text-sm text-gray-600 line-clamp-2">{t[expert.descKey as keyof typeof t] as string}</p>
+                </div>
+
+                <div className="flex items-center gap-3 sm:gap-4 text-xs sm:text-sm text-gray-500">
+                  <div className="flex items-center gap-1">
+                    <Users className="w-4 h-4 flex-shrink-0" />
+                    <span>{expert.consultations}</span>
+                  </div>
+                  <div className="flex items-center gap-1">
+                    <Clock className="w-4 h-4 flex-shrink-0" />
+                    <span>24/7</span>
+                  </div>
+                </div>
+
+                <div className="flex flex-wrap gap-1.5 sm:gap-2">
+                  {expert.specialty.map((spec, idx) => (
+                    <Badge key={idx} variant="secondary" className="text-xs">
+                      {spec}
+                    </Badge>
+                  ))}
+                </div>
+              </div>
+            </Card>
+          ))}
+        </div>
+
+        <div className="mt-8 sm:mt-10 lg:mt-12 text-center px-4">
+          <p className="text-sm sm:text-base text-gray-600 mb-4 sm:mb-6">
+            {t.moreExperts}
+          </p>
+          <button className="w-full sm:w-auto px-6 sm:px-8 py-3 sm:py-4 bg-gray-900 hover:bg-black text-white rounded-xl transition-colors">
+            {t.viewAllExperts}
+          </button>
+        </div>
+      </div>
+    </section>
+  );
+}

+ 54 - 0
src/components/FAQ.tsx

@@ -0,0 +1,54 @@
+import {
+  Accordion,
+  AccordionContent,
+  AccordionItem,
+  AccordionTrigger,
+} from './ui/accordion';
+import { useLanguage } from '../contexts/LanguageContext';
+
+export function FAQ() {
+  const { t } = useLanguage();
+  
+  const faqs = [
+    { questionKey: 'faq1Q', answerKey: 'faq1A' },
+    { questionKey: 'faq2Q', answerKey: 'faq2A' },
+    { questionKey: 'faq3Q', answerKey: 'faq3A' },
+    { questionKey: 'faq4Q', answerKey: 'faq4A' },
+    { questionKey: 'faq5Q', answerKey: 'faq5A' },
+    { questionKey: 'faq6Q', answerKey: 'faq6A' },
+    { questionKey: 'faq7Q', answerKey: 'faq7A' },
+    { questionKey: 'faq8Q', answerKey: 'faq8A' },
+  ];
+
+  return (
+    <section id="faq" className="py-12 sm:py-16 lg:py-20 bg-gray-50">
+      <div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
+        <div className="text-center space-y-3 sm:space-y-4 mb-8 sm:mb-10 lg:mb-12">
+          <h2 className="text-3xl sm:text-4xl lg:text-5xl text-gray-900">
+            {t.faqTitle}
+          </h2>
+          <p className="text-base sm:text-lg lg:text-xl text-gray-600 px-4">
+            {t.faqSubtitle}
+          </p>
+        </div>
+
+        <Accordion type="single" collapsible className="space-y-3 sm:space-y-4">
+          {faqs.map((faq, index) => (
+            <AccordionItem
+              key={index}
+              value={`item-${index}`}
+              className="bg-white rounded-lg sm:rounded-xl px-4 sm:px-6 border-2 border-gray-100"
+            >
+              <AccordionTrigger className="text-base sm:text-lg text-gray-900 hover:text-gray-600 text-left py-4 sm:py-5">
+                {t[faq.questionKey as keyof typeof t] as string}
+              </AccordionTrigger>
+              <AccordionContent className="text-sm sm:text-base text-gray-600 pb-4 sm:pb-5">
+                {t[faq.answerKey as keyof typeof t] as string}
+              </AccordionContent>
+            </AccordionItem>
+          ))}
+        </Accordion>
+      </div>
+    </section>
+  );
+}

+ 105 - 0
src/components/Features.tsx

@@ -0,0 +1,105 @@
+import { Globe, Zap, Shield, Users, Clock, Sparkles } from 'lucide-react';
+import { Card } from './ui/card';
+import { ImageWithFallback } from './figma/ImageWithFallback';
+import { useLanguage } from '../contexts/LanguageContext';
+
+export function Features() {
+  const { t } = useLanguage();
+  
+  const features = [
+    {
+      icon: Globe,
+      titleKey: 'globalExperts',
+      descKey: 'globalExpertsDesc',
+    },
+    {
+      icon: Zap,
+      titleKey: 'instantConsult',
+      descKey: 'instantConsultDesc',
+    },
+    {
+      icon: Shield,
+      titleKey: 'privacy',
+      descKey: 'privacyDesc',
+    },
+    {
+      icon: Users,
+      titleKey: 'professional',
+      descKey: 'professionalDesc',
+    },
+    {
+      icon: Clock,
+      titleKey: 'available247',
+      descKey: 'available247Desc',
+    },
+    {
+      icon: Sparkles,
+      titleKey: 'smartLearning',
+      descKey: 'smartLearningDesc',
+    },
+  ];
+
+  return (
+    <section id="features" className="py-12 sm:py-16 lg:py-20 bg-white">
+      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+        <div className="text-center space-y-3 sm:space-y-4 mb-10 sm:mb-12 lg:mb-16">
+          <h2 className="text-3xl sm:text-4xl lg:text-5xl text-gray-900">
+            {t.featuresTitle}
+          </h2>
+          <p className="text-base sm:text-lg lg:text-xl text-gray-600 max-w-2xl mx-auto px-4">
+            {t.featuresSubtitle}
+          </p>
+        </div>
+        
+        <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6 lg:gap-8 mb-12 sm:mb-16">
+          {features.map((feature, index) => {
+            const Icon = feature.icon;
+            return (
+              <Card key={index} className="p-5 sm:p-6 hover:shadow-lg transition-shadow border-2 border-gray-100">
+                <div className="w-12 h-12 bg-gray-100 rounded-xl flex items-center justify-center mb-3 sm:mb-4">
+                  <Icon className="w-6 h-6 text-gray-900" />
+                </div>
+                <h3 className="text-lg sm:text-xl text-gray-900 mb-2">{t[feature.titleKey as keyof typeof t] as string}</h3>
+                <p className="text-sm sm:text-base text-gray-600">{t[feature.descKey as keyof typeof t] as string}</p>
+              </Card>
+            );
+          })}
+        </div>
+
+        <div className="grid lg:grid-cols-2 gap-8 sm:gap-10 lg:gap-12 items-center mt-12 sm:mt-16 lg:mt-20">
+          <div className="rounded-xl sm:rounded-2xl overflow-hidden shadow-xl order-2 lg:order-1">
+            <ImageWithFallback
+              src="https://images.unsplash.com/photo-1697577418970-95d99b5a55cf?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxhcnRpZmljaWFsJTIwaW50ZWxsaWdlbmNlJTIwdGVjaG5vbG9neXxlbnwxfHx8fDE3NTkxNjQyODZ8MA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral"
+              alt="AI Technology"
+              className="w-full h-auto"
+            />
+          </div>
+          <div className="space-y-4 sm:space-y-6 order-1 lg:order-2">
+            <h3 className="text-2xl sm:text-3xl lg:text-4xl text-gray-900">
+              {t.aiConsultTitle}
+            </h3>
+            <p className="text-base sm:text-lg text-gray-600">
+              {t.aiConsultDesc}
+            </p>
+            <div className="flex items-start gap-3">
+              <div className="w-6 h-6 bg-gray-900 rounded-full flex items-center justify-center flex-shrink-0 mt-0.5 sm:mt-1">
+                <svg className="w-4 h-4 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
+                </svg>
+              </div>
+              <p className="text-sm sm:text-base text-gray-700">{t.aiConsultFeature1}</p>
+            </div>
+            <div className="flex items-start gap-3">
+              <div className="w-6 h-6 bg-gray-900 rounded-full flex items-center justify-center flex-shrink-0 mt-0.5 sm:mt-1">
+                <svg className="w-4 h-4 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
+                </svg>
+              </div>
+              <p className="text-sm sm:text-base text-gray-700">{t.aiConsultFeature2}</p>
+            </div>
+          </div>
+        </div>
+      </div>
+    </section>
+  );
+}

+ 83 - 0
src/components/Footer.tsx

@@ -0,0 +1,83 @@
+import { Phone, Twitter, Github, Linkedin, Mail } from 'lucide-react';
+import { useLanguage } from '../contexts/LanguageContext';
+import { useRouter } from '../contexts/RouterContext';
+
+export function Footer() {
+  const { t } = useLanguage();
+  const { navigateTo } = useRouter();
+  
+  return (
+    <footer className="bg-gray-900 text-gray-300">
+      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-10 sm:py-12">
+        <div className="flex flex-col space-y-8 mb-8">
+          {/* Logo and Description */}
+          <div className="space-y-3 sm:space-y-4">
+            <div className="flex items-center gap-2">
+              <div className="w-9 h-9 sm:w-10 sm:h-10 bg-white rounded-lg sm:rounded-xl flex items-center justify-center">
+                <Phone className="w-5 h-5 sm:w-6 sm:h-6 text-gray-900" />
+              </div>
+              <span className="text-xl sm:text-2xl text-white">Anycall</span>
+            </div>
+            <p className="text-sm sm:text-base max-w-md">
+              {t.footerDesc}
+            </p>
+          </div>
+
+          {/* Legal Section */}
+          <div>
+            <h4 className="text-white mb-3 sm:mb-4 text-base sm:text-lg">{t.legal}</h4>
+            <ul className="space-y-2 text-sm sm:text-base">
+              <li>
+                <button onClick={() => navigateTo('privacy')} className="hover:text-white transition-colors">
+                  {t.privacyPolicy}
+                </button>
+              </li>
+              <li>
+                <button onClick={() => navigateTo('terms')} className="hover:text-white transition-colors">
+                  {t.termsOfService}
+                </button>
+              </li>
+            </ul>
+          </div>
+        </div>
+
+        <div className="border-t border-gray-800 pt-6 sm:pt-8 flex flex-col sm:flex-row justify-between items-center gap-4 sm:gap-6">
+          <p className="text-xs sm:text-sm text-center sm:text-left">
+            {t.copyright}
+          </p>
+          
+          <div className="flex items-center gap-3 sm:gap-4">
+            <a
+              href="#"
+              className="w-10 h-10 sm:w-11 sm:h-11 bg-gray-800 hover:bg-white hover:text-gray-900 rounded-lg flex items-center justify-center transition-colors"
+              aria-label="Twitter"
+            >
+              <Twitter className="w-5 h-5" />
+            </a>
+            <a
+              href="#"
+              className="w-10 h-10 sm:w-11 sm:h-11 bg-gray-800 hover:bg-white hover:text-gray-900 rounded-lg flex items-center justify-center transition-colors"
+              aria-label="Github"
+            >
+              <Github className="w-5 h-5" />
+            </a>
+            <a
+              href="#"
+              className="w-10 h-10 sm:w-11 sm:h-11 bg-gray-800 hover:bg-white hover:text-gray-900 rounded-lg flex items-center justify-center transition-colors"
+              aria-label="LinkedIn"
+            >
+              <Linkedin className="w-5 h-5" />
+            </a>
+            <a
+              href="#"
+              className="w-10 h-10 sm:w-11 sm:h-11 bg-gray-800 hover:bg-white hover:text-gray-900 rounded-lg flex items-center justify-center transition-colors"
+              aria-label="Email"
+            >
+              <Mail className="w-5 h-5" />
+            </a>
+          </div>
+        </div>
+      </div>
+    </footer>
+  );
+}

+ 180 - 0
src/components/Header.tsx

@@ -0,0 +1,180 @@
+import { Phone, Menu, Globe } from 'lucide-react';
+import { useState } from 'react';
+import { useLanguage } from '../contexts/LanguageContext';
+import { useRouter } from '../contexts/RouterContext';
+import { Language, languages } from '../lib/i18n';
+import {
+  DropdownMenu,
+  DropdownMenuContent,
+  DropdownMenuItem,
+  DropdownMenuTrigger,
+} from './ui/dropdown-menu';
+
+export function Header() {
+  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
+  const { language, setLanguage, t } = useLanguage();
+  const { currentPage, navigateTo } = useRouter();
+
+  const scrollToSection = (id: string) => {
+    // Close mobile menu first for better UX
+    setMobileMenuOpen(false);
+    console.log(id,3333)
+    if (currentPage !== 'home') {
+      navigateTo('home', id);
+    } else {
+      // Small delay to ensure mobile menu is closed
+      setTimeout(() => {
+        const element = document.getElementById(id);
+        if (element) {
+          element.scrollIntoView({ behavior: 'smooth', block: 'start' });
+          window.history.pushState(null, '', `#${id}`);
+        }
+      }, 50);
+    }
+  };
+
+  const handleNavigation = (page: 'faq' | 'contact') => {
+    setMobileMenuOpen(false);
+    console.log(page)
+    navigateTo(page);
+  };
+
+  const scrollToTop = () => {
+    setMobileMenuOpen(false);
+    
+    if (currentPage !== 'home') {
+      navigateTo('home');
+    } else {
+      setTimeout(() => {
+        window.scrollTo({ top: 0, behavior: 'smooth' });
+        window.history.pushState(null, '', '');
+      }, 50);
+    }
+  };
+
+  return (
+    <header className="sticky top-0 z-50 bg-white/95 backdrop-blur-sm border-b border-gray-200">
+      <nav className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+        <div className="flex items-center justify-between h-14 sm:h-16">
+          <button onClick={() => navigateTo('home')} className="flex items-center gap-2">
+            <div className="w-9 h-9 sm:w-10 sm:h-10 bg-gray-900 rounded-lg sm:rounded-xl flex items-center justify-center">
+              <Phone className="w-5 h-5 sm:w-6 sm:h-6 text-white" />
+            </div>
+            <span className="text-xl sm:text-2xl text-gray-900">Anycall</span>
+          </button>
+
+          {/* Desktop Navigation */}
+          <div className="hidden md:flex items-center gap-8">
+            <button
+              onClick={scrollToTop}
+              className="text-gray-600 hover:text-gray-900 transition-colors"
+            >
+              {t.home}
+            </button>
+            <button
+              onClick={() => scrollToSection('features')}
+              className="text-gray-600 hover:text-gray-900 transition-colors"
+            >
+              {t.features}
+            </button>
+            <button
+              onClick={() => scrollToSection('faq')}
+              className="text-gray-600 hover:text-gray-900 transition-colors"
+            >
+              {t.faq}
+            </button>
+            <button
+              onClick={() => scrollToSection('contact')}
+              className="text-gray-600 hover:text-gray-900 transition-colors"
+            >
+              {t.contact}
+            </button>
+            
+            {/* Language Switcher */}
+            <DropdownMenu>
+              <DropdownMenuTrigger asChild>
+                <button className="flex items-center gap-2 text-gray-600 hover:text-gray-900 transition-colors">
+                  <Globe className="w-4 h-4" />
+                  <span>{languages[language]}</span>
+                </button>
+              </DropdownMenuTrigger>
+              <DropdownMenuContent align="end">
+                {Object.entries(languages).map(([code, name]) => (
+                  <DropdownMenuItem
+                    key={code}
+                    onClick={() => setLanguage(code as Language)}
+                    className={language === code ? 'bg-gray-100 text-gray-900' : ''}
+                  >
+                    {name}
+                  </DropdownMenuItem>
+                ))}
+              </DropdownMenuContent>
+            </DropdownMenu>
+          </div>
+
+          {/* Mobile menu button */}
+          <button
+            className="md:hidden p-2 hover:bg-gray-100 rounded-lg transition-colors"
+            onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
+            aria-label="Toggle menu"
+          >
+            <Menu className="w-6 h-6 text-gray-900" />
+          </button>
+        </div>
+
+        {/* Mobile Navigation */}
+        {mobileMenuOpen && (
+          <div className="md:hidden py-4 space-y-2 border-t border-gray-200">
+            <button
+              onClick={scrollToTop}
+              className="block w-full text-left px-4 py-3 text-base text-gray-600 hover:text-gray-900 hover:bg-gray-50 rounded-lg transition-colors"
+            >
+              {t.home}
+            </button>
+            <button
+              onClick={() => scrollToSection('features')}
+              className="block w-full text-left px-4 py-3 text-base text-gray-600 hover:text-gray-900 hover:bg-gray-50 rounded-lg transition-colors"
+            >
+              {t.features}
+            </button>
+            <button
+              onClick={() => handleNavigation('faq')}
+              className="block w-full text-left px-4 py-3 text-base text-gray-600 hover:text-gray-900 hover:bg-gray-50 rounded-lg transition-colors"
+            >
+              {t.faq}
+            </button>
+            <button
+              onClick={() => handleNavigation('contact')}
+              className="block w-full text-left px-4 py-3 text-base text-gray-600 hover:text-gray-900 hover:bg-gray-50 rounded-lg transition-colors"
+            >
+              {t.contact}
+            </button>
+            
+            {/* Mobile Language Switcher */}
+            <div className="px-4 pt-4 pb-2 mt-2 border-t border-gray-200">
+              <p className="text-sm text-gray-500 mb-3 flex items-center gap-2">
+                <Globe className="w-4 h-4" />
+                {t.contact.includes('Contact') ? 'Language' : '语言'}
+              </p>
+              <div className="grid grid-cols-2 gap-2">
+                {Object.entries(languages).map(([code, name]) => (
+                  <button
+                    key={code}
+                    onClick={() => setLanguage(code as Language)}
+                    className={`px-3 py-2.5 rounded-lg text-sm transition-colors ${
+                      language === code
+                        ? 'bg-gray-900 text-white'
+                        : 'bg-gray-100 text-gray-600 hover:bg-gray-200'
+                    }`}
+                  >
+                    {name}
+                  </button>
+                ))}
+              </div>
+            </div>
+          </div>
+        )}
+      </nav>
+    </header>
+  );
+}

+ 60 - 0
src/components/Hero.tsx

@@ -0,0 +1,60 @@
+import { Phone } from 'lucide-react';
+import { Button } from './ui/button';
+import { ImageWithFallback } from './figma/ImageWithFallback';
+import { useLanguage } from '../contexts/LanguageContext';
+
+// Apple Logo SVG
+const AppleLogo = () => (
+  <svg className="w-5 h-5" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+    <path d="M17.05 20.28c-.98.95-2.05.8-3.08.35-1.09-.46-2.09-.48-3.24 0-1.44.62-2.2.44-3.06-.35C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09l.01-.01zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z"/>
+  </svg>
+);
+
+export function Hero() {
+  const { t } = useLanguage();
+  
+  return (
+    <section className="relative overflow-hidden bg-gradient-to-br from-gray-50 via-white to-gray-50">
+      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12 sm:py-20 lg:py-32">
+        <div className="grid lg:grid-cols-2 gap-8 lg:gap-12 items-center">
+          <div className="space-y-6 sm:space-y-8 text-center lg:text-left">
+            <div className="inline-flex items-center gap-2 px-4 py-2 bg-gray-100 rounded-full">
+              <Phone className="w-4 h-4 text-gray-900" />
+              <span className="text-sm text-gray-700">{t.heroTag}</span>
+            </div>
+            
+            <h1 className="text-3xl sm:text-4xl md:text-5xl lg:text-6xl xl:text-7xl text-gray-900 leading-tight">
+              {t.heroTitle1}
+              <span className="block text-gray-900 mt-2">{t.heroTitle2}</span>
+            </h1>
+            
+            <p className="text-base sm:text-lg lg:text-xl text-gray-600 max-w-xl mx-auto lg:mx-0">
+              {t.heroDescription}
+            </p>
+            
+            <div className="flex flex-col sm:flex-row gap-3 sm:gap-4 justify-center lg:justify-start">
+              <a
+                href="#"
+                className="inline-flex items-center justify-center gap-3 bg-gray-900 hover:bg-black text-white px-8 py-4 rounded-xl transition-colors w-full sm:w-[200px]"
+              >
+                <AppleLogo />
+                <span>{t.downloadIOS}</span>
+              </a>
+            </div>
+          </div>
+          
+          <div className="relative mt-8 lg:mt-0">
+            <div className="absolute inset-0 bg-gray-900/10 rounded-3xl blur-3xl"></div>
+            <div className="relative rounded-2xl sm:rounded-3xl overflow-hidden shadow-2xl">
+              <ImageWithFallback
+                src="https://images.unsplash.com/photo-1659353219596-80cd21857b52?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxwcm9mZXNzaW9uYWwlMjB2aWRlbyUyMGNhbGwlMjBjb25zdWx0YXRpb258ZW58MXx8fHwxNzU5MjMyNTUwfDA&ixlib=rb-4.1.0&q=80&w=1080&utm_source=figma&utm_medium=referral"
+                alt="Professional video consultation"
+                className="w-full h-auto"
+              />
+            </div>
+          </div>
+        </div>
+      </div>
+    </section>
+  );
+}

+ 27 - 0
src/components/figma/ImageWithFallback.tsx

@@ -0,0 +1,27 @@
+import React, { useState } from 'react'
+
+const ERROR_IMG_SRC =
+  'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODgiIGhlaWdodD0iODgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBvcGFjaXR5PSIuMyIgZmlsbD0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIzLjciPjxyZWN0IHg9IjE2IiB5PSIxNiIgd2lkdGg9IjU2IiBoZWlnaHQ9IjU2IiByeD0iNiIvPjxwYXRoIGQ9Im0xNiA1OCAxNi0xOCAzMiAzMiIvPjxjaXJjbGUgY3g9IjUzIiBjeT0iMzUiIHI9IjciLz48L3N2Zz4KCg=='
+
+export function ImageWithFallback(props: React.ImgHTMLAttributes<HTMLImageElement>) {
+  const [didError, setDidError] = useState(false)
+
+  const handleError = () => {
+    setDidError(true)
+  }
+
+  const { src, alt, style, className, ...rest } = props
+
+  return didError ? (
+    <div
+      className={`inline-block bg-gray-100 text-center align-middle ${className ?? ''}`}
+      style={style}
+    >
+      <div className="flex items-center justify-center w-full h-full">
+        <img src={ERROR_IMG_SRC} alt="Error loading image" {...rest} data-original-url={src} />
+      </div>
+    </div>
+  ) : (
+    <img src={src} alt={alt} className={className} style={style} {...rest} onError={handleError} />
+  )
+}

+ 105 - 0
src/components/pages/About.tsx

@@ -0,0 +1,105 @@
+import { Phone, Users, Globe, Target, Heart, Zap } from 'lucide-react';
+import { useLanguage } from '../../contexts/LanguageContext';
+
+export function About() {
+  const { t } = useLanguage();
+  
+  return (
+    <div className="min-h-screen bg-white">
+      {/* Hero Section */}
+      <section className="py-16 sm:py-20 lg:py-24 bg-gradient-to-b from-gray-50 to-white">
+        <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+          <div className="text-center space-y-4 sm:space-y-6">
+            <h1 className="text-4xl sm:text-5xl lg:text-6xl text-gray-900">
+              {t.aboutTitle}
+            </h1>
+            <p className="text-lg sm:text-xl text-gray-600 max-w-3xl mx-auto">
+              {t.aboutSubtitle}
+            </p>
+          </div>
+        </div>
+      </section>
+
+      {/* Mission Section */}
+      <section className="py-12 sm:py-16 lg:py-20">
+        <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+          <div className="grid lg:grid-cols-2 gap-10 lg:gap-16 items-center">
+            <div className="space-y-6">
+              <h2 className="text-3xl sm:text-4xl text-gray-900">{t.ourMission}</h2>
+              <p className="text-base sm:text-lg text-gray-600 leading-relaxed">
+                {t.missionText1}
+              </p>
+              <p className="text-base sm:text-lg text-gray-600 leading-relaxed">
+                {t.missionText2}
+              </p>
+            </div>
+            <div className="grid grid-cols-2 gap-6">
+              <div className="bg-gray-50 p-6 rounded-2xl">
+                <Users className="w-10 h-10 text-gray-900 mb-4" />
+                <h3 className="text-2xl text-gray-900 mb-2">10K+</h3>
+                <p className="text-sm text-gray-600">{t.activeUsers}</p>
+              </div>
+              <div className="bg-gray-50 p-6 rounded-2xl">
+                <Globe className="w-10 h-10 text-gray-900 mb-4" />
+                <h3 className="text-2xl text-gray-900 mb-2">50+</h3>
+                <p className="text-sm text-gray-600">{t.countries}</p>
+              </div>
+              <div className="bg-gray-50 p-6 rounded-2xl">
+                <Target className="w-10 h-10 text-gray-900 mb-4" />
+                <h3 className="text-2xl text-gray-900 mb-2">100+</h3>
+                <p className="text-sm text-gray-600">{t.experts}</p>
+              </div>
+              <div className="bg-gray-50 p-6 rounded-2xl">
+                <Heart className="w-10 h-10 text-gray-900 mb-4" />
+                <h3 className="text-2xl text-gray-900 mb-2">98%</h3>
+                <p className="text-sm text-gray-600">{t.satisfaction}</p>
+              </div>
+            </div>
+          </div>
+        </div>
+      </section>
+
+      {/* Values Section */}
+      <section className="py-12 sm:py-16 lg:py-20 bg-gray-50">
+        <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+          <div className="text-center mb-12 lg:mb-16">
+            <h2 className="text-3xl sm:text-4xl text-gray-900 mb-4">{t.ourValues}</h2>
+            <p className="text-lg text-gray-600 max-w-2xl mx-auto">
+              {t.valuesSubtitle}
+            </p>
+          </div>
+          
+          <div className="grid md:grid-cols-3 gap-8">
+            <div className="bg-white p-8 rounded-2xl">
+              <Zap className="w-12 h-12 text-gray-900 mb-4" />
+              <h3 className="text-xl text-gray-900 mb-3">{t.innovation}</h3>
+              <p className="text-gray-600">{t.innovationText}</p>
+            </div>
+            <div className="bg-white p-8 rounded-2xl">
+              <Users className="w-12 h-12 text-gray-900 mb-4" />
+              <h3 className="text-xl text-gray-900 mb-3">{t.accessibility}</h3>
+              <p className="text-gray-600">{t.accessibilityText}</p>
+            </div>
+            <div className="bg-white p-8 rounded-2xl">
+              <Heart className="w-12 h-12 text-gray-900 mb-4" />
+              <h3 className="text-xl text-gray-900 mb-3">{t.quality}</h3>
+              <p className="text-gray-600">{t.qualityText}</p>
+            </div>
+          </div>
+        </div>
+      </section>
+
+      {/* Team Section */}
+      <section className="py-12 sm:py-16 lg:py-20">
+        <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+          <div className="text-center mb-12">
+            <h2 className="text-3xl sm:text-4xl text-gray-900 mb-4">{t.ourTeam}</h2>
+            <p className="text-lg text-gray-600 max-w-2xl mx-auto">
+              {t.teamSubtitle}
+            </p>
+          </div>
+        </div>
+      </section>
+    </div>
+  );
+}

+ 118 - 0
src/components/pages/Blog.tsx

@@ -0,0 +1,118 @@
+import { Calendar, User, ArrowRight } from 'lucide-react';
+import { useLanguage } from '../../contexts/LanguageContext';
+import { Card } from '../ui/card';
+import { Button } from '../ui/button';
+
+export function Blog() {
+  const { t } = useLanguage();
+  
+  const blogPosts = [
+    {
+      id: 1,
+      titleKey: 'blog1Title',
+      excerptKey: 'blog1Excerpt',
+      date: '2025-01-15',
+      author: 'Sarah Chen',
+      category: t.aiTech || 'AI Technology',
+    },
+    {
+      id: 2,
+      titleKey: 'blog2Title',
+      excerptKey: 'blog2Excerpt',
+      date: '2025-01-10',
+      author: 'Michael Rodriguez',
+      category: t.mentalHealth || 'Mental Health',
+    },
+    {
+      id: 3,
+      titleKey: 'blog3Title',
+      excerptKey: 'blog3Excerpt',
+      date: '2025-01-05',
+      author: 'Emily Watson',
+      category: t.business || 'Business',
+    },
+    {
+      id: 4,
+      titleKey: 'blog4Title',
+      excerptKey: 'blog4Excerpt',
+      date: '2024-12-28',
+      author: 'David Kim',
+      category: t.userStory || 'User Stories',
+    },
+    {
+      id: 5,
+      titleKey: 'blog5Title',
+      excerptKey: 'blog5Excerpt',
+      date: '2024-12-20',
+      author: 'Lisa Anderson',
+      category: t.healthcare || 'Healthcare',
+    },
+    {
+      id: 6,
+      titleKey: 'blog6Title',
+      excerptKey: 'blog6Excerpt',
+      date: '2024-12-15',
+      author: 'James Park',
+      category: t.aiTech || 'AI Technology',
+    },
+  ];
+  
+  return (
+    <div className="min-h-screen bg-white">
+      {/* Hero Section */}
+      <section className="py-16 sm:py-20 lg:py-24 bg-gradient-to-b from-gray-50 to-white">
+        <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+          <div className="text-center space-y-4 sm:space-y-6">
+            <h1 className="text-4xl sm:text-5xl lg:text-6xl text-gray-900">
+              {t.blogTitle}
+            </h1>
+            <p className="text-lg sm:text-xl text-gray-600 max-w-3xl mx-auto">
+              {t.blogSubtitle}
+            </p>
+          </div>
+        </div>
+      </section>
+
+      {/* Blog Posts Grid */}
+      <section className="py-12 sm:py-16 lg:py-20">
+        <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+          <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
+            {blogPosts.map((post) => (
+              <Card key={post.id} className="overflow-hidden hover:shadow-lg transition-shadow">
+                <div className="h-48 bg-gradient-to-br from-gray-100 to-gray-200"></div>
+                <div className="p-6 space-y-4">
+                  <div className="flex items-center gap-4 text-sm text-gray-600">
+                    <span className="px-3 py-1 bg-gray-100 rounded-full">{post.category}</span>
+                    <div className="flex items-center gap-1">
+                      <Calendar className="w-4 h-4" />
+                      <span>{post.date}</span>
+                    </div>
+                  </div>
+                  
+                  <h3 className="text-xl text-gray-900">
+                    {t[post.titleKey as keyof typeof t] || post.titleKey}
+                  </h3>
+                  
+                  <p className="text-gray-600 line-clamp-3">
+                    {t[post.excerptKey as keyof typeof t] || post.excerptKey}
+                  </p>
+                  
+                  <div className="flex items-center justify-between pt-4 border-t border-gray-100">
+                    <div className="flex items-center gap-2 text-sm text-gray-600">
+                      <User className="w-4 h-4" />
+                      <span>{post.author}</span>
+                    </div>
+                    <Button variant="ghost" size="sm" className="gap-2">
+                      {t.readMore || 'Read More'}
+                      <ArrowRight className="w-4 h-4" />
+                    </Button>
+                  </div>
+                </div>
+              </Card>
+            ))}
+          </div>
+        </div>
+      </section>
+    </div>
+  );
+}

+ 160 - 0
src/components/pages/Careers.tsx

@@ -0,0 +1,160 @@
+import { MapPin, Clock, Briefcase, ArrowRight } from 'lucide-react';
+import { useLanguage } from '../../contexts/LanguageContext';
+import { Card } from '../ui/card';
+import { Button } from '../ui/button';
+
+export function Careers() {
+  const { t } = useLanguage();
+  
+  const jobOpenings = [
+    {
+      id: 1,
+      titleKey: 'job1Title',
+      department: t.engineering || 'Engineering',
+      location: 'Singapore',
+      type: t.fullTime || 'Full-time',
+      descKey: 'job1Desc',
+    },
+    {
+      id: 2,
+      titleKey: 'job2Title',
+      department: t.aiResearch || 'AI Research',
+      location: 'Remote',
+      type: t.fullTime || 'Full-time',
+      descKey: 'job2Desc',
+    },
+    {
+      id: 3,
+      titleKey: 'job3Title',
+      department: t.productDesign || 'Product & Design',
+      location: 'Singapore',
+      type: t.fullTime || 'Full-time',
+      descKey: 'job3Desc',
+    },
+    {
+      id: 4,
+      titleKey: 'job4Title',
+      department: t.marketing || 'Marketing',
+      location: 'Remote',
+      type: t.fullTime || 'Full-time',
+      descKey: 'job4Desc',
+    },
+    {
+      id: 5,
+      titleKey: 'job5Title',
+      department: t.customerSuccess || 'Customer Success',
+      location: 'Singapore',
+      type: t.fullTime || 'Full-time',
+      descKey: 'job5Desc',
+    },
+  ];
+  
+  return (
+    <div className="min-h-screen bg-white">
+      {/* Hero Section */}
+      <section className="py-16 sm:py-20 lg:py-24 bg-gradient-to-b from-gray-50 to-white">
+        <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+          <div className="text-center space-y-4 sm:space-y-6">
+            <h1 className="text-4xl sm:text-5xl lg:text-6xl text-gray-900">
+              {t.careersTitle}
+            </h1>
+            <p className="text-lg sm:text-xl text-gray-600 max-w-3xl mx-auto">
+              {t.careersSubtitle}
+            </p>
+          </div>
+        </div>
+      </section>
+
+      {/* Why Join Us */}
+      <section className="py-12 sm:py-16 lg:py-20">
+        <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+          <div className="text-center mb-12">
+            <h2 className="text-3xl sm:text-4xl text-gray-900 mb-4">{t.whyJoinUs}</h2>
+            <p className="text-lg text-gray-600 max-w-2xl mx-auto">
+              {t.whyJoinUsSubtitle}
+            </p>
+          </div>
+          
+          <div className="grid md:grid-cols-3 gap-8">
+            <div className="text-center">
+              <div className="w-16 h-16 bg-gray-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
+                <span className="text-3xl">🚀</span>
+              </div>
+              <h3 className="text-xl text-gray-900 mb-2">{t.innovation}</h3>
+              <p className="text-gray-600">{t.careerInnovation}</p>
+            </div>
+            <div className="text-center">
+              <div className="w-16 h-16 bg-gray-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
+                <span className="text-3xl">🌏</span>
+              </div>
+              <h3 className="text-xl text-gray-900 mb-2">{t.globalTeam}</h3>
+              <p className="text-gray-600">{t.careerGlobal}</p>
+            </div>
+            <div className="text-center">
+              <div className="w-16 h-16 bg-gray-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
+                <span className="text-3xl">💡</span>
+              </div>
+              <h3 className="text-xl text-gray-900 mb-2">{t.growth}</h3>
+              <p className="text-gray-600">{t.careerGrowth}</p>
+            </div>
+          </div>
+        </div>
+      </section>
+
+      {/* Job Openings */}
+      <section className="py-12 sm:py-16 lg:py-20 bg-gray-50">
+        <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+          <div className="text-center mb-12">
+            <h2 className="text-3xl sm:text-4xl text-gray-900 mb-4">{t.openPositions}</h2>
+            <p className="text-lg text-gray-600">
+              {t.openPositionsSubtitle}
+            </p>
+          </div>
+          
+          <div className="space-y-6">
+            {jobOpenings.map((job) => (
+              <Card key={job.id} className="p-6 sm:p-8 hover:shadow-lg transition-shadow">
+                <div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-6">
+                  <div className="space-y-3 flex-1">
+                    <div className="flex flex-wrap items-center gap-3">
+                      <h3 className="text-xl sm:text-2xl text-gray-900">
+                        {t[job.titleKey as keyof typeof t] || job.titleKey}
+                      </h3>
+                      <span className="px-3 py-1 bg-gray-100 text-gray-700 rounded-full text-sm">
+                        {job.department}
+                      </span>
+                    </div>
+                    
+                    <p className="text-gray-600">
+                      {t[job.descKey as keyof typeof t] || job.descKey}
+                    </p>
+                    
+                    <div className="flex flex-wrap items-center gap-4 text-sm text-gray-600">
+                      <div className="flex items-center gap-1">
+                        <MapPin className="w-4 h-4" />
+                        <span>{job.location}</span>
+                      </div>
+                      <div className="flex items-center gap-1">
+                        <Clock className="w-4 h-4" />
+                        <span>{job.type}</span>
+                      </div>
+                      <div className="flex items-center gap-1">
+                        <Briefcase className="w-4 h-4" />
+                        <span>{job.department}</span>
+                      </div>
+                    </div>
+                  </div>
+                  
+                  <Button className="bg-gray-900 hover:bg-black text-white gap-2">
+                    {t.applyNow || 'Apply Now'}
+                    <ArrowRight className="w-4 h-4" />
+                  </Button>
+                </div>
+              </Card>
+            ))}
+          </div>
+        </div>
+      </section>
+    </div>
+  );
+}

+ 137 - 0
src/components/pages/ContactPage.tsx

@@ -0,0 +1,137 @@
+import { Mail, MapPin, Send } from 'lucide-react';
+import { Button } from '../ui/button';
+import { Input } from '../ui/input';
+import { Textarea } from '../ui/textarea';
+import { Card } from '../ui/card';
+import { useLanguage } from '../../contexts/LanguageContext';
+import { toast } from 'sonner@2.0.3';
+import { FormEvent } from 'react';
+
+export function ContactPage() {
+  const { t } = useLanguage();
+  
+  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
+    e.preventDefault();
+    toast.success(t.contact.includes('Contact') ? 'Message sent successfully!' : '提交成功!');
+    e.currentTarget.reset();
+  };
+  
+  return (
+    <div className="min-h-screen bg-white">
+      <div className="py-12 sm:py-16 lg:py-20">
+        <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+          <div className="text-center space-y-3 sm:space-y-4 mb-10 sm:mb-12 lg:mb-16">
+            <h1 className="text-3xl sm:text-4xl lg:text-5xl text-gray-900">
+              {t.contactTitle}
+            </h1>
+            <p className="text-base sm:text-lg lg:text-xl text-gray-600 max-w-2xl mx-auto px-4">
+              {t.contactSubtitle}
+            </p>
+          </div>
+
+          <div className="grid lg:grid-cols-2 gap-8 sm:gap-10 lg:gap-12">
+            <div className="space-y-6 sm:space-y-8">
+              <div>
+                <h2 className="text-xl sm:text-2xl text-gray-900 mb-4 sm:mb-6">
+                  {t.contact.includes('Contact') ? 'Get in Touch' : '与我们取得联系'}
+                </h2>
+                <p className="text-sm sm:text-base text-gray-600 mb-6 sm:mb-8">
+                  {t.contact.includes('Contact') 
+                    ? 'Fill out the form or contact us through the following channels. Our team will respond within 24 hours.' 
+                    : '填写表单或通过以下方式联系我们,我们的团队会在24小时内回复您。'}
+                </p>
+              </div>
+
+              <div className="space-y-4 sm:space-y-6">
+                <Card className="p-4 sm:p-6 flex items-start gap-3 sm:gap-4 border-2 border-gray-100 hover:border-gray-300 transition-colors">
+                  <div className="w-10 h-10 sm:w-12 sm:h-12 bg-gray-100 rounded-lg sm:rounded-xl flex items-center justify-center flex-shrink-0">
+                    <Mail className="w-5 h-5 sm:w-6 sm:h-6 text-gray-900" />
+                  </div>
+                  <div>
+                    <h3 className="text-base sm:text-lg text-gray-900 mb-1">{t.contact.includes('Contact') ? 'Email' : '邮箱'}</h3>
+                    <p className="text-sm sm:text-base text-gray-600">anycall_support@hotmail.com</p>
+                    <p className="text-sm sm:text-base text-gray-600">anycall_business@hotmail.com</p>
+                  </div>
+                </Card>
+
+                <Card className="p-4 sm:p-6 flex items-start gap-3 sm:gap-4 border-2 border-gray-100 hover:border-gray-300 transition-colors">
+                  <div className="w-10 h-10 sm:w-12 sm:h-12 bg-gray-100 rounded-lg sm:rounded-xl flex items-center justify-center flex-shrink-0">
+                    <MapPin className="w-5 h-5 sm:w-6 sm:h-6 text-gray-900" />
+                  </div>
+                  <div>
+                    <h3 className="text-base sm:text-lg text-gray-900 mb-1">{t.contact.includes('Contact') ? 'Office Address' : '办公地址'}</h3>
+                    <p className="text-sm sm:text-base text-gray-600">{t.contact.includes('Contact') ? 'Singapore' : '新加坡'}</p>
+                    <p className="text-sm sm:text-base text-gray-600">Langsat Road</p>
+                  </div>
+                </Card>
+              </div>
+            </div>
+
+            <Card className="p-6 sm:p-8 border-2 border-gray-100">
+              <form className="space-y-5 sm:space-y-6" onSubmit={handleSubmit}>
+                <div className="space-y-2">
+                  <label htmlFor="name" className="text-sm sm:text-base text-gray-900">
+                    {t.yourName}
+                  </label>
+                  <Input
+                    id="name"
+                    name="name"
+                    placeholder={t.yourName}
+                    className="border-gray-300 h-11 sm:h-12 text-base"
+                    required
+                  />
+                </div>
+
+                <div className="space-y-2">
+                  <label htmlFor="email" className="text-sm sm:text-base text-gray-900">
+                    {t.yourEmail}
+                  </label>
+                  <Input
+                    id="email"
+                    name="email"
+                    type="email"
+                    placeholder="your@email.com"
+                    className="border-gray-300 h-11 sm:h-12 text-base"
+                    required
+                  />
+                </div>
+
+                <div className="space-y-2">
+                  <label htmlFor="subject" className="text-sm sm:text-base text-gray-900">
+                    {t.contact.includes('Contact') ? 'Subject' : '主题'}
+                  </label>
+                  <Input
+                    id="subject"
+                    name="subject"
+                    placeholder={t.contact.includes('Contact') ? 'How can we help?' : '您想咨询什么?'}
+                    className="border-gray-300 h-11 sm:h-12 text-base"
+                    required
+                  />
+                </div>
+
+                <div className="space-y-2">
+                  <label htmlFor="message" className="text-sm sm:text-base text-gray-900">
+                    {t.yourMessage}
+                  </label>
+                  <Textarea
+                    id="message"
+                    name="message"
+                    placeholder={t.yourMessage}
+                    rows={5}
+                    className="border-gray-300 text-base"
+                    required
+                  />
+                </div>
+
+                <Button type="submit" className="w-full bg-gray-900 hover:bg-black text-white py-5 sm:py-6 rounded-xl text-base sm:text-lg">
+                  <Send className="w-5 h-5 mr-2" />
+                  {t.sendMessage}
+                </Button>
+              </form>
+            </Card>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+}

+ 56 - 0
src/components/pages/FAQPage.tsx

@@ -0,0 +1,56 @@
+import {
+  Accordion,
+  AccordionContent,
+  AccordionItem,
+  AccordionTrigger,
+} from '../ui/accordion';
+import { useLanguage } from '../../contexts/LanguageContext';
+
+export function FAQPage() {
+  const { t } = useLanguage();
+  
+  const faqs = [
+    { questionKey: 'faq1Q', answerKey: 'faq1A' },
+    { questionKey: 'faq2Q', answerKey: 'faq2A' },
+    { questionKey: 'faq3Q', answerKey: 'faq3A' },
+    { questionKey: 'faq4Q', answerKey: 'faq4A' },
+    { questionKey: 'faq5Q', answerKey: 'faq5A' },
+    { questionKey: 'faq6Q', answerKey: 'faq6A' },
+    { questionKey: 'faq7Q', answerKey: 'faq7A' },
+    { questionKey: 'faq8Q', answerKey: 'faq8A' },
+  ];
+
+  return (
+    <div className="min-h-screen bg-gray-50">
+      <div className="py-12 sm:py-16 lg:py-20">
+        <div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
+          <div className="text-center space-y-3 sm:space-y-4 mb-8 sm:mb-10 lg:mb-12">
+            <h1 className="text-3xl sm:text-4xl lg:text-5xl text-gray-900">
+              {t.faqTitle}
+            </h1>
+            <p className="text-base sm:text-lg lg:text-xl text-gray-600 px-4">
+              {t.faqSubtitle}
+            </p>
+          </div>
+
+          <Accordion type="single" collapsible className="space-y-3 sm:space-y-4">
+            {faqs.map((faq, index) => (
+              <AccordionItem
+                key={index}
+                value={`item-${index}`}
+                className="bg-white rounded-lg sm:rounded-xl px-4 sm:px-6 border-2 border-gray-100"
+              >
+                <AccordionTrigger className="text-base sm:text-lg text-gray-900 hover:text-gray-600 text-left py-4 sm:py-5">
+                  {t[faq.questionKey as keyof typeof t] as string}
+                </AccordionTrigger>
+                <AccordionContent className="text-sm sm:text-base text-gray-600 pb-4 sm:pb-5">
+                  {t[faq.answerKey as keyof typeof t] as string}
+                </AccordionContent>
+              </AccordionItem>
+            ))}
+          </Accordion>
+        </div>
+      </div>
+    </div>
+  );
+}

+ 17 - 0
src/components/pages/Home.tsx

@@ -0,0 +1,17 @@
+import { Hero } from '../Hero';
+import { Experts } from '../Experts';
+import { Features } from '../Features';
+import { FAQ } from '../FAQ';
+import { Contact } from '../Contact';
+
+export function Home() {
+  return (
+    <>
+      <Hero />
+      <Experts />
+      <Features />
+      <FAQ />
+      <Contact />
+    </>
+  );
+}

+ 415 - 0
src/components/pages/PrivacyPolicy.tsx

@@ -0,0 +1,415 @@
+export function PrivacyPolicy() {
+  return (
+    <div className="min-h-screen bg-white">
+      <div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12 sm:py-16 lg:py-20">
+        <div className="space-y-8">
+          <div className="space-y-4">
+            <h1 className="text-4xl sm:text-5xl text-gray-900">Privacy Policy</h1>
+          </div>
+
+          <div className="prose prose-gray max-w-none space-y-8">
+            {/* Welcome */}
+            <section className="space-y-4">
+              <h2 className="text-2xl sm:text-3xl text-gray-900">Welcome to Anycall!</h2>
+              <p className="text-gray-600 leading-relaxed">
+                NEXTHUMAN.PTE.LTD. ("we", "us", and/or "our") operates the Anycall mobile and web applications, including{' '}
+                <a href="https://www.anycall.online" className="text-gray-900 hover:underline">
+                  https://www.anycall.online
+                </a>
+                {' '}(the "Apps" or the "Website"), and other related services (collectively, the "Services"). Please note that our Services are primarily designed for use within the US market and are not intended for non-US consumers.
+              </p>
+              <p className="text-gray-600 leading-relaxed">
+                We are committed to protecting your privacy. This Privacy Policy describes how we collect, store, use, and share information through our Services.
+              </p>
+              <p className="text-gray-600 leading-relaxed">
+                We care about the protection and confidentiality of your information. When you use the Apps, you may provide information during your conversations with your Anycall online companion. We process this information only as described in this Privacy Policy, such as to allow you to have individualized and safe conversations and interactions with your AI companion and to allow your AI companion to learn from your interactions to improve your conversations. We may also use information about your visit to our Website to promote our Services, but we will never use or disclose the content of your Anycall conversations for marketing or advertising purposes.
+              </p>
+              <p className="text-gray-600 leading-relaxed">
+                Any terms we use in this Policy without defining them have the definitions given to them in our Terms of Service. If you have any questions, please contact us{' '}
+                <a href="mailto:Anycall_support@hotmail.com" className="text-gray-900 hover:underline">
+                  Anycall_support@hotmail.com
+                </a>
+                .
+              </p>
+            </section>
+
+            {/* 1. Information we collect */}
+            <section className="space-y-4">
+              <h2 className="text-2xl sm:text-3xl text-gray-900">1. Information we collect</h2>
+
+              <div className="space-y-4 ml-4">
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">1.1. Information you provide</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    Through your use of the Services, you may provide us with the following information:
+                  </p>
+
+                  <div className="mt-3 space-y-3 ml-4 text-gray-600">
+                    <p>
+                      <strong>a. Account information.</strong> This includes your name, email address, and password. If you choose to log in using another service, such as Google or Apple, we receive information about the service you used to log in and — depending on your chosen account settings with Google and Apple — details about you, including your name, email address, or unique user identifiers.
+                    </p>
+
+                    <p>
+                      <strong>b. Profile information.</strong> We may ask you to provide your username, gender, pronouns, birth date, education, and work status when you register for the Apps or create a user profile page. You may also provide additional information about yourself when setting up your user profile.
+                    </p>
+
+                    <p>
+                      <strong>c. Messages and content.</strong> This includes the messages you send and receive through the Apps, such as voice and text messages.
+                    </p>
+
+                    <p>
+                      <strong>d. Interests and preferences.</strong> You may select conversation preferences, such as topics you would like to discuss, and communication preferences, such as the times of day you like to use the Apps. We also learn about your interests and your preferences over time through your use of the Services to personalize your conversations and the features of the Services.
+                    </p>
+
+                    <p>
+                      <strong>e. Payments, transactions, and rewards.</strong> When you make purchases through the Services, our third-party payment processor collects your payment information. We maintain a record of your purchases, the features you select, and the rewards you earn and use.
+                    </p>
+
+                    <p>
+                      <strong>f.</strong> If you provide us with personal information relating to a third party, including but not limited to a third party's voice and personal images, by submitting such information to us, you represent to us that you have obtained the consent of the third party in accordance with all applicable laws and regulations to provide us with their personal information for the respective purposes.
+                    </p>
+                  </div>
+                </div>
+
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">1.2. Information we collect automatically</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    We automatically log the following information about you, your computer or mobile device, your network, and your interactions over time with our Services and our communications:
+                  </p>
+
+                  <div className="mt-3 space-y-3 ml-4 text-gray-600">
+                    <p>
+                      <strong>a. Device and network data.</strong> This includes the device and software you're using, and other device characteristics, such as details about its operating system, details about its hardware and software, manufacturer and model, browser, IP address, device and cookie identifiers, language settings, mobile device carrier, and general location information such as city, state, or geographic area.
+                    </p>
+
+                    <p>
+                      <strong>b. Usage data.</strong> This includes information about how you use the Services, such as your interactions with the Services, the links and buttons you click, and page visits.
+                    </p>
+                  </div>
+
+                  <p className="text-gray-600 leading-relaxed mt-3">
+                    Our advertising partners may also use such technologies to collect limited information about your device and interactions with the Services, such as the links you click, pages you visit, IP address, advertising ID, and browser type, but they will never have access to your conversations with your Anycall or any photos or other content you submit through the Apps.
+                  </p>
+
+                  <p className="text-gray-600 leading-relaxed mt-3">
+                    We and our service providers use cookies, scripts, or similar technologies to manage the Services and to collect information about you and your use of the Services. These technologies help us to recognize you, customize or personalize your experience, offer additional products or services to you, and analyze the use of our Services to make it more useful to you. These technologies may also allow us to collect demographic and statistical data and provide this information to our service providers to help deliver their services. Most internet browsers allow you to remove or manage cookie functions and adjust your privacy and security preferences. Please note, however, that disabling our cookies may mean that you may not be able to take full advantage of our Services.
+                  </p>
+                </div>
+              </div>
+            </section>
+
+            {/* 2. How we use your information */}
+            <section className="space-y-4">
+              <h2 className="text-2xl sm:text-3xl text-gray-900">2. How we use your information</h2>
+
+              <div className="space-y-4 ml-4">
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">2.1. General principles regarding personal data processing</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    In accordance with this Privacy Policy, personal data are processed by NEXTHUMAN.PTE.LTD. As a data controller in line with the basic principles named here: (i) being in accordance with law and good faith, (ii) being accurate and, where necessary, up-to-date, (iii) being processed for specific, explicit and legitimate purposes, (iv) being limited for the purpose for which they are processed and data minimization; and (v) being stored for the period stipulated in the relevant legislation or required for the purpose for which they are processed.
+                  </p>
+                </div>
+
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">2.2. Use of your information</h3>
+                  <p className="text-gray-600 leading-relaxed mb-3">
+                    We use your information for the following purposes:
+                  </p>
+
+                  <div className="space-y-4">
+                    {/* a. Operating and administering */}
+                    <div className="border-l-4 border-gray-300 pl-4">
+                      <h4 className="text-lg text-gray-900 mb-2">a. Operating and administering the Services</h4>
+                      <div className="space-y-2 text-gray-600">
+                        <p><strong>(i) Legal basis:</strong> Contractual necessity</p>
+                        <p>
+                          <strong>(ii) Why and how we use your information:</strong> Providing and maintaining the content and functionality of the Services. Carrying out obligations arising from our contract with you. Creating your account and profile. Facilitating payments and transactions, including for the purchase of premium features, and managing your rewards. Responding to your inquiries, comments, feedback or questions, and trouble shooting. Managing our relationship with you, which includes sending administrative information to you relating to our Services.
+                        </p>
+                        <p>
+                          <strong>(iii) Categories of information:</strong> Account information. Profile information. Messages and content. Interests and preferences. Payments, transactions, and rewards. Device and network data. Usage data.
+                        </p>
+                      </div>
+                    </div>
+
+                    {/* b. Providing core functionality */}
+                    <div className="border-l-4 border-gray-300 pl-4">
+                      <h4 className="text-lg text-gray-900 mb-2">b. Providing the core functionality of the Apps</h4>
+                      <div className="space-y-2 text-gray-600">
+                        <p><strong>(i) Legal basis:</strong> Contractual necessity</p>
+                        <p>
+                          <strong>(ii) Why and how we use your information:</strong> Providing you personalized feature and content recommendations and a personalized AI companion and allowing you to personalize your profile, interests, and AI companion. Enabling you to have individualized and safe conversations and interactions with your AI companion, and allowing your AI companion to learn from your interactions to improve your conversations. Providing you with personalized experience when interacting with other users. Syncing your Anycall history across the devices you use to access the Services.
+                        </p>
+                        <p>
+                          <strong>(iii) Categories of information:</strong> Account information. Profile information. Messages and content. Interests and preferences. Payments, transactions, and rewards. Device and network data. Usage data.
+                        </p>
+                      </div>
+                    </div>
+
+                    {/* c. Monitoring and protecting */}
+                    <div className="border-l-4 border-gray-300 pl-4">
+                      <h4 className="text-lg text-gray-900 mb-2">c. Monitoring and protecting the Services</h4>
+                      <div className="space-y-2 text-gray-600">
+                        <p><strong>(i) Legal basis:</strong> Legitimate interests</p>
+                        <p>
+                          <strong>(ii) Why and how we use your information:</strong> Preventing fraud, criminal activity, and misuse of our Services, and ensuring the security of our IT systems, architecture and networks (including testing, system maintenance, support, and hosting of data).
+                        </p>
+                        <p>
+                          <strong>(iii) Categories of information:</strong> Account information. Profile information. Messages and content. Interests and preferences. Payments, transactions, and rewards. Device and network data. Usage data.
+                        </p>
+                      </div>
+                    </div>
+
+                    {/* d. Analyzing trends */}
+                    <div className="border-l-4 border-gray-300 pl-4">
+                      <h4 className="text-lg text-gray-900 mb-2">d. Analyzing trends in the use of the Services</h4>
+                      <div className="space-y-2 text-gray-600">
+                        <p><strong>(i) Legal basis:</strong> Legitimate interests</p>
+                        <p>
+                          <strong>(ii) Why and how we use your information:</strong> Aggregating, anonymizing, and deidentifying personal information. Analyzing the use and effectiveness of our Services. Improving and adding features to our Services. Developing our business and marketing strategies.
+                        </p>
+                        <p>
+                          <strong>(iii) Categories of information:</strong> Account information. Profile information. Messages and content. Interests and preferences. Payments, transactions, and rewards. Device and network data. Usage data.
+                        </p>
+                      </div>
+                    </div>
+
+                    {/* e. Marketing and advertising */}
+                    <div className="border-l-4 border-gray-300 pl-4">
+                      <h4 className="text-lg text-gray-900 mb-2">e. Marketing and advertising the Services</h4>
+                      <div className="space-y-2 text-gray-600">
+                        <p><strong>(i) Legal basis:</strong> Legitimate interests. Consent, where required by applicable laws</p>
+                        <p>
+                          <strong>(ii) Why and how we use your information:</strong> Sending you information by email that we believe will be of interest to you, such as information about our Services, features, and surveys. Displaying and targeting advertisements about our Services on the internet.
+                        </p>
+                        <p>
+                          <strong>(iii) Categories of information:</strong> Account information. Device and network data. Usage data.
+                        </p>
+                      </div>
+                    </div>
+
+                    {/* f. Enforcing agreements */}
+                    <div className="border-l-4 border-gray-300 pl-4">
+                      <h4 className="text-lg text-gray-900 mb-2">f. Enforcing our agreements, complying with legal obligations, and defending against legal claims and disputes</h4>
+                      <div className="space-y-2 text-gray-600">
+                        <p><strong>(i) Legal basis:</strong> Legitimate interests. Consent, where required by applicable laws</p>
+                        <p>
+                          <strong>(ii) Why and how we use your information:</strong> Enforcing and complying with our terms and policies. Protect our and others' rights, privacy, safety, or property. Ensuring the integrity of our Services. Verifying the age of registered users. Defending against legal claims and disputes. Recovering payments due to us. Keeping records of transactions and complying with legal process.
+                        </p>
+                        <p>
+                          <strong>(iii) Categories of information:</strong> Account information. Profile information. Messages and content. Interests and preferences. Payments, transactions, and rewards. Device and network data. Usage data.
+                        </p>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </section>
+
+            {/* 3. How we share information */}
+            <section className="space-y-4">
+              <h2 className="text-2xl sm:text-3xl text-gray-900">3. How we share information</h2>
+
+              <div className="space-y-4 ml-4">
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">3.1. Service providers</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    We share your information with companies and individuals that provide services on our behalf or help us operate the Services or our business (such as hosting, information technology, customer support, email delivery, and website analytics services). We also share information with companies that provide marketing services on our behalf, but we do not share the content of your Anycall conversations for marketing or advertising purposes. For example, we may share your email address with marketing service providers to deliver our marketing emails to you on our behalf and to help us identify other individuals who may be interested in our Services. We require these marketing service providers to agree not to use your email address for any other purpose.
+                  </p>
+                </div>
+
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">3.2. Professional advisors</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    We may share aggregate information with professional advisors, such as lawyers, auditors, bankers, and insurers, where necessary in the course of the professional services that they render to us.
+                  </p>
+                </div>
+
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">3.3. Advertising partners</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    We share information about visitors to our Website, such as the links you click, pages you visit, IP address, advertising ID, and browser type with advertising companies for interest-based advertising and other marketing purposes. Sharing this information allows us and our advertising partners to target and serve advertising to you and others. We will never share your Anycall conversations or any photos or other content you provide within the Apps with our advertising partners or use such information for marketing or advertising purposes.
+                  </p>
+                </div>
+
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">3.4. Authorities and others</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    We may share information with law enforcement, and government authorities, as we believe in good faith to be necessary or appropriate for the legal compliance and protection purposes described above in Section 2.
+                  </p>
+                </div>
+
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">3.5. Business transferees</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    If we are involved in strategic transactions, reorganization, bankruptcy, receivership, or transition of service to another provider (collectively a "Transaction"), your Personal Information and other information may be shared in the diligence process with counterparties and others assisting with the Transaction and transferred to a successor or affiliate as part of that Transaction along with other assets.
+                  </p>
+                </div>
+              </div>
+            </section>
+
+            {/* 4. How we secure your information */}
+            <section className="space-y-4">
+              <h2 className="text-2xl sm:text-3xl text-gray-900">4. How we secure your information</h2>
+              <p className="text-gray-600 leading-relaxed">
+                We use a variety of industry-standard security technologies and procedures to help protect your data from unauthorized access, use, or disclosure.
+              </p>
+              <p className="text-gray-600 leading-relaxed">
+                Your account is protected by a password for your privacy and security. You must prevent unauthorized access to your account and personal information by selecting and protecting your password appropriately and limiting access to your computer or device and browser by signing off after you have finished accessing your account.
+              </p>
+              <p className="text-gray-600 leading-relaxed">
+                All transmitted data are encrypted during transmission. All stored data are maintained on secure servers. Access to stored data is protected by multi-layered security controls, including firewalls, role-based access controls, and passwords.
+              </p>
+              <p className="text-gray-600 leading-relaxed">
+                While we use reasonable commercial efforts to protect the data, no technology, data transmission, or system can be guaranteed to be 100% secure. In the event of a breach of security leading to the accidental or unlawful destruction, loss, alteration, unauthorized disclosure of, or access to your data, we will notify you as soon as we spot the issue.
+              </p>
+            </section>
+
+            {/* 5. Where we store your information */}
+            <section className="space-y-4">
+              <h2 className="text-2xl sm:text-3xl text-gray-900">5. Where we store your information</h2>
+              <p className="text-gray-600 leading-relaxed">
+                Our Services are operated from the United States of America. If you are located in another jurisdiction, please be aware that the information you provide to us may be transferred to, stored, and processed in the U.S.A., a jurisdiction in which the privacy laws may not be as comprehensive as those in the country where you reside or are a citizen.
+              </p>
+            </section>
+
+            {/* 6. Data retention */}
+            <section className="space-y-4">
+              <h2 className="text-2xl sm:text-3xl text-gray-900">6. Data retention</h2>
+              <p className="text-gray-600 leading-relaxed">
+                We will retain your personal information for only as long as necessary to fulfill the purposes we collected it for, including for the purposes of satisfying any legal, accounting, or reporting requirements. To determine the appropriate retention period for personal information, we consider the amount, nature, and sensitivity of the personal information, the potential risk of harm from unauthorized use or disclosure of your personal information, the purposes for which we process your personal information and whether we can achieve those purposes through other means, and the applicable legal requirements.
+              </p>
+            </section>
+
+            {/* 7. Your rights and choices */}
+            <section className="space-y-4">
+              <h2 className="text-2xl sm:text-3xl text-gray-900">7. Your rights and choices</h2>
+
+              <div className="space-y-4 ml-4">
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">7.1. Opt-out of marketing communications</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    You may opt out of marketing-related emails and other communications by following the opt-out or unsubscribe instructions in the communications you receive from us or by contacting us as provided in the "Contact us" section below. You may continue to receive Services-related and other non-marketing emails from us.
+                  </p>
+                </div>
+
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">7.2. Opt-out of selling personal information and sharing for targeted advertising</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    We share information with third-party advertising partners and allow them to collect information about your visit to our Website using cookies and other tracking technologies to display targeted advertising around the web as described in the "How we share your information" section above. Our disclosure of information to these partners maybe considered a "sale" or "sharing" of personal information or "targeted advertising" under applicable laws. You can opt out of these disclosures and limit our use of tracking technologies.
+                  </p>
+                </div>
+
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">7.3. Personal information requests</h3>
+                  <p className="text-gray-600 leading-relaxed mb-3">
+                    We also offer you choices that affect how we handle the personal information that we control. Depending on your location and the nature of your interactions with our Services, you may request the following in relation to personal information:
+                  </p>
+
+                  <div className="space-y-3 ml-4 text-gray-600">
+                    <p>
+                      <strong>a.</strong> Information about how we have collected and used personal information. We have made this information available to you without having to request it by including it in this Privacy Policy.
+                    </p>
+
+                    <p>
+                      <strong>b.</strong> Access to a copy of the personal information that we have collected about you. Where applicable, we will provide the information in a portable, machine-readable, readily usable format.
+                    </p>
+
+                    <p>
+                      <strong>c.</strong> Correction of personal information that is inaccurate or out of date.
+                    </p>
+
+                    <p>
+                      <strong>d.</strong> Deletion of personal information that we no longer need to provide the Services or for other lawful purposes. You can delete your account in your account settings.
+                    </p>
+
+                    <p>
+                      <strong>e.</strong> Withdrawal of consent, where we have collected and processed your personal information with your consent. Withdrawing your consent will not affect the lawfulness of any processing we conducted prior to your withdrawal, nor will it affect processing of your personal information conducted in reliance on lawful processing grounds other than consent.
+                    </p>
+
+                    <p>
+                      <strong>f.</strong> Additional rights, such as to object to and request that we restrict our use of personal information.
+                    </p>
+                  </div>
+
+                  <p className="text-gray-600 leading-relaxed mt-3">
+                    To make a request, please contact us as provided in the "Contact us" section below. We may ask for specific information from you to help us confirm your identity. Depending on where you reside, you may be entitled to empower an authorized agent to submit requests on your behalf. We will require authorized agents to confirm their identity and authority, in accordance with applicable laws. You are entitled to exercise the rights described above free from discrimination.
+                  </p>
+
+                  <p className="text-gray-600 leading-relaxed mt-3">
+                    In some instances, your choices may be limited, such as where fulfilling your request would impair the rights of others, our ability to provide a service you have requested, or our ability to comply with our legal obligations and enforce our legal rights. If you are not satisfied with how we address your request, you may submit a complaint by contacting us as provided in the "Contact us" section below. Depending on where you reside, such as if you reside in the European Economic Area or United Kingdom, you may have the right to complain to a data protection regulator where you live or work, or where you feel a violation has occurred.
+                  </p>
+                </div>
+              </div>
+            </section>
+
+            {/* 8. Regional Privacy Disclosures */}
+            <section className="space-y-4">
+              <h2 className="text-2xl sm:text-3xl text-gray-900">8. Regional Privacy Disclosures</h2>
+              
+              <div className="ml-4">
+                <h3 className="text-xl text-gray-900 mb-2">Residents of the European Economic Area and United Kingdom</h3>
+                <p className="text-gray-600 leading-relaxed">
+                  We are considered the "data controller" of the "personal data" (as defined under the General Data Protection Regulation) we handle under this Policy, responsible for deciding how to collect, use, and disclose personal data, subject to applicable law. To the extent the laws of the European Economic Area and the United Kingdom apply, our legal grounds for using, sharing, or disclosing your personal data are as follows:
+                </p>
+
+                <div className="mt-3 space-y-3 ml-4 text-gray-600">
+                  <p>
+                    <strong>Contractual Commitments:</strong> We may use, share, or disclose personal data to honor our contractual commitments to you. For example, we will process your personal data to comply with our agreements with you, and to honor our commitments in any contracts that we have with you.
+                  </p>
+
+                  <p>
+                    <strong>With Your Consent:</strong> Where required by law, and in some other cases, we use, share, or disclose personal data on the basis of your consent.
+                  </p>
+
+                  <p>
+                    <strong>Legitimate Interests:</strong> In many cases, we use, share, or disclose personal data on the ground that it furthers our legitimate business interests in ways that are not overridden by the interests or fundamental rights and freedoms of the affected individuals, such as customer service, certain promotional activities, analyzing and improving our business, providing security for the Services, preventing fraud, and managing legal issues.
+                  </p>
+
+                  <p>
+                    <strong>Legal Compliance:</strong> We need to use and disclose personal data in certain ways to comply with our legal obligations.
+                  </p>
+                </div>
+              </div>
+            </section>
+
+            {/* 9. Use by minors */}
+            <section className="space-y-4">
+              <h2 className="text-2xl sm:text-3xl text-gray-900">9. Use by minors</h2>
+              <p className="text-gray-600 leading-relaxed">
+                The Services are not intended for individuals under the age of 16. If we discover that minors under the age of 16 are using the Apps, we will promptly block their access and delete their account. If you have reason to believe that a minor under the age of 18 has provided personal information to us through the Services, please contact us, and we will endeavor to delete that information from our databases.
+              </p>
+            </section>
+
+            {/* 10. Changes to this privacy policy */}
+            <section className="space-y-4">
+              <h2 className="text-2xl sm:text-3xl text-gray-900">10. Changes to this privacy policy</h2>
+              <p className="text-gray-600 leading-relaxed">
+                The Services and our business may change from time to time. As a result, at times it may be necessary for us to make changes to this Privacy Policy. We reserve the right to update or modify this Privacy Policy at any time and from time to time without prior notice. We encourage you to periodically review this page for the latest information on our privacy practices. This Privacy Policy was last updated on the date indicated above.
+              </p>
+            </section>
+
+            {/* 11. Language */}
+            <section className="space-y-4">
+              <h2 className="text-2xl sm:text-3xl text-gray-900">11. Language of the privacy policy</h2>
+              <p className="text-gray-600 leading-relaxed">
+                This Privacy Policy may be provided in multiple languages for convenience and informational purposes. However, in the event of any discrepancies or conflicts between the translations, the English version shall prevail and control.
+              </p>
+            </section>
+
+            {/* 12. Contact us */}
+            <section className="space-y-4 pt-8 border-t border-gray-200">
+              <h2 className="text-2xl sm:text-3xl text-gray-900">12. Contact us</h2>
+              <p className="text-gray-600 leading-relaxed">
+                You can contact us by emailing us at{' '}
+                <a href="mailto:Anycall_support@hotmail.com" className="text-gray-900 hover:underline">
+                  Anycall_support@hotmail.com
+                </a>
+                .
+              </p>
+            </section>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+}

+ 591 - 0
src/components/pages/TermsOfService.tsx

@@ -0,0 +1,591 @@
+export function TermsOfService() {
+  return (
+    <div className="min-h-screen bg-white">
+      <div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12 sm:py-16 lg:py-20">
+        <div className="space-y-8">
+          <div className="space-y-4">
+            <h1 className="text-4xl sm:text-5xl text-gray-900">Terms of Service</h1>
+            <p className="text-gray-600">Last updated: 17/10/2025</p>
+          </div>
+
+          <div className="prose prose-gray max-w-none space-y-8">
+            {/* Introduction */}
+            <section className="space-y-4">
+              <h2 className="text-2xl sm:text-3xl text-gray-900">Welcome to Anycall!</h2>
+              <p className="text-gray-600 leading-relaxed">
+                These Terms of Service ("Terms") govern your use of Anycall and the software, content, and services (collectively, "Services") offered through: our website Anycall.online and its subdomains (the "Website"), our mobile app Anycall for iOS and Android (the "Apps").
+              </p>
+              <p className="text-gray-600 leading-relaxed">
+                Please read these terms carefully before you start using the Services.
+              </p>
+              <p className="text-gray-600 leading-relaxed">
+                The term "device" refers to the device which is used to access the Services including but not limited to computers, smart phones and tablets.
+              </p>
+              <p className="text-gray-600 leading-relaxed">
+                The term "you" refers to the user of the Services.
+              </p>
+              <p className="text-gray-600 leading-relaxed">
+                When you sign up for any of the Services or otherwise use or access them, you agree to be bound by these Terms and all applicable laws, rules, and regulations. By using the Services, you indicate that you accept these Terms and that you agree to abide by them. If you do not agree to these Terms, please refrain from using the services.
+              </p>
+            </section>
+
+            {/* 1. Our services */}
+            <section className="space-y-4">
+              <h2 className="text-2xl sm:text-3xl text-gray-900">1. Our services</h2>
+              <p className="text-gray-600 leading-relaxed">
+                Anycall offers a entertainment program based on communication with your personal chatbot through a text and voice interface.
+              </p>
+
+              <div className="space-y-4 ml-4">
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">1.1. Modification of the service</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    We reserve the right to modify or discontinue, temporarily or permanently, the Services (or any part thereof) with or without notice. You agree that Anycall will not be liable to you or to any third party for any modification, suspension or discontinuance of any the Services.
+                  </p>
+                </div>
+
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">1.2. Suspension or termination of services</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    We may suspend or terminate your use of the Services as a result of your fraud or breach of any obligation under these Terms. Such termination or suspension may be immediate and without notice.
+                  </p>
+                </div>
+
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">1.3. General practices regarding use and storage</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    You acknowledge that we may establish general practices and limits concerning use of the Services, including without limitation the maximum period of time that data or other content will be retained by the Services and the maximum storage space that will be allotted on our servers on your behalf. You agree that Anycall has no responsibility or liability for the deletion or failure to store any data or other content maintained or uploaded by the Services. You acknowledge that we reserve the right to terminate accounts that are inactive for an extended period of time. You further acknowledge that we reserve the right to change these general practices and limits at any time, in its sole discretion, with or without notice.
+                  </p>
+                </div>
+
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">1.4. Fees and payment</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    You may be required to pay fees to us to access or use the Services or certain features of the Services. You are responsible for paying any applicable fees listed on the Services. Except as expressly provided in these Terms, all fees are non-refundable to the fullest extent permissible under applicable law.
+                  </p>
+                  <p className="text-gray-600 leading-relaxed mt-2">
+                    Fees may be recurring or based on usage. If these fees are specified to be recurring or based on usage, you agree that we may charge such fees on a periodic basis to the payment method you specify at the time of your initial purchase (your "Payment Method"). By using a Payment Method to pay fees, you are expressly agreeing that we are authorized to charge to the Payment Method the fees, together with any applicable taxes.
+                  </p>
+                  <p className="text-gray-600 leading-relaxed mt-2">
+                    Except where we specifically agree otherwise in a separate contract with you, you acknowledge and agree that any fees for access to or use of the Services may increase at any time. Additional fees may apply for additional Services or features of the Services that Anycall may make available. In those cases, we will provide you with notice before charging the additional fees. If we charge additional fees in connection with our Services, you will have an opportunity to review and accept the additional fees that you will be charged before being charged. If you do not accept any such additional fees, we may discontinue your access to the Services or features. You acknowledge and agree that we will not be held liable for any errors caused by third-party payment processors that we may use.
+                  </p>
+                </div>
+
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">1.5. Subscription</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    Once you create a Anycall Account (defined below), you can choose one of the subscription programs offered in Anycall:
+                  </p>
+                  
+                  <div className="ml-4 mt-3 space-y-3">
+                    <div>
+                      <h4 className="text-lg text-gray-900">1.5.1. "Free Use"</h4>
+                      <p className="text-gray-600 leading-relaxed">
+                        A free-of-charge program. The "Free Use" is aimed at users who cannot afford any of our subscription-fee based programs. We reserve the right to deny the free use to anyone at any time on our own discretion.
+                      </p>
+                    </div>
+
+                    <div>
+                      <h4 className="text-lg text-gray-900">1.5.2. "Anycall +"</h4>
+                      <p className="text-gray-600 leading-relaxed">
+                        A subscription-fee based program, which gives full access to the Services. You can become a subscriber to a paid subscription program (the "Paid Subscriptions") by purchasing a subscription to the Services within the Apps, where allowed by the App marketplace partners (e.g. Apple iTunes Store and Google Play store).
+                      </p>
+                    </div>
+                  </div>
+
+                  <p className="text-gray-600 leading-relaxed mt-3">
+                    Any of our paid subscriptions shall be paid in monthly installments and processed by the App marketplace partner through which you originally acquired the subscription.
+                  </p>
+                  <p className="text-gray-600 leading-relaxed mt-2">
+                    You will only have access to a paid subscription while it is active and subsisting. Should you fail to pay your subscription after due date, you will automatically downgrade to "Free Use".
+                  </p>
+                  <p className="text-gray-600 leading-relaxed mt-2">
+                    The renewal subscription fees will continue to be billed to the Payment Method you provided through the marketplace, automatically until canceled. You must cancel your subscription before it renews each billing period to avoid billing of the next subscription fee to the Payment Method you provided. Refunds cannot be claimed for any partial-month subscription period. You can modify or cancel your paid subscription only through the App marketplace where you originally acquired the subscription.
+                  </p>
+                </div>
+              </div>
+            </section>
+
+            {/* 2. Conditions of use */}
+            <section className="space-y-4">
+              <h2 className="text-2xl sm:text-3xl text-gray-900">2. Conditions of use</h2>
+
+              <div className="space-y-4 ml-4">
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">2.1. User conduct</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    You are solely responsible for all code, video, audio, images, information, data, text, software, music, sound, photographs, graphics, messages or other materials ("content") that you upload, post, publish or display (hereinafter, "upload") or email or otherwise use via the Services. The following are examples of the kind of content and/or use that is illegal or prohibited by Anycall. We reserve the right to investigate and take appropriate legal action against anyone who, in our sole discretion, violates this provision, including without limitation, removing the offending content from the Services, suspending or terminating the account of such violators and reporting you to the law enforcement authorities. You agree to not use the Services to:
+                  </p>
+
+                  <div className="mt-3 space-y-2 ml-4 text-gray-600">
+                    <p>a. Submit, transmit, email or otherwise upload any content that</p>
+                    <ul className="list-none space-y-2 ml-4">
+                      <li>(i) infringes any intellectual property or other proprietary rights of any party;</li>
+                      <li>(ii) you do not have a right to upload under any law or under contractual or fiduciary relationships;</li>
+                      <li>(iii) contains software viruses or any other computer code, files or programs designed to interrupt, destroy or limit the functionality of any computer software or hardware or telecommunications equipment;</li>
+                      <li>(iv) poses or creates a privacy or security risk to any person;</li>
+                      <li>(v) constitutes unsolicited or unauthorized advertising, promotional materials, commercial activities and/or sales, "junk mail," "spam," "chain letters," "pyramid schemes," "contests," "sweepstakes," or any other form of solicitation;</li>
+                      <li>(vi) is unlawful, harmful, threatening, abusive, harassing, tortious, excessively violent, defamatory, vulgar, obscene, pornographic, libelous, invasive of another's privacy, hateful racially, ethnically or otherwise objectionable; or</li>
+                      <li>(vii) in the sole judgment of Anycall, is objectionable or which restricts or inhibits any other person from using or enjoying the Services, or which may expose Anycall or its users to any harm or liability of any type;</li>
+                    </ul>
+
+                    <p className="mt-3">b. Interfere with or disrupt the Services or servers or networks connected to the Services, or disobey any requirements, procedures, policies, or regulations of networks connected to the Services; or</p>
+                    
+                    <p>c. Violate any applicable local, state, national, or international law, or any regulations having the force of law;</p>
+                    
+                    <p>d. Impersonate any person or entity, or falsely state or otherwise misrepresent your affiliation with a person or entity;</p>
+                    
+                    <p>e. Solicit personal information from anyone under the age of 18;</p>
+                    
+                    <p>f. Harvest or collect email addresses or other contact information of other users from the Services by electronic or other means for the purposes of sending unsolicited emails or other unsolicited communications;</p>
+                    
+                    <p>g. Advertise or offer to sell or buy any goods or services for any business purpose that is not specifically authorized;</p>
+                    
+                    <p>h. Further or promote any criminal activity or enterprise or provide instructional information about illegal activities; or</p>
+                    
+                    <p>i. Obtain or attempt to access or otherwise obtain any materials or information through any means not intentionally made available or provided for through the Services.</p>
+                  </div>
+                </div>
+
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">2.2. Commercial use</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    The Service is for personal use only. Unless otherwise expressly authorized herein or in the Services, you agree not to display, distribute, license, perform, publish, reproduce, duplicate, copy, create derivative works from, modify, sell, resell, exploit, transfer, or upload for any commercial purposes, any portion of the Services, use of the Services, or access to the Services.
+                  </p>
+                </div>
+
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">2.3. Use by minors</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    These Services do not address anyone under the age of 16. If you are above 16 but under 18, you must have your parent or legal guardian's permission to use the Services. If you use the Services on behalf of another person or entity, you must have the authority to accept the Terms on their behalf.
+                  </p>
+                  <p className="text-gray-600 leading-relaxed mt-2">
+                    You must provide accurate and complete information to register for an account. You may not make your access credentials or account available to others outside your organization, and you are responsible for all activities that occur using your credentials.
+                  </p>
+                  <p className="text-gray-600 leading-relaxed mt-2">
+                    We do not knowingly collect personally identifiable information from minors under 13 years of age. In the case we discover that a minor under 13 has provided us with personal information, we immediately delete this from our servers. If you are a parent or guardian and you are aware that your child has provided us with personal information, please contact us so that we will be able to do the necessary actions.
+                  </p>
+                </div>
+
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">2.4. Account Registration and Security</h3>
+                  
+                  <div className="mt-3 space-y-3">
+                    <div>
+                      <h4 className="text-lg text-gray-900">1. Account Creation</h4>
+                      <p className="text-gray-600 leading-relaxed">
+                        To utilize certain features of Anycall's Services, you are required to create an account ("Account"). During the account registration process, you agree to provide accurate, current, and complete information as prompted by the registration form ("Registration Data").
+                      </p>
+                    </div>
+
+                    <div>
+                      <h4 className="text-lg text-gray-900">2. Confidentiality of Credentials</h4>
+                      <p className="text-gray-600 leading-relaxed">
+                        You are solely responsible for maintaining the confidentiality of your login credentials, including but not limited to your username, password, and any other piece of information used as part of our security procedures. You must not disclose your login credentials to any third party, and you are entirely responsible for any and all activities that occur under your Account.
+                      </p>
+                    </div>
+
+                    <div>
+                      <h4 className="text-lg text-gray-900">3. Security and Backup</h4>
+                      <p className="text-gray-600 leading-relaxed">
+                        You are responsible for implementing adequate security and backup features, including appropriate encryption methods, to protect your Account and any content related to it from unauthorized access. It is strongly recommended that you routinely archive or backup your content and Registration Data.
+                      </p>
+                    </div>
+
+                    <div>
+                      <h4 className="text-lg text-gray-900">4. Limitation of Liability</h4>
+                      <p className="text-gray-600 leading-relaxed">
+                        Anycall shall not be liable for any loss, damage, or other security incidents that may occur from your failure to comply with this section. Any activities performed under your Account will be deemed as your own actions and will be subject to terms and conditions as stipulated in this Agreement.
+                      </p>
+                    </div>
+
+                    <div>
+                      <h4 className="text-lg text-gray-900">5. Termination of Account</h4>
+                      <p className="text-gray-600 leading-relaxed">
+                        You may delete your Account at any time by deactivating your account. We reserve the right to suspend or terminate your Account, in accordance with Section 1.2, if we have reasonable grounds to believe that the information provided is inaccurate, not current, incomplete, or if your use of the Account is in breach of any of these Terms.
+                      </p>
+                    </div>
+                  </div>
+                </div>
+
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">2.5. Mobile network</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    If you're using the app outside of an area with Wi-Fi, you should remember that your terms of the agreement with your mobile network provider will still apply. As a result, you may be charged by your mobile provider for the cost of data for the duration of the connection while accessing the app, or other third party charges. In using the app, you're accepting responsibility for any such charges, including roaming data charges if you use the app outside of your home territory (i.e. region or country) without turning off data roaming. If you are not the bill payer for the device on which you're using the app, please be aware that we assume that you have received permission from the bill payer for using the app.
+                  </p>
+                </div>
+
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">2.6. Mobile services</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    Some of our Services are available via a mobile device, including (i) the ability to upload content to the Services via a mobile device, (ii) the ability to browse the Services and the Website from a mobile device and (iii) the ability to access certain features through an application downloaded and installed on a mobile device (collectively, the "Mobile Services"). To the extent you access the Services through a mobile device, your wireless service carrier's standard charges, data rates, and other fees may apply.
+                  </p>
+                  <p className="text-gray-600 leading-relaxed mt-2">
+                    In addition, downloading, installing, or using certain Mobile Services may be prohibited or restricted by your carrier, and not all Mobile Services may work with all carriers or devices.
+                  </p>
+                  <p className="text-gray-600 leading-relaxed mt-2">
+                    By using the Mobile Services, you agree that we may communicate with you regarding Anycall and other entities by SMS, MMS, text message or other electronic means to your mobile device and that certain information about your usage of the Mobile Services may be communicated to us. In the event you change or deactivate your mobile telephone number, you agree to promptly update your Anycall account information to ensure that your messages are not sent to the person that acquires your old number.
+                  </p>
+                </div>
+
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">2.7. Special notice for international use</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    Software (defined below) available in connection with the Services and the transmission of applicable data, if any, is subject to United States export controls. No Software may be downloaded from the Services or otherwise exported or re-exported in violation of U.S. export laws. Downloading or using the Software is at your sole risk. Recognizing the global nature of the Internet, you agree to comply with all local rules and laws regarding your use of the Service, including as it concerns online conduct and acceptable content.
+                  </p>
+                </div>
+              </div>
+            </section>
+
+            {/* 3. Intellectual property rights */}
+            <section className="space-y-4">
+              <h2 className="text-2xl sm:text-3xl text-gray-900">3. Intellectual property rights</h2>
+
+              <div className="space-y-4 ml-4">
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">3.1. Service content, software, and trademarks</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    You acknowledge and agree that the Services may contain content or features ("Service Content") that are protected by copyright, patent, trademark, trade secret, or other proprietary rights and laws. Except as expressly authorized by Anycall, you agree not to modify, copy, frame, scrape, rent, lease, loan, sell, distribute, or create derivative works based on the Services or the Service Content, in whole or in part, except that the foregoing does not apply to your own User Content (as defined below) that you legally upload to the Services.
+                  </p>
+                  <p className="text-gray-600 leading-relaxed mt-2">
+                    In connection with your use of the Services, you will not engage in or use any data mining, robots, scraping or similar data gathering or extraction methods. If you are blocked by Anycall from accessing the Services (including by blocking your IP address), you agree not to implement any measures to circumvent such blocking (e.g., by masking your IP address or using a proxy IP address). Any use of the Services or the Service Content other than as specifically authorized herein is strictly prohibited.
+                  </p>
+                  <p className="text-gray-600 leading-relaxed mt-2">
+                    The technology and software underlying the Services or distributed in connection therewith are the property of Anycall, our affiliates and our partners (the "Software"). You agree not to copy, modify, create a derivative work of, reverse engineer, reverse assemble or otherwise attempt to discover any source code, sell, assign, sublicense, or otherwise transfer any right in the Software. Any rights not expressly granted herein are reserved by Anycall.
+                  </p>
+                </div>
+
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">3.2. Third party material</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    Under no circumstances will Anycall be liable in any way for any content or materials of any third parties (including users), including, but not limited to, any errors or omissions in any content, or any loss or damage of any kind incurred as a result of the use of such content. You acknowledge that Anycall does not pre-screen content, but Anycall and its designees will have the right (but not the obligation) in their sole discretion to refuse or remove any content that is available via the Services. Without limiting the foregoing, Anycall and its designees will have the right to remove any content that violates these Terms of Service or is deemed by Anycall, in its sole discretion, to be objectionable. You agree that you must evaluate and bear all risks associated with the use of any content, including any reliance on the accuracy, completeness, or usefulness of such content.
+                  </p>
+                </div>
+
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">3.3. User content transmitted through the Services</h3>
+                  
+                  <div className="mt-3 space-y-3">
+                    <div>
+                      <h4 className="text-lg text-gray-900">3.3.1. Ownership and Warranties</h4>
+                      <p className="text-gray-600 leading-relaxed">
+                        Any text, images, videos, data, or other materials that you create, upload, or generate using Anycall Services shall collectively be referred to as "User-Generated Content." You retain complete ownership of all User-Generated Content that you produce through Anycall Services, including intellectual property rights and other proprietary rights. To the extent you create derivative works, you own all rights in those derivative works as permissible under applicable law.
+                      </p>
+                      <p className="text-gray-600 leading-relaxed mt-2">
+                        With respect to the content or other materials you upload through the Services or share with other users or recipients (collectively, "User Content"), you represent and warrant that you own all rights, title, and interest in and to such User Content, including, without limitation, all copyrights and rights of publicity contained therein.
+                      </p>
+                      <p className="text-gray-600 leading-relaxed mt-2">
+                        By uploading any User Content, you hereby grant and will grant Anycall and its affiliated companies a non-exclusive, worldwide, royalty-free, fully paid-up, transferable, sublicensable, perpetual, irrevocable license to copy, display, upload, perform, distribute, store, modify, and otherwise use your User Content in connection with the operation of the Services or the promotion, advertising, or marketing thereof in any form, medium, or technology now known or later developed.
+                      </p>
+                      <p className="text-gray-600 leading-relaxed mt-2">
+                        You acknowledge and agree that any questions, comments, suggestions, ideas, feedback, or other information about the Services ("Submissions"), provided by you to Anycall, are non-confidential and Anycall will be entitled to the unrestricted use and dissemination of these Submissions for any purpose, commercial or otherwise, without acknowledgment or compensation to you. You acknowledge and agree that Anycall may preserve content and may also disclose content if required to do so by law or in the good faith belief.
+                      </p>
+                      <p className="text-gray-600 leading-relaxed mt-2">
+                        You further acknowledge that you, and not Anycall, are entirely responsible for all User Content that you upload, post, e-mail, transmit or otherwise make available through Anycall's Services, and that you and other Registered Users of Anycall, and not Anycall, are similarly responsible for all Content that you and they make available through Anycall's Services.
+                      </p>
+                      <p className="text-gray-600 leading-relaxed mt-2">
+                        For avoidance of doubt, violations of Anycall's Terms of Use unrelated to intellectual property issues does not affect the user's intellectual property rights in any characters or their generations.
+                      </p>
+                    </div>
+
+                    <div>
+                      <h4 className="text-lg text-gray-900">3.3.2. Similarity of Content</h4>
+                      <p className="text-gray-600 leading-relaxed">
+                        Due to the machine learning algorithms deployed by Anycall, the output generated by the Services may resemble outputs for other users. Such outputs are not considered your exclusive content.
+                      </p>
+                    </div>
+
+                    <div>
+                      <h4 className="text-lg text-gray-900">3.3.3. Accuracy of Output</h4>
+                      <p className="text-gray-600 leading-relaxed">
+                        The Services are based on rapidly evolving fields of AI and machine learning. You acknowledge that our outputs may contain inaccuracies or errors. You are responsible for evaluating the accuracy of outputs for your use case.
+                      </p>
+                    </div>
+                  </div>
+                </div>
+
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">3.4. Copyright complaints</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    Anycall respects the intellectual property of others, and we ask our users to do the same. If you believe that your work has been copied in a way that constitutes copyright infringement, or that your intellectual property rights have been otherwise violated, you should notify Anycall of your infringement claim in accordance with the procedure set forth below.
+                  </p>
+                  <p className="text-gray-600 leading-relaxed mt-2">
+                    We will process and investigate notices of alleged infringement and will take appropriate actions under the Digital Millennium Copyright Act ("DMCA") and other applicable intellectual property laws with respect to any alleged or actual infringement. A notification of claimed copyright infringement should be emailed to Anycall's Copyright Agent at{' '}
+                    <a href="mailto:Anycall_support@hotmail.com" className="text-gray-900 hover:underline">
+                      Anycall_support@hotmail.com
+                    </a>
+                    {' '}(Subject line: "DMCA Takedown Request"). To be effective, the notification must be in writing and contain the following information:
+                  </p>
+
+                  <div className="mt-3 space-y-2 ml-4 text-gray-600">
+                    <p>a. <strong>Electronic or Physical Signature:</strong> An electronic or physical signature of the person authorized to act on behalf of the owner of the copyright or other intellectual property interest;</p>
+                    <p>b. <strong>Proof of Copyright Ownership:</strong> A description of the copyrighted work or other intellectual property that you claim has been infringed, accompanied by proof of copyright ownership such as a copy of the copyrighted work itself or a copy of the registration form for said work.</p>
+                    <p>c. <strong>Infringing Material:</strong> A description of where the material that you claim is infringing is located on the Services, with enough detail that we may find it on the Services;</p>
+                    <p>d. <strong>Contact Details:</strong> Your address, telephone number, and email address;</p>
+                    <p>e. <strong>Good Faith Statement:</strong> A statement by you that you have a good faith belief that the disputed use is not authorized by the copyright or intellectual property owner, its agent, or the law;</p>
+                    <p>f. <strong>Accuracy Affidavit:</strong> A statement by you, made under penalty of perjury, that the above information in your Notice is accurate and that you are the copyright or intellectual property owner or authorized to act on the copyright or intellectual property owner's behalf.</p>
+                  </div>
+                </div>
+
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">3.5. Counter-notice</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    If you believe that your User Content that was removed (or to which access was disabled) is not infringing, or that you have the authorization from the copyright owner, the copyright owner's agent, or pursuant to the law, to upload and use the content in your User Content, you may send a written counter-notice containing the following information to the Copyright Agent:
+                  </p>
+
+                  <div className="mt-3 space-y-2 ml-4 text-gray-600">
+                    <p>a. <strong>Physical or Electronic Signature:</strong> Your electronic or physical signature;</p>
+                    <p>b. <strong>Identification and Proof of Ownership:</strong> Identification of the User Content that has been removed or to which access has been disabled, including the location where the content appeared before its removal or disabling. Accompany this with proof of ownership or authorization, such as a copy of the copyrighted work itself or a copy of the registration form for said work.</p>
+                    <p>c. <strong>Good Faith Statement:</strong> A statement that you have a good faith belief that the content was removed or disabled as a result of mistake or a misidentification of the content; and</p>
+                    <p>d. <strong>Contact Details:</strong> Your name, address, telephone number, and email address;</p>
+                    <p>f. <strong>Jurisdiction Consent and Acceptance of Service of Process:</strong> A statement that you consent to the jurisdiction of the federal court located within Northern District of California and a statement that you will accept service of process from the person who provided notification of the alleged infringement.</p>
+                  </div>
+
+                  <p className="text-gray-600 leading-relaxed mt-3">
+                    If a counter-notice is received by the Copyright Agent, Anycall will send a copy of the counter-notice to the original complaining party informing that person that it may replace the removed content or cease disabling it in 10 business days. Unless the copyright owner files an action seeking a court order against the content provider, member or user, the removed content may be replaced, or access to it restored, in 10 to 14 business days or more after receipt of the counter-notice, at our sole discretion.
+                  </p>
+                  <p className="text-gray-600 leading-relaxed mt-2">
+                    <strong>Repeat Infringer Policy:</strong> In accordance with the DMCA and other applicable law, Anycall has adopted a policy of terminating, in appropriate circumstances and at its sole discretion, users who are deemed to be repeat infringers. Anycall may also at its sole discretion limit access to the Services, whether or not there is any repeat infringement.
+                  </p>
+                </div>
+
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">3.6. Monitoring and Enforcement</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    Anycall may, but not obligated to: (a) remove or refuse to post any of your Content that we deem necessary or appropriate in our sole discretion, including if we believe that such Content violates this Terms of Use, infringes any intellectual property right or other right of any person or entity, or could create liability for the Company; (b) take appropriate legal action, including without limitation, referral to law enforcement, for any illegal or unauthorized use of the Services; and/or (c) terminate or suspend your access to all or part of the Services for any reason or no reason, including any violation of this Terms of Use.
+                  </p>
+                  <p className="text-gray-600 leading-relaxed mt-2">
+                    Please note that the successful uploading of Content does not constitute an endorsement by Anycall of the legality or appropriateness of said Content.
+                  </p>
+                </div>
+
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">3.7. Other Intellectual Property Rights</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    You represent and warrant that the User Content does not infringe upon the trademark, patent, trade secret or other intellectual property rights of any third party.
+                  </p>
+                  <p className="text-gray-600 leading-relaxed mt-2">
+                    If you believe that any content infringes third party rights or does not comply with these Terms, you can send us an email at{' '}
+                    <a href="mailto:Anycall_support@hotmail.com" className="text-gray-900 hover:underline">
+                      Anycall_support@hotmail.com
+                    </a>
+                    {' '}with the required documents and information under this Section.
+                  </p>
+                </div>
+              </div>
+            </section>
+
+            {/* Continue with remaining sections... */}
+            {/* Due to length, I'll create the remaining sections in a condensed format */}
+
+            <section className="space-y-4">
+              <h2 className="text-2xl sm:text-3xl text-gray-900">4. Third party websites</h2>
+              <p className="text-gray-600 leading-relaxed">
+                The Services may provide links or other access to other sites and resources on the Internet. We have no control over such sites and resources and we are not responsible for and do not endorse such sites and resources. You further acknowledge and agree that Anycall will not be responsible or liable, directly or indirectly, for any damage or loss caused or alleged to be caused by or in connection with use of or reliance on any content, events, goods or services available on or through any such site or resource. Any dealings you have with third parties found while using the Services are between you and the third party, and you agree that Anycall is not liable for any loss or claim that you may have against any such third party.
+              </p>
+            </section>
+
+            <section className="space-y-4">
+              <h2 className="text-2xl sm:text-3xl text-gray-900">5. Social networking services</h2>
+              <p className="text-gray-600 leading-relaxed">
+                You may enable, connect or log in to the Services via various online third party services, such as social media and social networking services like Facebook, Instagram or Twitter ("Social Networking Services"). By logging in or directly integrating these Social Networking Services into the Services, we make your online experiences richer and more personalized. To take advantage of this feature and capabilities, we may ask you to authenticate, register for or log into Social Networking Services on the websites of their respective providers. As part of such integration, the Social Networking Services will provide us with access to certain information that you have provided to such Social Networking Services, and we will use, store and disclose such information in accordance with our Privacy Policy. However, please remember that the manner in which Social Networking Services use, store and disclose your information is governed solely by the policies of such third parties, and Anycall shall have no liability or responsibility for the privacy practices or other actions of any third party site or service that may be enabled within the Service.
+              </p>
+              <p className="text-gray-600 leading-relaxed mt-2">
+                In addition, Anycall is not responsible for the accuracy, availability or reliability of any information, content, goods, data, opinions, advice or statements made available in connection with Social Networking Services. As such, Anycall is not liable for any damage or loss caused or alleged to be caused by or in connection with use of or reliance on any such Social Networking Services. Anycall enables these features merely as a convenience and the integration or inclusion of such features does not imply an endorsement or recommendation.
+              </p>
+            </section>
+
+            <section className="space-y-4">
+              <h2 className="text-2xl sm:text-3xl text-gray-900">6. Disclaimer of warranties</h2>
+              <p className="text-gray-600 leading-relaxed uppercase">
+                Your use of the service is at your sole risk. the service is provided on an "as is" and "as available" basis. Company expressly disclaims all warranties of any kind, whether express, implied or statutory, including, but not limited to the implied warranties of merchantability, fitness for a particular purpose, title and non-infringement.
+              </p>
+              <p className="text-gray-600 leading-relaxed mt-2">
+                Anycall makes no warranty that (i) the service will meet your requirements, (ii) the service will be uninterrupted, timely, secure, or error-free, (iii) the results that may be obtained from the use of the service will be accurate or reliable, or (iv) the quality of any products, services, information, or other material purchased or obtained by you through the service will meet your expectations.
+              </p>
+            </section>
+
+            <section className="space-y-4">
+              <h2 className="text-2xl sm:text-3xl text-gray-900">7. Limitation of liability</h2>
+              <p className="text-gray-600 leading-relaxed">
+                You expressly understand and agree that Anycall will not be liable for any indirect, incidental, special, damages for loss of profits including but not limited to, damages for loss of goodwill, use, data or other intangible losses (even if company has been advised of the possibility of such damages), whether based on contract, tort, negligence, strict liability or otherwise, resulting from: (i) the use or the inability to use the service; (ii) the cost of procurement of substitute goods and services resulting from any goods, data, information or services purchased or obtained or messages received or transactions entered into through or from the service; (iii) unauthorized access to or alteration of your transmissions or data; (iv) statements or conduct of any third party on the service; or (v) any other matter relating to the service. In no event will Anycall's total liability to you for all damages, losses or causes of action exceed the amount you have paid company in the last six (6) months, or, if greater, one hundred dollars ($100).
+              </p>
+              <p className="text-gray-600 leading-relaxed mt-2">
+                Some jurisdictions do not allow the disclaimer or exclusion of certain warranties or the limitation or exclusion of liability for incidental or consequential damages. Accordingly, some of the above limitations set forth above may not apply to you or be enforceable with respect to you. If you are dissatisfied with any portion of the services or with these terms of service, your sole and exclusive remedy is to discontinue use of the services.
+              </p>
+              <p className="text-gray-600 leading-relaxed mt-2">
+                If you are a user from New Jersey, the foregoing sections titled "Disclaimer of Warranties" and "Limitation of Liability" are intended to be only as broad as is permitted under the laws of the State of New Jersey. If any portion of these sections is held to be invalid under the laws of the State of New Jersey, the invalidity of such portion shall not affect the validity of the remaining portions of the applicable sections.
+              </p>
+            </section>
+
+            <section className="space-y-4">
+              <h2 className="text-2xl sm:text-3xl text-gray-900">8. Indemnification and Equitable Relief</h2>
+              
+              <div className="space-y-4 ml-4">
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">8.1 Indemnification</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    You agree to defend, indemnify, and hold harmless Anycall, its subsidiaries, affiliates, officers, directors, employees, agents, and licensors (collectively referred to as the "Anycall Parties") from and against any claims, actions, losses, damages, expenses, including reasonable attorneys' fees, resulting from or relating to:
+                  </p>
+                  <div className="mt-2 space-y-1 ml-4 text-gray-600">
+                    <p>a. Your use of the Anycall Services, including any user-generated content or activities under your Anycall account;</p>
+                    <p>b. Any breach of this Agreement, including violations of any applicable laws or rights of a third party; or</p>
+                    <p>c. Any disputes between you and other users of the Anycall Services.</p>
+                  </div>
+                </div>
+
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">8.2 Equitable Relief</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    You acknowledge that your violation of this Agreement may cause irreparable harm to Anycall. Therefore, Anycall shall have the right to seek injunctive or other equitable relief against you in addition to any other legal remedies available. You waive any requirement that Anycall post a bond or other security in connection with such relief.
+                  </p>
+                </div>
+              </div>
+            </section>
+
+            <section className="space-y-4">
+              <h2 className="text-2xl sm:text-3xl text-gray-900">9. Arbitration</h2>
+              <p className="text-gray-600 leading-relaxed font-semibold">
+                Please read this section carefully because it affects your rights. By agreeing to binding arbitration, you waive your right to litigate disputes through a court and to have a judge or jury decide your case. The laws of some jurisdictions do not allow mandatory arbitration provisions or class action waivers, so some or all of this section may not apply to you.
+              </p>
+              <p className="text-gray-600 leading-relaxed mt-2">
+                In order to expedite and control the cost of disputes, Anycall and you agree that any legal or equitable claim, dispute, action, or proceeding arising from or related to your use of the Services or these Terms ("Dispute") will be resolved as follows to the fullest extent permitted by applicable law. This applies to all Disputes, whether based in contract, tort, statute, fraud, misrepresentation, or any other legal theory, even if the Dispute arises after the termination of these Terms.
+              </p>
+              <p className="text-gray-600 leading-relaxed mt-2">
+                You understand and agree that you and Anycall are hereby waiving the right to a trial by jury and the right to join and participate in a class action, to the fullest extent permitted under the law.
+              </p>
+
+              <div className="mt-4 space-y-3 ml-4">
+                <div>
+                  <h4 className="text-lg text-gray-900">a. Opt-Out of Arbitration Agreement</h4>
+                  <p className="text-gray-600 leading-relaxed">
+                    If you are an individual consumer, you can opt out of arbitration within 30 days of the date that you first agreed to these Terms (including any earlier version). If you are an individual consumer and have previously agreed to arbitration, then you may opt out of any future revisions to the arbitration provision within 30 days of receiving notice of the updated arbitration provision, in which case the prior version of the arbitration provision will apply. To opt out of arbitration (or revisions to this arbitration provision), you must send your name, residence address, username, email or phone number you use for your Services account, and a clear statement that you want to opt out of this arbitration agreement (or of the revisions to it), and you must send them here:{' '}
+                    <a href="mailto:Anycall_support@hotmail.com" className="text-gray-900 hover:underline">
+                      Anycall_support@hotmail.com
+                    </a>
+                    .
+                  </p>
+                </div>
+
+                <div>
+                  <h4 className="text-lg text-gray-900">b. Notice of Dispute</h4>
+                  <p className="text-gray-600 leading-relaxed">
+                    In the event of a Dispute, you or Anycall must give the other a written statement that sets forth the name, address, and contact information of the party giving it, the facts giving rise to the Dispute, and a proposed solution (a "Notice of Dispute"). You must send any Notice of Dispute by first class U.S. Mail to Anycall at 20 EMERALD HILL ROAD, Singapore, 229302, Singapore and also via email to{' '}
+                    <a href="mailto:Anycall_support@hotmail.com" className="text-gray-900 hover:underline">
+                      Anycall_support@hotmail.com
+                    </a>
+                    . Anycall will send any Notice of Dispute to you by first class U.S. Mail to your address if Anycall has it, or otherwise to your email address. You and Anycall will attempt to resolve any Dispute through informal negotiation within 45 days from the date the Notice of Dispute is sent. After 45 days, you or Anycall may commence arbitration. An arbitrator will decide any disputes over whether this subsection has been violated, and has the power to enjoin the filing or prosecution of arbitrations. Unless prohibited by applicable law, the arbitrator will not administer any arbitration unless the requirements of this subsection have been met.
+                  </p>
+                </div>
+
+                <div>
+                  <h4 className="text-lg text-gray-900">c. Mediation, Binding Arbitration, and Governing Law</h4>
+                  <p className="text-gray-600 leading-relaxed">
+                    You and Anycall will endeavor to settle any Dispute by mediation under the Mediation Rules of Judicial Arbitration and Mediation Services, Inc. ("JAMS"). The place of mediation will be Singapore. Any Dispute which has not been resolved by mediation as provided herein within 30 days after appointment of a mediator or such time period as you or Anycall may otherwise agree, will be finally resolved by binding arbitration as described in this Section. You are giving up the right to litigate (or participate in as a party or class member) all Disputes in court before a judge or jury. Instead, all Disputes will be resolved before a neutral arbitrator, whose decision will be final except for a limited right of appeal under the Federal Arbitration Act. The arbitrator will decide all issues pertaining to arbitrability, including his or her own jurisdictional validity and enforceability of the Agreement (e.g., unconscionability). The place of arbitration will be Singapore. Any court with jurisdiction over the parties may enforce the arbitrator's award.
+                  </p>
+                </div>
+
+                <div>
+                  <h4 className="text-lg text-gray-900">d. Class Action Waiver</h4>
+                  <p className="text-gray-600 leading-relaxed">
+                    To the fullest extent permissible under applicable law, you and Anycall agree that any proceedings to resolve or litigate any dispute in any forum will be conducted solely on an individual basis, and neither you nor Anycall will seek to have any dispute heard as a class action or in any other proceeding in which either party acts or proposes to act in a representative capacity. No arbitration or proceeding will be combined with another without the prior written consent of all parties to all affected arbitrations or proceedings. Class actions and class arbitrations are not permitted; for example, you may bring a claim only on your own behalf and cannot seek relief that would affect other Services users. Nor may an arbitrator consolidate arbitrations unless all parties agree. If there is a final judicial determination that the limitations of this paragraph are unenforceable as to a particular claim or a particular request for relief (such as a request for injunctive relief), then the parties agree that such a claim or request for relief will be decided by a court after all other claims and requests for relief are arbitrated.
+                  </p>
+                </div>
+
+                {/* Additional arbitration subsections would continue here - abbreviated for space */}
+                
+                <div>
+                  <h4 className="text-lg text-gray-900">i. Enforceability</h4>
+                  <p className="text-gray-600 leading-relaxed">
+                    If the waiver of class actions above is found unenforceable, or this entire section is found unenforceable, then this entire section will be null and void. If that happens, you and Anycall agree that our Terms will be governed by and construed and interpreted in accordance with the laws of Singapore, without giving effect to conflict of law principles. If a lawsuit or court proceeding is permitted under our Terms notwithstanding this Section, you and Anycall agree that any such dispute will be litigated in the state or federal courts located in Singapore, and you and Anycall submit to the personal and exclusive jurisdiction of those courts. By using the Services, you waive any claims that may arise under the laws of other jurisdictions.
+                  </p>
+                </div>
+
+                <div>
+                  <h4 className="text-lg text-gray-900">j. Confidentiality</h4>
+                  <p className="text-gray-600 leading-relaxed">
+                    All aspects of the arbitration proceeding, and any ruling, decision, or award by the arbitrator, will be strictly confidential for the benefit of all parties.
+                  </p>
+                </div>
+              </div>
+            </section>
+
+            <section className="space-y-4">
+              <h2 className="text-2xl sm:text-3xl text-gray-900">10. User disputes</h2>
+              <p className="text-gray-600 leading-relaxed">
+                You agree that you are solely responsible for your interactions with any other user in connection with the Service and Anycall will have no liability or responsibility with respect thereto. Anycall reserves the right but has no obligation, to become involved in any way with disputes between you and any other user of the Service.
+              </p>
+            </section>
+
+            <section className="space-y-4">
+              <h2 className="text-2xl sm:text-3xl text-gray-900">11. Changes to terms of service</h2>
+              <p className="text-gray-600 leading-relaxed">
+                We may update our Terms of Service from time to time. Thus, you are advised to review this page periodically for any changes. We will notify you of any changes by posting the new Terms and Conditions on this page.
+              </p>
+            </section>
+
+            <section className="space-y-4">
+              <h2 className="text-2xl sm:text-3xl text-gray-900">12. Your privacy</h2>
+              <p className="text-gray-600 leading-relaxed">
+                At Anycall, we respect the privacy of our users. For details please see our Privacy Policy. By using the Service, you consent to our collection and use of personal data as outlined therein.
+              </p>
+            </section>
+
+            <section className="space-y-4">
+              <h2 className="text-2xl sm:text-3xl text-gray-900">13. Miscellaneous</h2>
+              
+              <div className="space-y-4 ml-4">
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">13.1. Relationship of the Parties</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    These Terms do not create a partnership, joint venture or agency relationship between you and Anycall or any of Anycall's affiliates. Anycall and you are independent contractors and neither party will have the power to bind the other or to incur obligations on the other's behalf without the other party's prior written consent.
+                  </p>
+                </div>
+
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">13.2. Entire Agreement</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    These Terms constitute the entire agreement between you and Anycall and govern your use of our Services, superseding any prior agreements between you and Anycall with respect to the Services.
+                  </p>
+                </div>
+
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">13.3. Severance</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    If any provision of these Terms is found by a court of competent jurisdiction to be invalid, the parties nevertheless agree that the court should endeavor to give effect to the parties' intentions as reflected in the provision, and the other provisions of these Terms remain in full force and effect.
+                  </p>
+                </div>
+
+                <div>
+                  <h3 className="text-xl text-gray-900 mb-2">13.4. Assignment and Delegation</h3>
+                  <p className="text-gray-600 leading-relaxed">
+                    You may not assign or delegate any rights or obligations under these Terms, including in connection with a change of control. Any purported assignment and delegation shall be null and void. We may assign these Terms in connection with a merger, acquisition or sale of all or substantially all of our assets, or to any affiliate or as part of a corporate reorganization.
+                  </p>
+                </div>
+              </div>
+            </section>
+
+            <section className="space-y-4">
+              <h2 className="text-2xl sm:text-3xl text-gray-900">14. Language</h2>
+              <p className="text-gray-600 leading-relaxed">
+                These Terms of Service may be provided in multiple languages for convenience and informational purposes. However, in the event of any discrepancies or conflicts between the translations, the English version shall prevail and control.
+              </p>
+            </section>
+
+            {/* Contact Section */}
+            <section className="space-y-4 pt-8 border-t border-gray-200">
+              <h2 className="text-2xl sm:text-3xl text-gray-900">Contact Us</h2>
+              <p className="text-gray-600 leading-relaxed">
+                If you have any questions or suggestions about our Terms and Conditions, do not hesitate to contact us at{' '}
+                <a href="mailto:Anycall_support@hotmail.com" className="text-gray-900 hover:underline">
+                  Anycall_support@hotmail.com
+                </a>
+                .
+              </p>
+            </section>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+}

+ 66 - 0
src/components/ui/accordion.tsx

@@ -0,0 +1,66 @@
+"use client";
+
+import * as React from "react";
+import * as AccordionPrimitive from "@radix-ui/react-accordion@1.2.3";
+import { ChevronDownIcon } from "lucide-react@0.487.0";
+
+import { cn } from "./utils";
+
+function Accordion({
+  ...props
+}: React.ComponentProps<typeof AccordionPrimitive.Root>) {
+  return <AccordionPrimitive.Root data-slot="accordion" {...props} />;
+}
+
+function AccordionItem({
+  className,
+  ...props
+}: React.ComponentProps<typeof AccordionPrimitive.Item>) {
+  return (
+    <AccordionPrimitive.Item
+      data-slot="accordion-item"
+      className={cn("border-b last:border-b-0", className)}
+      {...props}
+    />
+  );
+}
+
+function AccordionTrigger({
+  className,
+  children,
+  ...props
+}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
+  return (
+    <AccordionPrimitive.Header className="flex">
+      <AccordionPrimitive.Trigger
+        data-slot="accordion-trigger"
+        className={cn(
+          "focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
+          className,
+        )}
+        {...props}
+      >
+        {children}
+        <ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
+      </AccordionPrimitive.Trigger>
+    </AccordionPrimitive.Header>
+  );
+}
+
+function AccordionContent({
+  className,
+  children,
+  ...props
+}: React.ComponentProps<typeof AccordionPrimitive.Content>) {
+  return (
+    <AccordionPrimitive.Content
+      data-slot="accordion-content"
+      className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
+      {...props}
+    >
+      <div className={cn("pt-0 pb-4", className)}>{children}</div>
+    </AccordionPrimitive.Content>
+  );
+}
+
+export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };

+ 157 - 0
src/components/ui/alert-dialog.tsx

@@ -0,0 +1,157 @@
+"use client";
+
+import * as React from "react";
+import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog@1.1.6";
+
+import { cn } from "./utils";
+import { buttonVariants } from "./button";
+
+function AlertDialog({
+  ...props
+}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
+  return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />;
+}
+
+function AlertDialogTrigger({
+  ...props
+}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
+  return (
+    <AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
+  );
+}
+
+function AlertDialogPortal({
+  ...props
+}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
+  return (
+    <AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
+  );
+}
+
+function AlertDialogOverlay({
+  className,
+  ...props
+}: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
+  return (
+    <AlertDialogPrimitive.Overlay
+      data-slot="alert-dialog-overlay"
+      className={cn(
+        "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function AlertDialogContent({
+  className,
+  ...props
+}: React.ComponentProps<typeof AlertDialogPrimitive.Content>) {
+  return (
+    <AlertDialogPortal>
+      <AlertDialogOverlay />
+      <AlertDialogPrimitive.Content
+        data-slot="alert-dialog-content"
+        className={cn(
+          "bg-background 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 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
+          className,
+        )}
+        {...props}
+      />
+    </AlertDialogPortal>
+  );
+}
+
+function AlertDialogHeader({
+  className,
+  ...props
+}: React.ComponentProps<"div">) {
+  return (
+    <div
+      data-slot="alert-dialog-header"
+      className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
+      {...props}
+    />
+  );
+}
+
+function AlertDialogFooter({
+  className,
+  ...props
+}: React.ComponentProps<"div">) {
+  return (
+    <div
+      data-slot="alert-dialog-footer"
+      className={cn(
+        "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function AlertDialogTitle({
+  className,
+  ...props
+}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
+  return (
+    <AlertDialogPrimitive.Title
+      data-slot="alert-dialog-title"
+      className={cn("text-lg font-semibold", className)}
+      {...props}
+    />
+  );
+}
+
+function AlertDialogDescription({
+  className,
+  ...props
+}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
+  return (
+    <AlertDialogPrimitive.Description
+      data-slot="alert-dialog-description"
+      className={cn("text-muted-foreground text-sm", className)}
+      {...props}
+    />
+  );
+}
+
+function AlertDialogAction({
+  className,
+  ...props
+}: React.ComponentProps<typeof AlertDialogPrimitive.Action>) {
+  return (
+    <AlertDialogPrimitive.Action
+      className={cn(buttonVariants(), className)}
+      {...props}
+    />
+  );
+}
+
+function AlertDialogCancel({
+  className,
+  ...props
+}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) {
+  return (
+    <AlertDialogPrimitive.Cancel
+      className={cn(buttonVariants({ variant: "outline" }), className)}
+      {...props}
+    />
+  );
+}
+
+export {
+  AlertDialog,
+  AlertDialogPortal,
+  AlertDialogOverlay,
+  AlertDialogTrigger,
+  AlertDialogContent,
+  AlertDialogHeader,
+  AlertDialogFooter,
+  AlertDialogTitle,
+  AlertDialogDescription,
+  AlertDialogAction,
+  AlertDialogCancel,
+};

+ 66 - 0
src/components/ui/alert.tsx

@@ -0,0 +1,66 @@
+import * as React from "react";
+import { cva, type VariantProps } from "class-variance-authority@0.7.1";
+
+import { cn } from "./utils";
+
+const alertVariants = cva(
+  "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
+  {
+    variants: {
+      variant: {
+        default: "bg-card text-card-foreground",
+        destructive:
+          "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
+      },
+    },
+    defaultVariants: {
+      variant: "default",
+    },
+  },
+);
+
+function Alert({
+  className,
+  variant,
+  ...props
+}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
+  return (
+    <div
+      data-slot="alert"
+      role="alert"
+      className={cn(alertVariants({ variant }), className)}
+      {...props}
+    />
+  );
+}
+
+function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
+  return (
+    <div
+      data-slot="alert-title"
+      className={cn(
+        "col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function AlertDescription({
+  className,
+  ...props
+}: React.ComponentProps<"div">) {
+  return (
+    <div
+      data-slot="alert-description"
+      className={cn(
+        "text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+export { Alert, AlertTitle, AlertDescription };

+ 11 - 0
src/components/ui/aspect-ratio.tsx

@@ -0,0 +1,11 @@
+"use client";
+
+import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio@1.1.2";
+
+function AspectRatio({
+  ...props
+}: React.ComponentProps<typeof AspectRatioPrimitive.Root>) {
+  return <AspectRatioPrimitive.Root data-slot="aspect-ratio" {...props} />;
+}
+
+export { AspectRatio };

+ 53 - 0
src/components/ui/avatar.tsx

@@ -0,0 +1,53 @@
+"use client";
+
+import * as React from "react";
+import * as AvatarPrimitive from "@radix-ui/react-avatar@1.1.3";
+
+import { cn } from "./utils";
+
+function Avatar({
+  className,
+  ...props
+}: React.ComponentProps<typeof AvatarPrimitive.Root>) {
+  return (
+    <AvatarPrimitive.Root
+      data-slot="avatar"
+      className={cn(
+        "relative flex size-10 shrink-0 overflow-hidden rounded-full",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function AvatarImage({
+  className,
+  ...props
+}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
+  return (
+    <AvatarPrimitive.Image
+      data-slot="avatar-image"
+      className={cn("aspect-square size-full", className)}
+      {...props}
+    />
+  );
+}
+
+function AvatarFallback({
+  className,
+  ...props
+}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
+  return (
+    <AvatarPrimitive.Fallback
+      data-slot="avatar-fallback"
+      className={cn(
+        "bg-muted flex size-full items-center justify-center rounded-full",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+export { Avatar, AvatarImage, AvatarFallback };

+ 46 - 0
src/components/ui/badge.tsx

@@ -0,0 +1,46 @@
+import * as React from "react";
+import { Slot } from "@radix-ui/react-slot@1.1.2";
+import { cva, type VariantProps } from "class-variance-authority@0.7.1";
+
+import { cn } from "./utils";
+
+const badgeVariants = cva(
+  "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
+  {
+    variants: {
+      variant: {
+        default:
+          "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
+        secondary:
+          "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
+        destructive:
+          "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
+        outline:
+          "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
+      },
+    },
+    defaultVariants: {
+      variant: "default",
+    },
+  },
+);
+
+function Badge({
+  className,
+  variant,
+  asChild = false,
+  ...props
+}: React.ComponentProps<"span"> &
+  VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
+  const Comp = asChild ? Slot : "span";
+
+  return (
+    <Comp
+      data-slot="badge"
+      className={cn(badgeVariants({ variant }), className)}
+      {...props}
+    />
+  );
+}
+
+export { Badge, badgeVariants };

+ 109 - 0
src/components/ui/breadcrumb.tsx

@@ -0,0 +1,109 @@
+import * as React from "react";
+import { Slot } from "@radix-ui/react-slot@1.1.2";
+import { ChevronRight, MoreHorizontal } from "lucide-react@0.487.0";
+
+import { cn } from "./utils";
+
+function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
+  return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />;
+}
+
+function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
+  return (
+    <ol
+      data-slot="breadcrumb-list"
+      className={cn(
+        "text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
+  return (
+    <li
+      data-slot="breadcrumb-item"
+      className={cn("inline-flex items-center gap-1.5", className)}
+      {...props}
+    />
+  );
+}
+
+function BreadcrumbLink({
+  asChild,
+  className,
+  ...props
+}: React.ComponentProps<"a"> & {
+  asChild?: boolean;
+}) {
+  const Comp = asChild ? Slot : "a";
+
+  return (
+    <Comp
+      data-slot="breadcrumb-link"
+      className={cn("hover:text-foreground transition-colors", className)}
+      {...props}
+    />
+  );
+}
+
+function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
+  return (
+    <span
+      data-slot="breadcrumb-page"
+      role="link"
+      aria-disabled="true"
+      aria-current="page"
+      className={cn("text-foreground font-normal", className)}
+      {...props}
+    />
+  );
+}
+
+function BreadcrumbSeparator({
+  children,
+  className,
+  ...props
+}: React.ComponentProps<"li">) {
+  return (
+    <li
+      data-slot="breadcrumb-separator"
+      role="presentation"
+      aria-hidden="true"
+      className={cn("[&>svg]:size-3.5", className)}
+      {...props}
+    >
+      {children ?? <ChevronRight />}
+    </li>
+  );
+}
+
+function BreadcrumbEllipsis({
+  className,
+  ...props
+}: React.ComponentProps<"span">) {
+  return (
+    <span
+      data-slot="breadcrumb-ellipsis"
+      role="presentation"
+      aria-hidden="true"
+      className={cn("flex size-9 items-center justify-center", className)}
+      {...props}
+    >
+      <MoreHorizontal className="size-4" />
+      <span className="sr-only">More</span>
+    </span>
+  );
+}
+
+export {
+  Breadcrumb,
+  BreadcrumbList,
+  BreadcrumbItem,
+  BreadcrumbLink,
+  BreadcrumbPage,
+  BreadcrumbSeparator,
+  BreadcrumbEllipsis,
+};

+ 58 - 0
src/components/ui/button.tsx

@@ -0,0 +1,58 @@
+import * as React from "react";
+import { Slot } from "@radix-ui/react-slot@1.1.2";
+import { cva, type VariantProps } from "class-variance-authority@0.7.1";
+
+import { cn } from "./utils";
+
+const buttonVariants = cva(
+  "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
+  {
+    variants: {
+      variant: {
+        default: "bg-primary text-primary-foreground hover:bg-primary/90",
+        destructive:
+          "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
+        outline:
+          "border bg-background text-foreground hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
+        secondary:
+          "bg-secondary text-secondary-foreground hover:bg-secondary/80",
+        ghost:
+          "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
+        link: "text-primary underline-offset-4 hover:underline",
+      },
+      size: {
+        default: "h-9 px-4 py-2 has-[>svg]:px-3",
+        sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
+        lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
+        icon: "size-9 rounded-md",
+      },
+    },
+    defaultVariants: {
+      variant: "default",
+      size: "default",
+    },
+  },
+);
+
+function Button({
+  className,
+  variant,
+  size,
+  asChild = false,
+  ...props
+}: React.ComponentProps<"button"> &
+  VariantProps<typeof buttonVariants> & {
+    asChild?: boolean;
+  }) {
+  const Comp = asChild ? Slot : "button";
+
+  return (
+    <Comp
+      data-slot="button"
+      className={cn(buttonVariants({ variant, size, className }))}
+      {...props}
+    />
+  );
+}
+
+export { Button, buttonVariants };

+ 75 - 0
src/components/ui/calendar.tsx

@@ -0,0 +1,75 @@
+"use client";
+
+import * as React from "react";
+import { ChevronLeft, ChevronRight } from "lucide-react@0.487.0";
+import { DayPicker } from "react-day-picker@8.10.1";
+
+import { cn } from "./utils";
+import { buttonVariants } from "./button";
+
+function Calendar({
+  className,
+  classNames,
+  showOutsideDays = true,
+  ...props
+}: React.ComponentProps<typeof DayPicker>) {
+  return (
+    <DayPicker
+      showOutsideDays={showOutsideDays}
+      className={cn("p-3", className)}
+      classNames={{
+        months: "flex flex-col sm:flex-row gap-2",
+        month: "flex flex-col gap-4",
+        caption: "flex justify-center pt-1 relative items-center w-full",
+        caption_label: "text-sm font-medium",
+        nav: "flex items-center gap-1",
+        nav_button: cn(
+          buttonVariants({ variant: "outline" }),
+          "size-7 bg-transparent p-0 opacity-50 hover:opacity-100",
+        ),
+        nav_button_previous: "absolute left-1",
+        nav_button_next: "absolute right-1",
+        table: "w-full border-collapse space-x-1",
+        head_row: "flex",
+        head_cell:
+          "text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]",
+        row: "flex w-full mt-2",
+        cell: cn(
+          "relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-range-end)]:rounded-r-md",
+          props.mode === "range"
+            ? "[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md"
+            : "[&:has([aria-selected])]:rounded-md",
+        ),
+        day: cn(
+          buttonVariants({ variant: "ghost" }),
+          "size-8 p-0 font-normal aria-selected:opacity-100",
+        ),
+        day_range_start:
+          "day-range-start aria-selected:bg-primary aria-selected:text-primary-foreground",
+        day_range_end:
+          "day-range-end aria-selected:bg-primary aria-selected:text-primary-foreground",
+        day_selected:
+          "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
+        day_today: "bg-accent text-accent-foreground",
+        day_outside:
+          "day-outside text-muted-foreground aria-selected:text-muted-foreground",
+        day_disabled: "text-muted-foreground opacity-50",
+        day_range_middle:
+          "aria-selected:bg-accent aria-selected:text-accent-foreground",
+        day_hidden: "invisible",
+        ...classNames,
+      }}
+      components={{
+        IconLeft: ({ className, ...props }) => (
+          <ChevronLeft className={cn("size-4", className)} {...props} />
+        ),
+        IconRight: ({ className, ...props }) => (
+          <ChevronRight className={cn("size-4", className)} {...props} />
+        ),
+      }}
+      {...props}
+    />
+  );
+}
+
+export { Calendar };

+ 92 - 0
src/components/ui/card.tsx

@@ -0,0 +1,92 @@
+import * as React from "react";
+
+import { cn } from "./utils";
+
+function Card({ className, ...props }: React.ComponentProps<"div">) {
+  return (
+    <div
+      data-slot="card"
+      className={cn(
+        "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
+  return (
+    <div
+      data-slot="card-header"
+      className={cn(
+        "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 pt-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
+  return (
+    <h4
+      data-slot="card-title"
+      className={cn("leading-none", className)}
+      {...props}
+    />
+  );
+}
+
+function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
+  return (
+    <p
+      data-slot="card-description"
+      className={cn("text-muted-foreground", className)}
+      {...props}
+    />
+  );
+}
+
+function CardAction({ className, ...props }: React.ComponentProps<"div">) {
+  return (
+    <div
+      data-slot="card-action"
+      className={cn(
+        "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function CardContent({ className, ...props }: React.ComponentProps<"div">) {
+  return (
+    <div
+      data-slot="card-content"
+      className={cn("px-6 [&:last-child]:pb-6", className)}
+      {...props}
+    />
+  );
+}
+
+function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
+  return (
+    <div
+      data-slot="card-footer"
+      className={cn("flex items-center px-6 pb-6 [.border-t]:pt-6", className)}
+      {...props}
+    />
+  );
+}
+
+export {
+  Card,
+  CardHeader,
+  CardFooter,
+  CardTitle,
+  CardAction,
+  CardDescription,
+  CardContent,
+};

+ 241 - 0
src/components/ui/carousel.tsx

@@ -0,0 +1,241 @@
+"use client";
+
+import * as React from "react";
+import useEmblaCarousel, {
+  type UseEmblaCarouselType,
+} from "embla-carousel-react@8.6.0";
+import { ArrowLeft, ArrowRight } from "lucide-react@0.487.0";
+
+import { cn } from "./utils";
+import { Button } from "./button";
+
+type CarouselApi = UseEmblaCarouselType[1];
+type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
+type CarouselOptions = UseCarouselParameters[0];
+type CarouselPlugin = UseCarouselParameters[1];
+
+type CarouselProps = {
+  opts?: CarouselOptions;
+  plugins?: CarouselPlugin;
+  orientation?: "horizontal" | "vertical";
+  setApi?: (api: CarouselApi) => void;
+};
+
+type CarouselContextProps = {
+  carouselRef: ReturnType<typeof useEmblaCarousel>[0];
+  api: ReturnType<typeof useEmblaCarousel>[1];
+  scrollPrev: () => void;
+  scrollNext: () => void;
+  canScrollPrev: boolean;
+  canScrollNext: boolean;
+} & CarouselProps;
+
+const CarouselContext = React.createContext<CarouselContextProps | null>(null);
+
+function useCarousel() {
+  const context = React.useContext(CarouselContext);
+
+  if (!context) {
+    throw new Error("useCarousel must be used within a <Carousel />");
+  }
+
+  return context;
+}
+
+function Carousel({
+  orientation = "horizontal",
+  opts,
+  setApi,
+  plugins,
+  className,
+  children,
+  ...props
+}: React.ComponentProps<"div"> & CarouselProps) {
+  const [carouselRef, api] = useEmblaCarousel(
+    {
+      ...opts,
+      axis: orientation === "horizontal" ? "x" : "y",
+    },
+    plugins,
+  );
+  const [canScrollPrev, setCanScrollPrev] = React.useState(false);
+  const [canScrollNext, setCanScrollNext] = React.useState(false);
+
+  const onSelect = React.useCallback((api: CarouselApi) => {
+    if (!api) return;
+    setCanScrollPrev(api.canScrollPrev());
+    setCanScrollNext(api.canScrollNext());
+  }, []);
+
+  const scrollPrev = React.useCallback(() => {
+    api?.scrollPrev();
+  }, [api]);
+
+  const scrollNext = React.useCallback(() => {
+    api?.scrollNext();
+  }, [api]);
+
+  const handleKeyDown = React.useCallback(
+    (event: React.KeyboardEvent<HTMLDivElement>) => {
+      if (event.key === "ArrowLeft") {
+        event.preventDefault();
+        scrollPrev();
+      } else if (event.key === "ArrowRight") {
+        event.preventDefault();
+        scrollNext();
+      }
+    },
+    [scrollPrev, scrollNext],
+  );
+
+  React.useEffect(() => {
+    if (!api || !setApi) return;
+    setApi(api);
+  }, [api, setApi]);
+
+  React.useEffect(() => {
+    if (!api) return;
+    onSelect(api);
+    api.on("reInit", onSelect);
+    api.on("select", onSelect);
+
+    return () => {
+      api?.off("select", onSelect);
+    };
+  }, [api, onSelect]);
+
+  return (
+    <CarouselContext.Provider
+      value={{
+        carouselRef,
+        api: api,
+        opts,
+        orientation:
+          orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
+        scrollPrev,
+        scrollNext,
+        canScrollPrev,
+        canScrollNext,
+      }}
+    >
+      <div
+        onKeyDownCapture={handleKeyDown}
+        className={cn("relative", className)}
+        role="region"
+        aria-roledescription="carousel"
+        data-slot="carousel"
+        {...props}
+      >
+        {children}
+      </div>
+    </CarouselContext.Provider>
+  );
+}
+
+function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
+  const { carouselRef, orientation } = useCarousel();
+
+  return (
+    <div
+      ref={carouselRef}
+      className="overflow-hidden"
+      data-slot="carousel-content"
+    >
+      <div
+        className={cn(
+          "flex",
+          orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
+          className,
+        )}
+        {...props}
+      />
+    </div>
+  );
+}
+
+function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
+  const { orientation } = useCarousel();
+
+  return (
+    <div
+      role="group"
+      aria-roledescription="slide"
+      data-slot="carousel-item"
+      className={cn(
+        "min-w-0 shrink-0 grow-0 basis-full",
+        orientation === "horizontal" ? "pl-4" : "pt-4",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function CarouselPrevious({
+  className,
+  variant = "outline",
+  size = "icon",
+  ...props
+}: React.ComponentProps<typeof Button>) {
+  const { orientation, scrollPrev, canScrollPrev } = useCarousel();
+
+  return (
+    <Button
+      data-slot="carousel-previous"
+      variant={variant}
+      size={size}
+      className={cn(
+        "absolute size-8 rounded-full",
+        orientation === "horizontal"
+          ? "top-1/2 -left-12 -translate-y-1/2"
+          : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
+        className,
+      )}
+      disabled={!canScrollPrev}
+      onClick={scrollPrev}
+      {...props}
+    >
+      <ArrowLeft />
+      <span className="sr-only">Previous slide</span>
+    </Button>
+  );
+}
+
+function CarouselNext({
+  className,
+  variant = "outline",
+  size = "icon",
+  ...props
+}: React.ComponentProps<typeof Button>) {
+  const { orientation, scrollNext, canScrollNext } = useCarousel();
+
+  return (
+    <Button
+      data-slot="carousel-next"
+      variant={variant}
+      size={size}
+      className={cn(
+        "absolute size-8 rounded-full",
+        orientation === "horizontal"
+          ? "top-1/2 -right-12 -translate-y-1/2"
+          : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
+        className,
+      )}
+      disabled={!canScrollNext}
+      onClick={scrollNext}
+      {...props}
+    >
+      <ArrowRight />
+      <span className="sr-only">Next slide</span>
+    </Button>
+  );
+}
+
+export {
+  type CarouselApi,
+  Carousel,
+  CarouselContent,
+  CarouselItem,
+  CarouselPrevious,
+  CarouselNext,
+};

+ 353 - 0
src/components/ui/chart.tsx

@@ -0,0 +1,353 @@
+"use client";
+
+import * as React from "react";
+import * as RechartsPrimitive from "recharts@2.15.2";
+
+import { cn } from "./utils";
+
+// Format: { THEME_NAME: CSS_SELECTOR }
+const THEMES = { light: "", dark: ".dark" } as const;
+
+export type ChartConfig = {
+  [k in string]: {
+    label?: React.ReactNode;
+    icon?: React.ComponentType;
+  } & (
+    | { color?: string; theme?: never }
+    | { color?: never; theme: Record<keyof typeof THEMES, string> }
+  );
+};
+
+type ChartContextProps = {
+  config: ChartConfig;
+};
+
+const ChartContext = React.createContext<ChartContextProps | null>(null);
+
+function useChart() {
+  const context = React.useContext(ChartContext);
+
+  if (!context) {
+    throw new Error("useChart must be used within a <ChartContainer />");
+  }
+
+  return context;
+}
+
+function ChartContainer({
+  id,
+  className,
+  children,
+  config,
+  ...props
+}: React.ComponentProps<"div"> & {
+  config: ChartConfig;
+  children: React.ComponentProps<
+    typeof RechartsPrimitive.ResponsiveContainer
+  >["children"];
+}) {
+  const uniqueId = React.useId();
+  const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`;
+
+  return (
+    <ChartContext.Provider value={{ config }}>
+      <div
+        data-slot="chart"
+        data-chart={chartId}
+        className={cn(
+          "[&_.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-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 flex aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
+          className,
+        )}
+        {...props}
+      >
+        <ChartStyle id={chartId} config={config} />
+        <RechartsPrimitive.ResponsiveContainer>
+          {children}
+        </RechartsPrimitive.ResponsiveContainer>
+      </div>
+    </ChartContext.Provider>
+  );
+}
+
+const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
+  const colorConfig = Object.entries(config).filter(
+    ([, config]) => config.theme || config.color,
+  );
+
+  if (!colorConfig.length) {
+    return null;
+  }
+
+  return (
+    <style
+      dangerouslySetInnerHTML={{
+        __html: Object.entries(THEMES)
+          .map(
+            ([theme, prefix]) => `
+${prefix} [data-chart=${id}] {
+${colorConfig
+  .map(([key, itemConfig]) => {
+    const color =
+      itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
+      itemConfig.color;
+    return color ? `  --color-${key}: ${color};` : null;
+  })
+  .join("\n")}
+}
+`,
+          )
+          .join("\n"),
+      }}
+    />
+  );
+};
+
+const ChartTooltip = RechartsPrimitive.Tooltip;
+
+function ChartTooltipContent({
+  active,
+  payload,
+  className,
+  indicator = "dot",
+  hideLabel = false,
+  hideIndicator = false,
+  label,
+  labelFormatter,
+  labelClassName,
+  formatter,
+  color,
+  nameKey,
+  labelKey,
+}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
+  React.ComponentProps<"div"> & {
+    hideLabel?: boolean;
+    hideIndicator?: boolean;
+    indicator?: "line" | "dot" | "dashed";
+    nameKey?: string;
+    labelKey?: string;
+  }) {
+  const { config } = useChart();
+
+  const tooltipLabel = React.useMemo(() => {
+    if (hideLabel || !payload?.length) {
+      return null;
+    }
+
+    const [item] = payload;
+    const key = `${labelKey || item?.dataKey || item?.name || "value"}`;
+    const itemConfig = getPayloadConfigFromPayload(config, item, key);
+    const value =
+      !labelKey && typeof label === "string"
+        ? config[label as keyof typeof config]?.label || label
+        : itemConfig?.label;
+
+    if (labelFormatter) {
+      return (
+        <div className={cn("font-medium", labelClassName)}>
+          {labelFormatter(value, payload)}
+        </div>
+      );
+    }
+
+    if (!value) {
+      return null;
+    }
+
+    return <div className={cn("font-medium", labelClassName)}>{value}</div>;
+  }, [
+    label,
+    labelFormatter,
+    payload,
+    hideLabel,
+    labelClassName,
+    config,
+    labelKey,
+  ]);
+
+  if (!active || !payload?.length) {
+    return null;
+  }
+
+  const nestLabel = payload.length === 1 && indicator !== "dot";
+
+  return (
+    <div
+      className={cn(
+        "border-border/50 bg-background grid min-w-[8rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl",
+        className,
+      )}
+    >
+      {!nestLabel ? tooltipLabel : null}
+      <div className="grid gap-1.5">
+        {payload.map((item, index) => {
+          const key = `${nameKey || item.name || item.dataKey || "value"}`;
+          const itemConfig = getPayloadConfigFromPayload(config, item, key);
+          const indicatorColor = color || item.payload.fill || item.color;
+
+          return (
+            <div
+              key={item.dataKey}
+              className={cn(
+                "[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5",
+                indicator === "dot" && "items-center",
+              )}
+            >
+              {formatter && item?.value !== undefined && item.name ? (
+                formatter(item.value, item.name, item, index, item.payload)
+              ) : (
+                <>
+                  {itemConfig?.icon ? (
+                    <itemConfig.icon />
+                  ) : (
+                    !hideIndicator && (
+                      <div
+                        className={cn(
+                          "shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)",
+                          {
+                            "h-2.5 w-2.5": indicator === "dot",
+                            "w-1": indicator === "line",
+                            "w-0 border-[1.5px] border-dashed bg-transparent":
+                              indicator === "dashed",
+                            "my-0.5": nestLabel && indicator === "dashed",
+                          },
+                        )}
+                        style={
+                          {
+                            "--color-bg": indicatorColor,
+                            "--color-border": indicatorColor,
+                          } as React.CSSProperties
+                        }
+                      />
+                    )
+                  )}
+                  <div
+                    className={cn(
+                      "flex flex-1 justify-between leading-none",
+                      nestLabel ? "items-end" : "items-center",
+                    )}
+                  >
+                    <div className="grid gap-1.5">
+                      {nestLabel ? tooltipLabel : null}
+                      <span className="text-muted-foreground">
+                        {itemConfig?.label || item.name}
+                      </span>
+                    </div>
+                    {item.value && (
+                      <span className="text-foreground font-mono font-medium tabular-nums">
+                        {item.value.toLocaleString()}
+                      </span>
+                    )}
+                  </div>
+                </>
+              )}
+            </div>
+          );
+        })}
+      </div>
+    </div>
+  );
+}
+
+const ChartLegend = RechartsPrimitive.Legend;
+
+function ChartLegendContent({
+  className,
+  hideIcon = false,
+  payload,
+  verticalAlign = "bottom",
+  nameKey,
+}: React.ComponentProps<"div"> &
+  Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
+    hideIcon?: boolean;
+    nameKey?: string;
+  }) {
+  const { config } = useChart();
+
+  if (!payload?.length) {
+    return null;
+  }
+
+  return (
+    <div
+      className={cn(
+        "flex items-center justify-center gap-4",
+        verticalAlign === "top" ? "pb-3" : "pt-3",
+        className,
+      )}
+    >
+      {payload.map((item) => {
+        const key = `${nameKey || item.dataKey || "value"}`;
+        const itemConfig = getPayloadConfigFromPayload(config, item, key);
+
+        return (
+          <div
+            key={item.value}
+            className={cn(
+              "[&>svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3",
+            )}
+          >
+            {itemConfig?.icon && !hideIcon ? (
+              <itemConfig.icon />
+            ) : (
+              <div
+                className="h-2 w-2 shrink-0 rounded-[2px]"
+                style={{
+                  backgroundColor: item.color,
+                }}
+              />
+            )}
+            {itemConfig?.label}
+          </div>
+        );
+      })}
+    </div>
+  );
+}
+
+// Helper to extract item config from a payload.
+function getPayloadConfigFromPayload(
+  config: ChartConfig,
+  payload: unknown,
+  key: string,
+) {
+  if (typeof payload !== "object" || payload === null) {
+    return undefined;
+  }
+
+  const payloadPayload =
+    "payload" in payload &&
+    typeof payload.payload === "object" &&
+    payload.payload !== null
+      ? payload.payload
+      : undefined;
+
+  let configLabelKey: string = key;
+
+  if (
+    key in payload &&
+    typeof payload[key as keyof typeof payload] === "string"
+  ) {
+    configLabelKey = payload[key as keyof typeof payload] as string;
+  } else if (
+    payloadPayload &&
+    key in payloadPayload &&
+    typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
+  ) {
+    configLabelKey = payloadPayload[
+      key as keyof typeof payloadPayload
+    ] as string;
+  }
+
+  return configLabelKey in config
+    ? config[configLabelKey]
+    : config[key as keyof typeof config];
+}
+
+export {
+  ChartContainer,
+  ChartTooltip,
+  ChartTooltipContent,
+  ChartLegend,
+  ChartLegendContent,
+  ChartStyle,
+};

+ 32 - 0
src/components/ui/checkbox.tsx

@@ -0,0 +1,32 @@
+"use client";
+
+import * as React from "react";
+import * as CheckboxPrimitive from "@radix-ui/react-checkbox@1.1.4";
+import { CheckIcon } from "lucide-react@0.487.0";
+
+import { cn } from "./utils";
+
+function Checkbox({
+  className,
+  ...props
+}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
+  return (
+    <CheckboxPrimitive.Root
+      data-slot="checkbox"
+      className={cn(
+        "peer border bg-input-background dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
+        className,
+      )}
+      {...props}
+    >
+      <CheckboxPrimitive.Indicator
+        data-slot="checkbox-indicator"
+        className="flex items-center justify-center text-current transition-none"
+      >
+        <CheckIcon className="size-3.5" />
+      </CheckboxPrimitive.Indicator>
+    </CheckboxPrimitive.Root>
+  );
+}
+
+export { Checkbox };

+ 33 - 0
src/components/ui/collapsible.tsx

@@ -0,0 +1,33 @@
+"use client";
+
+import * as CollapsiblePrimitive from "@radix-ui/react-collapsible@1.1.3";
+
+function Collapsible({
+  ...props
+}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
+  return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />;
+}
+
+function CollapsibleTrigger({
+  ...props
+}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {
+  return (
+    <CollapsiblePrimitive.CollapsibleTrigger
+      data-slot="collapsible-trigger"
+      {...props}
+    />
+  );
+}
+
+function CollapsibleContent({
+  ...props
+}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
+  return (
+    <CollapsiblePrimitive.CollapsibleContent
+      data-slot="collapsible-content"
+      {...props}
+    />
+  );
+}
+
+export { Collapsible, CollapsibleTrigger, CollapsibleContent };

+ 177 - 0
src/components/ui/command.tsx

@@ -0,0 +1,177 @@
+"use client";
+
+import * as React from "react";
+import { Command as CommandPrimitive } from "cmdk@1.1.1";
+import { SearchIcon } from "lucide-react@0.487.0";
+
+import { cn } from "./utils";
+import {
+  Dialog,
+  DialogContent,
+  DialogDescription,
+  DialogHeader,
+  DialogTitle,
+} from "./dialog";
+
+function Command({
+  className,
+  ...props
+}: React.ComponentProps<typeof CommandPrimitive>) {
+  return (
+    <CommandPrimitive
+      data-slot="command"
+      className={cn(
+        "bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function CommandDialog({
+  title = "Command Palette",
+  description = "Search for a command to run...",
+  children,
+  ...props
+}: React.ComponentProps<typeof Dialog> & {
+  title?: string;
+  description?: string;
+}) {
+  return (
+    <Dialog {...props}>
+      <DialogHeader className="sr-only">
+        <DialogTitle>{title}</DialogTitle>
+        <DialogDescription>{description}</DialogDescription>
+      </DialogHeader>
+      <DialogContent className="overflow-hidden p-0">
+        <Command className="[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[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">
+          {children}
+        </Command>
+      </DialogContent>
+    </Dialog>
+  );
+}
+
+function CommandInput({
+  className,
+  ...props
+}: React.ComponentProps<typeof CommandPrimitive.Input>) {
+  return (
+    <div
+      data-slot="command-input-wrapper"
+      className="flex h-9 items-center gap-2 border-b px-3"
+    >
+      <SearchIcon className="size-4 shrink-0 opacity-50" />
+      <CommandPrimitive.Input
+        data-slot="command-input"
+        className={cn(
+          "placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
+          className,
+        )}
+        {...props}
+      />
+    </div>
+  );
+}
+
+function CommandList({
+  className,
+  ...props
+}: React.ComponentProps<typeof CommandPrimitive.List>) {
+  return (
+    <CommandPrimitive.List
+      data-slot="command-list"
+      className={cn(
+        "max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function CommandEmpty({
+  ...props
+}: React.ComponentProps<typeof CommandPrimitive.Empty>) {
+  return (
+    <CommandPrimitive.Empty
+      data-slot="command-empty"
+      className="py-6 text-center text-sm"
+      {...props}
+    />
+  );
+}
+
+function CommandGroup({
+  className,
+  ...props
+}: React.ComponentProps<typeof CommandPrimitive.Group>) {
+  return (
+    <CommandPrimitive.Group
+      data-slot="command-group"
+      className={cn(
+        "text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function CommandSeparator({
+  className,
+  ...props
+}: React.ComponentProps<typeof CommandPrimitive.Separator>) {
+  return (
+    <CommandPrimitive.Separator
+      data-slot="command-separator"
+      className={cn("bg-border -mx-1 h-px", className)}
+      {...props}
+    />
+  );
+}
+
+function CommandItem({
+  className,
+  ...props
+}: React.ComponentProps<typeof CommandPrimitive.Item>) {
+  return (
+    <CommandPrimitive.Item
+      data-slot="command-item"
+      className={cn(
+        "data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function CommandShortcut({
+  className,
+  ...props
+}: React.ComponentProps<"span">) {
+  return (
+    <span
+      data-slot="command-shortcut"
+      className={cn(
+        "text-muted-foreground ml-auto text-xs tracking-widest",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+export {
+  Command,
+  CommandDialog,
+  CommandInput,
+  CommandList,
+  CommandEmpty,
+  CommandGroup,
+  CommandItem,
+  CommandShortcut,
+  CommandSeparator,
+};

+ 252 - 0
src/components/ui/context-menu.tsx

@@ -0,0 +1,252 @@
+"use client";
+
+import * as React from "react";
+import * as ContextMenuPrimitive from "@radix-ui/react-context-menu@2.2.6";
+import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react@0.487.0";
+
+import { cn } from "./utils";
+
+function ContextMenu({
+  ...props
+}: React.ComponentProps<typeof ContextMenuPrimitive.Root>) {
+  return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />;
+}
+
+function ContextMenuTrigger({
+  ...props
+}: React.ComponentProps<typeof ContextMenuPrimitive.Trigger>) {
+  return (
+    <ContextMenuPrimitive.Trigger data-slot="context-menu-trigger" {...props} />
+  );
+}
+
+function ContextMenuGroup({
+  ...props
+}: React.ComponentProps<typeof ContextMenuPrimitive.Group>) {
+  return (
+    <ContextMenuPrimitive.Group data-slot="context-menu-group" {...props} />
+  );
+}
+
+function ContextMenuPortal({
+  ...props
+}: React.ComponentProps<typeof ContextMenuPrimitive.Portal>) {
+  return (
+    <ContextMenuPrimitive.Portal data-slot="context-menu-portal" {...props} />
+  );
+}
+
+function ContextMenuSub({
+  ...props
+}: React.ComponentProps<typeof ContextMenuPrimitive.Sub>) {
+  return <ContextMenuPrimitive.Sub data-slot="context-menu-sub" {...props} />;
+}
+
+function ContextMenuRadioGroup({
+  ...props
+}: React.ComponentProps<typeof ContextMenuPrimitive.RadioGroup>) {
+  return (
+    <ContextMenuPrimitive.RadioGroup
+      data-slot="context-menu-radio-group"
+      {...props}
+    />
+  );
+}
+
+function ContextMenuSubTrigger({
+  className,
+  inset,
+  children,
+  ...props
+}: React.ComponentProps<typeof ContextMenuPrimitive.SubTrigger> & {
+  inset?: boolean;
+}) {
+  return (
+    <ContextMenuPrimitive.SubTrigger
+      data-slot="context-menu-sub-trigger"
+      data-inset={inset}
+      className={cn(
+        "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
+        className,
+      )}
+      {...props}
+    >
+      {children}
+      <ChevronRightIcon className="ml-auto" />
+    </ContextMenuPrimitive.SubTrigger>
+  );
+}
+
+function ContextMenuSubContent({
+  className,
+  ...props
+}: React.ComponentProps<typeof ContextMenuPrimitive.SubContent>) {
+  return (
+    <ContextMenuPrimitive.SubContent
+      data-slot="context-menu-sub-content"
+      className={cn(
+        "bg-popover text-popover-foreground 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-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function ContextMenuContent({
+  className,
+  ...props
+}: React.ComponentProps<typeof ContextMenuPrimitive.Content>) {
+  return (
+    <ContextMenuPrimitive.Portal>
+      <ContextMenuPrimitive.Content
+        data-slot="context-menu-content"
+        className={cn(
+          "bg-popover text-popover-foreground 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-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-context-menu-content-available-height) min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
+          className,
+        )}
+        {...props}
+      />
+    </ContextMenuPrimitive.Portal>
+  );
+}
+
+function ContextMenuItem({
+  className,
+  inset,
+  variant = "default",
+  ...props
+}: React.ComponentProps<typeof ContextMenuPrimitive.Item> & {
+  inset?: boolean;
+  variant?: "default" | "destructive";
+}) {
+  return (
+    <ContextMenuPrimitive.Item
+      data-slot="context-menu-item"
+      data-inset={inset}
+      data-variant={variant}
+      className={cn(
+        "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function ContextMenuCheckboxItem({
+  className,
+  children,
+  checked,
+  ...props
+}: React.ComponentProps<typeof ContextMenuPrimitive.CheckboxItem>) {
+  return (
+    <ContextMenuPrimitive.CheckboxItem
+      data-slot="context-menu-checkbox-item"
+      className={cn(
+        "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
+        className,
+      )}
+      checked={checked}
+      {...props}
+    >
+      <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
+        <ContextMenuPrimitive.ItemIndicator>
+          <CheckIcon className="size-4" />
+        </ContextMenuPrimitive.ItemIndicator>
+      </span>
+      {children}
+    </ContextMenuPrimitive.CheckboxItem>
+  );
+}
+
+function ContextMenuRadioItem({
+  className,
+  children,
+  ...props
+}: React.ComponentProps<typeof ContextMenuPrimitive.RadioItem>) {
+  return (
+    <ContextMenuPrimitive.RadioItem
+      data-slot="context-menu-radio-item"
+      className={cn(
+        "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
+        className,
+      )}
+      {...props}
+    >
+      <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
+        <ContextMenuPrimitive.ItemIndicator>
+          <CircleIcon className="size-2 fill-current" />
+        </ContextMenuPrimitive.ItemIndicator>
+      </span>
+      {children}
+    </ContextMenuPrimitive.RadioItem>
+  );
+}
+
+function ContextMenuLabel({
+  className,
+  inset,
+  ...props
+}: React.ComponentProps<typeof ContextMenuPrimitive.Label> & {
+  inset?: boolean;
+}) {
+  return (
+    <ContextMenuPrimitive.Label
+      data-slot="context-menu-label"
+      data-inset={inset}
+      className={cn(
+        "text-foreground px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function ContextMenuSeparator({
+  className,
+  ...props
+}: React.ComponentProps<typeof ContextMenuPrimitive.Separator>) {
+  return (
+    <ContextMenuPrimitive.Separator
+      data-slot="context-menu-separator"
+      className={cn("bg-border -mx-1 my-1 h-px", className)}
+      {...props}
+    />
+  );
+}
+
+function ContextMenuShortcut({
+  className,
+  ...props
+}: React.ComponentProps<"span">) {
+  return (
+    <span
+      data-slot="context-menu-shortcut"
+      className={cn(
+        "text-muted-foreground ml-auto text-xs tracking-widest",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+export {
+  ContextMenu,
+  ContextMenuTrigger,
+  ContextMenuContent,
+  ContextMenuItem,
+  ContextMenuCheckboxItem,
+  ContextMenuRadioItem,
+  ContextMenuLabel,
+  ContextMenuSeparator,
+  ContextMenuShortcut,
+  ContextMenuGroup,
+  ContextMenuPortal,
+  ContextMenuSub,
+  ContextMenuSubContent,
+  ContextMenuSubTrigger,
+  ContextMenuRadioGroup,
+};

+ 135 - 0
src/components/ui/dialog.tsx

@@ -0,0 +1,135 @@
+"use client";
+
+import * as React from "react";
+import * as DialogPrimitive from "@radix-ui/react-dialog@1.1.6";
+import { XIcon } from "lucide-react@0.487.0";
+
+import { cn } from "./utils";
+
+function Dialog({
+  ...props
+}: React.ComponentProps<typeof DialogPrimitive.Root>) {
+  return <DialogPrimitive.Root data-slot="dialog" {...props} />;
+}
+
+function DialogTrigger({
+  ...props
+}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
+  return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
+}
+
+function DialogPortal({
+  ...props
+}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
+  return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
+}
+
+function DialogClose({
+  ...props
+}: React.ComponentProps<typeof DialogPrimitive.Close>) {
+  return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
+}
+
+function DialogOverlay({
+  className,
+  ...props
+}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
+  return (
+    <DialogPrimitive.Overlay
+      data-slot="dialog-overlay"
+      className={cn(
+        "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function DialogContent({
+  className,
+  children,
+  ...props
+}: React.ComponentProps<typeof DialogPrimitive.Content>) {
+  return (
+    <DialogPortal data-slot="dialog-portal">
+      <DialogOverlay />
+      <DialogPrimitive.Content
+        data-slot="dialog-content"
+        className={cn(
+          "bg-background 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 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
+          className,
+        )}
+        {...props}
+      >
+        {children}
+        <DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4">
+          <XIcon />
+          <span className="sr-only">Close</span>
+        </DialogPrimitive.Close>
+      </DialogPrimitive.Content>
+    </DialogPortal>
+  );
+}
+
+function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
+  return (
+    <div
+      data-slot="dialog-header"
+      className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
+      {...props}
+    />
+  );
+}
+
+function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
+  return (
+    <div
+      data-slot="dialog-footer"
+      className={cn(
+        "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function DialogTitle({
+  className,
+  ...props
+}: React.ComponentProps<typeof DialogPrimitive.Title>) {
+  return (
+    <DialogPrimitive.Title
+      data-slot="dialog-title"
+      className={cn("text-lg leading-none font-semibold", className)}
+      {...props}
+    />
+  );
+}
+
+function DialogDescription({
+  className,
+  ...props
+}: React.ComponentProps<typeof DialogPrimitive.Description>) {
+  return (
+    <DialogPrimitive.Description
+      data-slot="dialog-description"
+      className={cn("text-muted-foreground text-sm", className)}
+      {...props}
+    />
+  );
+}
+
+export {
+  Dialog,
+  DialogClose,
+  DialogContent,
+  DialogDescription,
+  DialogFooter,
+  DialogHeader,
+  DialogOverlay,
+  DialogPortal,
+  DialogTitle,
+  DialogTrigger,
+};

+ 132 - 0
src/components/ui/drawer.tsx

@@ -0,0 +1,132 @@
+"use client";
+
+import * as React from "react";
+import { Drawer as DrawerPrimitive } from "vaul@1.1.2";
+
+import { cn } from "./utils";
+
+function Drawer({
+  ...props
+}: React.ComponentProps<typeof DrawerPrimitive.Root>) {
+  return <DrawerPrimitive.Root data-slot="drawer" {...props} />;
+}
+
+function DrawerTrigger({
+  ...props
+}: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
+  return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />;
+}
+
+function DrawerPortal({
+  ...props
+}: React.ComponentProps<typeof DrawerPrimitive.Portal>) {
+  return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />;
+}
+
+function DrawerClose({
+  ...props
+}: React.ComponentProps<typeof DrawerPrimitive.Close>) {
+  return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />;
+}
+
+function DrawerOverlay({
+  className,
+  ...props
+}: React.ComponentProps<typeof DrawerPrimitive.Overlay>) {
+  return (
+    <DrawerPrimitive.Overlay
+      data-slot="drawer-overlay"
+      className={cn(
+        "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function DrawerContent({
+  className,
+  children,
+  ...props
+}: React.ComponentProps<typeof DrawerPrimitive.Content>) {
+  return (
+    <DrawerPortal data-slot="drawer-portal">
+      <DrawerOverlay />
+      <DrawerPrimitive.Content
+        data-slot="drawer-content"
+        className={cn(
+          "group/drawer-content bg-background fixed z-50 flex h-auto flex-col",
+          "data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b",
+          "data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t",
+          "data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm",
+          "data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm",
+          className,
+        )}
+        {...props}
+      >
+        <div className="bg-muted mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block" />
+        {children}
+      </DrawerPrimitive.Content>
+    </DrawerPortal>
+  );
+}
+
+function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
+  return (
+    <div
+      data-slot="drawer-header"
+      className={cn("flex flex-col gap-1.5 p-4", className)}
+      {...props}
+    />
+  );
+}
+
+function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
+  return (
+    <div
+      data-slot="drawer-footer"
+      className={cn("mt-auto flex flex-col gap-2 p-4", className)}
+      {...props}
+    />
+  );
+}
+
+function DrawerTitle({
+  className,
+  ...props
+}: React.ComponentProps<typeof DrawerPrimitive.Title>) {
+  return (
+    <DrawerPrimitive.Title
+      data-slot="drawer-title"
+      className={cn("text-foreground font-semibold", className)}
+      {...props}
+    />
+  );
+}
+
+function DrawerDescription({
+  className,
+  ...props
+}: React.ComponentProps<typeof DrawerPrimitive.Description>) {
+  return (
+    <DrawerPrimitive.Description
+      data-slot="drawer-description"
+      className={cn("text-muted-foreground text-sm", className)}
+      {...props}
+    />
+  );
+}
+
+export {
+  Drawer,
+  DrawerPortal,
+  DrawerOverlay,
+  DrawerTrigger,
+  DrawerClose,
+  DrawerContent,
+  DrawerHeader,
+  DrawerFooter,
+  DrawerTitle,
+  DrawerDescription,
+};

+ 257 - 0
src/components/ui/dropdown-menu.tsx

@@ -0,0 +1,257 @@
+"use client";
+
+import * as React from "react";
+import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu@2.1.6";
+import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react@0.487.0";
+
+import { cn } from "./utils";
+
+function DropdownMenu({
+  ...props
+}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
+  return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
+}
+
+function DropdownMenuPortal({
+  ...props
+}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
+  return (
+    <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
+  );
+}
+
+function DropdownMenuTrigger({
+  ...props
+}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
+  return (
+    <DropdownMenuPrimitive.Trigger
+      data-slot="dropdown-menu-trigger"
+      {...props}
+    />
+  );
+}
+
+function DropdownMenuContent({
+  className,
+  sideOffset = 4,
+  ...props
+}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
+  return (
+    <DropdownMenuPrimitive.Portal>
+      <DropdownMenuPrimitive.Content
+        data-slot="dropdown-menu-content"
+        sideOffset={sideOffset}
+        className={cn(
+          "bg-popover text-popover-foreground 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-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
+          className,
+        )}
+        {...props}
+      />
+    </DropdownMenuPrimitive.Portal>
+  );
+}
+
+function DropdownMenuGroup({
+  ...props
+}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
+  return (
+    <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
+  );
+}
+
+function DropdownMenuItem({
+  className,
+  inset,
+  variant = "default",
+  ...props
+}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
+  inset?: boolean;
+  variant?: "default" | "destructive";
+}) {
+  return (
+    <DropdownMenuPrimitive.Item
+      data-slot="dropdown-menu-item"
+      data-inset={inset}
+      data-variant={variant}
+      className={cn(
+        "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function DropdownMenuCheckboxItem({
+  className,
+  children,
+  checked,
+  ...props
+}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
+  return (
+    <DropdownMenuPrimitive.CheckboxItem
+      data-slot="dropdown-menu-checkbox-item"
+      className={cn(
+        "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
+        className,
+      )}
+      checked={checked}
+      {...props}
+    >
+      <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
+        <DropdownMenuPrimitive.ItemIndicator>
+          <CheckIcon className="size-4" />
+        </DropdownMenuPrimitive.ItemIndicator>
+      </span>
+      {children}
+    </DropdownMenuPrimitive.CheckboxItem>
+  );
+}
+
+function DropdownMenuRadioGroup({
+  ...props
+}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
+  return (
+    <DropdownMenuPrimitive.RadioGroup
+      data-slot="dropdown-menu-radio-group"
+      {...props}
+    />
+  );
+}
+
+function DropdownMenuRadioItem({
+  className,
+  children,
+  ...props
+}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
+  return (
+    <DropdownMenuPrimitive.RadioItem
+      data-slot="dropdown-menu-radio-item"
+      className={cn(
+        "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
+        className,
+      )}
+      {...props}
+    >
+      <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
+        <DropdownMenuPrimitive.ItemIndicator>
+          <CircleIcon className="size-2 fill-current" />
+        </DropdownMenuPrimitive.ItemIndicator>
+      </span>
+      {children}
+    </DropdownMenuPrimitive.RadioItem>
+  );
+}
+
+function DropdownMenuLabel({
+  className,
+  inset,
+  ...props
+}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
+  inset?: boolean;
+}) {
+  return (
+    <DropdownMenuPrimitive.Label
+      data-slot="dropdown-menu-label"
+      data-inset={inset}
+      className={cn(
+        "px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function DropdownMenuSeparator({
+  className,
+  ...props
+}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
+  return (
+    <DropdownMenuPrimitive.Separator
+      data-slot="dropdown-menu-separator"
+      className={cn("bg-border -mx-1 my-1 h-px", className)}
+      {...props}
+    />
+  );
+}
+
+function DropdownMenuShortcut({
+  className,
+  ...props
+}: React.ComponentProps<"span">) {
+  return (
+    <span
+      data-slot="dropdown-menu-shortcut"
+      className={cn(
+        "text-muted-foreground ml-auto text-xs tracking-widest",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function DropdownMenuSub({
+  ...props
+}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
+  return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
+}
+
+function DropdownMenuSubTrigger({
+  className,
+  inset,
+  children,
+  ...props
+}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
+  inset?: boolean;
+}) {
+  return (
+    <DropdownMenuPrimitive.SubTrigger
+      data-slot="dropdown-menu-sub-trigger"
+      data-inset={inset}
+      className={cn(
+        "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8",
+        className,
+      )}
+      {...props}
+    >
+      {children}
+      <ChevronRightIcon className="ml-auto size-4" />
+    </DropdownMenuPrimitive.SubTrigger>
+  );
+}
+
+function DropdownMenuSubContent({
+  className,
+  ...props
+}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
+  return (
+    <DropdownMenuPrimitive.SubContent
+      data-slot="dropdown-menu-sub-content"
+      className={cn(
+        "bg-popover text-popover-foreground 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-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+export {
+  DropdownMenu,
+  DropdownMenuPortal,
+  DropdownMenuTrigger,
+  DropdownMenuContent,
+  DropdownMenuGroup,
+  DropdownMenuLabel,
+  DropdownMenuItem,
+  DropdownMenuCheckboxItem,
+  DropdownMenuRadioGroup,
+  DropdownMenuRadioItem,
+  DropdownMenuSeparator,
+  DropdownMenuShortcut,
+  DropdownMenuSub,
+  DropdownMenuSubTrigger,
+  DropdownMenuSubContent,
+};

+ 168 - 0
src/components/ui/form.tsx

@@ -0,0 +1,168 @@
+"use client";
+
+import * as React from "react";
+import * as LabelPrimitive from "@radix-ui/react-label@2.1.2";
+import { Slot } from "@radix-ui/react-slot@1.1.2";
+import {
+  Controller,
+  FormProvider,
+  useFormContext,
+  useFormState,
+  type ControllerProps,
+  type FieldPath,
+  type FieldValues,
+} from "react-hook-form@7.55.0";
+
+import { cn } from "./utils";
+import { Label } from "./label";
+
+const Form = FormProvider;
+
+type FormFieldContextValue<
+  TFieldValues extends FieldValues = FieldValues,
+  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
+> = {
+  name: TName;
+};
+
+const FormFieldContext = React.createContext<FormFieldContextValue>(
+  {} as FormFieldContextValue,
+);
+
+const FormField = <
+  TFieldValues extends FieldValues = FieldValues,
+  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
+>({
+  ...props
+}: ControllerProps<TFieldValues, TName>) => {
+  return (
+    <FormFieldContext.Provider value={{ name: props.name }}>
+      <Controller {...props} />
+    </FormFieldContext.Provider>
+  );
+};
+
+const useFormField = () => {
+  const fieldContext = React.useContext(FormFieldContext);
+  const itemContext = React.useContext(FormItemContext);
+  const { getFieldState } = useFormContext();
+  const formState = useFormState({ name: fieldContext.name });
+  const fieldState = getFieldState(fieldContext.name, formState);
+
+  if (!fieldContext) {
+    throw new Error("useFormField should be used within <FormField>");
+  }
+
+  const { id } = itemContext;
+
+  return {
+    id,
+    name: fieldContext.name,
+    formItemId: `${id}-form-item`,
+    formDescriptionId: `${id}-form-item-description`,
+    formMessageId: `${id}-form-item-message`,
+    ...fieldState,
+  };
+};
+
+type FormItemContextValue = {
+  id: string;
+};
+
+const FormItemContext = React.createContext<FormItemContextValue>(
+  {} as FormItemContextValue,
+);
+
+function FormItem({ className, ...props }: React.ComponentProps<"div">) {
+  const id = React.useId();
+
+  return (
+    <FormItemContext.Provider value={{ id }}>
+      <div
+        data-slot="form-item"
+        className={cn("grid gap-2", className)}
+        {...props}
+      />
+    </FormItemContext.Provider>
+  );
+}
+
+function FormLabel({
+  className,
+  ...props
+}: React.ComponentProps<typeof LabelPrimitive.Root>) {
+  const { error, formItemId } = useFormField();
+
+  return (
+    <Label
+      data-slot="form-label"
+      data-error={!!error}
+      className={cn("data-[error=true]:text-destructive", className)}
+      htmlFor={formItemId}
+      {...props}
+    />
+  );
+}
+
+function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
+  const { error, formItemId, formDescriptionId, formMessageId } =
+    useFormField();
+
+  return (
+    <Slot
+      data-slot="form-control"
+      id={formItemId}
+      aria-describedby={
+        !error
+          ? `${formDescriptionId}`
+          : `${formDescriptionId} ${formMessageId}`
+      }
+      aria-invalid={!!error}
+      {...props}
+    />
+  );
+}
+
+function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
+  const { formDescriptionId } = useFormField();
+
+  return (
+    <p
+      data-slot="form-description"
+      id={formDescriptionId}
+      className={cn("text-muted-foreground text-sm", className)}
+      {...props}
+    />
+  );
+}
+
+function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
+  const { error, formMessageId } = useFormField();
+  const body = error ? String(error?.message ?? "") : props.children;
+
+  if (!body) {
+    return null;
+  }
+
+  return (
+    <p
+      data-slot="form-message"
+      id={formMessageId}
+      className={cn("text-destructive text-sm", className)}
+      {...props}
+    >
+      {body}
+    </p>
+  );
+}
+
+export {
+  useFormField,
+  Form,
+  FormItem,
+  FormLabel,
+  FormControl,
+  FormDescription,
+  FormMessage,
+  FormField,
+};

+ 44 - 0
src/components/ui/hover-card.tsx

@@ -0,0 +1,44 @@
+"use client";
+
+import * as React from "react";
+import * as HoverCardPrimitive from "@radix-ui/react-hover-card@1.1.6";
+
+import { cn } from "./utils";
+
+function HoverCard({
+  ...props
+}: React.ComponentProps<typeof HoverCardPrimitive.Root>) {
+  return <HoverCardPrimitive.Root data-slot="hover-card" {...props} />;
+}
+
+function HoverCardTrigger({
+  ...props
+}: React.ComponentProps<typeof HoverCardPrimitive.Trigger>) {
+  return (
+    <HoverCardPrimitive.Trigger data-slot="hover-card-trigger" {...props} />
+  );
+}
+
+function HoverCardContent({
+  className,
+  align = "center",
+  sideOffset = 4,
+  ...props
+}: React.ComponentProps<typeof HoverCardPrimitive.Content>) {
+  return (
+    <HoverCardPrimitive.Portal data-slot="hover-card-portal">
+      <HoverCardPrimitive.Content
+        data-slot="hover-card-content"
+        align={align}
+        sideOffset={sideOffset}
+        className={cn(
+          "bg-popover text-popover-foreground 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-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
+          className,
+        )}
+        {...props}
+      />
+    </HoverCardPrimitive.Portal>
+  );
+}
+
+export { HoverCard, HoverCardTrigger, HoverCardContent };

+ 77 - 0
src/components/ui/input-otp.tsx

@@ -0,0 +1,77 @@
+"use client";
+
+import * as React from "react";
+import { OTPInput, OTPInputContext } from "input-otp@1.4.2";
+import { MinusIcon } from "lucide-react@0.487.0";
+
+import { cn } from "./utils";
+
+function InputOTP({
+  className,
+  containerClassName,
+  ...props
+}: React.ComponentProps<typeof OTPInput> & {
+  containerClassName?: string;
+}) {
+  return (
+    <OTPInput
+      data-slot="input-otp"
+      containerClassName={cn(
+        "flex items-center gap-2 has-disabled:opacity-50",
+        containerClassName,
+      )}
+      className={cn("disabled:cursor-not-allowed", className)}
+      {...props}
+    />
+  );
+}
+
+function InputOTPGroup({ className, ...props }: React.ComponentProps<"div">) {
+  return (
+    <div
+      data-slot="input-otp-group"
+      className={cn("flex items-center gap-1", className)}
+      {...props}
+    />
+  );
+}
+
+function InputOTPSlot({
+  index,
+  className,
+  ...props
+}: React.ComponentProps<"div"> & {
+  index: number;
+}) {
+  const inputOTPContext = React.useContext(OTPInputContext);
+  const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {};
+
+  return (
+    <div
+      data-slot="input-otp-slot"
+      data-active={isActive}
+      className={cn(
+        "data-[active=true]:border-ring data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:ring-destructive/20 dark:data-[active=true]:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[active=true]:aria-invalid:border-destructive dark:bg-input/30 border-input relative flex h-9 w-9 items-center justify-center border-y border-r text-sm bg-input-background transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md data-[active=true]:z-10 data-[active=true]:ring-[3px]",
+        className,
+      )}
+      {...props}
+    >
+      {char}
+      {hasFakeCaret && (
+        <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
+          <div className="animate-caret-blink bg-foreground h-4 w-px duration-1000" />
+        </div>
+      )}
+    </div>
+  );
+}
+
+function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
+  return (
+    <div data-slot="input-otp-separator" role="separator" {...props}>
+      <MinusIcon />
+    </div>
+  );
+}
+
+export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator };

+ 21 - 0
src/components/ui/input.tsx

@@ -0,0 +1,21 @@
+import * as React from "react";
+
+import { cn } from "./utils";
+
+function Input({ className, type, ...props }: React.ComponentProps<"input">) {
+  return (
+    <input
+      type={type}
+      data-slot="input"
+      className={cn(
+        "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border px-3 py-1 text-base bg-input-background transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
+        "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
+        "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+export { Input };

+ 24 - 0
src/components/ui/label.tsx

@@ -0,0 +1,24 @@
+"use client";
+
+import * as React from "react";
+import * as LabelPrimitive from "@radix-ui/react-label@2.1.2";
+
+import { cn } from "./utils";
+
+function Label({
+  className,
+  ...props
+}: React.ComponentProps<typeof LabelPrimitive.Root>) {
+  return (
+    <LabelPrimitive.Root
+      data-slot="label"
+      className={cn(
+        "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+export { Label };

+ 276 - 0
src/components/ui/menubar.tsx

@@ -0,0 +1,276 @@
+"use client";
+
+import * as React from "react";
+import * as MenubarPrimitive from "@radix-ui/react-menubar@1.1.6";
+import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react@0.487.0";
+
+import { cn } from "./utils";
+
+function Menubar({
+  className,
+  ...props
+}: React.ComponentProps<typeof MenubarPrimitive.Root>) {
+  return (
+    <MenubarPrimitive.Root
+      data-slot="menubar"
+      className={cn(
+        "bg-background flex h-9 items-center gap-1 rounded-md border p-1 shadow-xs",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function MenubarMenu({
+  ...props
+}: React.ComponentProps<typeof MenubarPrimitive.Menu>) {
+  return <MenubarPrimitive.Menu data-slot="menubar-menu" {...props} />;
+}
+
+function MenubarGroup({
+  ...props
+}: React.ComponentProps<typeof MenubarPrimitive.Group>) {
+  return <MenubarPrimitive.Group data-slot="menubar-group" {...props} />;
+}
+
+function MenubarPortal({
+  ...props
+}: React.ComponentProps<typeof MenubarPrimitive.Portal>) {
+  return <MenubarPrimitive.Portal data-slot="menubar-portal" {...props} />;
+}
+
+function MenubarRadioGroup({
+  ...props
+}: React.ComponentProps<typeof MenubarPrimitive.RadioGroup>) {
+  return (
+    <MenubarPrimitive.RadioGroup data-slot="menubar-radio-group" {...props} />
+  );
+}
+
+function MenubarTrigger({
+  className,
+  ...props
+}: React.ComponentProps<typeof MenubarPrimitive.Trigger>) {
+  return (
+    <MenubarPrimitive.Trigger
+      data-slot="menubar-trigger"
+      className={cn(
+        "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex items-center rounded-sm px-2 py-1 text-sm font-medium outline-hidden select-none",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function MenubarContent({
+  className,
+  align = "start",
+  alignOffset = -4,
+  sideOffset = 8,
+  ...props
+}: React.ComponentProps<typeof MenubarPrimitive.Content>) {
+  return (
+    <MenubarPortal>
+      <MenubarPrimitive.Content
+        data-slot="menubar-content"
+        align={align}
+        alignOffset={alignOffset}
+        sideOffset={sideOffset}
+        className={cn(
+          "bg-popover text-popover-foreground data-[state=open]:animate-in 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-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[12rem] origin-(--radix-menubar-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-md",
+          className,
+        )}
+        {...props}
+      />
+    </MenubarPortal>
+  );
+}
+
+function MenubarItem({
+  className,
+  inset,
+  variant = "default",
+  ...props
+}: React.ComponentProps<typeof MenubarPrimitive.Item> & {
+  inset?: boolean;
+  variant?: "default" | "destructive";
+}) {
+  return (
+    <MenubarPrimitive.Item
+      data-slot="menubar-item"
+      data-inset={inset}
+      data-variant={variant}
+      className={cn(
+        "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function MenubarCheckboxItem({
+  className,
+  children,
+  checked,
+  ...props
+}: React.ComponentProps<typeof MenubarPrimitive.CheckboxItem>) {
+  return (
+    <MenubarPrimitive.CheckboxItem
+      data-slot="menubar-checkbox-item"
+      className={cn(
+        "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
+        className,
+      )}
+      checked={checked}
+      {...props}
+    >
+      <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
+        <MenubarPrimitive.ItemIndicator>
+          <CheckIcon className="size-4" />
+        </MenubarPrimitive.ItemIndicator>
+      </span>
+      {children}
+    </MenubarPrimitive.CheckboxItem>
+  );
+}
+
+function MenubarRadioItem({
+  className,
+  children,
+  ...props
+}: React.ComponentProps<typeof MenubarPrimitive.RadioItem>) {
+  return (
+    <MenubarPrimitive.RadioItem
+      data-slot="menubar-radio-item"
+      className={cn(
+        "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
+        className,
+      )}
+      {...props}
+    >
+      <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
+        <MenubarPrimitive.ItemIndicator>
+          <CircleIcon className="size-2 fill-current" />
+        </MenubarPrimitive.ItemIndicator>
+      </span>
+      {children}
+    </MenubarPrimitive.RadioItem>
+  );
+}
+
+function MenubarLabel({
+  className,
+  inset,
+  ...props
+}: React.ComponentProps<typeof MenubarPrimitive.Label> & {
+  inset?: boolean;
+}) {
+  return (
+    <MenubarPrimitive.Label
+      data-slot="menubar-label"
+      data-inset={inset}
+      className={cn(
+        "px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function MenubarSeparator({
+  className,
+  ...props
+}: React.ComponentProps<typeof MenubarPrimitive.Separator>) {
+  return (
+    <MenubarPrimitive.Separator
+      data-slot="menubar-separator"
+      className={cn("bg-border -mx-1 my-1 h-px", className)}
+      {...props}
+    />
+  );
+}
+
+function MenubarShortcut({
+  className,
+  ...props
+}: React.ComponentProps<"span">) {
+  return (
+    <span
+      data-slot="menubar-shortcut"
+      className={cn(
+        "text-muted-foreground ml-auto text-xs tracking-widest",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function MenubarSub({
+  ...props
+}: React.ComponentProps<typeof MenubarPrimitive.Sub>) {
+  return <MenubarPrimitive.Sub data-slot="menubar-sub" {...props} />;
+}
+
+function MenubarSubTrigger({
+  className,
+  inset,
+  children,
+  ...props
+}: React.ComponentProps<typeof MenubarPrimitive.SubTrigger> & {
+  inset?: boolean;
+}) {
+  return (
+    <MenubarPrimitive.SubTrigger
+      data-slot="menubar-sub-trigger"
+      data-inset={inset}
+      className={cn(
+        "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-none select-none data-[inset]:pl-8",
+        className,
+      )}
+      {...props}
+    >
+      {children}
+      <ChevronRightIcon className="ml-auto h-4 w-4" />
+    </MenubarPrimitive.SubTrigger>
+  );
+}
+
+function MenubarSubContent({
+  className,
+  ...props
+}: React.ComponentProps<typeof MenubarPrimitive.SubContent>) {
+  return (
+    <MenubarPrimitive.SubContent
+      data-slot="menubar-sub-content"
+      className={cn(
+        "bg-popover text-popover-foreground 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-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-menubar-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+export {
+  Menubar,
+  MenubarPortal,
+  MenubarMenu,
+  MenubarTrigger,
+  MenubarContent,
+  MenubarGroup,
+  MenubarSeparator,
+  MenubarLabel,
+  MenubarItem,
+  MenubarShortcut,
+  MenubarCheckboxItem,
+  MenubarRadioGroup,
+  MenubarRadioItem,
+  MenubarSub,
+  MenubarSubTrigger,
+  MenubarSubContent,
+};

+ 168 - 0
src/components/ui/navigation-menu.tsx

@@ -0,0 +1,168 @@
+import * as React from "react";
+import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu@1.2.5";
+import { cva } from "class-variance-authority@0.7.1";
+import { ChevronDownIcon } from "lucide-react@0.487.0";
+
+import { cn } from "./utils";
+
+function NavigationMenu({
+  className,
+  children,
+  viewport = true,
+  ...props
+}: React.ComponentProps<typeof NavigationMenuPrimitive.Root> & {
+  viewport?: boolean;
+}) {
+  return (
+    <NavigationMenuPrimitive.Root
+      data-slot="navigation-menu"
+      data-viewport={viewport}
+      className={cn(
+        "group/navigation-menu relative flex max-w-max flex-1 items-center justify-center",
+        className,
+      )}
+      {...props}
+    >
+      {children}
+      {viewport && <NavigationMenuViewport />}
+    </NavigationMenuPrimitive.Root>
+  );
+}
+
+function NavigationMenuList({
+  className,
+  ...props
+}: React.ComponentProps<typeof NavigationMenuPrimitive.List>) {
+  return (
+    <NavigationMenuPrimitive.List
+      data-slot="navigation-menu-list"
+      className={cn(
+        "group flex flex-1 list-none items-center justify-center gap-1",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function NavigationMenuItem({
+  className,
+  ...props
+}: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) {
+  return (
+    <NavigationMenuPrimitive.Item
+      data-slot="navigation-menu-item"
+      className={cn("relative", className)}
+      {...props}
+    />
+  );
+}
+
+const navigationMenuTriggerStyle = cva(
+  "group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=open]:hover:bg-accent data-[state=open]:text-accent-foreground data-[state=open]:focus:bg-accent data-[state=open]:bg-accent/50 focus-visible:ring-ring/50 outline-none transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1",
+);
+
+function NavigationMenuTrigger({
+  className,
+  children,
+  ...props
+}: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) {
+  return (
+    <NavigationMenuPrimitive.Trigger
+      data-slot="navigation-menu-trigger"
+      className={cn(navigationMenuTriggerStyle(), "group", className)}
+      {...props}
+    >
+      {children}{" "}
+      <ChevronDownIcon
+        className="relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180"
+        aria-hidden="true"
+      />
+    </NavigationMenuPrimitive.Trigger>
+  );
+}
+
+function NavigationMenuContent({
+  className,
+  ...props
+}: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) {
+  return (
+    <NavigationMenuPrimitive.Content
+      data-slot="navigation-menu-content"
+      className={cn(
+        "data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 top-0 left-0 w-full p-2 pr-2.5 md:absolute md:w-auto",
+        "group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:duration-200 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function NavigationMenuViewport({
+  className,
+  ...props
+}: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) {
+  return (
+    <div
+      className={cn(
+        "absolute top-full left-0 isolate z-50 flex justify-center",
+      )}
+    >
+      <NavigationMenuPrimitive.Viewport
+        data-slot="navigation-menu-viewport"
+        className={cn(
+          "origin-top-center bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border shadow md:w-[var(--radix-navigation-menu-viewport-width)]",
+          className,
+        )}
+        {...props}
+      />
+    </div>
+  );
+}
+
+function NavigationMenuLink({
+  className,
+  ...props
+}: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) {
+  return (
+    <NavigationMenuPrimitive.Link
+      data-slot="navigation-menu-link"
+      className={cn(
+        "data-[active=true]:focus:bg-accent data-[active=true]:hover:bg-accent data-[active=true]:bg-accent/50 data-[active=true]:text-accent-foreground hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring/50 [&_svg:not([class*='text-'])]:text-muted-foreground flex flex-col gap-1 rounded-sm p-2 text-sm transition-all outline-none focus-visible:ring-[3px] focus-visible:outline-1 [&_svg:not([class*='size-'])]:size-4",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function NavigationMenuIndicator({
+  className,
+  ...props
+}: React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>) {
+  return (
+    <NavigationMenuPrimitive.Indicator
+      data-slot="navigation-menu-indicator"
+      className={cn(
+        "data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden",
+        className,
+      )}
+      {...props}
+    >
+      <div className="bg-border relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm shadow-md" />
+    </NavigationMenuPrimitive.Indicator>
+  );
+}
+
+export {
+  NavigationMenu,
+  NavigationMenuList,
+  NavigationMenuItem,
+  NavigationMenuContent,
+  NavigationMenuTrigger,
+  NavigationMenuLink,
+  NavigationMenuIndicator,
+  NavigationMenuViewport,
+  navigationMenuTriggerStyle,
+};

+ 127 - 0
src/components/ui/pagination.tsx

@@ -0,0 +1,127 @@
+import * as React from "react";
+import {
+  ChevronLeftIcon,
+  ChevronRightIcon,
+  MoreHorizontalIcon,
+} from "lucide-react@0.487.0";
+
+import { cn } from "./utils";
+import { Button, buttonVariants } from "./button";
+
+function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
+  return (
+    <nav
+      role="navigation"
+      aria-label="pagination"
+      data-slot="pagination"
+      className={cn("mx-auto flex w-full justify-center", className)}
+      {...props}
+    />
+  );
+}
+
+function PaginationContent({
+  className,
+  ...props
+}: React.ComponentProps<"ul">) {
+  return (
+    <ul
+      data-slot="pagination-content"
+      className={cn("flex flex-row items-center gap-1", className)}
+      {...props}
+    />
+  );
+}
+
+function PaginationItem({ ...props }: React.ComponentProps<"li">) {
+  return <li data-slot="pagination-item" {...props} />;
+}
+
+type PaginationLinkProps = {
+  isActive?: boolean;
+} & Pick<React.ComponentProps<typeof Button>, "size"> &
+  React.ComponentProps<"a">;
+
+function PaginationLink({
+  className,
+  isActive,
+  size = "icon",
+  ...props
+}: PaginationLinkProps) {
+  return (
+    <a
+      aria-current={isActive ? "page" : undefined}
+      data-slot="pagination-link"
+      data-active={isActive}
+      className={cn(
+        buttonVariants({
+          variant: isActive ? "outline" : "ghost",
+          size,
+        }),
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function PaginationPrevious({
+  className,
+  ...props
+}: React.ComponentProps<typeof PaginationLink>) {
+  return (
+    <PaginationLink
+      aria-label="Go to previous page"
+      size="default"
+      className={cn("gap-1 px-2.5 sm:pl-2.5", className)}
+      {...props}
+    >
+      <ChevronLeftIcon />
+      <span className="hidden sm:block">Previous</span>
+    </PaginationLink>
+  );
+}
+
+function PaginationNext({
+  className,
+  ...props
+}: React.ComponentProps<typeof PaginationLink>) {
+  return (
+    <PaginationLink
+      aria-label="Go to next page"
+      size="default"
+      className={cn("gap-1 px-2.5 sm:pr-2.5", className)}
+      {...props}
+    >
+      <span className="hidden sm:block">Next</span>
+      <ChevronRightIcon />
+    </PaginationLink>
+  );
+}
+
+function PaginationEllipsis({
+  className,
+  ...props
+}: React.ComponentProps<"span">) {
+  return (
+    <span
+      aria-hidden
+      data-slot="pagination-ellipsis"
+      className={cn("flex size-9 items-center justify-center", className)}
+      {...props}
+    >
+      <MoreHorizontalIcon className="size-4" />
+      <span className="sr-only">More pages</span>
+    </span>
+  );
+}
+
+export {
+  Pagination,
+  PaginationContent,
+  PaginationLink,
+  PaginationItem,
+  PaginationPrevious,
+  PaginationNext,
+  PaginationEllipsis,
+};

+ 48 - 0
src/components/ui/popover.tsx

@@ -0,0 +1,48 @@
+"use client";
+
+import * as React from "react";
+import * as PopoverPrimitive from "@radix-ui/react-popover@1.1.6";
+
+import { cn } from "./utils";
+
+function Popover({
+  ...props
+}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
+  return <PopoverPrimitive.Root data-slot="popover" {...props} />;
+}
+
+function PopoverTrigger({
+  ...props
+}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
+  return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
+}
+
+function PopoverContent({
+  className,
+  align = "center",
+  sideOffset = 4,
+  ...props
+}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
+  return (
+    <PopoverPrimitive.Portal>
+      <PopoverPrimitive.Content
+        data-slot="popover-content"
+        align={align}
+        sideOffset={sideOffset}
+        className={cn(
+          "bg-popover text-popover-foreground 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-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
+          className,
+        )}
+        {...props}
+      />
+    </PopoverPrimitive.Portal>
+  );
+}
+
+function PopoverAnchor({
+  ...props
+}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
+  return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;
+}
+
+export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };

+ 31 - 0
src/components/ui/progress.tsx

@@ -0,0 +1,31 @@
+"use client";
+
+import * as React from "react";
+import * as ProgressPrimitive from "@radix-ui/react-progress@1.1.2";
+
+import { cn } from "./utils";
+
+function Progress({
+  className,
+  value,
+  ...props
+}: React.ComponentProps<typeof ProgressPrimitive.Root>) {
+  return (
+    <ProgressPrimitive.Root
+      data-slot="progress"
+      className={cn(
+        "bg-primary/20 relative h-2 w-full overflow-hidden rounded-full",
+        className,
+      )}
+      {...props}
+    >
+      <ProgressPrimitive.Indicator
+        data-slot="progress-indicator"
+        className="bg-primary h-full w-full flex-1 transition-all"
+        style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
+      />
+    </ProgressPrimitive.Root>
+  );
+}
+
+export { Progress };

+ 45 - 0
src/components/ui/radio-group.tsx

@@ -0,0 +1,45 @@
+"use client";
+
+import * as React from "react";
+import * as RadioGroupPrimitive from "@radix-ui/react-radio-group@1.2.3";
+import { CircleIcon } from "lucide-react@0.487.0";
+
+import { cn } from "./utils";
+
+function RadioGroup({
+  className,
+  ...props
+}: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
+  return (
+    <RadioGroupPrimitive.Root
+      data-slot="radio-group"
+      className={cn("grid gap-3", className)}
+      {...props}
+    />
+  );
+}
+
+function RadioGroupItem({
+  className,
+  ...props
+}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
+  return (
+    <RadioGroupPrimitive.Item
+      data-slot="radio-group-item"
+      className={cn(
+        "border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
+        className,
+      )}
+      {...props}
+    >
+      <RadioGroupPrimitive.Indicator
+        data-slot="radio-group-indicator"
+        className="relative flex items-center justify-center"
+      >
+        <CircleIcon className="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" />
+      </RadioGroupPrimitive.Indicator>
+    </RadioGroupPrimitive.Item>
+  );
+}
+
+export { RadioGroup, RadioGroupItem };

+ 56 - 0
src/components/ui/resizable.tsx

@@ -0,0 +1,56 @@
+"use client";
+
+import * as React from "react";
+import { GripVerticalIcon } from "lucide-react@0.487.0";
+import * as ResizablePrimitive from "react-resizable-panels@2.1.7";
+
+import { cn } from "./utils";
+
+function ResizablePanelGroup({
+  className,
+  ...props
+}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) {
+  return (
+    <ResizablePrimitive.PanelGroup
+      data-slot="resizable-panel-group"
+      className={cn(
+        "flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function ResizablePanel({
+  ...props
+}: React.ComponentProps<typeof ResizablePrimitive.Panel>) {
+  return <ResizablePrimitive.Panel data-slot="resizable-panel" {...props} />;
+}
+
+function ResizableHandle({
+  withHandle,
+  className,
+  ...props
+}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
+  withHandle?: boolean;
+}) {
+  return (
+    <ResizablePrimitive.PanelResizeHandle
+      data-slot="resizable-handle"
+      className={cn(
+        "bg-border focus-visible:ring-ring relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90",
+        className,
+      )}
+      {...props}
+    >
+      {withHandle && (
+        <div className="bg-border z-10 flex h-4 w-3 items-center justify-center rounded-xs border">
+          <GripVerticalIcon className="size-2.5" />
+        </div>
+      )}
+    </ResizablePrimitive.PanelResizeHandle>
+  );
+}
+
+export { ResizablePanelGroup, ResizablePanel, ResizableHandle };

+ 58 - 0
src/components/ui/scroll-area.tsx

@@ -0,0 +1,58 @@
+"use client";
+
+import * as React from "react";
+import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area@1.2.3";
+
+import { cn } from "./utils";
+
+function ScrollArea({
+  className,
+  children,
+  ...props
+}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
+  return (
+    <ScrollAreaPrimitive.Root
+      data-slot="scroll-area"
+      className={cn("relative", className)}
+      {...props}
+    >
+      <ScrollAreaPrimitive.Viewport
+        data-slot="scroll-area-viewport"
+        className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
+      >
+        {children}
+      </ScrollAreaPrimitive.Viewport>
+      <ScrollBar />
+      <ScrollAreaPrimitive.Corner />
+    </ScrollAreaPrimitive.Root>
+  );
+}
+
+function ScrollBar({
+  className,
+  orientation = "vertical",
+  ...props
+}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
+  return (
+    <ScrollAreaPrimitive.ScrollAreaScrollbar
+      data-slot="scroll-area-scrollbar"
+      orientation={orientation}
+      className={cn(
+        "flex touch-none p-px transition-colors select-none",
+        orientation === "vertical" &&
+          "h-full w-2.5 border-l border-l-transparent",
+        orientation === "horizontal" &&
+          "h-2.5 flex-col border-t border-t-transparent",
+        className,
+      )}
+      {...props}
+    >
+      <ScrollAreaPrimitive.ScrollAreaThumb
+        data-slot="scroll-area-thumb"
+        className="bg-border relative flex-1 rounded-full"
+      />
+    </ScrollAreaPrimitive.ScrollAreaScrollbar>
+  );
+}
+
+export { ScrollArea, ScrollBar };

+ 189 - 0
src/components/ui/select.tsx

@@ -0,0 +1,189 @@
+"use client";
+
+import * as React from "react";
+import * as SelectPrimitive from "@radix-ui/react-select@2.1.6";
+import {
+  CheckIcon,
+  ChevronDownIcon,
+  ChevronUpIcon,
+} from "lucide-react@0.487.0";
+
+import { cn } from "./utils";
+
+function Select({
+  ...props
+}: React.ComponentProps<typeof SelectPrimitive.Root>) {
+  return <SelectPrimitive.Root data-slot="select" {...props} />;
+}
+
+function SelectGroup({
+  ...props
+}: React.ComponentProps<typeof SelectPrimitive.Group>) {
+  return <SelectPrimitive.Group data-slot="select-group" {...props} />;
+}
+
+function SelectValue({
+  ...props
+}: React.ComponentProps<typeof SelectPrimitive.Value>) {
+  return <SelectPrimitive.Value data-slot="select-value" {...props} />;
+}
+
+function SelectTrigger({
+  className,
+  size = "default",
+  children,
+  ...props
+}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
+  size?: "sm" | "default";
+}) {
+  return (
+    <SelectPrimitive.Trigger
+      data-slot="select-trigger"
+      data-size={size}
+      className={cn(
+        "border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-full items-center justify-between gap-2 rounded-md border bg-input-background px-3 py-2 text-sm whitespace-nowrap transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
+        className,
+      )}
+      {...props}
+    >
+      {children}
+      <SelectPrimitive.Icon asChild>
+        <ChevronDownIcon className="size-4 opacity-50" />
+      </SelectPrimitive.Icon>
+    </SelectPrimitive.Trigger>
+  );
+}
+
+function SelectContent({
+  className,
+  children,
+  position = "popper",
+  ...props
+}: React.ComponentProps<typeof SelectPrimitive.Content>) {
+  return (
+    <SelectPrimitive.Portal>
+      <SelectPrimitive.Content
+        data-slot="select-content"
+        className={cn(
+          "bg-popover text-popover-foreground 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-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
+          position === "popper" &&
+            "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
+          className,
+        )}
+        position={position}
+        {...props}
+      >
+        <SelectScrollUpButton />
+        <SelectPrimitive.Viewport
+          className={cn(
+            "p-1",
+            position === "popper" &&
+              "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1",
+          )}
+        >
+          {children}
+        </SelectPrimitive.Viewport>
+        <SelectScrollDownButton />
+      </SelectPrimitive.Content>
+    </SelectPrimitive.Portal>
+  );
+}
+
+function SelectLabel({
+  className,
+  ...props
+}: React.ComponentProps<typeof SelectPrimitive.Label>) {
+  return (
+    <SelectPrimitive.Label
+      data-slot="select-label"
+      className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
+      {...props}
+    />
+  );
+}
+
+function SelectItem({
+  className,
+  children,
+  ...props
+}: React.ComponentProps<typeof SelectPrimitive.Item>) {
+  return (
+    <SelectPrimitive.Item
+      data-slot="select-item"
+      className={cn(
+        "focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
+        className,
+      )}
+      {...props}
+    >
+      <span className="absolute right-2 flex size-3.5 items-center justify-center">
+        <SelectPrimitive.ItemIndicator>
+          <CheckIcon className="size-4" />
+        </SelectPrimitive.ItemIndicator>
+      </span>
+      <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
+    </SelectPrimitive.Item>
+  );
+}
+
+function SelectSeparator({
+  className,
+  ...props
+}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
+  return (
+    <SelectPrimitive.Separator
+      data-slot="select-separator"
+      className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
+      {...props}
+    />
+  );
+}
+
+function SelectScrollUpButton({
+  className,
+  ...props
+}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
+  return (
+    <SelectPrimitive.ScrollUpButton
+      data-slot="select-scroll-up-button"
+      className={cn(
+        "flex cursor-default items-center justify-center py-1",
+        className,
+      )}
+      {...props}
+    >
+      <ChevronUpIcon className="size-4" />
+    </SelectPrimitive.ScrollUpButton>
+  );
+}
+
+function SelectScrollDownButton({
+  className,
+  ...props
+}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
+  return (
+    <SelectPrimitive.ScrollDownButton
+      data-slot="select-scroll-down-button"
+      className={cn(
+        "flex cursor-default items-center justify-center py-1",
+        className,
+      )}
+      {...props}
+    >
+      <ChevronDownIcon className="size-4" />
+    </SelectPrimitive.ScrollDownButton>
+  );
+}
+
+export {
+  Select,
+  SelectContent,
+  SelectGroup,
+  SelectItem,
+  SelectLabel,
+  SelectScrollDownButton,
+  SelectScrollUpButton,
+  SelectSeparator,
+  SelectTrigger,
+  SelectValue,
+};

+ 28 - 0
src/components/ui/separator.tsx

@@ -0,0 +1,28 @@
+"use client";
+
+import * as React from "react";
+import * as SeparatorPrimitive from "@radix-ui/react-separator@1.1.2";
+
+import { cn } from "./utils";
+
+function Separator({
+  className,
+  orientation = "horizontal",
+  decorative = true,
+  ...props
+}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
+  return (
+    <SeparatorPrimitive.Root
+      data-slot="separator-root"
+      decorative={decorative}
+      orientation={orientation}
+      className={cn(
+        "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+export { Separator };

+ 139 - 0
src/components/ui/sheet.tsx

@@ -0,0 +1,139 @@
+"use client";
+
+import * as React from "react";
+import * as SheetPrimitive from "@radix-ui/react-dialog@1.1.6";
+import { XIcon } from "lucide-react@0.487.0";
+
+import { cn } from "./utils";
+
+function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
+  return <SheetPrimitive.Root data-slot="sheet" {...props} />;
+}
+
+function SheetTrigger({
+  ...props
+}: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
+  return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />;
+}
+
+function SheetClose({
+  ...props
+}: React.ComponentProps<typeof SheetPrimitive.Close>) {
+  return <SheetPrimitive.Close data-slot="sheet-close" {...props} />;
+}
+
+function SheetPortal({
+  ...props
+}: React.ComponentProps<typeof SheetPrimitive.Portal>) {
+  return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />;
+}
+
+function SheetOverlay({
+  className,
+  ...props
+}: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
+  return (
+    <SheetPrimitive.Overlay
+      data-slot="sheet-overlay"
+      className={cn(
+        "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function SheetContent({
+  className,
+  children,
+  side = "right",
+  ...props
+}: React.ComponentProps<typeof SheetPrimitive.Content> & {
+  side?: "top" | "right" | "bottom" | "left";
+}) {
+  return (
+    <SheetPortal>
+      <SheetOverlay />
+      <SheetPrimitive.Content
+        data-slot="sheet-content"
+        className={cn(
+          "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
+          side === "right" &&
+            "data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm",
+          side === "left" &&
+            "data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm",
+          side === "top" &&
+            "data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b",
+          side === "bottom" &&
+            "data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t",
+          className,
+        )}
+        {...props}
+      >
+        {children}
+        <SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
+          <XIcon className="size-4" />
+          <span className="sr-only">Close</span>
+        </SheetPrimitive.Close>
+      </SheetPrimitive.Content>
+    </SheetPortal>
+  );
+}
+
+function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
+  return (
+    <div
+      data-slot="sheet-header"
+      className={cn("flex flex-col gap-1.5 p-4", className)}
+      {...props}
+    />
+  );
+}
+
+function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
+  return (
+    <div
+      data-slot="sheet-footer"
+      className={cn("mt-auto flex flex-col gap-2 p-4", className)}
+      {...props}
+    />
+  );
+}
+
+function SheetTitle({
+  className,
+  ...props
+}: React.ComponentProps<typeof SheetPrimitive.Title>) {
+  return (
+    <SheetPrimitive.Title
+      data-slot="sheet-title"
+      className={cn("text-foreground font-semibold", className)}
+      {...props}
+    />
+  );
+}
+
+function SheetDescription({
+  className,
+  ...props
+}: React.ComponentProps<typeof SheetPrimitive.Description>) {
+  return (
+    <SheetPrimitive.Description
+      data-slot="sheet-description"
+      className={cn("text-muted-foreground text-sm", className)}
+      {...props}
+    />
+  );
+}
+
+export {
+  Sheet,
+  SheetTrigger,
+  SheetClose,
+  SheetContent,
+  SheetHeader,
+  SheetFooter,
+  SheetTitle,
+  SheetDescription,
+};

+ 726 - 0
src/components/ui/sidebar.tsx

@@ -0,0 +1,726 @@
+"use client";
+
+import * as React from "react";
+import { Slot } from "@radix-ui/react-slot@1.1.2";
+import { VariantProps, cva } from "class-variance-authority@0.7.1";
+import { PanelLeftIcon } from "lucide-react@0.487.0";
+
+import { useIsMobile } from "./use-mobile";
+import { cn } from "./utils";
+import { Button } from "./button";
+import { Input } from "./input";
+import { Separator } from "./separator";
+import {
+  Sheet,
+  SheetContent,
+  SheetDescription,
+  SheetHeader,
+  SheetTitle,
+} from "./sheet";
+import { Skeleton } from "./skeleton";
+import {
+  Tooltip,
+  TooltipContent,
+  TooltipProvider,
+  TooltipTrigger,
+} from "./tooltip";
+
+const SIDEBAR_COOKIE_NAME = "sidebar_state";
+const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
+const SIDEBAR_WIDTH = "16rem";
+const SIDEBAR_WIDTH_MOBILE = "18rem";
+const SIDEBAR_WIDTH_ICON = "3rem";
+const SIDEBAR_KEYBOARD_SHORTCUT = "b";
+
+type SidebarContextProps = {
+  state: "expanded" | "collapsed";
+  open: boolean;
+  setOpen: (open: boolean) => void;
+  openMobile: boolean;
+  setOpenMobile: (open: boolean) => void;
+  isMobile: boolean;
+  toggleSidebar: () => void;
+};
+
+const SidebarContext = React.createContext<SidebarContextProps | null>(null);
+
+function useSidebar() {
+  const context = React.useContext(SidebarContext);
+  if (!context) {
+    throw new Error("useSidebar must be used within a SidebarProvider.");
+  }
+
+  return context;
+}
+
+function SidebarProvider({
+  defaultOpen = true,
+  open: openProp,
+  onOpenChange: setOpenProp,
+  className,
+  style,
+  children,
+  ...props
+}: React.ComponentProps<"div"> & {
+  defaultOpen?: boolean;
+  open?: boolean;
+  onOpenChange?: (open: boolean) => void;
+}) {
+  const isMobile = useIsMobile();
+  const [openMobile, setOpenMobile] = React.useState(false);
+
+  // This is the internal state of the sidebar.
+  // We use openProp and setOpenProp for control from outside the component.
+  const [_open, _setOpen] = React.useState(defaultOpen);
+  const open = openProp ?? _open;
+  const setOpen = React.useCallback(
+    (value: boolean | ((value: boolean) => boolean)) => {
+      const openState = typeof value === "function" ? value(open) : value;
+      if (setOpenProp) {
+        setOpenProp(openState);
+      } else {
+        _setOpen(openState);
+      }
+
+      // This sets the cookie to keep the sidebar state.
+      document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
+    },
+    [setOpenProp, open],
+  );
+
+  // Helper to toggle the sidebar.
+  const toggleSidebar = React.useCallback(() => {
+    return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);
+  }, [isMobile, setOpen, setOpenMobile]);
+
+  // Adds a keyboard shortcut to toggle the sidebar.
+  React.useEffect(() => {
+    const handleKeyDown = (event: KeyboardEvent) => {
+      if (
+        event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
+        (event.metaKey || event.ctrlKey)
+      ) {
+        event.preventDefault();
+        toggleSidebar();
+      }
+    };
+
+    window.addEventListener("keydown", handleKeyDown);
+    return () => window.removeEventListener("keydown", handleKeyDown);
+  }, [toggleSidebar]);
+
+  // We add a state so that we can do data-state="expanded" or "collapsed".
+  // This makes it easier to style the sidebar with Tailwind classes.
+  const state = open ? "expanded" : "collapsed";
+
+  const contextValue = React.useMemo<SidebarContextProps>(
+    () => ({
+      state,
+      open,
+      setOpen,
+      isMobile,
+      openMobile,
+      setOpenMobile,
+      toggleSidebar,
+    }),
+    [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar],
+  );
+
+  return (
+    <SidebarContext.Provider value={contextValue}>
+      <TooltipProvider delayDuration={0}>
+        <div
+          data-slot="sidebar-wrapper"
+          style={
+            {
+              "--sidebar-width": SIDEBAR_WIDTH,
+              "--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
+              ...style,
+            } as React.CSSProperties
+          }
+          className={cn(
+            "group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full",
+            className,
+          )}
+          {...props}
+        >
+          {children}
+        </div>
+      </TooltipProvider>
+    </SidebarContext.Provider>
+  );
+}
+
+function Sidebar({
+  side = "left",
+  variant = "sidebar",
+  collapsible = "offcanvas",
+  className,
+  children,
+  ...props
+}: React.ComponentProps<"div"> & {
+  side?: "left" | "right";
+  variant?: "sidebar" | "floating" | "inset";
+  collapsible?: "offcanvas" | "icon" | "none";
+}) {
+  const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
+
+  if (collapsible === "none") {
+    return (
+      <div
+        data-slot="sidebar"
+        className={cn(
+          "bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col",
+          className,
+        )}
+        {...props}
+      >
+        {children}
+      </div>
+    );
+  }
+
+  if (isMobile) {
+    return (
+      <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
+        <SheetContent
+          data-sidebar="sidebar"
+          data-slot="sidebar"
+          data-mobile="true"
+          className="bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden"
+          style={
+            {
+              "--sidebar-width": SIDEBAR_WIDTH_MOBILE,
+            } as React.CSSProperties
+          }
+          side={side}
+        >
+          <SheetHeader className="sr-only">
+            <SheetTitle>Sidebar</SheetTitle>
+            <SheetDescription>Displays the mobile sidebar.</SheetDescription>
+          </SheetHeader>
+          <div className="flex h-full w-full flex-col">{children}</div>
+        </SheetContent>
+      </Sheet>
+    );
+  }
+
+  return (
+    <div
+      className="group peer text-sidebar-foreground hidden md:block"
+      data-state={state}
+      data-collapsible={state === "collapsed" ? collapsible : ""}
+      data-variant={variant}
+      data-side={side}
+      data-slot="sidebar"
+    >
+      {/* This is what handles the sidebar gap on desktop */}
+      <div
+        data-slot="sidebar-gap"
+        className={cn(
+          "relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear",
+          "group-data-[collapsible=offcanvas]:w-0",
+          "group-data-[side=right]:rotate-180",
+          variant === "floating" || variant === "inset"
+            ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]"
+            : "group-data-[collapsible=icon]:w-(--sidebar-width-icon)",
+        )}
+      />
+      <div
+        data-slot="sidebar-container"
+        className={cn(
+          "fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex",
+          side === "left"
+            ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
+            : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
+          // Adjust the padding for floating and inset variants.
+          variant === "floating" || variant === "inset"
+            ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
+            : "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l",
+          className,
+        )}
+        {...props}
+      >
+        <div
+          data-sidebar="sidebar"
+          data-slot="sidebar-inner"
+          className="bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm"
+        >
+          {children}
+        </div>
+      </div>
+    </div>
+  );
+}
+
+function SidebarTrigger({
+  className,
+  onClick,
+  ...props
+}: React.ComponentProps<typeof Button>) {
+  const { toggleSidebar } = useSidebar();
+
+  return (
+    <Button
+      data-sidebar="trigger"
+      data-slot="sidebar-trigger"
+      variant="ghost"
+      size="icon"
+      className={cn("size-7", className)}
+      onClick={(event) => {
+        onClick?.(event);
+        toggleSidebar();
+      }}
+      {...props}
+    >
+      <PanelLeftIcon />
+      <span className="sr-only">Toggle Sidebar</span>
+    </Button>
+  );
+}
+
+function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
+  const { toggleSidebar } = useSidebar();
+
+  return (
+    <button
+      data-sidebar="rail"
+      data-slot="sidebar-rail"
+      aria-label="Toggle Sidebar"
+      tabIndex={-1}
+      onClick={toggleSidebar}
+      title="Toggle Sidebar"
+      className={cn(
+        "hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex",
+        "in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize",
+        "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
+        "hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full",
+        "[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
+        "[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
+  return (
+    <main
+      data-slot="sidebar-inset"
+      className={cn(
+        "bg-background relative flex w-full flex-1 flex-col",
+        "md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function SidebarInput({
+  className,
+  ...props
+}: React.ComponentProps<typeof Input>) {
+  return (
+    <Input
+      data-slot="sidebar-input"
+      data-sidebar="input"
+      className={cn("bg-background h-8 w-full shadow-none", className)}
+      {...props}
+    />
+  );
+}
+
+function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) {
+  return (
+    <div
+      data-slot="sidebar-header"
+      data-sidebar="header"
+      className={cn("flex flex-col gap-2 p-2", className)}
+      {...props}
+    />
+  );
+}
+
+function SidebarFooter({ className, ...props }: React.ComponentProps<"div">) {
+  return (
+    <div
+      data-slot="sidebar-footer"
+      data-sidebar="footer"
+      className={cn("flex flex-col gap-2 p-2", className)}
+      {...props}
+    />
+  );
+}
+
+function SidebarSeparator({
+  className,
+  ...props
+}: React.ComponentProps<typeof Separator>) {
+  return (
+    <Separator
+      data-slot="sidebar-separator"
+      data-sidebar="separator"
+      className={cn("bg-sidebar-border mx-2 w-auto", className)}
+      {...props}
+    />
+  );
+}
+
+function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
+  return (
+    <div
+      data-slot="sidebar-content"
+      data-sidebar="content"
+      className={cn(
+        "flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
+  return (
+    <div
+      data-slot="sidebar-group"
+      data-sidebar="group"
+      className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
+      {...props}
+    />
+  );
+}
+
+function SidebarGroupLabel({
+  className,
+  asChild = false,
+  ...props
+}: React.ComponentProps<"div"> & { asChild?: boolean }) {
+  const Comp = asChild ? Slot : "div";
+
+  return (
+    <Comp
+      data-slot="sidebar-group-label"
+      data-sidebar="group-label"
+      className={cn(
+        "text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
+        "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function SidebarGroupAction({
+  className,
+  asChild = false,
+  ...props
+}: React.ComponentProps<"button"> & { asChild?: boolean }) {
+  const Comp = asChild ? Slot : "button";
+
+  return (
+    <Comp
+      data-slot="sidebar-group-action"
+      data-sidebar="group-action"
+      className={cn(
+        "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
+        // Increases the hit area of the button on mobile.
+        "after:absolute after:-inset-2 md:after:hidden",
+        "group-data-[collapsible=icon]:hidden",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function SidebarGroupContent({
+  className,
+  ...props
+}: React.ComponentProps<"div">) {
+  return (
+    <div
+      data-slot="sidebar-group-content"
+      data-sidebar="group-content"
+      className={cn("w-full text-sm", className)}
+      {...props}
+    />
+  );
+}
+
+function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
+  return (
+    <ul
+      data-slot="sidebar-menu"
+      data-sidebar="menu"
+      className={cn("flex w-full min-w-0 flex-col gap-1", className)}
+      {...props}
+    />
+  );
+}
+
+function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
+  return (
+    <li
+      data-slot="sidebar-menu-item"
+      data-sidebar="menu-item"
+      className={cn("group/menu-item relative", className)}
+      {...props}
+    />
+  );
+}
+
+const sidebarMenuButtonVariants = cva(
+  "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
+  {
+    variants: {
+      variant: {
+        default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
+        outline:
+          "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
+      },
+      size: {
+        default: "h-8 text-sm",
+        sm: "h-7 text-xs",
+        lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!",
+      },
+    },
+    defaultVariants: {
+      variant: "default",
+      size: "default",
+    },
+  },
+);
+
+function SidebarMenuButton({
+  asChild = false,
+  isActive = false,
+  variant = "default",
+  size = "default",
+  tooltip,
+  className,
+  ...props
+}: React.ComponentProps<"button"> & {
+  asChild?: boolean;
+  isActive?: boolean;
+  tooltip?: string | React.ComponentProps<typeof TooltipContent>;
+} & VariantProps<typeof sidebarMenuButtonVariants>) {
+  const Comp = asChild ? Slot : "button";
+  const { isMobile, state } = useSidebar();
+
+  const button = (
+    <Comp
+      data-slot="sidebar-menu-button"
+      data-sidebar="menu-button"
+      data-size={size}
+      data-active={isActive}
+      className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
+      {...props}
+    />
+  );
+
+  if (!tooltip) {
+    return button;
+  }
+
+  if (typeof tooltip === "string") {
+    tooltip = {
+      children: tooltip,
+    };
+  }
+
+  return (
+    <Tooltip>
+      <TooltipTrigger asChild>{button}</TooltipTrigger>
+      <TooltipContent
+        side="right"
+        align="center"
+        hidden={state !== "collapsed" || isMobile}
+        {...tooltip}
+      />
+    </Tooltip>
+  );
+}
+
+function SidebarMenuAction({
+  className,
+  asChild = false,
+  showOnHover = false,
+  ...props
+}: React.ComponentProps<"button"> & {
+  asChild?: boolean;
+  showOnHover?: boolean;
+}) {
+  const Comp = asChild ? Slot : "button";
+
+  return (
+    <Comp
+      data-slot="sidebar-menu-action"
+      data-sidebar="menu-action"
+      className={cn(
+        "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
+        // Increases the hit area of the button on mobile.
+        "after:absolute after:-inset-2 md:after:hidden",
+        "peer-data-[size=sm]/menu-button:top-1",
+        "peer-data-[size=default]/menu-button:top-1.5",
+        "peer-data-[size=lg]/menu-button:top-2.5",
+        "group-data-[collapsible=icon]:hidden",
+        showOnHover &&
+          "peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function SidebarMenuBadge({
+  className,
+  ...props
+}: React.ComponentProps<"div">) {
+  return (
+    <div
+      data-slot="sidebar-menu-badge"
+      data-sidebar="menu-badge"
+      className={cn(
+        "text-sidebar-foreground pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums select-none",
+        "peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground",
+        "peer-data-[size=sm]/menu-button:top-1",
+        "peer-data-[size=default]/menu-button:top-1.5",
+        "peer-data-[size=lg]/menu-button:top-2.5",
+        "group-data-[collapsible=icon]:hidden",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function SidebarMenuSkeleton({
+  className,
+  showIcon = false,
+  ...props
+}: React.ComponentProps<"div"> & {
+  showIcon?: boolean;
+}) {
+  // Random width between 50 to 90%.
+  const width = React.useMemo(() => {
+    return `${Math.floor(Math.random() * 40) + 50}%`;
+  }, []);
+
+  return (
+    <div
+      data-slot="sidebar-menu-skeleton"
+      data-sidebar="menu-skeleton"
+      className={cn("flex h-8 items-center gap-2 rounded-md px-2", className)}
+      {...props}
+    >
+      {showIcon && (
+        <Skeleton
+          className="size-4 rounded-md"
+          data-sidebar="menu-skeleton-icon"
+        />
+      )}
+      <Skeleton
+        className="h-4 max-w-(--skeleton-width) flex-1"
+        data-sidebar="menu-skeleton-text"
+        style={
+          {
+            "--skeleton-width": width,
+          } as React.CSSProperties
+        }
+      />
+    </div>
+  );
+}
+
+function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
+  return (
+    <ul
+      data-slot="sidebar-menu-sub"
+      data-sidebar="menu-sub"
+      className={cn(
+        "border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5",
+        "group-data-[collapsible=icon]:hidden",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function SidebarMenuSubItem({
+  className,
+  ...props
+}: React.ComponentProps<"li">) {
+  return (
+    <li
+      data-slot="sidebar-menu-sub-item"
+      data-sidebar="menu-sub-item"
+      className={cn("group/menu-sub-item relative", className)}
+      {...props}
+    />
+  );
+}
+
+function SidebarMenuSubButton({
+  asChild = false,
+  size = "md",
+  isActive = false,
+  className,
+  ...props
+}: React.ComponentProps<"a"> & {
+  asChild?: boolean;
+  size?: "sm" | "md";
+  isActive?: boolean;
+}) {
+  const Comp = asChild ? Slot : "a";
+
+  return (
+    <Comp
+      data-slot="sidebar-menu-sub-button"
+      data-sidebar="menu-sub-button"
+      data-size={size}
+      data-active={isActive}
+      className={cn(
+        "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
+        "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
+        size === "sm" && "text-xs",
+        size === "md" && "text-sm",
+        "group-data-[collapsible=icon]:hidden",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+export {
+  Sidebar,
+  SidebarContent,
+  SidebarFooter,
+  SidebarGroup,
+  SidebarGroupAction,
+  SidebarGroupContent,
+  SidebarGroupLabel,
+  SidebarHeader,
+  SidebarInput,
+  SidebarInset,
+  SidebarMenu,
+  SidebarMenuAction,
+  SidebarMenuBadge,
+  SidebarMenuButton,
+  SidebarMenuItem,
+  SidebarMenuSkeleton,
+  SidebarMenuSub,
+  SidebarMenuSubButton,
+  SidebarMenuSubItem,
+  SidebarProvider,
+  SidebarRail,
+  SidebarSeparator,
+  SidebarTrigger,
+  useSidebar,
+};

+ 13 - 0
src/components/ui/skeleton.tsx

@@ -0,0 +1,13 @@
+import { cn } from "./utils";
+
+function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
+  return (
+    <div
+      data-slot="skeleton"
+      className={cn("bg-accent animate-pulse rounded-md", className)}
+      {...props}
+    />
+  );
+}
+
+export { Skeleton };

+ 63 - 0
src/components/ui/slider.tsx

@@ -0,0 +1,63 @@
+"use client";
+
+import * as React from "react";
+import * as SliderPrimitive from "@radix-ui/react-slider@1.2.3";
+
+import { cn } from "./utils";
+
+function Slider({
+  className,
+  defaultValue,
+  value,
+  min = 0,
+  max = 100,
+  ...props
+}: React.ComponentProps<typeof SliderPrimitive.Root>) {
+  const _values = React.useMemo(
+    () =>
+      Array.isArray(value)
+        ? value
+        : Array.isArray(defaultValue)
+          ? defaultValue
+          : [min, max],
+    [value, defaultValue, min, max],
+  );
+
+  return (
+    <SliderPrimitive.Root
+      data-slot="slider"
+      defaultValue={defaultValue}
+      value={value}
+      min={min}
+      max={max}
+      className={cn(
+        "relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col",
+        className,
+      )}
+      {...props}
+    >
+      <SliderPrimitive.Track
+        data-slot="slider-track"
+        className={cn(
+          "bg-muted relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-4 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5",
+        )}
+      >
+        <SliderPrimitive.Range
+          data-slot="slider-range"
+          className={cn(
+            "bg-primary absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full",
+          )}
+        />
+      </SliderPrimitive.Track>
+      {Array.from({ length: _values.length }, (_, index) => (
+        <SliderPrimitive.Thumb
+          data-slot="slider-thumb"
+          key={index}
+          className="border-primary bg-background ring-ring/50 block size-4 shrink-0 rounded-full border shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
+        />
+      ))}
+    </SliderPrimitive.Root>
+  );
+}
+
+export { Slider };

+ 25 - 0
src/components/ui/sonner.tsx

@@ -0,0 +1,25 @@
+"use client";
+
+import { useTheme } from "next-themes@0.4.6";
+import { Toaster as Sonner, ToasterProps } from "sonner@2.0.3";
+
+const Toaster = ({ ...props }: ToasterProps) => {
+  const { theme = "system" } = useTheme();
+
+  return (
+    <Sonner
+      theme={theme as ToasterProps["theme"]}
+      className="toaster group"
+      style={
+        {
+          "--normal-bg": "var(--popover)",
+          "--normal-text": "var(--popover-foreground)",
+          "--normal-border": "var(--border)",
+        } as React.CSSProperties
+      }
+      {...props}
+    />
+  );
+};
+
+export { Toaster };

+ 31 - 0
src/components/ui/switch.tsx

@@ -0,0 +1,31 @@
+"use client";
+
+import * as React from "react";
+import * as SwitchPrimitive from "@radix-ui/react-switch@1.1.3";
+
+import { cn } from "./utils";
+
+function Switch({
+  className,
+  ...props
+}: React.ComponentProps<typeof SwitchPrimitive.Root>) {
+  return (
+    <SwitchPrimitive.Root
+      data-slot="switch"
+      className={cn(
+        "peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-switch-background focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
+        className,
+      )}
+      {...props}
+    >
+      <SwitchPrimitive.Thumb
+        data-slot="switch-thumb"
+        className={cn(
+          "bg-card dark:data-[state=unchecked]:bg-card-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0",
+        )}
+      />
+    </SwitchPrimitive.Root>
+  );
+}
+
+export { Switch };

+ 116 - 0
src/components/ui/table.tsx

@@ -0,0 +1,116 @@
+"use client";
+
+import * as React from "react";
+
+import { cn } from "./utils";
+
+function Table({ className, ...props }: React.ComponentProps<"table">) {
+  return (
+    <div
+      data-slot="table-container"
+      className="relative w-full overflow-x-auto"
+    >
+      <table
+        data-slot="table"
+        className={cn("w-full caption-bottom text-sm", className)}
+        {...props}
+      />
+    </div>
+  );
+}
+
+function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
+  return (
+    <thead
+      data-slot="table-header"
+      className={cn("[&_tr]:border-b", className)}
+      {...props}
+    />
+  );
+}
+
+function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
+  return (
+    <tbody
+      data-slot="table-body"
+      className={cn("[&_tr:last-child]:border-0", className)}
+      {...props}
+    />
+  );
+}
+
+function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
+  return (
+    <tfoot
+      data-slot="table-footer"
+      className={cn(
+        "bg-muted/50 border-t font-medium [&>tr]:last:border-b-0",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
+  return (
+    <tr
+      data-slot="table-row"
+      className={cn(
+        "hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function TableHead({ className, ...props }: React.ComponentProps<"th">) {
+  return (
+    <th
+      data-slot="table-head"
+      className={cn(
+        "text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function TableCell({ className, ...props }: React.ComponentProps<"td">) {
+  return (
+    <td
+      data-slot="table-cell"
+      className={cn(
+        "p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function TableCaption({
+  className,
+  ...props
+}: React.ComponentProps<"caption">) {
+  return (
+    <caption
+      data-slot="table-caption"
+      className={cn("text-muted-foreground mt-4 text-sm", className)}
+      {...props}
+    />
+  );
+}
+
+export {
+  Table,
+  TableHeader,
+  TableBody,
+  TableFooter,
+  TableHead,
+  TableRow,
+  TableCell,
+  TableCaption,
+};

+ 66 - 0
src/components/ui/tabs.tsx

@@ -0,0 +1,66 @@
+"use client";
+
+import * as React from "react";
+import * as TabsPrimitive from "@radix-ui/react-tabs@1.1.3";
+
+import { cn } from "./utils";
+
+function Tabs({
+  className,
+  ...props
+}: React.ComponentProps<typeof TabsPrimitive.Root>) {
+  return (
+    <TabsPrimitive.Root
+      data-slot="tabs"
+      className={cn("flex flex-col gap-2", className)}
+      {...props}
+    />
+  );
+}
+
+function TabsList({
+  className,
+  ...props
+}: React.ComponentProps<typeof TabsPrimitive.List>) {
+  return (
+    <TabsPrimitive.List
+      data-slot="tabs-list"
+      className={cn(
+        "bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-xl p-[3px] flex",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function TabsTrigger({
+  className,
+  ...props
+}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
+  return (
+    <TabsPrimitive.Trigger
+      data-slot="tabs-trigger"
+      className={cn(
+        "data-[state=active]:bg-card dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-xl border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+function TabsContent({
+  className,
+  ...props
+}: React.ComponentProps<typeof TabsPrimitive.Content>) {
+  return (
+    <TabsPrimitive.Content
+      data-slot="tabs-content"
+      className={cn("flex-1 outline-none", className)}
+      {...props}
+    />
+  );
+}
+
+export { Tabs, TabsList, TabsTrigger, TabsContent };

+ 18 - 0
src/components/ui/textarea.tsx

@@ -0,0 +1,18 @@
+import * as React from "react";
+
+import { cn } from "./utils";
+
+function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
+  return (
+    <textarea
+      data-slot="textarea"
+      className={cn(
+        "resize-none border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-input-background px-3 py-2 text-base transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
+        className,
+      )}
+      {...props}
+    />
+  );
+}
+
+export { Textarea };

+ 73 - 0
src/components/ui/toggle-group.tsx

@@ -0,0 +1,73 @@
+"use client";
+
+import * as React from "react";
+import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group@1.1.2";
+import { type VariantProps } from "class-variance-authority@0.7.1";
+
+import { cn } from "./utils";
+import { toggleVariants } from "./toggle";
+
+const ToggleGroupContext = React.createContext<
+  VariantProps<typeof toggleVariants>
+>({
+  size: "default",
+  variant: "default",
+});
+
+function ToggleGroup({
+  className,
+  variant,
+  size,
+  children,
+  ...props
+}: React.ComponentProps<typeof ToggleGroupPrimitive.Root> &
+  VariantProps<typeof toggleVariants>) {
+  return (
+    <ToggleGroupPrimitive.Root
+      data-slot="toggle-group"
+      data-variant={variant}
+      data-size={size}
+      className={cn(
+        "group/toggle-group flex w-fit items-center rounded-md data-[variant=outline]:shadow-xs",
+        className,
+      )}
+      {...props}
+    >
+      <ToggleGroupContext.Provider value={{ variant, size }}>
+        {children}
+      </ToggleGroupContext.Provider>
+    </ToggleGroupPrimitive.Root>
+  );
+}
+
+function ToggleGroupItem({
+  className,
+  children,
+  variant,
+  size,
+  ...props
+}: React.ComponentProps<typeof ToggleGroupPrimitive.Item> &
+  VariantProps<typeof toggleVariants>) {
+  const context = React.useContext(ToggleGroupContext);
+
+  return (
+    <ToggleGroupPrimitive.Item
+      data-slot="toggle-group-item"
+      data-variant={context.variant || variant}
+      data-size={context.size || size}
+      className={cn(
+        toggleVariants({
+          variant: context.variant || variant,
+          size: context.size || size,
+        }),
+        "min-w-0 flex-1 shrink-0 rounded-none shadow-none first:rounded-l-md last:rounded-r-md focus:z-10 focus-visible:z-10 data-[variant=outline]:border-l-0 data-[variant=outline]:first:border-l",
+        className,
+      )}
+      {...props}
+    >
+      {children}
+    </ToggleGroupPrimitive.Item>
+  );
+}
+
+export { ToggleGroup, ToggleGroupItem };

+ 47 - 0
src/components/ui/toggle.tsx

@@ -0,0 +1,47 @@
+"use client";
+
+import * as React from "react";
+import * as TogglePrimitive from "@radix-ui/react-toggle@1.1.2";
+import { cva, type VariantProps } from "class-variance-authority@0.7.1";
+
+import { cn } from "./utils";
+
+const toggleVariants = cva(
+  "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap",
+  {
+    variants: {
+      variant: {
+        default: "bg-transparent",
+        outline:
+          "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
+      },
+      size: {
+        default: "h-9 px-2 min-w-9",
+        sm: "h-8 px-1.5 min-w-8",
+        lg: "h-10 px-2.5 min-w-10",
+      },
+    },
+    defaultVariants: {
+      variant: "default",
+      size: "default",
+    },
+  },
+);
+
+function Toggle({
+  className,
+  variant,
+  size,
+  ...props
+}: React.ComponentProps<typeof TogglePrimitive.Root> &
+  VariantProps<typeof toggleVariants>) {
+  return (
+    <TogglePrimitive.Root
+      data-slot="toggle"
+      className={cn(toggleVariants({ variant, size, className }))}
+      {...props}
+    />
+  );
+}
+
+export { Toggle, toggleVariants };

+ 61 - 0
src/components/ui/tooltip.tsx

@@ -0,0 +1,61 @@
+"use client";
+
+import * as React from "react";
+import * as TooltipPrimitive from "@radix-ui/react-tooltip@1.1.8";
+
+import { cn } from "./utils";
+
+function TooltipProvider({
+  delayDuration = 0,
+  ...props
+}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
+  return (
+    <TooltipPrimitive.Provider
+      data-slot="tooltip-provider"
+      delayDuration={delayDuration}
+      {...props}
+    />
+  );
+}
+
+function Tooltip({
+  ...props
+}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
+  return (
+    <TooltipProvider>
+      <TooltipPrimitive.Root data-slot="tooltip" {...props} />
+    </TooltipProvider>
+  );
+}
+
+function TooltipTrigger({
+  ...props
+}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
+  return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
+}
+
+function TooltipContent({
+  className,
+  sideOffset = 0,
+  children,
+  ...props
+}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
+  return (
+    <TooltipPrimitive.Portal>
+      <TooltipPrimitive.Content
+        data-slot="tooltip-content"
+        sideOffset={sideOffset}
+        className={cn(
+          "bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
+          className,
+        )}
+        {...props}
+      >
+        {children}
+        <TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
+      </TooltipPrimitive.Content>
+    </TooltipPrimitive.Portal>
+  );
+}
+
+export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };

+ 21 - 0
src/components/ui/use-mobile.ts

@@ -0,0 +1,21 @@
+import * as React from "react";
+
+const MOBILE_BREAKPOINT = 768;
+
+export function useIsMobile() {
+  const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
+    undefined,
+  );
+
+  React.useEffect(() => {
+    const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
+    const onChange = () => {
+      setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
+    };
+    mql.addEventListener("change", onChange);
+    setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
+    return () => mql.removeEventListener("change", onChange);
+  }, []);
+
+  return !!isMobile;
+}

+ 6 - 0
src/components/ui/utils.ts

@@ -0,0 +1,6 @@
+import { clsx, type ClassValue } from "clsx";
+import { twMerge } from "tailwind-merge";
+
+export function cn(...inputs: ClassValue[]) {
+  return twMerge(clsx(inputs));
+}

+ 34 - 0
src/contexts/LanguageContext.tsx

@@ -0,0 +1,34 @@
+import React, { createContext, useContext, useState, ReactNode } from 'react';
+import { Language, translations } from '../lib/i18n';
+
+interface LanguageContextType {
+  language: Language;
+  setLanguage: (lang: Language) => void;
+  t: typeof translations.zh;
+}
+
+const LanguageContext = createContext<LanguageContextType | undefined>(undefined);
+
+export function LanguageProvider({ children }: { children: ReactNode }) {
+  const [language, setLanguage] = useState<Language>('en');
+
+  const value: LanguageContextType = {
+    language,
+    setLanguage,
+    t: translations[language],
+  };
+
+  return (
+    <LanguageContext.Provider value={value}>
+      {children}
+    </LanguageContext.Provider>
+  );
+}
+
+export function useLanguage() {
+  const context = useContext(LanguageContext);
+  if (context === undefined) {
+    throw new Error('useLanguage must be used within a LanguageProvider');
+  }
+  return context;
+}

+ 154 - 0
src/contexts/RouterContext.tsx

@@ -0,0 +1,154 @@
+import React, { createContext, useContext, useState, ReactNode, useEffect } from 'react';
+
+type PageType = 'home' | 'about' | 'blog' | 'careers' | 'privacy' | 'terms' | 'faq' | 'contact';
+
+interface RouterContextType {
+  currentPage: PageType;
+  navigateTo: (page: PageType, hash?: string) => void;
+}
+
+const RouterContext = createContext<RouterContextType | undefined>(undefined);
+
+// Map page types to URL paths
+const pageToPath: Record<PageType, string> = {
+  home: '/',
+  about: '/about',
+  blog: '/blog',
+  careers: '/careers',
+  privacy: '/privacy',
+  terms: '/terms',
+  faq: '/faq',
+  contact: '/contact',
+};
+
+// Map URL paths to page types
+const pathToPage: Record<string, PageType> = {
+  '/': 'home',
+  '/about': 'about',
+  '/blog': 'blog',
+  '/careers': 'careers',
+  '/privacy': 'privacy',
+  '/terms': 'terms',
+  '/faq': 'faq',
+  '/contact': 'contact',
+};
+
+// Helper function to scroll to an element with retry logic
+function scrollToElement(elementId: string, initialDelay: number = 150) {
+  let attempts = 0;
+  const maxAttempts = 10;
+  
+  const tryScroll = () => {
+    const element = document.getElementById(elementId);
+    if (element) {
+      element.scrollIntoView({ behavior: 'smooth', block: 'start' });
+      return true;
+    }
+    return false;
+  };
+  
+  // Try immediately
+  setTimeout(() => {
+    if (!tryScroll()) {
+      // If element not found, retry with increasing delays
+      const retryInterval = setInterval(() => {
+        attempts++;
+        if (tryScroll() || attempts >= maxAttempts) {
+          clearInterval(retryInterval);
+        }
+      }, 100);
+    }
+  }, initialDelay);
+}
+
+export function RouterProvider({ children }: { children: ReactNode }) {
+  // Initialize page based on current URL path
+  const getInitialPage = (): PageType => {
+    const path = window.location.pathname;
+    return pathToPage[path] || 'home';
+  };
+
+  const [currentPage, setCurrentPage] = useState<PageType>(getInitialPage);
+  
+  // Handle initial page load
+  useEffect(() => {
+    const pathname = window.location.pathname;
+    
+    // Ensure pathname is normalized
+    if (pathname === '' || pathname === null) {
+      window.history.replaceState({ page: 'home' }, '', '/');
+    }
+  }, []);
+
+  const navigateTo = (page: PageType, hash?: string) => {
+    setCurrentPage(page);
+    
+    const path = pageToPath[page];
+    const url = hash ? `${path}#${hash}` : path;
+    
+    // Update browser URL without reloading
+    window.history.pushState({ page }, '', url);
+    
+    if (hash) {
+      // If there's a hash, scroll to the section
+      scrollToElement(hash, 150);
+    } else {
+      window.scrollTo({ top: 0, behavior: 'smooth' });
+    }
+  };
+
+  // Handle browser back/forward buttons
+  useEffect(() => {
+    const handlePopState = (event: PopStateEvent) => {
+      const path = window.location.pathname;
+      const page = pathToPage[path] || 'home';
+      setCurrentPage(page);
+      
+      // Handle hash if present
+      const hash = window.location.hash.replace('#', '');
+      if (hash) {
+        scrollToElement(hash);
+      } else {
+        window.scrollTo({ top: 0, behavior: 'smooth' });
+      }
+    };
+
+    window.addEventListener('popstate', handlePopState);
+    
+    return () => {
+      window.removeEventListener('popstate', handlePopState);
+    };
+  }, []);
+
+  // Handle initial hash on page load (separate effect to run after render)
+  useEffect(() => {
+    const hash = window.location.hash.replace('#', '');
+    if (hash) {
+      // Use longer delay for initial page load to ensure all components are mounted
+      scrollToElement(hash, 800);
+    }
+  }, [currentPage]);
+  
+  // Additional effect to handle hash on first mount
+  useEffect(() => {
+    const hash = window.location.hash.replace('#', '');
+    if (hash) {
+      // Extra attempt with even longer delay for slow network connections
+      scrollToElement(hash, 1200);
+    }
+  }, []);
+
+  return (
+    <RouterContext.Provider value={{ currentPage, navigateTo }}>
+      {children}
+    </RouterContext.Provider>
+  );
+}
+
+export function useRouter() {
+  const context = useContext(RouterContext);
+  if (context === undefined) {
+    throw new Error('useRouter must be used within a RouterProvider');
+  }
+  return context;
+}

+ 61 - 0
src/guidelines/Guidelines.md

@@ -0,0 +1,61 @@
+**Add your own guidelines here**
+<!--
+
+System Guidelines
+
+Use this file to provide the AI with rules and guidelines you want it to follow.
+This template outlines a few examples of things you can add. You can add your own sections and format it to suit your needs
+
+TIP: More context isn't always better. It can confuse the LLM. Try and add the most important rules you need
+
+# General guidelines
+
+Any general rules you want the AI to follow.
+For example:
+
+* Only use absolute positioning when necessary. Opt for responsive and well structured layouts that use flexbox and grid by default
+* Refactor code as you go to keep code clean
+* Keep file sizes small and put helper functions and components in their own files.
+
+--------------
+
+# Design system guidelines
+Rules for how the AI should make generations look like your company's design system
+
+Additionally, if you select a design system to use in the prompt box, you can reference
+your design system's components, tokens, variables and components.
+For example:
+
+* Use a base font-size of 14px
+* Date formats should always be in the format “Jun 10”
+* The bottom toolbar should only ever have a maximum of 4 items
+* Never use the floating action button with the bottom toolbar
+* Chips should always come in sets of 3 or more
+* Don't use a dropdown if there are 2 or fewer options
+
+You can also create sub sections and add more specific details
+For example:
+
+
+## Button
+The Button component is a fundamental interactive element in our design system, designed to trigger actions or navigate
+users through the application. It provides visual feedback and clear affordances to enhance user experience.
+
+### Usage
+Buttons should be used for important actions that users need to take, such as form submissions, confirming choices,
+or initiating processes. They communicate interactivity and should have clear, action-oriented labels.
+
+### Variants
+* Primary Button
+  * Purpose : Used for the main action in a section or page
+  * Visual Style : Bold, filled with the primary brand color
+  * Usage : One primary button per section to guide users toward the most important action
+* Secondary Button
+  * Purpose : Used for alternative or supporting actions
+  * Visual Style : Outlined with the primary color, transparent background
+  * Usage : Can appear alongside a primary button for less important actions
+* Tertiary Button
+  * Purpose : Used for the least important actions
+  * Visual Style : Text-only with no border, using primary color
+  * Usage : For actions that should be available but not emphasized
+-->

+ 3060 - 0
src/index.css

@@ -0,0 +1,3060 @@
+/*! tailwindcss v4.1.3 | MIT License | https://tailwindcss.com */
+@layer properties {
+  @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) {
+    *, :before, :after, ::backdrop {
+      --tw-translate-x: 0;
+      --tw-translate-y: 0;
+      --tw-translate-z: 0;
+      --tw-space-y-reverse: 0;
+      --tw-border-style: solid;
+      --tw-gradient-position: initial;
+      --tw-gradient-from: #0000;
+      --tw-gradient-via: #0000;
+      --tw-gradient-to: #0000;
+      --tw-gradient-stops: initial;
+      --tw-gradient-via-stops: initial;
+      --tw-gradient-from-position: 0%;
+      --tw-gradient-via-position: 50%;
+      --tw-gradient-to-position: 100%;
+      --tw-leading: initial;
+      --tw-font-weight: initial;
+      --tw-tracking: initial;
+      --tw-shadow: 0 0 #0000;
+      --tw-shadow-color: initial;
+      --tw-shadow-alpha: 100%;
+      --tw-inset-shadow: 0 0 #0000;
+      --tw-inset-shadow-color: initial;
+      --tw-inset-shadow-alpha: 100%;
+      --tw-ring-color: initial;
+      --tw-ring-shadow: 0 0 #0000;
+      --tw-inset-ring-color: initial;
+      --tw-inset-ring-shadow: 0 0 #0000;
+      --tw-ring-inset: initial;
+      --tw-ring-offset-width: 0px;
+      --tw-ring-offset-color: #fff;
+      --tw-ring-offset-shadow: 0 0 #0000;
+      --tw-outline-style: solid;
+      --tw-blur: initial;
+      --tw-brightness: initial;
+      --tw-contrast: initial;
+      --tw-grayscale: initial;
+      --tw-hue-rotate: initial;
+      --tw-invert: initial;
+      --tw-opacity: initial;
+      --tw-saturate: initial;
+      --tw-sepia: initial;
+      --tw-drop-shadow: initial;
+      --tw-drop-shadow-color: initial;
+      --tw-drop-shadow-alpha: 100%;
+      --tw-drop-shadow-size: initial;
+      --tw-backdrop-blur: initial;
+      --tw-backdrop-brightness: initial;
+      --tw-backdrop-contrast: initial;
+      --tw-backdrop-grayscale: initial;
+      --tw-backdrop-hue-rotate: initial;
+      --tw-backdrop-invert: initial;
+      --tw-backdrop-opacity: initial;
+      --tw-backdrop-saturate: initial;
+      --tw-backdrop-sepia: initial;
+      --tw-duration: initial;
+      --tw-scale-x: 1;
+      --tw-scale-y: 1;
+      --tw-scale-z: 1;
+    }
+  }
+}
+
+@layer theme {
+  :root, :host {
+    --font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+    --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+    --color-yellow-500: oklch(.795 .184 86.047);
+    --color-gray-50: oklch(.985 .002 247.839);
+    --color-gray-100: oklch(.967 .003 264.542);
+    --color-gray-200: oklch(.928 .006 264.531);
+    --color-gray-300: oklch(.872 .01 258.338);
+    --color-gray-500: oklch(.551 .027 264.364);
+    --color-gray-600: oklch(.446 .03 256.802);
+    --color-gray-700: oklch(.373 .034 259.733);
+    --color-gray-800: oklch(.278 .033 256.848);
+    --color-gray-900: oklch(.21 .034 264.665);
+    --color-black: #000;
+    --color-white: #fff;
+    --spacing: .25rem;
+    --container-md: 28rem;
+    --container-xl: 36rem;
+    --container-2xl: 42rem;
+    --container-3xl: 48rem;
+    --container-4xl: 56rem;
+    --container-7xl: 80rem;
+    --text-xs: .75rem;
+    --text-xs--line-height: calc(1 / .75);
+    --text-sm: .875rem;
+    --text-sm--line-height: calc(1.25 / .875);
+    --text-base: 1rem;
+    --text-base--line-height: calc(1.5 / 1);
+    --text-lg: 1.125rem;
+    --text-lg--line-height: calc(1.75 / 1.125);
+    --text-xl: 1.25rem;
+    --text-xl--line-height: calc(1.75 / 1.25);
+    --text-2xl: 1.5rem;
+    --text-2xl--line-height: calc(2 / 1.5);
+    --text-3xl: 1.875rem;
+    --text-3xl--line-height: calc(2.25 / 1.875);
+    --text-4xl: 2.25rem;
+    --text-4xl--line-height: calc(2.5 / 2.25);
+    --text-5xl: 3rem;
+    --text-5xl--line-height: 1;
+    --text-6xl: 3.75rem;
+    --text-6xl--line-height: 1;
+    --text-7xl: 4.5rem;
+    --text-7xl--line-height: 1;
+    --font-weight-normal: 400;
+    --font-weight-medium: 500;
+    --font-weight-semibold: 600;
+    --tracking-widest: .1em;
+    --leading-tight: 1.25;
+    --leading-relaxed: 1.625;
+    --radius-2xl: 1rem;
+    --radius-3xl: 1.5rem;
+    --blur-sm: 8px;
+    --blur-3xl: 64px;
+    --default-transition-duration: .15s;
+    --default-transition-timing-function: cubic-bezier(.4, 0, .2, 1);
+    --default-font-family: var(--font-sans);
+    --default-font-feature-settings: var(--font-sans--font-feature-settings);
+    --default-font-variation-settings: var(--font-sans--font-variation-settings);
+    --default-mono-font-family: var(--font-mono);
+    --default-mono-font-feature-settings: var(--font-mono--font-feature-settings);
+    --default-mono-font-variation-settings: var(--font-mono--font-variation-settings);
+  }
+}
+
+@layer base {
+  *, :after, :before, ::backdrop {
+    box-sizing: border-box;
+    border: 0 solid;
+    margin: 0;
+    padding: 0;
+  }
+
+  ::file-selector-button {
+    box-sizing: border-box;
+    border: 0 solid;
+    margin: 0;
+    padding: 0;
+  }
+
+  html, :host {
+    -webkit-text-size-adjust: 100%;
+    tab-size: 4;
+    line-height: 1.5;
+    font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");
+    font-feature-settings: var(--default-font-feature-settings, normal);
+    font-variation-settings: var(--default-font-variation-settings, normal);
+    -webkit-tap-highlight-color: transparent;
+  }
+
+  body {
+    line-height: inherit;
+  }
+
+  hr {
+    height: 0;
+    color: inherit;
+    border-top-width: 1px;
+  }
+
+  abbr:where([title]) {
+    -webkit-text-decoration: underline dotted;
+    text-decoration: underline dotted;
+  }
+
+  h1, h2, h3, h4, h5, h6 {
+    font-size: inherit;
+    font-weight: inherit;
+  }
+
+  a {
+    color: inherit;
+    -webkit-text-decoration: inherit;
+    -webkit-text-decoration: inherit;
+    text-decoration: inherit;
+  }
+
+  b, strong {
+    font-weight: bolder;
+  }
+
+  code, kbd, samp, pre {
+    font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);
+    font-feature-settings: var(--default-mono-font-feature-settings, normal);
+    font-variation-settings: var(--default-mono-font-variation-settings, normal);
+    font-size: 1em;
+  }
+
+  small {
+    font-size: 80%;
+  }
+
+  sub, sup {
+    vertical-align: baseline;
+    font-size: 75%;
+    line-height: 0;
+    position: relative;
+  }
+
+  sub {
+    bottom: -.25em;
+  }
+
+  sup {
+    top: -.5em;
+  }
+
+  table {
+    text-indent: 0;
+    border-color: inherit;
+    border-collapse: collapse;
+  }
+
+  :-moz-focusring {
+    outline: auto;
+  }
+
+  progress {
+    vertical-align: baseline;
+  }
+
+  summary {
+    display: list-item;
+  }
+
+  ol, ul, menu {
+    list-style: none;
+  }
+
+  img, svg, video, canvas, audio, iframe, embed, object {
+    vertical-align: middle;
+    display: block;
+  }
+
+  img, video {
+    max-width: 100%;
+    height: auto;
+  }
+
+  button, input, select, optgroup, textarea {
+    font: inherit;
+    font-feature-settings: inherit;
+    font-variation-settings: inherit;
+    letter-spacing: inherit;
+    color: inherit;
+    opacity: 1;
+    background-color: #0000;
+    border-radius: 0;
+  }
+
+  ::file-selector-button {
+    font: inherit;
+    font-feature-settings: inherit;
+    font-variation-settings: inherit;
+    letter-spacing: inherit;
+    color: inherit;
+    opacity: 1;
+    background-color: #0000;
+    border-radius: 0;
+  }
+
+  :where(select:is([multiple], [size])) optgroup {
+    font-weight: bolder;
+  }
+
+  :where(select:is([multiple], [size])) optgroup option {
+    padding-inline-start: 20px;
+  }
+
+  ::file-selector-button {
+    margin-inline-end: 4px;
+  }
+
+  ::placeholder {
+    opacity: 1;
+    color: currentColor;
+  }
+
+  @supports (color: color-mix(in lab, red, red)) {
+    ::placeholder {
+      color: color-mix(in oklab, currentColor 50%, transparent);
+    }
+  }
+
+  textarea {
+    resize: vertical;
+  }
+
+  ::-webkit-search-decoration {
+    -webkit-appearance: none;
+  }
+
+  ::-webkit-date-and-time-value {
+    min-height: 1lh;
+    text-align: inherit;
+  }
+
+  ::-webkit-datetime-edit {
+    display: inline-flex;
+  }
+
+  ::-webkit-datetime-edit-fields-wrapper {
+    padding: 0;
+  }
+
+  ::-webkit-datetime-edit {
+    padding-block: 0;
+  }
+
+  ::-webkit-datetime-edit-year-field {
+    padding-block: 0;
+  }
+
+  ::-webkit-datetime-edit-month-field {
+    padding-block: 0;
+  }
+
+  ::-webkit-datetime-edit-day-field {
+    padding-block: 0;
+  }
+
+  ::-webkit-datetime-edit-hour-field {
+    padding-block: 0;
+  }
+
+  ::-webkit-datetime-edit-minute-field {
+    padding-block: 0;
+  }
+
+  ::-webkit-datetime-edit-second-field {
+    padding-block: 0;
+  }
+
+  ::-webkit-datetime-edit-millisecond-field {
+    padding-block: 0;
+  }
+
+  ::-webkit-datetime-edit-meridiem-field {
+    padding-block: 0;
+  }
+
+  :-moz-ui-invalid {
+    box-shadow: none;
+  }
+
+  button, input:where([type="button"], [type="reset"], [type="submit"]) {
+    appearance: button;
+  }
+
+  ::file-selector-button {
+    appearance: button;
+  }
+
+  ::-webkit-inner-spin-button {
+    height: auto;
+  }
+
+  ::-webkit-outer-spin-button {
+    height: auto;
+  }
+
+  [hidden]:where(:not([hidden="until-found"])) {
+    display: none !important;
+  }
+
+  * {
+    border-color: var(--border);
+    outline-color: var(--ring);
+  }
+
+  @supports (color: color-mix(in lab, red, red)) {
+    * {
+      outline-color: color-mix(in oklab, var(--ring) 50%, transparent);
+    }
+  }
+
+  * {
+    border-color: var(--border);
+    outline-color: var(--ring);
+  }
+
+  @supports (color: color-mix(in lab, red, red)) {
+    * {
+      outline-color: color-mix(in oklab, var(--ring) 50%, transparent);
+    }
+  }
+
+  body {
+    background-color: var(--background);
+    color: var(--foreground);
+  }
+
+  html {
+    scroll-behavior: smooth;
+  }
+
+  section[id] {
+    scroll-margin-top: 7rem;
+  }
+
+  @media (width >= 640px) {
+    section[id] {
+      scroll-margin-top: 7.5rem;
+    }
+  }
+
+  @media (width >= 1024px) {
+    section[id] {
+      scroll-margin-top: 8rem;
+    }
+  }
+
+  :where(:not(:has([class*=" text-"]), :not(:has([class^="text-"])))) h1 {
+    font-size: var(--text-2xl);
+    font-weight: var(--font-weight-medium);
+    line-height: 1.5;
+  }
+
+  :where(:not(:has([class*=" text-"]), :not(:has([class^="text-"])))) h2 {
+    font-size: var(--text-xl);
+    font-weight: var(--font-weight-medium);
+    line-height: 1.5;
+  }
+
+  :where(:not(:has([class*=" text-"]), :not(:has([class^="text-"])))) h3 {
+    font-size: var(--text-lg);
+    font-weight: var(--font-weight-medium);
+    line-height: 1.5;
+  }
+
+  :where(:not(:has([class*=" text-"]), :not(:has([class^="text-"])))) h4 {
+    font-size: var(--text-base);
+    font-weight: var(--font-weight-medium);
+    line-height: 1.5;
+  }
+
+  :where(:not(:has([class*=" text-"]), :not(:has([class^="text-"])))) p {
+    font-size: var(--text-base);
+    font-weight: var(--font-weight-normal);
+    line-height: 1.5;
+  }
+
+  :where(:not(:has([class*=" text-"]), :not(:has([class^="text-"])))) label, :where(:not(:has([class*=" text-"]), :not(:has([class^="text-"])))) button {
+    font-size: var(--text-base);
+    font-weight: var(--font-weight-medium);
+    line-height: 1.5;
+  }
+
+  :where(:not(:has([class*=" text-"]), :not(:has([class^="text-"])))) input {
+    font-size: var(--text-base);
+    font-weight: var(--font-weight-normal);
+    line-height: 1.5;
+  }
+}
+
+@layer utilities {
+  .\@container\/card-header {
+    container: card-header / inline-size;
+  }
+
+  .pointer-events-none {
+    pointer-events: none;
+  }
+
+  .absolute {
+    position: absolute;
+  }
+
+  .relative {
+    position: relative;
+  }
+
+  .sticky {
+    position: sticky;
+  }
+
+  .inset-0 {
+    inset: calc(var(--spacing) * 0);
+  }
+
+  .top-0 {
+    top: calc(var(--spacing) * 0);
+  }
+
+  .top-4 {
+    top: calc(var(--spacing) * 4);
+  }
+
+  .right-4 {
+    right: calc(var(--spacing) * 4);
+  }
+
+  .left-2 {
+    left: calc(var(--spacing) * 2);
+  }
+
+  .z-50 {
+    z-index: 50;
+  }
+
+  .order-1 {
+    order: 1;
+  }
+
+  .order-2 {
+    order: 2;
+  }
+
+  .col-start-2 {
+    grid-column-start: 2;
+  }
+
+  .row-span-2 {
+    grid-row: span 2 / span 2;
+  }
+
+  .row-start-1 {
+    grid-row-start: 1;
+  }
+
+  .-mx-1 {
+    margin-inline: calc(var(--spacing) * -1);
+  }
+
+  .mx-auto {
+    margin-inline: auto;
+  }
+
+  .my-1 {
+    margin-block: calc(var(--spacing) * 1);
+  }
+
+  .mt-0\.5 {
+    margin-top: calc(var(--spacing) * .5);
+  }
+
+  .mt-2 {
+    margin-top: calc(var(--spacing) * 2);
+  }
+
+  .mt-3 {
+    margin-top: calc(var(--spacing) * 3);
+  }
+
+  .mt-4 {
+    margin-top: calc(var(--spacing) * 4);
+  }
+
+  .mt-8 {
+    margin-top: calc(var(--spacing) * 8);
+  }
+
+  .mt-12 {
+    margin-top: calc(var(--spacing) * 12);
+  }
+
+  .mr-2 {
+    margin-right: calc(var(--spacing) * 2);
+  }
+
+  .mb-1 {
+    margin-bottom: calc(var(--spacing) * 1);
+  }
+
+  .mb-2 {
+    margin-bottom: calc(var(--spacing) * 2);
+  }
+
+  .mb-3 {
+    margin-bottom: calc(var(--spacing) * 3);
+  }
+
+  .mb-4 {
+    margin-bottom: calc(var(--spacing) * 4);
+  }
+
+  .mb-6 {
+    margin-bottom: calc(var(--spacing) * 6);
+  }
+
+  .mb-8 {
+    margin-bottom: calc(var(--spacing) * 8);
+  }
+
+  .mb-10 {
+    margin-bottom: calc(var(--spacing) * 10);
+  }
+
+  .mb-12 {
+    margin-bottom: calc(var(--spacing) * 12);
+  }
+
+  .ml-4 {
+    margin-left: calc(var(--spacing) * 4);
+  }
+
+  .ml-auto {
+    margin-left: auto;
+  }
+
+  .line-clamp-2 {
+    -webkit-line-clamp: 2;
+    -webkit-box-orient: vertical;
+    display: -webkit-box;
+    overflow: hidden;
+  }
+
+  .line-clamp-3 {
+    -webkit-line-clamp: 3;
+    -webkit-box-orient: vertical;
+    display: -webkit-box;
+    overflow: hidden;
+  }
+
+  .block {
+    display: block;
+  }
+
+  .flex {
+    display: flex;
+  }
+
+  .grid {
+    display: grid;
+  }
+
+  .hidden {
+    display: none;
+  }
+
+  .inline-block {
+    display: inline-block;
+  }
+
+  .inline-flex {
+    display: inline-flex;
+  }
+
+  .field-sizing-content {
+    field-sizing: content;
+  }
+
+  .size-2 {
+    width: calc(var(--spacing) * 2);
+    height: calc(var(--spacing) * 2);
+  }
+
+  .size-3\.5 {
+    width: calc(var(--spacing) * 3.5);
+    height: calc(var(--spacing) * 3.5);
+  }
+
+  .size-4 {
+    width: calc(var(--spacing) * 4);
+    height: calc(var(--spacing) * 4);
+  }
+
+  .size-9 {
+    width: calc(var(--spacing) * 9);
+    height: calc(var(--spacing) * 9);
+  }
+
+  .h-4 {
+    height: calc(var(--spacing) * 4);
+  }
+
+  .h-5 {
+    height: calc(var(--spacing) * 5);
+  }
+
+  .h-6 {
+    height: calc(var(--spacing) * 6);
+  }
+
+  .h-8 {
+    height: calc(var(--spacing) * 8);
+  }
+
+  .h-9 {
+    height: calc(var(--spacing) * 9);
+  }
+
+  .h-10 {
+    height: calc(var(--spacing) * 10);
+  }
+
+  .h-11 {
+    height: calc(var(--spacing) * 11);
+  }
+
+  .h-12 {
+    height: calc(var(--spacing) * 12);
+  }
+
+  .h-14 {
+    height: calc(var(--spacing) * 14);
+  }
+
+  .h-16 {
+    height: calc(var(--spacing) * 16);
+  }
+
+  .h-48 {
+    height: calc(var(--spacing) * 48);
+  }
+
+  .h-64 {
+    height: calc(var(--spacing) * 64);
+  }
+
+  .h-auto {
+    height: auto;
+  }
+
+  .h-full {
+    height: 100%;
+  }
+
+  .h-px {
+    height: 1px;
+  }
+
+  .max-h-\(--radix-dropdown-menu-content-available-height\) {
+    max-height: var(--radix-dropdown-menu-content-available-height);
+  }
+
+  .min-h-16 {
+    min-height: calc(var(--spacing) * 16);
+  }
+
+  .min-h-screen {
+    min-height: 100vh;
+  }
+
+  .w-4 {
+    width: calc(var(--spacing) * 4);
+  }
+
+  .w-5 {
+    width: calc(var(--spacing) * 5);
+  }
+
+  .w-6 {
+    width: calc(var(--spacing) * 6);
+  }
+
+  .w-9 {
+    width: calc(var(--spacing) * 9);
+  }
+
+  .w-10 {
+    width: calc(var(--spacing) * 10);
+  }
+
+  .w-12 {
+    width: calc(var(--spacing) * 12);
+  }
+
+  .w-16 {
+    width: calc(var(--spacing) * 16);
+  }
+
+  .w-fit {
+    width: fit-content;
+  }
+
+  .w-full {
+    width: 100%;
+  }
+
+  .max-w-2xl {
+    max-width: var(--container-2xl);
+  }
+
+  .max-w-3xl {
+    max-width: var(--container-3xl);
+  }
+
+  .max-w-4xl {
+    max-width: var(--container-4xl);
+  }
+
+  .max-w-7xl {
+    max-width: var(--container-7xl);
+  }
+
+  .max-w-md {
+    max-width: var(--container-md);
+  }
+
+  .max-w-none {
+    max-width: none;
+  }
+
+  .max-w-xl {
+    max-width: var(--container-xl);
+  }
+
+  .min-w-0 {
+    min-width: calc(var(--spacing) * 0);
+  }
+
+  .min-w-\[8rem\] {
+    min-width: 8rem;
+  }
+
+  .flex-1 {
+    flex: 1;
+  }
+
+  .flex-shrink-0, .shrink-0 {
+    flex-shrink: 0;
+  }
+
+  .origin-\(--radix-dropdown-menu-content-transform-origin\) {
+    transform-origin: var(--radix-dropdown-menu-content-transform-origin);
+  }
+
+  .translate-y-0\.5 {
+    --tw-translate-y: calc(var(--spacing) * .5);
+    translate: var(--tw-translate-x) var(--tw-translate-y);
+  }
+
+  .cursor-default {
+    cursor: default;
+  }
+
+  .resize-none {
+    resize: none;
+  }
+
+  .list-none {
+    list-style-type: none;
+  }
+
+  .auto-rows-min {
+    grid-auto-rows: min-content;
+  }
+
+  .grid-cols-1 {
+    grid-template-columns: repeat(1, minmax(0, 1fr));
+  }
+
+  .grid-cols-2 {
+    grid-template-columns: repeat(2, minmax(0, 1fr));
+  }
+
+  .grid-rows-\[auto_auto\] {
+    grid-template-rows: auto auto;
+  }
+
+  .flex-col {
+    flex-direction: column;
+  }
+
+  .flex-wrap {
+    flex-wrap: wrap;
+  }
+
+  .items-center {
+    align-items: center;
+  }
+
+  .items-start {
+    align-items: flex-start;
+  }
+
+  .justify-between {
+    justify-content: space-between;
+  }
+
+  .justify-center {
+    justify-content: center;
+  }
+
+  .gap-1 {
+    gap: calc(var(--spacing) * 1);
+  }
+
+  .gap-1\.5 {
+    gap: calc(var(--spacing) * 1.5);
+  }
+
+  .gap-2 {
+    gap: calc(var(--spacing) * 2);
+  }
+
+  .gap-3 {
+    gap: calc(var(--spacing) * 3);
+  }
+
+  .gap-4 {
+    gap: calc(var(--spacing) * 4);
+  }
+
+  .gap-6 {
+    gap: calc(var(--spacing) * 6);
+  }
+
+  .gap-8 {
+    gap: calc(var(--spacing) * 8);
+  }
+
+  .gap-10 {
+    gap: calc(var(--spacing) * 10);
+  }
+
+  :where(.space-y-1 > :not(:last-child)) {
+    --tw-space-y-reverse: 0;
+    margin-block-start: calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));
+    margin-block-end: calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)));
+  }
+
+  :where(.space-y-2 > :not(:last-child)) {
+    --tw-space-y-reverse: 0;
+    margin-block-start: calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));
+    margin-block-end: calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)));
+  }
+
+  :where(.space-y-3 > :not(:last-child)) {
+    --tw-space-y-reverse: 0;
+    margin-block-start: calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));
+    margin-block-end: calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)));
+  }
+
+  :where(.space-y-4 > :not(:last-child)) {
+    --tw-space-y-reverse: 0;
+    margin-block-start: calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));
+    margin-block-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)));
+  }
+
+  :where(.space-y-5 > :not(:last-child)) {
+    --tw-space-y-reverse: 0;
+    margin-block-start: calc(calc(var(--spacing) * 5) * var(--tw-space-y-reverse));
+    margin-block-end: calc(calc(var(--spacing) * 5) * calc(1 - var(--tw-space-y-reverse)));
+  }
+
+  :where(.space-y-6 > :not(:last-child)) {
+    --tw-space-y-reverse: 0;
+    margin-block-start: calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));
+    margin-block-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)));
+  }
+
+  :where(.space-y-8 > :not(:last-child)) {
+    --tw-space-y-reverse: 0;
+    margin-block-start: calc(calc(var(--spacing) * 8) * var(--tw-space-y-reverse));
+    margin-block-end: calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse)));
+  }
+
+  .self-start {
+    align-self: flex-start;
+  }
+
+  .justify-self-end {
+    justify-self: flex-end;
+  }
+
+  .overflow-hidden {
+    overflow: hidden;
+  }
+
+  .overflow-x-hidden {
+    overflow-x: hidden;
+  }
+
+  .overflow-y-auto {
+    overflow-y: auto;
+  }
+
+  .rounded-2xl {
+    border-radius: var(--radius-2xl);
+  }
+
+  .rounded-3xl {
+    border-radius: var(--radius-3xl);
+  }
+
+  .rounded-full {
+    border-radius: 3.40282e38px;
+  }
+
+  .rounded-lg {
+    border-radius: var(--radius);
+  }
+
+  .rounded-md {
+    border-radius: calc(var(--radius)  - 2px);
+  }
+
+  .rounded-sm {
+    border-radius: calc(var(--radius)  - 4px);
+  }
+
+  .rounded-xl {
+    border-radius: calc(var(--radius)  + 4px);
+  }
+
+  .border {
+    border-style: var(--tw-border-style);
+    border-width: 1px;
+  }
+
+  .border-2 {
+    border-style: var(--tw-border-style);
+    border-width: 2px;
+  }
+
+  .border-t {
+    border-top-style: var(--tw-border-style);
+    border-top-width: 1px;
+  }
+
+  .border-b {
+    border-bottom-style: var(--tw-border-style);
+    border-bottom-width: 1px;
+  }
+
+  .border-l-4 {
+    border-left-style: var(--tw-border-style);
+    border-left-width: 4px;
+  }
+
+  .border-gray-100 {
+    border-color: var(--color-gray-100);
+  }
+
+  .border-gray-200 {
+    border-color: var(--color-gray-200);
+  }
+
+  .border-gray-300 {
+    border-color: var(--color-gray-300);
+  }
+
+  .border-gray-800 {
+    border-color: var(--color-gray-800);
+  }
+
+  .border-input {
+    border-color: var(--input);
+  }
+
+  .border-transparent {
+    border-color: #0000;
+  }
+
+  .bg-background {
+    background-color: var(--background);
+  }
+
+  .bg-border {
+    background-color: var(--border);
+  }
+
+  .bg-card {
+    background-color: var(--card);
+  }
+
+  .bg-destructive {
+    background-color: var(--destructive);
+  }
+
+  .bg-gray-50 {
+    background-color: var(--color-gray-50);
+  }
+
+  .bg-gray-100 {
+    background-color: var(--color-gray-100);
+  }
+
+  .bg-gray-800 {
+    background-color: var(--color-gray-800);
+  }
+
+  .bg-gray-900 {
+    background-color: var(--color-gray-900);
+  }
+
+  .bg-gray-900\/10 {
+    background-color: color-mix(in srgb, oklch(.21 .034 264.665) 10%, transparent);
+  }
+
+  @supports (color: color-mix(in lab, red, red)) {
+    .bg-gray-900\/10 {
+      background-color: color-mix(in oklab, var(--color-gray-900) 10%, transparent);
+    }
+  }
+
+  .bg-input-background {
+    background-color: var(--input-background);
+  }
+
+  .bg-popover {
+    background-color: var(--popover);
+  }
+
+  .bg-primary {
+    background-color: var(--primary);
+  }
+
+  .bg-secondary {
+    background-color: var(--secondary);
+  }
+
+  .bg-white {
+    background-color: var(--color-white);
+  }
+
+  .bg-white\/95 {
+    background-color: #fffffff2;
+  }
+
+  @supports (color: color-mix(in lab, red, red)) {
+    .bg-white\/95 {
+      background-color: color-mix(in oklab, var(--color-white) 95%, transparent);
+    }
+  }
+
+  .bg-gradient-to-b {
+    --tw-gradient-position: to bottom in oklab;
+    background-image: linear-gradient(var(--tw-gradient-stops));
+  }
+
+  .bg-gradient-to-br {
+    --tw-gradient-position: to bottom right in oklab;
+    background-image: linear-gradient(var(--tw-gradient-stops));
+  }
+
+  .from-gray-50 {
+    --tw-gradient-from: var(--color-gray-50);
+    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+  }
+
+  .from-gray-100 {
+    --tw-gradient-from: var(--color-gray-100);
+    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+  }
+
+  .via-white {
+    --tw-gradient-via: var(--color-white);
+    --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
+    --tw-gradient-stops: var(--tw-gradient-via-stops);
+  }
+
+  .to-gray-50 {
+    --tw-gradient-to: var(--color-gray-50);
+    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+  }
+
+  .to-gray-200 {
+    --tw-gradient-to: var(--color-gray-200);
+    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+  }
+
+  .to-white {
+    --tw-gradient-to: var(--color-white);
+    --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+  }
+
+  .fill-current {
+    fill: currentColor;
+  }
+
+  .fill-yellow-500 {
+    fill: var(--color-yellow-500);
+  }
+
+  .object-cover {
+    object-fit: cover;
+  }
+
+  .p-1 {
+    padding: calc(var(--spacing) * 1);
+  }
+
+  .p-2 {
+    padding: calc(var(--spacing) * 2);
+  }
+
+  .p-4 {
+    padding: calc(var(--spacing) * 4);
+  }
+
+  .p-5 {
+    padding: calc(var(--spacing) * 5);
+  }
+
+  .p-6 {
+    padding: calc(var(--spacing) * 6);
+  }
+
+  .p-8 {
+    padding: calc(var(--spacing) * 8);
+  }
+
+  .px-2 {
+    padding-inline: calc(var(--spacing) * 2);
+  }
+
+  .px-3 {
+    padding-inline: calc(var(--spacing) * 3);
+  }
+
+  .px-4 {
+    padding-inline: calc(var(--spacing) * 4);
+  }
+
+  .px-6 {
+    padding-inline: calc(var(--spacing) * 6);
+  }
+
+  .px-8 {
+    padding-inline: calc(var(--spacing) * 8);
+  }
+
+  .py-0\.5 {
+    padding-block: calc(var(--spacing) * .5);
+  }
+
+  .py-1 {
+    padding-block: calc(var(--spacing) * 1);
+  }
+
+  .py-1\.5 {
+    padding-block: calc(var(--spacing) * 1.5);
+  }
+
+  .py-2 {
+    padding-block: calc(var(--spacing) * 2);
+  }
+
+  .py-2\.5 {
+    padding-block: calc(var(--spacing) * 2.5);
+  }
+
+  .py-3 {
+    padding-block: calc(var(--spacing) * 3);
+  }
+
+  .py-4 {
+    padding-block: calc(var(--spacing) * 4);
+  }
+
+  .py-5 {
+    padding-block: calc(var(--spacing) * 5);
+  }
+
+  .py-10 {
+    padding-block: calc(var(--spacing) * 10);
+  }
+
+  .py-12 {
+    padding-block: calc(var(--spacing) * 12);
+  }
+
+  .py-16 {
+    padding-block: calc(var(--spacing) * 16);
+  }
+
+  .pt-0 {
+    padding-top: calc(var(--spacing) * 0);
+  }
+
+  .pt-4 {
+    padding-top: calc(var(--spacing) * 4);
+  }
+
+  .pt-6 {
+    padding-top: calc(var(--spacing) * 6);
+  }
+
+  .pt-8 {
+    padding-top: calc(var(--spacing) * 8);
+  }
+
+  .pr-2 {
+    padding-right: calc(var(--spacing) * 2);
+  }
+
+  .pb-2 {
+    padding-bottom: calc(var(--spacing) * 2);
+  }
+
+  .pb-4 {
+    padding-bottom: calc(var(--spacing) * 4);
+  }
+
+  .pb-6 {
+    padding-bottom: calc(var(--spacing) * 6);
+  }
+
+  .pl-4 {
+    padding-left: calc(var(--spacing) * 4);
+  }
+
+  .pl-8 {
+    padding-left: calc(var(--spacing) * 8);
+  }
+
+  .text-center {
+    text-align: center;
+  }
+
+  .text-left {
+    text-align: left;
+  }
+
+  .align-middle {
+    vertical-align: middle;
+  }
+
+  .text-2xl {
+    font-size: var(--text-2xl);
+    line-height: var(--tw-leading, var(--text-2xl--line-height));
+  }
+
+  .text-3xl {
+    font-size: var(--text-3xl);
+    line-height: var(--tw-leading, var(--text-3xl--line-height));
+  }
+
+  .text-4xl {
+    font-size: var(--text-4xl);
+    line-height: var(--tw-leading, var(--text-4xl--line-height));
+  }
+
+  .text-base {
+    font-size: var(--text-base);
+    line-height: var(--tw-leading, var(--text-base--line-height));
+  }
+
+  .text-lg {
+    font-size: var(--text-lg);
+    line-height: var(--tw-leading, var(--text-lg--line-height));
+  }
+
+  .text-sm {
+    font-size: var(--text-sm);
+    line-height: var(--tw-leading, var(--text-sm--line-height));
+  }
+
+  .text-xl {
+    font-size: var(--text-xl);
+    line-height: var(--tw-leading, var(--text-xl--line-height));
+  }
+
+  .text-xs {
+    font-size: var(--text-xs);
+    line-height: var(--tw-leading, var(--text-xs--line-height));
+  }
+
+  .leading-none {
+    --tw-leading: 1;
+    line-height: 1;
+  }
+
+  .leading-relaxed {
+    --tw-leading: var(--leading-relaxed);
+    line-height: var(--leading-relaxed);
+  }
+
+  .leading-tight {
+    --tw-leading: var(--leading-tight);
+    line-height: var(--leading-tight);
+  }
+
+  .font-medium {
+    --tw-font-weight: var(--font-weight-medium);
+    font-weight: var(--font-weight-medium);
+  }
+
+  .font-semibold {
+    --tw-font-weight: var(--font-weight-semibold);
+    font-weight: var(--font-weight-semibold);
+  }
+
+  .tracking-widest {
+    --tw-tracking: var(--tracking-widest);
+    letter-spacing: var(--tracking-widest);
+  }
+
+  .whitespace-nowrap {
+    white-space: nowrap;
+  }
+
+  .text-card-foreground {
+    color: var(--card-foreground);
+  }
+
+  .text-foreground {
+    color: var(--foreground);
+  }
+
+  .text-gray-300 {
+    color: var(--color-gray-300);
+  }
+
+  .text-gray-500 {
+    color: var(--color-gray-500);
+  }
+
+  .text-gray-600 {
+    color: var(--color-gray-600);
+  }
+
+  .text-gray-700 {
+    color: var(--color-gray-700);
+  }
+
+  .text-gray-900 {
+    color: var(--color-gray-900);
+  }
+
+  .text-muted-foreground {
+    color: var(--muted-foreground);
+  }
+
+  .text-popover-foreground {
+    color: var(--popover-foreground);
+  }
+
+  .text-primary {
+    color: var(--primary);
+  }
+
+  .text-primary-foreground {
+    color: var(--primary-foreground);
+  }
+
+  .text-secondary-foreground {
+    color: var(--secondary-foreground);
+  }
+
+  .text-white {
+    color: var(--color-white);
+  }
+
+  .text-yellow-500 {
+    color: var(--color-yellow-500);
+  }
+
+  .uppercase {
+    text-transform: uppercase;
+  }
+
+  .underline-offset-4 {
+    text-underline-offset: 4px;
+  }
+
+  .shadow-2xl {
+    --tw-shadow: 0 25px 50px -12px var(--tw-shadow-color, #00000040);
+    box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+  }
+
+  .shadow-lg {
+    --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, #0000001a), 0 4px 6px -4px var(--tw-shadow-color, #0000001a);
+    box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+  }
+
+  .shadow-md {
+    --tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, #0000001a), 0 2px 4px -2px var(--tw-shadow-color, #0000001a);
+    box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+  }
+
+  .shadow-xl {
+    --tw-shadow: 0 20px 25px -5px var(--tw-shadow-color, #0000001a), 0 8px 10px -6px var(--tw-shadow-color, #0000001a);
+    box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+  }
+
+  .outline-hidden {
+    --tw-outline-style: none;
+    outline-style: none;
+  }
+
+  @media (forced-colors: active) {
+    .outline-hidden {
+      outline-offset: 2px;
+      outline: 2px solid #0000;
+    }
+  }
+
+  .outline {
+    outline-style: var(--tw-outline-style);
+    outline-width: 1px;
+  }
+
+  .blur-3xl {
+    --tw-blur: blur(var(--blur-3xl));
+    filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
+  }
+
+  .filter {
+    filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
+  }
+
+  .backdrop-blur {
+    --tw-backdrop-blur: blur(8px);
+    -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+    backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+  }
+
+  .backdrop-blur-sm {
+    --tw-backdrop-blur: blur(var(--blur-sm));
+    -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+    backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+  }
+
+  .transition {
+    transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter;
+    transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
+    transition-duration: var(--tw-duration, var(--default-transition-duration));
+  }
+
+  .transition-\[color\,box-shadow\] {
+    transition-property: color, box-shadow;
+    transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
+    transition-duration: var(--tw-duration, var(--default-transition-duration));
+  }
+
+  .transition-all {
+    transition-property: all;
+    transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
+    transition-duration: var(--tw-duration, var(--default-transition-duration));
+  }
+
+  .transition-colors {
+    transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;
+    transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
+    transition-duration: var(--tw-duration, var(--default-transition-duration));
+  }
+
+  .transition-shadow {
+    transition-property: box-shadow;
+    transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
+    transition-duration: var(--tw-duration, var(--default-transition-duration));
+  }
+
+  .transition-transform {
+    transition-property: transform, translate, scale, rotate;
+    transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
+    transition-duration: var(--tw-duration, var(--default-transition-duration));
+  }
+
+  .duration-200 {
+    --tw-duration: .2s;
+    transition-duration: .2s;
+  }
+
+  .duration-300 {
+    --tw-duration: .3s;
+    transition-duration: .3s;
+  }
+
+  .outline-none {
+    --tw-outline-style: none;
+    outline-style: none;
+  }
+
+  .select-none {
+    -webkit-user-select: none;
+    user-select: none;
+  }
+
+  @media (hover: hover) {
+    .group-hover\:scale-105:is(:where(.group):hover *) {
+      --tw-scale-x: 105%;
+      --tw-scale-y: 105%;
+      --tw-scale-z: 105%;
+      scale: var(--tw-scale-x) var(--tw-scale-y);
+    }
+  }
+
+  .selection\:bg-primary ::selection, .selection\:bg-primary::selection {
+    background-color: var(--primary);
+  }
+
+  .selection\:text-primary-foreground ::selection, .selection\:text-primary-foreground::selection {
+    color: var(--primary-foreground);
+  }
+
+  .file\:inline-flex::file-selector-button {
+    display: inline-flex;
+  }
+
+  .file\:h-7::file-selector-button {
+    height: calc(var(--spacing) * 7);
+  }
+
+  .file\:border-0::file-selector-button {
+    border-style: var(--tw-border-style);
+    border-width: 0;
+  }
+
+  .file\:bg-transparent::file-selector-button {
+    background-color: #0000;
+  }
+
+  .file\:text-sm::file-selector-button {
+    font-size: var(--text-sm);
+    line-height: var(--tw-leading, var(--text-sm--line-height));
+  }
+
+  .file\:font-medium::file-selector-button {
+    --tw-font-weight: var(--font-weight-medium);
+    font-weight: var(--font-weight-medium);
+  }
+
+  .file\:text-foreground::file-selector-button {
+    color: var(--foreground);
+  }
+
+  .placeholder\:text-muted-foreground::placeholder {
+    color: var(--muted-foreground);
+  }
+
+  .last\:border-b-0:last-child {
+    border-bottom-style: var(--tw-border-style);
+    border-bottom-width: 0;
+  }
+
+  @media (hover: hover) {
+    .hover\:border-gray-300:hover {
+      border-color: var(--color-gray-300);
+    }
+  }
+
+  @media (hover: hover) {
+    .hover\:bg-accent:hover {
+      background-color: var(--accent);
+    }
+  }
+
+  @media (hover: hover) {
+    .hover\:bg-black:hover {
+      background-color: var(--color-black);
+    }
+  }
+
+  @media (hover: hover) {
+    .hover\:bg-destructive\/90:hover {
+      background-color: var(--destructive);
+    }
+
+    @supports (color: color-mix(in lab, red, red)) {
+      .hover\:bg-destructive\/90:hover {
+        background-color: color-mix(in oklab, var(--destructive) 90%, transparent);
+      }
+    }
+  }
+
+  @media (hover: hover) {
+    .hover\:bg-gray-50:hover {
+      background-color: var(--color-gray-50);
+    }
+  }
+
+  @media (hover: hover) {
+    .hover\:bg-gray-100:hover {
+      background-color: var(--color-gray-100);
+    }
+  }
+
+  @media (hover: hover) {
+    .hover\:bg-gray-200:hover {
+      background-color: var(--color-gray-200);
+    }
+  }
+
+  @media (hover: hover) {
+    .hover\:bg-primary\/90:hover {
+      background-color: var(--primary);
+    }
+
+    @supports (color: color-mix(in lab, red, red)) {
+      .hover\:bg-primary\/90:hover {
+        background-color: color-mix(in oklab, var(--primary) 90%, transparent);
+      }
+    }
+  }
+
+  @media (hover: hover) {
+    .hover\:bg-secondary\/80:hover {
+      background-color: var(--secondary);
+    }
+
+    @supports (color: color-mix(in lab, red, red)) {
+      .hover\:bg-secondary\/80:hover {
+        background-color: color-mix(in oklab, var(--secondary) 80%, transparent);
+      }
+    }
+  }
+
+  @media (hover: hover) {
+    .hover\:bg-white:hover {
+      background-color: var(--color-white);
+    }
+  }
+
+  @media (hover: hover) {
+    .hover\:text-accent-foreground:hover {
+      color: var(--accent-foreground);
+    }
+  }
+
+  @media (hover: hover) {
+    .hover\:text-gray-600:hover {
+      color: var(--color-gray-600);
+    }
+  }
+
+  @media (hover: hover) {
+    .hover\:text-gray-900:hover {
+      color: var(--color-gray-900);
+    }
+  }
+
+  @media (hover: hover) {
+    .hover\:text-white:hover {
+      color: var(--color-white);
+    }
+  }
+
+  @media (hover: hover) {
+    .hover\:underline:hover {
+      text-decoration-line: underline;
+    }
+  }
+
+  @media (hover: hover) {
+    .hover\:shadow-lg:hover {
+      --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, #0000001a), 0 4px 6px -4px var(--tw-shadow-color, #0000001a);
+      box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+    }
+  }
+
+  @media (hover: hover) {
+    .hover\:shadow-xl:hover {
+      --tw-shadow: 0 20px 25px -5px var(--tw-shadow-color, #0000001a), 0 8px 10px -6px var(--tw-shadow-color, #0000001a);
+      box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+    }
+  }
+
+  .focus\:bg-accent:focus {
+    background-color: var(--accent);
+  }
+
+  .focus\:text-accent-foreground:focus {
+    color: var(--accent-foreground);
+  }
+
+  .focus-visible\:border-ring:focus-visible {
+    border-color: var(--ring);
+  }
+
+  .focus-visible\:ring-\[3px\]:focus-visible {
+    --tw-ring-shadow: var(--tw-ring-inset, ) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
+    box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+  }
+
+  .focus-visible\:ring-destructive\/20:focus-visible {
+    --tw-ring-color: var(--destructive);
+  }
+
+  @supports (color: color-mix(in lab, red, red)) {
+    .focus-visible\:ring-destructive\/20:focus-visible {
+      --tw-ring-color: color-mix(in oklab, var(--destructive) 20%, transparent);
+    }
+  }
+
+  .focus-visible\:ring-ring\/50:focus-visible {
+    --tw-ring-color: var(--ring);
+  }
+
+  @supports (color: color-mix(in lab, red, red)) {
+    .focus-visible\:ring-ring\/50:focus-visible {
+      --tw-ring-color: color-mix(in oklab, var(--ring) 50%, transparent);
+    }
+  }
+
+  .disabled\:pointer-events-none:disabled {
+    pointer-events: none;
+  }
+
+  .disabled\:cursor-not-allowed:disabled {
+    cursor: not-allowed;
+  }
+
+  .disabled\:opacity-50:disabled {
+    opacity: .5;
+  }
+
+  .has-data-\[slot\=card-action\]\:grid-cols-\[1fr_auto\]:has([data-slot="card-action"]) {
+    grid-template-columns: 1fr auto;
+  }
+
+  .has-\[\>svg\]\:px-2\.5:has( > svg) {
+    padding-inline: calc(var(--spacing) * 2.5);
+  }
+
+  .has-\[\>svg\]\:px-3:has( > svg) {
+    padding-inline: calc(var(--spacing) * 3);
+  }
+
+  .has-\[\>svg\]\:px-4:has( > svg) {
+    padding-inline: calc(var(--spacing) * 4);
+  }
+
+  .aria-invalid\:border-destructive[aria-invalid="true"] {
+    border-color: var(--destructive);
+  }
+
+  .aria-invalid\:ring-destructive\/20[aria-invalid="true"] {
+    --tw-ring-color: var(--destructive);
+  }
+
+  @supports (color: color-mix(in lab, red, red)) {
+    .aria-invalid\:ring-destructive\/20[aria-invalid="true"] {
+      --tw-ring-color: color-mix(in oklab, var(--destructive) 20%, transparent);
+    }
+  }
+
+  .data-\[disabled\]\:pointer-events-none[data-disabled] {
+    pointer-events: none;
+  }
+
+  .data-\[disabled\]\:opacity-50[data-disabled] {
+    opacity: .5;
+  }
+
+  .data-\[inset\]\:pl-8[data-inset] {
+    padding-left: calc(var(--spacing) * 8);
+  }
+
+  .data-\[side\=bottom\]\:slide-in-from-top-2[data-side="bottom"] {
+    --tw-enter-translate-y: calc(2 * var(--spacing) * -1);
+  }
+
+  .data-\[side\=left\]\:slide-in-from-right-2[data-side="left"] {
+    --tw-enter-translate-x: calc(2 * var(--spacing));
+  }
+
+  .data-\[side\=right\]\:slide-in-from-left-2[data-side="right"] {
+    --tw-enter-translate-x: calc(2 * var(--spacing) * -1);
+  }
+
+  .data-\[side\=top\]\:slide-in-from-bottom-2[data-side="top"] {
+    --tw-enter-translate-y: calc(2 * var(--spacing));
+  }
+
+  .data-\[state\=closed\]\:animate-accordion-up[data-state="closed"] {
+    animation: accordion-up var(--tw-duration, .2s) ease-out;
+  }
+
+  .data-\[state\=closed\]\:animate-out[data-state="closed"] {
+    animation: exit var(--tw-duration, .15s) var(--tw-ease, ease);
+  }
+
+  .data-\[state\=closed\]\:fade-out-0[data-state="closed"] {
+    --tw-exit-opacity: 0;
+  }
+
+  .data-\[state\=closed\]\:zoom-out-95[data-state="closed"] {
+    --tw-exit-scale: .95;
+  }
+
+  .data-\[state\=open\]\:animate-accordion-down[data-state="open"] {
+    animation: accordion-down var(--tw-duration, .2s) ease-out;
+  }
+
+  .data-\[state\=open\]\:animate-in[data-state="open"] {
+    animation: enter var(--tw-duration, .15s) var(--tw-ease, ease);
+  }
+
+  .data-\[state\=open\]\:bg-accent[data-state="open"] {
+    background-color: var(--accent);
+  }
+
+  .data-\[state\=open\]\:text-accent-foreground[data-state="open"] {
+    color: var(--accent-foreground);
+  }
+
+  .data-\[state\=open\]\:fade-in-0[data-state="open"] {
+    --tw-enter-opacity: 0;
+  }
+
+  .data-\[state\=open\]\:zoom-in-95[data-state="open"] {
+    --tw-enter-scale: .95;
+  }
+
+  .data-\[variant\=destructive\]\:text-destructive[data-variant="destructive"] {
+    color: var(--destructive);
+  }
+
+  .data-\[variant\=destructive\]\:focus\:bg-destructive\/10[data-variant="destructive"]:focus {
+    background-color: var(--destructive);
+  }
+
+  @supports (color: color-mix(in lab, red, red)) {
+    .data-\[variant\=destructive\]\:focus\:bg-destructive\/10[data-variant="destructive"]:focus {
+      background-color: color-mix(in oklab, var(--destructive) 10%, transparent);
+    }
+  }
+
+  .data-\[variant\=destructive\]\:focus\:text-destructive[data-variant="destructive"]:focus {
+    color: var(--destructive);
+  }
+
+  @media (width >= 40rem) {
+    .sm\:mt-1 {
+      margin-top: calc(var(--spacing) * 1);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:mt-10 {
+      margin-top: calc(var(--spacing) * 10);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:mt-16 {
+      margin-top: calc(var(--spacing) * 16);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:mb-4 {
+      margin-bottom: calc(var(--spacing) * 4);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:mb-6 {
+      margin-bottom: calc(var(--spacing) * 6);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:mb-8 {
+      margin-bottom: calc(var(--spacing) * 8);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:mb-10 {
+      margin-bottom: calc(var(--spacing) * 10);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:mb-12 {
+      margin-bottom: calc(var(--spacing) * 12);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:mb-16 {
+      margin-bottom: calc(var(--spacing) * 16);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:h-6 {
+      height: calc(var(--spacing) * 6);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:h-10 {
+      height: calc(var(--spacing) * 10);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:h-11 {
+      height: calc(var(--spacing) * 11);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:h-12 {
+      height: calc(var(--spacing) * 12);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:h-16 {
+      height: calc(var(--spacing) * 16);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:w-6 {
+      width: calc(var(--spacing) * 6);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:w-10 {
+      width: calc(var(--spacing) * 10);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:w-11 {
+      width: calc(var(--spacing) * 11);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:w-12 {
+      width: calc(var(--spacing) * 12);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:w-\[200px\] {
+      width: 200px;
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:w-auto {
+      width: auto;
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:grid-cols-2 {
+      grid-template-columns: repeat(2, minmax(0, 1fr));
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:flex-row {
+      flex-direction: row;
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:gap-2 {
+      gap: calc(var(--spacing) * 2);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:gap-4 {
+      gap: calc(var(--spacing) * 4);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:gap-6 {
+      gap: calc(var(--spacing) * 6);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:gap-10 {
+      gap: calc(var(--spacing) * 10);
+    }
+  }
+
+  @media (width >= 40rem) {
+    :where(.sm\:space-y-4 > :not(:last-child)) {
+      --tw-space-y-reverse: 0;
+      margin-block-start: calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));
+      margin-block-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)));
+    }
+  }
+
+  @media (width >= 40rem) {
+    :where(.sm\:space-y-6 > :not(:last-child)) {
+      --tw-space-y-reverse: 0;
+      margin-block-start: calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));
+      margin-block-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)));
+    }
+  }
+
+  @media (width >= 40rem) {
+    :where(.sm\:space-y-8 > :not(:last-child)) {
+      --tw-space-y-reverse: 0;
+      margin-block-start: calc(calc(var(--spacing) * 8) * var(--tw-space-y-reverse));
+      margin-block-end: calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse)));
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:rounded-2xl {
+      border-radius: var(--radius-2xl);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:rounded-3xl {
+      border-radius: var(--radius-3xl);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:rounded-xl {
+      border-radius: calc(var(--radius)  + 4px);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:p-6 {
+      padding: calc(var(--spacing) * 6);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:p-8 {
+      padding: calc(var(--spacing) * 8);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:px-6 {
+      padding-inline: calc(var(--spacing) * 6);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:px-8 {
+      padding-inline: calc(var(--spacing) * 8);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:py-4 {
+      padding-block: calc(var(--spacing) * 4);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:py-5 {
+      padding-block: calc(var(--spacing) * 5);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:py-6 {
+      padding-block: calc(var(--spacing) * 6);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:py-12 {
+      padding-block: calc(var(--spacing) * 12);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:py-16 {
+      padding-block: calc(var(--spacing) * 16);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:py-20 {
+      padding-block: calc(var(--spacing) * 20);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:pt-8 {
+      padding-top: calc(var(--spacing) * 8);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:pb-5 {
+      padding-bottom: calc(var(--spacing) * 5);
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:text-left {
+      text-align: left;
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:text-2xl {
+      font-size: var(--text-2xl);
+      line-height: var(--tw-leading, var(--text-2xl--line-height));
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:text-3xl {
+      font-size: var(--text-3xl);
+      line-height: var(--tw-leading, var(--text-3xl--line-height));
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:text-4xl {
+      font-size: var(--text-4xl);
+      line-height: var(--tw-leading, var(--text-4xl--line-height));
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:text-5xl {
+      font-size: var(--text-5xl);
+      line-height: var(--tw-leading, var(--text-5xl--line-height));
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:text-base {
+      font-size: var(--text-base);
+      line-height: var(--tw-leading, var(--text-base--line-height));
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:text-lg {
+      font-size: var(--text-lg);
+      line-height: var(--tw-leading, var(--text-lg--line-height));
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:text-sm {
+      font-size: var(--text-sm);
+      line-height: var(--tw-leading, var(--text-sm--line-height));
+    }
+  }
+
+  @media (width >= 40rem) {
+    .sm\:text-xl {
+      font-size: var(--text-xl);
+      line-height: var(--tw-leading, var(--text-xl--line-height));
+    }
+  }
+
+  @media (width >= 48rem) {
+    .md\:flex {
+      display: flex;
+    }
+  }
+
+  @media (width >= 48rem) {
+    .md\:hidden {
+      display: none;
+    }
+  }
+
+  @media (width >= 48rem) {
+    .md\:grid-cols-2 {
+      grid-template-columns: repeat(2, minmax(0, 1fr));
+    }
+  }
+
+  @media (width >= 48rem) {
+    .md\:grid-cols-3 {
+      grid-template-columns: repeat(3, minmax(0, 1fr));
+    }
+  }
+
+  @media (width >= 48rem) {
+    .md\:text-5xl {
+      font-size: var(--text-5xl);
+      line-height: var(--tw-leading, var(--text-5xl--line-height));
+    }
+  }
+
+  @media (width >= 48rem) {
+    .md\:text-sm {
+      font-size: var(--text-sm);
+      line-height: var(--tw-leading, var(--text-sm--line-height));
+    }
+  }
+
+  @media (width >= 64rem) {
+    .lg\:order-1 {
+      order: 1;
+    }
+  }
+
+  @media (width >= 64rem) {
+    .lg\:order-2 {
+      order: 2;
+    }
+  }
+
+  @media (width >= 64rem) {
+    .lg\:mx-0 {
+      margin-inline: calc(var(--spacing) * 0);
+    }
+  }
+
+  @media (width >= 64rem) {
+    .lg\:mt-0 {
+      margin-top: calc(var(--spacing) * 0);
+    }
+  }
+
+  @media (width >= 64rem) {
+    .lg\:mt-12 {
+      margin-top: calc(var(--spacing) * 12);
+    }
+  }
+
+  @media (width >= 64rem) {
+    .lg\:mt-20 {
+      margin-top: calc(var(--spacing) * 20);
+    }
+  }
+
+  @media (width >= 64rem) {
+    .lg\:mb-12 {
+      margin-bottom: calc(var(--spacing) * 12);
+    }
+  }
+
+  @media (width >= 64rem) {
+    .lg\:mb-16 {
+      margin-bottom: calc(var(--spacing) * 16);
+    }
+  }
+
+  @media (width >= 64rem) {
+    .lg\:grid-cols-2 {
+      grid-template-columns: repeat(2, minmax(0, 1fr));
+    }
+  }
+
+  @media (width >= 64rem) {
+    .lg\:grid-cols-3 {
+      grid-template-columns: repeat(3, minmax(0, 1fr));
+    }
+  }
+
+  @media (width >= 64rem) {
+    .lg\:grid-cols-4 {
+      grid-template-columns: repeat(4, minmax(0, 1fr));
+    }
+  }
+
+  @media (width >= 64rem) {
+    .lg\:flex-row {
+      flex-direction: row;
+    }
+  }
+
+  @media (width >= 64rem) {
+    .lg\:items-center {
+      align-items: center;
+    }
+  }
+
+  @media (width >= 64rem) {
+    .lg\:justify-between {
+      justify-content: space-between;
+    }
+  }
+
+  @media (width >= 64rem) {
+    .lg\:justify-start {
+      justify-content: flex-start;
+    }
+  }
+
+  @media (width >= 64rem) {
+    .lg\:gap-8 {
+      gap: calc(var(--spacing) * 8);
+    }
+  }
+
+  @media (width >= 64rem) {
+    .lg\:gap-12 {
+      gap: calc(var(--spacing) * 12);
+    }
+  }
+
+  @media (width >= 64rem) {
+    .lg\:gap-16 {
+      gap: calc(var(--spacing) * 16);
+    }
+  }
+
+  @media (width >= 64rem) {
+    .lg\:px-8 {
+      padding-inline: calc(var(--spacing) * 8);
+    }
+  }
+
+  @media (width >= 64rem) {
+    .lg\:py-20 {
+      padding-block: calc(var(--spacing) * 20);
+    }
+  }
+
+  @media (width >= 64rem) {
+    .lg\:py-24 {
+      padding-block: calc(var(--spacing) * 24);
+    }
+  }
+
+  @media (width >= 64rem) {
+    .lg\:py-32 {
+      padding-block: calc(var(--spacing) * 32);
+    }
+  }
+
+  @media (width >= 64rem) {
+    .lg\:text-left {
+      text-align: left;
+    }
+  }
+
+  @media (width >= 64rem) {
+    .lg\:text-4xl {
+      font-size: var(--text-4xl);
+      line-height: var(--tw-leading, var(--text-4xl--line-height));
+    }
+  }
+
+  @media (width >= 64rem) {
+    .lg\:text-5xl {
+      font-size: var(--text-5xl);
+      line-height: var(--tw-leading, var(--text-5xl--line-height));
+    }
+  }
+
+  @media (width >= 64rem) {
+    .lg\:text-6xl {
+      font-size: var(--text-6xl);
+      line-height: var(--tw-leading, var(--text-6xl--line-height));
+    }
+  }
+
+  @media (width >= 64rem) {
+    .lg\:text-xl {
+      font-size: var(--text-xl);
+      line-height: var(--tw-leading, var(--text-xl--line-height));
+    }
+  }
+
+  @media (width >= 80rem) {
+    .xl\:text-7xl {
+      font-size: var(--text-7xl);
+      line-height: var(--tw-leading, var(--text-7xl--line-height));
+    }
+  }
+
+  .dark\:border-input:is(.dark *) {
+    border-color: var(--input);
+  }
+
+  .dark\:bg-destructive\/60:is(.dark *) {
+    background-color: var(--destructive);
+  }
+
+  @supports (color: color-mix(in lab, red, red)) {
+    .dark\:bg-destructive\/60:is(.dark *) {
+      background-color: color-mix(in oklab, var(--destructive) 60%, transparent);
+    }
+  }
+
+  .dark\:bg-input\/30:is(.dark *) {
+    background-color: var(--input);
+  }
+
+  @supports (color: color-mix(in lab, red, red)) {
+    .dark\:bg-input\/30:is(.dark *) {
+      background-color: color-mix(in oklab, var(--input) 30%, transparent);
+    }
+  }
+
+  @media (hover: hover) {
+    .dark\:hover\:bg-accent\/50:is(.dark *):hover {
+      background-color: var(--accent);
+    }
+
+    @supports (color: color-mix(in lab, red, red)) {
+      .dark\:hover\:bg-accent\/50:is(.dark *):hover {
+        background-color: color-mix(in oklab, var(--accent) 50%, transparent);
+      }
+    }
+  }
+
+  @media (hover: hover) {
+    .dark\:hover\:bg-input\/50:is(.dark *):hover {
+      background-color: var(--input);
+    }
+
+    @supports (color: color-mix(in lab, red, red)) {
+      .dark\:hover\:bg-input\/50:is(.dark *):hover {
+        background-color: color-mix(in oklab, var(--input) 50%, transparent);
+      }
+    }
+  }
+
+  .dark\:focus-visible\:ring-destructive\/40:is(.dark *):focus-visible {
+    --tw-ring-color: var(--destructive);
+  }
+
+  @supports (color: color-mix(in lab, red, red)) {
+    .dark\:focus-visible\:ring-destructive\/40:is(.dark *):focus-visible {
+      --tw-ring-color: color-mix(in oklab, var(--destructive) 40%, transparent);
+    }
+  }
+
+  .dark\:aria-invalid\:ring-destructive\/40:is(.dark *)[aria-invalid="true"] {
+    --tw-ring-color: var(--destructive);
+  }
+
+  @supports (color: color-mix(in lab, red, red)) {
+    .dark\:aria-invalid\:ring-destructive\/40:is(.dark *)[aria-invalid="true"] {
+      --tw-ring-color: color-mix(in oklab, var(--destructive) 40%, transparent);
+    }
+  }
+
+  .dark\:data-\[variant\=destructive\]\:focus\:bg-destructive\/20:is(.dark *)[data-variant="destructive"]:focus {
+    background-color: var(--destructive);
+  }
+
+  @supports (color: color-mix(in lab, red, red)) {
+    .dark\:data-\[variant\=destructive\]\:focus\:bg-destructive\/20:is(.dark *)[data-variant="destructive"]:focus {
+      background-color: color-mix(in oklab, var(--destructive) 20%, transparent);
+    }
+  }
+
+  .\[\&_svg\]\:pointer-events-none svg {
+    pointer-events: none;
+  }
+
+  .\[\&_svg\]\:shrink-0 svg {
+    flex-shrink: 0;
+  }
+
+  .\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-4 svg:not([class*="size-"]) {
+    width: calc(var(--spacing) * 4);
+    height: calc(var(--spacing) * 4);
+  }
+
+  .\[\&_svg\:not\(\[class\*\=\'text-\'\]\)\]\:text-muted-foreground svg:not([class*="text-"]) {
+    color: var(--muted-foreground);
+  }
+
+  .\[\.border-b\]\:pb-6.border-b {
+    padding-bottom: calc(var(--spacing) * 6);
+  }
+
+  .\[\.border-t\]\:pt-6.border-t {
+    padding-top: calc(var(--spacing) * 6);
+  }
+
+  :is(.data-\[variant\=destructive\]\:\*\:\[svg\]\:\!text-destructive[data-variant="destructive"] > *):is(svg) {
+    color: var(--destructive) !important;
+  }
+
+  .\[\&\:last-child\]\:pb-6:last-child {
+    padding-bottom: calc(var(--spacing) * 6);
+  }
+
+  .\[\&\>svg\]\:pointer-events-none > svg {
+    pointer-events: none;
+  }
+
+  .\[\&\>svg\]\:size-3 > svg {
+    width: calc(var(--spacing) * 3);
+    height: calc(var(--spacing) * 3);
+  }
+
+  .\[\&\[data-state\=open\]\>svg\]\:rotate-180[data-state="open"] > svg {
+    rotate: 180deg;
+  }
+
+  @media (hover: hover) {
+    a.\[a\&\]\:hover\:bg-accent:hover {
+      background-color: var(--accent);
+    }
+  }
+
+  @media (hover: hover) {
+    a.\[a\&\]\:hover\:bg-destructive\/90:hover {
+      background-color: var(--destructive);
+    }
+
+    @supports (color: color-mix(in lab, red, red)) {
+      a.\[a\&\]\:hover\:bg-destructive\/90:hover {
+        background-color: color-mix(in oklab, var(--destructive) 90%, transparent);
+      }
+    }
+  }
+
+  @media (hover: hover) {
+    a.\[a\&\]\:hover\:bg-primary\/90:hover {
+      background-color: var(--primary);
+    }
+
+    @supports (color: color-mix(in lab, red, red)) {
+      a.\[a\&\]\:hover\:bg-primary\/90:hover {
+        background-color: color-mix(in oklab, var(--primary) 90%, transparent);
+      }
+    }
+  }
+
+  @media (hover: hover) {
+    a.\[a\&\]\:hover\:bg-secondary\/90:hover {
+      background-color: var(--secondary);
+    }
+
+    @supports (color: color-mix(in lab, red, red)) {
+      a.\[a\&\]\:hover\:bg-secondary\/90:hover {
+        background-color: color-mix(in oklab, var(--secondary) 90%, transparent);
+      }
+    }
+  }
+
+  @media (hover: hover) {
+    a.\[a\&\]\:hover\:text-accent-foreground:hover {
+      color: var(--accent-foreground);
+    }
+  }
+}
+
+:root {
+  --font-size: 16px;
+  --background: #fff;
+  --foreground: oklch(.145 0 0);
+  --card: #fff;
+  --card-foreground: oklch(.145 0 0);
+  --popover: oklch(1 0 0);
+  --popover-foreground: oklch(.145 0 0);
+  --primary: #030213;
+  --primary-foreground: oklch(1 0 0);
+  --secondary: oklch(.95 .0058 264.53);
+  --secondary-foreground: #030213;
+  --muted: #ececf0;
+  --muted-foreground: #717182;
+  --accent: #e9ebef;
+  --accent-foreground: #030213;
+  --destructive: #d4183d;
+  --destructive-foreground: #fff;
+  --border: #0000001a;
+  --input: transparent;
+  --input-background: #f3f3f5;
+  --switch-background: #cbced4;
+  --font-weight-medium: 500;
+  --font-weight-normal: 400;
+  --ring: oklch(.708 0 0);
+  --chart-1: oklch(.646 .222 41.116);
+  --chart-2: oklch(.6 .118 184.704);
+  --chart-3: oklch(.398 .07 227.392);
+  --chart-4: oklch(.828 .189 84.429);
+  --chart-5: oklch(.769 .188 70.08);
+  --radius: .625rem;
+  --sidebar: oklch(.985 0 0);
+  --sidebar-foreground: oklch(.145 0 0);
+  --sidebar-primary: #030213;
+  --sidebar-primary-foreground: oklch(.985 0 0);
+  --sidebar-accent: oklch(.97 0 0);
+  --sidebar-accent-foreground: oklch(.205 0 0);
+  --sidebar-border: oklch(.922 0 0);
+  --sidebar-ring: oklch(.708 0 0);
+}
+
+.dark {
+  --background: oklch(.145 0 0);
+  --foreground: oklch(.985 0 0);
+  --card: oklch(.145 0 0);
+  --card-foreground: oklch(.985 0 0);
+  --popover: oklch(.145 0 0);
+  --popover-foreground: oklch(.985 0 0);
+  --primary: oklch(.985 0 0);
+  --primary-foreground: oklch(.205 0 0);
+  --secondary: oklch(.269 0 0);
+  --secondary-foreground: oklch(.985 0 0);
+  --muted: oklch(.269 0 0);
+  --muted-foreground: oklch(.708 0 0);
+  --accent: oklch(.269 0 0);
+  --accent-foreground: oklch(.985 0 0);
+  --destructive: oklch(.396 .141 25.723);
+  --destructive-foreground: oklch(.637 .237 25.331);
+  --border: oklch(.269 0 0);
+  --input: oklch(.269 0 0);
+  --ring: oklch(.439 0 0);
+  --font-weight-medium: 500;
+  --font-weight-normal: 400;
+  --chart-1: oklch(.488 .243 264.376);
+  --chart-2: oklch(.696 .17 162.48);
+  --chart-3: oklch(.769 .188 70.08);
+  --chart-4: oklch(.627 .265 303.9);
+  --chart-5: oklch(.645 .246 16.439);
+  --sidebar: oklch(.205 0 0);
+  --sidebar-foreground: oklch(.985 0 0);
+  --sidebar-primary: oklch(.488 .243 264.376);
+  --sidebar-primary-foreground: oklch(.985 0 0);
+  --sidebar-accent: oklch(.269 0 0);
+  --sidebar-accent-foreground: oklch(.985 0 0);
+  --sidebar-border: oklch(.269 0 0);
+  --sidebar-ring: oklch(.439 0 0);
+}
+
+html {
+  font-size: var(--font-size);
+}
+
+@property --tw-translate-x {
+  syntax: "*";
+  inherits: false;
+  initial-value: 0;
+}
+
+@property --tw-translate-y {
+  syntax: "*";
+  inherits: false;
+  initial-value: 0;
+}
+
+@property --tw-translate-z {
+  syntax: "*";
+  inherits: false;
+  initial-value: 0;
+}
+
+@property --tw-space-y-reverse {
+  syntax: "*";
+  inherits: false;
+  initial-value: 0;
+}
+
+@property --tw-border-style {
+  syntax: "*";
+  inherits: false;
+  initial-value: solid;
+}
+
+@property --tw-gradient-position {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-gradient-from {
+  syntax: "<color>";
+  inherits: false;
+  initial-value: #0000;
+}
+
+@property --tw-gradient-via {
+  syntax: "<color>";
+  inherits: false;
+  initial-value: #0000;
+}
+
+@property --tw-gradient-to {
+  syntax: "<color>";
+  inherits: false;
+  initial-value: #0000;
+}
+
+@property --tw-gradient-stops {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-gradient-via-stops {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-gradient-from-position {
+  syntax: "<length-percentage>";
+  inherits: false;
+  initial-value: 0%;
+}
+
+@property --tw-gradient-via-position {
+  syntax: "<length-percentage>";
+  inherits: false;
+  initial-value: 50%;
+}
+
+@property --tw-gradient-to-position {
+  syntax: "<length-percentage>";
+  inherits: false;
+  initial-value: 100%;
+}
+
+@property --tw-leading {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-font-weight {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-tracking {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-shadow {
+  syntax: "*";
+  inherits: false;
+  initial-value: 0 0 #0000;
+}
+
+@property --tw-shadow-color {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-shadow-alpha {
+  syntax: "<percentage>";
+  inherits: false;
+  initial-value: 100%;
+}
+
+@property --tw-inset-shadow {
+  syntax: "*";
+  inherits: false;
+  initial-value: 0 0 #0000;
+}
+
+@property --tw-inset-shadow-color {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-inset-shadow-alpha {
+  syntax: "<percentage>";
+  inherits: false;
+  initial-value: 100%;
+}
+
+@property --tw-ring-color {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-ring-shadow {
+  syntax: "*";
+  inherits: false;
+  initial-value: 0 0 #0000;
+}
+
+@property --tw-inset-ring-color {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-inset-ring-shadow {
+  syntax: "*";
+  inherits: false;
+  initial-value: 0 0 #0000;
+}
+
+@property --tw-ring-inset {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-ring-offset-width {
+  syntax: "<length>";
+  inherits: false;
+  initial-value: 0;
+}
+
+@property --tw-ring-offset-color {
+  syntax: "*";
+  inherits: false;
+  initial-value: #fff;
+}
+
+@property --tw-ring-offset-shadow {
+  syntax: "*";
+  inherits: false;
+  initial-value: 0 0 #0000;
+}
+
+@property --tw-outline-style {
+  syntax: "*";
+  inherits: false;
+  initial-value: solid;
+}
+
+@property --tw-blur {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-brightness {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-contrast {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-grayscale {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-hue-rotate {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-invert {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-opacity {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-saturate {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-sepia {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-drop-shadow {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-drop-shadow-color {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-drop-shadow-alpha {
+  syntax: "<percentage>";
+  inherits: false;
+  initial-value: 100%;
+}
+
+@property --tw-drop-shadow-size {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-backdrop-blur {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-backdrop-brightness {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-backdrop-contrast {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-backdrop-grayscale {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-backdrop-hue-rotate {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-backdrop-invert {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-backdrop-opacity {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-backdrop-saturate {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-backdrop-sepia {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-duration {
+  syntax: "*";
+  inherits: false
+}
+
+@property --tw-scale-x {
+  syntax: "*";
+  inherits: false;
+  initial-value: 1;
+}
+
+@property --tw-scale-y {
+  syntax: "*";
+  inherits: false;
+  initial-value: 1;
+}
+
+@property --tw-scale-z {
+  syntax: "*";
+  inherits: false;
+  initial-value: 1;
+}
+
+@keyframes enter {
+  from {
+    opacity: var(--tw-enter-opacity, 1);
+    transform: translate3d(var(--tw-enter-translate-x, 0), var(--tw-enter-translate-y, 0), 0) scale3d(var(--tw-enter-scale, 1), var(--tw-enter-scale, 1), var(--tw-enter-scale, 1)) rotate(var(--tw-enter-rotate, 0));
+  }
+}
+
+@keyframes exit {
+  to {
+    opacity: var(--tw-exit-opacity, 1);
+    transform: translate3d(var(--tw-exit-translate-x, 0), var(--tw-exit-translate-y, 0), 0) scale3d(var(--tw-exit-scale, 1), var(--tw-exit-scale, 1), var(--tw-exit-scale, 1)) rotate(var(--tw-exit-rotate, 0));
+  }
+}
+
+@keyframes accordion-down {
+  from {
+    height: 0;
+  }
+
+  to {
+    height: var(--radix-accordion-content-height, var(--bits-accordion-content-height));
+  }
+}
+
+@keyframes accordion-up {
+  from {
+    height: var(--radix-accordion-content-height, var(--bits-accordion-content-height));
+  }
+
+  to {
+    height: 0;
+  }
+}

+ 27 - 0
src/index.html

@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta name="description" content="Anycall - Connect with AI avatars of top global experts for professional consultations anytime">
+  <title>Anycall - Call any professional or interesting people</title>
+  
+  <!-- Canonical URL to prevent duplicate content issues -->
+  <link rel="canonical" href="https://www.anycall.online/" />
+  
+  <!-- Open Graph Meta Tags -->
+  <meta property="og:title" content="Anycall - Call any professional or interesting people">
+  <meta property="og:description" content="Connect with AI avatars of top global experts for professional consultations anytime">
+  <meta property="og:url" content="https://www.anycall.online/">
+  <meta property="og:type" content="website">
+  
+  <!-- Twitter Card Meta Tags -->
+  <meta name="twitter:card" content="summary_large_image">
+  <meta name="twitter:title" content="Anycall - Call any professional or interesting people">
+  <meta name="twitter:description" content="Connect with AI avatars of top global experts for professional consultations anytime">
+</head>
+<body>
+  <div id="root"></div>
+  <script type="module" src="/src/main.tsx"></script>
+</body>
+</html>

+ 979 - 0
src/lib/i18n.ts

@@ -0,0 +1,979 @@
+export type Language = 'zh' | 'en' | 'ja' | 'fr' | 'ko' | 'es';
+
+export const languages = {
+  zh: '中文',
+  en: 'English',
+  ja: '日本語',
+  fr: 'Français',
+  ko: '한국어',
+  es: 'Español'
+};
+
+export const translations = {
+  zh: {
+    // Header
+    home: '首页',
+    features: '功能',
+    faq: '常见问题',
+    contact: '联系我们',
+    getStarted: '开始使用',
+    
+    // Hero
+    heroTag: '连接全球行业专家',
+    heroTitle1: '呼叫任何专业人士',
+    heroTitle2: '或有趣的人',
+    heroDescription: 'Anycall连接全球顶尖专家的AI分身,随时为您提供专业咨询。无论是心理健康、法律咨询还是商业指导,专家触手可及。',
+    downloadIOS: 'App Store',
+    downloadAndroid: 'Android',
+    
+    // Experts
+    expertsTitle: '专业的行业专家',
+    expertsSubtitle: '汇聚全球各领域顶尖专家,随时为您提供专业指导',
+    viewAllExperts: '查看所有专家',
+    moreExperts: '还有更多专业领域的专家等待您的发现',
+    
+    psychologist: '心理咨询师',
+    psychologistDesc: '提供情绪管理、压力缓解和心理健康指导',
+    lawyer: '法律顾问',
+    lawyerDesc: '专业法律咨询,解答合同、知识产权等问题',
+    businessConsultant: '跨国商业顾问',
+    businessConsultantDesc: '国际商务拓展、跨境贸易和市场分析专家',
+    fitnessCoach: '康复教练',
+    fitnessCoachDesc: '运动康复、健康管理和体能训练指导',
+    educator: '教育专家',
+    educatorDesc: '学习方法、教育规划和职业发展指导',
+    financialAdvisor: '财务规划师',
+    financialAdvisorDesc: '投资理财、财富管理和税务筹划咨询',
+    careerMentor: '职业导师',
+    careerMentorDesc: '职业转型、简历优化和面试技巧指导',
+    nutritionist: '营养师',
+    nutritionistDesc: '饮食健康、营养搭配和体重管理方案',
+    
+    // Features
+    featuresTitle: '强大的功能特性',
+    featuresSubtitle: 'Anycall利用前沿AI技术,为您提供前所未有的沟通体验',
+    
+    globalExperts: '全球专家资源',
+    globalExpertsDesc: '汇聚各领域顶尖专家的AI分身,随时提供专业指导。',
+    instantConsult: '即时咨询',
+    instantConsultDesc: '无需预约等待,专家AI分身即刻响应您的咨询需求。',
+    privacy: '隐私保护',
+    privacyDesc: '端到端加密,确保咨询内容绝对安全私密。',
+    professional: '专业定制',
+    professionalDesc: 'AI分身基于专家的专业知识和经验,提供精准建议。',
+    available247: '24/7可用',
+    available247Desc: '突破时区限制,全天候获取专业咨询服务。',
+    smartLearning: '持续优化',
+    smartLearningDesc: 'AI不断学习最新专业知识,确保建议的准确性。',
+    
+    aiConsultTitle: '专业AI咨询体验',
+    aiConsultDesc: '我们的AI技术深度学习各领域专家的专业知识和实践经验,能够模拟专家的思维方式和沟通风格。无论是心理咨询、法律建议还是商业策略,Anycall都能为您提供专业且贴心的指导。',
+    aiConsultFeature1: '基于专家真实知识体系的专业建议',
+    aiConsultFeature2: '自然流畅的对话体验,如同真人咨询',
+    
+    // FAQ
+    faqTitle: '常见问题',
+    faqSubtitle: '关于Anycall的一切,您想知道的都在这里',
+    
+    faq1Q: '什么是Anycall?',
+    faq1A: 'Anycall是一个专业的AI咨询平台,连接全球各领域顶尖专家的AI分身。无论您需要心理咨询、法律建议、商业指导还是健康管理,都能随时获得专业的帮助和建议。',
+    faq2Q: '专家AI分身是如何创建的?',
+    faq2A: '平台上的AI专家由线下用户自行注册。每位专家需要提供专业资质证明、从业经历等材料,我们会对这些材料进行真实性审核。AI分身基于专家提供的专业知识、经验和材料进行训练。虽然我们会尽力验证专家资质的真实性,但我们建议用户在使用咨询服务时,仍需自行判断内容的准确性和价值,对于重要决策建议寻求多方意见。',
+    faq3Q: '咨询费用如何计算?',
+    faq3A: '我们提供灵活的计费方案,包括按分钟计费和订阅套餐。不同专业领域的咨询费率可能有所不同。新用户可获得免费试用额度,体验我们的专业服务。',
+    faq4Q: '咨询内容会保密吗?',
+    faq4A: '绝对保密。我们采用端到端加密技术保护您的咨询内容,不会存储或分享您的对话记录。我们严格遵守职业道德和数据保护法规,确保您的隐私绝对安全。',
+    faq5Q: '有哪些专业领域的专家?',
+    faq5A: '我们平台汇聚了心理咨询师、法律顾问、商业顾问、康复教练、教育专家、财务规划师、职业导师、营养师等各领域的顶尖专家。专家库还在持续扩充中。',
+    faq6Q: 'AI咨询能替代真人专家吗?',
+    faq6A: 'AI咨询旨在提供便捷、即时的专业建议,适合日常咨询和初步指导。对于复杂或紧急情况,我们建议寻求真人专家的帮助。我们也提供转介服务,帮您联系真人专家。',
+    faq7Q: '如何选择合适的专家?',
+    faq7A: '您可以根据专业领域、专长方向、用户评分等筛选专家。每位专家的详细资料页面都会展示其专业背景、擅长领域和用户评价,帮助您做出最佳选择。',
+    faq8Q: '如果遇到技术问题怎么办?',
+    faq8A: '我们提供7x24小时客户支持。您可以通过应用内的帮助中心、邮件或在线客服联系我们。我们的技术团队会尽快为您解决问题,确保您的咨询体验顺畅。',
+    
+    // Contact
+    contactTitle: '联系我们',
+    contactSubtitle: '有任何问题或建议?我们随时为您服务',
+    yourName: '您的姓名',
+    yourEmail: '您的邮箱',
+    yourMessage: '您的留言',
+    sendMessage: '发送消息',
+    
+    // Footer
+    footerDesc: '连接全球专家,获取专业指导。随时随地与行业顶尖专家的AI分身对话。',
+    product: '产品',
+    productIntro: '功能介绍',
+    pricing: '定价方案',
+    useCases: '使用案例',
+    apiDocs: 'API文档',
+    company: '公司',
+    about: '关于我们',
+    blog: '博客',
+    careers: '加入我们',
+    contactUs: '联系方式',
+    legal: '法律',
+    privacyPolicy: '隐私政策',
+    termsOfService: '服务条款',
+    cookiePolicy: 'Cookie政策',
+    disclaimer: '免责声明',
+    copyright: '© 2025 Anycall. 保留所有权利。',
+    
+    // About Page
+    aboutTitle: '关于我们',
+    aboutSubtitle: '我们正在通过AI技术让全球专业知识触手可及,改变人们获取专业咨询的方式',
+    ourMission: '我们的使命',
+    missionText1: 'Anycall致力于打破专业知识获取的障碍,让每个人都能随时随地与全球顶尖专家对话。',
+    missionText2: '通过先进的AI技术,我们为用户提供便捷、高效、专业的咨询服务,帮助人们在生活和事业中做出更好的决策。',
+    activeUsers: '活跃用户',
+    countries: '覆盖国家',
+    experts: '专业专家',
+    satisfaction: '满意度',
+    ourValues: '我们的价值观',
+    valuesSubtitle: '这些核心价值观指引着我们的每一个决策和行动',
+    innovation: '创新',
+    innovationText: '我们不断探索AI技术的前沿,为用户提供最先进的咨询体验。',
+    accessibility: '普及性',
+    accessibilityText: '我们致力于让专业知识触手可及,打破地域和时间的限制。',
+    quality: '质量',
+    qualityText: '我们与顶尖专家合作,确保AI分身提供准确、可靠的专业建议。',
+    ourTeam: '我们的团队',
+    teamSubtitle: '来自全球的专家和技术人才正在共同构建未来的咨询平台',
+    
+    // Blog Page
+    blogTitle: '博客',
+    blogSubtitle: '探索AI咨询的最新资讯、行业洞察和用户故事',
+    aiTech: 'AI技术',
+    mentalHealth: '心理健康',
+    business: '商业',
+    userStory: '用户故事',
+    healthcare: '健康医疗',
+    readMore: '阅读更多',
+    blog1Title: 'AI如何改变心理健康咨询行业',
+    blog1Excerpt: '探索人工智能如何让心理健康支持变得更加便捷和普及,帮助更多人获得及时的心理关怀。',
+    blog2Title: '24/7随时获取专业建议的价值',
+    blog2Excerpt: '了解即时咨询如何帮助用户在关键时刻做出明智决策,不再受时间和地点的限制。',
+    blog3Title: '跨国商业中的AI咨询应用',
+    blog3Excerpt: '从市场分析到文化咨询,看AI如何助力企业拓展全球市场。',
+    blog4Title: '用户故事:Anycall如何改变了我的生活',
+    blog4Excerpt: '一位用户分享她通过Anycall获得职业指导和心理支持的真实经历。',
+    blog5Title: '健康管理的未来:AI营养师和康复教练',
+    blog5Excerpt: '探索AI如何提供个性化的健康管理方案,让每个人都能拥有私人健康顾问。',
+    blog6Title: 'AI咨询的道德与隐私保护',
+    blog6Excerpt: '深入了解我们如何确保用户隐私,以及AI咨询在道德层面的考量。',
+    
+    // Careers Page
+    careersTitle: '加入我们',
+    careersSubtitle: '与我们一起塑造AI咨询的未来,让专业知识惠及全球',
+    whyJoinUs: '为什么选择我们',
+    whyJoinUsSubtitle: '加入Anycall,成为改变世界的一份子',
+    careerInnovation: '在AI技术的最前沿工作,参与创新项目。',
+    globalTeam: '全球化团队',
+    careerGlobal: '与来自世界各地的优秀人才协作。',
+    growth: '成长机会',
+    careerGrowth: '获得持续学习和职业发展的机会。',
+    openPositions: '职位空缺',
+    openPositionsSubtitle: '找到适合你的职位,开启新的职业旅程',
+    engineering: '工程技术',
+    aiResearch: 'AI研究',
+    productDesign: '产品设计',
+    marketing: '市场营销',
+    customerSuccess: '客户成功',
+    fullTime: '全职',
+    applyNow: '立即申请',
+    job1Title: '高级全栈工程师',
+    job1Desc: '负责开发和维护Anycall平台的核心功能,优化用户体验和系统性能。',
+    job2Title: 'AI研究科学家',
+    job2Desc: '研究和开发下一代对话AI技术,提升专家AI分身的智能水平。',
+    job3Title: '产品设计师',
+    job3Desc: '设计直观、美观的用户界面,提升产品的易用性和用户满意度。',
+    job4Title: '内容营销专员',
+    job4Desc: '创作引人入胜的内容,向全球用户传递Anycall的价值。',
+    job5Title: '客户成功经理',
+    job5Desc: '帮助用户获得最佳咨询体验,建立长期的客户关系。',
+    
+    // Legal Pages
+    lastUpdated: '最后更新',
+    
+    // Privacy Policy
+    privacyIntro: '引言',
+    privacyIntroText: 'Anycall重视您的隐私。本隐私政策说明我们如何收集、使用和保护您的个人信息。使用我们的服务即表示您同意本政策中描述的做法。',
+    infoWeCollect: '我们收集的信息',
+    personalInfo: '个人信息',
+    personalInfoText: '当您注册和使用Anycall时,我们可能收集以下信息:',
+    privacyItem1: '姓名、电子邮件地址、电话号码',
+    privacyItem2: '账户登录凭据',
+    privacyItem3: '使用服务时的咨询内容和互动记录',
+    privacyItem4: '支付信息(通过第三方支付处理商安全处理)',
+    howWeUseInfo: '我们如何使用信息',
+    howWeUseInfoText: '我们收集的信息用于以下目的:',
+    useItem1: '提供和改进我们的AI咨询服务',
+    useItem2: '处理交易和账单',
+    useItem3: '发送服务相关的通知和更新',
+    useItem4: '确保平台安全,防止欺诈行为',
+    useItem5: '进行研究和分析以优化用户体验',
+    dataSecurity: '数据安全',
+    dataSecurityText: '我们采用行业标准的加密技术和安全措施保护您的数据。所有咨询内容都经过端到端加密,确保您的隐私绝对安全。',
+    dataSharing: '数据共享',
+    dataSharingText: '我们不会向第三方出售您的个人信息。只有在法律要求或经您明确同意的情况下,我们才会与第三方共享您的信息。',
+    yourRights: '您的权利',
+    yourRightsText: '您有权:',
+    rightItem1: '访问和查看我们持有的关于您的个人信息',
+    rightItem2: '要求更正或删除您的个人信息',
+    rightItem3: '随时撤回对数据处理的同意',
+    rightItem4: '向相关监管机构投诉',
+    contactPrivacy: '联系我们',
+    contactPrivacyText: '如果您对我们的隐私政策有任何疑问,请通过以下方式联系我们:',
+    
+    // Terms of Service
+    termsIntro: '服务条款概述',
+    termsIntroText: '欢迎使用Anycall。访问或使用我们的服务即表示您同意遵守这些服务条款。请仔细阅读以下内容。',
+    serviceDescription: '服务说明',
+    serviceDescriptionText: 'Anycall提供基于人工智能的专业咨询服务。我们的AI分身是基于真实专家的知识训练而成,旨在提供信息和指导,但不能替代真人专业服务。',
+    userResponsibilities: '用户责任',
+    userResponsibilitiesText: '使用Anycall服务时,您同意:',
+    respItem1: '提供准确、真实的注册信息',
+    respItem2: '维护账户安全,不与他人共享登录凭据',
+    respItem3: '遵守所有适用的法律法规',
+    respItem4: '不滥用服务或从事任何非法活动',
+    aiLimitations: 'AI服务的局限性',
+    aiLimitationsText: 'AI咨询服务仅供参考,不构成专业医疗、法律或财务建议。对于紧急或严重的情况,请咨询合格的真人专业人士。',
+    paymentTerms: '付款条款',
+    paymentTermsText: '我们提供多种付费选项。所有费用均以显示的货币计价。订阅可能会自动续订,您可以随时在账户设置中取消。',
+    intellectualProperty: '知识产权',
+    intellectualPropertyText: 'Anycall及其所有内容、功能和特性均属于我们或我们的许可方的财产。未经授权,不得复制、分发或创建衍生作品。',
+    termination: '服务终止',
+    terminationText: '我们保留在您违反这些条款时暂停或终止您的账户的权利。您也可以随时关闭账户。',
+    limitationLiability: '责任限制',
+    limitationLiabilityText: '在法律允许的最大范围内,Anycall不对使用或无法使用服务造成的任何损害负责。',
+    changesTerms: '条款变更',
+    changesTermsText: '我们可能会不时更新这些条款。重大变更将通过电子邮件或服务内通知告知您。继续使用服务即表示您接受修订后的条款。',
+    contactTerms: '联系我们',
+    contactTermsText: '如有任何关于服务条款的问题,请联系我们:',
+    
+    // Cookie Policy
+    cookieIntro: 'Cookie政策说明',
+    cookieIntroText: '本政策解释了Anycall如何使用Cookie和类似技术来识别您访问我们的服务。它还说明了这些技术是什么以及我们为何使用它们。',
+    whatAreCookies: '什么是Cookie',
+    whatAreCookiesText: 'Cookie是当您访问网站时放置在您设备上的小文本文件。它们被广泛用于使网站工作或更有效地工作,以及向网站所有者提供信息。',
+    howWeUseCookies: '我们如何使用Cookie',
+    howWeUseCookiesText: 'Anycall使用以下类型的Cookie:',
+    essentialCookies: '必要Cookie',
+    essentialCookiesText: '这些Cookie对于网站的基本功能是必需的,例如安全登录和账户管理。',
+    analyticsCookies: '分析Cookie',
+    analyticsCookiesText: '这些Cookie帮助我们了解访问者如何与网站互动,从而改进我们的服务。',
+    functionalCookies: '功能Cookie',
+    functionalCookiesText: '这些Cookie使网站能够记住您的选择(如语言偏���)并提供增强的个性化功能。',
+    marketingCookies: '营销Cookie',
+    marketingCookiesText: '这些Cookie用于跟踪访问者的行为,以便向他们展示相关的广告。',
+    manageCookies: '管理Cookie',
+    manageCookiesText: '您可以通过浏览器设置控制和/或删除Cookie。但是,禁用某些Cookie可能会影响网站的功能。',
+    thirdPartyCookies: '第三方Cookie',
+    thirdPartyCookiesText: '我们可能使用第三方服务(如Google Analytics)来帮助我们分析用户如何使用我们的服务。这些第三方有自己的隐私政策。',
+    updatesCookiePolicy: 'Cookie政策更新',
+    updatesCookiePolicyText: '我们可能会不时更新此Cookie政策。任何更改都将发布在此页面上。',
+    contactCookies: '联系我们',
+    contactCookiesText: '如果您对我们的Cookie使用有任何疑问,请联系我们:',
+  },
+  en: {
+    // Header
+    home: 'Home',
+    features: 'Features',
+    faq: 'FAQ',
+    contact: 'Contact',
+    getStarted: 'Get Started',
+    
+    // Hero
+    heroTag: 'Connect with Global Experts',
+    heroTitle1: 'Call any professional',
+    heroTitle2: 'or interesting people',
+    heroDescription: 'Anycall connects you with AI avatars of top global experts, providing professional consultations anytime. From mental health to legal advice and business guidance, experts are at your fingertips.',
+    downloadIOS: 'App Store',
+    downloadAndroid: 'Android',
+    
+    // Experts
+    expertsTitle: 'Professional Industry Experts',
+    expertsSubtitle: 'Connecting top experts from around the world to provide professional guidance anytime',
+    viewAllExperts: 'View All Experts',
+    moreExperts: 'Discover more experts across various professional fields',
+    
+    psychologist: 'Psychologist',
+    psychologistDesc: 'Emotional management, stress relief, and mental health guidance',
+    lawyer: 'Legal Advisor',
+    lawyerDesc: 'Professional legal consultation on contracts, IP, and more',
+    businessConsultant: 'Business Consultant',
+    businessConsultantDesc: 'International business expansion and cross-border trade expert',
+    fitnessCoach: 'Fitness Coach',
+    fitnessCoachDesc: 'Sports rehabilitation, health management, and training guidance',
+    educator: 'Education Expert',
+    educatorDesc: 'Learning methods, education planning, and career development',
+    financialAdvisor: 'Financial Advisor',
+    financialAdvisorDesc: 'Investment, wealth management, and tax planning consultation',
+    careerMentor: 'Career Mentor',
+    careerMentorDesc: 'Career transition, resume optimization, and interview coaching',
+    nutritionist: 'Nutritionist',
+    nutritionistDesc: 'Diet health, nutrition planning, and weight management',
+    
+    // Features
+    featuresTitle: 'Powerful Features',
+    featuresSubtitle: 'Anycall leverages cutting-edge AI to provide unprecedented communication experiences',
+    
+    globalExperts: 'Global Expert Resources',
+    globalExpertsDesc: 'Access AI avatars of top experts across all fields for professional guidance.',
+    instantConsult: 'Instant Consultation',
+    instantConsultDesc: 'No appointment needed. Expert AI avatars respond to your queries immediately.',
+    privacy: 'Privacy Protection',
+    privacyDesc: 'End-to-end encryption ensures your consultation content is absolutely secure.',
+    professional: 'Professional Customization',
+    professionalDesc: 'AI avatars based on expert knowledge and experience provide precise advice.',
+    available247: '24/7 Available',
+    available247Desc: 'Break through time zones and access professional consultation services anytime.',
+    smartLearning: 'Continuous Optimization',
+    smartLearningDesc: 'AI continuously learns the latest professional knowledge for accurate advice.',
+    
+    aiConsultTitle: 'Professional AI Consultation Experience',
+    aiConsultDesc: 'Our AI technology deeply learns expert knowledge and practical experience, simulating expert thinking and communication styles. Whether psychology, legal advice, or business strategy, Anycall provides professional and thoughtful guidance.',
+    aiConsultFeature1: 'Professional advice based on real expert knowledge systems',
+    aiConsultFeature2: 'Natural, smooth conversation experience like real consultations',
+    
+    // FAQ
+    faqTitle: 'Frequently Asked Questions',
+    faqSubtitle: 'Everything you want to know about Anycall',
+    
+    faq1Q: 'What is Anycall?',
+    faq1A: 'Anycall is a professional AI consultation platform connecting AI avatars of top global experts. Whether you need psychological counseling, legal advice, business guidance, or health management, you can get professional help anytime.',
+    faq2Q: 'How are expert AI avatars created?',
+    faq2A: 'AI experts on the platform are registered by offline users themselves. Each expert must provide professional credentials, work experience, and other materials, which we verify for authenticity. AI avatars are trained based on the professional knowledge, experience, and materials provided by experts. While we make every effort to verify expert credentials, we recommend users exercise their own judgment regarding the accuracy and value of consultation content, and seek multiple opinions for important decisions.',
+    faq3Q: 'How is consultation pricing calculated?',
+    faq3A: 'We offer flexible pricing including per-minute billing and subscription packages. Consultation rates may vary by professional field. New users receive free trial credits to experience our professional services.',
+    faq4Q: 'Is consultation content confidential?',
+    faq4A: 'Absolutely. We use end-to-end encryption to protect your consultation content and never store or share your conversation records. We strictly follow professional ethics and data protection regulations to ensure absolute privacy.',
+    faq5Q: 'What professional fields are covered?',
+    faq5A: 'Our platform includes psychologists, legal advisors, business consultants, fitness coaches, education experts, financial advisors, career mentors, nutritionists, and more. Our expert library continues to expand.',
+    faq6Q: 'Can AI consultation replace real experts?',
+    faq6A: 'AI consultation aims to provide convenient, instant professional advice suitable for daily consultation and initial guidance. For complex or urgent situations, we recommend seeking real expert help. We also provide referral services.',
+    faq7Q: 'How to choose the right expert?',
+    faq7A: 'You can filter experts by professional field, specialization, user ratings, etc. Each expert profile page shows their background, expertise, and user reviews to help you make the best choice.',
+    faq8Q: 'What if I encounter technical issues?',
+    faq8A: 'We provide 24/7 customer support. You can contact us through the in-app help center, email, or online chat. Our technical team will quickly resolve issues to ensure smooth consultation experience.',
+    
+    // Contact
+    contactTitle: 'Contact Us',
+    contactSubtitle: 'Have questions or suggestions? We\'re here to help',
+    yourName: 'Your Name',
+    yourEmail: 'Your Email',
+    yourMessage: 'Your Message',
+    sendMessage: 'Send Message',
+    
+    // Footer
+    footerDesc: 'Connect with global experts for professional guidance. Talk to AI avatars of top industry experts anytime, anywhere.',
+    product: 'Product',
+    productIntro: 'Features',
+    pricing: 'Pricing',
+    useCases: 'Use Cases',
+    apiDocs: 'API Docs',
+    company: 'Company',
+    about: 'About Us',
+    blog: 'Blog',
+    careers: 'Careers',
+    contactUs: 'Contact',
+    legal: 'Legal',
+    privacyPolicy: 'Privacy Policy',
+    termsOfService: 'Terms of Service',
+    cookiePolicy: 'Cookie Policy',
+    disclaimer: 'Disclaimer',
+    copyright: '© 2025 Anycall. All rights reserved.',
+    
+    // About Page
+    aboutTitle: 'About Us',
+    aboutSubtitle: 'We are transforming how people access professional expertise by making global knowledge accessible through AI technology',
+    ourMission: 'Our Mission',
+    missionText1: 'Anycall is dedicated to breaking down barriers to professional knowledge, enabling everyone to connect with top global experts anytime, anywhere.',
+    missionText2: 'Through advanced AI technology, we provide convenient, efficient, and professional consulting services that help people make better decisions in life and career.',
+    activeUsers: 'Active Users',
+    countries: 'Countries',
+    experts: 'Experts',
+    satisfaction: 'Satisfaction',
+    ourValues: 'Our Values',
+    valuesSubtitle: 'These core values guide every decision and action we take',
+    innovation: 'Innovation',
+    innovationText: 'We continuously explore cutting-edge AI technology to provide the most advanced consulting experience.',
+    accessibility: 'Accessibility',
+    accessibilityText: 'We are committed to making professional knowledge accessible, breaking down geographical and time barriers.',
+    quality: 'Quality',
+    qualityText: 'We partner with top experts to ensure AI avatars provide accurate and reliable professional advice.',
+    ourTeam: 'Our Team',
+    teamSubtitle: 'Global experts and tech talents are building the future of consulting together',
+    
+    // Blog Page
+    blogTitle: 'Blog',
+    blogSubtitle: 'Explore the latest news, industry insights, and user stories about AI consulting',
+    aiTech: 'AI Technology',
+    mentalHealth: 'Mental Health',
+    business: 'Business',
+    userStory: 'User Stories',
+    healthcare: 'Healthcare',
+    readMore: 'Read More',
+    blog1Title: 'How AI is Transforming Mental Health Consulting',
+    blog1Excerpt: 'Explore how artificial intelligence is making mental health support more accessible and widespread, helping more people receive timely psychological care.',
+    blog2Title: 'The Value of 24/7 Access to Professional Advice',
+    blog2Excerpt: 'Learn how instant consultation helps users make informed decisions at critical moments, no longer limited by time and location.',
+    blog3Title: 'AI Consulting in Cross-Border Business',
+    blog3Excerpt: 'From market analysis to cultural consulting, see how AI empowers businesses to expand globally.',
+    blog4Title: 'User Story: How Anycall Changed My Life',
+    blog4Excerpt: 'A user shares her real experience of receiving career guidance and psychological support through Anycall.',
+    blog5Title: 'The Future of Health Management: AI Nutritionists and Rehabilitation Coaches',
+    blog5Excerpt: 'Explore how AI provides personalized health management solutions, giving everyone a personal health advisor.',
+    blog6Title: 'Ethics and Privacy Protection in AI Consulting',
+    blog6Excerpt: 'Deep dive into how we ensure user privacy and the ethical considerations of AI consulting.',
+    
+    // Careers Page
+    careersTitle: 'Careers',
+    careersSubtitle: 'Join us in shaping the future of AI consulting and bringing professional knowledge to the world',
+    whyJoinUs: 'Why Join Us',
+    whyJoinUsSubtitle: 'Join Anycall and be part of changing the world',
+    careerInnovation: 'Work at the forefront of AI technology and participate in innovative projects.',
+    globalTeam: 'Global Team',
+    careerGlobal: 'Collaborate with exceptional talents from around the world.',
+    growth: 'Growth Opportunities',
+    careerGrowth: 'Get continuous learning and career development opportunities.',
+    openPositions: 'Open Positions',
+    openPositionsSubtitle: 'Find the right position for you and start a new career journey',
+    engineering: 'Engineering',
+    aiResearch: 'AI Research',
+    productDesign: 'Product & Design',
+    marketing: 'Marketing',
+    customerSuccess: 'Customer Success',
+    fullTime: 'Full-time',
+    applyNow: 'Apply Now',
+    job1Title: 'Senior Full-Stack Engineer',
+    job1Desc: 'Develop and maintain core features of the Anycall platform, optimizing user experience and system performance.',
+    job2Title: 'AI Research Scientist',
+    job2Desc: 'Research and develop next-generation conversational AI technology to enhance the intelligence of expert AI avatars.',
+    job3Title: 'Product Designer',
+    job3Desc: 'Design intuitive and beautiful user interfaces to improve product usability and user satisfaction.',
+    job4Title: 'Content Marketing Specialist',
+    job4Desc: 'Create compelling content to communicate Anycall\'s value to global users.',
+    job5Title: 'Customer Success Manager',
+    job5Desc: 'Help users get the best consulting experience and build long-term customer relationships.',
+    
+    // Legal Pages
+    lastUpdated: 'Last Updated',
+    
+    // Privacy Policy
+    privacyIntro: 'Introduction',
+    privacyIntroText: 'Anycall values your privacy. This Privacy Policy explains how we collect, use, and protect your personal information. By using our services, you agree to the practices described in this policy.',
+    infoWeCollect: 'Information We Collect',
+    personalInfo: 'Personal Information',
+    personalInfoText: 'When you register and use Anycall, we may collect the following information:',
+    privacyItem1: 'Name, email address, phone number',
+    privacyItem2: 'Account login credentials',
+    privacyItem3: 'Consultation content and interaction records when using services',
+    privacyItem4: 'Payment information (securely processed through third-party payment processors)',
+    howWeUseInfo: 'How We Use Information',
+    howWeUseInfoText: 'The information we collect is used for the following purposes:',
+    useItem1: 'Provide and improve our AI consulting services',
+    useItem2: 'Process transactions and billing',
+    useItem3: 'Send service-related notifications and updates',
+    useItem4: 'Ensure platform security and prevent fraud',
+    useItem5: 'Conduct research and analysis to optimize user experience',
+    dataSecurity: 'Data Security',
+    dataSecurityText: 'We use industry-standard encryption technologies and security measures to protect your data. All consultation content is end-to-end encrypted to ensure absolute privacy.',
+    dataSharing: 'Data Sharing',
+    dataSharingText: 'We do not sell your personal information to third parties. We only share your information with third parties when required by law or with your explicit consent.',
+    yourRights: 'Your Rights',
+    yourRightsText: 'You have the right to:',
+    rightItem1: 'Access and view personal information we hold about you',
+    rightItem2: 'Request correction or deletion of your personal information',
+    rightItem3: 'Withdraw consent to data processing at any time',
+    rightItem4: 'File a complaint with relevant regulatory authorities',
+    contactPrivacy: 'Contact Us',
+    contactPrivacyText: 'If you have any questions about our privacy policy, please contact us at:',
+    
+    // Terms of Service
+    termsIntro: 'Terms of Service Overview',
+    termsIntroText: 'Welcome to Anycall. By accessing or using our services, you agree to be bound by these Terms of Service. Please read the following carefully.',
+    serviceDescription: 'Service Description',
+    serviceDescriptionText: 'Anycall provides AI-based professional consulting services. Our AI avatars are trained on real expert knowledge to provide information and guidance, but cannot replace real professional services.',
+    userResponsibilities: 'User Responsibilities',
+    userResponsibilitiesText: 'When using Anycall services, you agree to:',
+    respItem1: 'Provide accurate and truthful registration information',
+    respItem2: 'Maintain account security and not share login credentials with others',
+    respItem3: 'Comply with all applicable laws and regulations',
+    respItem4: 'Not misuse the service or engage in any illegal activities',
+    aiLimitations: 'AI Service Limitations',
+    aiLimitationsText: 'AI consulting services are for reference only and do not constitute professional medical, legal, or financial advice. For urgent or serious situations, please consult qualified real professionals.',
+    paymentTerms: 'Payment Terms',
+    paymentTermsText: 'We offer multiple payment options. All fees are charged in the currency displayed. Subscriptions may auto-renew, which you can cancel anytime in account settings.',
+    intellectualProperty: 'Intellectual Property',
+    intellectualPropertyText: 'Anycall and all its content, features, and functions are the property of us or our licensors. Unauthorized reproduction, distribution, or creation of derivative works is prohibited.',
+    termination: 'Termination',
+    terminationText: 'We reserve the right to suspend or terminate your account if you violate these terms. You may also close your account at any time.',
+    limitationLiability: 'Limitation of Liability',
+    limitationLiabilityText: 'To the maximum extent permitted by law, Anycall is not liable for any damages arising from the use or inability to use the service.',
+    changesTerms: 'Changes to Terms',
+    changesTermsText: 'We may update these terms from time to time. Significant changes will be communicated via email or in-service notification. Continued use of the service indicates acceptance of revised terms.',
+    contactTerms: 'Contact Us',
+    contactTermsText: 'If you have any questions about the Terms of Service, please contact us at:',
+    
+    // Cookie Policy
+    cookieIntro: 'Cookie Policy Explanation',
+    cookieIntroText: 'This policy explains how Anycall uses cookies and similar technologies to identify you when you visit our services. It also explains what these technologies are and why we use them.',
+    whatAreCookies: 'What Are Cookies',
+    whatAreCookiesText: 'Cookies are small text files placed on your device when you visit a website. They are widely used to make websites work more efficiently and provide information to website owners.',
+    howWeUseCookies: 'How We Use Cookies',
+    howWeUseCookiesText: 'Anycall uses the following types of cookies:',
+    essentialCookies: 'Essential Cookies',
+    essentialCookiesText: 'These cookies are necessary for basic website functionality, such as secure login and account management.',
+    analyticsCookies: 'Analytics Cookies',
+    analyticsCookiesText: 'These cookies help us understand how visitors interact with the website, allowing us to improve our services.',
+    functionalCookies: 'Functional Cookies',
+    functionalCookiesText: 'These cookies enable the website to remember your choices (such as language preferences) and provide enhanced personalized features.',
+    marketingCookies: 'Marketing Cookies',
+    marketingCookiesText: 'These cookies are used to track visitor behavior to show them relevant advertisements.',
+    manageCookies: 'Managing Cookies',
+    manageCookiesText: 'You can control and/or delete cookies through your browser settings. However, disabling certain cookies may affect website functionality.',
+    thirdPartyCookies: 'Third-Party Cookies',
+    thirdPartyCookiesText: 'We may use third-party services (such as Google Analytics) to help us analyze how users use our service. These third parties have their own privacy policies.',
+    updatesCookiePolicy: 'Cookie Policy Updates',
+    updatesCookiePolicyText: 'We may update this Cookie Policy from time to time. Any changes will be posted on this page.',
+    contactCookies: 'Contact Us',
+    contactCookiesText: 'If you have any questions about our cookie usage, please contact us at:',
+  },
+  ja: {
+    // Header
+    home: 'ホーム',
+    features: '機能',
+    faq: 'よくある質問',
+    contact: 'お問い合わせ',
+    getStarted: '始める',
+    
+    // Hero
+    heroTag: 'グローバル専門家とつながる',
+    heroTitle1: 'あらゆる専門家や',
+    heroTitle2: '興味深い人に電話',
+    heroDescription: 'Anycallは世界トップクラスの専門家のAIアバターと繋がり、いつでも専門的なコンサルティングを提供します。メンタルヘルス、法律相談、ビジネスガイダンスなど、専門家がすぐそばに。',
+    downloadIOS: 'App Store',
+    downloadAndroid: 'Android',
+    
+    // Experts
+    expertsTitle: 'プロフェッショナルな業界専門家',
+    expertsSubtitle: '世界中の各分野のトップ専門家を集め、いつでも専門的な指導を提供',
+    viewAllExperts: 'すべての専門家を見る',
+    moreExperts: 'さらに多くの専門分野の専門家を発見',
+    
+    psychologist: '心理カウンセラー',
+    psychologistDesc: '感情管理、ストレス緩和、メンタルヘルスガイダンス',
+    lawyer: '法律顧問',
+    lawyerDesc: '契約、知的財産などの専門的な法律相談',
+    businessConsultant: '国際ビジネスコンサルタント',
+    businessConsultantDesc: '国際ビジネス展開、越境貿易、市場分析の専門家',
+    fitnessCoach: 'リハビリコーチ',
+    fitnessCoachDesc: 'スポーツリハビリ、健康管理、体力トレーニングガイダンス',
+    educator: '教育専門家',
+    educatorDesc: '学習方法、教育計画、キャリア開発ガイダンス',
+    financialAdvisor: 'ファイナンシャルプランナー',
+    financialAdvisorDesc: '投資、資産管理、税務計画コンサルティング',
+    careerMentor: 'キャリアメンター',
+    careerMentorDesc: 'キャリア転換、履歴書最適化、面接テクニックガイダンス',
+    nutritionist: '栄養士',
+    nutritionistDesc: '食事の健康、栄養計画、体重管理プログラム',
+    
+    // Features
+    featuresTitle: '強力な機能',
+    featuresSubtitle: 'Anycallは最先端のAI技術を活用し、前例のないコミュニケーション体験を提供',
+    
+    globalExperts: 'グローバル専門家リソース',
+    globalExpertsDesc: '各分野のトップ専門家のAIアバターが専門的な指導を提供。',
+    instantConsult: '即時コンサルティング',
+    instantConsultDesc: '予約不要。専門家AIアバターが即座に相談に応答。',
+    privacy: 'プライバシー保護',
+    privacyDesc: 'エンドツーエンド暗号化で相談内容の絶対的な安全性を確保。',
+    professional: '専門的なカスタマイズ',
+    professionalDesc: '専門家の知識と経験に基づくAIアバターが正確なアドバイスを提供。',
+    available247: '24時間365日利用��能',
+    available247Desc: 'タイムゾーンを超えて、いつでも専門的なコンサルティングサービスにアクセス。',
+    smartLearning: '継続的な最適化',
+    smartLearningDesc: 'AIは最新の専門知識を継続的に学習し、アドバイスの正確性を確保。',
+    
+    aiConsultTitle: '専門的なAIコンサルティング体験',
+    aiConsultDesc: '当社のAI技術は各分野の専門家の専門知識と実践経験を深く学習し、専門家の思考方式とコミュニケーションスタイルをシミュレート。心理カウンセリング、法律アドバイス、ビジネス戦略など、Anycallは専門的で丁寧な指導を提供します。',
+    aiConsultFeature1: '専門家の実際の知識体系に基づく専門的なアドバイス',
+    aiConsultFeature2: '実際のコンサルティングのような自然でスムーズな会話体験',
+    
+    // FAQ
+    faqTitle: 'よくある質問',
+    faqSubtitle: 'Anycallについて知りたいことすべて',
+    
+    faq1Q: 'Anycallとは何ですか?',
+    faq1A: 'Anycallは、世界トップクラスの専門家のAIアバターとつながる専門的なAIコンサルティングプラットフォームです。心理カウンセリング、法律相談、ビジネスガイダンス、健康管理など、いつでも専門的なサポートを受けることができます。',
+    faq2Q: '専門家のAIアバターはどのように作成されますか?',
+    faq2A: 'プラットフォーム上のAI専門家は、オフラインユーザー自身が登録します。各専門家は専門資格証明、職務経歴などの資料を提供する必要があり、当社はこれらの資料の真正性を審査します。AIアバターは専門家が提供する専門知識、経験、資料に基づいてトレーニングされます。専門家資格の真正性を検証するよう努めていますが、コンサルティングサービスを利用する際は、内容の正確性と価値についてご自身で判断していただき、重要な決定については複数の意見を求めることをお勧めします。',
+    faq3Q: 'コンサルティング料金はどのように計算されますか?',
+    faq3A: '分単位の課金とサブスクリプションパッケージを含む柔軟な料金プランを提供しています。専門分野によってコンサルティング料金は異なる場合があります。新規ユーザーは無料トライアルクレジットを受け取れます。',
+    faq4Q: 'コンサルティング内容は秘密にされますか?',
+    faq4A: '絶対に秘密です。エンドツーエンド暗号化技術でコンサルティング内容を保護し、会話記録を保存または共有しません。職業倫理とデータ保護規制を厳格に遵守し、絶対的なプライバシーを確保します。',
+    faq5Q: 'どのような専門分野がありますか?',
+    faq5A: '当プラットフォームには、心理カウンセラー、法律顧問、ビジネスコンサルタント、リハビリコーチ、教育専門家、ファイナンシャルプラ���ナー、キャリアメンター、栄養士など、各分野のトップ専門家が集まっています。専門家ライブラリは継続的に拡大中です。',
+    faq6Q: 'AIコンサルティングは実際の専門家に代わることができますか?',
+    faq6A: 'AIコンサルティングは、便利で即時的な専門的アドバイスを提供し、日常的な相談と初期ガイダンスに適しています。複雑または緊急の状況には、実際の専門家の助けを求めることをお勧めします。紹介サービスも提供しています。',
+    faq7Q: '適切な専門家をどのように選択しますか?',
+    faq7A: '専門分野、専門方向、ユーザー評価などで専門家をフィルタリングできます。各専門家の詳細プロフィールページには、専門背景、得意分野、ユーザーレビューが表示され、最適な選択をサポートします。',
+    faq8Q: '技術的な問題が発生した場合は?',
+    faq8A: '24時間年中無休のカスタマーサポートを提供しています。アプリ内ヘルプセンター、メール、オンラインチャットでお問い合わせいただけます。技術チームが迅速に問題を解決し、スムーズなコンサルティング体験を保証します。',
+    
+    // Contact
+    contactTitle: 'お問い合わせ',
+    contactSubtitle: 'ご質問やご提案はありますか?お気軽にお問い合わせください',
+    yourName: 'お名前',
+    yourEmail: 'メールアドレス',
+    yourMessage: 'メッセージ',
+    sendMessage: '送信',
+    
+    // Footer
+    footerDesc: 'グローバル専門家とつながり、専門的なガイダンスを受ける。いつでもどこでも業界トップ専門家のAIアバターと対話。',
+    product: '製品',
+    productIntro: '機能紹介',
+    pricing: '料金プラン',
+    useCases: '使用例',
+    apiDocs: 'APIドキュメント',
+    company: '会社',
+    about: '会社概要',
+    blog: 'ブログ',
+    careers: '採用情報',
+    contactUs: '連絡先',
+    legal: '法的情報',
+    privacyPolicy: 'プライバシーポリシー',
+    termsOfService: '利用規約',
+    cookiePolicy: 'Cookieポリシー',
+    disclaimer: '免責事項',
+    copyright: '© 2025 Anycall. All rights reserved.',
+  },
+  fr: {
+    // Header
+    home: 'Accueil',
+    features: 'Fonctionnalités',
+    faq: 'FAQ',
+    contact: 'Contact',
+    getStarted: 'Commencer',
+    
+    // Hero
+    heroTag: 'Connectez-vous aux Experts Mondiaux',
+    heroTitle1: 'Appelez tout professionnel',
+    heroTitle2: 'ou personne intéressante',
+    heroDescription: 'Anycall vous connecte aux avatars IA des meilleurs experts mondiaux, offrant des consultations professionnelles à tout moment. De la santé mentale aux conseils juridiques et au guidage commercial, les experts sont à portée de main.',
+    downloadIOS: 'App Store',
+    downloadAndroid: 'Android',
+    
+    // Experts
+    expertsTitle: 'Experts Professionnels',
+    expertsSubtitle: 'Rassembler les meilleurs experts du monde entier pour fournir des conseils professionnels à tout moment',
+    viewAllExperts: 'Voir tous les experts',
+    moreExperts: 'Découvrez plus d\'experts dans divers domaines professionnels',
+    
+    psychologist: 'Psychologue',
+    psychologistDesc: 'Gestion émotionnelle, soulagement du stress et conseils en santé mentale',
+    lawyer: 'Conseiller Juridique',
+    lawyerDesc: 'Consultation juridique professionnelle sur les contrats, la PI, etc.',
+    businessConsultant: 'Consultant Commercial',
+    businessConsultantDesc: 'Expert en expansion commerciale internationale et commerce transfrontalier',
+    fitnessCoach: 'Coach Fitness',
+    fitnessCoachDesc: 'Réhabilitation sportive, gestion de la santé et conseils d\'entraînement',
+    educator: 'Expert en Éducation',
+    educatorDesc: 'Méthodes d\'apprentissage, planification éducative et développement de carrière',
+    financialAdvisor: 'Conseiller Financier',
+    financialAdvisorDesc: 'Investissement, gestion de patrimoine et consultation fiscale',
+    careerMentor: 'Mentor de Carrière',
+    careerMentorDesc: 'Transition de carrière, optimisation de CV et coaching d\'entretien',
+    nutritionist: 'Nutritionniste',
+    nutritionistDesc: 'Santé alimentaire, planification nutritionnelle et gestion du poids',
+    
+    // Features
+    featuresTitle: 'Fonctionnalités Puissantes',
+    featuresSubtitle: 'Anycall utilise l\'IA de pointe pour offrir une expérience de communication sans précédent',
+    
+    globalExperts: 'Ressources d\'Experts Mondiaux',
+    globalExpertsDesc: 'Accédez aux avatars IA des meilleurs experts dans tous les domaines.',
+    instantConsult: 'Consultation Instantanée',
+    instantConsultDesc: 'Pas de rendez-vous nécessaire. Les avatars IA répondent immédiatement.',
+    privacy: 'Protection de la Vie Privée',
+    privacyDesc: 'Chiffrement de bout en bout pour une sécurité absolue de vos consultations.',
+    professional: 'Personnalisation Professionnelle',
+    professionalDesc: 'Avatars IA basés sur les connaissances et l\'expérience des experts.',
+    available247: 'Disponible 24/7',
+    available247Desc: 'Dépassez les fuseaux horaires et accédez aux services à tout moment.',
+    smartLearning: 'Optimisation Continue',
+    smartLearningDesc: 'L\'IA apprend continuellement les dernières connaissances professionnelles.',
+    
+    aiConsultTitle: 'Expérience de Consultation IA Professionnelle',
+    aiConsultDesc: 'Notre technologie IA apprend en profondeur les connaissances et l\'expérience pratique des experts, simulant leur mode de pensée et style de communication. Que ce soit psychologie, conseil juridique ou stratégie commerciale, Anycall fournit des conseils professionnels et attentionnés.',
+    aiConsultFeature1: 'Conseils professionnels basés sur les systèmes de connaissances réels des experts',
+    aiConsultFeature2: 'Expérience de conversation naturelle et fluide comme les vraies consultations',
+    
+    // FAQ
+    faqTitle: 'Questions Fréquentes',
+    faqSubtitle: 'Tout ce que vous voulez savoir sur Anycall',
+    
+    faq1Q: 'Qu\'est-ce qu\'Anycall?',
+    faq1A: 'Anycall est une plateforme de consultation IA professionnelle connectant les avatars IA des meilleurs experts mondiaux. Que vous ayez besoin de conseil psychologique, juridique, commercial ou de gestion de la santé, vous pouvez obtenir une aide professionnelle à tout moment.',
+    faq2Q: 'Comment les avatars IA des experts sont-ils créés?',
+    faq2A: 'Les experts IA sur la plateforme sont enregistrés par les utilisateurs hors ligne eux-mêmes. Chaque expert doit fournir des justificatifs professionnels, une expérience de travail et d\'autres documents, que nous vérifions pour leur authenticité. Les avatars IA sont formés sur la base des connaissances professionnelles, de l\'expérience et des documents fournis par les experts. Bien que nous fassions tous les efforts pour vérifier les qualifications des experts, nous recommandons aux utilisateurs d\'exercer leur propre jugement concernant l\'exactitude et la valeur du contenu de consultation, et de rechercher plusieurs avis pour les décisions importantes.',
+    faq3Q: 'Comment les prix de consultation sont-ils calculés?',
+    faq3A: 'Nous proposons des tarifs flexibles incluant la facturation à la minute et des forfaits d\'abonnement. Les tarifs peuvent varier selon le domaine professionnel. Les nouveaux utilisateurs reçoivent des crédits d\'essai gratuits.',
+    faq4Q: 'Le contenu de consultation est-il confidentiel?',
+    faq4A: 'Absolument. Nous utilisons le chiffrement de bout en bout pour protéger votre contenu de consultation et ne stockons ni ne partageons jamais vos enregistrements de conversation. Nous respectons strictement l\'éthique professionnelle et les réglementations sur la protection des données.',
+    faq5Q: 'Quels domaines professionnels sont couverts?',
+    faq5A: 'Notre plateforme comprend des psychologues, conseillers juridiques, consultants commerciaux, coachs fitness, experts en éducation, conseillers financiers, mentors de carrière, nutritionnistes et plus encore. Notre bibliothèque d\'experts continue de s\'étendre.',
+    faq6Q: 'La consultation IA peut-elle remplacer les vrais experts?',
+    faq6A: 'La consultation IA vise à fournir des conseils professionnels pratiques et instantanés adaptés aux consultations quotidiennes et aux conseils initiaux. Pour les situations complexes ou urgentes, nous recommandons de consulter de vrais experts. Nous proposons également des services de référence.',
+    faq7Q: 'Comment choisir le bon expert?',
+    faq7A: 'Vous pouvez filtrer les experts par domaine professionnel, spécialisation, notes des utilisateurs, etc. Chaque page de profil d\'expert affiche son parcours, son expertise et les avis des utilisateurs pour vous aider à faire le meilleur choix.',
+    faq8Q: 'Que faire en cas de problème technique?',
+    faq8A: 'Nous fournissons un support client 24/7. Vous pouvez nous contacter via le centre d\'aide de l\'application, par email ou par chat en ligne. Notre équipe technique résoudra rapidement les problèmes pour garantir une expérience de consultation fluide.',
+    
+    // Contact
+    contactTitle: 'Nous Contacter',
+    contactSubtitle: 'Des questions ou des suggestions? Nous sommes là pour vous aider',
+    yourName: 'Votre Nom',
+    yourEmail: 'Votre Email',
+    yourMessage: 'Votre Message',
+    sendMessage: 'Envoyer',
+    
+    // Footer
+    footerDesc: 'Connectez-vous aux experts mondiaux pour des conseils professionnels. Parlez aux avatars IA des meilleurs experts à tout moment, n\'importe où.',
+    product: 'Produit',
+    productIntro: 'Fonctionnalités',
+    pricing: 'Tarifs',
+    useCases: 'Cas d\'Usage',
+    apiDocs: 'Documentation API',
+    company: 'Entreprise',
+    about: 'À Propos',
+    blog: 'Blog',
+    careers: 'Carrières',
+    contactUs: 'Contact',
+    legal: 'Juridique',
+    privacyPolicy: 'Politique de Confidentialité',
+    termsOfService: 'Conditions d\'Utilisation',
+    cookiePolicy: 'Politique de Cookies',
+    disclaimer: 'Avertissement',
+    copyright: '© 2025 Anycall. Tous droits réservés.',
+  },
+  ko: {
+    // Header
+    home: '홈',
+    features: '기능',
+    faq: 'FAQ',
+    contact: '문의하기',
+    getStarted: '시작하기',
+    
+    // Hero
+    heroTag: '글로벌 전문가와 연결',
+    heroTitle1: '모든 전문가나',
+    heroTitle2: '흥미로운 사람에게 전화',
+    heroDescription: 'Anycall은 세계 최고의 전문가 AI 아바타와 연결하여 언제든지 전문 상담을 제공합니다. 정신 건강, 법률 상담, 비즈니스 지도 등 전문가가 바로 여기에 있습니다.',
+    downloadIOS: 'App Store',
+    downloadAndroid: 'Android',
+    
+    // Experts
+    expertsTitle: '전문 업계 전문가',
+    expertsSubtitle: '전 세계 각 분야의 최고 전문가를 모아 언제든지 전문적인 지도를 제공',
+    viewAllExperts: '모든 전문가 보기',
+    moreExperts: '더 많은 전문 분야의 전문가를 발견하세요',
+    
+    psychologist: '심리 상담사',
+    psychologistDesc: '감정 관리, 스트레스 완화 및 정신 건강 지도',
+    lawyer: '법률 고문',
+    lawyerDesc: '계약, 지적 재산권 등에 대한 전문 법률 상담',
+    businessConsultant: '국제 비즈니스 컨설턴트',
+    businessConsultantDesc: '국제 비즈니스 확장, 국경 간 무역 및 시장 분석 전문가',
+    fitnessCoach: '재활 코치',
+    fitnessCoachDesc: '스포츠 재활, 건강 관리 및 체력 훈련 지도',
+    educator: '교육 전문가',
+    educatorDesc: '학습 방법, 교육 계획 및 진로 개발 지도',
+    financialAdvisor: '재무 설계사',
+    financialAdvisorDesc: '투자, 자산 관리 및 세무 계획 상담',
+    careerMentor: '커리어 멘토',
+    careerMentorDesc: '커리어 전환, 이력서 최적화 및 면접 코칭',
+    nutritionist: '영양사',
+    nutritionistDesc: '식이 건강, 영양 계획 및 체중 관리',
+    
+    // Features
+    featuresTitle: '강력한 기능',
+    featuresSubtitle: 'Anycall은 최첨단 AI 기술을 활용하여 전례 없는 커뮤니케이션 경험을 제공',
+    
+    globalExperts: '글로벌 전문가 리소스',
+    globalExpertsDesc: '모든 분야의 최고 전문가 AI 아바타에 액세스하여 전문적인 지도를 받으세요.',
+    instantConsult: '즉시 상담',
+    instantConsultDesc: '예약 불필요. 전문가 AI 아바타가 즉시 응답합니다.',
+    privacy: '개인 정보 보호',
+    privacyDesc: '엔드투엔드 암호화로 상담 내용의 절대적인 보안을 보장합니다.',
+    professional: '전문적 맞춤화',
+    professionalDesc: '전문가의 지식과 경험을 기반으로 한 AI 아바타가 정확한 조언을 제공합니다.',
+    available247: '24/7 이용 가능',
+    available247Desc: '시간대를 초월하여 언제든지 전문 상담 서비스에 액세스하세요.',
+    smartLearning: '지속적인 최적화',
+    smartLearningDesc: 'AI는 최신 전문 지식을 지속적으로 학습하여 조언의 정확성을 보장합니다.',
+    
+    aiConsultTitle: '전문 AI 상담 경험',
+    aiConsultDesc: '우리의 AI 기술은 각 분야 전문가의 전문 지식과 실무 경험을 깊이 학습하여 전문가의 사고 방식과 커뮤니케이션 스타일을 시뮬레이션합니다. 심리 상담, 법률 조언, 비즈니스 전략 등 Anycall은 전문적이고 세심한 지도를 제공합니다.',
+    aiConsultFeature1: '전문가의 실제 지식 체계를 기반으로 한 전문적인 조언',
+    aiConsultFeature2: '실제 상담과 같은 자연스럽고 원활한 대화 경험',
+    
+    // FAQ
+    faqTitle: '자주 묻는 질문',
+    faqSubtitle: 'Anycall에 대해 알고 싶은 모든 것',
+    
+    faq1Q: 'Anycall이란 무엇인가요?',
+    faq1A: 'Anycall은 전 세계 최고 전문가의 AI 아바타와 연결하는 전문 AI 컨설팅 플랫폼입니다. 심리 상담, 법률 자문, 비즈니스 가이드, 건강 관리 등 언제든지 전문적인 도움을 받을 수 있습니다.',
+    faq2Q: '전문가 AI 아바타는 어떻게 만들어지나요?',
+    faq2A: '플랫폼의 AI 전문가는 오프라인 사용자가 직접 등록합니다. 각 전문가는 전문 자격증, 경력 등의 자료를 제공해야 하며, 저희는 이러한 자료의 진위를 심사합니다. AI 아바타는 전문가가 제공한 전문 지식, 경험 및 자료를 기반으로 교육됩니다. 전문가 자격의 진위를 검증하기 위해 노력하고 있지만, 컨설팅 서비스를 이용할 때 콘텐츠의 정확성과 가치에 대해 스스로 판단하고 중요한 결정에 대해서는 여러 의견을 구하시기를 권장합니다.',
+    faq3Q: '상담 비용은 어떻게 계산되나요?',
+    faq3A: '분 단위 청구 및 구독 패키지를 포함한 유연한 요금제를 제공합니다. 전문 분야에 따라 상담 요금이 다를 수 있습니다. 신규 사용자는 무료 체험 크레딧을 받을 수 있습니다.',
+    faq4Q: '상담 내용은 기밀로 유지되나요?',
+    faq4A: '절대적으로 기밀입니다. 엔드투엔드 암호화 기술로 상담 내용을 보호하며 대화 기록을 저장하거나 공유하지 않습니다. 우리는 직업 윤리 및 데이터 보호 규정을 엄격히 준수하여 절대적인 개인 정보를 보장합니다.',
+    faq5Q: '어떤 전문 분야가 포함되나요?',
+    faq5A: '우리 플랫폼에는 심리 상담사, 법률 고문, 비즈니스 컨설턴트, 재활 코치, 교육 전문가, 재무 설계사, 커리어 멘토, 영양사 등 각 분야의 최고 전문가가 있습니다. 전문가 라이브러리는 계속 확장 중입니다.',
+    faq6Q: 'AI 상담이 실제 전문가를 대체할 수 있나요?',
+    faq6A: 'AI 상담은 편리하고 즉각적인 전문적인 조언을 제공하며 일상적인 상담 및 초기 지도에 적합합니다. 복잡하거나 긴급한 상황의 경우 실제 전문가의 도움을 구하는 것이 좋습니다. 우리는 또한 의뢰 서비스를 제공합니다.',
+    faq7Q: '적합한 전문가를 어떻게 선택하나요?',
+    faq7A: '전문 분야, 전문 방향, 사용자 평점 등으로 전문가를 필터링할 수 있습니다. 각 전문가의 세부 프로필 페이지에는 전문 배경, 전문 분야 및 사용자 리뷰가 표시되어 최선의 선택을 할 수 있도록 도와줍니다.',
+    faq8Q: '기술적인 문제가 발생하면 어떻게 하나요?',
+    faq8A: '연중무휴 24시간 고객 지원을 제공합니다. 앱 내 도움말 센터, 이메일 또는 온라인 채팅을 통해 문의할 수 있습니다. 기술 팀이 신속하게 문제를 해결하여 원활한 상담 경험을 보장합니다.',
+    
+    // Contact
+    contactTitle: '문의하기',
+    contactSubtitle: '질문이나 제안이 있으신가요? 언제든지 도와드리겠습니다',
+    yourName: '이름',
+    yourEmail: '이메일',
+    yourMessage: '메시지',
+    sendMessage: '보내기',
+    
+    // Footer
+    footerDesc: '글로벌 전문가와 연결하여 전문적인 지도를 받으세요. 언제 어디서나 업계 최고 전문가의 AI 아바타와 대화하세요.',
+    product: '제품',
+    productIntro: '기�� 소개',
+    pricing: '요금제',
+    useCases: '사용 사례',
+    apiDocs: 'API 문서',
+    company: '회사',
+    about: '회사 소개',
+    blog: '블로그',
+    careers: '채용',
+    contactUs: '연락처',
+    legal: '법률',
+    privacyPolicy: '개인정보 처리방침',
+    termsOfService: '서비스 약관',
+    cookiePolicy: '쿠키 정책',
+    disclaimer: '면책 조항',
+    copyright: '© 2025 Anycall. All rights reserved.',
+  },
+  es: {
+    // Header
+    home: 'Inicio',
+    features: 'Características',
+    faq: 'Preguntas Frecuentes',
+    contact: 'Contacto',
+    getStarted: 'Comenzar',
+    
+    // Hero
+    heroTag: 'Conéctese con Expertos Globales',
+    heroTitle1: 'Llame a cualquier profesional',
+    heroTitle2: 'o persona interesante',
+    heroDescription: 'Anycall lo conecta con avatares IA de los mejores expertos globales, brindando consultas profesionales en cualquier momento. Desde salud mental hasta asesoramiento legal y orientación empresarial, los expertos están a su alcance.',
+    downloadIOS: 'App Store',
+    downloadAndroid: 'Android',
+    
+    // Experts
+    expertsTitle: 'Expertos Profesionales',
+    expertsSubtitle: 'Reuniendo a los mejores expertos de todo el mundo para brindar orientación profesional en cualquier momento',
+    viewAllExperts: 'Ver Todos los Expertos',
+    moreExperts: 'Descubra más expertos en diversos campos profesionales',
+    
+    psychologist: 'Psicólogo',
+    psychologistDesc: 'Gestión emocional, alivio del estrés y orientación en salud mental',
+    lawyer: 'Asesor Legal',
+    lawyerDesc: 'Consulta legal profesional sobre contratos, PI y más',
+    businessConsultant: 'Consultor de Negocios',
+    businessConsultantDesc: 'Experto en expansión comercial internacional y comercio transfronterizo',
+    fitnessCoach: 'Entrenador de Fitness',
+    fitnessCoachDesc: 'Rehabilitación deportiva, gestión de la salud y orientación de entrenamiento',
+    educator: 'Experto en Educación',
+    educatorDesc: 'Métodos de aprendizaje, planificación educativa y desarrollo profesional',
+    financialAdvisor: 'Asesor Financiero',
+    financialAdvisorDesc: 'Inversión, gestión patrimonial y consultoría fiscal',
+    careerMentor: 'Mentor de Carrera',
+    careerMentorDesc: 'Transición profesional, optimización de CV y coaching de entrevistas',
+    nutritionist: 'Nutricionista',
+    nutritionistDesc: 'Salud dietética, planificación nutricional y gestión del peso',
+    
+    // Features
+    featuresTitle: 'Características Potentes',
+    featuresSubtitle: 'Anycall aprovecha la IA de vanguardia para ofrecer experiencias de comunicación sin precedentes',
+    
+    globalExperts: 'Recursos de Expertos Globales',
+    globalExpertsDesc: 'Acceda a avatares IA de los mejores expertos en todos los campos.',
+    instantConsult: 'Consulta Instantánea',
+    instantConsultDesc: 'Sin cita necesaria. Los avatares IA responden inmediatamente.',
+    privacy: 'Protección de Privacidad',
+    privacyDesc: 'Cifrado de extremo a extremo garantiza la seguridad absoluta de sus consultas.',
+    professional: 'Personalización Profesional',
+    professionalDesc: 'Avatares IA basados en el conocimiento y experiencia de expertos.',
+    available247: 'Disponible 24/7',
+    available247Desc: 'Supere las zonas horarias y acceda a servicios en cualquier momento.',
+    smartLearning: 'Optimización Continua',
+    smartLearningDesc: 'La IA aprende continuamente los últimos conocimientos profesionales.',
+    
+    aiConsultTitle: 'Experiencia de Consulta IA Profesional',
+    aiConsultDesc: 'Nuestra tecnología IA aprende profundamente el conocimiento y la experiencia práctica de los expertos, simulando su modo de pensamiento y estilo de comunicación. Ya sea psicología, asesoramiento legal o estrategia empresarial, Anycall proporciona orientación profesional y atenta.',
+    aiConsultFeature1: 'Asesoramiento profesional basado en sistemas de conocimiento reales de expertos',
+    aiConsultFeature2: 'Experiencia de conversación natural y fluida como consultas reales',
+    
+    // FAQ
+    faqTitle: 'Preguntas Frecuentes',
+    faqSubtitle: 'Todo lo que quiere saber sobre Anycall',
+    
+    faq1Q: '¿Qué es Anycall?',
+    faq1A: 'Anycall es una plataforma de consultoría de IA profesional que conecta avatares de IA de los mejores expertos globales. Ya sea que necesite asesoramiento psicológico, legal, comercial o de gestión de salud, puede obtener ayuda profesional en cualquier momento.',
+    faq2Q: '¿Cómo se crean los avatares de IA de expertos?',
+    faq2A: 'Los expertos de IA en la plataforma son registrados por los propios usuarios fuera de línea. Cada experto debe proporcionar credenciales profesionales, experiencia laboral y otros materiales, que verificamos para autenticidad. Los avatares de IA se entrenan basándose en el conocimiento profesional, la experiencia y los materiales proporcionados por los expertos. Aunque hacemos todos los esfuerzos para verificar las credenciales de los expertos, recomendamos que los usuarios ejerzan su propio juicio sobre la exactitud y el valor del contenido de consultoría, y busquen múltiples opiniones para decisiones importantes.',
+    faq3Q: '¿Cómo se calculan los precios de consulta?',
+    faq3A: 'Ofrecemos precios flexibles que incluyen facturación por minuto y paquetes de suscripción. Las tarifas pueden variar según el campo profesional. Los nuevos usuarios reciben créditos de prueba gratuitos.',
+    faq4Q: '¿El contenido de consulta es confidencial?',
+    faq4A: 'Absolutamente. Utilizamos cifrado de extremo a extremo para proteger su contenido de consulta y nunca almacenamos ni compartimos sus registros de conversación. Cumplimos estrictamente con la ética profesional y las regulaciones de protección de datos.',
+    faq5Q: '¿Qué campos profesionales están cubiertos?',
+    faq5A: 'Nuestra plataforma incluye psicólogos, asesores legales, consultores comerciales, entrenadores de fitness, expertos en educación, asesores financieros, mentores de carrera, nutricionistas y más. Nuestra biblioteca de expertos continúa expandiéndose.',
+    faq6Q: '¿Puede la consulta IA reemplazar a los expertos reales?',
+    faq6A: 'La consulta IA tiene como objetivo proporcionar asesoramiento profesional conveniente e instantáneo adecuado para consultas diarias y orientación inicial. Para situaciones complejas o urgentes, recomendamos buscar ayuda de expertos reales. También proporcionamos servicios de referencia.',
+    faq7Q: '¿Cómo elegir el experto adecuado?',
+    faq7A: 'Puede filtrar expertos por campo profesional, especialización, calificaciones de usuarios, etc. Cada página de perfil de experto muestra su experiencia, áreas de especialización y reseñas de usuarios para ayudarle a tomar la mejor decisión.',
+    faq8Q: '¿Qué hacer si encuentro problemas técnicos?',
+    faq8A: 'Proporcionamos soporte al cliente 24/7. Puede contactarnos a través del centro de ayuda de la aplicación, correo electrónico o chat en línea. Nuestro equipo técnico resolverá rápidamente los problemas para garantizar una experiencia de consulta fluida.',
+    
+    // Contact
+    contactTitle: 'Contáctenos',
+    contactSubtitle: '¿Tiene preguntas o sugerencias? Estamos aquí para ayudar',
+    yourName: 'Su Nombre',
+    yourEmail: 'Su Email',
+    yourMessage: 'Su Mensaje',
+    sendMessage: 'Enviar Mensaje',
+    
+    // Footer
+    footerDesc: 'Conéctese con expertos globales para orientación profesional. Hable con avatares IA de los mejores expertos en cualquier momento y lugar.',
+    product: 'Producto',
+    productIntro: 'Características',
+    pricing: 'Precios',
+    useCases: 'Casos de Uso',
+    apiDocs: 'Documentación API',
+    company: 'Empresa',
+    about: 'Sobre Nosotros',
+    blog: 'Blog',
+    careers: 'Carreras',
+    contactUs: 'Contacto',
+    legal: 'Legal',
+    privacyPolicy: 'Política de Privacidad',
+    termsOfService: 'Términos de Servicio',
+    cookiePolicy: 'Política de Cookies',
+    disclaimer: 'Descargo de Responsabilidad',
+    copyright: '© 2025 Anycall. Todos los derechos reservados.',
+  },
+};

+ 7 - 0
src/main.tsx

@@ -0,0 +1,7 @@
+
+  import { createRoot } from "react-dom/client";
+  import App from "./App.tsx";
+  import "./index.css";
+
+  createRoot(document.getElementById("root")!).render(<App />);
+  

+ 2 - 0
src/public/_redirects/Code-component-66-108.tsx

@@ -0,0 +1,2 @@
+# Netlify redirects
+/*    /index.html   200

+ 2 - 0
src/public/_redirects/Code-component-66-81.tsx

@@ -0,0 +1,2 @@
+# Netlify redirects
+/*    /index.html   200

+ 212 - 0
src/styles/globals.css

@@ -0,0 +1,212 @@
+@custom-variant dark (&:is(.dark *));
+
+:root {
+  --font-size: 16px;
+  --background: #ffffff;
+  --foreground: oklch(0.145 0 0);
+  --card: #ffffff;
+  --card-foreground: oklch(0.145 0 0);
+  --popover: oklch(1 0 0);
+  --popover-foreground: oklch(0.145 0 0);
+  --primary: #030213;
+  --primary-foreground: oklch(1 0 0);
+  --secondary: oklch(0.95 0.0058 264.53);
+  --secondary-foreground: #030213;
+  --muted: #ececf0;
+  --muted-foreground: #717182;
+  --accent: #e9ebef;
+  --accent-foreground: #030213;
+  --destructive: #d4183d;
+  --destructive-foreground: #ffffff;
+  --border: rgba(0, 0, 0, 0.1);
+  --input: transparent;
+  --input-background: #f3f3f5;
+  --switch-background: #cbced4;
+  --font-weight-medium: 500;
+  --font-weight-normal: 400;
+  --ring: oklch(0.708 0 0);
+  --chart-1: oklch(0.646 0.222 41.116);
+  --chart-2: oklch(0.6 0.118 184.704);
+  --chart-3: oklch(0.398 0.07 227.392);
+  --chart-4: oklch(0.828 0.189 84.429);
+  --chart-5: oklch(0.769 0.188 70.08);
+  --radius: 0.625rem;
+  --sidebar: oklch(0.985 0 0);
+  --sidebar-foreground: oklch(0.145 0 0);
+  --sidebar-primary: #030213;
+  --sidebar-primary-foreground: oklch(0.985 0 0);
+  --sidebar-accent: oklch(0.97 0 0);
+  --sidebar-accent-foreground: oklch(0.205 0 0);
+  --sidebar-border: oklch(0.922 0 0);
+  --sidebar-ring: oklch(0.708 0 0);
+}
+
+.dark {
+  --background: oklch(0.145 0 0);
+  --foreground: oklch(0.985 0 0);
+  --card: oklch(0.145 0 0);
+  --card-foreground: oklch(0.985 0 0);
+  --popover: oklch(0.145 0 0);
+  --popover-foreground: oklch(0.985 0 0);
+  --primary: oklch(0.985 0 0);
+  --primary-foreground: oklch(0.205 0 0);
+  --secondary: oklch(0.269 0 0);
+  --secondary-foreground: oklch(0.985 0 0);
+  --muted: oklch(0.269 0 0);
+  --muted-foreground: oklch(0.708 0 0);
+  --accent: oklch(0.269 0 0);
+  --accent-foreground: oklch(0.985 0 0);
+  --destructive: oklch(0.396 0.141 25.723);
+  --destructive-foreground: oklch(0.637 0.237 25.331);
+  --border: oklch(0.269 0 0);
+  --input: oklch(0.269 0 0);
+  --ring: oklch(0.439 0 0);
+  --font-weight-medium: 500;
+  --font-weight-normal: 400;
+  --chart-1: oklch(0.488 0.243 264.376);
+  --chart-2: oklch(0.696 0.17 162.48);
+  --chart-3: oklch(0.769 0.188 70.08);
+  --chart-4: oklch(0.627 0.265 303.9);
+  --chart-5: oklch(0.645 0.246 16.439);
+  --sidebar: oklch(0.205 0 0);
+  --sidebar-foreground: oklch(0.985 0 0);
+  --sidebar-primary: oklch(0.488 0.243 264.376);
+  --sidebar-primary-foreground: oklch(0.985 0 0);
+  --sidebar-accent: oklch(0.269 0 0);
+  --sidebar-accent-foreground: oklch(0.985 0 0);
+  --sidebar-border: oklch(0.269 0 0);
+  --sidebar-ring: oklch(0.439 0 0);
+}
+
+@theme inline {
+  --color-background: var(--background);
+  --color-foreground: var(--foreground);
+  --color-card: var(--card);
+  --color-card-foreground: var(--card-foreground);
+  --color-popover: var(--popover);
+  --color-popover-foreground: var(--popover-foreground);
+  --color-primary: var(--primary);
+  --color-primary-foreground: var(--primary-foreground);
+  --color-secondary: var(--secondary);
+  --color-secondary-foreground: var(--secondary-foreground);
+  --color-muted: var(--muted);
+  --color-muted-foreground: var(--muted-foreground);
+  --color-accent: var(--accent);
+  --color-accent-foreground: var(--accent-foreground);
+  --color-destructive: var(--destructive);
+  --color-destructive-foreground: var(--destructive-foreground);
+  --color-border: var(--border);
+  --color-input: var(--input);
+  --color-input-background: var(--input-background);
+  --color-switch-background: var(--switch-background);
+  --color-ring: var(--ring);
+  --color-chart-1: var(--chart-1);
+  --color-chart-2: var(--chart-2);
+  --color-chart-3: var(--chart-3);
+  --color-chart-4: var(--chart-4);
+  --color-chart-5: var(--chart-5);
+  --radius-sm: calc(var(--radius) - 4px);
+  --radius-md: calc(var(--radius) - 2px);
+  --radius-lg: var(--radius);
+  --radius-xl: calc(var(--radius) + 4px);
+  --color-sidebar: var(--sidebar);
+  --color-sidebar-foreground: var(--sidebar-foreground);
+  --color-sidebar-primary: var(--sidebar-primary);
+  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+  --color-sidebar-accent: var(--sidebar-accent);
+  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+  --color-sidebar-border: var(--sidebar-border);
+  --color-sidebar-ring: var(--sidebar-ring);
+}
+
+@layer base {
+  * {
+    @apply border-border outline-ring/50;
+  }
+
+  body {
+    @apply bg-background text-foreground;
+  }
+
+  /* Smooth scrolling for anchor links */
+  html {
+    scroll-behavior: smooth;
+  }
+
+  /* Offset anchor position to account for fixed header */
+  /* Mobile needs larger offset due to browser address bar and section padding */
+  section[id] {
+    scroll-margin-top: 7rem; /* 112px for mobile - header (56px) + section padding (48px) */
+  }
+
+  @media (min-width: 640px) {
+    section[id] {
+      scroll-margin-top: 7.5rem; /* 120px for tablet - header (64px) + section padding (64px) */
+    }
+  }
+
+  @media (min-width: 1024px) {
+    section[id] {
+      scroll-margin-top: 8rem; /* 128px for desktop - header (64px) + section padding (80px) */
+    }
+  }
+}
+
+/**
+ * Base typography. This is not applied to elements which have an ancestor with a Tailwind text class.
+ */
+@layer base {
+  :where(:not(:has([class*=" text-"]), :not(:has([class^="text-"])))) {
+    h1 {
+      font-size: var(--text-2xl);
+      font-weight: var(--font-weight-medium);
+      line-height: 1.5;
+    }
+
+    h2 {
+      font-size: var(--text-xl);
+      font-weight: var(--font-weight-medium);
+      line-height: 1.5;
+    }
+
+    h3 {
+      font-size: var(--text-lg);
+      font-weight: var(--font-weight-medium);
+      line-height: 1.5;
+    }
+
+    h4 {
+      font-size: var(--text-base);
+      font-weight: var(--font-weight-medium);
+      line-height: 1.5;
+    }
+
+    p {
+      font-size: var(--text-base);
+      font-weight: var(--font-weight-normal);
+      line-height: 1.5;
+    }
+
+    label {
+      font-size: var(--text-base);
+      font-weight: var(--font-weight-medium);
+      line-height: 1.5;
+    }
+
+    button {
+      font-size: var(--text-base);
+      font-weight: var(--font-weight-medium);
+      line-height: 1.5;
+    }
+
+    input {
+      font-size: var(--text-base);
+      font-weight: var(--font-weight-normal);
+      line-height: 1.5;
+    }
+  }
+}
+
+html {
+  font-size: var(--font-size);
+}

+ 44 - 0
src/vercel.json

@@ -0,0 +1,44 @@
+{
+  "redirects": [
+    {
+      "source": "/:path((?!www\\.).*)",
+      "has": [
+        {
+          "type": "host",
+          "value": "anycall.online"
+        }
+      ],
+      "destination": "https://www.anycall.online/:path*",
+      "permanent": true
+    }
+  ],
+  "rewrites": [
+    {
+      "source": "/(.*)",
+      "destination": "/index.html"
+    }
+  ],
+  "headers": [
+    {
+      "source": "/(.*)",
+      "headers": [
+        {
+          "key": "X-Content-Type-Options",
+          "value": "nosniff"
+        },
+        {
+          "key": "X-Frame-Options",
+          "value": "DENY"
+        },
+        {
+          "key": "X-XSS-Protection",
+          "value": "1; mode=block"
+        },
+        {
+          "key": "Referrer-Policy",
+          "value": "strict-origin-when-cross-origin"
+        }
+      ]
+    }
+  ]
+}

+ 61 - 0
vite.config.ts

@@ -0,0 +1,61 @@
+
+  import { defineConfig } from 'vite';
+  import react from '@vitejs/plugin-react-swc';
+  import path from 'path';
+
+  export default defineConfig({
+    plugins: [react()],
+    resolve: {
+      extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
+      alias: {
+        'vaul@1.1.2': 'vaul',
+        'sonner@2.0.3': 'sonner',
+        'recharts@2.15.2': 'recharts',
+        'react-resizable-panels@2.1.7': 'react-resizable-panels',
+        'react-hook-form@7.55.0': 'react-hook-form',
+        'react-day-picker@8.10.1': 'react-day-picker',
+        'next-themes@0.4.6': 'next-themes',
+        'lucide-react@0.487.0': 'lucide-react',
+        'input-otp@1.4.2': 'input-otp',
+        'embla-carousel-react@8.6.0': 'embla-carousel-react',
+        'cmdk@1.1.1': 'cmdk',
+        'class-variance-authority@0.7.1': 'class-variance-authority',
+        '@radix-ui/react-tooltip@1.1.8': '@radix-ui/react-tooltip',
+        '@radix-ui/react-toggle@1.1.2': '@radix-ui/react-toggle',
+        '@radix-ui/react-toggle-group@1.1.2': '@radix-ui/react-toggle-group',
+        '@radix-ui/react-tabs@1.1.3': '@radix-ui/react-tabs',
+        '@radix-ui/react-switch@1.1.3': '@radix-ui/react-switch',
+        '@radix-ui/react-slot@1.1.2': '@radix-ui/react-slot',
+        '@radix-ui/react-slider@1.2.3': '@radix-ui/react-slider',
+        '@radix-ui/react-separator@1.1.2': '@radix-ui/react-separator',
+        '@radix-ui/react-select@2.1.6': '@radix-ui/react-select',
+        '@radix-ui/react-scroll-area@1.2.3': '@radix-ui/react-scroll-area',
+        '@radix-ui/react-radio-group@1.2.3': '@radix-ui/react-radio-group',
+        '@radix-ui/react-progress@1.1.2': '@radix-ui/react-progress',
+        '@radix-ui/react-popover@1.1.6': '@radix-ui/react-popover',
+        '@radix-ui/react-navigation-menu@1.2.5': '@radix-ui/react-navigation-menu',
+        '@radix-ui/react-menubar@1.1.6': '@radix-ui/react-menubar',
+        '@radix-ui/react-label@2.1.2': '@radix-ui/react-label',
+        '@radix-ui/react-hover-card@1.1.6': '@radix-ui/react-hover-card',
+        '@radix-ui/react-dropdown-menu@2.1.6': '@radix-ui/react-dropdown-menu',
+        '@radix-ui/react-dialog@1.1.6': '@radix-ui/react-dialog',
+        '@radix-ui/react-context-menu@2.2.6': '@radix-ui/react-context-menu',
+        '@radix-ui/react-collapsible@1.1.3': '@radix-ui/react-collapsible',
+        '@radix-ui/react-checkbox@1.1.4': '@radix-ui/react-checkbox',
+        '@radix-ui/react-avatar@1.1.3': '@radix-ui/react-avatar',
+        '@radix-ui/react-aspect-ratio@1.1.2': '@radix-ui/react-aspect-ratio',
+        '@radix-ui/react-alert-dialog@1.1.6': '@radix-ui/react-alert-dialog',
+        '@radix-ui/react-accordion@1.2.3': '@radix-ui/react-accordion',
+        '@': path.resolve(__dirname, './src'),
+      },
+    },
+    build: {
+      target: 'esnext',
+      outDir: 'build',
+    },
+    base: '/fara/h5/',
+    server: {
+      port: 3000,
+      open: true,
+    },
+  });

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików