diff --git a/src/problem1/sum_to_n.js b/src/problem1/sum_to_n.js
new file mode 100644
index 0000000000..4e93b97f6b
--- /dev/null
+++ b/src/problem1/sum_to_n.js
@@ -0,0 +1,57 @@
+/**
+ * Problem: Summation from 1 to n.
+ * Input: n — integer (result assumed < Number.MAX_SAFE_INTEGER).
+ * Output: 1 + 2 + ... + n (e.g. sum_to_n(5) === 15).
+ *
+ * Edge cases handled:
+ * - n < 1: returns 0 (empty sum).
+ * - Non-integer n: truncated via Math.floor for predictable behavior.
+ */
+
+/**
+ * Implementation A: Closed-form formula.
+ * Uses the identity: 1 + 2 + ... + n = n * (n + 1) / 2.
+ *
+ * Time: O(1)
+ * Space: O(1)
+ */
+var sum_to_n_a = function (n) {
+ var k = Math.floor(Number(n));
+ if (k < 1) return 0;
+ return (k * (k + 1)) / 2;
+};
+
+/**
+ * Implementation B: Iterative loop (for).
+ * Accumulates the sum in a single variable.
+ *
+ * Time: O(n)
+ * Space: O(1)
+ */
+var sum_to_n_b = function (n) {
+ var k = Math.floor(Number(n));
+ if (k < 1) return 0;
+ var sum = 0;
+ for (var i = 1; i <= k; i++) {
+ sum += i;
+ }
+ return sum;
+};
+
+/**
+ * Implementation C: Recursion.
+ * Base case: n < 1 → 0. Otherwise: n + sum_to_n_c(n - 1).
+ *
+ * Time: O(n)
+ * Space: O(n) — call stack depth
+ */
+var sum_to_n_c = function (n) {
+ var k = Math.floor(Number(n));
+ if (k < 1) return 0;
+ return k + sum_to_n_c(k - 1);
+};
+
+// Export for tests and reuse
+if (typeof module !== "undefined" && module.exports) {
+ module.exports = { sum_to_n_a, sum_to_n_b, sum_to_n_c };
+}
diff --git a/src/problem1/sum_to_n.test.js b/src/problem1/sum_to_n.test.js
new file mode 100644
index 0000000000..70c53141bd
--- /dev/null
+++ b/src/problem1/sum_to_n.test.js
@@ -0,0 +1,51 @@
+/**
+ * Tests for sum_to_n implementations.
+ * Run with: node sum_to_n.test.js
+ */
+
+var assert = require("assert");
+var { sum_to_n_a, sum_to_n_b, sum_to_n_c } = require("./sum_to_n.js");
+
+var implementations = [
+ { name: "sum_to_n_a", fn: sum_to_n_a },
+ { name: "sum_to_n_b", fn: sum_to_n_b },
+ { name: "sum_to_n_c", fn: sum_to_n_c },
+];
+
+function runTests() {
+ var passed = 0;
+ var failed = 0;
+
+ function test(description, n, expected) {
+ implementations.forEach(function (impl) {
+ try {
+ var result = impl.fn(n);
+ assert.strictEqual(
+ result,
+ expected,
+ impl.name + "(" + n + ") expected " + expected + ", got " + result
+ );
+ passed++;
+ } catch (err) {
+ console.error("FAIL " + impl.name + ": " + description + " — " + err.message);
+ failed++;
+ }
+ });
+ }
+
+ // Normal cases
+ test("sum to 5", 5, 15);
+ test("sum to 1", 1, 1);
+ test("sum to 10", 10, 55);
+ test("sum to 100", 100, 5050);
+
+ // Edge cases
+ test("n = 0", 0, 0);
+ test("n negative", -5, 0);
+ test("non-integer (floor)", 5.9, 15);
+
+ console.log("Tests: " + passed + " passed, " + failed + " failed");
+ process.exit(failed > 0 ? 1 : 0);
+}
+
+runTests();
diff --git a/src/problem2/script.js b/src/problem2/.keep
similarity index 100%
rename from src/problem2/script.js
rename to src/problem2/.keep
diff --git a/src/problem2/App.jsx b/src/problem2/App.jsx
new file mode 100644
index 0000000000..d3864f5b0d
--- /dev/null
+++ b/src/problem2/App.jsx
@@ -0,0 +1,10 @@
+import React from "react";
+import { SwapForm } from "./src/components/SwapForm";
+
+export default function App() {
+ return (
+
+
+
+ );
+}
diff --git a/src/problem2/README.md b/src/problem2/README.md
new file mode 100644
index 0000000000..2abf049004
--- /dev/null
+++ b/src/problem2/README.md
@@ -0,0 +1,46 @@
+# Problem 2: Currency Swap Form
+
+Form đổi token dùng **Vite** + **React**. Logic viết bằng **TypeScript** (services, hooks, utils, types); UI giữ bằng JSX trong `src/components`.
+
+## Chạy
+
+```bash
+cd src/problem2
+npm install
+npm run dev
+```
+
+Mở http://localhost:5173
+
+## Nguồn dữ liệu
+
+- **Giá token:** https://interview.switcheo.com/prices.json
+- **Icon token:** https://github.com/Switcheo/token-icons/tree/main/tokens (vd: SWTH.svg)
+
+---
+
+## Cấu trúc (components, logic = TS)
+
+```
+src/
+├── components/ # UI (JSX)
+│ ├── SwapForm.jsx
+│ ├── SwapCard.jsx
+│ ├── TokenSelect.jsx
+│ ├── TokenIcon.jsx
+│ └── SwapFormStyles.jsx
+├── hooks/ # Logic (TypeScript)
+│ ├── useTokensWithPrices.ts
+│ └── useSwapForm.ts
+├── services/ # API (TypeScript)
+│ └── priceService.ts
+├── utils/ # Hàm thuần (TypeScript)
+│ ├── tokenIcon.ts
+│ ├── exchange.ts
+│ └── validation.ts
+└── types/ # Định nghĩa type (TypeScript)
+ └── index.ts
+```
+
+- **Logic (TS):** `types`, `services`, `hooks`, `utils` — toàn bộ file `.ts`.
+- **UI (JSX):** `components` — file `.jsx`, chỉ render và nhận props, gọi hooks từ container.
diff --git a/src/problem2/index.css b/src/problem2/index.css
new file mode 100644
index 0000000000..5038f0d445
--- /dev/null
+++ b/src/problem2/index.css
@@ -0,0 +1,51 @@
+:root {
+ --bg-primary: #0d0f14;
+ --bg-card: #151922;
+ --bg-input: #1a1e28;
+ --border: #2a3142;
+ --text-primary: #e6e8ec;
+ --text-secondary: #8b92a5;
+ --accent: #00d395;
+ --accent-dim: #00a876;
+ --error: #ff5c5c;
+ --radius: 14px;
+ --radius-sm: 10px;
+ --shadow: 0 12px 40px rgba(0, 0, 0, 0.4);
+ --font-sans: "DM Sans", -apple-system, BlinkMacSystemFont, sans-serif;
+}
+
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+ min-height: 100vh;
+ background: var(--bg-primary);
+ color: var(--text-primary);
+ font-family: var(--font-sans);
+ font-size: 16px;
+ line-height: 1.5;
+ -webkit-font-smoothing: antialiased;
+}
+
+.app {
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 24px;
+ width: 100%;
+}
+
+#root {
+ width: 100%;
+ max-width: 520px;
+ margin: 0 auto;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 100vh;
+}
diff --git a/src/problem2/index.html b/src/problem2/index.html
index 4058a68bff..6d53a41a31 100644
--- a/src/problem2/index.html
+++ b/src/problem2/index.html
@@ -1,27 +1,16 @@
-
-
-
-
- Fancy Form
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+ Currency Swap
+
+
+
+
+
+
+
+
+
diff --git a/src/problem2/main.jsx b/src/problem2/main.jsx
new file mode 100644
index 0000000000..f18b0f0d14
--- /dev/null
+++ b/src/problem2/main.jsx
@@ -0,0 +1,10 @@
+import React from "react";
+import ReactDOM from "react-dom/client";
+import App from "./App";
+import "./index.css";
+
+ReactDOM.createRoot(document.getElementById("root")).render(
+
+
+
+);
diff --git a/src/problem2/package-lock.json b/src/problem2/package-lock.json
new file mode 100644
index 0000000000..50c1daff37
--- /dev/null
+++ b/src/problem2/package-lock.json
@@ -0,0 +1,1729 @@
+{
+ "name": "currency-swap-form",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "currency-swap-form",
+ "version": "1.0.0",
+ "dependencies": {
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ },
+ "devDependencies": {
+ "@types/react": "^18.2.0",
+ "@types/react-dom": "^18.2.0",
+ "@vitejs/plugin-react": "^4.2.1",
+ "typescript": "^5.3.0",
+ "vite": "^5.4.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
+ "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
+ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helpers": "^7.28.6",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/traverse": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@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.29.1",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
+ "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@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.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.28.6",
+ "@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.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
+ "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
+ "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.6",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
+ "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
+ "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.0"
+ },
+ "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.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
+ "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+ "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.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "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/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.27",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
+ "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
+ "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
+ "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
+ "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
+ "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
+ "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
+ "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
+ "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
+ "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
+ "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
+ "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
+ "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
+ "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
+ "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
+ "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
+ "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
+ "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
+ "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
+ "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
+ "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
+ "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
+ "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
+ "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
+ "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
+ "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "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/prop-types": {
+ "version": "15.7.15",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.28",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
+ "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.3.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^18.0.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.28.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.27",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz",
+ "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "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/caniuse-lite": {
+ "version": "1.0.30001777",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz",
+ "integrity": "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==",
+ "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/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/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.307",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz",
+ "integrity": "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "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/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/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/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "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/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/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "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/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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-releases": {
+ "version": "2.0.36",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz",
+ "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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/postcss": {
+ "version": "8.5.8",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
+ "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
+ "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/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
+ "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
+ "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.59.0",
+ "@rollup/rollup-android-arm64": "4.59.0",
+ "@rollup/rollup-darwin-arm64": "4.59.0",
+ "@rollup/rollup-darwin-x64": "4.59.0",
+ "@rollup/rollup-freebsd-arm64": "4.59.0",
+ "@rollup/rollup-freebsd-x64": "4.59.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
+ "@rollup/rollup-linux-arm-musleabihf": "4.59.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.59.0",
+ "@rollup/rollup-linux-arm64-musl": "4.59.0",
+ "@rollup/rollup-linux-loong64-gnu": "4.59.0",
+ "@rollup/rollup-linux-loong64-musl": "4.59.0",
+ "@rollup/rollup-linux-ppc64-gnu": "4.59.0",
+ "@rollup/rollup-linux-ppc64-musl": "4.59.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.59.0",
+ "@rollup/rollup-linux-riscv64-musl": "4.59.0",
+ "@rollup/rollup-linux-s390x-gnu": "4.59.0",
+ "@rollup/rollup-linux-x64-gnu": "4.59.0",
+ "@rollup/rollup-linux-x64-musl": "4.59.0",
+ "@rollup/rollup-openbsd-x64": "4.59.0",
+ "@rollup/rollup-openharmony-arm64": "4.59.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.59.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.59.0",
+ "@rollup/rollup-win32-x64-gnu": "4.59.0",
+ "@rollup/rollup-win32-x64-msvc": "4.59.0",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "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/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/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "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/vite": {
+ "version": "5.4.21",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.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",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "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"
+ }
+ }
+}
diff --git a/src/problem2/package.json b/src/problem2/package.json
new file mode 100644
index 0000000000..3c89a90899
--- /dev/null
+++ b/src/problem2/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "currency-swap-form",
+ "private": true,
+ "version": "1.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ },
+ "devDependencies": {
+ "@types/react": "^18.2.0",
+ "@types/react-dom": "^18.2.0",
+ "@vitejs/plugin-react": "^4.2.1",
+ "typescript": "^5.3.0",
+ "vite": "^5.4.0"
+ }
+}
diff --git a/src/problem2/src/components/SwapCard.jsx b/src/problem2/src/components/SwapCard.jsx
new file mode 100644
index 0000000000..a5801e6acf
--- /dev/null
+++ b/src/problem2/src/components/SwapCard.jsx
@@ -0,0 +1,135 @@
+import React from "react";
+import { TokenSelect } from "./TokenSelect";
+
+export function SwapCard({
+ title = "Swap",
+ tokens = [],
+ tokensLoading,
+ tokensError,
+ onRetry,
+ fromToken,
+ toToken,
+ fromAmountRaw,
+ fromAmountError,
+ toAmount,
+ exchangeRate,
+ submitState,
+ canSubmit,
+ sameTokenError,
+ onFromTokenSelect,
+ onToTokenSelect,
+ onFromAmountChange,
+ onSwapDirection,
+ onSubmit,
+}) {
+ return (
+
+
{title}
+
+ {tokensError && (
+
+ {tokensError}
+
+
+ )}
+
+ {tokensLoading && (
+
Loading tokens…
+ )}
+
+ {!tokensLoading && (
+
+ )}
+
+ );
+}
diff --git a/src/problem2/src/components/SwapForm.jsx b/src/problem2/src/components/SwapForm.jsx
new file mode 100644
index 0000000000..13d2575234
--- /dev/null
+++ b/src/problem2/src/components/SwapForm.jsx
@@ -0,0 +1,75 @@
+import React, { useState, useEffect, useRef } from "react";
+import { useTokensWithPrices } from "../hooks/useTokensWithPrices";
+import { useSwapForm } from "../hooks/useSwapForm";
+import { SwapCard } from "./SwapCard";
+import { SwapFormStyles } from "./SwapFormStyles";
+import { Toast } from "./Toast";
+
+function formatAmount(num) {
+ if (num >= 1e6) return num.toExponential(2);
+ if (num >= 1) return num.toFixed(4);
+ if (num > 0) return num.toFixed(6);
+ return "0";
+}
+
+export function SwapForm() {
+ const { tokens, loading: tokensLoading, error: tokensError, refetch } = useTokensWithPrices();
+ const swap = useSwapForm(tokens);
+ const [toast, setToast] = useState(null);
+ const prevSubmitState = useRef(swap.submitState);
+
+ useEffect(() => {
+ if (swap.submitState === "success" && prevSubmitState.current !== "success") {
+ const s = swap.lastSwapSummary;
+ const msg = s
+ ? `Swapped ${formatAmount(s.fromAmount)} ${s.fromCurrency} for ${formatAmount(s.toAmount)} ${s.toCurrency}`
+ : "Swap successful";
+ setToast({ message: msg, type: "success" });
+ } else if (swap.submitState === "error" && prevSubmitState.current !== "error") {
+ const s = swap.lastSwapSummary;
+ const msg = s
+ ? `Swap failed: ${formatAmount(s.fromAmount)} ${s.fromCurrency} → ${formatAmount(s.toAmount)} ${s.toCurrency}. Please try again.`
+ : "Swap failed. Please try again.";
+ setToast({ message: msg, type: "error" });
+ }
+ prevSubmitState.current = swap.submitState;
+ }, [swap.submitState, swap.lastSwapSummary]);
+
+ return (
+ <>
+
+ {toast && (
+
+ setToast(null)}
+ duration={4000}
+ />
+
+ )}
+
+ >
+ );
+}
diff --git a/src/problem2/src/components/SwapFormStyles.jsx b/src/problem2/src/components/SwapFormStyles.jsx
new file mode 100644
index 0000000000..2f92f10513
--- /dev/null
+++ b/src/problem2/src/components/SwapFormStyles.jsx
@@ -0,0 +1,390 @@
+import React from "react";
+
+export function SwapFormStyles() {
+ return (
+
+ );
+}
diff --git a/src/problem2/src/components/Toast.jsx b/src/problem2/src/components/Toast.jsx
new file mode 100644
index 0000000000..2e41a7a19b
--- /dev/null
+++ b/src/problem2/src/components/Toast.jsx
@@ -0,0 +1,27 @@
+import React, { useEffect } from "react";
+
+export function Toast({ message, type = "info", onDismiss, duration = 4000 }) {
+ useEffect(() => {
+ if (!duration || !onDismiss) return;
+ const id = setTimeout(onDismiss, duration);
+ return () => clearTimeout(id);
+ }, [duration, onDismiss]);
+
+ return (
+
+ {message}
+
+
+ );
+}
diff --git a/src/problem2/src/components/TokenIcon.jsx b/src/problem2/src/components/TokenIcon.jsx
new file mode 100644
index 0000000000..8e2e865517
--- /dev/null
+++ b/src/problem2/src/components/TokenIcon.jsx
@@ -0,0 +1,24 @@
+import React, { useState } from "react";
+
+export function TokenIcon({ src, symbol, className = "", priority = false }) {
+ const [imageError, setImageError] = useState(false);
+ const showFallback = !src || imageError;
+
+ return (
+
+ {showFallback ? (
+ {symbol?.slice(0, 1) ?? "?"}
+ ) : (
+
setImageError(true)}
+ />
+ )}
+
+ );
+}
diff --git a/src/problem2/src/components/TokenSelect.jsx b/src/problem2/src/components/TokenSelect.jsx
new file mode 100644
index 0000000000..93810c096e
--- /dev/null
+++ b/src/problem2/src/components/TokenSelect.jsx
@@ -0,0 +1,64 @@
+import React, { useState, useRef, useEffect } from "react";
+import { TokenIcon } from "./TokenIcon";
+
+export function TokenSelect({ tokens, selectedToken, onSelect, label, disabled }) {
+ const [open, setOpen] = useState(false);
+ const listRef = useRef(null);
+
+ useEffect(() => {
+ if (!open) return;
+ const handleClickOutside = (e) => {
+ if (listRef.current && !listRef.current.contains(e.target)) setOpen(false);
+ };
+ document.addEventListener("click", handleClickOutside);
+ return () => document.removeEventListener("click", handleClickOutside);
+ }, [open]);
+
+ return (
+
+
+
+ {open && (
+
+ {tokens.map((token) => (
+ -
+
+
+ ))}
+
+ )}
+
+ );
+}
diff --git a/src/problem2/src/hooks/useSwapForm.ts b/src/problem2/src/hooks/useSwapForm.ts
new file mode 100644
index 0000000000..0b2ea796f3
--- /dev/null
+++ b/src/problem2/src/hooks/useSwapForm.ts
@@ -0,0 +1,100 @@
+import { useState, useMemo, useCallback, FormEvent } from "react";
+import { convertAmount, getExchangeRate } from "../utils/exchange";
+import { validateAmount } from "../utils/validation";
+import type { Token } from "../types";
+
+const INITIAL_FROM_AMOUNT = "";
+const SUBMIT_DELAY_MS = 1800;
+
+export type SubmitState = "idle" | "loading" | "success" | "error";
+
+export interface LastSwapSummary {
+ fromAmount: number;
+ fromCurrency: string;
+ toAmount: number;
+ toCurrency: string;
+}
+
+export function useSwapForm(_tokens: Token[]) {
+ const [fromToken, setFromToken] = useState(null);
+ const [toToken, setToToken] = useState(null);
+ const [fromAmountRaw, setFromAmountRaw] = useState(INITIAL_FROM_AMOUNT);
+ const [submitState, setSubmitState] = useState("idle");
+ const [lastSwapSummary, setLastSwapSummary] = useState(null);
+
+ const fromAmount = fromAmountRaw === "" ? 0 : Number(fromAmountRaw);
+ const fromAmountError = validateAmount(fromAmountRaw);
+
+ const toAmount = useMemo(() => {
+ if (!fromToken || !toToken || fromAmount <= 0) return 0;
+ return convertAmount(fromAmount, fromToken, toToken);
+ }, [fromToken, toToken, fromAmount]);
+
+ const exchangeRate = useMemo(() => {
+ if (!fromToken || !toToken) return null;
+ return getExchangeRate(fromToken, toToken);
+ }, [fromToken, toToken]);
+
+ const handleSwapDirection = useCallback(() => {
+ setFromToken(toToken);
+ setToToken(fromToken);
+ setFromAmountRaw(toAmount > 0 ? String(toAmount) : "");
+ }, [fromToken, toToken, toAmount]);
+
+ const canSubmit =
+ !!fromToken &&
+ !!toToken &&
+ fromToken.currency !== toToken.currency &&
+ fromAmount > 0 &&
+ !fromAmountError &&
+ submitState !== "loading";
+
+ const handleSubmit = useCallback(
+ async (e: FormEvent) => {
+ e.preventDefault();
+ if (!canSubmit || !fromToken || !toToken) return;
+ const fromAmt = fromAmountRaw === "" ? 0 : Number(fromAmountRaw);
+ const toAmt = convertAmount(fromAmt, fromToken, toToken);
+ setSubmitState("loading");
+ try {
+ await new Promise((r) => setTimeout(r, SUBMIT_DELAY_MS));
+ setLastSwapSummary({
+ fromAmount: fromAmt,
+ fromCurrency: fromToken.currency,
+ toAmount: toAmt,
+ toCurrency: toToken.currency,
+ });
+ setSubmitState("success");
+ setFromAmountRaw(INITIAL_FROM_AMOUNT);
+ setTimeout(() => setSubmitState("idle"), 2000);
+ } catch {
+ setLastSwapSummary({
+ fromAmount: fromAmt,
+ fromCurrency: fromToken.currency,
+ toAmount: toAmt,
+ toCurrency: toToken.currency,
+ });
+ setSubmitState("error");
+ setTimeout(() => setSubmitState("idle"), 3000);
+ }
+ },
+ [canSubmit, fromToken, toToken, fromAmountRaw]
+ );
+
+ return {
+ fromToken,
+ toToken,
+ fromAmountRaw,
+ fromAmountError,
+ toAmount,
+ exchangeRate,
+ submitState,
+ canSubmit,
+ lastSwapSummary,
+ setFromToken,
+ setToToken,
+ setFromAmountRaw,
+ handleSwapDirection,
+ handleSubmit,
+ };
+}
diff --git a/src/problem2/src/hooks/useTokensWithPrices.ts b/src/problem2/src/hooks/useTokensWithPrices.ts
new file mode 100644
index 0000000000..7def36ca4f
--- /dev/null
+++ b/src/problem2/src/hooks/useTokensWithPrices.ts
@@ -0,0 +1,35 @@
+import { useState, useEffect, useCallback } from "react";
+import { fetchTokensWithPrices } from "../services/priceService";
+import { preloadTokenImages } from "../utils/imagePreload";
+import type { Token } from "../types";
+
+export function useTokensWithPrices(): {
+ tokens: Token[];
+ loading: boolean;
+ error: string | null;
+ refetch: () => Promise;
+} {
+ const [tokens, setTokens] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ const refetch = useCallback(async () => {
+ setLoading(true);
+ setError(null);
+ try {
+ const data = await fetchTokensWithPrices();
+ setTokens(data);
+ preloadTokenImages(data.map((t) => t.imageUrl));
+ } catch (e) {
+ setError(e instanceof Error ? e.message : "Something went wrong");
+ } finally {
+ setLoading(false);
+ }
+ }, []);
+
+ useEffect(() => {
+ refetch();
+ }, [refetch]);
+
+ return { tokens, loading, error, refetch };
+}
diff --git a/src/problem2/src/services/priceService.ts b/src/problem2/src/services/priceService.ts
new file mode 100644
index 0000000000..d5b2273df4
--- /dev/null
+++ b/src/problem2/src/services/priceService.ts
@@ -0,0 +1,30 @@
+import type { PriceRecord, Token } from "../types";
+import { getTokenImageUrl } from "../utils/tokenIcon";
+
+const PRICES_URL = "https://interview.switcheo.com/prices.json";
+
+export async function fetchTokensWithPrices(): Promise {
+ const res = await fetch(PRICES_URL);
+ if (!res.ok) {
+ throw new Error("Failed to load token prices. Please try again.");
+ }
+ const data: PriceRecord[] = await res.json();
+
+ const byCurrency = new Map();
+ for (const row of data) {
+ if (row.price == null || row.price <= 0) continue;
+ const existing = byCurrency.get(row.currency);
+ if (
+ !existing ||
+ new Date(row.date).getTime() > new Date(existing.date).getTime()
+ ) {
+ byCurrency.set(row.currency, row);
+ }
+ }
+
+ return Array.from(byCurrency.values()).map((row) => ({
+ currency: row.currency,
+ price: row.price,
+ imageUrl: getTokenImageUrl(row.currency),
+ }));
+}
diff --git a/src/problem2/src/types/index.ts b/src/problem2/src/types/index.ts
new file mode 100644
index 0000000000..71b7e82c78
--- /dev/null
+++ b/src/problem2/src/types/index.ts
@@ -0,0 +1,11 @@
+export interface PriceRecord {
+ currency: string;
+ date: string;
+ price: number;
+}
+
+export interface Token {
+ currency: string;
+ price: number;
+ imageUrl: string;
+}
diff --git a/src/problem2/src/utils/exchange.ts b/src/problem2/src/utils/exchange.ts
new file mode 100644
index 0000000000..9023849810
--- /dev/null
+++ b/src/problem2/src/utils/exchange.ts
@@ -0,0 +1,15 @@
+import type { Token } from "../types";
+
+export function getExchangeRate(fromToken: Token, toToken: Token): number {
+ if (!fromToken?.price || !toToken?.price) return 0;
+ return fromToken.price / toToken.price;
+}
+
+export function convertAmount(
+ amount: number,
+ fromToken: Token,
+ toToken: Token
+): number {
+ const rate = getExchangeRate(fromToken, toToken);
+ return amount * rate;
+}
diff --git a/src/problem2/src/utils/imagePreload.ts b/src/problem2/src/utils/imagePreload.ts
new file mode 100644
index 0000000000..4e51604b4d
--- /dev/null
+++ b/src/problem2/src/utils/imagePreload.ts
@@ -0,0 +1,14 @@
+/**
+ * Preload token images in background so dropdown shows them from cache.
+ */
+
+const preloadCache = new Set();
+
+export function preloadTokenImages(urls: string[], limit = 20): void {
+ const toLoad = urls.filter(Boolean).filter((url) => !preloadCache.has(url));
+ toLoad.slice(0, limit).forEach((url) => {
+ preloadCache.add(url);
+ const img = new Image();
+ img.src = url;
+ });
+}
diff --git a/src/problem2/src/utils/tokenIcon.ts b/src/problem2/src/utils/tokenIcon.ts
new file mode 100644
index 0000000000..d76ed2bc2c
--- /dev/null
+++ b/src/problem2/src/utils/tokenIcon.ts
@@ -0,0 +1,7 @@
+const TOKEN_ICONS_BASE =
+ "https://raw.githubusercontent.com/Switcheo/token-icons/main/tokens";
+
+export function getTokenImageUrl(currency: string): string {
+ if (!currency || typeof currency !== "string") return "";
+ return `${TOKEN_ICONS_BASE}/${encodeURIComponent(currency)}.svg`;
+}
diff --git a/src/problem2/src/utils/validation.ts b/src/problem2/src/utils/validation.ts
new file mode 100644
index 0000000000..5f1348d870
--- /dev/null
+++ b/src/problem2/src/utils/validation.ts
@@ -0,0 +1,7 @@
+export function validateAmount(value: string): string | null {
+ if (value === "" || value == null) return null;
+ const num = Number(value);
+ if (Number.isNaN(num)) return "Enter a valid number";
+ if (num <= 0) return "Amount must be greater than 0";
+ return null;
+}
diff --git a/src/problem2/style.css b/src/problem2/style.css
deleted file mode 100644
index 915af91c72..0000000000
--- a/src/problem2/style.css
+++ /dev/null
@@ -1,8 +0,0 @@
-body {
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: center;
- min-width: 360px;
- font-family: Arial, Helvetica, sans-serif;
-}
diff --git a/src/problem2/tsconfig.json b/src/problem2/tsconfig.json
new file mode 100644
index 0000000000..1f9520c128
--- /dev/null
+++ b/src/problem2/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["src/*"]
+ }
+ },
+ "include": ["src"]
+}
diff --git a/src/problem2/vite.config.js b/src/problem2/vite.config.js
new file mode 100644
index 0000000000..86759bd80f
--- /dev/null
+++ b/src/problem2/vite.config.js
@@ -0,0 +1,7 @@
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+
+export default defineConfig({
+ plugins: [react()],
+ root: ".",
+});
diff --git a/src/problem3/ANALYSIS.md b/src/problem3/ANALYSIS.md
new file mode 100644
index 0000000000..7d5ea3c9b6
--- /dev/null
+++ b/src/problem3/ANALYSIS.md
@@ -0,0 +1,164 @@
+# Problem 3: WalletPage — Computational Inefficiencies & Anti-Patterns
+
+## 1. Issues and How to Improve
+
+### 1.1 Bug: Wrong variable in filter (`lhsPriority`)
+
+**Issue:** In the filter callback, the code checks `if (lhsPriority > -99)` but `lhsPriority` is never defined in that scope. The variable that was computed is `balancePriority`.
+
+**Impact:** Either a ReferenceError at runtime or (if `lhsPriority` exists elsewhere) incorrect filtering.
+
+**Fix:** Use `balancePriority` instead of `lhsPriority` in the condition.
+
+---
+
+### 1.2 Bug: Inverted / unclear filter logic
+
+**Issue:** The filter returns `true` when `balance.amount <= 0`, so only zero or negative balances are kept. Typically we want to show balances that are meaningful (e.g. positive, or at least not negative). The condition is also nested incorrectly: we only consider amount when priority > -99, which keeps zero/negative balances for known blockchains.
+
+**Impact:** Wrong set of balances is displayed (likely only empty/negative ones).
+
+**Fix:** Clarify requirements. Usually we want to **exclude** zero/negative balances: return `false` when `balance.amount <= 0`, and keep balances with `balancePriority > -99` (or whatever product rule is). Example: `return balancePriority > -99 && balance.amount > 0;`
+
+---
+
+### 1.3 Missing `blockchain` on `WalletBalance`
+
+**Issue:** The code uses `balance.blockchain` but the interface `WalletBalance` only has `currency` and `amount`. TypeScript would error and runtime could be undefined.
+
+**Fix:** Add `blockchain` to the `WalletBalance` interface and use a union type instead of `any` for the `getPriority` argument (see type safety below).
+
+---
+
+### 1.4 Unnecessary dependency in `useMemo` (`prices`)
+
+**Issue:** `sortedBalances` is memoized with dependency array `[balances, prices]`. The memoized value does not use `prices` at all. When `prices` changes, the filter/sort runs again for no reason.
+
+**Impact:** Extra work on every price update, more re-renders, worse performance.
+
+**Fix:** Remove `prices` from the dependency array: `[balances]`.
+
+---
+
+### 1.5 Incomplete sort comparator (missing `return 0`)
+
+**Issue:** The sort callback returns `-1` or `1` but does not return `0` when `leftPriority === rightPriority`. In that case the function returns `undefined`, which can lead to unstable sort order and harder-to-reason-about behavior.
+
+**Fix:** Add `return 0` when priorities are equal:
+`if (leftPriority > rightPriority) return -1; if (rightPriority > leftPriority) return 1; return 0;`
+
+---
+
+### 1.6 Dead code and wrong data source for rows
+
+**Issue:**
+- `formattedBalances` is computed (map that adds `formatted`) but never used.
+- `rows` are built from `sortedBalances`, which is `WalletBalance[]` and has no `formatted` property. The code then uses `balance.formatted` in `WalletRow`, which would be undefined.
+
+**Impact:** Formatted amounts are wrong/undefined; unnecessary array allocation every render.
+
+**Fix:** Use `formattedBalances` when rendering rows, and derive rows from it. Optionally combine formatting with the same `useMemo` that produces sorted data to avoid two passes (see refactor).
+
+---
+
+### 1.7 Using array index as `key`
+
+**Issue:** `key={index}` is used for list items. When the list is sorted/filtered, indices change and React can reuse the wrong component instance, leading to bugs and unnecessary DOM updates.
+
+**Fix:** Use a stable key, e.g. `key={balance.currency}` or `key={`${balance.currency}-${balance.blockchain}`}` if currency can repeat per chain.
+
+---
+
+### 1.8 `getPriority` recreated every render
+
+**Issue:** `getPriority` is defined inside the component, so a new function is created on every render. It doesn’t depend on props/state.
+
+**Impact:** Minor CPU and memory churn; if passed to children or used in deps, can break memoization.
+
+**Fix:** Move `getPriority` outside the component (or to a utils file). It becomes a pure function and is not recreated.
+
+---
+
+### 1.9 Type safety: `any` for blockchain
+
+**Issue:** `getPriority(blockchain: any)` violates the project rule “Avoid `any`” and loses type safety.
+
+**Fix:** Define a union type, e.g.
+`type Blockchain = 'Osmosis' | 'Ethereum' | 'Arbitrum' | 'Zilliqa' | 'Neo';`
+and use it in `WalletBalance.blockchain` and in `getPriority(blockchain: Blockchain)`.
+
+---
+
+### 1.10 Empty/redundant `Props` interface
+
+**Issue:** `interface Props extends BoxProps {}` adds no members. It’s redundant.
+
+**Fix:** Use `BoxProps` directly as the component props type, or extend it only when you add specific props (e.g. `children`).
+
+---
+
+### 1.11 Heavy logic inside the component
+
+**Issue:** Filtering, sorting, and formatting are done directly in the component. Rules say: “Business logic must not live in UI components” and “Shared logic goes to hooks”.
+
+**Impact:** Harder to test, reuse, and keep the component readable.
+
+**Fix:** Move “get filtered & sorted formatted balances” into a custom hook (e.g. `useSortedFormattedBalances(balances)`) or to a pure function in `utils`. The component only consumes the result and renders.
+
+---
+
+### 1.12 No error handling
+
+**Issue:** `useWalletBalances()` and `usePrices()` are used with no loading/error handling. Rules: “Never ignore errors”, “Provide safe fallbacks”.
+
+**Fix:** Use error/loading states from the hooks and render loading UI or error boundaries and fallbacks (e.g. empty list, error message).
+
+---
+
+### 1.13 Unused variable / missing definition
+
+**Issue:** `children` is destructured from `props` but never used. `classes.row` is used but `classes` is not defined in the snippet (likely from `makeStyles` or similar).
+
+**Fix:** Remove `children` if not needed, or use it in the JSX. Ensure `classes` is defined (e.g. from theme or CSS module) or replace with a string class name.
+
+---
+
+### 1.14 Memoization of derived list for rows
+
+**Issue:** `formattedBalances` and `rows` are recomputed on every render. For larger lists this is unnecessary work when only `balances` or `prices` change.
+
+**Fix:** Use `useMemo` for the data that feeds the rows (e.g. formatted balances) with correct dependencies (`balances`, and `prices` only where needed for USD value). Optionally memoize row list or use `React.memo` on `WalletRow` for list performance.
+
+---
+
+## 2. Summary Table
+
+| # | Category | Issue | Fix |
+|---|-----------------|--------------------------------------------|-----|
+| 1 | Bug | Wrong variable `lhsPriority` in filter | Use `balancePriority` |
+| 2 | Bug | Filter keeps zero/negative balances | Keep positive (or clarify rule) |
+| 3 | Type / data | `WalletBalance` missing `blockchain` | Add `blockchain` to interface |
+| 4 | Performance | `prices` in useMemo deps unnecessarily | Remove `prices` from deps |
+| 5 | Correctness | Sort comparator doesn’t return 0 | Return 0 when equal |
+| 6 | Bug / dead code | `formattedBalances` unused; rows use wrong source | Use `formattedBalances` for rows |
+| 7 | Anti-pattern | `key={index}` | Stable key (e.g. `currency`) |
+| 8 | Performance | `getPriority` recreated every render | Move outside component |
+| 9 | Type safety | `any` for blockchain | Use union type |
+| 10| Code quality | Empty Props interface | Use BoxProps or extend meaningfully |
+| 11| Architecture | Business logic in UI component | Move to hook or utils |
+| 12| Robustness | No error/loading handling | Add error/loading states and fallbacks |
+| 13| Clean code | Unused `children`; undefined `classes` | Remove or use; define classes |
+| 14| Performance | No memo for formatted/row data | useMemo with correct deps |
+
+---
+
+## 3. Refactored Code
+
+See the refactored implementation in:
+
+- `src/types/wallet.ts` — types and blockchain priority
+- `src/utils/walletBalance.ts` — pure helpers (priority, filter, sort, format)
+- `src/hooks/useSortedFormattedBalances.ts` — hook that encapsulates logic
+- `src/components/WalletPage.tsx` — UI-only component
+
+This follows **rules.md**: separation of concerns, no `any`, reusable types, logic in hooks/utils, performance (memoization, stable keys), and room for error handling.
diff --git a/src/problem3/src/components/WalletPage.tsx b/src/problem3/src/components/WalletPage.tsx
new file mode 100644
index 0000000000..f29682133e
--- /dev/null
+++ b/src/problem3/src/components/WalletPage.tsx
@@ -0,0 +1,51 @@
+import React, { useMemo } from 'react';
+import type { WalletPageProps, FormattedWalletBalance } from './types/wallet';
+import { useSortedFormattedBalances } from './hooks/useSortedFormattedBalances';
+
+// Assume these hooks are provided elsewhere (e.g. from API layer)
+declare function useWalletBalances(): import('./types/wallet').WalletBalance[];
+declare function usePrices(): Record;
+
+// Placeholder: replace with your actual WalletRow component
+interface WalletRowProps {
+ className?: string;
+ key?: string;
+ amount: number;
+ usdValue: number;
+ formattedAmount: string;
+}
+function WalletRow({ className, amount, usdValue, formattedAmount }: WalletRowProps) {
+ return (
+
+ {formattedAmount}
+ ${usdValue.toFixed(2)}
+
+ );
+}
+
+export const WalletPage: React.FC = (props) => {
+ const { ...rest } = props;
+ const balances = useWalletBalances();
+ const prices = usePrices();
+
+ const formattedBalances = useSortedFormattedBalances(balances);
+
+ const rows = useMemo(
+ () =>
+ formattedBalances.map((balance: FormattedWalletBalance) => {
+ const usdValue = (prices[balance.currency] ?? 0) * balance.amount;
+ return (
+
+ );
+ }),
+ [formattedBalances, prices]
+ );
+
+ return {rows}
;
+};
diff --git a/src/problem3/src/hooks/useSortedFormattedBalances.ts b/src/problem3/src/hooks/useSortedFormattedBalances.ts
new file mode 100644
index 0000000000..a25d6797c4
--- /dev/null
+++ b/src/problem3/src/hooks/useSortedFormattedBalances.ts
@@ -0,0 +1,17 @@
+/**
+ * Hook: sorted and formatted wallet balances.
+ * Only depends on balances — not prices — to avoid unnecessary recalc (per rules.md §7).
+ */
+
+import { useMemo } from 'react';
+import { getSortedFormattedBalances } from '../utils/walletBalance';
+import type { WalletBalance, FormattedWalletBalance } from '../types/wallet';
+
+export function useSortedFormattedBalances(
+ balances: WalletBalance[]
+): FormattedWalletBalance[] {
+ return useMemo(
+ () => getSortedFormattedBalances(balances),
+ [balances]
+ );
+}
diff --git a/src/problem3/src/types/wallet.ts b/src/problem3/src/types/wallet.ts
new file mode 100644
index 0000000000..3fd1bc1cc5
--- /dev/null
+++ b/src/problem3/src/types/wallet.ts
@@ -0,0 +1,31 @@
+/**
+ * Wallet and balance types.
+ * Reusable, strong typing — no `any` (per rules.md §9).
+ */
+
+import type { ReactNode } from 'react';
+
+export type Blockchain =
+ | 'Osmosis'
+ | 'Ethereum'
+ | 'Arbitrum'
+ | 'Zilliqa'
+ | 'Neo';
+
+export interface WalletBalance {
+ currency: string;
+ amount: number;
+ blockchain: Blockchain;
+}
+
+export interface FormattedWalletBalance extends WalletBalance {
+ formatted: string;
+}
+
+export interface BoxProps {
+ className?: string;
+ children?: ReactNode;
+ [key: string]: unknown;
+}
+
+export type WalletPageProps = BoxProps;
diff --git a/src/problem3/src/utils/walletBalance.ts b/src/problem3/src/utils/walletBalance.ts
new file mode 100644
index 0000000000..ba0c867d4f
--- /dev/null
+++ b/src/problem3/src/utils/walletBalance.ts
@@ -0,0 +1,57 @@
+/**
+ * Pure wallet balance logic — testable, no UI (per rules.md: separation of concerns).
+ */
+
+import type { Blockchain, WalletBalance, FormattedWalletBalance } from '../types/wallet';
+
+const BLOCKCHAIN_PRIORITY: Record = {
+ Osmosis: 100,
+ Ethereum: 50,
+ Arbitrum: 30,
+ Zilliqa: 20,
+ Neo: 20,
+};
+
+const DEFAULT_PRIORITY = -99;
+
+export function getPriority(blockchain: Blockchain): number {
+ return BLOCKCHAIN_PRIORITY[blockchain] ?? DEFAULT_PRIORITY;
+}
+
+/**
+ * Filter balances: known blockchain and positive amount.
+ * (Original kept zero/negative — likely bug; adjust rule if product needs otherwise.)
+ */
+export function filterBalances(balances: WalletBalance[]): WalletBalance[] {
+ return balances.filter((balance) => {
+ const priority = getPriority(balance.blockchain);
+ return priority > DEFAULT_PRIORITY && balance.amount > 0;
+ });
+}
+
+/**
+ * Sort by priority descending (higher first).
+ * Comparator returns -1 | 0 | 1 for stable sort.
+ */
+export function sortBalancesByPriority(balances: WalletBalance[]): WalletBalance[] {
+ return [...balances].sort((lhs, rhs) => {
+ const leftPriority = getPriority(lhs.blockchain);
+ const rightPriority = getPriority(rhs.blockchain);
+ if (leftPriority > rightPriority) return -1;
+ if (rightPriority > leftPriority) return 1;
+ return 0;
+ });
+}
+
+export function formatBalance(balance: WalletBalance): FormattedWalletBalance {
+ return {
+ ...balance,
+ formatted: balance.amount.toFixed(),
+ };
+}
+
+export function getSortedFormattedBalances(balances: WalletBalance[]): FormattedWalletBalance[] {
+ const filtered = filterBalances(balances);
+ const sorted = sortBalancesByPriority(filtered);
+ return sorted.map(formatBalance);
+}