Ver Fonte

Initialize Esim Vite React project

Add initial project structure for Esim Vite, including React components, pages, Redux store, API services, configuration files, and documentation. This sets up the foundation for a premium travel eSIM and SIM card web application with global connectivity features.
hieubt há 1 mês atrás
pai
commit
eec5c0de28
44 ficheiros alterados com 8238 adições e 0 exclusões
  1. 24 0
      EsimLao/esim-vite/.gitignore
  2. 20 0
      EsimLao/esim-vite/README.md
  3. 58 0
      EsimLao/esim-vite/index.html
  4. 7 0
      EsimLao/esim-vite/metadata.json
  5. 4539 0
      EsimLao/esim-vite/package-lock.json
  6. 29 0
      EsimLao/esim-vite/package.json
  7. 84 0
      EsimLao/esim-vite/src/App.tsx
  8. 37 0
      EsimLao/esim-vite/src/apis/axios.ts
  9. 45 0
      EsimLao/esim-vite/src/apis/baseApi.ts
  10. 15 0
      EsimLao/esim-vite/src/apis/productApi.ts
  11. 13 0
      EsimLao/esim-vite/src/app/store.ts
  12. 176 0
      EsimLao/esim-vite/src/components/ChatView.tsx
  13. 63 0
      EsimLao/esim-vite/src/components/Footer.tsx
  14. 448 0
      EsimLao/esim-vite/src/components/Header.tsx
  15. 141 0
      EsimLao/esim-vite/src/components/ImageView.tsx
  16. 119 0
      EsimLao/esim-vite/src/components/SearchView.tsx
  17. 69 0
      EsimLao/esim-vite/src/components/Sidebar.tsx
  18. 34 0
      EsimLao/esim-vite/src/components/TestimonialCard.tsx
  19. 31 0
      EsimLao/esim-vite/src/components/TopLoader.tsx
  20. 191 0
      EsimLao/esim-vite/src/components/VideoView.tsx
  21. 22 0
      EsimLao/esim-vite/src/features/areas/areasSlice.ts
  22. 23 0
      EsimLao/esim-vite/src/features/loading/loadingSlice.ts
  23. 5 0
      EsimLao/esim-vite/src/hooks/useRedux.ts
  24. 24 0
      EsimLao/esim-vite/src/index.tsx
  25. 79 0
      EsimLao/esim-vite/src/pages/buy-sim/BuySimView.tsx
  26. 41 0
      EsimLao/esim-vite/src/pages/contact/ContactView.tsx
  27. 136 0
      EsimLao/esim-vite/src/pages/handbook/HandbookView.tsx
  28. 371 0
      EsimLao/esim-vite/src/pages/home/HomeView.tsx
  29. 106 0
      EsimLao/esim-vite/src/pages/home/components/HomeBanner.tsx
  30. 102 0
      EsimLao/esim-vite/src/pages/home/components/HomeFaq.tsx
  31. 193 0
      EsimLao/esim-vite/src/pages/home/components/HomeProduct.tsx
  32. 49 0
      EsimLao/esim-vite/src/pages/home/components/HomeSearch.tsx
  33. 105 0
      EsimLao/esim-vite/src/pages/home/components/HomeTestimonial.tsx
  34. 44 0
      EsimLao/esim-vite/src/pages/login/LoginView.tsx
  35. 137 0
      EsimLao/esim-vite/src/pages/news/NewsDetailView.tsx
  36. 134 0
      EsimLao/esim-vite/src/pages/news/NewsView.tsx
  37. 210 0
      EsimLao/esim-vite/src/pages/product-detail/ProductDetailView.tsx
  38. 73 0
      EsimLao/esim-vite/src/pages/support/SupportView.tsx
  39. 95 0
      EsimLao/esim-vite/src/services/gemini.ts
  40. 13 0
      EsimLao/esim-vite/src/services/product/type.ts
  41. 78 0
      EsimLao/esim-vite/src/services/types.ts
  42. 10 0
      EsimLao/esim-vite/src/vite-env.d.ts
  43. 22 0
      EsimLao/esim-vite/tsconfig.json
  44. 23 0
      EsimLao/esim-vite/vite.config.ts

+ 24 - 0
EsimLao/esim-vite/.gitignore

@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 20 - 0
EsimLao/esim-vite/README.md

@@ -0,0 +1,20 @@
+<div align="center">
+<img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" />
+</div>
+
+# Run and deploy your AI Studio app
+
+This contains everything you need to run your app locally.
+
+View your app in AI Studio: https://ai.studio/apps/drive/1Iycz8nBgG9XSpMkhPpCmBFxU6DXVxcvT
+
+## Run Locally
+
+**Prerequisites:**  Node.js
+
+
+1. Install dependencies:
+   `npm install`
+2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
+3. Run the app:
+   `npm run dev`

+ 58 - 0
EsimLao/esim-vite/index.html

@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>InfiGate | Stay Connected Everywhere</title>
+    <script src="https://cdn.tailwindcss.com"></script>
+    <link rel="preconnect" href="https://fonts.googleapis.com" />
+    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
+    <link
+      href="https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:ital,wght@0,400;0,700;1,400;1,700&display=swap"
+      rel="stylesheet"
+    />
+    <style>
+      body {
+        font-family: "Atkinson Hyperlegible", sans-serif;
+        background-color: #ffffff;
+        color: #1e293b;
+        margin: 0;
+        padding: 0;
+      }
+      /* Custom Scrollbar - Cleaner for Light UI */
+      ::-webkit-scrollbar {
+        width: 8px;
+      }
+      ::-webkit-scrollbar-track {
+        background: #f8fafc;
+      }
+      ::-webkit-scrollbar-thumb {
+        background: #cbd5e1;
+        border-radius: 10px;
+      }
+      ::-webkit-scrollbar-thumb:hover {
+        background: #94a3b8;
+      }
+      .infigate-shadow {
+        box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05),
+          0 2px 4px -1px rgba(0, 0, 0, 0.03);
+      }
+    </style>
+    <script type="importmap">
+      {
+        "imports": {
+          "react/": "https://esm.sh/react@^19.2.3/",
+          "react": "https://esm.sh/react@^19.2.3",
+          "@google/genai": "https://esm.sh/@google/genai@^1.34.0",
+          "react-dom/": "https://esm.sh/react-dom@^19.2.3/",
+          "react-router-dom": "https://esm.sh/react-router-dom@^7.11.0"
+        }
+      }
+    </script>
+    <link rel="stylesheet" href="./src/index.css" />
+  </head>
+  <body>
+    <div id="root"></div>
+    <script type="module" src="./src/index.tsx"></script>
+  </body>
+</html>

+ 7 - 0
EsimLao/esim-vite/metadata.json

@@ -0,0 +1,7 @@
+{
+  "name": "Esim",
+  "description": "Premium travel eSIM and SIM card solutions for global connectivity. High-speed 5G/4G coverage in over 200 countries.",
+  "requestFramePermissions": [
+    "geolocation"
+  ]
+}

+ 4539 - 0
EsimLao/esim-vite/package-lock.json

@@ -0,0 +1,4539 @@
+{
+  "name": "esim-vite",
+  "version": "0.0.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "esim-vite",
+      "version": "0.0.0",
+      "dependencies": {
+        "@google/genai": "^1.34.0",
+        "@reduxjs/toolkit": "^2.11.2",
+        "@tanstack/react-query": "^5.90.16",
+        "axios": "^1.13.2",
+        "meta": "^2.2.25",
+        "react": "^19.2.3",
+        "react-dom": "^19.2.3",
+        "react-redux": "^9.2.0",
+        "react-router-dom": "^7.11.0",
+        "redux": "^5.0.1"
+      },
+      "devDependencies": {
+        "@types/node": "^22.14.0",
+        "@vitejs/plugin-react": "^5.0.0",
+        "typescript": "~5.8.2",
+        "vite": "^6.2.0"
+      }
+    },
+    "node_modules/@babel/code-frame": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+      "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-validator-identifier": "^7.27.1",
+        "js-tokens": "^4.0.0",
+        "picocolors": "^1.1.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/compat-data": {
+      "version": "7.28.5",
+      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz",
+      "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/core": {
+      "version": "7.28.5",
+      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
+      "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/code-frame": "^7.27.1",
+        "@babel/generator": "^7.28.5",
+        "@babel/helper-compilation-targets": "^7.27.2",
+        "@babel/helper-module-transforms": "^7.28.3",
+        "@babel/helpers": "^7.28.4",
+        "@babel/parser": "^7.28.5",
+        "@babel/template": "^7.27.2",
+        "@babel/traverse": "^7.28.5",
+        "@babel/types": "^7.28.5",
+        "@jridgewell/remapping": "^2.3.5",
+        "convert-source-map": "^2.0.0",
+        "debug": "^4.1.0",
+        "gensync": "^1.0.0-beta.2",
+        "json5": "^2.2.3",
+        "semver": "^6.3.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/babel"
+      }
+    },
+    "node_modules/@babel/generator": {
+      "version": "7.28.5",
+      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
+      "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/parser": "^7.28.5",
+        "@babel/types": "^7.28.5",
+        "@jridgewell/gen-mapping": "^0.3.12",
+        "@jridgewell/trace-mapping": "^0.3.28",
+        "jsesc": "^3.0.2"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-compilation-targets": {
+      "version": "7.27.2",
+      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
+      "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/compat-data": "^7.27.2",
+        "@babel/helper-validator-option": "^7.27.1",
+        "browserslist": "^4.24.0",
+        "lru-cache": "^5.1.1",
+        "semver": "^6.3.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-globals": {
+      "version": "7.28.0",
+      "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+      "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-module-imports": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
+      "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/traverse": "^7.27.1",
+        "@babel/types": "^7.27.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-module-transforms": {
+      "version": "7.28.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
+      "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-module-imports": "^7.27.1",
+        "@babel/helper-validator-identifier": "^7.27.1",
+        "@babel/traverse": "^7.28.3"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0"
+      }
+    },
+    "node_modules/@babel/helper-plugin-utils": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
+      "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-string-parser": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+      "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-validator-identifier": {
+      "version": "7.28.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+      "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-validator-option": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+      "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helpers": {
+      "version": "7.28.4",
+      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
+      "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/template": "^7.27.2",
+        "@babel/types": "^7.28.4"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/parser": {
+      "version": "7.28.5",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
+      "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/types": "^7.28.5"
+      },
+      "bin": {
+        "parser": "bin/babel-parser.js"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-react-jsx-self": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+      "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.27.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/plugin-transform-react-jsx-source": {
+      "version": "7.27.1",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+      "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-plugin-utils": "^7.27.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.0.0-0"
+      }
+    },
+    "node_modules/@babel/template": {
+      "version": "7.27.2",
+      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
+      "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/code-frame": "^7.27.1",
+        "@babel/parser": "^7.27.2",
+        "@babel/types": "^7.27.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/traverse": {
+      "version": "7.28.5",
+      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz",
+      "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/code-frame": "^7.27.1",
+        "@babel/generator": "^7.28.5",
+        "@babel/helper-globals": "^7.28.0",
+        "@babel/parser": "^7.28.5",
+        "@babel/template": "^7.27.2",
+        "@babel/types": "^7.28.5",
+        "debug": "^4.3.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/types": {
+      "version": "7.28.5",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
+      "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.27.1",
+        "@babel/helper-validator-identifier": "^7.28.5"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@esbuild/aix-ppc64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
+      "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "aix"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-arm": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
+      "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-arm64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
+      "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-x64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
+      "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/darwin-arm64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
+      "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/darwin-x64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
+      "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/freebsd-arm64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
+      "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/freebsd-x64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
+      "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-arm": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
+      "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-arm64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
+      "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-ia32": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
+      "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-loong64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
+      "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-mips64el": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
+      "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
+      "cpu": [
+        "mips64el"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-ppc64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
+      "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-riscv64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
+      "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-s390x": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
+      "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-x64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
+      "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/netbsd-arm64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
+      "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/netbsd-x64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
+      "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/openbsd-arm64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
+      "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/openbsd-x64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
+      "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/openharmony-arm64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
+      "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openharmony"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/sunos-x64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
+      "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "sunos"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-arm64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
+      "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-ia32": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
+      "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-x64": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
+      "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@google/genai": {
+      "version": "1.34.0",
+      "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.34.0.tgz",
+      "integrity": "sha512-vu53UMPvjmb7PGzlYu6Tzxso8Dfhn+a7eQFaS2uNemVtDZKwzSpJ5+ikqBbXplF7RGB1STcVDqCkPvquiwb2sw==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "google-auth-library": "^10.3.0",
+        "ws": "^8.18.0"
+      },
+      "engines": {
+        "node": ">=20.0.0"
+      },
+      "peerDependencies": {
+        "@modelcontextprotocol/sdk": "^1.24.0"
+      },
+      "peerDependenciesMeta": {
+        "@modelcontextprotocol/sdk": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@isaacs/cliui": {
+      "version": "8.0.2",
+      "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+      "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+      "license": "ISC",
+      "dependencies": {
+        "string-width": "^5.1.2",
+        "string-width-cjs": "npm:string-width@^4.2.0",
+        "strip-ansi": "^7.0.1",
+        "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+        "wrap-ansi": "^8.1.0",
+        "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@jridgewell/gen-mapping": {
+      "version": "0.3.13",
+      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+      "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/sourcemap-codec": "^1.5.0",
+        "@jridgewell/trace-mapping": "^0.3.24"
+      }
+    },
+    "node_modules/@jridgewell/remapping": {
+      "version": "2.3.5",
+      "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+      "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/gen-mapping": "^0.3.5",
+        "@jridgewell/trace-mapping": "^0.3.24"
+      }
+    },
+    "node_modules/@jridgewell/resolve-uri": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+      "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/sourcemap-codec": {
+      "version": "1.5.5",
+      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+      "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@jridgewell/trace-mapping": {
+      "version": "0.3.31",
+      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+      "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/resolve-uri": "^3.1.0",
+        "@jridgewell/sourcemap-codec": "^1.4.14"
+      }
+    },
+    "node_modules/@pkgjs/parseargs": {
+      "version": "0.11.0",
+      "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+      "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+      "license": "MIT",
+      "optional": true,
+      "engines": {
+        "node": ">=14"
+      }
+    },
+    "node_modules/@reduxjs/toolkit": {
+      "version": "2.11.2",
+      "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz",
+      "integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@standard-schema/spec": "^1.0.0",
+        "@standard-schema/utils": "^0.3.0",
+        "immer": "^11.0.0",
+        "redux": "^5.0.1",
+        "redux-thunk": "^3.1.0",
+        "reselect": "^5.1.0"
+      },
+      "peerDependencies": {
+        "react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
+        "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
+      },
+      "peerDependenciesMeta": {
+        "react": {
+          "optional": true
+        },
+        "react-redux": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@rolldown/pluginutils": {
+      "version": "1.0.0-beta.53",
+      "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz",
+      "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@rollup/rollup-android-arm-eabi": {
+      "version": "4.54.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.54.0.tgz",
+      "integrity": "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-android-arm64": {
+      "version": "4.54.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.54.0.tgz",
+      "integrity": "sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-arm64": {
+      "version": "4.54.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.54.0.tgz",
+      "integrity": "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-x64": {
+      "version": "4.54.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.54.0.tgz",
+      "integrity": "sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-freebsd-arm64": {
+      "version": "4.54.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.54.0.tgz",
+      "integrity": "sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-freebsd-x64": {
+      "version": "4.54.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.54.0.tgz",
+      "integrity": "sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+      "version": "4.54.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.54.0.tgz",
+      "integrity": "sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+      "version": "4.54.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.54.0.tgz",
+      "integrity": "sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-gnu": {
+      "version": "4.54.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.54.0.tgz",
+      "integrity": "sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-musl": {
+      "version": "4.54.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.54.0.tgz",
+      "integrity": "sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-loong64-gnu": {
+      "version": "4.54.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.54.0.tgz",
+      "integrity": "sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+      "version": "4.54.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.54.0.tgz",
+      "integrity": "sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+      "version": "4.54.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.54.0.tgz",
+      "integrity": "sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-riscv64-musl": {
+      "version": "4.54.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.54.0.tgz",
+      "integrity": "sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-s390x-gnu": {
+      "version": "4.54.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.54.0.tgz",
+      "integrity": "sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-gnu": {
+      "version": "4.54.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.54.0.tgz",
+      "integrity": "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-musl": {
+      "version": "4.54.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.54.0.tgz",
+      "integrity": "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-openharmony-arm64": {
+      "version": "4.54.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.54.0.tgz",
+      "integrity": "sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openharmony"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-arm64-msvc": {
+      "version": "4.54.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.54.0.tgz",
+      "integrity": "sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-ia32-msvc": {
+      "version": "4.54.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.54.0.tgz",
+      "integrity": "sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-x64-gnu": {
+      "version": "4.54.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.54.0.tgz",
+      "integrity": "sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-x64-msvc": {
+      "version": "4.54.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.54.0.tgz",
+      "integrity": "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@standard-schema/spec": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+      "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
+      "license": "MIT"
+    },
+    "node_modules/@standard-schema/utils": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
+      "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
+      "license": "MIT"
+    },
+    "node_modules/@tanstack/query-core": {
+      "version": "5.90.16",
+      "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.16.tgz",
+      "integrity": "sha512-MvtWckSVufs/ja463/K4PyJeqT+HMlJWtw6PrCpywznd2NSgO3m4KwO9RqbFqGg6iDE8vVMFWMeQI4Io3eEYww==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/tannerlinsley"
+      }
+    },
+    "node_modules/@tanstack/react-query": {
+      "version": "5.90.16",
+      "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.16.tgz",
+      "integrity": "sha512-bpMGOmV4OPmif7TNMteU/Ehf/hoC0Kf98PDc0F4BZkFrEapRMEqI/V6YS0lyzwSV6PQpY1y4xxArUIfBW5LVxQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@tanstack/query-core": "5.90.16"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/tannerlinsley"
+      },
+      "peerDependencies": {
+        "react": "^18 || ^19"
+      }
+    },
+    "node_modules/@types/babel__core": {
+      "version": "7.20.5",
+      "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+      "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/parser": "^7.20.7",
+        "@babel/types": "^7.20.7",
+        "@types/babel__generator": "*",
+        "@types/babel__template": "*",
+        "@types/babel__traverse": "*"
+      }
+    },
+    "node_modules/@types/babel__generator": {
+      "version": "7.27.0",
+      "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+      "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/types": "^7.0.0"
+      }
+    },
+    "node_modules/@types/babel__template": {
+      "version": "7.4.4",
+      "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+      "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/parser": "^7.1.0",
+        "@babel/types": "^7.0.0"
+      }
+    },
+    "node_modules/@types/babel__traverse": {
+      "version": "7.28.0",
+      "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+      "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/types": "^7.28.2"
+      }
+    },
+    "node_modules/@types/estree": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+      "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/node": {
+      "version": "22.19.3",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz",
+      "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "undici-types": "~6.21.0"
+      }
+    },
+    "node_modules/@types/use-sync-external-store": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
+      "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
+      "license": "MIT"
+    },
+    "node_modules/@vitejs/plugin-react": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz",
+      "integrity": "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/core": "^7.28.5",
+        "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+        "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+        "@rolldown/pluginutils": "1.0.0-beta.53",
+        "@types/babel__core": "^7.20.5",
+        "react-refresh": "^0.18.0"
+      },
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      },
+      "peerDependencies": {
+        "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+      }
+    },
+    "node_modules/agent-base": {
+      "version": "7.1.4",
+      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+      "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 14"
+      }
+    },
+    "node_modules/ansi-escapes": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
+      "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/ansi-regex": {
+      "version": "6.2.2",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+      "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+      }
+    },
+    "node_modules/ansi-styles": {
+      "version": "6.2.3",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+      "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
+    "node_modules/array-unique": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
+      "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/async": {
+      "version": "3.2.6",
+      "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
+      "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
+      "license": "MIT"
+    },
+    "node_modules/asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+      "license": "MIT"
+    },
+    "node_modules/axios": {
+      "version": "1.13.2",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
+      "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
+      "license": "MIT",
+      "dependencies": {
+        "follow-redirects": "^1.15.6",
+        "form-data": "^4.0.4",
+        "proxy-from-env": "^1.1.0"
+      }
+    },
+    "node_modules/balanced-match": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+      "license": "MIT"
+    },
+    "node_modules/base64-js": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+      "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/baseline-browser-mapping": {
+      "version": "2.9.11",
+      "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz",
+      "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "bin": {
+        "baseline-browser-mapping": "dist/cli.js"
+      }
+    },
+    "node_modules/bignumber.js": {
+      "version": "9.3.1",
+      "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
+      "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==",
+      "license": "MIT",
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/brace-expansion": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+      "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+      "license": "MIT",
+      "dependencies": {
+        "balanced-match": "^1.0.0"
+      }
+    },
+    "node_modules/braces": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+      "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+      "license": "MIT",
+      "dependencies": {
+        "fill-range": "^7.1.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/browserslist": {
+      "version": "4.28.1",
+      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
+      "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/browserslist"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "baseline-browser-mapping": "^2.9.0",
+        "caniuse-lite": "^1.0.30001759",
+        "electron-to-chromium": "^1.5.263",
+        "node-releases": "^2.0.27",
+        "update-browserslist-db": "^1.2.0"
+      },
+      "bin": {
+        "browserslist": "cli.js"
+      },
+      "engines": {
+        "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+      }
+    },
+    "node_modules/buffer-equal-constant-time": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+      "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/call-bind-apply-helpers": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+      "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/camelcase": {
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+      "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/caniuse-lite": {
+      "version": "1.0.30001762",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001762.tgz",
+      "integrity": "sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "CC-BY-4.0"
+    },
+    "node_modules/chalk": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+      "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-styles": "^4.1.0",
+        "supports-color": "^7.1.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/chalk/node_modules/ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "license": "MIT",
+      "dependencies": {
+        "color-convert": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
+    "node_modules/chardet": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
+      "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
+      "license": "MIT"
+    },
+    "node_modules/cli-cursor": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
+      "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==",
+      "license": "MIT",
+      "dependencies": {
+        "restore-cursor": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/cli-width": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz",
+      "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==",
+      "license": "ISC"
+    },
+    "node_modules/cliui": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
+      "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
+      "license": "ISC",
+      "dependencies": {
+        "string-width": "^3.1.0",
+        "strip-ansi": "^5.2.0",
+        "wrap-ansi": "^5.1.0"
+      }
+    },
+    "node_modules/cliui/node_modules/ansi-regex": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
+      "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/cliui/node_modules/ansi-styles": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+      "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+      "license": "MIT",
+      "dependencies": {
+        "color-convert": "^1.9.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/cliui/node_modules/color-convert": {
+      "version": "1.9.3",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+      "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+      "license": "MIT",
+      "dependencies": {
+        "color-name": "1.1.3"
+      }
+    },
+    "node_modules/cliui/node_modules/color-name": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+      "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+      "license": "MIT"
+    },
+    "node_modules/cliui/node_modules/emoji-regex": {
+      "version": "7.0.3",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+      "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+      "license": "MIT"
+    },
+    "node_modules/cliui/node_modules/is-fullwidth-code-point": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+      "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/cliui/node_modules/string-width": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+      "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+      "license": "MIT",
+      "dependencies": {
+        "emoji-regex": "^7.0.1",
+        "is-fullwidth-code-point": "^2.0.0",
+        "strip-ansi": "^5.1.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/cliui/node_modules/strip-ansi": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+      "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-regex": "^4.1.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/cliui/node_modules/wrap-ansi": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
+      "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-styles": "^3.2.0",
+        "string-width": "^3.0.0",
+        "strip-ansi": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/color-convert": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "license": "MIT",
+      "dependencies": {
+        "color-name": "~1.1.4"
+      },
+      "engines": {
+        "node": ">=7.0.0"
+      }
+    },
+    "node_modules/color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+      "license": "MIT"
+    },
+    "node_modules/combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "license": "MIT",
+      "dependencies": {
+        "delayed-stream": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/commander": {
+      "version": "2.15.1",
+      "resolved": "git+ssh://git@github.com/mateodelnorte/commander.js.git#9060bf880b791cf39245d425f1e8a41a55616781",
+      "license": "MIT",
+      "dependencies": {
+        "find-module-bin": "^1.0.0"
+      }
+    },
+    "node_modules/concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+      "license": "MIT"
+    },
+    "node_modules/convert-source-map": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+      "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/cookie": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
+      "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
+      }
+    },
+    "node_modules/cross-spawn": {
+      "version": "7.0.6",
+      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+      "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+      "license": "MIT",
+      "dependencies": {
+        "path-key": "^3.1.0",
+        "shebang-command": "^2.0.0",
+        "which": "^2.0.1"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/data-uri-to-buffer": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
+      "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 12"
+      }
+    },
+    "node_modules/debug": {
+      "version": "4.4.3",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+      "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/decamelize": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+      "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/dedent": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
+      "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==",
+      "license": "MIT"
+    },
+    "node_modules/delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/detect-file": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz",
+      "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/dunder-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+      "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "gopd": "^1.2.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/eastasianwidth": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+      "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+      "license": "MIT"
+    },
+    "node_modules/ecdsa-sig-formatter": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+      "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "node_modules/electron-to-chromium": {
+      "version": "1.5.267",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz",
+      "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==",
+      "dev": true,
+      "license": "ISC"
+    },
+    "node_modules/emoji-regex": {
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+      "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+      "license": "MIT"
+    },
+    "node_modules/es-define-property": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+      "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-errors": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-object-atoms": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+      "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-set-tostringtag": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+      "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.6",
+        "has-tostringtag": "^1.0.2",
+        "hasown": "^2.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es6-promisify": {
+      "version": "6.1.1",
+      "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-6.1.1.tgz",
+      "integrity": "sha512-HBL8I3mIki5C1Cc9QjKUenHtnG0A5/xA8Q/AllRcfiwl2CZFXGK7ddBiCoRwAix4i2KxcQfjtIVcrVbB3vbmwg==",
+      "license": "MIT"
+    },
+    "node_modules/esbuild": {
+      "version": "0.25.12",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
+      "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "bin": {
+        "esbuild": "bin/esbuild"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "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"
+      }
+    },
+    "node_modules/escalade": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+      "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/escape-string-regexp": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+      "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.8.0"
+      }
+    },
+    "node_modules/expand-tilde": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz",
+      "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==",
+      "license": "MIT",
+      "dependencies": {
+        "homedir-polyfill": "^1.0.1"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/extend": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+      "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
+      "license": "MIT"
+    },
+    "node_modules/external-editor": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
+      "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
+      "license": "MIT",
+      "dependencies": {
+        "chardet": "^0.7.0",
+        "iconv-lite": "^0.4.24",
+        "tmp": "^0.0.33"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/fdir": {
+      "version": "6.5.0",
+      "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+      "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "peerDependencies": {
+        "picomatch": "^3 || ^4"
+      },
+      "peerDependenciesMeta": {
+        "picomatch": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/fetch-blob": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
+      "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/jimmywarting"
+        },
+        {
+          "type": "paypal",
+          "url": "https://paypal.me/jimmywarting"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "node-domexception": "^1.0.0",
+        "web-streams-polyfill": "^3.0.3"
+      },
+      "engines": {
+        "node": "^12.20 || >= 14.13"
+      }
+    },
+    "node_modules/figures": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
+      "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==",
+      "license": "MIT",
+      "dependencies": {
+        "escape-string-regexp": "^1.0.5"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/fill-range": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+      "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+      "license": "MIT",
+      "dependencies": {
+        "to-regex-range": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/find-module-bin": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/find-module-bin/-/find-module-bin-1.1.0.tgz",
+      "integrity": "sha512-v53mIFOiuump1BSkwURrLblQm+LpWDVQOkQdph/xLPRrbyCU48RryPUSYxih6+gkAdNw9AkqWL7OXUhmE1ddmg==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "^4.1.1",
+        "global-paths": "^1.0.0"
+      }
+    },
+    "node_modules/find-up": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+      "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+      "license": "MIT",
+      "dependencies": {
+        "locate-path": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/findup-sync": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-4.0.0.tgz",
+      "integrity": "sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==",
+      "license": "MIT",
+      "dependencies": {
+        "detect-file": "^1.0.0",
+        "is-glob": "^4.0.0",
+        "micromatch": "^4.0.2",
+        "resolve-dir": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/follow-redirects": {
+      "version": "1.15.11",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
+      "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://github.com/sponsors/RubenVerborgh"
+        }
+      ],
+      "license": "MIT",
+      "engines": {
+        "node": ">=4.0"
+      },
+      "peerDependenciesMeta": {
+        "debug": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/foreground-child": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
+      "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
+      "license": "ISC",
+      "dependencies": {
+        "cross-spawn": "^7.0.6",
+        "signal-exit": "^4.0.1"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/form-data": {
+      "version": "4.0.5",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+      "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+      "license": "MIT",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "es-set-tostringtag": "^2.1.0",
+        "hasown": "^2.0.2",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/formdata-polyfill": {
+      "version": "4.0.10",
+      "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
+      "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
+      "license": "MIT",
+      "dependencies": {
+        "fetch-blob": "^3.1.2"
+      },
+      "engines": {
+        "node": ">=12.20.0"
+      }
+    },
+    "node_modules/fs.realpath": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+      "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+      "license": "ISC"
+    },
+    "node_modules/fsevents": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "node_modules/function-bind": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/gaxios": {
+      "version": "7.1.3",
+      "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz",
+      "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "extend": "^3.0.2",
+        "https-proxy-agent": "^7.0.1",
+        "node-fetch": "^3.3.2",
+        "rimraf": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/gcp-metadata": {
+      "version": "8.1.2",
+      "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz",
+      "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "gaxios": "^7.0.0",
+        "google-logging-utils": "^1.0.0",
+        "json-bigint": "^1.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/gensync": {
+      "version": "1.0.0-beta.2",
+      "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+      "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/get-caller-file": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+      "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+      "license": "ISC",
+      "engines": {
+        "node": "6.* || 8.* || >= 10.*"
+      }
+    },
+    "node_modules/get-intrinsic": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+      "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "es-define-property": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "es-object-atoms": "^1.1.1",
+        "function-bind": "^1.1.2",
+        "get-proto": "^1.0.1",
+        "gopd": "^1.2.0",
+        "has-symbols": "^1.1.0",
+        "hasown": "^2.0.2",
+        "math-intrinsics": "^1.1.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-meta-file": {
+      "version": "1.3.6",
+      "resolved": "https://registry.npmjs.org/get-meta-file/-/get-meta-file-1.3.6.tgz",
+      "integrity": "sha512-JExB2n7WZMj+tKaxgjAP7VkSN2Xbooa0ToyNa9/H7QFVa0vLbzHr0e1m5NX1IT/c01sDHO6JCGBsrON6stOozg==",
+      "license": "ISC",
+      "dependencies": {
+        "chalk": "^3.0.0",
+        "debug": "^4.3.2",
+        "dedent": "^0.7.0",
+        "findup-sync": "^4.0.0",
+        "prompt-sync": "^4.2.0",
+        "tildify": "^2.0.0"
+      }
+    },
+    "node_modules/get-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+      "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+      "license": "MIT",
+      "dependencies": {
+        "dunder-proto": "^1.0.1",
+        "es-object-atoms": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/git-remote-origin-url": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-3.1.0.tgz",
+      "integrity": "sha512-yVSfaTMO7Bqk6Xx3696ufNfjdrajX7Ig9GuAeO2V3Ji7stkDoBNFldnWIAsy0qviUd0Z+X2P6ziJENKztW7cBQ==",
+      "license": "MIT",
+      "dependencies": {
+        "gitconfiglocal": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/gitconfiglocal": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-2.1.0.tgz",
+      "integrity": "sha512-qoerOEliJn3z+Zyn1HW2F6eoYJqKwS6MgC9cztTLUB/xLWX8gD/6T60pKn4+t/d6tP7JlybI7Z3z+I572CR/Vg==",
+      "license": "BSD",
+      "dependencies": {
+        "ini": "^1.3.2"
+      }
+    },
+    "node_modules/glob": {
+      "version": "10.5.0",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+      "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
+      "license": "ISC",
+      "dependencies": {
+        "foreground-child": "^3.1.0",
+        "jackspeak": "^3.1.2",
+        "minimatch": "^9.0.4",
+        "minipass": "^7.1.2",
+        "package-json-from-dist": "^1.0.0",
+        "path-scurry": "^1.11.1"
+      },
+      "bin": {
+        "glob": "dist/esm/bin.mjs"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/global-modules": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-0.2.3.tgz",
+      "integrity": "sha512-JeXuCbvYzYXcwE6acL9V2bAOeSIGl4dD+iwLY9iUx2VBJJ80R18HCn+JCwHM9Oegdfya3lEkGCdaRkSyc10hDA==",
+      "license": "MIT",
+      "dependencies": {
+        "global-prefix": "^0.1.4",
+        "is-windows": "^0.2.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/global-modules/node_modules/is-windows": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz",
+      "integrity": "sha512-n67eJYmXbniZB7RF4I/FTjK1s6RPOCTxhYrVYLRaCt3lF0mpWZPKr3T2LSZAqyjQsxR2qMmGYXXzK0YWwcPM1Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/global-paths": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/global-paths/-/global-paths-1.0.0.tgz",
+      "integrity": "sha512-L3Ctm3aer+82rIdINZrFkyF0+YJQeK+kHR5ilnOpnEzj6N/lnqxuYpl8GSP+oepPgOmHbOtEPy9w0ghRm6YSuQ==",
+      "license": "MIT",
+      "dependencies": {
+        "array-unique": "^0.3.2",
+        "global-modules": "^0.2.3",
+        "is-windows": "^1.0.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/global-prefix": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-0.1.5.tgz",
+      "integrity": "sha512-gOPiyxcD9dJGCEArAhF4Hd0BAqvAe/JzERP7tYumE4yIkmIedPUVXcJFWbV3/p/ovIIvKjkrTk+f1UVkq7vvbw==",
+      "license": "MIT",
+      "dependencies": {
+        "homedir-polyfill": "^1.0.0",
+        "ini": "^1.3.4",
+        "is-windows": "^0.2.0",
+        "which": "^1.2.12"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/global-prefix/node_modules/is-windows": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz",
+      "integrity": "sha512-n67eJYmXbniZB7RF4I/FTjK1s6RPOCTxhYrVYLRaCt3lF0mpWZPKr3T2LSZAqyjQsxR2qMmGYXXzK0YWwcPM1Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/global-prefix/node_modules/which": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+      "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+      "license": "ISC",
+      "dependencies": {
+        "isexe": "^2.0.0"
+      },
+      "bin": {
+        "which": "bin/which"
+      }
+    },
+    "node_modules/google-auth-library": {
+      "version": "10.5.0",
+      "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.5.0.tgz",
+      "integrity": "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "base64-js": "^1.3.0",
+        "ecdsa-sig-formatter": "^1.0.11",
+        "gaxios": "^7.0.0",
+        "gcp-metadata": "^8.0.0",
+        "google-logging-utils": "^1.0.0",
+        "gtoken": "^8.0.0",
+        "jws": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/google-logging-utils": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz",
+      "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=14"
+      }
+    },
+    "node_modules/gopd": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+      "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/gtoken": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz",
+      "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==",
+      "license": "MIT",
+      "dependencies": {
+        "gaxios": "^7.0.0",
+        "jws": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/has-flag": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/has-symbols": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+      "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-tostringtag": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+      "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+      "license": "MIT",
+      "dependencies": {
+        "has-symbols": "^1.0.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/hasown": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+      "license": "MIT",
+      "dependencies": {
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/homedir-polyfill": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz",
+      "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==",
+      "license": "MIT",
+      "dependencies": {
+        "parse-passwd": "^1.0.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/https-proxy-agent": {
+      "version": "7.0.6",
+      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+      "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+      "license": "MIT",
+      "dependencies": {
+        "agent-base": "^7.1.2",
+        "debug": "4"
+      },
+      "engines": {
+        "node": ">= 14"
+      }
+    },
+    "node_modules/iconv-lite": {
+      "version": "0.4.24",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "license": "MIT",
+      "dependencies": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/immer": {
+      "version": "11.1.3",
+      "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.3.tgz",
+      "integrity": "sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q==",
+      "license": "MIT",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/immer"
+      }
+    },
+    "node_modules/inflight": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+      "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+      "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+      "license": "ISC",
+      "dependencies": {
+        "once": "^1.3.0",
+        "wrappy": "1"
+      }
+    },
+    "node_modules/inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "license": "ISC"
+    },
+    "node_modules/ini": {
+      "version": "1.3.8",
+      "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+      "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+      "license": "ISC"
+    },
+    "node_modules/inquirer": {
+      "version": "6.5.2",
+      "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz",
+      "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-escapes": "^3.2.0",
+        "chalk": "^2.4.2",
+        "cli-cursor": "^2.1.0",
+        "cli-width": "^2.0.0",
+        "external-editor": "^3.0.3",
+        "figures": "^2.0.0",
+        "lodash": "^4.17.12",
+        "mute-stream": "0.0.7",
+        "run-async": "^2.2.0",
+        "rxjs": "^6.4.0",
+        "string-width": "^2.1.0",
+        "strip-ansi": "^5.1.0",
+        "through": "^2.3.6"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/inquirer/node_modules/ansi-regex": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
+      "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/inquirer/node_modules/ansi-styles": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+      "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+      "license": "MIT",
+      "dependencies": {
+        "color-convert": "^1.9.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/inquirer/node_modules/chalk": {
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+      "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-styles": "^3.2.1",
+        "escape-string-regexp": "^1.0.5",
+        "supports-color": "^5.3.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/inquirer/node_modules/color-convert": {
+      "version": "1.9.3",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+      "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+      "license": "MIT",
+      "dependencies": {
+        "color-name": "1.1.3"
+      }
+    },
+    "node_modules/inquirer/node_modules/color-name": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+      "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+      "license": "MIT"
+    },
+    "node_modules/inquirer/node_modules/has-flag": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+      "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/inquirer/node_modules/is-fullwidth-code-point": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+      "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/inquirer/node_modules/string-width": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+      "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+      "license": "MIT",
+      "dependencies": {
+        "is-fullwidth-code-point": "^2.0.0",
+        "strip-ansi": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/inquirer/node_modules/string-width/node_modules/ansi-regex": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz",
+      "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/inquirer/node_modules/string-width/node_modules/strip-ansi": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+      "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-regex": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/inquirer/node_modules/strip-ansi": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+      "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-regex": "^4.1.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/inquirer/node_modules/supports-color": {
+      "version": "5.5.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+      "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+      "license": "MIT",
+      "dependencies": {
+        "has-flag": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-fullwidth-code-point": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/is-glob": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+      "license": "MIT",
+      "dependencies": {
+        "is-extglob": "^2.1.1"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-number": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.12.0"
+      }
+    },
+    "node_modules/is-windows": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
+      "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+      "license": "ISC"
+    },
+    "node_modules/jackspeak": {
+      "version": "3.4.3",
+      "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+      "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+      "license": "BlueOak-1.0.0",
+      "dependencies": {
+        "@isaacs/cliui": "^8.0.2"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      },
+      "optionalDependencies": {
+        "@pkgjs/parseargs": "^0.11.0"
+      }
+    },
+    "node_modules/js-tokens": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/jsesc": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+      "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+      "dev": true,
+      "license": "MIT",
+      "bin": {
+        "jsesc": "bin/jsesc"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/json-bigint": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
+      "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
+      "license": "MIT",
+      "dependencies": {
+        "bignumber.js": "^9.0.0"
+      }
+    },
+    "node_modules/json5": {
+      "version": "2.2.3",
+      "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+      "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+      "dev": true,
+      "license": "MIT",
+      "bin": {
+        "json5": "lib/cli.js"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/jwa": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
+      "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
+      "license": "MIT",
+      "dependencies": {
+        "buffer-equal-constant-time": "^1.0.1",
+        "ecdsa-sig-formatter": "1.0.11",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "node_modules/jws": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
+      "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
+      "license": "MIT",
+      "dependencies": {
+        "jwa": "^2.0.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "node_modules/locate-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+      "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+      "license": "MIT",
+      "dependencies": {
+        "p-locate": "^3.0.0",
+        "path-exists": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/lodash": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+      "license": "MIT"
+    },
+    "node_modules/loop": {
+      "version": "3.3.6",
+      "resolved": "https://registry.npmjs.org/loop/-/loop-3.3.6.tgz",
+      "integrity": "sha512-loP1rvosCzG3qHznhwH73LOBPcSlSx6bzRnu8ODeTyNIqWOcUUdtzDcM0LraCyT6WnZY/+Komf8NFBRKO6izsA==",
+      "license": "ISC",
+      "dependencies": {
+        "async": "^3.2.0",
+        "chalk": "^3.0.0",
+        "debug": "^4.3.2",
+        "lodash": "^4.17.21",
+        "meta-exec": "^1.4.2",
+        "should": "^13.2.3",
+        "yargs": "^14.2.3"
+      },
+      "bin": {
+        "loop": "bin/loop"
+      }
+    },
+    "node_modules/lru-cache": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+      "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "yallist": "^3.0.2"
+      }
+    },
+    "node_modules/math-intrinsics": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+      "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/meta": {
+      "version": "2.2.25",
+      "resolved": "https://registry.npmjs.org/meta/-/meta-2.2.25.tgz",
+      "integrity": "sha512-iKO5fCXYVzbMBAispFycgDDn/6/0F7ER2L8iPFIHbhgN/WtnM2zpdDHBUH0vnsPh0HO+GxmdZ75thIhIPSjHqg==",
+      "license": "MIT",
+      "dependencies": {
+        "chalk": "3.0.0",
+        "commander": "github:mateodelnorte/commander.js",
+        "debug": "4.3.2",
+        "meta-git": "1.1.7",
+        "meta-init": "1.2.5",
+        "meta-loop": "1.2.5",
+        "meta-project": "2.5.0",
+        "tabtab": "3.0.2",
+        "tildify": "2.0.0"
+      },
+      "bin": {
+        "meta": "bin/meta"
+      }
+    },
+    "node_modules/meta-exec": {
+      "version": "1.4.4",
+      "resolved": "https://registry.npmjs.org/meta-exec/-/meta-exec-1.4.4.tgz",
+      "integrity": "sha512-rWGvtS4E22eCPlYNqEmW9buaTlxoOvbqiVbqLLxGvaamKRBRkgOalV1qmV+2w9O/giFPLqiEdffGmaGRqQNuFQ==",
+      "license": "ISC",
+      "dependencies": {
+        "chalk": "^3.0.0",
+        "debug": "^4.3.2",
+        "get-meta-file": "^1.3.4"
+      }
+    },
+    "node_modules/meta-git": {
+      "version": "1.1.7",
+      "resolved": "https://registry.npmjs.org/meta-git/-/meta-git-1.1.7.tgz",
+      "integrity": "sha512-4v7v1MwfmjJOR6Ev9XzOGJEnV7wmltyQ+FjukE5bCDDBD7JQgNxtRmOJ+B7VhE55nMA1cGhu6J5l3CM0cEVuLw==",
+      "license": "ISC",
+      "dependencies": {
+        "chalk": "^3.0.0",
+        "commander": "^2.20.3",
+        "debug": "^4.3.2",
+        "get-meta-file": "^1.3.5",
+        "loop": "^3.3.4",
+        "meta-exec": "^1.4.2",
+        "meta-loop": "^1.2.2"
+      },
+      "bin": {
+        "meta-git": "bin/meta-git",
+        "meta-git-add": "bin/meta-git-add",
+        "meta-git-branch": "bin/meta-git-branch",
+        "meta-git-checkout": "bin/meta-git-checkout",
+        "meta-git-clean": "bin/meta-git-clean",
+        "meta-git-clone": "bin/meta-git-clone",
+        "meta-git-commit": "bin/meta-git-commit",
+        "meta-git-diff": "bin/meta-git-diff",
+        "meta-git-fetch": "bin/meta-git-fetch",
+        "meta-git-merge": "bin/meta-git-merge",
+        "meta-git-pull": "bin/meta-git-pull",
+        "meta-git-push": "bin/meta-git-push",
+        "meta-git-remote": "bin/meta-git-remote",
+        "meta-git-status": "bin/meta-git-status",
+        "meta-git-tag": "bin/meta-git-tag",
+        "meta-git-update": "bin/meta-git-update"
+      }
+    },
+    "node_modules/meta-git/node_modules/commander": {
+      "version": "2.20.3",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+      "license": "MIT"
+    },
+    "node_modules/meta-init": {
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/meta-init/-/meta-init-1.2.5.tgz",
+      "integrity": "sha512-0sD3GBldanS9s6EzGdKnDGP1y9eumDNQwEtY4ZZ09C63Y+g/fONCzAvuBHssvDzOpyHFnJWPhWK8VXlAQt+52w==",
+      "license": "ISC",
+      "dependencies": {
+        "async": "^3.2.0",
+        "debug": "^4.3.2",
+        "get-meta-file": "^1.3.4",
+        "git-remote-origin-url": "^3.1.0",
+        "inquirer": "^6.5.2"
+      },
+      "bin": {
+        "meta-init": "bin/meta-init"
+      }
+    },
+    "node_modules/meta-loop": {
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/meta-loop/-/meta-loop-1.2.5.tgz",
+      "integrity": "sha512-3GjhCmyLSOhyPuyAR3cEqFjcfDBRiCP2A4tstM7azC8pizaN0qb5QWQNCO0xtTguBqRCxXDkmUEfjRNc2WF93g==",
+      "license": "ISC",
+      "dependencies": {
+        "debug": "^4.3.2",
+        "get-meta-file": "^1.3.5",
+        "loop": "^3.3.5"
+      },
+      "bin": {
+        "meta-exec": "bin/meta-exec",
+        "meta-loop": "bin/meta-loop"
+      }
+    },
+    "node_modules/meta-project": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/meta-project/-/meta-project-2.5.0.tgz",
+      "integrity": "sha512-Xjzhv57qcVafzmRC/xAHp1AUbREgO4trmmajiHg8ZMOg1WJfKDy56qCcOXgE7CTQOYwRL/uzyzAWwWvhnmGcuw==",
+      "license": "ISC",
+      "dependencies": {
+        "chalk": "3.0.0",
+        "commander": "2.20.3",
+        "get-meta-file": "1.3.6",
+        "loop": "3.3.6",
+        "meta-exec": "1.4.4",
+        "mkdirp": "0.5.5",
+        "rimraf": "2.7.1",
+        "tildify": "^2.0.0"
+      },
+      "bin": {
+        "meta-project": "bin/meta-project",
+        "meta-project-add": "bin/meta-project-add",
+        "meta-project-create": "bin/meta-project-create",
+        "meta-project-import": "bin/meta-project-import",
+        "meta-project-migrate": "bin/meta-project-migrate"
+      }
+    },
+    "node_modules/meta-project/node_modules/brace-expansion": {
+      "version": "1.1.12",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+      "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+      "license": "MIT",
+      "dependencies": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "node_modules/meta-project/node_modules/commander": {
+      "version": "2.20.3",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+      "license": "MIT"
+    },
+    "node_modules/meta-project/node_modules/glob": {
+      "version": "7.2.3",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+      "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+      "deprecated": "Glob versions prior to v9 are no longer supported",
+      "license": "ISC",
+      "dependencies": {
+        "fs.realpath": "^1.0.0",
+        "inflight": "^1.0.4",
+        "inherits": "2",
+        "minimatch": "^3.1.1",
+        "once": "^1.3.0",
+        "path-is-absolute": "^1.0.0"
+      },
+      "engines": {
+        "node": "*"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/meta-project/node_modules/minimatch": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+      "license": "ISC",
+      "dependencies": {
+        "brace-expansion": "^1.1.7"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/meta-project/node_modules/rimraf": {
+      "version": "2.7.1",
+      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
+      "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
+      "deprecated": "Rimraf versions prior to v4 are no longer supported",
+      "license": "ISC",
+      "dependencies": {
+        "glob": "^7.1.3"
+      },
+      "bin": {
+        "rimraf": "bin.js"
+      }
+    },
+    "node_modules/meta/node_modules/debug": {
+      "version": "4.3.2",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
+      "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "2.1.2"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/meta/node_modules/ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+      "license": "MIT"
+    },
+    "node_modules/micromatch": {
+      "version": "4.0.8",
+      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+      "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+      "license": "MIT",
+      "dependencies": {
+        "braces": "^3.0.3",
+        "picomatch": "^2.3.1"
+      },
+      "engines": {
+        "node": ">=8.6"
+      }
+    },
+    "node_modules/micromatch/node_modules/picomatch": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mimic-fn": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
+      "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/minimatch": {
+      "version": "9.0.5",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+      "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+      "license": "ISC",
+      "dependencies": {
+        "brace-expansion": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/minimist": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+      "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/minipass": {
+      "version": "7.1.2",
+      "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+      "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      }
+    },
+    "node_modules/mkdirp": {
+      "version": "0.5.5",
+      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
+      "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+      "license": "MIT",
+      "dependencies": {
+        "minimist": "^1.2.5"
+      },
+      "bin": {
+        "mkdirp": "bin/cmd.js"
+      }
+    },
+    "node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "license": "MIT"
+    },
+    "node_modules/mute-stream": {
+      "version": "0.0.7",
+      "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
+      "integrity": "sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ==",
+      "license": "ISC"
+    },
+    "node_modules/nanoid": {
+      "version": "3.3.11",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+      "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "bin": {
+        "nanoid": "bin/nanoid.cjs"
+      },
+      "engines": {
+        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+      }
+    },
+    "node_modules/node-domexception": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
+      "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
+      "deprecated": "Use your platform's native DOMException instead",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/jimmywarting"
+        },
+        {
+          "type": "github",
+          "url": "https://paypal.me/jimmywarting"
+        }
+      ],
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.5.0"
+      }
+    },
+    "node_modules/node-fetch": {
+      "version": "3.3.2",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
+      "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
+      "license": "MIT",
+      "dependencies": {
+        "data-uri-to-buffer": "^4.0.0",
+        "fetch-blob": "^3.1.4",
+        "formdata-polyfill": "^4.0.10"
+      },
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/node-fetch"
+      }
+    },
+    "node_modules/node-releases": {
+      "version": "2.0.27",
+      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
+      "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+      "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+      "license": "ISC",
+      "dependencies": {
+        "wrappy": "1"
+      }
+    },
+    "node_modules/onetime": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
+      "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==",
+      "license": "MIT",
+      "dependencies": {
+        "mimic-fn": "^1.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/os-tmpdir": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+      "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/p-limit": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+      "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+      "license": "MIT",
+      "dependencies": {
+        "p-try": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/p-locate": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+      "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+      "license": "MIT",
+      "dependencies": {
+        "p-limit": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/p-try": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+      "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/package-json-from-dist": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
+      "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
+      "license": "BlueOak-1.0.0"
+    },
+    "node_modules/parse-passwd": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz",
+      "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/path-exists": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+      "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/path-is-absolute": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+      "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/path-key": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+      "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/path-scurry": {
+      "version": "1.11.1",
+      "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+      "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+      "license": "BlueOak-1.0.0",
+      "dependencies": {
+        "lru-cache": "^10.2.0",
+        "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+      },
+      "engines": {
+        "node": ">=16 || 14 >=14.18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/path-scurry/node_modules/lru-cache": {
+      "version": "10.4.3",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+      "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+      "license": "ISC"
+    },
+    "node_modules/picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+      "dev": true,
+      "license": "ISC"
+    },
+    "node_modules/picomatch": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+      "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/postcss": {
+      "version": "8.5.6",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+      "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/postcss"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "nanoid": "^3.3.11",
+        "picocolors": "^1.1.1",
+        "source-map-js": "^1.2.1"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      }
+    },
+    "node_modules/prompt-sync": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/prompt-sync/-/prompt-sync-4.2.0.tgz",
+      "integrity": "sha512-BuEzzc5zptP5LsgV5MZETjDaKSWfchl5U9Luiu8SKp7iZWD5tZalOxvNcZRwv+d2phNFr8xlbxmFNcRKfJOzJw==",
+      "license": "MIT",
+      "dependencies": {
+        "strip-ansi": "^5.0.0"
+      }
+    },
+    "node_modules/prompt-sync/node_modules/ansi-regex": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
+      "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/prompt-sync/node_modules/strip-ansi": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+      "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-regex": "^4.1.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/proxy-from-env": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+      "license": "MIT"
+    },
+    "node_modules/react": {
+      "version": "19.2.3",
+      "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
+      "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/react-dom": {
+      "version": "19.2.3",
+      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
+      "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
+      "license": "MIT",
+      "dependencies": {
+        "scheduler": "^0.27.0"
+      },
+      "peerDependencies": {
+        "react": "^19.2.3"
+      }
+    },
+    "node_modules/react-redux": {
+      "version": "9.2.0",
+      "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
+      "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/use-sync-external-store": "^0.0.6",
+        "use-sync-external-store": "^1.4.0"
+      },
+      "peerDependencies": {
+        "@types/react": "^18.2.25 || ^19",
+        "react": "^18.0 || ^19",
+        "redux": "^5.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        },
+        "redux": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/react-refresh": {
+      "version": "0.18.0",
+      "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
+      "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/react-router": {
+      "version": "7.11.0",
+      "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.11.0.tgz",
+      "integrity": "sha512-uI4JkMmjbWCZc01WVP2cH7ZfSzH91JAZUDd7/nIprDgWxBV1TkkmLToFh7EbMTcMak8URFRa2YoBL/W8GWnCTQ==",
+      "license": "MIT",
+      "dependencies": {
+        "cookie": "^1.0.1",
+        "set-cookie-parser": "^2.6.0"
+      },
+      "engines": {
+        "node": ">=20.0.0"
+      },
+      "peerDependencies": {
+        "react": ">=18",
+        "react-dom": ">=18"
+      },
+      "peerDependenciesMeta": {
+        "react-dom": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/react-router-dom": {
+      "version": "7.11.0",
+      "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.11.0.tgz",
+      "integrity": "sha512-e49Ir/kMGRzFOOrYQBdoitq3ULigw4lKbAyKusnvtDu2t4dBX4AGYPrzNvorXmVuOyeakai6FUPW5MmibvVG8g==",
+      "license": "MIT",
+      "dependencies": {
+        "react-router": "7.11.0"
+      },
+      "engines": {
+        "node": ">=20.0.0"
+      },
+      "peerDependencies": {
+        "react": ">=18",
+        "react-dom": ">=18"
+      }
+    },
+    "node_modules/redux": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
+      "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
+      "license": "MIT"
+    },
+    "node_modules/redux-thunk": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
+      "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
+      "license": "MIT",
+      "peerDependencies": {
+        "redux": "^5.0.0"
+      }
+    },
+    "node_modules/require-directory": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+      "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/require-main-filename": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+      "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+      "license": "ISC"
+    },
+    "node_modules/reselect": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
+      "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
+      "license": "MIT"
+    },
+    "node_modules/resolve-dir": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz",
+      "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==",
+      "license": "MIT",
+      "dependencies": {
+        "expand-tilde": "^2.0.0",
+        "global-modules": "^1.0.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/resolve-dir/node_modules/global-modules": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz",
+      "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==",
+      "license": "MIT",
+      "dependencies": {
+        "global-prefix": "^1.0.1",
+        "is-windows": "^1.0.1",
+        "resolve-dir": "^1.0.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/resolve-dir/node_modules/global-prefix": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz",
+      "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==",
+      "license": "MIT",
+      "dependencies": {
+        "expand-tilde": "^2.0.2",
+        "homedir-polyfill": "^1.0.1",
+        "ini": "^1.3.4",
+        "is-windows": "^1.0.1",
+        "which": "^1.2.14"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/resolve-dir/node_modules/which": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+      "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+      "license": "ISC",
+      "dependencies": {
+        "isexe": "^2.0.0"
+      },
+      "bin": {
+        "which": "bin/which"
+      }
+    },
+    "node_modules/restore-cursor": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
+      "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==",
+      "license": "MIT",
+      "dependencies": {
+        "onetime": "^2.0.0",
+        "signal-exit": "^3.0.2"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/restore-cursor/node_modules/signal-exit": {
+      "version": "3.0.7",
+      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+      "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+      "license": "ISC"
+    },
+    "node_modules/rimraf": {
+      "version": "5.0.10",
+      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz",
+      "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==",
+      "license": "ISC",
+      "dependencies": {
+        "glob": "^10.3.7"
+      },
+      "bin": {
+        "rimraf": "dist/esm/bin.mjs"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/rollup": {
+      "version": "4.54.0",
+      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz",
+      "integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/estree": "1.0.8"
+      },
+      "bin": {
+        "rollup": "dist/bin/rollup"
+      },
+      "engines": {
+        "node": ">=18.0.0",
+        "npm": ">=8.0.0"
+      },
+      "optionalDependencies": {
+        "@rollup/rollup-android-arm-eabi": "4.54.0",
+        "@rollup/rollup-android-arm64": "4.54.0",
+        "@rollup/rollup-darwin-arm64": "4.54.0",
+        "@rollup/rollup-darwin-x64": "4.54.0",
+        "@rollup/rollup-freebsd-arm64": "4.54.0",
+        "@rollup/rollup-freebsd-x64": "4.54.0",
+        "@rollup/rollup-linux-arm-gnueabihf": "4.54.0",
+        "@rollup/rollup-linux-arm-musleabihf": "4.54.0",
+        "@rollup/rollup-linux-arm64-gnu": "4.54.0",
+        "@rollup/rollup-linux-arm64-musl": "4.54.0",
+        "@rollup/rollup-linux-loong64-gnu": "4.54.0",
+        "@rollup/rollup-linux-ppc64-gnu": "4.54.0",
+        "@rollup/rollup-linux-riscv64-gnu": "4.54.0",
+        "@rollup/rollup-linux-riscv64-musl": "4.54.0",
+        "@rollup/rollup-linux-s390x-gnu": "4.54.0",
+        "@rollup/rollup-linux-x64-gnu": "4.54.0",
+        "@rollup/rollup-linux-x64-musl": "4.54.0",
+        "@rollup/rollup-openharmony-arm64": "4.54.0",
+        "@rollup/rollup-win32-arm64-msvc": "4.54.0",
+        "@rollup/rollup-win32-ia32-msvc": "4.54.0",
+        "@rollup/rollup-win32-x64-gnu": "4.54.0",
+        "@rollup/rollup-win32-x64-msvc": "4.54.0",
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/run-async": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
+      "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.12.0"
+      }
+    },
+    "node_modules/rxjs": {
+      "version": "6.6.7",
+      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
+      "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "tslib": "^1.9.0"
+      },
+      "engines": {
+        "npm": ">=2.0.0"
+      }
+    },
+    "node_modules/safe-buffer": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+      "license": "MIT"
+    },
+    "node_modules/scheduler": {
+      "version": "0.27.0",
+      "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+      "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+      "license": "MIT"
+    },
+    "node_modules/semver": {
+      "version": "6.3.1",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+      "dev": true,
+      "license": "ISC",
+      "bin": {
+        "semver": "bin/semver.js"
+      }
+    },
+    "node_modules/set-blocking": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+      "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
+      "license": "ISC"
+    },
+    "node_modules/set-cookie-parser": {
+      "version": "2.7.2",
+      "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
+      "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
+      "license": "MIT"
+    },
+    "node_modules/shebang-command": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+      "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+      "license": "MIT",
+      "dependencies": {
+        "shebang-regex": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/shebang-regex": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+      "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/should": {
+      "version": "13.2.3",
+      "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz",
+      "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==",
+      "license": "MIT",
+      "dependencies": {
+        "should-equal": "^2.0.0",
+        "should-format": "^3.0.3",
+        "should-type": "^1.4.0",
+        "should-type-adaptors": "^1.0.1",
+        "should-util": "^1.0.0"
+      }
+    },
+    "node_modules/should-equal": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz",
+      "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==",
+      "license": "MIT",
+      "dependencies": {
+        "should-type": "^1.4.0"
+      }
+    },
+    "node_modules/should-format": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz",
+      "integrity": "sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==",
+      "license": "MIT",
+      "dependencies": {
+        "should-type": "^1.3.0",
+        "should-type-adaptors": "^1.0.1"
+      }
+    },
+    "node_modules/should-type": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz",
+      "integrity": "sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==",
+      "license": "MIT"
+    },
+    "node_modules/should-type-adaptors": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz",
+      "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==",
+      "license": "MIT",
+      "dependencies": {
+        "should-type": "^1.3.0",
+        "should-util": "^1.0.0"
+      }
+    },
+    "node_modules/should-util": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz",
+      "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==",
+      "license": "MIT"
+    },
+    "node_modules/signal-exit": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+      "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/source-map-js": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+      "dev": true,
+      "license": "BSD-3-Clause",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/string-width": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+      "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+      "license": "MIT",
+      "dependencies": {
+        "eastasianwidth": "^0.2.0",
+        "emoji-regex": "^9.2.2",
+        "strip-ansi": "^7.0.1"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/string-width-cjs": {
+      "name": "string-width",
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+      "license": "MIT",
+      "dependencies": {
+        "emoji-regex": "^8.0.0",
+        "is-fullwidth-code-point": "^3.0.0",
+        "strip-ansi": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/string-width-cjs/node_modules/ansi-regex": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/string-width-cjs/node_modules/emoji-regex": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+      "license": "MIT"
+    },
+    "node_modules/string-width-cjs/node_modules/strip-ansi": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/strip-ansi": {
+      "version": "7.1.2",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
+      "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-regex": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+      }
+    },
+    "node_modules/strip-ansi-cjs": {
+      "name": "strip-ansi",
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/supports-color": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+      "license": "MIT",
+      "dependencies": {
+        "has-flag": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/tabtab": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/tabtab/-/tabtab-3.0.2.tgz",
+      "integrity": "sha512-jANKmUe0sIQc/zTALTBy186PoM/k6aPrh3A7p6AaAfF6WPSbTx1JYeGIGH162btpH+mmVEXln+UxwViZHO2Jhg==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "^4.0.1",
+        "es6-promisify": "^6.0.0",
+        "inquirer": "^6.0.0",
+        "minimist": "^1.2.0",
+        "mkdirp": "^0.5.1",
+        "untildify": "^3.0.3"
+      }
+    },
+    "node_modules/through": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+      "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
+      "license": "MIT"
+    },
+    "node_modules/tildify": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz",
+      "integrity": "sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/tinyglobby": {
+      "version": "0.2.15",
+      "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+      "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "fdir": "^6.5.0",
+        "picomatch": "^4.0.3"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/SuperchupuDev"
+      }
+    },
+    "node_modules/tmp": {
+      "version": "0.0.33",
+      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
+      "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+      "license": "MIT",
+      "dependencies": {
+        "os-tmpdir": "~1.0.2"
+      },
+      "engines": {
+        "node": ">=0.6.0"
+      }
+    },
+    "node_modules/to-regex-range": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+      "license": "MIT",
+      "dependencies": {
+        "is-number": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=8.0"
+      }
+    },
+    "node_modules/tslib": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+      "license": "0BSD"
+    },
+    "node_modules/typescript": {
+      "version": "5.8.3",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
+      "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=14.17"
+      }
+    },
+    "node_modules/undici-types": {
+      "version": "6.21.0",
+      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+      "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/untildify": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/untildify/-/untildify-3.0.3.tgz",
+      "integrity": "sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/update-browserslist-db": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+      "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/browserslist"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "escalade": "^3.2.0",
+        "picocolors": "^1.1.1"
+      },
+      "bin": {
+        "update-browserslist-db": "cli.js"
+      },
+      "peerDependencies": {
+        "browserslist": ">= 4.21.0"
+      }
+    },
+    "node_modules/use-sync-external-store": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+      "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+      "license": "MIT",
+      "peerDependencies": {
+        "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+      }
+    },
+    "node_modules/vite": {
+      "version": "6.4.1",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
+      "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "esbuild": "^0.25.0",
+        "fdir": "^6.4.4",
+        "picomatch": "^4.0.2",
+        "postcss": "^8.5.3",
+        "rollup": "^4.34.9",
+        "tinyglobby": "^0.2.13"
+      },
+      "bin": {
+        "vite": "bin/vite.js"
+      },
+      "engines": {
+        "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/vitejs/vite?sponsor=1"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.3"
+      },
+      "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
+        }
+      }
+    },
+    "node_modules/web-streams-polyfill": {
+      "version": "3.3.3",
+      "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
+      "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/which": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+      "license": "ISC",
+      "dependencies": {
+        "isexe": "^2.0.0"
+      },
+      "bin": {
+        "node-which": "bin/node-which"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/which-module": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
+      "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
+      "license": "ISC"
+    },
+    "node_modules/wrap-ansi": {
+      "version": "8.1.0",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+      "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-styles": "^6.1.0",
+        "string-width": "^5.0.1",
+        "strip-ansi": "^7.0.1"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+      }
+    },
+    "node_modules/wrap-ansi-cjs": {
+      "name": "wrap-ansi",
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+      "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-styles": "^4.0.0",
+        "string-width": "^4.1.0",
+        "strip-ansi": "^6.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+      }
+    },
+    "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "license": "MIT",
+      "dependencies": {
+        "color-convert": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
+    "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+      "license": "MIT"
+    },
+    "node_modules/wrap-ansi-cjs/node_modules/string-width": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+      "license": "MIT",
+      "dependencies": {
+        "emoji-regex": "^8.0.0",
+        "is-fullwidth-code-point": "^3.0.0",
+        "strip-ansi": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+      "license": "ISC"
+    },
+    "node_modules/ws": {
+      "version": "8.18.3",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
+      "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.0.0"
+      },
+      "peerDependencies": {
+        "bufferutil": "^4.0.1",
+        "utf-8-validate": ">=5.0.2"
+      },
+      "peerDependenciesMeta": {
+        "bufferutil": {
+          "optional": true
+        },
+        "utf-8-validate": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/y18n": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
+      "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
+      "license": "ISC"
+    },
+    "node_modules/yallist": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+      "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+      "dev": true,
+      "license": "ISC"
+    },
+    "node_modules/yargs": {
+      "version": "14.2.3",
+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.3.tgz",
+      "integrity": "sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==",
+      "license": "MIT",
+      "dependencies": {
+        "cliui": "^5.0.0",
+        "decamelize": "^1.2.0",
+        "find-up": "^3.0.0",
+        "get-caller-file": "^2.0.1",
+        "require-directory": "^2.1.1",
+        "require-main-filename": "^2.0.0",
+        "set-blocking": "^2.0.0",
+        "string-width": "^3.0.0",
+        "which-module": "^2.0.0",
+        "y18n": "^4.0.0",
+        "yargs-parser": "^15.0.1"
+      }
+    },
+    "node_modules/yargs-parser": {
+      "version": "15.0.3",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.3.tgz",
+      "integrity": "sha512-/MVEVjTXy/cGAjdtQf8dW3V9b97bPN7rNn8ETj6BmAQL7ibC7O1Q9SPJbGjgh3SlwoBNXMzj/ZGIj8mBgl12YA==",
+      "license": "ISC",
+      "dependencies": {
+        "camelcase": "^5.0.0",
+        "decamelize": "^1.2.0"
+      }
+    },
+    "node_modules/yargs/node_modules/ansi-regex": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
+      "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/yargs/node_modules/emoji-regex": {
+      "version": "7.0.3",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+      "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+      "license": "MIT"
+    },
+    "node_modules/yargs/node_modules/is-fullwidth-code-point": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+      "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/yargs/node_modules/string-width": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+      "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+      "license": "MIT",
+      "dependencies": {
+        "emoji-regex": "^7.0.1",
+        "is-fullwidth-code-point": "^2.0.0",
+        "strip-ansi": "^5.1.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/yargs/node_modules/strip-ansi": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+      "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-regex": "^4.1.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    }
+  }
+}

+ 29 - 0
EsimLao/esim-vite/package.json

@@ -0,0 +1,29 @@
+{
+  "name": "esim-vite",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "@google/genai": "^1.34.0",
+    "@reduxjs/toolkit": "^2.11.2",
+    "@tanstack/react-query": "^5.90.16",
+    "axios": "^1.13.2",
+    "meta": "^2.2.25",
+    "react": "^19.2.3",
+    "react-dom": "^19.2.3",
+    "react-redux": "^9.2.0",
+    "react-router-dom": "^7.11.0",
+    "redux": "^5.0.1"
+  },
+  "devDependencies": {
+    "@types/node": "^22.14.0",
+    "@vitejs/plugin-react": "^5.0.0",
+    "typescript": "~5.8.2",
+    "vite": "^6.2.0"
+  }
+}

+ 84 - 0
EsimLao/esim-vite/src/App.tsx

@@ -0,0 +1,84 @@
+import React from "react";
+import { Routes, Route, useLocation, Navigate } from "react-router-dom";
+import Header from "./components/Header";
+import Footer from "./components/Footer";
+import HomeView from "./pages/home/HomeView";
+import Sidebar from "./components/Sidebar";
+import ProductDetailView from "./pages/product-detail/ProductDetailView";
+import BuySimView from "./pages/buy-sim/BuySimView";
+import SupportView from "./pages/support/SupportView";
+import NewsView from "./pages/news/NewsView";
+import ArticleDetailView from "./pages/news/NewsDetailView";
+import LoginView from "./pages/login/LoginView";
+import ContactView from "./pages/contact/ContactView";
+import TopLoader from "./components/TopLoader";
+
+const App: React.FC = () => {
+  const location = useLocation();
+  const path = location.pathname;
+
+  const isAiView = path.startsWith("/ai");
+  const isPlainView = path === "/login";
+
+  // Helper to determine if footer should be shown
+  const showFooter = !isAiView && !isPlainView;
+
+  return (
+    <div
+      className={`flex flex-col min-h-screen bg-white selection:bg-red-100 ${
+        isAiView ? "h-screen overflow-hidden" : ""
+      }`}
+    >
+      {!isPlainView && <Header />}
+
+      <div className={`flex flex-1 ${isAiView ? "overflow-hidden" : ""}`}>
+        {isAiView && <Sidebar />}
+
+        <main
+          className={`flex-1 relative flex flex-col ${
+            isAiView ? "overflow-y-auto bg-slate-50" : "bg-white"
+          }`}
+        >
+          {isAiView && (
+            <div className="h-14 border-b border-slate-200 bg-white/50 backdrop-blur-md flex items-center justify-between px-6 shrink-0 z-10">
+              <div className="flex items-center space-x-2">
+                <span className="w-2 h-2 rounded-full bg-red-500 animate-pulse"></span>
+                <span className="text-[10px] font-black text-slate-400 uppercase tracking-widest">
+                  Assistant Active
+                </span>
+              </div>
+              <a
+                href="/"
+                className="text-[10px] font-bold text-[#EE0434] hover:underline"
+              >
+                Back to Store
+              </a>
+            </div>
+          )}
+
+          <TopLoader visible={true} />
+
+          <div className="flex-1">
+            <Routes>
+              <Route path="/" element={<HomeView />} />
+              <Route path="/buy-sim" element={<BuySimView />} />
+              <Route path="/product/:country" element={<ProductDetailView />} />
+              <Route path="/support" element={<SupportView />} />
+              <Route path="/news" element={<NewsView />} />
+              <Route path="/news/:id" element={<ArticleDetailView />} />
+              <Route path="/contact" element={<ContactView />} />
+              <Route path="/login" element={<LoginView />} />
+
+              {/* Fallback */}
+              <Route path="*" element={<Navigate to="/" replace />} />
+            </Routes>
+          </div>
+
+          {showFooter && <Footer />}
+        </main>
+      </div>
+    </div>
+  );
+};
+
+export default App;

+ 37 - 0
EsimLao/esim-vite/src/apis/axios.ts

@@ -0,0 +1,37 @@
+import axios, { AxiosInstance } from 'axios';
+
+export const createAxiosInstance = (baseURL: string): AxiosInstance => {
+  const instance = axios.create({
+    baseURL,
+    timeout: 10000,
+    headers: {
+      'Content-Type': 'application/json',
+    },
+  });
+
+  instance.interceptors.request.use((config) => {
+    const token = localStorage.getItem('token');
+    const lang = localStorage.getItem('selectedLanguage') || 'en';
+
+    if (lang) {
+      config.headers['Accept-Language'] = lang;
+    }
+    if (token) {
+      config.headers.Authorization = `Bearer ${token}`;
+    }
+    return config;
+  });
+
+  instance.interceptors.response.use(
+    (response) => response.data,
+    (error) => {
+      if (error.response?.status === 401) {
+        localStorage.removeItem('token');
+        window.location.href = '/login';
+      }
+      return Promise.reject(error);
+    }
+  );
+
+  return instance;
+};

+ 45 - 0
EsimLao/esim-vite/src/apis/baseApi.ts

@@ -0,0 +1,45 @@
+import { createAxiosInstance } from "./axios";
+
+export type Response<T> = {
+  errorCode: number;
+  message: string;
+  msg: string;
+  status: boolean;
+  data: T;
+};
+
+
+export const authApiInstance = createAxiosInstance(
+  import.meta.env.VITE_API_AUTH_URL
+);
+
+export const productApiInstance = createAxiosInstance(
+  import.meta.env.VITE_API_PRODUCT_URL
+);
+
+export class BaseApi {
+  protected baseUrl: string;
+  protected defaultTimeout = 10000; // 10 giây
+
+  constructor(baseUrl: string, timeout?: number) {
+    this.baseUrl = baseUrl;
+    if (timeout) {
+      this.defaultTimeout = timeout;
+    }
+  }
+
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  async productPost<T>(endpoint: string = '', data?: any, timeout?: number): Promise<Response<T>> {
+    return productApiInstance.post(`${this.baseUrl}${endpoint}`, data, {
+      timeout: timeout ?? this.defaultTimeout
+    });
+  }
+
+
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  async authPost<T>(endpoint: string = '', data?: any, timeout?: number): Promise<Response<T>> {
+    return authApiInstance.post(`${this.baseUrl}${endpoint}`, data, {
+      timeout: timeout ?? this.defaultTimeout
+    });
+  }
+}

+ 15 - 0
EsimLao/esim-vite/src/apis/productApi.ts

@@ -0,0 +1,15 @@
+import { AreaData } from '../services/product/type';
+import { BaseApi } from './baseApi';
+
+class ProductApi extends BaseApi {
+    constructor() {
+        super('/api');
+    }
+
+    async loadArea() {
+        return this.productPost('/list_area');
+    }
+}
+
+
+export const productApi = new ProductApi();

+ 13 - 0
EsimLao/esim-vite/src/app/store.ts

@@ -0,0 +1,13 @@
+import { configureStore } from '@reduxjs/toolkit';
+import loadingReducer from '../features/loading/loadingSlice';
+import areasReducer from '../features/areas/areasSlice';
+
+export const store = configureStore({
+  reducer: {
+    loading: loadingReducer,
+    areas: areasReducer,
+  },
+});
+
+export type RootState = ReturnType<typeof store.getState>;
+export type AppDispatch = typeof store.dispatch;

+ 176 - 0
EsimLao/esim-vite/src/components/ChatView.tsx

@@ -0,0 +1,176 @@
+import React, { useState, useRef, useEffect } from "react";
+import { ChatMessage } from "../services/types";
+import { chatStream } from "../services/gemini";
+
+const ChatView: React.FC = () => {
+  const [messages, setMessages] = useState<ChatMessage[]>([]);
+  const [input, setInput] = useState("");
+  const [isTyping, setIsTyping] = useState(false);
+  const scrollRef = useRef<HTMLDivElement>(null);
+
+  useEffect(() => {
+    if (scrollRef.current) {
+      scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
+    }
+  }, [messages, isTyping]);
+
+  const handleSend = async () => {
+    if (!input.trim() || isTyping) return;
+
+    const userMsg: ChatMessage = {
+      role: "user",
+      text: input,
+      timestamp: new Date(),
+    };
+    setMessages((prev) => [...prev, userMsg]);
+    setInput("");
+    setIsTyping(true);
+
+    let modelResponse = "";
+    const modelMsg: ChatMessage = {
+      role: "model",
+      text: "",
+      timestamp: new Date(),
+    };
+    setMessages((prev) => [...prev, modelMsg]);
+
+    try {
+      await chatStream([...messages, userMsg], (chunk) => {
+        modelResponse += chunk;
+        setMessages((prev) => {
+          const updated = [...prev];
+          updated[updated.length - 1] = {
+            ...updated[updated.length - 1],
+            text: modelResponse,
+          };
+          return updated;
+        });
+      });
+    } catch (error) {
+      console.error(error);
+      setMessages((prev) => {
+        const updated = [...prev];
+        updated[updated.length - 1] = {
+          ...updated[updated.length - 1],
+          text: "Connectivity Error: Unable to reach the processing unit.",
+        };
+        return updated;
+      });
+    } finally {
+      setIsTyping(false);
+    }
+  };
+
+  return (
+    <div className="flex flex-col h-full bg-slate-50/50">
+      <div ref={scrollRef} className="flex-1 overflow-y-auto p-10 space-y-8">
+        {messages.length === 0 && (
+          <div className="h-full flex flex-col items-center justify-center text-center space-y-6">
+            <div className="w-20 h-20 bg-white rounded-3xl flex items-center justify-center shadow-2xl border border-slate-100">
+              <svg
+                className="w-10 h-10 text-[#004ea8]"
+                fill="none"
+                stroke="currentColor"
+                viewBox="0 0 24 24"
+              >
+                <path
+                  strokeLinecap="round"
+                  strokeLinejoin="round"
+                  strokeWidth={2}
+                  d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"
+                />
+              </svg>
+            </div>
+            <div>
+              <h2 className="text-3xl font-black text-slate-900 tracking-tight">
+                Enterprise Intelligence
+              </h2>
+              <p className="text-slate-500 max-w-md mt-3 text-lg">
+                Sophisticated AI assistant for your professional workflow. Ask
+                anything from market analysis to complex coding solutions.
+              </p>
+            </div>
+          </div>
+        )}
+
+        {messages.map((msg, idx) => (
+          <div
+            key={idx}
+            className={`flex ${
+              msg.role === "user" ? "justify-end" : "justify-start"
+            } animate-in fade-in slide-in-from-bottom-2`}
+          >
+            <div
+              className={`max-w-[75%] rounded-3xl px-6 py-4 shadow-sm border ${
+                msg.role === "user"
+                  ? "bg-[#004ea8] text-white border-[#003d82] rounded-br-none shadow-blue-200 shadow-lg"
+                  : "bg-white text-slate-700 border-slate-100 rounded-bl-none shadow-md"
+              }`}
+            >
+              <p className="text-base leading-relaxed whitespace-pre-wrap font-medium">
+                {msg.text}
+              </p>
+              <div
+                className={`text-[10px] mt-3 font-bold uppercase tracking-widest ${
+                  msg.role === "user" ? "text-blue-100" : "text-[#004ea8]"
+                }`}
+              >
+                {msg.timestamp.toLocaleTimeString([], {
+                  hour: "2-digit",
+                  minute: "2-digit",
+                })}
+              </div>
+            </div>
+          </div>
+        ))}
+
+        {isTyping && (
+          <div className="flex justify-start">
+            <div className="bg-white border border-slate-100 rounded-3xl px-6 py-4 rounded-bl-none shadow-sm">
+              <div className="flex space-x-2">
+                <div className="w-2.5 h-2.5 bg-[#004ea8] rounded-full animate-bounce" />
+                <div className="w-2.5 h-2.5 bg-[#004ea8] rounded-full animate-bounce [animation-delay:-0.15s]" />
+                <div className="w-2.5 h-2.5 bg-[#004ea8] rounded-full animate-bounce [animation-delay:-0.3s]" />
+              </div>
+            </div>
+          </div>
+        )}
+      </div>
+
+      <div className="p-8 bg-white border-t border-slate-200">
+        <div className="max-w-5xl mx-auto relative">
+          <input
+            type="text"
+            value={input}
+            onChange={(e) => setInput(e.target.value)}
+            onKeyDown={(e) => e.key === "Enter" && handleSend()}
+            placeholder="Input your query here..."
+            className="w-full bg-slate-50 text-slate-900 rounded-2xl px-8 py-5 focus:outline-none focus:ring-4 focus:ring-blue-100 border border-slate-200 placeholder-slate-400 text-lg shadow-inner transition-all"
+          />
+          <button
+            disabled={isTyping}
+            onClick={handleSend}
+            className="absolute right-3 top-3 bottom-3 bg-[#004ea8] hover:bg-[#003d82] disabled:bg-slate-300 text-white rounded-xl px-8 transition-all font-bold shadow-lg flex items-center space-x-2 active:scale-95"
+          >
+            <span>Execute</span>
+            <svg
+              className="w-5 h-5"
+              fill="none"
+              stroke="currentColor"
+              viewBox="0 0 24 24"
+            >
+              <path
+                strokeLinecap="round"
+                strokeLinejoin="round"
+                strokeWidth={2}
+                d="M13 5l7 7-7 7M5 5l7 7-7 7"
+              />
+            </svg>
+          </button>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default ChatView;

+ 63 - 0
EsimLao/esim-vite/src/components/Footer.tsx

@@ -0,0 +1,63 @@
+
+import React from 'react';
+
+const Footer: React.FC = () => {
+  return (
+    <footer className="bg-white border-t border-slate-100 pt-16 pb-8">
+      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+        {/* Info Columns */}
+        <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-12 mb-16">
+          <div className="space-y-6">
+            <div className="flex items-center space-x-2">
+              <svg className="w-8 h-8 text-[#EE0434]" viewBox="0 0 24 24" fill="currentColor">
+                <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
+              </svg>
+              <span className="text-xl font-black text-[#EE0434]">InfiGate</span>
+            </div>
+            <p className="text-sm font-bold text-[#EE0434] leading-tight">WellZone Technology Solutions Joint Stock Company</p>
+            <p className="text-sm font-medium text-slate-600">Tax Code: 0109310641</p>
+            <div className="space-y-3 text-sm text-slate-500">
+              <p className="flex items-start"><svg className="w-4 h-4 mr-2 mt-0.5 text-[#EE0434]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"/><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"/></svg> 10th Floor, 83B Ly Thuong Kiet, Cua Nam Ward, Hanoi City</p>
+              <p className="flex items-center"><svg className="w-4 h-4 mr-2 text-[#EE0434]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg> info@infigate.global</p>
+              <p className="flex items-center"><svg className="w-4 h-4 mr-2 text-[#EE0434]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/></svg> Zalo/Whatsapp: +84989145663</p>
+            </div>
+          </div>
+          <div>
+            <h3 className="text-base font-bold text-slate-900 mb-6">InfiGate</h3>
+            <ul className="space-y-4 text-sm text-slate-500">
+              <li><a href="#" className="hover:text-[#EE0434] transition-colors">About Us</a></li>
+              <li><a href="#" className="hover:text-[#EE0434] transition-colors">Terms Of Service</a></li>
+              <li><a href="#" className="hover:text-[#EE0434] transition-colors">Privacy Policy</a></li>
+            </ul>
+          </div>
+          <div>
+            <h3 className="text-base font-bold text-slate-900 mb-6">Travel SIM</h3>
+            <ul className="space-y-4 text-sm text-slate-500">
+              <li><a href="#" className="hover:text-[#EE0434] transition-colors">China SIM</a></li>
+              <li><a href="#" className="hover:text-[#EE0434] transition-colors">Thailand SIM</a></li>
+              <li><a href="#" className="hover:text-[#EE0434] transition-colors">Japan SIM</a></li>
+            </ul>
+          </div>
+          <div>
+            <h3 className="text-base font-bold text-slate-900 mb-6">Guide</h3>
+            <ul className="space-y-4 text-sm text-slate-500">
+              <li><a href="#" className="hover:text-[#EE0434] transition-colors">What is eSIM</a></li>
+              <li><a href="#" className="hover:text-[#EE0434] transition-colors">Support</a></li>
+            </ul>
+          </div>
+        </div>
+
+        {/* Bottom */}
+        <div className="pt-8 border-t border-slate-100 flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0">
+          <p className="text-sm text-slate-400">© 2025 InfiGate All rights reserved</p>
+          <div className="bg-[#EE0434] px-4 py-2 rounded-lg flex items-center space-x-2">
+            <div className="w-5 h-5 bg-white rounded-full flex items-center justify-center text-[#EE0434]"><svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20"><path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd"/></svg></div>
+            <span className="text-[10px] text-white font-bold uppercase tracking-widest">Official Store Verified</span>
+          </div>
+        </div>
+      </div>
+    </footer>
+  );
+};
+
+export default Footer;

+ 448 - 0
EsimLao/esim-vite/src/components/Header.tsx

@@ -0,0 +1,448 @@
+
+import React, { useState, useEffect, useRef } from 'react';
+import { useNavigate, useLocation, Link } from 'react-router-dom';
+
+const Header: React.FC = () => {
+  const navigate = useNavigate();
+  const location = useLocation();
+  const [isMenuOpen, setIsMenuOpen] = useState(false);
+  const [isBuySimExpanded, setIsBuySimExpanded] = useState(false);
+  const [isGuideExpanded, setIsGuideExpanded] = useState(false);
+  const [isBuySimMegaVisible, setIsBuySimMegaVisible] = useState(false);
+  const [isGuideMegaVisible, setIsGuideMegaVisible] = useState(false);
+  const [isLangMenuOpen, setIsLangMenuOpen] = useState(false);
+  const [selectedLang, setSelectedLang] = useState<'en' | 'vi'>('en');
+  const [activeDesktopTab, setActiveDesktopTab] = useState<'popular' | 'region'>('popular');
+  const [isScrolled, setIsScrolled] = useState(false);
+  
+  const langMenuRef = useRef<HTMLDivElement>(null);
+
+  const countries = [
+    { name: 'China', flag: 'cn' },
+    { name: 'Hong Kong', flag: 'hk' },
+    { name: 'Japan', flag: 'jp' },
+    { name: 'Singapore', flag: 'sg' },
+    { name: 'South Korea', flag: 'kr' },
+    { name: 'Taiwan', flag: 'tw' },
+    { name: 'Thailand', flag: 'th' },
+    { name: 'United States', flag: 'us' },
+  ];
+
+  const regionList = [
+    "Americas", "Asia", "Asia 11 countries", "Asialink 7 countries",
+    "Australia & New Zealand", "EU 33 countries", "EU 40 countries", "Eurolink",
+    "Europe", "Europe 33 countries", "Hong Kong & Macau", "Middle East and Africa",
+    "North America & Canada", "North America A", "Oceania", "Singapore & Malaysia"
+  ];
+
+  const guideItems = [
+    { label: "What is eSIM", path: "/support" },
+    { label: "Installation instructions", path: "/support" },
+    { label: "Support", path: "/support" },
+    { label: "Order Tracking Search", path: "/support" }
+  ];
+
+  const languages = [
+    { code: 'en', label: 'English', flag: 'us' },
+    { code: 'vi', label: 'Tiếng Việt', flag: 'vn' }
+  ];
+
+  useEffect(() => {
+    const handleScroll = () => {
+      setIsScrolled(window.scrollY > 300);
+    };
+    window.addEventListener('scroll', handleScroll);
+    return () => window.removeEventListener('scroll', handleScroll);
+  }, []);
+
+  const handleCountryClick = (c: { name: string; flag: string }) => {
+    navigate(`/product/${c.name.toLowerCase()}`, { state: { country: c.name, flag: c.flag } });
+    setIsBuySimMegaVisible(false);
+    setIsMenuOpen(false);
+  };
+
+  const handleRegionClick = (region: string) => {
+    navigate(`/product/${region.toLowerCase()}`, { state: { country: region, flag: 'un' } });
+    setIsBuySimMegaVisible(false);
+    setIsMenuOpen(false);
+  };
+
+  useEffect(() => {
+    const handleClickOutside = (event: MouseEvent) => {
+      if (langMenuRef.current && !langMenuRef.current.contains(event.target as Node)) {
+        setIsLangMenuOpen(false);
+      }
+    };
+    document.addEventListener('mousedown', handleClickOutside);
+    return () => document.removeEventListener('mousedown', handleClickOutside);
+  }, []);
+
+  useEffect(() => {
+    const handleResize = () => {
+      if (window.innerWidth >= 1024) setIsMenuOpen(false);
+    };
+    window.addEventListener('resize', handleResize);
+    return () => window.removeEventListener('resize', handleResize);
+  }, []);
+
+  const currentLangObj = languages.find(l => l.code === selectedLang) || languages[0];
+
+  const isActive = (path: string) => location.pathname === path;
+
+  return (
+    <>
+      <header className="sticky top-0 z-[60] w-full bg-white border-b border-slate-100 shadow-sm transition-all duration-300">
+        <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+          <div className="flex justify-between items-center h-20">
+            {/* Logo */}
+            <Link to="/" className="flex-shrink-0 flex items-center">
+              <div className="flex items-center space-x-1">
+                <svg className="w-8 h-8 text-[#EE0434]" viewBox="0 0 24 24" fill="currentColor">
+                  <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
+                </svg>
+                <span className="text-2xl font-bold tracking-tighter">
+                  <span className="text-[#EE0434]">Infi</span>
+                  <span className="text-[#333]">Gate</span>
+                </span>
+              </div>
+            </Link>
+
+            {/* Desktop Search on Scroll */}
+            <div className={`hidden lg:flex items-center transition-all duration-500 overflow-hidden ${isScrolled ? 'flex-1 max-w-md mx-8 opacity-100' : 'max-w-0 opacity-0 pointer-events-none'}`}>
+               <div className="relative w-full">
+                 <input 
+                   type="text" 
+                   placeholder="Search country..." 
+                   className="w-full bg-slate-50 border border-slate-200 rounded-full py-2.5 px-6 pl-12 text-sm focus:outline-none focus:ring-2 focus:ring-red-100 focus:border-[#EE0434] transition-all"
+                 />
+                 <svg className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
+               </div>
+            </div>
+
+            {/* Desktop Nav */}
+            <nav className={`hidden lg:flex items-center h-full transition-all duration-300 ${isScrolled ? 'space-x-4' : 'space-x-8'}`}>
+              <Link to="/" className={`text-[17px] font-bold ${isActive('/') ? 'text-[#EE0434]' : 'text-slate-700 hover:text-[#EE0434]'}`}>Home</Link>
+              
+              <div 
+                className="relative h-full flex items-center"
+                onMouseEnter={() => setIsBuySimMegaVisible(true)}
+                onMouseLeave={() => setIsBuySimMegaVisible(false)}
+              >
+                <Link 
+                  to="/buy-sim"
+                  className={`flex items-center text-[17px] font-bold transition-colors ${isActive('/buy-sim') ? 'text-[#EE0434]' : 'text-slate-700 hover:text-[#EE0434]'}`}
+                >
+                  Buy SIM <svg className={`ml-1 w-4 h-4 transition-transform ${isBuySimMegaVisible ? 'rotate-180' : ''}`} fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" /></svg>
+                </Link>
+
+                {isBuySimMegaVisible && (
+                  <div className="absolute top-full left-1/2 -translate-x-1/2 w-[950px] bg-white rounded-[32px] shadow-[0_30px_60px_-15px_rgba(0,0,0,0.15)] border border-slate-100 mt-0 overflow-hidden flex animate-in fade-in slide-in-from-top-2 duration-300">
+                    <div className="w-[280px] bg-red-50 p-10 flex flex-col">
+                      <h3 className="text-4xl font-black text-slate-900 mb-4">Buy SIM</h3>
+                      <button 
+                        onClick={() => {
+                          navigate('/buy-sim');
+                          setIsBuySimMegaVisible(false);
+                        }}
+                        className="text-[#EE0434] font-bold text-xl flex items-center group mb-8"
+                      >
+                        View all <svg className="ml-2 w-5 h-5 transition-transform group-hover:translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M9 5l7 7-7 7" /></svg>
+                      </button>
+                    </div>
+                    <div className="flex-1 p-10">
+                      <div className="flex space-x-4 mb-10">
+                        <button 
+                          onClick={() => setActiveDesktopTab('popular')} 
+                          className={`px-8 py-3 rounded-full text-base font-bold transition-all ${activeDesktopTab === 'popular' ? 'bg-[#EE0434] text-white shadow-lg shadow-red-100' : 'bg-slate-50 text-slate-900 hover:bg-slate-100'}`}
+                        >
+                          Most Popular
+                        </button>
+                        <button 
+                          onClick={() => setActiveDesktopTab('region')} 
+                          className={`px-8 py-3 rounded-full text-base font-bold transition-all ${activeDesktopTab === 'region' ? 'bg-[#EE0434] text-white shadow-lg shadow-red-100' : 'bg-slate-50 text-slate-900 hover:bg-slate-100'}`}
+                        >
+                          Region
+                        </button>
+                      </div>
+
+                      {activeDesktopTab === 'popular' ? (
+                        <div className="grid grid-cols-4 gap-y-8 gap-x-4">
+                          {countries.map((c) => (
+                            <div key={c.name} onClick={() => handleCountryClick(c)} className="flex items-center space-x-3 group cursor-pointer hover:bg-slate-50 p-2 rounded-xl transition-colors">
+                              <div className="w-7 h-7 rounded-full overflow-hidden border border-slate-200 shadow-sm shrink-0">
+                                 <img src={`https://flagcdn.com/w40/${c.flag}.png`} alt={c.name} className="w-full h-full object-cover" />
+                              </div>
+                              <span className="text-[16px] font-bold text-slate-700 group-hover:text-[#EE0434] transition-colors">{c.name}</span>
+                            </div>
+                          ))}
+                        </div>
+                      ) : (
+                        <div className="grid grid-cols-4 gap-y-6 gap-x-2">
+                          {regionList.map((region) => (
+                            <div 
+                              key={region} 
+                              onClick={() => handleRegionClick(region)} 
+                              className="flex items-center space-x-3 group cursor-pointer hover:bg-slate-50 p-2 rounded-xl transition-colors min-w-0"
+                            >
+                              <div className="w-7 h-7 rounded-full bg-red-50 flex items-center justify-center shrink-0 border border-red-100/50">
+                                <svg className="w-4 h-4 text-red-300" fill="currentColor" viewBox="0 0 24 24">
+                                  <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7.01-3.55-7.5-7.5H7v-1H3.5c.49-3.95 3.55-7.01 7.5-7.5v3.5h1V3.5c3.95.49 7.01 3.55 7.5 7.5H17v1h3.5c-.49 3.95-3.55-7.01-7.5 7.5v-3.5h-1v3.5z"/>
+                                </svg>
+                              </div>
+                              <span className="text-[16px] font-bold text-slate-700 group-hover:text-[#EE0434] transition-colors truncate">
+                                {region}
+                              </span>
+                            </div>
+                          ))}
+                        </div>
+                      )}
+                    </div>
+                  </div>
+                )}
+              </div>
+
+              <div 
+                className="relative h-full flex items-center"
+                onMouseEnter={() => setIsGuideMegaVisible(true)}
+                onMouseLeave={() => setIsGuideMegaVisible(false)}
+              >
+                <Link 
+                  to="/support"
+                  className={`flex items-center text-[17px] font-bold transition-colors ${isActive('/support') ? 'text-[#EE0434]' : 'text-slate-700 hover:text-[#EE0434]'}`}
+                >
+                  Guide <svg className={`ml-1 w-4 h-4 transition-transform ${isGuideMegaVisible ? 'rotate-180' : ''}`} fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" /></svg>
+                </Link>
+
+                {isGuideMegaVisible && (
+                  <div className="absolute top-full left-0 w-[600px] bg-white rounded-[32px] shadow-2xl border border-slate-100 mt-0 overflow-hidden flex animate-in fade-in slide-in-from-top-2 duration-300">
+                    <div className="flex-1 py-10 px-6 flex flex-col">
+                      {guideItems.map((item, idx) => (
+                        <button 
+                          key={item.label} 
+                          onClick={() => {
+                            navigate(item.path);
+                            setIsGuideMegaVisible(false);
+                          }}
+                          className={`w-full text-left px-6 py-4 rounded-2xl text-base font-medium transition-all text-slate-600 hover:bg-slate-50 hover:text-[#EE0434]`}
+                        >
+                          {item.label}
+                        </button>
+                      ))}
+                    </div>
+                  </div>
+                )}
+              </div>
+
+              <Link 
+                to="/news"
+                className={`text-[17px] font-bold transition-colors ${isActive('/news') ? 'text-[#EE0434]' : 'text-slate-700 hover:text-[#EE0434]'}`}
+              >
+                News
+              </Link>
+              
+              <Link 
+                to="/contact"
+                className={`text-[17px] font-bold transition-colors ${isActive('/contact') ? 'text-[#EE0434]' : 'text-slate-700 hover:text-[#EE0434]'}`}
+              >
+                Contact
+              </Link>
+            </nav>
+
+            {/* Icons */}
+            <div className="flex items-center space-x-5">
+              <Link 
+                to="/login"
+                className="p-2 text-slate-700 hover:text-[#EE0434] transition-colors"
+              >
+                <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /></svg>
+              </Link>
+              <button className="hidden sm:flex p-2 text-slate-700 hover:text-[#EE0434] relative">
+                <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z" /></svg>
+                <span className="absolute top-1 right-1 w-4 h-4 bg-[#EE0434] text-white text-[10px] flex items-center justify-center rounded-full font-black">0</span>
+              </button>
+
+              <div className="relative" ref={langMenuRef}>
+                <button 
+                  onClick={() => setIsLangMenuOpen(!isLangMenuOpen)}
+                  className="bg-white p-1 rounded-2xl border border-slate-100 shadow-sm hover:shadow-md transition-all flex items-center justify-center"
+                >
+                  <div className="w-8 h-8 rounded-full overflow-hidden border border-slate-50">
+                    <img 
+                      src={`https://flagcdn.com/w80/${currentLangObj.flag}.png`} 
+                      alt={currentLangObj.label} 
+                      className="w-full h-full object-cover"
+                    />
+                  </div>
+                </button>
+
+                {isLangMenuOpen && (
+                  <div className="absolute top-full right-0 mt-3 w-48 bg-white rounded-[24px] shadow-[0_20px_40px_rgba(0,0,0,0.1)] border border-slate-50 overflow-hidden animate-in fade-in slide-in-from-top-2 duration-200">
+                    <div className="flex flex-col">
+                      {languages.map((lang) => (
+                        <button
+                          key={lang.code}
+                          onClick={() => {
+                            setSelectedLang(lang.code as 'en' | 'vi');
+                            setIsLangMenuOpen(false);
+                          }}
+                          className={`flex items-center space-x-3 px-5 py-4 w-full text-left transition-colors ${
+                            selectedLang === lang.code ? 'bg-slate-50' : 'hover:bg-slate-50/50'
+                          }`}
+                        >
+                          <div className="w-7 h-7 rounded-full overflow-hidden border border-slate-100 shadow-sm">
+                            <img 
+                              src={`https://flagcdn.com/w80/${lang.flag}.png`} 
+                              alt={lang.label} 
+                              className="w-full h-full object-cover"
+                            />
+                          </div>
+                          <span className={`text-[15px] font-bold ${selectedLang === lang.code ? 'text-slate-900' : 'text-slate-500'}`}>
+                            {lang.label}
+                          </span>
+                        </button>
+                      ))}
+                    </div>
+                  </div>
+                )}
+              </div>
+
+              <button onClick={() => setIsMenuOpen(true)} className="lg:hidden p-2 text-slate-900">
+                <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M4 6h16M4 12h16m-7 6h7" /></svg>
+              </button>
+            </div>
+          </div>
+        </div>
+      </header>
+
+      {/* Full-Screen Mobile Menu with Slide-Right Transition */}
+      <div className={`fixed inset-0 z-[100] lg:hidden transition-all duration-500 ease-in-out ${isMenuOpen ? 'opacity-100 pointer-events-auto' : 'opacity-0 pointer-events-none'}`}>
+        <div className={`absolute inset-0 bg-white flex flex-col transform transition-transform duration-500 ease-out ${isMenuOpen ? 'translate-x-0' : '-translate-x-full'}`}>
+          {/* Mobile Menu Header */}
+          <div className="flex justify-between items-center p-6 border-b border-slate-50">
+            <Link to="/" onClick={() => setIsMenuOpen(false)} className="flex items-center space-x-1">
+              <svg className="w-7 h-7 text-[#EE0434]" viewBox="0 0 24 24" fill="currentColor">
+                <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
+              </svg>
+              <span className="text-xl font-black tracking-tighter">
+                <span className="text-[#EE0434]">Infi</span>
+                <span className="text-[#333]">Gate</span>
+              </span>
+            </Link>
+            <button onClick={() => setIsMenuOpen(false)} className="p-2 text-slate-400 hover:text-[#EE0434] transition-colors rounded-full hover:bg-slate-100">
+              <svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /></svg>
+            </button>
+          </div>
+
+          <div className="flex-1 overflow-y-auto px-6 py-8 space-y-6">
+            <div className="flex flex-col items-center space-y-4">
+              <Link 
+                to="/" 
+                onClick={() => setIsMenuOpen(false)}
+                className={`w-full text-center py-5 px-6 rounded-3xl text-2xl font-black transition-all ${isActive('/') ? 'bg-red-50 text-[#EE0434]' : 'text-slate-800 hover:bg-slate-50'}`}
+              >
+                Home
+              </Link>
+              
+              <div className="w-full">
+                <button 
+                  onClick={() => setIsBuySimExpanded(!isBuySimExpanded)}
+                  className={`w-full flex items-center justify-center space-x-3 py-5 px-6 rounded-3xl text-2xl font-black transition-all ${isActive('/buy-sim') ? 'bg-red-50 text-[#EE0434]' : 'text-slate-800 hover:bg-slate-50'}`}
+                >
+                  <span>Buy SIM</span>
+                  <svg className={`w-6 h-6 transition-transform duration-300 ${isBuySimExpanded ? 'rotate-180' : ''}`} fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M19 9l-7 7-7-7" /></svg>
+                </button>
+                <div className={`overflow-hidden transition-all duration-300 ease-in-out ${isBuySimExpanded ? 'max-h-[1000px] opacity-100 mt-4' : 'max-h-0 opacity-0'}`}>
+                  <div className="grid grid-cols-2 gap-3 px-2">
+                    <button 
+                      onClick={() => { navigate('/buy-sim'); setIsMenuOpen(false); }}
+                      className="col-span-2 text-center py-4 bg-slate-50 rounded-2xl text-[#EE0434] font-black text-sm uppercase tracking-wider shadow-sm"
+                    >
+                      View All Destinations →
+                    </button>
+                    {countries.map(c => (
+                      <button 
+                        key={c.name}
+                        onClick={() => handleCountryClick(c)}
+                        className="flex flex-col items-center justify-center space-y-2 py-5 rounded-2xl bg-white border border-slate-100 shadow-sm active:bg-slate-50"
+                      >
+                        <img src={`https://flagcdn.com/w80/${c.flag}.png`} alt={c.name} className="w-10 h-10 rounded-full object-cover border-2 border-slate-50" />
+                        <span className="text-sm font-bold text-slate-700">{c.name}</span>
+                      </button>
+                    ))}
+                  </div>
+                </div>
+              </div>
+
+              <div className="w-full">
+                <button 
+                  onClick={() => setIsGuideExpanded(!isGuideExpanded)}
+                  className={`w-full flex items-center justify-center space-x-3 py-5 px-6 rounded-3xl text-2xl font-black transition-all ${isActive('/support') ? 'bg-red-50 text-[#EE0434]' : 'text-slate-800 hover:bg-slate-50'}`}
+                >
+                  <span>Guide</span>
+                  <svg className={`w-6 h-6 transition-transform duration-300 ${isGuideExpanded ? 'rotate-180' : ''}`} fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M19 9l-7 7-7-7" /></svg>
+                </button>
+                <div className={`overflow-hidden transition-all duration-300 ease-in-out ${isGuideExpanded ? 'max-h-[400px] opacity-100 mt-4' : 'max-h-0 opacity-0'}`}>
+                  <div className="flex flex-col space-y-2 px-2">
+                    {guideItems.map(item => (
+                      <button 
+                        key={item.label}
+                        onClick={() => { navigate(item.path); setIsMenuOpen(false); }}
+                        className="w-full text-center py-4 rounded-2xl bg-slate-50 text-slate-600 font-bold hover:text-[#EE0434] active:bg-red-50"
+                      >
+                        {item.label}
+                      </button>
+                    ))}
+                  </div>
+                </div>
+              </div>
+
+              <Link 
+                to="/news" 
+                onClick={() => setIsMenuOpen(false)}
+                className={`w-full text-center py-5 px-6 rounded-3xl text-2xl font-black transition-all ${isActive('/news') ? 'bg-red-50 text-[#EE0434]' : 'text-slate-800 hover:bg-slate-50'}`}
+              >
+                News
+              </Link>
+              
+              <Link 
+                to="/contact" 
+                onClick={() => setIsMenuOpen(false)}
+                className={`w-full text-center py-5 px-6 rounded-3xl text-2xl font-black transition-all ${isActive('/contact') ? 'bg-red-50 text-[#EE0434]' : 'text-slate-800 hover:bg-slate-50'}`}
+              >
+                Contact
+              </Link>
+
+              <div className="w-full pt-4">
+                 <p className="text-center text-slate-400 font-bold text-xs uppercase tracking-widest mb-4">Select Language</p>
+                 <div className="flex justify-center space-x-4">
+                    {languages.map(lang => (
+                      <button
+                        key={lang.code}
+                        onClick={() => setSelectedLang(lang.code as 'en' | 'vi')}
+                        className={`flex items-center space-x-2 px-4 py-2 rounded-2xl transition-all border ${selectedLang === lang.code ? 'bg-red-50 border-[#EE0434] text-[#EE0434]' : 'bg-white border-slate-100 text-slate-500'}`}
+                      >
+                        <img src={`https://flagcdn.com/w40/${lang.flag}.png`} alt={lang.label} className="w-6 h-6 rounded-full object-cover border border-slate-100" />
+                        <span className="font-bold">{lang.label}</span>
+                      </button>
+                    ))}
+                 </div>
+              </div>
+            </div>
+          </div>
+
+          <div className="p-8 border-t border-slate-50 bg-slate-50/50">
+            <Link 
+              to="/login"
+              onClick={() => setIsMenuOpen(false)}
+              className="w-full bg-gradient-to-r from-[#E21c34] to-[#500B28] text-white py-5 rounded-[40px] font-black text-2xl shadow-xl active:scale-[0.98] transition-all flex justify-center"
+            >
+              Login / Register
+            </Link>
+          </div>
+        </div>
+      </div>
+    </>
+  );
+};
+
+export default Header;

+ 141 - 0
EsimLao/esim-vite/src/components/ImageView.tsx

@@ -0,0 +1,141 @@
+import React, { useState } from "react";
+import { generateImage } from "../services/gemini";
+import { GeneratedImage } from "../services/types";
+
+const ImageView: React.FC = () => {
+  const [prompt, setPrompt] = useState("");
+  const [loading, setLoading] = useState(false);
+  const [history, setHistory] = useState<GeneratedImage[]>([]);
+  const [aspectRatio, setAspectRatio] = useState<"1:1" | "16:9" | "9:16">(
+    "1:1"
+  );
+
+  const handleGenerate = async () => {
+    if (!prompt.trim() || loading) return;
+    setLoading(true);
+    try {
+      const url = await generateImage(prompt, aspectRatio);
+      setHistory((prev) => [{ url, prompt, timestamp: new Date() }, ...prev]);
+    } catch (error) {
+      console.error(error);
+      alert("Asset generation failed. Please refine your parameters.");
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  return (
+    <div className="flex flex-col h-full bg-slate-50">
+      <div className="p-10 bg-white border-b border-slate-200 shadow-sm z-10">
+        <div className="max-w-7xl mx-auto flex flex-col space-y-6">
+          <div>
+            <h2 className="text-3xl font-black text-slate-900 tracking-tight">
+              Image Forge
+            </h2>
+            <p className="text-slate-500 font-medium">
+              Generate high-fidelity visual assets for your brand and projects.
+            </p>
+          </div>
+          <div className="flex flex-col lg:flex-row gap-4">
+            <div className="flex-1 relative group">
+              <input
+                type="text"
+                value={prompt}
+                onChange={(e) => setPrompt(e.target.value)}
+                placeholder="High-resolution architectural render of a modern workspace..."
+                className="w-full bg-slate-50 text-slate-800 rounded-2xl px-6 py-4 focus:outline-none focus:ring-4 focus:ring-blue-500/10 border border-slate-200 text-lg transition-all"
+              />
+            </div>
+            <div className="flex gap-4">
+              <select
+                value={aspectRatio}
+                onChange={(e: any) => setAspectRatio(e.target.value)}
+                className="bg-white text-slate-700 border border-slate-200 rounded-2xl px-6 py-4 outline-none focus:ring-2 focus:ring-[#004ea8] font-semibold text-sm shadow-sm cursor-pointer hover:bg-slate-50 transition-colors"
+              >
+                <option value="1:1">Standard (1:1)</option>
+                <option value="16:9">Widescreen (16:9)</option>
+                <option value="9:16">Mobile (9:16)</option>
+              </select>
+              <button
+                onClick={handleGenerate}
+                disabled={loading}
+                className="bg-[#004ea8] hover:bg-[#003d82] disabled:opacity-50 text-white rounded-2xl px-10 py-4 font-bold transition-all shadow-xl active:scale-95 whitespace-nowrap"
+              >
+                {loading ? "Synthesizing..." : "Generate Assets"}
+              </button>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <div className="flex-1 overflow-y-auto p-10">
+        <div className="max-w-7xl mx-auto">
+          {history.length === 0 && !loading && (
+            <div className="h-[50vh] flex flex-col items-center justify-center opacity-40">
+              <div className="p-10 bg-white rounded-[40px] shadow-2xl border border-slate-100 mb-8">
+                <svg
+                  className="w-24 h-24 text-slate-200"
+                  fill="none"
+                  stroke="currentColor"
+                  viewBox="0 0 24 24"
+                >
+                  <path
+                    strokeLinecap="round"
+                    strokeLinejoin="round"
+                    strokeWidth={1}
+                    d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
+                  />
+                </svg>
+              </div>
+              <p className="text-2xl font-bold text-slate-400">
+                Project Gallery Empty
+              </p>
+              <p className="text-slate-300 mt-2">
+                Initialize generation to view results.
+              </p>
+            </div>
+          )}
+
+          <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-10">
+            {loading && (
+              <div className="aspect-square bg-white border border-slate-200 rounded-[32px] flex flex-col items-center justify-center shadow-xl animate-pulse">
+                <div className="w-16 h-16 border-4 border-[#004ea8] border-t-transparent rounded-full animate-spin"></div>
+                <p className="mt-6 text-slate-500 font-bold uppercase tracking-widest text-xs">
+                  Computing Vision...
+                </p>
+              </div>
+            )}
+            {history.map((img, i) => (
+              <div
+                key={i}
+                className="group relative bg-white border border-slate-200 rounded-[32px] overflow-hidden shadow-lg hover:shadow-2xl transition-all hover:scale-[1.03]"
+              >
+                <img
+                  src={img.url}
+                  alt={img.prompt}
+                  className="w-full h-auto object-cover"
+                />
+                <div className="absolute inset-0 bg-gradient-to-t from-[#004ea8]/90 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-all p-8 flex flex-col justify-end">
+                  <div className="bg-white/10 backdrop-blur-xl p-4 rounded-2xl border border-white/20">
+                    <p className="text-sm text-white font-semibold line-clamp-2">
+                      "{img.prompt}"
+                    </p>
+                    <a
+                      href={img.url}
+                      download={`asset-${i}.png`}
+                      className="mt-4 block w-full bg-white text-[#004ea8] text-center py-3 rounded-xl text-xs font-black uppercase tracking-widest transition-colors hover:bg-slate-100"
+                    >
+                      Export Resolution
+                    </a>
+                  </div>
+                </div>
+              </div>
+            ))}
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default ImageView;

+ 119 - 0
EsimLao/esim-vite/src/components/SearchView.tsx

@@ -0,0 +1,119 @@
+import React, { useState } from "react";
+import { searchGrounding } from "../services/gemini";
+import { GroundingSource } from "../services/types";
+
+const SearchView: React.FC = () => {
+  const [query, setQuery] = useState("");
+  const [loading, setLoading] = useState(false);
+  const [result, setResult] = useState<{
+    text: string;
+    sources: GroundingSource[];
+  } | null>(null);
+
+  const handleSearch = async () => {
+    if (!query.trim() || loading) return;
+    setLoading(true);
+    setResult(null);
+    try {
+      const data = await searchGrounding(query);
+      setResult(data);
+    } catch (error) {
+      console.error(error);
+      alert("Search failed. Try a different query.");
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  return (
+    <div className="flex flex-col h-full bg-gray-950">
+      <div className="p-12 max-w-4xl mx-auto w-full">
+        <div className="text-center mb-12">
+          <h2 className="text-4xl font-extrabold mb-4 bg-gradient-to-r from-blue-400 to-emerald-400 bg-clip-text text-transparent">
+            Insight Engine
+          </h2>
+          <p className="text-gray-400 text-lg">
+            Real-time web search powered by Google Grounding.
+          </p>
+        </div>
+
+        <div className="relative group">
+          <input
+            type="text"
+            value={query}
+            onChange={(e) => setQuery(e.target.value)}
+            onKeyDown={(e) => e.key === "Enter" && handleSearch()}
+            placeholder="What are the major highlights of the tech industry today?"
+            className="w-full bg-gray-900/50 text-xl text-gray-100 rounded-3xl px-8 py-6 focus:outline-none focus:ring-4 focus:ring-blue-500/20 border-2 border-gray-800 group-hover:border-gray-700 transition-all placeholder-gray-700 shadow-2xl"
+          />
+          <button
+            onClick={handleSearch}
+            disabled={loading}
+            className="absolute right-4 top-4 bottom-4 bg-blue-600 hover:bg-blue-500 disabled:bg-gray-800 text-white rounded-2xl px-8 font-bold transition-colors shadow-lg"
+          >
+            {loading ? "Thinking..." : "Search"}
+          </button>
+        </div>
+
+        {loading && (
+          <div className="mt-20 flex flex-col items-center space-y-4">
+            <div className="flex space-x-2">
+              <div className="w-3 h-3 bg-blue-500 rounded-full animate-ping"></div>
+              <div className="w-3 h-3 bg-blue-500 rounded-full animate-ping [animation-delay:0.2s]"></div>
+              <div className="w-3 h-3 bg-blue-500 rounded-full animate-ping [animation-delay:0.4s]"></div>
+            </div>
+            <p className="text-gray-500 animate-pulse font-medium">
+              Scouring the web for real-time information...
+            </p>
+          </div>
+        )}
+
+        {result && (
+          <div className="mt-12 space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500">
+            <div className="bg-gray-900/80 border border-gray-800 p-8 rounded-3xl shadow-xl leading-relaxed text-gray-200">
+              <div className="prose prose-invert max-w-none">
+                <p className="whitespace-pre-wrap text-lg">{result.text}</p>
+              </div>
+            </div>
+
+            {result.sources.length > 0 && (
+              <div className="space-y-3">
+                <h3 className="text-sm font-bold uppercase tracking-widest text-gray-500 px-2">
+                  Verified Sources
+                </h3>
+                <div className="flex flex-wrap gap-3">
+                  {result.sources.map((source, i) => (
+                    <a
+                      key={i}
+                      href={source.uri}
+                      target="_blank"
+                      rel="noopener noreferrer"
+                      className="bg-gray-900 border border-gray-800 hover:border-blue-500/50 hover:bg-gray-800 px-4 py-2 rounded-xl text-sm text-gray-300 transition-all flex items-center space-x-2"
+                    >
+                      <svg
+                        className="w-4 h-4 text-blue-400"
+                        fill="none"
+                        stroke="currentColor"
+                        viewBox="0 0 24 24"
+                      >
+                        <path
+                          strokeLinecap="round"
+                          strokeLinejoin="round"
+                          strokeWidth={2}
+                          d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
+                        />
+                      </svg>
+                      <span className="font-medium">{source.title}</span>
+                    </a>
+                  ))}
+                </div>
+              </div>
+            )}
+          </div>
+        )}
+      </div>
+    </div>
+  );
+};
+
+export default SearchView;

+ 69 - 0
EsimLao/esim-vite/src/components/Sidebar.tsx

@@ -0,0 +1,69 @@
+
+import React from 'react';
+import { useLocation, Link } from 'react-router-dom';
+
+const Sidebar: React.FC = () => {
+  const location = useLocation();
+  const path = location.pathname;
+
+  const menuItems = [
+    { id: '/ai/chat', label: 'Intelligent Chat', icon: 'M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z' },
+    { id: '/ai/image', label: 'Image Forge', icon: 'M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z' },
+    { id: '/ai/video', label: 'Cinematic Video', icon: 'M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z' },
+    { id: '/ai/search', label: 'Knowledge Base', icon: 'M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z' },
+  ];
+
+  return (
+    <div className="w-72 bg-white border-r border-slate-200 flex flex-col h-full z-20 shadow-xl">
+      <div className="p-8">
+        <h1 className="text-2xl font-black text-[#EE0434] tracking-tight">
+          LUMINA
+        </h1>
+        <div className="h-1 w-12 bg-[#EE0434] mt-1 rounded-full"></div>
+        <p className="text-[10px] text-slate-400 mt-3 uppercase tracking-[0.2em] font-bold">Solutions Platform</p>
+      </div>
+
+      <nav className="flex-1 px-4 space-y-1 mt-2">
+        {menuItems.map((item) => (
+          <Link
+            key={item.id}
+            to={item.id}
+            className={`w-full flex items-center space-x-3 px-6 py-4 rounded-xl transition-all duration-300 ${
+              path === item.id
+                ? 'bg-red-50 text-[#EE0434] font-semibold shadow-inner'
+                : 'text-slate-500 hover:bg-red-50/50 hover:text-[#EE0434]'
+            }`}
+          >
+            <svg
+              className={`w-6 h-6 transition-colors ${path === item.id ? 'text-[#EE0434]' : 'text-slate-300'}`}
+              fill="none"
+              stroke="currentColor"
+              viewBox="0 0 24 24"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d={item.icon} />
+            </svg>
+            <span className="text-sm">{item.label}</span>
+            {path === item.id && (
+                <div className="ml-auto w-1.5 h-6 bg-[#EE0434] rounded-full"></div>
+            )}
+          </Link>
+        ))}
+      </nav>
+
+      <div className="p-6 mt-auto">
+        <div className="bg-slate-50 p-4 rounded-2xl border border-slate-100 flex items-center space-x-3 shadow-sm hover:shadow-md transition-shadow cursor-pointer">
+          <div className="w-10 h-10 rounded-xl bg-[#EE0434] flex items-center justify-center text-white font-bold shadow-lg">
+            S
+          </div>
+          <div className="flex-1 min-w-0">
+            <p className="text-sm font-bold text-slate-800 truncate">Senior Executive</p>
+            <p className="text-[10px] text-[#EE0434] font-bold uppercase tracking-wider">Enterprise Pro</p>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default Sidebar;

+ 34 - 0
EsimLao/esim-vite/src/components/TestimonialCard.tsx

@@ -0,0 +1,34 @@
+import React, { useState, useEffect, useCallback } from "react";
+import { TestimonialType } from "../services/types";
+
+const TestimonialCard: React.FC<{ item: TestimonialType }> = ({ item }) => (
+  <div className="bg-white/10 backdrop-blur-md border border-white/20 rounded-[24px] md:rounded-[32px] p-5 md:p-6 min-w-[280px] md:min-w-[320px] max-w-[340px] shadow-xl text-white flex flex-col space-y-4">
+    <div className="flex items-center space-x-3">
+      <img
+        src={item.avatar}
+        alt={item.name}
+        className="w-10 h-10 md:w-12 md:h-12 rounded-full border-2 border-white/50 object-cover"
+      />
+      <div>
+        <h4 className="font-bold text-sm md:text-lg">{item.name}</h4>
+        <p className="text-white/60 text-[10px] md:text-xs">{item.location}</p>
+      </div>
+    </div>
+    <div className="flex space-x-1">
+      {[...Array(item.rating)].map((_, i) => (
+        <svg
+          key={i}
+          className="w-3.5 h-3.5 md:w-4 md:h-4 text-yellow-400"
+          fill="currentColor"
+          viewBox="0 0 20 20"
+        >
+          <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
+        </svg>
+      ))}
+    </div>
+    <p className="text-xs md:text-sm font-medium leading-relaxed italic text-white/90 whitespace-normal">
+      "{item.content}"
+    </p>
+  </div>
+);
+export default TestimonialCard;

+ 31 - 0
EsimLao/esim-vite/src/components/TopLoader.tsx

@@ -0,0 +1,31 @@
+import React from "react";
+
+const TopLoader: React.FC<{ visible: boolean }> = ({ visible }) => {
+  return (
+    <div
+      className={`fixed top-0 left-0 w-full h-[3px] z-[9999] pointer-events-none transition-opacity duration-500 ${
+        visible ? "opacity-100" : "opacity-0"
+      }`}
+    >
+      {/* Primary loading bar */}
+      <div className="absolute inset-0 bg-[#EE0434] shadow-[0_0_15px_rgba(238,4,52,0.6)] animate-[progress-fill_2s_infinite_ease-in-out]" />
+
+      {/* Secondary shimmer effect for a more active feel */}
+      <div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/40 to-transparent w-1/2 animate-[shimmer-flow_1.5s_infinite_linear]" />
+
+      <style>{`
+        @keyframes progress-fill {
+          0% { width: 0%; left: 0%; }
+          50% { width: 70%; left: 15%; }
+          100% { width: 0%; left: 100%; }
+        }
+        @keyframes shimmer-flow {
+          0% { transform: translateX(-100%); }
+          100% { transform: translateX(200%); }
+        }
+      `}</style>
+    </div>
+  );
+};
+
+export default TopLoader;

+ 191 - 0
EsimLao/esim-vite/src/components/VideoView.tsx

@@ -0,0 +1,191 @@
+import React, { useState, useEffect } from "react";
+import { generateVideo } from "../services/gemini";
+import { GeneratedVideo } from "../services/types";
+
+const VideoView: React.FC = () => {
+  const [prompt, setPrompt] = useState("");
+  const [loading, setLoading] = useState(false);
+  const [history, setHistory] = useState<GeneratedVideo[]>([]);
+  const [hasKey, setHasKey] = useState(false);
+  const [aspectRatio, setAspectRatio] = useState<"16:9" | "9:16">("16:9");
+
+  useEffect(() => {
+    const checkKey = async () => {
+      // @ts-ignore
+      const ok = await window.aistudio.hasSelectedApiKey();
+      setHasKey(ok);
+    };
+    checkKey();
+  }, []);
+
+  const handleSelectKey = async () => {
+    // @ts-ignore
+    await window.aistudio.openSelectKey();
+    setHasKey(true);
+  };
+
+  const handleGenerate = async () => {
+    if (!prompt.trim() || loading) return;
+    setLoading(true);
+    try {
+      const url = await generateVideo(prompt, aspectRatio);
+      setHistory((prev) => [{ url, prompt, timestamp: new Date() }, ...prev]);
+    } catch (error) {
+      console.error(error);
+      alert(
+        "Video generation encountered an error. Veo might be busy or key issues."
+      );
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  if (!hasKey) {
+    return (
+      <div className="flex flex-col items-center justify-center h-full bg-gray-950 p-12 text-center">
+        <div className="w-20 h-20 bg-yellow-500/10 rounded-full flex items-center justify-center mb-6">
+          <svg
+            className="w-10 h-10 text-yellow-500"
+            fill="none"
+            stroke="currentColor"
+            viewBox="0 0 24 24"
+          >
+            <path
+              strokeLinecap="round"
+              strokeLinejoin="round"
+              strokeWidth={2}
+              d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
+            />
+          </svg>
+        </div>
+        <h2 className="text-3xl font-bold mb-4">Activation Required</h2>
+        <p className="text-gray-400 max-w-md mb-8">
+          To access the professional-grade Veo 3.1 video engine, you must select
+          an API key from a billing-enabled Google Cloud project.
+        </p>
+        <div className="flex flex-col space-y-4">
+          <button
+            onClick={handleSelectKey}
+            className="bg-blue-600 hover:bg-blue-500 text-white px-8 py-3 rounded-xl font-bold transition-all shadow-xl"
+          >
+            Select Paid API Key
+          </button>
+          <a
+            href="https://ai.google.dev/gemini-api/docs/billing"
+            target="_blank"
+            className="text-sm text-blue-400 hover:underline"
+          >
+            Learn about billing →
+          </a>
+        </div>
+      </div>
+    );
+  }
+
+  return (
+    <div className="flex flex-col h-full bg-gray-950">
+      <div className="p-8 border-b border-gray-900 flex flex-col space-y-4">
+        <div className="flex items-center justify-between">
+          <h2 className="text-2xl font-bold">Cinematic Video</h2>
+          <span className="bg-indigo-500/10 text-indigo-400 text-[10px] uppercase tracking-widest px-2 py-1 rounded font-bold border border-indigo-500/20">
+            Veo 3.1 Pro
+          </span>
+        </div>
+        <div className="flex flex-col md:flex-row gap-4">
+          <textarea
+            rows={2}
+            value={prompt}
+            onChange={(e) => setPrompt(e.target.value)}
+            placeholder="A slow-motion tracking shot of a silver jaguar running through a dark neon forest..."
+            className="flex-1 bg-gray-900 text-gray-100 rounded-xl px-5 py-3 focus:outline-none focus:ring-2 focus:ring-blue-500/50 border border-gray-800 resize-none"
+          />
+          <div className="flex flex-col space-y-2">
+            <select
+              value={aspectRatio}
+              onChange={(e: any) => setAspectRatio(e.target.value)}
+              className="bg-gray-900 text-gray-200 border border-gray-800 rounded-xl px-4 py-2 outline-none"
+            >
+              <option value="16:9">Landscape (16:9)</option>
+              <option value="9:16">Portrait (9:16)</option>
+            </select>
+            <button
+              onClick={handleGenerate}
+              disabled={loading}
+              className="flex-1 bg-gradient-to-r from-indigo-600 to-purple-600 hover:from-indigo-500 hover:to-purple-500 disabled:opacity-50 text-white rounded-xl px-8 py-3 font-semibold transition-all shadow-lg"
+            >
+              {loading ? "Synthesizing..." : "Generate 720p"}
+            </button>
+          </div>
+        </div>
+        {loading && (
+          <div className="bg-gray-900 border border-gray-800 rounded-xl p-4 animate-pulse flex items-center space-x-4">
+            <div className="w-8 h-8 border-4 border-indigo-500 border-t-transparent rounded-full animate-spin"></div>
+            <div>
+              <p className="text-sm font-medium text-gray-300">
+                Wait, magic in progress...
+              </p>
+              <p className="text-xs text-gray-500">
+                Video generation typically takes 2-4 minutes. Do not close this
+                tab.
+              </p>
+            </div>
+          </div>
+        )}
+      </div>
+
+      <div className="flex-1 overflow-y-auto p-8 grid grid-cols-1 xl:grid-cols-2 gap-8">
+        {history.map((video, idx) => (
+          <div
+            key={idx}
+            className="bg-gray-900 border border-gray-800 rounded-2xl overflow-hidden shadow-2xl"
+          >
+            <video
+              controls
+              src={video.url}
+              className="w-full h-auto aspect-video bg-black"
+            />
+            <div className="p-4">
+              <p className="text-sm text-gray-400 font-medium line-clamp-2">
+                "{video.prompt}"
+              </p>
+              <div className="mt-4 flex justify-between items-center">
+                <span className="text-xs text-gray-600">
+                  {video.timestamp.toLocaleDateString()}
+                </span>
+                <a
+                  href={video.url}
+                  download
+                  className="text-xs text-blue-400 font-bold hover:underline"
+                >
+                  Export MP4
+                </a>
+              </div>
+            </div>
+          </div>
+        ))}
+        {history.length === 0 && !loading && (
+          <div className="col-span-full h-full flex flex-col items-center justify-center opacity-20 py-20">
+            <svg
+              className="w-24 h-24"
+              fill="none"
+              stroke="currentColor"
+              viewBox="0 0 24 24"
+            >
+              <path
+                strokeLinecap="round"
+                strokeLinejoin="round"
+                strokeWidth={1}
+                d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"
+              />
+            </svg>
+            <p className="mt-4 text-xl font-medium">
+              Ready to produce professional cinema?
+            </p>
+          </div>
+        )}
+      </div>
+    </div>
+  );
+};
+
+export default VideoView;

+ 22 - 0
EsimLao/esim-vite/src/features/areas/areasSlice.ts

@@ -0,0 +1,22 @@
+import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+import loadingSlice from '../loading/loadingSlice';
+import { AreaData } from '../../services/product/type';
+
+const areaSlice = createSlice({
+    name: 'areas',
+    initialState: {
+        areas: [] as AreaData[],
+    },
+    reducers: {
+        setAreas: (state, action: PayloadAction<any>) => {
+            console.log("Setting areas state to: ", action.payload);
+            return {
+                ...state,
+                areas: action.payload,
+            };
+        },
+    }
+});
+
+export const { setAreas } = areaSlice.actions;
+export default areaSlice.reducer;

+ 23 - 0
EsimLao/esim-vite/src/features/loading/loadingSlice.ts

@@ -0,0 +1,23 @@
+import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+
+const loadingSlice = createSlice({
+    name: 'loading',
+    initialState: {
+        isLoading: false,
+        message: 'Loading ...',
+    },
+    reducers: {
+        startLoading: (state, action: PayloadAction<any>) => {
+            console.log("Setting animation enabled state to: ", action.payload);
+            return {
+                ...state,
+                isLoading: true,
+                message: action.payload?.message ?? 'Loading ...',
+            };
+        },
+        stopLoading: state => { state.isLoading = false }
+    }
+});
+
+export const { startLoading, stopLoading } = loadingSlice.actions;
+export default loadingSlice.reducer;

+ 5 - 0
EsimLao/esim-vite/src/hooks/useRedux.ts

@@ -0,0 +1,5 @@
+import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
+import type { RootState, AppDispatch } from '../app/store';
+
+export const useAppDispatch = () => useDispatch<AppDispatch>();
+export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

+ 24 - 0
EsimLao/esim-vite/src/index.tsx

@@ -0,0 +1,24 @@
+import React from "react";
+import ReactDOM from "react-dom/client";
+import { BrowserRouter } from "react-router-dom";
+import App from "./App";
+import { store } from "./app/store";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { Provider } from "react-redux";
+
+const rootElement = document.getElementById("root");
+if (!rootElement) {
+  throw new Error("Could not find root element to mount to");
+}
+
+const queryClient = new QueryClient();
+const root = ReactDOM.createRoot(rootElement);
+root.render(
+  <QueryClientProvider client={queryClient}>
+    <Provider store={store}>
+      <BrowserRouter>
+        <App />
+      </BrowserRouter>
+    </Provider>
+  </QueryClientProvider>
+);

+ 79 - 0
EsimLao/esim-vite/src/pages/buy-sim/BuySimView.tsx

@@ -0,0 +1,79 @@
+
+import React from 'react';
+import { useNavigate, Link } from 'react-router-dom';
+import { SimProduct } from '../types';
+
+const BuySimView: React.FC = () => {
+  const navigate = useNavigate();
+  const popularSims: SimProduct[] = [
+    { id: 'p1', country: 'China', flag: 'cn', originalPrice: '$0.94', discountPrice: '$0.85', discountLabel: '-10%' },
+    { id: 'p2', country: 'Hong Kong', flag: 'hk', originalPrice: '$4.43', discountPrice: '$4.21', discountLabel: '-5%' },
+    { id: 'p3', country: 'Japan', flag: 'jp', originalPrice: '$1.44', discountPrice: '$1.3', discountLabel: '-10%' },
+    { id: 'p4', country: 'Singapore', flag: 'sg', originalPrice: '$4.43', discountPrice: '$4.21', discountLabel: '-5%' },
+    { id: 'p5', country: 'South Korea', flag: 'kr', originalPrice: '$0.94', discountPrice: '$0.85', discountLabel: '-10%' },
+    { id: 'p6', country: 'Taiwan', flag: 'tw', originalPrice: '$2.04', discountPrice: '$1.84', discountLabel: '-10%' },
+    { id: 'p7', country: 'Thailand', flag: 'th', originalPrice: '$1.89', discountPrice: '$1.78', discountLabel: '-6%' },
+    { id: 'p8', country: 'United States', flag: 'us', originalPrice: '$2.16', discountPrice: '$1.94', discountLabel: '-10%' },
+  ];
+
+  const handleSelect = (p: SimProduct) => {
+    navigate(`/product/${p.country.toLowerCase()}`, { state: {
+      country: p.country,
+      flag: p.flag,
+      image: p.isRegion 
+        ? 'https://images.unsplash.com/photo-1526772662000-3f88f10c053b?q=80&w=1000&auto=format&fit=crop'
+        : (p.country === 'Thailand' 
+           ? 'https://images.unsplash.com/photo-1528181304800-2f140819898f?q=80&w=1000&auto=format&fit=crop' 
+           : `https://images.unsplash.com/photo-1526772662000-3f88f10c053b?q=80&w=1000&auto=format&fit=crop`)
+    }});
+  };
+
+  const ProductCard: React.FC<{ p: SimProduct }> = ({ p }) => (
+    <div 
+      onClick={() => handleSelect(p)}
+      className="bg-white border border-slate-100 rounded-[20px] p-4 md:p-6 shadow-sm hover:shadow-lg transition-all relative flex items-center space-x-4 group cursor-pointer"
+    >
+      <div className="absolute top-0 right-0 bg-[#EE0434] text-white text-[10px] font-black px-3 py-1.5 rounded-tr-[19px] rounded-bl-[14px]">
+        {p.discountLabel}
+      </div>
+      <div className="w-12 h-12 rounded-full overflow-hidden shrink-0 border border-slate-50 bg-slate-50">
+        <img src={`https://flagcdn.com/w160/${p.flag}.png`} alt={p.country} className="w-full h-full object-cover scale-150" />
+      </div>
+      <div className="flex-1">
+        <h3 className="text-slate-900 font-black text-base md:text-lg mb-0.5 group-hover:text-[#EE0434] transition-colors">{p.country}</h3>
+        <p className="text-[#EE0434] font-bold text-sm md:text-lg">from: <span className="font-black">{p.discountPrice}</span></p>
+      </div>
+    </div>
+  );
+
+  return (
+    <div className="bg-[#fcfdfe] min-h-screen">
+      <div className="max-w-7xl mx-auto px-4 py-4 md:py-6 border-b border-slate-50">
+        <nav className="flex items-center space-x-2 text-xs md:text-sm text-slate-500">
+          <Link to="/" className="hover:text-[#EE0434]">Home</Link>
+          <svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M9 5l7 7-7 7" strokeWidth={2.5} strokeLinecap="round" strokeLinejoin="round"/></svg>
+          <span className="text-slate-900 font-bold">Buy Sim</span>
+        </nav>
+      </div>
+
+      <div className="max-w-7xl mx-auto px-4 py-8 md:py-12">
+        <div className="flex justify-center mb-12 md:mb-20">
+          <div className="w-full max-w-3xl relative group">
+            <input type="text" placeholder="Choose country you're going to" className="w-full bg-white rounded-full py-4 md:py-6 px-10 md:px-12 text-base md:text-xl text-slate-700 shadow-sm border border-slate-100 outline-none focus:ring-4 focus:ring-red-50 transition-all placeholder:text-slate-300" />
+            <button className="absolute right-2 md:right-3 top-2 md:top-3 bottom-2 md:bottom-3 aspect-square bg-[#EE0434] rounded-full flex items-center justify-center text-white shadow-lg shadow-red-200 hover:scale-105 transition-all">
+              <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" strokeWidth={2.5} strokeLinecap="round" strokeLinejoin="round"/></svg>
+            </button>
+          </div>
+        </div>
+        <section className="mb-12">
+          <h2 className="text-xl md:text-[32px] font-black text-slate-900 mb-6 md:mb-8 tracking-tight">SIM Popular</h2>
+          <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 md:gap-6">
+            {popularSims.map(p => <ProductCard key={p.id} p={p} />)}
+          </div>
+        </section>
+      </div>
+    </div>
+  );
+};
+
+export default BuySimView;

+ 41 - 0
EsimLao/esim-vite/src/pages/contact/ContactView.tsx

@@ -0,0 +1,41 @@
+
+import React from 'react';
+
+const ContactView: React.FC = () => {
+  return (
+    <div className="bg-white">
+      <section className="max-w-7xl mx-auto px-6 md:px-12 py-16 md:py-24">
+        <div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-start">
+          <div className="space-y-8">
+            <h1 className="text-5xl md:text-7xl font-black text-[#EE0434] tracking-tight leading-tight">Get in touch <br /> with us</h1>
+            <p className="text-slate-500 text-lg md:text-xl font-medium leading-relaxed max-w-lg">We're always ready to listen and support you with any questions. Leave your information, and our team will get back to you as soon as possible.</p>
+            <div className="space-y-6 pt-4">
+              <div className="space-y-2">
+                <div className="flex items-center space-x-3 text-slate-800">
+                   <svg className="w-6 h-6 text-[#EE0434]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round"/></svg>
+                   <span className="font-bold text-lg">Email</span>
+                </div>
+                <a href="mailto:info@infigate.global" className="block text-[#EE0434] font-black text-lg md:text-xl pl-9 hover:underline">info@infigate.global</a>
+              </div>
+            </div>
+          </div>
+          <div className="bg-white p-2">
+            <form className="space-y-8" onSubmit={(e) => e.preventDefault()}>
+              <div className="space-y-3">
+                <label className="block text-slate-800 font-bold text-lg">Full Name</label>
+                <input type="text" placeholder="Enter your full name" className="w-full bg-white border border-slate-200 rounded-xl py-4 px-6 focus:outline-none focus:ring-2 focus:ring-red-50 focus:border-[#EE0434] transition-all" />
+              </div>
+              <div className="space-y-3">
+                <label className="block text-slate-800 font-bold text-lg">Specific Requirements</label>
+                <textarea rows={6} placeholder="Describe your requirements" className="w-full bg-white border border-slate-200 rounded-2xl py-4 px-6 focus:outline-none focus:ring-2 focus:ring-red-50 focus:border-[#EE0434] transition-all" />
+              </div>
+              <button type="submit" className="w-full bg-[#EE0434] text-white py-5 rounded-[40px] font-black text-2xl shadow-lg hover:shadow-xl transition-all">Submit</button>
+            </form>
+          </div>
+        </div>
+      </section>
+    </div>
+  );
+};
+
+export default ContactView;

+ 136 - 0
EsimLao/esim-vite/src/pages/handbook/HandbookView.tsx

@@ -0,0 +1,136 @@
+
+import React from 'react';
+import { HandbookArticle } from '../types';
+
+interface HandbookViewProps {
+  onBack: () => void;
+  onArticleSelect: (article: HandbookArticle) => void;
+}
+
+const HandbookView: React.FC<HandbookViewProps> = ({ onBack, onArticleSelect }) => {
+  const articles: HandbookArticle[] = [
+    {
+      id: '1',
+      title: 'Siêu Sale Mùa Lễ Hội Cùng InfiGate – Giảm Đến 35% Khi Mua eSIM Du Lịch Quốc Tế',
+      date: '01/12/2025',
+      excerpt: 'Siêu Sale Mùa Lễ Hội Cùng InfiGate – Giảm Đến 35% Thời gian áp dụng ưu đãi: 01/12 – 31/12/2025',
+      image: 'https://images.unsplash.com/photo-1549463599-231a5401977e?q=80&w=800&auto=format&fit=crop'
+    },
+    {
+      id: '2',
+      title: 'Du lịch châu Á tháng 12: Mùa lễ hội rực rỡ và những trải nghiệm không thể bỏ lỡ – Đừng quên chuẩn bị SIM du lịch trước khi khởi hành!',
+      date: '28/11/2025',
+      excerpt: 'Tháng 12 là thời điểm lý tưởng để khám phá châu Á: từ mùa tuyết trắng Nhật – Hàn, biển ấm Đông...',
+      image: 'https://images.unsplash.com/photo-1493976040374-85c8e12f0c0e?q=80&w=800&auto=format&fit=crop'
+    },
+    {
+      id: '3',
+      title: 'InfiGate ra mắt bộ đôi eSIM & SIM du lịch – Mở rộng lựa chọn, tối ưu kết nối toàn cầu',
+      date: '27/11/2025',
+      excerpt: 'Khi nhu cầu du lịch bùng nổ mạnh mẽ, Internet trở thành “vũ khí tối thượng” cho mọi chuyến đi: dẫn...',
+      image: 'https://images.unsplash.com/photo-1526772662000-3f88f10c053b?q=80&w=800&auto=format&fit=crop'
+    },
+    {
+      id: '4',
+      title: 'Hướng dẫn cách cài đặt eSIM du lịch trên Samsung',
+      date: '14/11/2025',
+      excerpt: 'Hướng dẫn cài đặt eSIM du lịch trên Samsung nhanh và đơn giản. Áp dụng cho Galaxy S, A, Z. Gợi ý eSIM...',
+      image: 'https://images.unsplash.com/photo-1610945265064-0e34e5519bbf?q=80&w=800&auto=format&fit=crop'
+    },
+    {
+      id: '5',
+      title: 'Hướng dẫn cách cài đặt eSIM du lịch trên iPhone',
+      date: '14/11/2025',
+      excerpt: 'Hướng dẫn chi tiết cách kích hoạt eSIM trên các dòng iPhone từ XS trở lên. Giải pháp kết nối tối ưu...',
+      image: 'https://images.unsplash.com/photo-1510557880182-3d4d3cba35a5?q=80&w=800&auto=format&fit=crop'
+    },
+    {
+      id: '6',
+      title: 'Top 5 điểm đến mùa thu tuyệt đẹp tại Nhật Bản',
+      date: '05/11/2025',
+      excerpt: 'Khám phá vẻ đẹp của lá đỏ tại Kyoto, Tokyo và Hokkaido. Kinh nghiệm chuẩn bị SIM 4G tốc độ cao...',
+      image: 'https://images.unsplash.com/photo-1528114039593-4366cc08227d?q=80&w=800&auto=format&fit=crop'
+    },
+    {
+      id: '7',
+      title: 'Sự khác biệt giữa SIM vật lý và eSIM cho người đi du lịch',
+      date: '01/11/2025',
+      excerpt: 'Lựa chọn nào tốt nhất cho chuyến đi của bạn? So sánh tính tiện dụng, khả năng tương thích...',
+      image: 'https://images.unsplash.com/photo-1551817958-1110f7abe310?q=80&w=800&auto=format&fit=crop'
+    },
+    {
+      id: '8',
+      title: 'Bí kíp du lịch Thái Lan tự túc tiết kiệm',
+      date: '25/10/2025',
+      excerpt: 'Tổng hợp kinh nghiệm ăn chơi, di chuyển và đặt eSIM Thái Lan giá rẻ nhất chỉ từ 15k/ngày...',
+      image: 'https://images.unsplash.com/photo-1528181304800-2f140819898f?q=80&w=1000&auto=format&fit=crop'
+    }
+  ];
+
+  return (
+    <div className="bg-[#fcfdfe] min-h-screen">
+      {/* Breadcrumbs */}
+      <div className="max-w-7xl mx-auto px-4 py-4 md:py-6 border-b border-slate-50">
+        <nav className="flex items-center space-x-2 text-xs md:text-sm text-slate-500 font-medium">
+          <button onClick={onBack} className="hover:text-blue-500">Home</button>
+          <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+            <path d="M9 5l7 7-7 7" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round"/>
+          </svg>
+          <span className="text-slate-900 font-bold">Handbook</span>
+        </nav>
+      </div>
+
+      <div className="max-w-7xl mx-auto px-4 py-12">
+        <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 md:gap-8">
+          {articles.map((article) => (
+            <div 
+              key={article.id} 
+              onClick={() => onArticleSelect(article)}
+              className="bg-white rounded-[24px] overflow-hidden shadow-sm hover:shadow-xl transition-all duration-300 flex flex-col group cursor-pointer border border-slate-50"
+            >
+              {/* Card Image */}
+              <div className="relative aspect-[4/3] overflow-hidden">
+                <img 
+                  src={article.image} 
+                  alt={article.title} 
+                  className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110"
+                />
+              </div>
+
+              {/* Card Content */}
+              <div className="p-6 flex flex-col flex-1 space-y-3">
+                <div className="flex items-center text-slate-400 space-x-2">
+                   <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                     <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
+                   </svg>
+                   <span className="text-xs font-bold">{article.date}</span>
+                </div>
+                
+                <h3 className="text-[16px] md:text-[18px] font-black text-slate-800 leading-tight group-hover:text-[#004ea8] transition-colors line-clamp-3">
+                  {article.title}
+                </h3>
+                
+                <p className="text-xs md:text-sm text-slate-500 font-medium line-clamp-3 leading-relaxed">
+                  {article.excerpt}
+                </p>
+              </div>
+            </div>
+          ))}
+        </div>
+
+        {/* Pagination Placeholder */}
+        <div className="mt-16 flex justify-center items-center space-x-2">
+           <button className="w-10 h-10 rounded-full bg-[#004ea8] text-white font-black text-sm">1</button>
+           <button className="w-10 h-10 rounded-full bg-white text-slate-400 border border-slate-100 font-black text-sm hover:border-[#004ea8] hover:text-[#004ea8] transition-colors">2</button>
+           <button className="w-10 h-10 rounded-full bg-white text-slate-400 border border-slate-100 font-black text-sm hover:border-[#004ea8] hover:text-[#004ea8] transition-colors">
+             <svg className="w-4 h-4 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+               <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
+             </svg>
+           </button>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default HandbookView;

+ 371 - 0
EsimLao/esim-vite/src/pages/home/HomeView.tsx

@@ -0,0 +1,371 @@
+import React, { useState, useEffect, useCallback } from "react";
+import { useNavigate } from "react-router-dom";
+import { SimProduct } from "../../services/types";
+import HomeBanner from "./components/HomeBanner";
+import HomeTestimonial from "./components/HomeTestimonial";
+import HomeProduct from "./components/HomeProduct";
+import HomeSearch from "./components/HomeSearch";
+import HomeFaq from "./components/HomeFaq";
+
+const HomeView: React.FC = () => {
+  const [simType, setSimType] = useState<"eSIM" | "Physical">("eSIM");
+
+  const steps = [
+    {
+      number: "1",
+      title: "Compatible Device",
+      description: "Check if your phone supports eSIM technology.",
+    },
+    {
+      number: "2",
+      title: "Pick Your Plan",
+      description: "Select a data package for your destination.",
+    },
+    {
+      number: "3",
+      title: "Instant Activation",
+      description: "Scan the QR code and connect to high-speed data.",
+    },
+  ];
+
+  return (
+    <div className="bg-white overflow-x-hidden">
+      <style>{`
+        @keyframes marquee-up { 0% { transform: translateY(0); } 100% { transform: translateY(-50%); } }
+        @keyframes marquee-down { 0% { transform: translateY(-50%); } 100% { transform: translateY(0); } }
+        @keyframes scroll-x { 0% { transform: translateX(0); } 100% { transform: translateX(-33.33%); } }
+        @keyframes progress-bar { 0% { width: 0%; } 100% { width: 100%; } }
+        .animate-marquee-up { animation: marquee-up 40s linear infinite; }
+        .animate-marquee-down { animation: marquee-down 40s linear infinite; }
+        .animate-scroll-x { animation: scroll-x 10s linear infinite; }
+        .animate-progress { animation: progress-bar 6s linear infinite; }
+      `}</style>
+
+      {/* Main Search Section */}
+      <HomeSearch />
+
+      {/* Hero Banner */}
+      <HomeBanner />
+
+      {/* Product Selection */}
+      <HomeProduct />
+
+      {/* Customer Testimonials */}
+      <HomeTestimonial />
+
+      {/* WHY CHOOSE INFIGATE */}
+      <section className="py-16 md:py-24 bg-white px-4">
+        <div className="max-w-7xl mx-auto">
+          <h2 className="text-3xl md:text-6xl font-black text-center mb-12 md:mb-20 tracking-tight text-slate-900">
+            Why <span className="text-[#EE0434]">choose InfiGate</span>
+          </h2>
+          <div className="bg-gradient-to-br from-[#E21c34] to-[#500B28] rounded-[32px] md:rounded-[60px] p-8 md:p-16 lg:p-24 shadow-2xl relative overflow-hidden">
+            <div className="absolute top-0 right-0 w-[500px] h-[500px] bg-white/5 rounded-full blur-[100px] -translate-y-1/2 translate-x-1/2"></div>
+            <div className="absolute bottom-0 left-0 w-[300px] h-[300px] bg-black/10 rounded-full blur-[80px] translate-y-1/2 -translate-x-1/2"></div>
+
+            <div className="grid grid-cols-1 md:grid-cols-2 gap-12 md:gap-x-20 md:gap-y-16 relative z-10">
+              <div className="space-y-6">
+                <div className="flex items-center space-x-4">
+                  <div className="w-12 h-12 md:w-16 md:h-16 rounded-full bg-white/20 flex items-center justify-center shrink-0 border border-white/10 shadow-lg backdrop-blur-sm">
+                    <svg
+                      className="w-6 h-6 md:w-8 md:h-8 text-white"
+                      fill="none"
+                      stroke="currentColor"
+                      viewBox="0 0 24 24"
+                    >
+                      <path
+                        strokeLinecap="round"
+                        strokeLinejoin="round"
+                        strokeWidth={2}
+                        d="M13 10V3L4 14h7v7l9-11h-7z"
+                      />
+                    </svg>
+                  </div>
+                  <h3 className="text-lg md:text-2xl font-black text-white leading-tight">
+                    Fast – Convenient – Reasonable Cost
+                  </h3>
+                </div>
+                <ul className="space-y-4 text-sm md:text-lg text-white/90 list-disc pl-6 marker:text-red-200">
+                  <li>
+                    International travel starts from only 25,000 VND, saving up
+                    to 80% compared to traditional Roaming.
+                  </li>
+                  <li>
+                    Easily choose the suitable SIM or eSIM for various devices.
+                  </li>
+                  <li>
+                    eSIM: Receive QR via email, scan and install within 02
+                    minutes to start using immediately.
+                  </li>
+                  <li>
+                    Physical SIM: Fast nationwide delivery, compatible with all
+                    devices.
+                  </li>
+                </ul>
+              </div>
+
+              <div className="space-y-6">
+                <div className="flex items-center space-x-4">
+                  <div className="w-12 h-12 md:w-16 md:h-16 rounded-full bg-white/20 flex items-center justify-center shrink-0 border border-white/10 shadow-lg backdrop-blur-sm">
+                    <svg
+                      className="w-6 h-6 md:w-8 md:h-8 text-white"
+                      fill="none"
+                      stroke="currentColor"
+                      viewBox="0 0 24 24"
+                    >
+                      <path
+                        strokeLinecap="round"
+                        strokeLinejoin="round"
+                        strokeWidth={2}
+                        d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9"
+                      />
+                    </svg>
+                  </div>
+                  <h3 className="text-lg md:text-2xl font-black text-white leading-tight">
+                    Global Connection, Stable Anywhere
+                  </h3>
+                </div>
+                <ul className="space-y-4 text-sm md:text-lg text-white/90 list-disc pl-6 marker:text-red-200">
+                  <li>
+                    Enjoy ultra-fast 4G/5G speeds for smooth web browsing,
+                    YouTube, Facebook, and Google Maps.
+                  </li>
+                  <li>
+                    World-leading network infrastructure ensures stable
+                    connection anytime, anywhere.
+                  </li>
+                  <li>
+                    Flexible and diverse packages, suitable for personal,
+                    business, or travel needs.
+                  </li>
+                </ul>
+              </div>
+
+              <div className="space-y-6">
+                <div className="flex items-center space-x-4">
+                  <div className="w-12 h-12 md:w-16 md:h-16 rounded-full bg-white/20 flex items-center justify-center shrink-0 border border-white/10 shadow-lg backdrop-blur-sm">
+                    <svg
+                      className="w-6 h-6 md:w-8 md:h-8 text-white"
+                      fill="none"
+                      stroke="currentColor"
+                      viewBox="0 0 24 24"
+                    >
+                      <path
+                        strokeLinecap="round"
+                        strokeLinejoin="round"
+                        strokeWidth={2}
+                        d="M18.364 5.636l-3.536 3.536m0 5.656l3.536 3.536M9.172 9.172L5.636 5.636m3.536 9.192l-3.536 3.536M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-5 0a4 4 0 11-8 0 4 4 0 018 0z"
+                      />
+                    </svg>
+                  </div>
+                  <h3 className="text-lg md:text-2xl font-black text-white leading-tight">
+                    24/7 Customer Support
+                  </h3>
+                </div>
+                <ul className="space-y-4 text-sm md:text-lg text-white/90 list-disc pl-6 marker:text-red-200">
+                  <li>
+                    Our Customer Service team is available 24/7 throughout your
+                    journey.
+                  </li>
+                  <li>
+                    Support is available via multiple channels like Hotline,
+                    Zalo OA, and WhatsApp.
+                  </li>
+                </ul>
+              </div>
+
+              <div className="space-y-6">
+                <div className="flex items-center space-x-4">
+                  <div className="w-12 h-12 md:w-16 md:h-16 rounded-full bg-white/20 flex items-center justify-center shrink-0 border border-white/10 shadow-lg backdrop-blur-sm">
+                    <svg
+                      className="w-6 h-6 md:w-8 md:h-8 text-white"
+                      fill="none"
+                      stroke="currentColor"
+                      viewBox="0 0 24 24"
+                    >
+                      <path
+                        strokeLinecap="round"
+                        strokeLinejoin="round"
+                        strokeWidth={2}
+                        d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"
+                      />
+                    </svg>
+                  </div>
+                  <h3 className="text-lg md:text-2xl font-black text-white leading-tight">
+                    Customer-First Policy
+                  </h3>
+                </div>
+                <ul className="space-y-4 text-sm md:text-lg text-white/90 list-disc pl-6 marker:text-red-200">
+                  <li>
+                    Exchange for a new product within 1 hour or 100% refund if
+                    there are technical errors.
+                  </li>
+                  <li>
+                    Commitment to absolute peace of mind and transparent,
+                    reliable service experience.
+                  </li>
+                </ul>
+              </div>
+            </div>
+          </div>
+        </div>
+      </section>
+
+      {/* PARTNERSHIP */}
+      <section className="py-12 md:py-20 bg-white px-4">
+        <div className="max-w-7xl mx-auto space-y-10 md:space-y-16">
+          <h2 className="text-3xl md:text-6xl font-black text-center tracking-tight text-slate-900">
+            InfiGate has <span className="text-[#EE0434]">partnered with</span>
+          </h2>
+
+          <div className="space-y-8 md:space-y-12 max-w-5xl mx-auto">
+            <div className="space-y-4 md:space-y-6">
+              <div className="flex items-center space-x-3">
+                <div className="w-5 h-5 md:w-6 md:h-6 bg-[#EE0434] rounded-full flex items-center justify-center shrink-0">
+                  <svg
+                    className="w-3 h-3 md:w-4 md:h-4 text-white"
+                    fill="currentColor"
+                    viewBox="0 0 20 20"
+                  >
+                    <path
+                      fillRule="evenodd"
+                      d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
+                      clipRule="evenodd"
+                    />
+                  </svg>
+                </div>
+                <p className="text-sm md:text-2xl font-semibold text-slate-500">
+                  Official partner of global corporations
+                </p>
+              </div>
+              <div className="flex flex-wrap gap-3 md:gap-8 justify-center lg:justify-start">
+                <div className="bg-white border border-[#EE0434]/20 rounded-xl px-4 py-2 w-36 md:w-56 h-14 flex items-center justify-center shadow-sm">
+                  <img
+                    src="https://upload.wikimedia.org/wikipedia/commons/thumb/c/c5/Vietnam_Airlines_logo_2015.svg/1280px-Vietnam_Airlines_logo_2015.svg.png"
+                    alt="Vietnam Airlines"
+                    className="max-h-full max-w-full object-contain"
+                  />
+                </div>
+                <div className="bg-white border border-[#EE0434]/20 rounded-xl px-4 py-2 w-36 md:w-56 h-14 flex items-center justify-center shadow-sm">
+                  <img
+                    src="https://upload.wikimedia.org/wikipedia/commons/thumb/e/e0/Logo_Viettel.svg/1280px-Logo_Viettel.svg.png"
+                    alt="Viettel"
+                    className="max-h-full max-w-full object-contain"
+                  />
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </section>
+
+      {/* How it Works */}
+      <section className="py-12 md:py-24 bg-white px-4">
+        <div className="max-w-7xl mx-auto">
+          <h2 className="text-2xl md:text-6xl font-black text-[#EE0434] text-center mb-6 md:mb-10 tracking-tight leading-tight">
+            Buy a SIM easily, <br className="md:hidden" /> ready to explore
+          </h2>
+          <div className="flex justify-center mb-10 md:mb-20">
+            <div className="inline-flex p-1 bg-slate-100 rounded-full shadow-inner">
+              <button
+                onClick={() => setSimType("eSIM")}
+                className={`px-6 md:px-12 py-2.5 md:py-3 rounded-full text-[10px] md:text-sm font-bold transition-all ${
+                  simType === "eSIM"
+                    ? "bg-gradient-to-r from-[#E21c34] to-[#500B28] text-white shadow-md"
+                    : "bg-[#f0f0f0] text-[#8b8e96] hover:text-slate-800"
+                }`}
+              >
+                eSIM
+              </button>
+              <button
+                onClick={() => setSimType("Physical")}
+                className={`px-6 md:px-12 py-2.5 md:py-3 rounded-full text-[10px] md:text-sm font-bold transition-all ${
+                  simType === "Physical"
+                    ? "bg-gradient-to-r from-[#E21c34] to-[#500B28] text-white shadow-md"
+                    : "bg-[#f0f0f0] text-[#8b8e96] hover:text-slate-800"
+                }`}
+              >
+                Physical SIM
+              </button>
+            </div>
+          </div>
+          <div className="grid grid-cols-1 lg:grid-cols-2 gap-8 md:gap-16 items-center">
+            <div className="relative aspect-square bg-[#EE0434]/5 rounded-[24px] md:rounded-[48px] overflow-hidden flex items-center justify-center shadow-xl border border-red-50">
+              <div className="absolute inset-0 bg-gradient-to-br from-[#EE0434]/20 to-white/0 opacity-50"></div>
+              <div className="w-4/5 h-4/5 relative flex items-center justify-center">
+                <svg
+                  className="w-full h-full text-[#EE0434] drop-shadow-lg"
+                  viewBox="0 0 100 100"
+                  fill="none"
+                >
+                  <path
+                    d="M0 70 Q 25 20, 50 70 T 100 70"
+                    stroke="currentColor"
+                    strokeWidth="8"
+                    strokeLinecap="round"
+                    className="opacity-80"
+                  />
+                </svg>
+              </div>
+            </div>
+            <div className="space-y-4 md:space-y-12">
+              {steps.map((step, index) => (
+                <div
+                  key={index}
+                  className="flex items-start space-x-4 md:space-x-6 p-4 md:p-8 rounded-[20px] md:rounded-[32px] border bg-transparent border-transparent hover:bg-white hover:shadow-lg hover:border-slate-50 transition-all"
+                >
+                  <span className="text-xl md:text-3xl font-black text-[#EE0434] mt-0.5 shrink-0">
+                    {step.number}
+                  </span>
+                  <div>
+                    <h3 className="text-base md:text-2xl font-black text-slate-900 mb-0.5 md:mb-2">
+                      {step.title}
+                    </h3>
+                    <p className="text-slate-500 text-[11px] md:text-lg leading-relaxed">
+                      {step.description}
+                    </p>
+                  </div>
+                </div>
+              ))}
+            </div>
+          </div>
+        </div>
+      </section>
+
+      {/* FAQ */}
+      <HomeFaq />
+
+      {/* Refund */}
+      <section className="py-16 md:py-24 bg-white px-4">
+        <div className="max-w-7xl mx-auto flex flex-col items-center">
+          <h2 className="text-3xl md:text-[56px] font-black text-center mb-12 md:mb-20 tracking-tight text-slate-900">
+            <span className="text-[#EE0434]">100%</span> refund policy if
+            product is defective
+          </h2>
+          <button className="flex items-center bg-gradient-to-r from-[#E21c34] to-[#500B28] p-2 pr-10 rounded-full shadow-xl hover:scale-105 transition-all">
+            <div className="w-16 h-16 bg-white rounded-full flex items-center justify-center text-slate-800">
+              <svg
+                className="w-8 h-8"
+                fill="none"
+                stroke="currentColor"
+                viewBox="0 0 24 24"
+              >
+                <path
+                  strokeLinecap="round"
+                  strokeLinejoin="round"
+                  strokeWidth={2.5}
+                  d="M17 8l4 4m0 0l-4 4m4-4H3"
+                />
+              </svg>
+            </div>
+            <span className="ml-6 text-white text-2xl font-black">
+              Return and Refund Guide
+            </span>
+          </button>
+        </div>
+      </section>
+    </div>
+  );
+};
+
+export default HomeView;

+ 106 - 0
EsimLao/esim-vite/src/pages/home/components/HomeBanner.tsx

@@ -0,0 +1,106 @@
+import React, { useState, useEffect, useCallback } from "react";
+
+const HomeBanner = () => {
+  const [currentBanner, setCurrentBanner] = useState(0);
+
+  const banners = [
+    {
+      badge: "Global Connectivity",
+      title: "Travel Smarter",
+      highlight: "With InfiGate",
+      desc: "High-speed 5G/4G connectivity across 200+ countries. No roaming fees, just instant access.",
+      gradient: "from-[#EE0434] via-[#cc002d] to-[#ff4d6d]",
+      image:
+        "https://images.unsplash.com/photo-1530789253588-583c8d9c03b0?q=80&w=1000&auto=format&fit=crop",
+    },
+    {
+      badge: "Digital Nomads",
+      title: "Ultra-Fast 5G",
+      highlight: "Work Anywhere",
+      desc: "Stay productive with stable high-speed data for meetings and remote work.",
+      gradient: "from-[#ff4d6d] via-[#EE0434] to-[#80001a]",
+      image:
+        "https://images.unsplash.com/photo-1488190211105-8b0e65b80b4e?q=80&w=1000&auto=format&fit=crop",
+    },
+    {
+      badge: "Exclusive Offers",
+      title: "Asian Tours",
+      highlight: "Special Deals",
+      desc: "Plan your next trip with our local-rate eSIMs starting from just $0.85/day.",
+      gradient: "from-[#EE0434] via-[#ff6b6b] to-[#c92a2a]",
+      image:
+        "https://images.unsplash.com/photo-1493976040374-85c8e12f0c0e?q=80&w=1000&auto=format&fit=crop",
+    },
+  ];
+
+  const nextBanner = useCallback(() => {
+    setCurrentBanner((prev) => (prev + 1) % banners.length);
+  }, [banners.length]);
+
+  useEffect(() => {
+    const timer = setInterval(nextBanner, 6000);
+    return () => clearInterval(timer);
+  }, [nextBanner]);
+
+  return (
+    <section className="px-4 py-4 md:py-6 relative">
+      <div className="max-w-7xl mx-auto relative overflow-hidden rounded-[24px] md:rounded-[48px] min-h-[400px] md:min-h-[550px] shadow-2xl bg-slate-900">
+        {banners.map((banner, idx) => (
+          <div
+            key={idx}
+            className={`absolute inset-0 w-full h-full transition-all duration-[1000ms] ease-in-out flex items-center ${
+              idx === currentBanner
+                ? "opacity-100 z-20 scale-100"
+                : "opacity-0 z-10 pointer-events-none scale-105"
+            }`}
+          >
+            <div
+              className={`absolute inset-0 bg-gradient-to-br ${banner.gradient} opacity-90 transition-all`}
+            ></div>
+
+            <div className="relative z-30 flex flex-col items-center justify-center p-6 md:p-24 w-full h-full text-center">
+              <div
+                className={`text-white space-y-6 md:space-y-8 transition-all duration-1000 delay-300 ${
+                  idx === currentBanner
+                    ? "translate-y-0 opacity-100"
+                    : "translate-y-12 opacity-0"
+                }`}
+              >
+                <div className="inline-block px-4 py-1.5 bg-white/20 backdrop-blur-xl rounded-full text-[10px] md:text-xs font-black uppercase tracking-[0.3em] border border-white/10">
+                  {banner.badge}
+                </div>
+                <h1 className="text-4xl md:text-7xl lg:text-8xl font-black leading-[1.1] tracking-tighter max-w-4xl">
+                  {banner.title} <br />
+                  <span className="text-white/60 drop-shadow-sm">
+                    {banner.highlight}
+                  </span>
+                </h1>
+                <p className="text-sm md:text-xl text-white/80 font-medium max-w-2xl mx-auto leading-relaxed hidden sm:block">
+                  {banner.desc}
+                </p>
+              </div>
+            </div>
+          </div>
+        ))}
+
+        <div className="absolute bottom-10 left-1/2 -translate-x-1/2 z-40 flex items-center space-x-4">
+          {banners.map((_, i) => (
+            <button
+              key={i}
+              onClick={() => setCurrentBanner(i)}
+              className={`group relative h-1.5 transition-all duration-500 overflow-hidden rounded-full ${
+                i === currentBanner ? "w-16 bg-white/20" : "w-8 bg-white/40"
+              }`}
+            >
+              {i === currentBanner && (
+                <div className="absolute inset-0 bg-white animate-progress"></div>
+              )}
+            </button>
+          ))}
+        </div>
+      </div>
+    </section>
+  );
+};
+
+export default HomeBanner;

+ 102 - 0
EsimLao/esim-vite/src/pages/home/components/HomeFaq.tsx

@@ -0,0 +1,102 @@
+import React, { useState, useEffect, useCallback } from "react";
+
+const HomeFaq = () => {
+  const [openFaqIndex, setOpenFaqIndex] = useState<number | null>(0);
+
+  const faqs = [
+    {
+      question:
+        "1. Does using an eSIM/ SIM incur any additional fees or services?",
+      answer:
+        "No. The price shown in the order already includes all costs, and no additional fees will arise during usage.",
+    },
+    {
+      question: "2. I received the email but there is no QR code.",
+      answer:
+        "Please check your spam or junk folder first. If it's not there, contact our support team via Zalo OA or WhatsApp with your order ID, and we will assist you in resending the QR code manually.",
+    },
+    {
+      question: "3. I lost my SIM while traveling abroad. Can it be reissued?",
+      answer:
+        "For eSIMs, we can easily resend the digital profile to your registered email. For physical SIMs, reissuance while abroad may be difficult due to shipping; we recommend switching to an eSIM if your device supports it.",
+    },
+  ];
+
+  const toggleFaq = (index: number) => {
+    setOpenFaqIndex(openFaqIndex === index ? null : index);
+  };
+
+  return (
+    <>
+      <section className="py-16 md:py-24 bg-white px-4 border-t border-slate-50">
+        <div className="max-w-4xl mx-auto">
+          <div className="text-center mb-12 md:mb-20">
+            <h2 className="text-3xl md:text-6xl font-black tracking-tight text-[#EE0434] mb-4">
+              Frequently <span className="text-slate-900">Asked Questions</span>
+            </h2>
+            <p className="text-slate-500 text-sm md:text-xl font-medium">
+              We are always here to support you.
+            </p>
+          </div>
+
+          <div className="space-y-4">
+            {faqs.map((faq, index) => (
+              <div key={index} className="border-b border-slate-100">
+                <button
+                  onClick={() => toggleFaq(index)}
+                  className="w-full flex justify-between items-center py-6 text-left group transition-all"
+                >
+                  <span
+                    className={`text-base md:text-2xl font-black transition-colors ${
+                      openFaqIndex === index
+                        ? "text-[#EE0434]"
+                        : "text-slate-800 group-hover:text-[#EE0434]"
+                    }`}
+                  >
+                    {faq.question}
+                  </span>
+                  <div
+                    className={`shrink-0 w-6 h-6 md:w-8 md:h-8 flex items-center justify-center transition-all duration-500 ${
+                      openFaqIndex === index
+                        ? "text-[#EE0434] rotate-180"
+                        : "text-slate-300"
+                    }`}
+                  >
+                    <svg
+                      className="w-5 h-5 md:w-7 md:h-7"
+                      fill="none"
+                      stroke="currentColor"
+                      viewBox="0 0 24 24"
+                    >
+                      <path
+                        strokeLinecap="round"
+                        strokeLinejoin="round"
+                        strokeWidth={3}
+                        d="M19 9l-7 7-7-7"
+                      />
+                    </svg>
+                  </div>
+                </button>
+                <div
+                  className={`grid transition-all duration-500 ease-in-out ${
+                    openFaqIndex === index
+                      ? "grid-rows-[1fr] opacity-100"
+                      : "grid-rows-[0fr] opacity-0"
+                  }`}
+                >
+                  <div className="overflow-hidden">
+                    <p className="text-slate-600 text-sm md:text-xl leading-relaxed font-medium pb-8">
+                      {faq.answer}
+                    </p>
+                  </div>
+                </div>
+              </div>
+            ))}
+          </div>
+        </div>
+      </section>
+    </>
+  );
+};
+
+export default HomeFaq;

+ 193 - 0
EsimLao/esim-vite/src/pages/home/components/HomeProduct.tsx

@@ -0,0 +1,193 @@
+import { useAppDispatch, useAppSelector } from "../../../hooks/useRedux";
+import { SimProduct } from "@/src/services/types";
+import { useMutation } from "@tanstack/react-query";
+import React, { useState, useEffect, useCallback } from "react";
+import { useNavigate } from "react-router-dom";
+import { productApi } from "../../../apis/productApi";
+import {
+  startLoading,
+  stopLoading,
+} from "../../../features/loading/loadingSlice";
+
+const HomeProduct = () => {
+  const areas = useAppSelector((state) => state.areas.areas);
+  const [activeTab, setActiveTab] = useState<"country" | "region">("country");
+  const navigate = useNavigate();
+
+  const dispatch = useAppDispatch();
+
+  const products: SimProduct[] = [
+    {
+      id: "1",
+      country: "China",
+      flag: "cn",
+      originalPrice: "$0.94",
+      discountPrice: "$0.85",
+      discountLabel: "-10%",
+    },
+    {
+      id: "2",
+      country: "Hong Kong",
+      flag: "hk",
+      originalPrice: "$4.43",
+      discountPrice: "$4.21",
+      discountLabel: "-5%",
+    },
+    {
+      id: "3",
+      country: "Japan",
+      flag: "jp",
+      originalPrice: "$1.44",
+      discountPrice: "$1.3",
+      discountLabel: "-10%",
+    },
+    {
+      id: "4",
+      country: "Singapore",
+      flag: "sg",
+      originalPrice: "$4.43",
+      discountPrice: "$4.21",
+      discountLabel: "-5%",
+    },
+    {
+      id: "5",
+      country: "South Korea",
+      flag: "kr",
+      originalPrice: "$0.94",
+      discountPrice: "$0.85",
+      discountLabel: "-10%",
+    },
+    {
+      id: "6",
+      country: "Taiwan",
+      flag: "tw",
+      originalPrice: "$2.04",
+      discountPrice: "$1.84",
+      discountLabel: "-10%",
+    },
+    {
+      id: "7",
+      country: "Thailand",
+      flag: "th",
+      originalPrice: "$1.89",
+      discountPrice: "$1.78",
+      discountLabel: "-6%",
+    },
+    {
+      id: "8",
+      country: "United States",
+      flag: "us",
+      originalPrice: "$2.16",
+      discountPrice: "$1.94",
+      discountLabel: "-10%",
+    },
+  ];
+
+  useEffect(() => {
+    if (!areas || areas.length === 0) loadAreaApi.mutate();
+  }, []);
+
+  const loadAreaApi = useMutation({
+    mutationFn: async () => {
+      dispatch(startLoading({}));
+      const res = await productApi.loadArea();
+      console.log("Load areas response:", res);
+      return res;
+    },
+    onSuccess: (data) => {
+      dispatch(stopLoading());
+      console.log("Get config response data:", data);
+      if (data && data.errorCode === 0) {
+        console.log("Get config successful");
+      } else {
+        console.error("Get config failed, no token received");
+      }
+    },
+    onError: (error: any) => {
+      dispatch(stopLoading());
+      console.error("Get config error:", error.response.data);
+    },
+  });
+
+  const handleProductClick = (p: SimProduct) => {
+    navigate(`/product/${p.country.toLowerCase()}`, {
+      state: {
+        country: p.country,
+        flag: p.flag,
+        image:
+          p.country === "Thailand"
+            ? "https://images.unsplash.com/photo-1528181304800-2f140819898f?q=80&w=1000&auto=format&fit=crop"
+            : `https://images.unsplash.com/photo-1526772662000-3f88f10c053b?q=80&w=1000&auto=format&fit=crop`,
+      },
+    });
+    window.scrollTo({ top: 0, behavior: "smooth" });
+  };
+
+  return (
+    <section className="pt-12 pb-20 text-center px-4">
+      <h2 className="text-3xl md:text-5xl lg:text-6xl font-black text-slate-900 tracking-tight mb-4">
+        Fastest Data, <span className="text-[#EE0434]">Best Prices.</span>
+      </h2>
+      <p className="text-slate-500 font-bold mb-10 md:mb-12">
+        Connect instantly in over 200 destinations.
+      </p>
+
+      <div className="inline-flex p-1.5 bg-slate-100 rounded-full mb-12 md:mb-16 shadow-inner">
+        <button
+          onClick={() => setActiveTab("country")}
+          className={`px-8 md:px-12 py-3 rounded-full text-xs md:text-sm font-black transition-all ${
+            activeTab === "country"
+              ? "bg-gradient-to-r from-[#E21c34] to-[#500B28] text-white shadow-lg"
+              : "bg-[#f0f0f0] text-[#8b8e96] hover:text-slate-800"
+          }`}
+        >
+          Country List
+        </button>
+        <button
+          onClick={() => setActiveTab("region")}
+          className={`px-8 md:px-12 py-3 rounded-full text-xs md:text-sm font-black transition-all ${
+            activeTab === "region"
+              ? "bg-gradient-to-r from-[#E21c34] to-[#500B28] text-white shadow-lg"
+              : "bg-[#f0f0f0] text-[#8b8e96] hover:text-slate-800"
+          }`}
+        >
+          Regions
+        </button>
+      </div>
+
+      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
+        <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 md:gap-8">
+          {products.map((p) => (
+            <div
+              key={p.id}
+              onClick={() => handleProductClick(p)}
+              className="group bg-white border border-slate-100 rounded-[32px] p-6 md:p-10 shadow-sm hover:shadow-2xl transition-all relative overflow-hidden cursor-pointer flex flex-col items-center"
+            >
+              <div className="absolute top-4 right-4 bg-[#EE0434] text-white text-[10px] font-black px-2.5 py-1 rounded-lg z-10">
+                {p.discountLabel}
+              </div>
+              <div className="w-16 h-16 md:w-20 md:h-20 mb-6 rounded-full overflow-hidden shadow-xl border-4 border-white group-hover:scale-110 transition-transform">
+                <img
+                  src={`https://flagcdn.com/w160/${p.flag}.png`}
+                  alt={p.country}
+                  className="w-full h-full object-cover scale-150"
+                />
+              </div>
+              <h3 className="text-lg md:text-xl font-black text-slate-800 mb-2 truncate w-full group-hover:text-[#EE0434] transition-colors">
+                {p.country}
+              </h3>
+              <p className="text-xs text-slate-400 line-through mb-1">
+                {p.originalPrice}
+              </p>
+              <p className="text-lg md:text-2xl font-black text-[#EE0434]">
+                From {p.discountPrice}
+              </p>
+            </div>
+          ))}
+        </div>
+      </div>
+    </section>
+  );
+};
+
+export default HomeProduct;

+ 49 - 0
EsimLao/esim-vite/src/pages/home/components/HomeSearch.tsx

@@ -0,0 +1,49 @@
+const HomeSearch = () => {
+  return (
+    <section className="pt-8 pb-4 px-4 relative z-40 bg-white">
+      <div className="max-w-4xl mx-auto w-full relative">
+        <div className="relative group">
+          <input
+            type="text"
+            placeholder="Where are you going?"
+            className="w-full bg-white text-slate-900 rounded-full py-4 md:py-6 px-8 md:px-12 text-base md:text-xl shadow-[0_10px_40px_-10px_rgba(0,0,0,0.15)] border border-slate-100 outline-none focus:ring-4 focus:ring-[#EE0434]/10 transition-all placeholder:text-slate-400 pr-16 md:pr-48"
+          />
+          <button className="absolute right-2 top-2 bottom-2 aspect-square md:aspect-auto md:px-10 bg-gradient-to-r from-[#E21c34] to-[#500B28] rounded-full flex items-center justify-center text-white shadow-lg hover:shadow-[#EE0434]/25 hover:scale-[1.02] active:scale-95 transition-all">
+            <svg
+              className="w-6 h-6 md:hidden"
+              fill="none"
+              stroke="currentColor"
+              viewBox="0 0 24 24"
+            >
+              <path
+                d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
+                strokeWidth={3}
+                strokeLinecap="round"
+                strokeLinejoin="round"
+              />
+            </svg>
+            <span className="hidden md:block font-bold text-lg tracking-wide">
+              Search Destination
+            </span>
+          </button>
+        </div>
+        <div className="mt-3 flex justify-center gap-2 text-sm text-slate-400 font-medium">
+          <span>Popular:</span>
+          <button className="hover:text-[#EE0434] transition-colors">
+            Japan
+          </button>
+          ,
+          <button className="hover:text-[#EE0434] transition-colors">
+            Thailand
+          </button>
+          ,
+          <button className="hover:text-[#EE0434] transition-colors">
+            China
+          </button>
+        </div>
+      </div>
+    </section>
+  );
+};
+
+export default HomeSearch;

+ 105 - 0
EsimLao/esim-vite/src/pages/home/components/HomeTestimonial.tsx

@@ -0,0 +1,105 @@
+import TestimonialCard from "../../../components/TestimonialCard";
+
+const HomeTestimonial = () => {
+  const testimonials = [
+    {
+      name: "Quang Huy",
+      location: "Nghệ An",
+      rating: 5,
+      avatar: "https://i.pravatar.cc/150?u=huy",
+      content:
+        "Trong suốt hành trình đi phượt bên Mỹ, Huy dò đường bằng Google Map phát trực tiếp từ sim data tốc độ cao mua sẵn ở Việt Nam tại shop infigate. Huy chỉ cần gắn sim vô là xài thôi, rất dễ dàng.",
+    },
+    {
+      name: "Yan Lin",
+      location: "Trung Quốc",
+      rating: 5,
+      avatar: "https://i.pravatar.cc/150?u=yan",
+      content:
+        "不用下载APP,不用注册账号,只要插入SIM卡就能上网。对不太懂技术的人来说非常友好,客服还可以用中文沟通,太贴心了!",
+    },
+    {
+      name: "Hồng Mây",
+      location: "Hồ Chí Minh",
+      rating: 5,
+      avatar: "https://i.pravatar.cc/150?u=may",
+      content:
+        "Dùng thích nên lần nào đi công tác mình cũng mua 😂 chất lượng dùng mạng bên TQ nhanh, vượt được hết tường lửa để truy cập zalo, fb thoải mái, giá rẻ mỗi tội không gọi được điện thôi.",
+    },
+    {
+      name: "Min-jun",
+      location: "Hàn Quốc",
+      rating: 5,
+      avatar: "https://i.pravatar.cc/150?u=minjun",
+      content:
+        "현지 SIM 카드인데 데이터 무제한 요금제가 있어서 너무 좋아요. 가격도 저렴하고 속도도 빠름! 카카오톡, 인스타, 유튜브 다 문제없이 사용 중이에요.",
+    },
+    {
+      name: "David",
+      location: "Hoa Kỳ",
+      rating: 5,
+      avatar: "https://i.pravatar.cc/150?u=david",
+      content:
+        "As a traveler from the U.S., I was worried about staying online during my trip. This SIM card solved everything. It was delivered to my hotel and activated in minutes.",
+    },
+    {
+      name: "Thùy Linh",
+      location: "Hà Nội",
+      rating: 5,
+      avatar: "https://i.pravatar.cc/150?u=linh",
+      content:
+        "Dịch vụ tuyệt vời ở Phuket, Thái Lan nhờ eSIM infigate. Kết nối mạnh ổn định, đủ để cả nhà xem phim, lướt web, sử dụng mạng xã hội một cách thoải mái.",
+    },
+  ];
+
+  return (
+    <section className="relative overflow-hidden bg-[#EE0434] py-10 md:py-20 lg:py-24">
+      <div className="absolute top-0 right-0 w-[600px] h-[600px] bg-white/5 rounded-full blur-[100px] -translate-y-1/2 translate-x-1/2"></div>
+      <div className="absolute bottom-0 left-0 w-[400px] h-[400px] bg-black/5 rounded-full blur-[80px] translate-y-1/2 -translate-x-1/2"></div>
+
+      <div className="max-w-7xl mx-auto relative z-10 px-4 sm:px-6 lg:px-8">
+        <div className="grid grid-cols-1 lg:grid-cols-12 gap-8 lg:gap-12 items-center">
+          <div className="lg:col-span-4 text-white space-y-4 md:space-y-6 text-center lg:text-left">
+            <h2 className="text-2xl md:text-4xl lg:text-[36px] font-black leading-tight tracking-tight">
+              What our customers <br className="hidden lg:block" /> say about us
+            </h2>
+            <p className="text-sm md:text-xl font-medium text-white/80 max-w-sm mx-auto lg:mx-0">
+              Over 1,000,000 satisfied customers have used our services
+            </p>
+          </div>
+
+          <div className="hidden lg:col-span-8 lg:grid grid-cols-2 gap-6 h-[550px] overflow-hidden relative">
+            <div className="absolute inset-x-0 top-0 h-20 bg-gradient-to-b from-[#EE0434] to-transparent z-20"></div>
+            <div className="absolute inset-x-0 bottom-0 h-20 bg-gradient-to-t from-[#EE0434] to-transparent z-20"></div>
+
+            <div className="flex flex-col space-y-6 animate-marquee-up hover:[animation-play-state:paused]">
+              {[...testimonials, ...testimonials].map((item, i) => (
+                <TestimonialCard key={i} item={item} />
+              ))}
+            </div>
+
+            <div className="flex flex-col space-y-6 animate-marquee-down hover:[animation-play-state:paused]">
+              {[...testimonials, ...testimonials].reverse().map((item, i) => (
+                <TestimonialCard key={i} item={item} />
+              ))}
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <div className="lg:hidden w-screen overflow-hidden py-8 relative -ml-4">
+        <div className="flex space-x-6 animate-scroll-x whitespace-nowrap px-4">
+          {[...testimonials, ...testimonials, ...testimonials].map(
+            (item, i) => (
+              <div key={i} className="inline-block whitespace-normal align-top">
+                <TestimonialCard item={item} />
+              </div>
+            )
+          )}
+        </div>
+      </div>
+    </section>
+  );
+};
+
+export default HomeTestimonial;

+ 44 - 0
EsimLao/esim-vite/src/pages/login/LoginView.tsx

@@ -0,0 +1,44 @@
+
+import React, { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+
+const LoginView: React.FC = () => {
+  const navigate = useNavigate();
+  const [email, setEmail] = useState('');
+
+  return (
+    <div className="min-h-screen bg-white flex flex-col lg:flex-row overflow-hidden">
+      <div className="w-full lg:w-1/2 flex flex-col justify-center items-center p-6 md:p-12 lg:p-20 relative z-10 bg-white">
+        <button onClick={() => navigate(-1)} className="absolute top-6 left-6 lg:hidden p-2 text-slate-400 hover:text-[#EE0434] transition-colors">
+          <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M15 19l-7-7 7-7" /></svg>
+        </button>
+        <div className="w-full max-w-[420px] space-y-10">
+          <div className="space-y-4 text-center lg:text-left">
+            <div className="lg:hidden flex justify-center mb-8">
+               <div className="w-20 h-20 rounded-[24px] bg-gradient-to-br from-[#EE0434] to-[#80001a] flex items-center justify-center shadow-lg">
+                  <svg className="w-10 h-10 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round"/></svg>
+               </div>
+            </div>
+            <h1 className="text-3xl md:text-4xl lg:text-5xl font-black text-slate-900 tracking-tight">Welcome Back!</h1>
+            <p className="text-slate-400 font-medium text-base md:text-lg">Stay connected everywhere with InfiGate.</p>
+          </div>
+          <form className="space-y-8" onSubmit={(e) => e.preventDefault()}>
+            <div className="space-y-3">
+              <label className="block text-slate-500 font-black text-[10px] uppercase tracking-[0.2em] pl-1">Email Address</label>
+              <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Enter your email" className="w-full bg-slate-50 border-2 border-transparent focus:border-[#EE0434]/20 rounded-2xl py-4 md:py-5 px-6 focus:outline-none focus:bg-white transition-all text-slate-700 font-semibold" />
+            </div>
+            <button type="submit" className="w-full bg-[#EE0434] text-white py-4 md:py-5 rounded-2xl font-black text-xl md:text-2xl shadow-lg hover:scale-[1.01] active:scale-[0.98] transition-all">Login</button>
+          </form>
+        </div>
+      </div>
+      <div className="hidden lg:flex w-1/2 bg-gradient-to-br from-[#EE0434] to-[#80001a] items-center justify-center relative overflow-hidden">
+         <div className="relative z-10 text-white text-center space-y-4">
+             <h2 className="text-5xl font-black">Fast. Simple. Red.</h2>
+             <p className="text-xl opacity-80">Global connectivity for the modern traveler.</p>
+         </div>
+      </div>
+    </div>
+  );
+};
+
+export default LoginView;

+ 137 - 0
EsimLao/esim-vite/src/pages/news/NewsDetailView.tsx

@@ -0,0 +1,137 @@
+import React from "react";
+import { useLocation, useNavigate, Link } from "react-router-dom";
+import { NewsArticle } from "../../services/types";
+
+const ArticleDetailView: React.FC = () => {
+  const location = useLocation();
+  const navigate = useNavigate();
+  const article = location.state?.article as NewsArticle;
+
+  if (!article) {
+    return (
+      <div className="min-h-screen flex items-center justify-center">
+        <div className="text-center">
+          <p className="text-xl font-bold mb-4">Article not found</p>
+          <Link to="/news" className="text-[#EE0434] font-bold underline">
+            Back to News
+          </Link>
+        </div>
+      </div>
+    );
+  }
+
+  return (
+    <div className="bg-white min-h-screen pb-20">
+      <div className="max-w-7xl mx-auto px-4 py-4 md:py-6">
+        <nav className="flex items-center flex-wrap gap-2 text-xs md:text-sm text-slate-500 font-medium">
+          <Link to="/" className="hover:text-blue-500">
+            Home
+          </Link>
+          <svg
+            className="w-4 h-4"
+            fill="none"
+            stroke="currentColor"
+            viewBox="0 0 24 24"
+          >
+            <path
+              d="M9 5l7 7-7 7"
+              strokeWidth={2}
+              strokeLinecap="round"
+              strokeLinejoin="round"
+            />
+          </svg>
+          <Link to="/news" className="hover:text-blue-500">
+            News
+          </Link>
+          <svg
+            className="w-4 h-4"
+            fill="none"
+            stroke="currentColor"
+            viewBox="0 0 24 24"
+          >
+            <path
+              d="M9 5l7 7-7 7"
+              strokeWidth={2}
+              strokeLinecap="round"
+              strokeLinejoin="round"
+            />
+          </svg>
+          <span className="text-slate-900 font-bold truncate max-w-[200px] md:max-w-none">
+            {article.title}
+          </span>
+        </nav>
+      </div>
+
+      <div className="max-w-7xl mx-auto px-4">
+        <header className="py-6 md:py-10 border-t border-slate-50">
+          <h1 className="text-2xl md:text-3xl lg:text-4xl font-black text-[#EE0434] leading-tight mb-4">
+            {article.title}
+          </h1>
+          <div className="flex items-center text-slate-400 space-x-2">
+            <svg
+              className="w-4 h-4"
+              fill="none"
+              stroke="currentColor"
+              viewBox="0 0 24 24"
+            >
+              <path
+                strokeLinecap="round"
+                strokeLinejoin="round"
+                strokeWidth={2}
+                d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
+              />
+            </svg>
+            <span className="text-xs md:text-sm font-bold">{article.date}</span>
+          </div>
+        </header>
+
+        <div className="mb-8 md:mb-12">
+          <div className="aspect-[21/9] rounded-[24px] md:rounded-[40px] overflow-hidden shadow-2xl">
+            <img
+              src={article.image}
+              alt={article.title}
+              className="w-full h-full object-cover"
+            />
+          </div>
+        </div>
+
+        <div className="max-w-4xl mx-auto">
+          <article className="prose prose-lg prose-slate max-w-none text-slate-800">
+            <h2 className="text-lg md:text-xl font-black mb-4 text-slate-900 leading-snug">
+              {article.title}: Chi tiết – Dễ hiểu – Thành công 100%
+            </h2>
+            <p className="text-base md:text-lg leading-relaxed mb-4 font-medium text-slate-600">
+              Trong những năm gần đây, eSIM du lịch trở thành lựa chọn tối ưu
+              cho người đi công tác và du lịch nước ngoài nhờ tiện lợi, kích
+              hoạt nhanh, giá hợp lý và hoàn toàn không cần tháo SIM vật lý.
+            </p>
+          </article>
+
+          <div className="mt-16 pt-8 border-t border-slate-100 flex flex-col md:flex-row justify-between items-center gap-6">
+            <button
+              onClick={() => navigate("/news")}
+              className="flex items-center space-x-2 text-[#EE0434] font-black hover:translate-x-[-4px] transition-transform text-sm md:text-base"
+            >
+              <svg
+                className="w-4 h-4"
+                fill="none"
+                stroke="currentColor"
+                viewBox="0 0 24 24"
+              >
+                <path
+                  strokeLinecap="round"
+                  strokeLinejoin="round"
+                  strokeWidth={3}
+                  d="M15 19l-7-7 7-7"
+                />
+              </svg>
+              <span>Back to News</span>
+            </button>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default ArticleDetailView;

+ 134 - 0
EsimLao/esim-vite/src/pages/news/NewsView.tsx

@@ -0,0 +1,134 @@
+import React from "react";
+import { useNavigate, Link } from "react-router-dom";
+import { NewsArticle } from "../../services/types";
+
+const NewsView: React.FC = () => {
+  const navigate = useNavigate();
+  const articles: NewsArticle[] = [
+    {
+      id: "1",
+      title:
+        "Siêu Sale Mùa Lễ Hội Cùng InfiGate – Giảm Đến 35% Khi Mua eSIM Du Lịch Quốc Tế",
+      date: "01/12/2025",
+      excerpt:
+        "Siêu Sale Mùa Lễ Hội Cùng InfiGate – Giảm Đến 35% Thời gian áp dụng ưu đãi: 01/12 – 31/12/2025",
+      image:
+        "https://images.unsplash.com/photo-1549463599-231a5401977e?q=80&w=800&auto=format&fit=crop",
+    },
+    {
+      id: "2",
+      title:
+        "Du lịch châu Á tháng 12: Mùa lễ hội rực rỡ và những trải nghiệm không thể bỏ lỡ – Đừng quên chuẩn bị SIM du lịch trước khi khởi hành!",
+      date: "28/11/2025",
+      excerpt:
+        "Tháng 12 là thời điểm lý tưởng để khám phá châu Á: từ mùa tuyết trắng Nhật – Hàn, biển ấm Đông...",
+      image:
+        "https://images.unsplash.com/photo-1493976040374-85c8e12f0c0e?q=80&w=800&auto=format&fit=crop",
+    },
+    {
+      id: "3",
+      title:
+        "InfiGate ra mắt bộ đôi eSIM & SIM du lịch – Mở rộng lựa chọn, tối ưu kết nối toàn cầu",
+      date: "27/11/2025",
+      excerpt:
+        "Khi nhu cầu du lịch bùng nổ mạnh mẽ, Internet trở thành “vũ khí tối thượng” cho mọi chuyến đi: dẫn...",
+      image:
+        "https://images.unsplash.com/photo-1526772662000-3f88f10c053b?q=80&w=800&auto=format&fit=crop",
+    },
+    {
+      id: "4",
+      title: "Hướng dẫn cách cài đặt eSIM du lịch trên Samsung",
+      date: "14/11/2025",
+      excerpt:
+        "Hướng dẫn cài đặt eSIM du lịch trên Samsung nhanh và đơn giản. Áp dụng cho Galaxy S, A, Z. Gợi ý eSIM...",
+      image:
+        "https://images.unsplash.com/photo-1610945265064-0e34e5519bbf?q=80&w=800&auto=format&fit=crop",
+    },
+    {
+      id: "5",
+      title: "Hướng dẫn cách cài đặt eSIM du lịch trên iPhone",
+      date: "14/11/2025",
+      excerpt:
+        "Hướng dẫn chi tiết cách kích hoạt eSIM trên các dòng iPhone từ XS trở lên. Giải pháp kết nối tối ưu...",
+      image:
+        "https://images.unsplash.com/photo-1510557880182-3d4d3cba35a5?q=80&w=1000&auto=format&fit=crop",
+    },
+  ];
+
+  const handleArticleClick = (article: NewsArticle) => {
+    navigate(`/news/${article.id}`, { state: { article } });
+    window.scrollTo({ top: 0, behavior: "smooth" });
+  };
+
+  return (
+    <div className="bg-[#fcfdfe] min-h-screen">
+      <div className="max-w-7xl mx-auto px-4 py-4 md:py-6 border-b border-slate-50">
+        <nav className="flex items-center space-x-2 text-xs md:text-sm text-slate-500 font-medium">
+          <Link to="/" className="hover:text-blue-500">
+            Home
+          </Link>
+          <svg
+            className="w-4 h-4"
+            fill="none"
+            stroke="currentColor"
+            viewBox="0 0 24 24"
+          >
+            <path
+              d="M9 5l7 7-7 7"
+              strokeWidth={2}
+              strokeLinecap="round"
+              strokeLinejoin="round"
+            />
+          </svg>
+          <span className="text-slate-900 font-bold">News</span>
+        </nav>
+      </div>
+
+      <div className="max-w-7xl mx-auto px-4 py-12">
+        <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 md:gap-8">
+          {articles.map((article) => (
+            <div
+              key={article.id}
+              onClick={() => handleArticleClick(article)}
+              className="bg-white rounded-[24px] overflow-hidden shadow-sm hover:shadow-xl transition-all duration-300 flex flex-col group cursor-pointer border border-slate-50"
+            >
+              <div className="relative aspect-[4/3] overflow-hidden">
+                <img
+                  src={article.image}
+                  alt={article.title}
+                  className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110"
+                />
+              </div>
+              <div className="p-6 flex flex-col flex-1 space-y-3">
+                <div className="flex items-center text-slate-400 space-x-2">
+                  <svg
+                    className="w-4 h-4"
+                    fill="none"
+                    stroke="currentColor"
+                    viewBox="0 0 24 24"
+                  >
+                    <path
+                      strokeLinecap="round"
+                      strokeLinejoin="round"
+                      strokeWidth={2}
+                      d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
+                    />
+                  </svg>
+                  <span className="text-xs font-bold">{article.date}</span>
+                </div>
+                <h3 className="text-[16px] md:text-[18px] font-black text-slate-800 leading-tight group-hover:text-[#EE0434] transition-colors line-clamp-3">
+                  {article.title}
+                </h3>
+                <p className="text-xs md:text-sm text-slate-500 font-medium line-clamp-3 leading-relaxed">
+                  {article.excerpt}
+                </p>
+              </div>
+            </div>
+          ))}
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default NewsView;

+ 210 - 0
EsimLao/esim-vite/src/pages/product-detail/ProductDetailView.tsx

@@ -0,0 +1,210 @@
+import React, { useState } from "react";
+import { useLocation, useNavigate, Link } from "react-router-dom";
+import { SelectedProduct } from "../../services/types";
+
+const ProductDetailView: React.FC = () => {
+  const location = useLocation();
+  const navigate = useNavigate();
+  const product = location.state as SelectedProduct;
+
+  const [selectedDays, setSelectedDays] = useState<number>(7);
+  const [selectedData, setSelectedData] = useState<string>("Unlimited");
+  const [simType, setSimType] = useState<"eSIM" | "Physical">("eSIM");
+  const [quantity, setQuantity] = useState<number>(1);
+
+  if (!product) {
+    return (
+      <div className="min-h-screen flex items-center justify-center">
+        <div className="text-center">
+          <p className="text-xl font-bold mb-4">Product details missing</p>
+          <Link to="/buy-sim" className="text-[#EE0434] font-bold underline">
+            Go to Shop
+          </Link>
+        </div>
+      </div>
+    );
+  }
+
+  const daysOptions = [3, 5, 7, 8, 10, 15, 30];
+  const dataOptions = [
+    "1GB",
+    "2GB",
+    "3GB",
+    "5GB",
+    "10GB",
+    "15GB",
+    "20GB",
+    "50GB",
+    "Unlimited",
+  ];
+
+  const getPrices = () => {
+    const dataMultiplier: Record<string, number> = {
+      "1GB": 1,
+      "2GB": 1.5,
+      "3GB": 2,
+      "5GB": 3,
+      "10GB": 5,
+      "15GB": 7,
+      "20GB": 9,
+      "50GB": 15,
+      Unlimited: 20,
+    };
+    const base = selectedDays * 1.5 + (dataMultiplier[selectedData] || 5);
+    const discount = 0.1;
+    const original = base * quantity;
+    const final = original * (1 - discount);
+    return {
+      original: original.toFixed(2),
+      final: final.toFixed(2),
+      discountPercent: (discount * 100).toFixed(0),
+    };
+  };
+
+  const prices = getPrices();
+
+  return (
+    <div className="bg-white min-h-screen pb-20">
+      <div className="max-w-7xl mx-auto px-4 py-4 md:py-6">
+        <nav className="flex items-center space-x-2 text-xs md:text-sm text-slate-500">
+          <Link to="/" className="hover:text-[#EE0434] transition-colors">
+            Home
+          </Link>
+          <svg
+            className="w-3 h-3"
+            fill="none"
+            stroke="currentColor"
+            viewBox="0 0 24 24"
+          >
+            <path
+              d="M9 5l7 7-7 7"
+              strokeWidth={2.5}
+              strokeLinecap="round"
+              strokeLinejoin="round"
+            />
+          </svg>
+          <span className="text-slate-900 font-bold">{product.country}</span>
+        </nav>
+      </div>
+
+      <div className="max-w-7xl mx-auto px-4 grid grid-cols-1 lg:grid-cols-12 gap-8 lg:gap-12">
+        <div className="lg:col-span-5 space-y-6">
+          <div className="relative aspect-[3/4] rounded-[24px] md:rounded-[32px] overflow-hidden shadow-2xl group">
+            <img
+              src={product.image}
+              alt={product.country}
+              className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110"
+            />
+            <div className="absolute top-6 left-6 flex items-start space-x-4">
+              <div className="w-12 h-12 md:w-16 md:h-16 rounded-full overflow-hidden border-2 border-white/50 shadow-lg">
+                <img
+                  src={`https://flagcdn.com/w160/${product.flag}.png`}
+                  alt={product.country}
+                  className="w-full h-full object-cover scale-150"
+                />
+              </div>
+              <div className="text-white">
+                <h1 className="text-2xl md:text-4xl font-black drop-shadow-md">
+                  SIM {product.country}
+                </h1>
+                <p className="text-sm md:text-lg font-bold text-white/90">
+                  Verified: <span className="text-[#EE0434]">High Speed</span>
+                </p>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <div className="lg:col-span-7 space-y-8 md:space-y-10 pt-4">
+          <div className="space-y-4">
+            <h3 className="text-lg md:text-xl font-black text-slate-900 tracking-tight">
+              Number of days
+            </h3>
+            <div className="flex flex-wrap gap-3">
+              {daysOptions.map((day) => (
+                <button
+                  key={day}
+                  onClick={() => setSelectedDays(day)}
+                  className={`min-w-[50px] md:min-w-[70px] h-10 md:h-14 rounded-xl md:rounded-2xl font-bold text-base md:text-xl transition-all border-2 ${
+                    selectedDays === day
+                      ? "border-[#EE0434] text-white bg-[#EE0434] shadow-md"
+                      : "border-slate-100 text-slate-300"
+                  }`}
+                >
+                  {day}
+                </button>
+              ))}
+            </div>
+          </div>
+
+          <div className="space-y-4">
+            <h3 className="text-lg md:text-xl font-black text-slate-900 tracking-tight">
+              Data
+            </h3>
+            <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3">
+              {dataOptions.map((data) => (
+                <button
+                  key={data}
+                  onClick={() => setSelectedData(data)}
+                  className={`h-10 md:h-14 rounded-xl md:rounded-2xl font-bold text-sm md:text-xl transition-all border-2 ${
+                    selectedData === data
+                      ? "border-[#EE0434] text-white bg-[#EE0434] shadow-md"
+                      : "border-slate-100 text-slate-300"
+                  }`}
+                >
+                  {data}
+                </button>
+              ))}
+            </div>
+          </div>
+
+          <div className="space-y-4">
+            <div className="flex p-1 bg-slate-50 rounded-2xl border border-slate-100">
+              <button
+                onClick={() => setSimType("eSIM")}
+                className={`flex-1 py-3 md:py-4 rounded-xl font-black text-sm md:text-2xl transition-all ${
+                  simType === "eSIM"
+                    ? "bg-[#EE0434] text-white shadow-lg"
+                    : "text-slate-300"
+                }`}
+              >
+                eSIM
+              </button>
+              <button
+                onClick={() => setSimType("Physical")}
+                className={`flex-1 py-3 md:py-4 rounded-xl font-black text-sm md:text-2xl transition-all ${
+                  simType === "Physical"
+                    ? "bg-[#EE0434] text-white shadow-lg"
+                    : "text-slate-300"
+                }`}
+              >
+                Physical SIM
+              </button>
+            </div>
+          </div>
+
+          <div className="bg-red-50/50 border border-[#EE0434]/20 rounded-[24px] md:rounded-[40px] p-6 md:p-10 space-y-6">
+            <div className="flex justify-between items-end">
+              <div className="flex items-center space-x-2">
+                <span className="text-[#EE0434] font-black text-lg">
+                  -{prices.discountPercent}%
+                </span>
+                <span className="text-slate-300 font-bold text-xs line-through">
+                  ${prices.original}
+                </span>
+              </div>
+              <span className="text-[#EE0434] font-black text-2xl md:text-3xl">
+                ${prices.final}
+              </span>
+            </div>
+            <button className="w-full bg-[#EE0434] text-white py-4 rounded-full font-black text-lg shadow-xl hover:scale-105 transition-all">
+              Buy now
+            </button>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default ProductDetailView;

+ 73 - 0
EsimLao/esim-vite/src/pages/support/SupportView.tsx

@@ -0,0 +1,73 @@
+
+import React, { useState } from 'react';
+import { Link } from 'react-router-dom';
+
+const SupportView: React.FC = () => {
+  const [activeItem, setActiveItem] = useState('What is travel eSIM/SIM?');
+
+  const categories = [
+    {
+      title: 'What is travel eSIM/SIM?',
+      count: 3,
+      icon: (
+        <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+          <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 00-3 3z" />
+        </svg>
+      ),
+      items: ['What is travel eSIM/SIM', 'Physical travel SIM', 'How to buy travel eSIM/SIM'],
+      color: 'bg-red-500'
+    },
+    {
+      title: 'eSIM installation and activation',
+      count: 3,
+      icon: (
+        <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+          <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" />
+        </svg>
+      ),
+      items: ['Email and eSIM QR code', 'Installation guide for iPhone (iOS)', 'Installation guide for Android'],
+      color: 'bg-red-600'
+    }
+  ];
+
+  return (
+    <div className="bg-white min-h-screen">
+      <div className="max-w-7xl mx-auto px-4 py-6">
+        <nav className="flex items-center space-x-2 text-sm text-slate-500 font-medium">
+          <Link to="/" className="hover:text-[#EE0434]">Home</Link>
+          <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M9 5l7 7-7 7" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round"/></svg>
+          <span className="text-slate-900 font-bold">Support</span>
+        </nav>
+      </div>
+
+      <div className="max-w-7xl mx-auto px-4 pb-20">
+        <div className="grid grid-cols-1 lg:grid-cols-12 gap-12">
+          <aside className="lg:col-span-4 space-y-8">
+            {categories.map((cat, idx) => (
+              <div key={idx} className="space-y-4">
+                <div className="flex items-center space-x-4">
+                  <div className={`w-10 h-10 rounded-xl ${cat.color} flex items-center justify-center text-white shadow-md`}>{cat.icon}</div>
+                  <h3 className="text-[17px] font-black text-[#EE0434] leading-tight">{cat.title}</h3>
+                </div>
+                <div className="pl-5 border-l-2 border-slate-50 space-y-4 ml-5">
+                  {cat.items.map((item, i) => (
+                    <button key={i} onClick={() => setActiveItem(item)} className="group flex items-center justify-between w-full text-left relative">
+                      <div className={`absolute -left-[22px] w-2 h-2 rounded-full border-2 border-white ${activeItem === item ? 'bg-[#EE0434] scale-125' : 'bg-slate-200'}`}></div>
+                      <span className={`text-[14px] font-bold transition-colors pl-4 ${activeItem === item ? 'text-[#EE0434]' : 'text-slate-500 group-hover:text-[#EE0434]'}`}>{item}</span>
+                    </button>
+                  ))}
+                </div>
+              </div>
+            ))}
+          </aside>
+          <main className="lg:col-span-8">
+            <h1 className="text-4xl md:text-5xl font-black text-[#EE0434] mb-10">Guide & Help</h1>
+            <p className="text-lg leading-relaxed text-slate-600">Select a topic from the left to view detailed instructions for your travel SIM or eSIM. Our support team is also available 24/7 via chat.</p>
+          </main>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default SupportView;

+ 95 - 0
EsimLao/esim-vite/src/services/gemini.ts

@@ -0,0 +1,95 @@
+
+import { GoogleGenAI, Type, GenerateContentResponse, Modality } from "@google/genai";
+import { ChatMessage, GroundingSource } from "./types";
+
+export const getGeminiClient = () => {
+  return new GoogleGenAI({ apiKey: process.env.API_KEY || '' });
+};
+
+export const chatStream = async (
+  history: ChatMessage[],
+  onChunk: (text: string) => void
+) => {
+  const ai = getGeminiClient();
+  const chat = ai.chats.create({
+    model: 'gemini-3-flash-preview',
+    config: {
+      systemInstruction: 'You are Lumina, a world-class AI assistant. Be concise, professional, and helpful.',
+    }
+  });
+
+  // Convert custom history to Gemini format if needed, 
+  // but for a simple stream we can just send the last message for this demo architecture.
+  const lastMsg = history[history.length - 1].text;
+  const result = await chat.sendMessageStream({ message: lastMsg });
+
+  for await (const chunk of result) {
+    const text = chunk.text || '';
+    onChunk(text);
+  }
+};
+
+export const generateImage = async (prompt: string, aspectRatio: "1:1" | "16:9" | "9:16" = "1:1") => {
+  const ai = getGeminiClient();
+  const response = await ai.models.generateContent({
+    model: 'gemini-2.5-flash-image',
+    contents: { parts: [{ text: prompt }] },
+    config: {
+      imageConfig: { aspectRatio }
+    }
+  });
+
+  for (const part of response.candidates?.[0]?.content?.parts || []) {
+    if (part.inlineData) {
+      return `data:${part.inlineData.mimeType};base64,${part.inlineData.data}`;
+    }
+  }
+  throw new Error("No image data returned from API");
+};
+
+export const generateVideo = async (prompt: string, aspectRatio: "16:9" | "9:16" = "16:9") => {
+  const ai = getGeminiClient();
+  let operation = await ai.models.generateVideos({
+    model: 'veo-3.1-fast-generate-preview',
+    prompt: prompt,
+    config: {
+      numberOfVideos: 1,
+      resolution: '720p',
+      aspectRatio: aspectRatio
+    }
+  });
+
+  while (!operation.done) {
+    await new Promise(resolve => setTimeout(resolve, 5000));
+    operation = await ai.operations.getVideosOperation({ operation: operation });
+  }
+
+  const downloadLink = operation.response?.generatedVideos?.[0]?.video?.uri;
+  if (!downloadLink) throw new Error("Video generation failed");
+
+  const response = await fetch(`${downloadLink}&key=${process.env.API_KEY}`);
+  const blob = await response.blob();
+  return URL.createObjectURL(blob);
+};
+
+export const searchGrounding = async (query: string) => {
+  const ai = getGeminiClient();
+  const response = await ai.models.generateContent({
+    model: 'gemini-3-flash-preview',
+    contents: query,
+    config: {
+      tools: [{ googleSearch: {} }]
+    }
+  });
+
+  const text = response.text || '';
+  const chunks = response.candidates?.[0]?.groundingMetadata?.groundingChunks || [];
+  const sources: GroundingSource[] = chunks
+    .filter((c: any) => c.web)
+    .map((c: any) => ({
+      title: c.web.title,
+      uri: c.web.uri
+    }));
+
+  return { text, sources };
+};

+ 13 - 0
EsimLao/esim-vite/src/services/product/type.ts

@@ -0,0 +1,13 @@
+export interface AreaData {
+    id: number;
+    areaCode: string;
+    areaName1: string;
+    areaName2: string;
+    status: number;
+    imgUrl: string | null;
+    iconUrl: string | null;
+    isPopular: number;
+    isCountry: number;
+    minDisplayPrice: number;
+    minSellPrice: number;
+}

+ 78 - 0
EsimLao/esim-vite/src/services/types.ts

@@ -0,0 +1,78 @@
+export enum ViewMode {
+  HOME = 'home',
+  CHAT = 'chat',
+  IMAGE = 'image',
+  VIDEO = 'video',
+  SEARCH = 'search',
+  PRODUCT = 'product',
+  BUY_SIM = 'buy_sim',
+  SUPPORT = 'support',
+  NEWS = 'news',
+  ARTICLE_DETAIL = 'article_detail',
+  LOGIN = 'login',
+  CONTACT = 'contact'
+}
+
+export interface ChatMessage {
+  role: 'user' | 'model';
+  text: string;
+  timestamp: Date;
+}
+
+export interface GroundingSource {
+  title: string;
+  uri: string;
+}
+
+export interface GeneratedImage {
+  url: string;
+  prompt: string;
+  timestamp: Date;
+}
+
+export interface GeneratedVideo {
+  url: string;
+  prompt: string;
+  timestamp: Date;
+}
+
+export interface TestimonialType {
+  name: string;
+  location: string;
+  rating: number;
+  avatar: string;
+  content: string;
+}
+
+export interface SimProduct {
+  id: string;
+  country: string;
+  flag: string;
+  originalPrice: string;
+  discountPrice: string;
+  discountLabel: string;
+  isRegion?: boolean;
+}
+
+export interface SelectedProduct {
+  country: string;
+  flag: string;
+  image: string;
+}
+
+export interface NewsArticle {
+  id: string;
+  title: string;
+  date: string;
+  excerpt: string;
+  image: string;
+}
+
+// Fixed missing export for HandbookView.tsx
+export interface HandbookArticle {
+  id: string;
+  title: string;
+  date: string;
+  excerpt: string;
+  image: string;
+}

+ 10 - 0
EsimLao/esim-vite/src/vite-env.d.ts

@@ -0,0 +1,10 @@
+/// <reference types="vite/client" />
+
+interface ImportMetaEnv {
+    readonly VITE_API_AUTH_URL: string
+    readonly VITE_API_PRODUCT_URL: string
+}
+
+interface ImportMeta {
+    readonly env: ImportMetaEnv
+}

+ 22 - 0
EsimLao/esim-vite/tsconfig.json

@@ -0,0 +1,22 @@
+{
+  "compilerOptions": {
+    "target": "ES2022",
+    "experimentalDecorators": true,
+    "useDefineForClassFields": false,
+    "module": "ESNext",
+    "lib": ["ES2022", "DOM", "DOM.Iterable"],
+    "skipLibCheck": true,
+    "types": ["node"],
+    "moduleResolution": "bundler",
+    "isolatedModules": true,
+    "moduleDetection": "force",
+    "allowJs": true,
+    "jsx": "react-jsx",
+    "paths": {
+      "@/*": ["./*"]
+    },
+    "allowImportingTsExtensions": true,
+    "noEmit": true
+  },
+  "include": ["src"]
+}

+ 23 - 0
EsimLao/esim-vite/vite.config.ts

@@ -0,0 +1,23 @@
+import path from 'path';
+import { defineConfig, loadEnv } from 'vite';
+import react from '@vitejs/plugin-react';
+
+export default defineConfig(({ mode }) => {
+  const env = loadEnv(mode, './', '');
+  return {
+    server: {
+      port: 3000,
+      host: '0.0.0.0',
+    },
+    plugins: [react()],
+    define: {
+      'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY),
+      'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY)
+    },
+    // resolve: {
+    //   alias: {
+    //     '@': path.resolve(__dirname, '.'),
+    //   }
+    // }
+  };
+});