diff --git a/app/.gitignore b/app/.gitignore
index 61f96b0..fc5ae9f 100644
--- a/app/.gitignore
+++ b/app/.gitignore
@@ -23,5 +23,3 @@ dist-ssr
*.sln
*.sw?
.vercel
-.env
-.env*.local
diff --git a/app/bun.lock b/app/bun.lock
deleted file mode 100644
index 71166df..0000000
--- a/app/bun.lock
+++ /dev/null
@@ -1,945 +0,0 @@
-{
- "lockfileVersion": 1,
- "workspaces": {
- "": {
- "name": "app",
- "dependencies": {
- "@supabase/supabase-js": "^2.90.1",
- "@tanstack/react-query": "^5.90.19",
- "date-fns": "^4.1.0",
- "framer-motion": "^12.27.1",
- "lucide-react": "^0.562.0",
- "pptxgenjs": "^4.0.1",
- "react": "^19.2.0",
- "react-dom": "^19.2.0",
- "recharts": "^3.6.0",
- },
- "devDependencies": {
- "@eslint/js": "^9.39.1",
- "@tailwindcss/forms": "0.5",
- "@testing-library/jest-dom": "^6.9.1",
- "@testing-library/react": "^16.3.2",
- "@testing-library/user-event": "^14.6.1",
- "@types/node": "^24.10.1",
- "@types/react": "^19.2.5",
- "@types/react-dom": "^19.2.3",
- "@vitejs/plugin-react": "^5.1.1",
- "autoprefixer": "^10.4.23",
- "eslint": "^9.39.1",
- "eslint-plugin-react-hooks": "^7.0.1",
- "eslint-plugin-react-refresh": "^0.4.24",
- "globals": "^16.5.0",
- "jsdom": "^27.4.0",
- "postcss": "^8.5.6",
- "tailwindcss": "3",
- "typescript": "~5.9.3",
- "typescript-eslint": "^8.46.4",
- "vite": "^7.2.4",
- "vitest": "^4.0.17",
- },
- },
- },
- "packages": {
- "@acemir/cssom": ["@acemir/cssom@0.9.31", "", {}, "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA=="],
-
- "@adobe/css-tools": ["@adobe/css-tools@4.4.4", "", {}, "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg=="],
-
- "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
-
- "@asamuzakjp/css-color": ["@asamuzakjp/css-color@4.1.1", "", { "dependencies": { "@csstools/css-calc": "^2.1.4", "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "lru-cache": "^11.2.4" } }, "sha512-B0Hv6G3gWGMn0xKJ0txEi/jM5iFpT3MfDxmhZFb4W047GvytCf1DHQ1D69W3zHI4yWe2aTZAA0JnbMZ7Xc8DuQ=="],
-
- "@asamuzakjp/dom-selector": ["@asamuzakjp/dom-selector@6.7.6", "", { "dependencies": { "@asamuzakjp/nwsapi": "^2.3.9", "bidi-js": "^1.0.3", "css-tree": "^3.1.0", "is-potential-custom-element-name": "^1.0.1", "lru-cache": "^11.2.4" } }, "sha512-hBaJER6A9MpdG3WgdlOolHmbOYvSk46y7IQN/1+iqiCuUu6iWdQrs9DGKF8ocqsEqWujWf/V7b7vaDgiUmIvUg=="],
-
- "@asamuzakjp/nwsapi": ["@asamuzakjp/nwsapi@2.3.9", "", {}, "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q=="],
-
- "@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="],
-
- "@babel/compat-data": ["@babel/compat-data@7.28.6", "", {}, "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg=="],
-
- "@babel/core": ["@babel/core@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6", "@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" } }, "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw=="],
-
- "@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="],
-
- "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "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" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="],
-
- "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
-
- "@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="],
-
- "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="],
-
- "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="],
-
- "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
-
- "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
-
- "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
-
- "@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="],
-
- "@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="],
-
- "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="],
-
- "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="],
-
- "@babel/runtime": ["@babel/runtime@7.28.6", "", {}, "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA=="],
-
- "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
-
- "@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="],
-
- "@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="],
-
- "@csstools/color-helpers": ["@csstools/color-helpers@5.1.0", "", {}, "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA=="],
-
- "@csstools/css-calc": ["@csstools/css-calc@2.1.4", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4" } }, "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ=="],
-
- "@csstools/css-color-parser": ["@csstools/css-color-parser@3.1.0", "", { "dependencies": { "@csstools/color-helpers": "^5.1.0", "@csstools/css-calc": "^2.1.4" }, "peerDependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4" } }, "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA=="],
-
- "@csstools/css-parser-algorithms": ["@csstools/css-parser-algorithms@3.0.5", "", { "peerDependencies": { "@csstools/css-tokenizer": "^3.0.4" } }, "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ=="],
-
- "@csstools/css-syntax-patches-for-csstree": ["@csstools/css-syntax-patches-for-csstree@1.0.25", "", {}, "sha512-g0Kw9W3vjx5BEBAF8c5Fm2NcB/Fs8jJXh85aXqwEXiL+tqtOut07TWgyaGzAAfTM+gKckrrncyeGEZPcaRgm2Q=="],
-
- "@csstools/css-tokenizer": ["@csstools/css-tokenizer@3.0.4", "", {}, "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw=="],
-
- "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="],
-
- "@esbuild/android-arm": ["@esbuild/android-arm@0.27.2", "", { "os": "android", "cpu": "arm" }, "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA=="],
-
- "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.2", "", { "os": "android", "cpu": "arm64" }, "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA=="],
-
- "@esbuild/android-x64": ["@esbuild/android-x64@0.27.2", "", { "os": "android", "cpu": "x64" }, "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A=="],
-
- "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg=="],
-
- "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA=="],
-
- "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g=="],
-
- "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA=="],
-
- "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.2", "", { "os": "linux", "cpu": "arm" }, "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw=="],
-
- "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw=="],
-
- "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w=="],
-
- "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg=="],
-
- "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw=="],
-
- "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ=="],
-
- "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA=="],
-
- "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w=="],
-
- "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.2", "", { "os": "linux", "cpu": "x64" }, "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA=="],
-
- "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.2", "", { "os": "none", "cpu": "arm64" }, "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw=="],
-
- "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.2", "", { "os": "none", "cpu": "x64" }, "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA=="],
-
- "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA=="],
-
- "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg=="],
-
- "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.2", "", { "os": "none", "cpu": "arm64" }, "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag=="],
-
- "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg=="],
-
- "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg=="],
-
- "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ=="],
-
- "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.2", "", { "os": "win32", "cpu": "x64" }, "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ=="],
-
- "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="],
-
- "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
-
- "@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="],
-
- "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="],
-
- "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="],
-
- "@eslint/eslintrc": ["@eslint/eslintrc@3.3.3", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ=="],
-
- "@eslint/js": ["@eslint/js@9.39.2", "", {}, "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA=="],
-
- "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
-
- "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
-
- "@exodus/bytes": ["@exodus/bytes@1.9.0", "", { "peerDependencies": { "@noble/hashes": "^1.8.0 || ^2.0.0" }, "optionalPeers": ["@noble/hashes"] }, "sha512-lagqsvnk09NKogQaN/XrtlWeUF8SRhT12odMvbTIIaVObqzwAogL6jhR4DAp0gPuKoM1AOVrKUshJpRdpMFrww=="],
-
- "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
-
- "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="],
-
- "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
-
- "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
-
- "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
-
- "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
-
- "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
-
- "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
-
- "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
-
- "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
-
- "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
-
- "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
-
- "@reduxjs/toolkit": ["@reduxjs/toolkit@2.11.2", "", { "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" }, "optionalPeers": ["react", "react-redux"] }, "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ=="],
-
- "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.53", "", {}, "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ=="],
-
- "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.55.2", "", { "os": "android", "cpu": "arm" }, "sha512-21J6xzayjy3O6NdnlO6aXi/urvSRjm6nCI6+nF6ra2YofKruGixN9kfT+dt55HVNwfDmpDHJcaS3JuP/boNnlA=="],
-
- "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.55.2", "", { "os": "android", "cpu": "arm64" }, "sha512-eXBg7ibkNUZ+sTwbFiDKou0BAckeV6kIigK7y5Ko4mB/5A1KLhuzEKovsmfvsL8mQorkoincMFGnQuIT92SKqA=="],
-
- "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.55.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-UCbaTklREjrc5U47ypLulAgg4njaqfOVLU18VrCrI+6E5MQjuG0lSWaqLlAJwsD7NpFV249XgB0Bi37Zh5Sz4g=="],
-
- "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.55.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-dP67MA0cCMHFT2g5XyjtpVOtp7y4UyUxN3dhLdt11at5cPKnSm4lY+EhwNvDXIMzAMIo2KU+mc9wxaAQJTn7sQ=="],
-
- "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.55.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-WDUPLUwfYV9G1yxNRJdXcvISW15mpvod1Wv3ok+Ws93w1HjIVmCIFxsG2DquO+3usMNCpJQ0wqO+3GhFdl6Fow=="],
-
- "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.55.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Ng95wtHVEulRwn7R0tMrlUuiLVL/HXA8Lt/MYVpy88+s5ikpntzZba1qEulTuPnPIZuOPcW9wNEiqvZxZmgmqQ=="],
-
- "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.55.2", "", { "os": "linux", "cpu": "arm" }, "sha512-AEXMESUDWWGqD6LwO/HkqCZgUE1VCJ1OhbvYGsfqX2Y6w5quSXuyoy/Fg3nRqiwro+cJYFxiw5v4kB2ZDLhxrw=="],
-
- "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.55.2", "", { "os": "linux", "cpu": "arm" }, "sha512-ZV7EljjBDwBBBSv570VWj0hiNTdHt9uGznDtznBB4Caj3ch5rgD4I2K1GQrtbvJ/QiB+663lLgOdcADMNVC29Q=="],
-
- "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.55.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-uvjwc8NtQVPAJtq4Tt7Q49FOodjfbf6NpqXyW/rjXoV+iZ3EJAHLNAnKT5UJBc6ffQVgmXTUL2ifYiLABlGFqA=="],
-
- "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.55.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-s3KoWVNnye9mm/2WpOZ3JeUiediUVw6AvY/H7jNA6qgKA2V2aM25lMkVarTDfiicn/DLq3O0a81jncXszoyCFA=="],
-
- "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.55.2", "", { "os": "linux", "cpu": "none" }, "sha512-gi21faacK+J8aVSyAUptML9VQN26JRxe484IbF+h3hpG+sNVoMXPduhREz2CcYr5my0NE3MjVvQ5bMKX71pfVA=="],
-
- "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.55.2", "", { "os": "linux", "cpu": "none" }, "sha512-qSlWiXnVaS/ceqXNfnoFZh4IiCA0EwvCivivTGbEu1qv2o+WTHpn1zNmCTAoOG5QaVr2/yhCoLScQtc/7RxshA=="],
-
- "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.55.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-rPyuLFNoF1B0+wolH277E780NUKf+KoEDb3OyoLbAO18BbeKi++YN6gC/zuJoPPDlQRL3fIxHxCxVEWiem2yXw=="],
-
- "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.55.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-g+0ZLMook31iWV4PvqKU0i9E78gaZgYpSrYPed/4Bu+nGTgfOPtfs1h11tSSRPXSjC5EzLTjV/1A7L2Vr8pJoQ=="],
-
- "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.55.2", "", { "os": "linux", "cpu": "none" }, "sha512-i+sGeRGsjKZcQRh3BRfpLsM3LX3bi4AoEVqmGDyc50L6KfYsN45wVCSz70iQMwPWr3E5opSiLOwsC9WB4/1pqg=="],
-
- "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.55.2", "", { "os": "linux", "cpu": "none" }, "sha512-C1vLcKc4MfFV6I0aWsC7B2Y9QcsiEcvKkfxprwkPfLaN8hQf0/fKHwSF2lcYzA9g4imqnhic729VB9Fo70HO3Q=="],
-
- "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.55.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-68gHUK/howpQjh7g7hlD9DvTTt4sNLp1Bb+Yzw2Ki0xvscm2cOdCLZNJNhd2jW8lsTPrHAHuF751BygifW4bkQ=="],
-
- "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.55.2", "", { "os": "linux", "cpu": "x64" }, "sha512-1e30XAuaBP1MAizaOBApsgeGZge2/Byd6wV4a8oa6jPdHELbRHBiw7wvo4dp7Ie2PE8TZT4pj9RLGZv9N4qwlw=="],
-
- "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.55.2", "", { "os": "linux", "cpu": "x64" }, "sha512-4BJucJBGbuGnH6q7kpPqGJGzZnYrpAzRd60HQSt3OpX/6/YVgSsJnNzR8Ot74io50SeVT4CtCWe/RYIAymFPwA=="],
-
- "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.55.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-cT2MmXySMo58ENv8p6/O6wI/h/gLnD3D6JoajwXFZH6X9jz4hARqUhWpGuQhOgLNXscfZYRQMJvZDtWNzMAIDw=="],
-
- "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.55.2", "", { "os": "none", "cpu": "arm64" }, "sha512-sZnyUgGkuzIXaK3jNMPmUIyJrxu/PjmATQrocpGA1WbCPX8H5tfGgRSuYtqBYAvLuIGp8SPRb1O4d1Fkb5fXaQ=="],
-
- "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.55.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-sDpFbenhmWjNcEbBcoTV0PWvW5rPJFvu+P7XoTY0YLGRupgLbFY0XPfwIbJOObzO7QgkRDANh65RjhPmgSaAjQ=="],
-
- "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.55.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-GvJ03TqqaweWCigtKQVBErw2bEhu1tyfNQbarwr94wCGnczA9HF8wqEe3U/Lfu6EdeNP0p6R+APeHVwEqVxpUQ=="],
-
- "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.55.2", "", { "os": "win32", "cpu": "x64" }, "sha512-KvXsBvp13oZz9JGe5NYS7FNizLe99Ny+W8ETsuCyjXiKdiGrcz2/J/N8qxZ/RSwivqjQguug07NLHqrIHrqfYw=="],
-
- "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.55.2", "", { "os": "win32", "cpu": "x64" }, "sha512-xNO+fksQhsAckRtDSPWaMeT1uIM+JrDRXlerpnWNXhn1TdB3YZ6uKBMBTKP0eX9XtYEP978hHk1f8332i2AW8Q=="],
-
- "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
-
- "@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="],
-
- "@supabase/auth-js": ["@supabase/auth-js@2.90.1", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-vxb66dgo6h3yyPbR06735Ps+dK3hj0JwS8w9fdQPVZQmocSTlKUW5MfxSy99mN0XqCCuLMQ3jCEiIIUU23e9ng=="],
-
- "@supabase/functions-js": ["@supabase/functions-js@2.90.1", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-x9mV9dF1Lam9qL3zlpP6mSM5C9iqMPtF5B/tU1Jj/F0ufX5mjDf9ghVBaErVxmrQJRL4+iMKWKY2GnODkpS8tw=="],
-
- "@supabase/postgrest-js": ["@supabase/postgrest-js@2.90.1", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-jh6vqzaYzoFn3raaC0hcFt9h+Bt+uxNRBSdc7PfToQeRGk7PDPoweHsbdiPWREtDVTGKfu+PyPW9e2jbK+BCgQ=="],
-
- "@supabase/realtime-js": ["@supabase/realtime-js@2.90.1", "", { "dependencies": { "@types/phoenix": "^1.6.6", "@types/ws": "^8.18.1", "tslib": "2.8.1", "ws": "^8.18.2" } }, "sha512-PWbnEMkcQRuor8jhObp4+Snufkq8C6fBp+MchVp2qBPY1NXk/c3Iv3YyiFYVzo0Dzuw4nAlT4+ahuPggy4r32w=="],
-
- "@supabase/storage-js": ["@supabase/storage-js@2.90.1", "", { "dependencies": { "iceberg-js": "^0.8.1", "tslib": "2.8.1" } }, "sha512-GHY+Ps/K/RBfRj7kwx+iVf2HIdqOS43rM2iDOIDpapyUnGA9CCBFzFV/XvfzznGykd//z2dkGZhlZZprsVFqGg=="],
-
- "@supabase/supabase-js": ["@supabase/supabase-js@2.90.1", "", { "dependencies": { "@supabase/auth-js": "2.90.1", "@supabase/functions-js": "2.90.1", "@supabase/postgrest-js": "2.90.1", "@supabase/realtime-js": "2.90.1", "@supabase/storage-js": "2.90.1" } }, "sha512-U8KaKGLUgTIFHtwEW1dgw1gK7XrdpvvYo7nzzqPx721GqPe8WZbAiLh/hmyKLGBYQ/mmQNr20vU9tWSDZpii3w=="],
-
- "@tailwindcss/forms": ["@tailwindcss/forms@0.5.11", "", { "dependencies": { "mini-svg-data-uri": "^1.2.3" }, "peerDependencies": { "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1" } }, "sha512-h9wegbZDPurxG22xZSoWtdzc41/OlNEUQERNqI/0fOwa2aVlWGu7C35E/x6LDyD3lgtztFSSjKZyuVM0hxhbgA=="],
-
- "@tanstack/query-core": ["@tanstack/query-core@5.90.19", "", {}, "sha512-GLW5sjPVIvH491VV1ufddnfldyVB+teCnpPIvweEfkpRx7CfUmUGhoh9cdcUKBh/KwVxk22aNEDxeTsvmyB/WA=="],
-
- "@tanstack/react-query": ["@tanstack/react-query@5.90.19", "", { "dependencies": { "@tanstack/query-core": "5.90.19" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-qTZRZ4QyTzQc+M0IzrbKHxSeISUmRB3RPGmao5bT+sI6ayxSRhn0FXEnT5Hg3as8SBFcRosrXXRFB+yAcxVxJQ=="],
-
- "@testing-library/dom": ["@testing-library/dom@10.4.1", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "picocolors": "1.1.1", "pretty-format": "^27.0.2" } }, "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg=="],
-
- "@testing-library/jest-dom": ["@testing-library/jest-dom@6.9.1", "", { "dependencies": { "@adobe/css-tools": "^4.4.0", "aria-query": "^5.0.0", "css.escape": "^1.5.1", "dom-accessibility-api": "^0.6.3", "picocolors": "^1.1.1", "redent": "^3.0.0" } }, "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA=="],
-
- "@testing-library/react": ["@testing-library/react@16.3.2", "", { "dependencies": { "@babel/runtime": "^7.12.5" }, "peerDependencies": { "@testing-library/dom": "^10.0.0", "@types/react": "^18.0.0 || ^19.0.0", "@types/react-dom": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g=="],
-
- "@testing-library/user-event": ["@testing-library/user-event@14.6.1", "", { "peerDependencies": { "@testing-library/dom": ">=7.21.4" } }, "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw=="],
-
- "@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="],
-
- "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
-
- "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
-
- "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
-
- "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
-
- "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
-
- "@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="],
-
- "@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="],
-
- "@types/d3-ease": ["@types/d3-ease@3.0.2", "", {}, "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="],
-
- "@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="],
-
- "@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="],
-
- "@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="],
-
- "@types/d3-shape": ["@types/d3-shape@3.1.8", "", { "dependencies": { "@types/d3-path": "*" } }, "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w=="],
-
- "@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="],
-
- "@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="],
-
- "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
-
- "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
-
- "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
-
- "@types/node": ["@types/node@24.10.9", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw=="],
-
- "@types/phoenix": ["@types/phoenix@1.6.7", "", {}, "sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q=="],
-
- "@types/react": ["@types/react@19.2.8", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg=="],
-
- "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
-
- "@types/use-sync-external-store": ["@types/use-sync-external-store@0.0.6", "", {}, "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="],
-
- "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
-
- "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.53.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.53.1", "@typescript-eslint/type-utils": "8.53.1", "@typescript-eslint/utils": "8.53.1", "@typescript-eslint/visitor-keys": "8.53.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.53.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag=="],
-
- "@typescript-eslint/parser": ["@typescript-eslint/parser@8.53.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.53.1", "@typescript-eslint/types": "8.53.1", "@typescript-eslint/typescript-estree": "8.53.1", "@typescript-eslint/visitor-keys": "8.53.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg=="],
-
- "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.53.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.53.1", "@typescript-eslint/types": "^8.53.1", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-WYC4FB5Ra0xidsmlPb+1SsnaSKPmS3gsjIARwbEkHkoWloQmuzcfypljaJcR78uyLA1h8sHdWWPHSLDI+MtNog=="],
-
- "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.53.1", "", { "dependencies": { "@typescript-eslint/types": "8.53.1", "@typescript-eslint/visitor-keys": "8.53.1" } }, "sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ=="],
-
- "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.53.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-qfvLXS6F6b1y43pnf0pPbXJ+YoXIC7HKg0UGZ27uMIemKMKA6XH2DTxsEDdpdN29D+vHV07x/pnlPNVLhdhWiA=="],
-
- "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.53.1", "", { "dependencies": { "@typescript-eslint/types": "8.53.1", "@typescript-eslint/typescript-estree": "8.53.1", "@typescript-eslint/utils": "8.53.1", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-MOrdtNvyhy0rHyv0ENzub1d4wQYKb2NmIqG7qEqPWFW7Mpy2jzFC3pQ2yKDvirZB7jypm5uGjF2Qqs6OIqu47w=="],
-
- "@typescript-eslint/types": ["@typescript-eslint/types@8.53.1", "", {}, "sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A=="],
-
- "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.53.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.53.1", "@typescript-eslint/tsconfig-utils": "8.53.1", "@typescript-eslint/types": "8.53.1", "@typescript-eslint/visitor-keys": "8.53.1", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg=="],
-
- "@typescript-eslint/utils": ["@typescript-eslint/utils@8.53.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.53.1", "@typescript-eslint/types": "8.53.1", "@typescript-eslint/typescript-estree": "8.53.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-c4bMvGVWW4hv6JmDUEG7fSYlWOl3II2I4ylt0NM+seinYQlZMQIaKaXIIVJWt9Ofh6whrpM+EdDQXKXjNovvrg=="],
-
- "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.53.1", "", { "dependencies": { "@typescript-eslint/types": "8.53.1", "eslint-visitor-keys": "^4.2.1" } }, "sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg=="],
-
- "@vitejs/plugin-react": ["@vitejs/plugin-react@5.1.2", "", { "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" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ=="],
-
- "@vitest/expect": ["@vitest/expect@4.0.17", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.0.17", "@vitest/utils": "4.0.17", "chai": "^6.2.1", "tinyrainbow": "^3.0.3" } }, "sha512-mEoqP3RqhKlbmUmntNDDCJeTDavDR+fVYkSOw8qRwJFaW/0/5zA9zFeTrHqNtcmwh6j26yMmwx2PqUDPzt5ZAQ=="],
-
- "@vitest/mocker": ["@vitest/mocker@4.0.17", "", { "dependencies": { "@vitest/spy": "4.0.17", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-+ZtQhLA3lDh1tI2wxe3yMsGzbp7uuJSWBM1iTIKCbppWTSBN09PUC+L+fyNlQApQoR+Ps8twt2pbSSXg2fQVEQ=="],
-
- "@vitest/pretty-format": ["@vitest/pretty-format@4.0.17", "", { "dependencies": { "tinyrainbow": "^3.0.3" } }, "sha512-Ah3VAYmjcEdHg6+MwFE17qyLqBHZ+ni2ScKCiW2XrlSBV4H3Z7vYfPfz7CWQ33gyu76oc0Ai36+kgLU3rfF4nw=="],
-
- "@vitest/runner": ["@vitest/runner@4.0.17", "", { "dependencies": { "@vitest/utils": "4.0.17", "pathe": "^2.0.3" } }, "sha512-JmuQyf8aMWoo/LmNFppdpkfRVHJcsgzkbCA+/Bk7VfNH7RE6Ut2qxegeyx2j3ojtJtKIbIGy3h+KxGfYfk28YQ=="],
-
- "@vitest/snapshot": ["@vitest/snapshot@4.0.17", "", { "dependencies": { "@vitest/pretty-format": "4.0.17", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-npPelD7oyL+YQM2gbIYvlavlMVWUfNNGZPcu0aEUQXt7FXTuqhmgiYupPnAanhKvyP6Srs2pIbWo30K0RbDtRQ=="],
-
- "@vitest/spy": ["@vitest/spy@4.0.17", "", {}, "sha512-I1bQo8QaP6tZlTomQNWKJE6ym4SHf3oLS7ceNjozxxgzavRAgZDc06T7kD8gb9bXKEgcLNt00Z+kZO6KaJ62Ew=="],
-
- "@vitest/utils": ["@vitest/utils@4.0.17", "", { "dependencies": { "@vitest/pretty-format": "4.0.17", "tinyrainbow": "^3.0.3" } }, "sha512-RG6iy+IzQpa9SB8HAFHJ9Y+pTzI+h8553MrciN9eC6TFBErqrQaTas4vG+MVj8S4uKk8uTT2p0vgZPnTdxd96w=="],
-
- "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
-
- "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
-
- "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
-
- "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
-
- "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
-
- "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
-
- "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="],
-
- "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
-
- "arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="],
-
- "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
-
- "aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="],
-
- "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
-
- "autoprefixer": ["autoprefixer@10.4.23", "", { "dependencies": { "browserslist": "^4.28.1", "caniuse-lite": "^1.0.30001760", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA=="],
-
- "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
-
- "baseline-browser-mapping": ["baseline-browser-mapping@2.9.15", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-kX8h7K2srmDyYnXRIppo4AH/wYgzWVCs+eKr3RusRSQ5PvRYoEFmR/I0PbdTjKFAoKqp5+kbxnNTFO9jOfSVJg=="],
-
- "bidi-js": ["bidi-js@1.0.3", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="],
-
- "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
-
- "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
-
- "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
-
- "browserslist": ["browserslist@4.28.1", "", { "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" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
-
- "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
-
- "camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="],
-
- "caniuse-lite": ["caniuse-lite@1.0.30001765", "", {}, "sha512-LWcNtSyZrakjECqmpP4qdg0MMGdN368D7X8XvvAqOcqMv0RxnlqVKZl2V6/mBR68oYMxOZPLw/gO7DuisMHUvQ=="],
-
- "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
-
- "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
-
- "chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
-
- "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
-
- "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
-
- "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
-
- "commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="],
-
- "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
-
- "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
-
- "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
-
- "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
-
- "css-tree": ["css-tree@3.1.0", "", { "dependencies": { "mdn-data": "2.12.2", "source-map-js": "^1.0.1" } }, "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w=="],
-
- "css.escape": ["css.escape@1.5.1", "", {}, "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg=="],
-
- "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
-
- "cssstyle": ["cssstyle@5.3.7", "", { "dependencies": { "@asamuzakjp/css-color": "^4.1.1", "@csstools/css-syntax-patches-for-csstree": "^1.0.21", "css-tree": "^3.1.0", "lru-cache": "^11.2.4" } }, "sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ=="],
-
- "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
-
- "d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="],
-
- "d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="],
-
- "d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="],
-
- "d3-format": ["d3-format@3.1.2", "", {}, "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg=="],
-
- "d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="],
-
- "d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="],
-
- "d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="],
-
- "d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="],
-
- "d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="],
-
- "d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="],
-
- "d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="],
-
- "data-urls": ["data-urls@6.0.0", "", { "dependencies": { "whatwg-mimetype": "^4.0.0", "whatwg-url": "^15.0.0" } }, "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA=="],
-
- "date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="],
-
- "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
-
- "decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="],
-
- "decimal.js-light": ["decimal.js-light@2.5.1", "", {}, "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="],
-
- "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
-
- "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
-
- "didyoumean": ["didyoumean@1.2.2", "", {}, "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="],
-
- "dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="],
-
- "dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="],
-
- "electron-to-chromium": ["electron-to-chromium@1.5.267", "", {}, "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw=="],
-
- "entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
-
- "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
-
- "es-toolkit": ["es-toolkit@1.44.0", "", {}, "sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg=="],
-
- "esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="],
-
- "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
-
- "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
-
- "eslint": ["eslint@9.39.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.2", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw=="],
-
- "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.0.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA=="],
-
- "eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.4.26", "", { "peerDependencies": { "eslint": ">=8.40" } }, "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ=="],
-
- "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
-
- "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
-
- "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
-
- "esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="],
-
- "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
-
- "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
-
- "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
-
- "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
-
- "eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="],
-
- "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="],
-
- "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
-
- "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
-
- "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
-
- "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
-
- "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
-
- "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
-
- "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
-
- "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
-
- "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
-
- "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
-
- "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
-
- "fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="],
-
- "framer-motion": ["framer-motion@12.27.1", "", { "dependencies": { "motion-dom": "^12.27.1", "motion-utils": "^12.24.10", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-cEAqO69kcZt3gL0TGua8WTgRQfv4J57nqt1zxHtLKwYhAwA0x9kDS/JbMa1hJbwkGY74AGJKvZ9pX/IqWZtZWQ=="],
-
- "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
-
- "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
-
- "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
-
- "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
-
- "globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="],
-
- "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
-
- "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
-
- "hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="],
-
- "hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="],
-
- "html-encoding-sniffer": ["html-encoding-sniffer@6.0.0", "", { "dependencies": { "@exodus/bytes": "^1.6.0" } }, "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg=="],
-
- "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],
-
- "https": ["https@1.0.0", "", {}, "sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg=="],
-
- "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
-
- "iceberg-js": ["iceberg-js@0.8.1", "", {}, "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA=="],
-
- "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
-
- "image-size": ["image-size@1.2.1", "", { "dependencies": { "queue": "6.0.2" }, "bin": { "image-size": "bin/image-size.js" } }, "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw=="],
-
- "immediate": ["immediate@3.0.6", "", {}, "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="],
-
- "immer": ["immer@10.2.0", "", {}, "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw=="],
-
- "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
-
- "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
-
- "indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="],
-
- "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
-
- "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="],
-
- "is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="],
-
- "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
-
- "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
-
- "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
-
- "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
-
- "is-potential-custom-element-name": ["is-potential-custom-element-name@1.0.1", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="],
-
- "isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
-
- "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
-
- "jiti": ["jiti@1.21.7", "", { "bin": { "jiti": "bin/jiti.js" } }, "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A=="],
-
- "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
-
- "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
-
- "jsdom": ["jsdom@27.4.0", "", { "dependencies": { "@acemir/cssom": "^0.9.28", "@asamuzakjp/dom-selector": "^6.7.6", "@exodus/bytes": "^1.6.0", "cssstyle": "^5.3.4", "data-urls": "^6.0.0", "decimal.js": "^10.6.0", "html-encoding-sniffer": "^6.0.0", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", "parse5": "^8.0.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^6.0.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^8.0.0", "whatwg-mimetype": "^4.0.0", "whatwg-url": "^15.1.0", "ws": "^8.18.3", "xml-name-validator": "^5.0.0" }, "peerDependencies": { "canvas": "^3.0.0" }, "optionalPeers": ["canvas"] }, "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ=="],
-
- "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
-
- "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
-
- "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
-
- "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
-
- "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
-
- "jszip": ["jszip@3.10.1", "", { "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", "setimmediate": "^1.0.5" } }, "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g=="],
-
- "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
-
- "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
-
- "lie": ["lie@3.3.0", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ=="],
-
- "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
-
- "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
-
- "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
-
- "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
-
- "lru-cache": ["lru-cache@11.2.4", "", {}, "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg=="],
-
- "lucide-react": ["lucide-react@0.562.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw=="],
-
- "lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="],
-
- "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
-
- "mdn-data": ["mdn-data@2.12.2", "", {}, "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA=="],
-
- "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
-
- "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
-
- "min-indent": ["min-indent@1.0.1", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="],
-
- "mini-svg-data-uri": ["mini-svg-data-uri@1.4.4", "", { "bin": { "mini-svg-data-uri": "cli.js" } }, "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg=="],
-
- "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
-
- "motion-dom": ["motion-dom@12.27.1", "", { "dependencies": { "motion-utils": "^12.24.10" } }, "sha512-V/53DA2nBqKl9O2PMJleSUb/G0dsMMeZplZwgIQf5+X0bxIu7Q1cTv6DrjvTTGYRm3+7Y5wMlRZ1wT61boU/bQ=="],
-
- "motion-utils": ["motion-utils@12.24.10", "", {}, "sha512-x5TFgkCIP4pPsRLpKoI86jv/q8t8FQOiM/0E8QKBzfMozWHfkKap2gA1hOki+B5g3IsBNpxbUnfOum1+dgvYww=="],
-
- "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
-
- "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
-
- "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
-
- "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
-
- "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="],
-
- "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
-
- "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
-
- "object-hash": ["object-hash@3.0.0", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="],
-
- "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
-
- "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
-
- "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
-
- "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
-
- "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
-
- "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
-
- "parse5": ["parse5@8.0.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA=="],
-
- "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
-
- "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
-
- "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
-
- "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
-
- "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
-
- "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
-
- "pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="],
-
- "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="],
-
- "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
-
- "postcss-import": ["postcss-import@15.1.0", "", { "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", "resolve": "^1.1.7" }, "peerDependencies": { "postcss": "^8.0.0" } }, "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew=="],
-
- "postcss-js": ["postcss-js@4.1.0", "", { "dependencies": { "camelcase-css": "^2.0.1" }, "peerDependencies": { "postcss": "^8.4.21" } }, "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw=="],
-
- "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="],
-
- "postcss-nested": ["postcss-nested@6.2.0", "", { "dependencies": { "postcss-selector-parser": "^6.1.1" }, "peerDependencies": { "postcss": "^8.2.14" } }, "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ=="],
-
- "postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="],
-
- "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="],
-
- "pptxgenjs": ["pptxgenjs@4.0.1", "", { "dependencies": { "@types/node": "^22.8.1", "https": "^1.0.0", "image-size": "^1.2.1", "jszip": "^3.10.1" } }, "sha512-TeJISr8wouAuXw4C1F/mC33xbZs/FuEG6nH9FG1Zj+nuPcGMP5YRHl6X+j3HSUnS1f3at6k75ZZXPMZlA5Lj9A=="],
-
- "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
-
- "pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="],
-
- "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
-
- "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
-
- "queue": ["queue@6.0.2", "", { "dependencies": { "inherits": "~2.0.3" } }, "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA=="],
-
- "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
-
- "react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="],
-
- "react-dom": ["react-dom@19.2.3", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.3" } }, "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg=="],
-
- "react-is": ["react-is@19.2.3", "", {}, "sha512-qJNJfu81ByyabuG7hPFEbXqNcWSU3+eVus+KJs+0ncpGfMyYdvSmxiJxbWR65lYi1I+/0HBcliO029gc4F+PnA=="],
-
- "react-redux": ["react-redux@9.2.0", "", { "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" }, "optionalPeers": ["@types/react", "redux"] }, "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g=="],
-
- "react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="],
-
- "read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="],
-
- "readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
-
- "readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
-
- "recharts": ["recharts@3.6.0", "", { "dependencies": { "@reduxjs/toolkit": "1.x.x || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", "eventemitter3": "^5.0.1", "immer": "^10.1.1", "react-redux": "8.x.x || 9.x.x", "reselect": "5.1.1", "tiny-invariant": "^1.3.3", "use-sync-external-store": "^1.2.2", "victory-vendor": "^37.0.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-L5bjxvQRAe26RlToBAziKUB7whaGKEwD3znoM6fz3DrTowCIC/FnJYnuq1GEzB8Zv2kdTfaxQfi5GoH0tBinyg=="],
-
- "redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="],
-
- "redux": ["redux@5.0.1", "", {}, "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="],
-
- "redux-thunk": ["redux-thunk@3.1.0", "", { "peerDependencies": { "redux": "^5.0.0" } }, "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw=="],
-
- "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
-
- "reselect": ["reselect@5.1.1", "", {}, "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="],
-
- "resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="],
-
- "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
-
- "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
-
- "rollup": ["rollup@4.55.2", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.55.2", "@rollup/rollup-android-arm64": "4.55.2", "@rollup/rollup-darwin-arm64": "4.55.2", "@rollup/rollup-darwin-x64": "4.55.2", "@rollup/rollup-freebsd-arm64": "4.55.2", "@rollup/rollup-freebsd-x64": "4.55.2", "@rollup/rollup-linux-arm-gnueabihf": "4.55.2", "@rollup/rollup-linux-arm-musleabihf": "4.55.2", "@rollup/rollup-linux-arm64-gnu": "4.55.2", "@rollup/rollup-linux-arm64-musl": "4.55.2", "@rollup/rollup-linux-loong64-gnu": "4.55.2", "@rollup/rollup-linux-loong64-musl": "4.55.2", "@rollup/rollup-linux-ppc64-gnu": "4.55.2", "@rollup/rollup-linux-ppc64-musl": "4.55.2", "@rollup/rollup-linux-riscv64-gnu": "4.55.2", "@rollup/rollup-linux-riscv64-musl": "4.55.2", "@rollup/rollup-linux-s390x-gnu": "4.55.2", "@rollup/rollup-linux-x64-gnu": "4.55.2", "@rollup/rollup-linux-x64-musl": "4.55.2", "@rollup/rollup-openbsd-x64": "4.55.2", "@rollup/rollup-openharmony-arm64": "4.55.2", "@rollup/rollup-win32-arm64-msvc": "4.55.2", "@rollup/rollup-win32-ia32-msvc": "4.55.2", "@rollup/rollup-win32-x64-gnu": "4.55.2", "@rollup/rollup-win32-x64-msvc": "4.55.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-PggGy4dhwx5qaW+CKBilA/98Ql9keyfnb7lh4SR6shQ91QQQi1ORJ1v4UinkdP2i87OBs9AQFooQylcrrRfIcg=="],
-
- "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
-
- "safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
-
- "saxes": ["saxes@6.0.0", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="],
-
- "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
-
- "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
-
- "setimmediate": ["setimmediate@1.0.5", "", {}, "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="],
-
- "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
-
- "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
-
- "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
-
- "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
-
- "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
-
- "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],
-
- "string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
-
- "strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="],
-
- "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
-
- "sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="],
-
- "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
-
- "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
-
- "symbol-tree": ["symbol-tree@3.2.4", "", {}, "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="],
-
- "tailwindcss": ["tailwindcss@3.4.19", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.7", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ=="],
-
- "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="],
-
- "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="],
-
- "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
-
- "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
-
- "tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
-
- "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
-
- "tinyrainbow": ["tinyrainbow@3.0.3", "", {}, "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q=="],
-
- "tldts": ["tldts@7.0.19", "", { "dependencies": { "tldts-core": "^7.0.19" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA=="],
-
- "tldts-core": ["tldts-core@7.0.19", "", {}, "sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A=="],
-
- "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
-
- "tough-cookie": ["tough-cookie@6.0.0", "", { "dependencies": { "tldts": "^7.0.5" } }, "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w=="],
-
- "tr46": ["tr46@6.0.0", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw=="],
-
- "ts-api-utils": ["ts-api-utils@2.4.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA=="],
-
- "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="],
-
- "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
-
- "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
-
- "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
-
- "typescript-eslint": ["typescript-eslint@8.53.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.53.1", "@typescript-eslint/parser": "8.53.1", "@typescript-eslint/typescript-estree": "8.53.1", "@typescript-eslint/utils": "8.53.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-gB+EVQfP5RDElh9ittfXlhZJdjSU4jUSTyE2+ia8CYyNvet4ElfaLlAIqDvQV9JPknKx0jQH1racTYe/4LaLSg=="],
-
- "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
-
- "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
-
- "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
-
- "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
-
- "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
-
- "victory-vendor": ["victory-vendor@37.3.6", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ=="],
-
- "vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],
-
- "vitest": ["vitest@4.0.17", "", { "dependencies": { "@vitest/expect": "4.0.17", "@vitest/mocker": "4.0.17", "@vitest/pretty-format": "4.0.17", "@vitest/runner": "4.0.17", "@vitest/snapshot": "4.0.17", "@vitest/spy": "4.0.17", "@vitest/utils": "4.0.17", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.0.17", "@vitest/browser-preview": "4.0.17", "@vitest/browser-webdriverio": "4.0.17", "@vitest/ui": "4.0.17", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-FQMeF0DJdWY0iOnbv466n/0BudNdKj1l5jYgl5JVTwjSsZSlqyXFt/9+1sEyhR6CLowbZpV7O1sCHrzBhucKKg=="],
-
- "w3c-xmlserializer": ["w3c-xmlserializer@5.0.0", "", { "dependencies": { "xml-name-validator": "^5.0.0" } }, "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA=="],
-
- "webidl-conversions": ["webidl-conversions@8.0.1", "", {}, "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ=="],
-
- "whatwg-mimetype": ["whatwg-mimetype@4.0.0", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="],
-
- "whatwg-url": ["whatwg-url@15.1.0", "", { "dependencies": { "tr46": "^6.0.0", "webidl-conversions": "^8.0.0" } }, "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g=="],
-
- "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
-
- "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
-
- "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
-
- "ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="],
-
- "xml-name-validator": ["xml-name-validator@5.0.0", "", {}, "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg=="],
-
- "xmlchars": ["xmlchars@2.2.0", "", {}, "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="],
-
- "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
-
- "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
-
- "zod": ["zod@4.3.5", "", {}, "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g=="],
-
- "zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="],
-
- "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
-
- "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
-
- "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
-
- "@reduxjs/toolkit/immer": ["immer@11.1.3", "", {}, "sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q=="],
-
- "@testing-library/dom/aria-query": ["aria-query@5.3.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A=="],
-
- "@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="],
-
- "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
-
- "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
-
- "@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
-
- "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
-
- "chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
-
- "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
-
- "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
-
- "pptxgenjs/@types/node": ["@types/node@22.19.7", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw=="],
-
- "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
-
- "pretty-format/react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="],
-
- "readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
-
- "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
-
- "pptxgenjs/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
- }
-}
diff --git a/app/generate-pptx.mjs b/app/generate-pptx.mjs
deleted file mode 100644
index d3aabc5..0000000
--- a/app/generate-pptx.mjs
+++ /dev/null
@@ -1,159 +0,0 @@
-import pptxgen from 'pptxgenjs';
-
-// Colors
-const teal = '319795';
-const purple = '7C3AED';
-const blue = '2563EB';
-const orange = 'EA580C';
-const green = '16A34A';
-
-// Team photos
-const teamPhotos = {
- 'Max Ghenis': '/tmp/max-ghenis.png',
- 'Pavel Makarchuk': '/tmp/pavel-makarchuk.jpeg',
- 'Daniel Feenberg': '/tmp/daniel-feenberg.jpg',
-};
-
-// Common slide generators
-function addTeamSlide(pptx) {
- const slide = pptx.addSlide();
- slide.addText('PolicyEngine POSE Team', { x: 0.5, y: 0.3, w: '90%', fontSize: 32, bold: true, color: '1F2937' });
-
- const teamData = [
- { name: 'Max Ghenis', role: 'Co-Founder & CEO', bio: 'MS Economics' },
- { name: 'Pavel Makarchuk', role: 'Chief of Staff', bio: 'Led development of PolicyEngine US state-level tax-benefit model' },
- { name: 'Daniel Feenberg', role: 'Advisor', bio: 'PhD Economics, created TAXSIM' },
- ];
-
- teamData.forEach((member, i) => {
- const xPos = 0.8 + i * 3.0;
- // Add photo
- slide.addImage({
- path: teamPhotos[member.name],
- x: xPos + 0.5,
- y: 1.2,
- w: 1.5,
- h: 1.5,
- rounding: true
- });
- slide.addText(member.name, { x: xPos, y: 2.9, w: 2.5, fontSize: 16, bold: true, color: '1F2937', align: 'center' });
- slide.addText(member.role, { x: xPos, y: 3.3, w: 2.5, fontSize: 12, color: teal, align: 'center' });
- slide.addText(member.bio, { x: xPos, y: 3.7, w: 2.5, fontSize: 9, color: '6B7280', align: 'center', wrap: true });
- });
- return slide;
-}
-
-function addThesisSlide(pptx) {
- const slide = pptx.addSlide();
- slide.addText('4373 PolicyEngine | OSE Thesis', { x: 0.5, y: 0.2, w: '90%', fontSize: 24, bold: true, color: '1F2937' });
- const thesisLines = [
- { prefix: 'FOR', text: 'economists, policy researchers, think tanks, journalists, advocates, and developers building benefit access tools', color: teal },
- { prefix: 'WHO NEED TO', text: 'understand taxes and benefits for households or analyze policy impacts on populations', color: purple },
- { prefix: 'THE STATUS QUO', text: 'proprietary microsimulation tools', color: 'DC2626', suffix: 'FAILS DUE TO high cost, limited accessibility, and restrictions in government/secure environments, CAUSING policy decisions without rigorous distributional analysis.' },
- { prefix: 'WE WILL ESTABLISH A MANAGING ORGANIZATION FOR', text: 'open-source fiscal policy simulation', color: blue },
- { prefix: 'TO DELIVER', text: 'PolicyEngine models, web apps, and APIs', color: blue, suffix: 'WITH AGPL license and transparent governance.' },
- { prefix: 'WE WILL GROW THE COMMUNITY THROUGH', text: 'documentation and partnerships with universities and think tanks', color: green },
- { prefix: 'WE WILL ACHIEVE', text: 'democratized access to sophisticated policy analysis', color: green },
- { prefix: 'MEASURE SUCCESS BY', text: 'citations, applications built on PolicyEngine, their users, contributors, and funding', color: orange },
- { prefix: 'AND SUSTAIN THE ECOSYSTEM VIA', text: 'diversified foundation grants, government funding, and SaaS offerings', color: orange },
- ];
- thesisLines.forEach((line, i) => {
- const yPos = 0.7 + i * 0.48;
- slide.addText([
- { text: line.prefix + ' ', options: { bold: true, color: '374151' } },
- { text: line.text, options: { color: line.color } },
- ...(line.suffix ? [{ text: ' ' + line.suffix, options: { color: '374151' } }] : []),
- ], { x: 0.5, y: yPos, w: 9, fontSize: 11 });
- });
- return slide;
-}
-
-function addAssumptionsSlide(pptx) {
- const slide = pptx.addSlide();
- slide.addText('Assumptions', { x: 0.5, y: 0.3, w: '90%', fontSize: 32, bold: true, color: '1F2937' });
- const goalsAndAssumptions = [
- { goal: 'Grow adoption among policy analysts', assumption: 'Policy researchers will adopt open-source tools if they are accessible without programming expertise', color: teal },
- { goal: 'Achieve sustainable, diversified funding', assumption: 'Funders value transparency and reproducibility enough to fund open-source over proprietary alternatives', color: purple },
- { goal: 'Build active contributor community', assumption: 'Developers will contribute for policy impact without requiring competitive compensation', color: blue },
- ];
- goalsAndAssumptions.forEach((item, i) => {
- const yPos = 1.0 + i * 1.3;
- slide.addShape('rect', { x: 0.5, y: yPos, w: 9, h: 1.1, fill: { color: item.color, transparency: 90 }, line: { color: item.color } });
- slide.addText([{ text: 'Goal: ', options: { color: '6B7280' } }, { text: item.goal, options: { bold: true, color: item.color } }], { x: 0.7, y: yPos + 0.15, w: 8.5, fontSize: 12 });
- slide.addText([{ text: 'Assumption: ', options: { color: '6B7280' } }, { text: item.assumption, options: { color: item.color } }], { x: 0.7, y: yPos + 0.55, w: 8.5, fontSize: 10 });
- });
- return slide;
-}
-
-function addInterviewLogSlide(pptx) {
- const slide = pptx.addSlide();
- slide.addText('Interview log', { x: 0.5, y: 0.2, w: '90%', fontSize: 28, bold: true, color: '1F2937' });
-
- // Add screenshot of the POSE Tracker app (preserve aspect ratio 2208x1948)
- slide.addImage({
- path: '/tmp/interview-log.png',
- x: 2.5,
- y: 0.55,
- w: 5.0,
- h: 4.4,
- });
-
- return slide;
-}
-
-function addGoalsCharterSlide(pptx) {
- const slide = pptx.addSlide();
- slide.addText('Team goals and charter', { x: 0.5, y: 0.3, w: '90%', fontSize: 32, bold: true, color: '1F2937' });
-
- slide.addText('Goals', { x: 0.5, y: 1.0, w: 4, fontSize: 16, bold: true, color: '1F2937' });
- const goals = [
- 'Complete 100 ecosystem discovery interviews across all 6 stakeholder segments',
- 'Identify 3+ sustainable funding models beyond traditional grants',
- 'Establish partnerships with 5+ policy think tanks and media organizations',
- 'Develop community governance structure with clear decision rights',
- ];
- goals.forEach((goal, i) => {
- slide.addShape('rect', { x: 0.5, y: 1.4 + i * 0.38, w: 4.3, h: 0.33, fill: { color: green, transparency: 90 }, line: { color: green } });
- slide.addText(`${i + 1}. ${goal}`, { x: 0.6, y: 1.45 + i * 0.38, w: 4.1, fontSize: 8, color: green });
- });
-
- slide.addText('Working agreements', { x: 5.2, y: 1.0, w: 4, fontSize: 16, bold: true, color: '1F2937' });
- const agreements = [
- 'Weekly team sync (Mondays)',
- '24-hour response time on Slack',
- 'Share interview notes within 24 hours',
- 'Consensus on strategic decisions, CEO decides operational matters',
- ];
- agreements.forEach((agreement, i) => {
- slide.addShape('rect', { x: 5.2, y: 1.4 + i * 0.38, w: 4.3, h: 0.33, fill: { color: teal, transparency: 90 }, line: { color: teal } });
- slide.addText(`• ${agreement}`, { x: 5.3, y: 1.45 + i * 0.38, w: 4.1, fontSize: 8, color: teal });
- });
- return slide;
-}
-
-// Generate PPTX 1: Team, Thesis, Assumptions, Interview Log
-const pptx1 = new pptxgen();
-pptx1.layout = 'LAYOUT_16x9';
-pptx1.title = 'PolicyEngine POSE - Weekly Session';
-pptx1.author = 'PolicyEngine';
-
-addTeamSlide(pptx1);
-addThesisSlide(pptx1);
-addAssumptionsSlide(pptx1);
-addInterviewLogSlide(pptx1);
-
-await pptx1.writeFile({ fileName: '/Users/maxghenis/Downloads/4373_PolicyEngine_01202026.pptx' });
-console.log('Created: 4373_PolicyEngine_01202026.pptx');
-
-// Generate PPTX 2: Team, Thesis, Goals & Charter
-const pptx2 = new pptxgen();
-pptx2.layout = 'LAYOUT_16x9';
-pptx2.title = 'PolicyEngine POSE - Kickoff';
-pptx2.author = 'PolicyEngine';
-
-addTeamSlide(pptx2);
-addThesisSlide(pptx2);
-addGoalsCharterSlide(pptx2);
-
-await pptx2.writeFile({ fileName: '/Users/maxghenis/Downloads/4373_PolicyEngine_01202026_OfficeHours.pptx' });
-console.log('Created: 4373_PolicyEngine_01202026_OfficeHours.pptx');
diff --git a/app/package-lock.json b/app/package-lock.json
new file mode 100644
index 0000000..579e270
--- /dev/null
+++ b/app/package-lock.json
@@ -0,0 +1,4153 @@
+{
+ "name": "app",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "app",
+ "version": "0.0.0",
+ "dependencies": {
+ "lucide-react": "^0.576.0",
+ "pptxgenjs": "^4.0.1",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0",
+ "recharts": "^3.7.0"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.39.1",
+ "@tailwindcss/vite": "^4.2.1",
+ "@types/node": "^24.10.1",
+ "@types/react": "^19.2.7",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "^5.1.1",
+ "eslint": "^9.39.1",
+ "eslint-plugin-react-hooks": "^7.0.1",
+ "eslint-plugin-react-refresh": "^0.4.24",
+ "globals": "^16.5.0",
+ "tailwindcss": "^4.2.1",
+ "typescript": "~5.9.3",
+ "typescript-eslint": "^8.48.0",
+ "vite": "^7.3.1"
+ }
+ },
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
+ "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz",
+ "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz",
+ "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz",
+ "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz",
+ "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz",
+ "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz",
+ "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz",
+ "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz",
+ "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz",
+ "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz",
+ "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz",
+ "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz",
+ "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz",
+ "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz",
+ "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz",
+ "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz",
+ "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz",
+ "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz",
+ "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz",
+ "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz",
+ "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz",
+ "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz",
+ "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+ "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
+ "dev": true,
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "dev": true,
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.21.1",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
+ "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
+ "dev": true,
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.7",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.2"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
+ "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
+ "dev": true,
+ "dependencies": {
+ "@eslint/core": "^0.17.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
+ "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.4.tgz",
+ "integrity": "sha512-4h4MVF8pmBsncB60r0wSJiIeUKTSD4m7FmTFThG8RHlsg9ajqckLm9OraguFGZE4vVdpiI1Q4+hFnisopmG6gQ==",
+ "dev": true,
+ "dependencies": {
+ "ajv": "^6.14.0",
+ "debug": "^4.3.2",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.1",
+ "minimatch": "^3.1.3",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "9.39.3",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.3.tgz",
+ "integrity": "sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==",
+ "dev": true,
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
+ "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
+ "dev": true,
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
+ "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
+ "dev": true,
+ "dependencies": {
+ "@eslint/core": "^0.17.0",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "dev": true,
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.7",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
+ "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
+ "dev": true,
+ "dependencies": {
+ "@humanfs/core": "^0.19.1",
+ "@humanwhocodes/retry": "^0.4.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "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,
+ "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,
+ "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,
+ "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
+ },
+ "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,
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.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==",
+ "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/@reduxjs/toolkit/node_modules/immer": {
+ "version": "11.1.4",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.4.tgz",
+ "integrity": "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/immer"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-rc.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz",
+ "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==",
+ "dev": true
+ },
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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=="
+ },
+ "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=="
+ },
+ "node_modules/@tailwindcss/node": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz",
+ "integrity": "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.5",
+ "enhanced-resolve": "^5.19.0",
+ "jiti": "^2.6.1",
+ "lightningcss": "1.31.1",
+ "magic-string": "^0.30.21",
+ "source-map-js": "^1.2.1",
+ "tailwindcss": "4.2.1"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.1.tgz",
+ "integrity": "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 20"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.2.1",
+ "@tailwindcss/oxide-darwin-arm64": "4.2.1",
+ "@tailwindcss/oxide-darwin-x64": "4.2.1",
+ "@tailwindcss/oxide-freebsd-x64": "4.2.1",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.2.1",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.2.1",
+ "@tailwindcss/oxide-linux-x64-musl": "4.2.1",
+ "@tailwindcss/oxide-wasm32-wasi": "4.2.1",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.2.1"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-android-arm64": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.1.tgz",
+ "integrity": "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.1.tgz",
+ "integrity": "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.1.tgz",
+ "integrity": "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.1.tgz",
+ "integrity": "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.1.tgz",
+ "integrity": "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.1.tgz",
+ "integrity": "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.1.tgz",
+ "integrity": "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz",
+ "integrity": "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz",
+ "integrity": "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.1.tgz",
+ "integrity": "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==",
+ "bundleDependencies": [
+ "@napi-rs/wasm-runtime",
+ "@emnapi/core",
+ "@emnapi/runtime",
+ "@tybys/wasm-util",
+ "@emnapi/wasi-threads",
+ "tslib"
+ ],
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.8.1",
+ "@emnapi/runtime": "^1.8.1",
+ "@emnapi/wasi-threads": "^1.1.0",
+ "@napi-rs/wasm-runtime": "^1.1.1",
+ "@tybys/wasm-util": "^0.10.1",
+ "tslib": "^2.8.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz",
+ "integrity": "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.1.tgz",
+ "integrity": "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/vite": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.1.tgz",
+ "integrity": "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w==",
+ "dev": true,
+ "dependencies": {
+ "@tailwindcss/node": "4.2.1",
+ "@tailwindcss/oxide": "4.2.1",
+ "tailwindcss": "4.2.1"
+ },
+ "peerDependencies": {
+ "vite": "^5.2.0 || ^6 || ^7"
+ }
+ },
+ "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,
+ "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,
+ "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,
+ "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,
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/d3-array": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
+ "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="
+ },
+ "node_modules/@types/d3-color": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
+ "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="
+ },
+ "node_modules/@types/d3-ease": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
+ "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="
+ },
+ "node_modules/@types/d3-interpolate": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
+ "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
+ "dependencies": {
+ "@types/d3-color": "*"
+ }
+ },
+ "node_modules/@types/d3-path": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
+ "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="
+ },
+ "node_modules/@types/d3-scale": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
+ "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
+ "dependencies": {
+ "@types/d3-time": "*"
+ }
+ },
+ "node_modules/@types/d3-shape": {
+ "version": "3.1.8",
+ "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz",
+ "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==",
+ "dependencies": {
+ "@types/d3-path": "*"
+ }
+ },
+ "node_modules/@types/d3-time": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
+ "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="
+ },
+ "node_modules/@types/d3-timer": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
+ "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="
+ },
+ "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
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true
+ },
+ "node_modules/@types/node": {
+ "version": "24.11.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.11.0.tgz",
+ "integrity": "sha512-fPxQqz4VTgPI/IQ+lj9r0h+fDR66bzoeMGHp8ASee+32OSGIkeASsoZuJixsQoVef1QJbeubcPBxKk22QVoWdw==",
+ "dev": true,
+ "dependencies": {
+ "undici-types": "~7.16.0"
+ }
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.14",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
+ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
+ "devOptional": true,
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.2.3",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
+ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
+ "dev": true,
+ "peerDependencies": {
+ "@types/react": "^19.2.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=="
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz",
+ "integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.12.2",
+ "@typescript-eslint/scope-manager": "8.56.1",
+ "@typescript-eslint/type-utils": "8.56.1",
+ "@typescript-eslint/utils": "8.56.1",
+ "@typescript-eslint/visitor-keys": "8.56.1",
+ "ignore": "^7.0.5",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.4.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^8.56.1",
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz",
+ "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "8.56.1",
+ "@typescript-eslint/types": "8.56.1",
+ "@typescript-eslint/typescript-estree": "8.56.1",
+ "@typescript-eslint/visitor-keys": "8.56.1",
+ "debug": "^4.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz",
+ "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.56.1",
+ "@typescript-eslint/types": "^8.56.1",
+ "debug": "^4.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz",
+ "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "8.56.1",
+ "@typescript-eslint/visitor-keys": "8.56.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/tsconfig-utils": {
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz",
+ "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==",
+ "dev": true,
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz",
+ "integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "8.56.1",
+ "@typescript-eslint/typescript-estree": "8.56.1",
+ "@typescript-eslint/utils": "8.56.1",
+ "debug": "^4.4.3",
+ "ts-api-utils": "^2.4.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz",
+ "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==",
+ "dev": true,
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz",
+ "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/project-service": "8.56.1",
+ "@typescript-eslint/tsconfig-utils": "8.56.1",
+ "@typescript-eslint/types": "8.56.1",
+ "@typescript-eslint/visitor-keys": "8.56.1",
+ "debug": "^4.4.3",
+ "minimatch": "^10.2.2",
+ "semver": "^7.7.3",
+ "tinyglobby": "^0.2.15",
+ "ts-api-utils": "^2.4.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
+ "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+ "version": "10.2.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
+ "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^5.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz",
+ "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.9.1",
+ "@typescript-eslint/scope-manager": "8.56.1",
+ "@typescript-eslint/types": "8.56.1",
+ "@typescript-eslint/typescript-estree": "8.56.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz",
+ "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "8.56.1",
+ "eslint-visitor-keys": "^5.0.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
+ "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
+ "dev": true,
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "5.1.4",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.4.tgz",
+ "integrity": "sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.29.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-rc.3",
+ "@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/acorn": {
+ "version": "8.16.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
+ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
+ "dev": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
+ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
+ "dev": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "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==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "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==",
+ "dev": true
+ },
+ "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,
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "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==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "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"
+ }
+ ],
+ "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/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001776",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001776.tgz",
+ "integrity": "sha512-sg01JDPzZ9jGshqKSckOQthXnYwOEP50jeVFhaSFbZcOy05TiuuaffDOfcwtCisJ9kNQuLBFibYywv2Bgm9osw==",
+ "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"
+ }
+ ]
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "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==",
+ "dev": true,
+ "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==",
+ "dev": true
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true
+ },
+ "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
+ },
+ "node_modules/core-util-is": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
+ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "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==",
+ "devOptional": true
+ },
+ "node_modules/d3-array": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
+ "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
+ "dependencies": {
+ "internmap": "1 - 2"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-color": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
+ "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-ease": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
+ "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-format": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz",
+ "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-interpolate": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
+ "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
+ "dependencies": {
+ "d3-color": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-path": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
+ "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-scale": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
+ "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
+ "dependencies": {
+ "d3-array": "2.10.0 - 3",
+ "d3-format": "1 - 3",
+ "d3-interpolate": "1.2.0 - 3",
+ "d3-time": "2.1.1 - 3",
+ "d3-time-format": "2 - 4"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-shape": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
+ "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
+ "dependencies": {
+ "d3-path": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
+ "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
+ "dependencies": {
+ "d3-array": "2 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time-format": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
+ "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
+ "dependencies": {
+ "d3-time": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-timer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
+ "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
+ "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==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decimal.js-light": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
+ "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.20.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz",
+ "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.3.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/es-toolkit": {
+ "version": "1.45.0",
+ "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.45.0.tgz",
+ "integrity": "sha512-RArCX+Zea16+R1jg4mH223Z8p/ivbJjIkU3oC6ld2bdUfmDxiCkFYSi9zLOR2anucWJUeH4Djnzgd0im0nD3dw==",
+ "workspaces": [
+ "docs",
+ "benchmarks"
+ ]
+ },
+ "node_modules/esbuild": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
+ "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.3",
+ "@esbuild/android-arm": "0.27.3",
+ "@esbuild/android-arm64": "0.27.3",
+ "@esbuild/android-x64": "0.27.3",
+ "@esbuild/darwin-arm64": "0.27.3",
+ "@esbuild/darwin-x64": "0.27.3",
+ "@esbuild/freebsd-arm64": "0.27.3",
+ "@esbuild/freebsd-x64": "0.27.3",
+ "@esbuild/linux-arm": "0.27.3",
+ "@esbuild/linux-arm64": "0.27.3",
+ "@esbuild/linux-ia32": "0.27.3",
+ "@esbuild/linux-loong64": "0.27.3",
+ "@esbuild/linux-mips64el": "0.27.3",
+ "@esbuild/linux-ppc64": "0.27.3",
+ "@esbuild/linux-riscv64": "0.27.3",
+ "@esbuild/linux-s390x": "0.27.3",
+ "@esbuild/linux-x64": "0.27.3",
+ "@esbuild/netbsd-arm64": "0.27.3",
+ "@esbuild/netbsd-x64": "0.27.3",
+ "@esbuild/openbsd-arm64": "0.27.3",
+ "@esbuild/openbsd-x64": "0.27.3",
+ "@esbuild/openharmony-arm64": "0.27.3",
+ "@esbuild/sunos-x64": "0.27.3",
+ "@esbuild/win32-arm64": "0.27.3",
+ "@esbuild/win32-ia32": "0.27.3",
+ "@esbuild/win32-x64": "0.27.3"
+ }
+ },
+ "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,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "9.39.3",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.3.tgz",
+ "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.8.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.21.1",
+ "@eslint/config-helpers": "^0.4.2",
+ "@eslint/core": "^0.17.0",
+ "@eslint/eslintrc": "^3.3.1",
+ "@eslint/js": "9.39.3",
+ "@eslint/plugin-kit": "^0.4.1",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^8.4.0",
+ "eslint-visitor-keys": "^4.2.1",
+ "espree": "^10.4.0",
+ "esquery": "^1.5.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz",
+ "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.24.4",
+ "@babel/parser": "^7.24.4",
+ "hermes-parser": "^0.25.1",
+ "zod": "^3.25.0 || ^4.0.0",
+ "zod-validation-error": "^3.5.0 || ^4.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-react-refresh": {
+ "version": "0.4.26",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz",
+ "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==",
+ "dev": true,
+ "peerDependencies": {
+ "eslint": ">=8.40"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "dev": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/eventemitter3": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
+ "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true
+ },
+ "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,
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.4.tgz",
+ "integrity": "sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==",
+ "dev": true
+ },
+ "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,
+ "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,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "16.5.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz",
+ "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true
+ },
+ "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==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/hermes-estree": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
+ "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==",
+ "dev": true
+ },
+ "node_modules/hermes-parser": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz",
+ "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==",
+ "dev": true,
+ "dependencies": {
+ "hermes-estree": "0.25.1"
+ }
+ },
+ "node_modules/https": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/https/-/https-1.0.0.tgz",
+ "integrity": "sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg=="
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/image-size": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz",
+ "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==",
+ "dependencies": {
+ "queue": "6.0.2"
+ },
+ "bin": {
+ "image-size": "bin/image-size.js"
+ },
+ "engines": {
+ "node": ">=16.x"
+ }
+ },
+ "node_modules/immediate": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
+ },
+ "node_modules/immer": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz",
+ "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/immer"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+ },
+ "node_modules/internmap": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
+ "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "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==",
+ "dev": true,
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true
+ },
+ "node_modules/jiti": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
+ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
+ "dev": true,
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "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
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true
+ },
+ "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,
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jszip": {
+ "version": "3.10.1",
+ "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
+ "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
+ "dependencies": {
+ "lie": "~3.3.0",
+ "pako": "~1.0.2",
+ "readable-stream": "~2.3.6",
+ "setimmediate": "^1.0.5"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lie": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
+ "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
+ "dependencies": {
+ "immediate": "~3.0.5"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz",
+ "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==",
+ "dev": true,
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.31.1",
+ "lightningcss-darwin-arm64": "1.31.1",
+ "lightningcss-darwin-x64": "1.31.1",
+ "lightningcss-freebsd-x64": "1.31.1",
+ "lightningcss-linux-arm-gnueabihf": "1.31.1",
+ "lightningcss-linux-arm64-gnu": "1.31.1",
+ "lightningcss-linux-arm64-musl": "1.31.1",
+ "lightningcss-linux-x64-gnu": "1.31.1",
+ "lightningcss-linux-x64-musl": "1.31.1",
+ "lightningcss-win32-arm64-msvc": "1.31.1",
+ "lightningcss-win32-x64-msvc": "1.31.1"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz",
+ "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz",
+ "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz",
+ "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz",
+ "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz",
+ "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz",
+ "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz",
+ "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz",
+ "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz",
+ "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz",
+ "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz",
+ "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true
+ },
+ "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,
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/lucide-react": {
+ "version": "0.576.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.576.0.tgz",
+ "integrity": "sha512-koNxU14BXrxUfZQ9cUaP0ES1uyPZKYDjk31FQZB6dQ/x+tXk979sVAn9ppZ/pVeJJyOxVM8j1E+8QEuSc02Vug==",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "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
+ },
+ "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"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true
+ },
+ "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
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/pako": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "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"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/pptxgenjs": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/pptxgenjs/-/pptxgenjs-4.0.1.tgz",
+ "integrity": "sha512-TeJISr8wouAuXw4C1F/mC33xbZs/FuEG6nH9FG1Zj+nuPcGMP5YRHl6X+j3HSUnS1f3at6k75ZZXPMZlA5Lj9A==",
+ "dependencies": {
+ "@types/node": "^22.8.1",
+ "https": "^1.0.0",
+ "image-size": "^1.2.1",
+ "jszip": "^3.10.1"
+ }
+ },
+ "node_modules/pptxgenjs/node_modules/@types/node": {
+ "version": "22.19.13",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.13.tgz",
+ "integrity": "sha512-akNQMv0wW5uyRpD2v2IEyRSZiR+BeGuoB6L310EgGObO44HSMNT8z1xzio28V8qOrgYaopIDNA18YgdXd+qTiw==",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/pptxgenjs/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=="
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/process-nextick-args": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/queue": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
+ "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
+ "dependencies": {
+ "inherits": "~2.0.3"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
+ "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
+ "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.4"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz",
+ "integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==",
+ "peer": true
+ },
+ "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==",
+ "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,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/readable-stream": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/recharts": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.7.0.tgz",
+ "integrity": "sha512-l2VCsy3XXeraxIID9fx23eCb6iCBsxUQDnE8tWm6DFdszVAO7WVY/ChAD9wVit01y6B2PMupYiMmQwhgPHc9Ew==",
+ "workspaces": [
+ "www"
+ ],
+ "dependencies": {
+ "@reduxjs/toolkit": "1.x.x || 2.x.x",
+ "clsx": "^2.1.1",
+ "decimal.js-light": "^2.5.1",
+ "es-toolkit": "^1.39.3",
+ "eventemitter3": "^5.0.1",
+ "immer": "^10.1.1",
+ "react-redux": "8.x.x || 9.x.x",
+ "reselect": "5.1.1",
+ "tiny-invariant": "^1.3.3",
+ "use-sync-external-store": "^1.2.2",
+ "victory-vendor": "^37.0.2"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "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=="
+ },
+ "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==",
+ "peerDependencies": {
+ "redux": "^5.0.0"
+ }
+ },
+ "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=="
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "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,
+ "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/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ },
+ "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=="
+ },
+ "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,
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/setimmediate": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+ "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
+ },
+ "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==",
+ "dev": true,
+ "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==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz",
+ "integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==",
+ "dev": true
+ },
+ "node_modules/tapable": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
+ "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/tiny-invariant": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
+ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/ts-api-utils": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz",
+ "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==",
+ "dev": true,
+ "engines": {
+ "node": ">=18.12"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "dev": true,
+ "optional": true
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.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,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/typescript-eslint": {
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.56.1.tgz",
+ "integrity": "sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/eslint-plugin": "8.56.1",
+ "@typescript-eslint/parser": "8.56.1",
+ "@typescript-eslint/typescript-estree": "8.56.1",
+ "@typescript-eslint/utils": "8.56.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
+ "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
+ "dev": true
+ },
+ "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"
+ }
+ ],
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.1.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==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
+ },
+ "node_modules/victory-vendor": {
+ "version": "37.3.6",
+ "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz",
+ "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==",
+ "dependencies": {
+ "@types/d3-array": "^3.0.3",
+ "@types/d3-ease": "^3.0.0",
+ "@types/d3-interpolate": "^3.0.1",
+ "@types/d3-scale": "^4.0.2",
+ "@types/d3-shape": "^3.1.0",
+ "@types/d3-time": "^3.0.0",
+ "@types/d3-timer": "^3.0.0",
+ "d3-array": "^3.1.6",
+ "d3-ease": "^3.0.1",
+ "d3-interpolate": "^3.0.1",
+ "d3-scale": "^4.0.2",
+ "d3-shape": "^3.1.0",
+ "d3-time": "^3.0.0",
+ "d3-timer": "^3.0.1"
+ }
+ },
+ "node_modules/vite": {
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
+ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.27.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "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/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/zod": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
+ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-validation-error": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz",
+ "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "zod": "^3.25.0 || ^4.0.0"
+ }
+ }
+ }
+}
diff --git a/app/package.json b/app/package.json
index 819ae34..33e851c 100644
--- a/app/package.json
+++ b/app/package.json
@@ -7,42 +7,29 @@
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
- "preview": "vite preview",
- "test": "vitest",
- "test:run": "vitest run"
+ "preview": "vite preview"
},
"dependencies": {
- "@supabase/supabase-js": "^2.90.1",
- "@tanstack/react-query": "^5.90.19",
- "date-fns": "^4.1.0",
- "framer-motion": "^12.27.1",
- "lucide-react": "^0.562.0",
+ "lucide-react": "^0.576.0",
"pptxgenjs": "^4.0.1",
"react": "^19.2.0",
"react-dom": "^19.2.0",
- "recharts": "^3.6.0"
+ "recharts": "^3.7.0"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
- "@tailwindcss/forms": "0.5",
- "@testing-library/jest-dom": "^6.9.1",
- "@testing-library/react": "^16.3.2",
- "@testing-library/user-event": "^14.6.1",
+ "@tailwindcss/vite": "^4.2.1",
"@types/node": "^24.10.1",
- "@types/react": "^19.2.5",
+ "@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.1",
- "autoprefixer": "^10.4.23",
"eslint": "^9.39.1",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"globals": "^16.5.0",
- "jsdom": "^27.4.0",
- "postcss": "^8.5.6",
- "tailwindcss": "3",
+ "tailwindcss": "^4.2.1",
"typescript": "~5.9.3",
- "typescript-eslint": "^8.46.4",
- "vite": "^7.2.4",
- "vitest": "^4.0.17"
+ "typescript-eslint": "^8.48.0",
+ "vite": "^7.3.1"
}
}
diff --git a/app/postcss.config.js b/app/postcss.config.js
deleted file mode 100644
index 2e7af2b..0000000
--- a/app/postcss.config.js
+++ /dev/null
@@ -1,6 +0,0 @@
-export default {
- plugins: {
- tailwindcss: {},
- autoprefixer: {},
- },
-}
diff --git a/app/public/assets/ecosystem/canvas.png b/app/public/assets/ecosystem/canvas.png
new file mode 100644
index 0000000..0c9c0d4
Binary files /dev/null and b/app/public/assets/ecosystem/canvas.png differ
diff --git a/app/public/assets/ecosystem/cosilico.png b/app/public/assets/ecosystem/cosilico.png
new file mode 100644
index 0000000..26614f2
Binary files /dev/null and b/app/public/assets/ecosystem/cosilico.png differ
diff --git a/app/public/assets/ecosystem/policyengine.png b/app/public/assets/ecosystem/policyengine.png
new file mode 100644
index 0000000..e07bf1d
Binary files /dev/null and b/app/public/assets/ecosystem/policyengine.png differ
diff --git a/app/public/assets/ecosystem/rules-foundation.png b/app/public/assets/ecosystem/rules-foundation.png
new file mode 100644
index 0000000..915ef1f
Binary files /dev/null and b/app/public/assets/ecosystem/rules-foundation.png differ
diff --git a/app/public/assets/team/daniel-feenberg.jpg b/app/public/assets/team/daniel.jpg
similarity index 100%
rename from app/public/assets/team/daniel-feenberg.jpg
rename to app/public/assets/team/daniel.jpg
diff --git a/app/public/assets/team/max-ghenis.png b/app/public/assets/team/max.png
similarity index 100%
rename from app/public/assets/team/max-ghenis.png
rename to app/public/assets/team/max.png
diff --git a/app/public/assets/team/pavel-makarchuk.jpeg b/app/public/assets/team/pavel.jpg
similarity index 100%
rename from app/public/assets/team/pavel-makarchuk.jpeg
rename to app/public/assets/team/pavel.jpg
diff --git a/app/public/ecosystem-map-cosilico.html b/app/public/ecosystem-map-cosilico.html
deleted file mode 100644
index b4ac6e5..0000000
--- a/app/public/ecosystem-map-cosilico.html
+++ /dev/null
@@ -1,586 +0,0 @@
-
-
-
-
-
- Cosilico Ecosystem Map | POSE Winter 2026
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Arrow types
-
- revenue
-
- ideas / feedback
-
- technical / API
-
- data / analysis
-
- influence
-
-
-
-
-
- Government
-
-
-
- Commercial / Financial
-
-
-
- AI / Tech
-
-
-
- Data / Platforms
-
-
-
- Funding
-
-
-
-
-
-
- Implement tax rules accurately
- Calculate eligibility
- (vs Chainbridge, Deloitte)
-
-
-
-
- Tax calcs, imputation,
- ACA subsidies
- ($0.02/household API)
-
-
-
-
- RLVR signals, function calling
- (verifiable policy ground truth)
-
-
-
-
- 200+ attributes from minimal input
- (Microplex enrichment)
-
-
-
-
-
- Cosilico
- Public Benefit Corp — production infrastructure
-
-
-
- Cosilico Rules
- Deterministic calculations
-
-
- Rules API
-
-
- Verifier API (RLVR)
-
-
- Eligibility engines
-
-
-
- Cosilico Simulate
- Statistical modeling
-
-
- Microsim API
-
-
- Imputation
-
-
- Microplex
-
-
- Open source core (AGPL) · Hosted API (paid)
-
-
-
-
-
-
-
-
-
-
- 3
- AI labs
- Research Scientist, Product Lead
- (Anthropic, OpenAI, DeepMind)
- compute + revenue
-
-
-
-
-
-
- 2
- AI agent builders
- Engineer, PM at enterprise AI
- (Enterprise AI vendors)
- $10B+ enterprise AI mkt
-
-
-
-
-
-
-
-
- 3
- State revenue departments
- IT Director, Tax Administrator
- (State tax agencies)
- $1B+ state IT contracts
- vs Chainbridge, Corticon
-
-
-
-
-
-
- 3
- Benefits agencies
- Program Dir, Systems Mgr
- (SNAP, TANF, Medicaid)
- $500M+ eligibility contracts
- vs Deloitte, Accenture
-
-
-
-
-
-
-
-
- 3
- Tax software vendors
- PM, Engineer
- (TurboTax, H&R Block, TaxAct)
- $90B+ tax software mkt
-
-
-
-
-
-
- 2
- Financial planners
- CFP, Wealth Mgr, Robo-advisor PM
- (Betterment, Wealthfront)
- $5B+ wealth tech mkt
-
-
-
-
-
-
- 2
- Banks / lenders
- CDO, Underwriting Lead
- (Banks, mortgage, fintechs)
- $100B+ consumer lending
-
-
-
-
-
-
- 2
- Insurance / actuaries
- Actuary, Compliance Officer
- (Health insurers, ACA mktplace)
- $50B+ health ins admin
-
-
-
-
-
-
-
-
- 2
- Marketing / data platforms
- Data Science Lead, Audience PM
- (LiveRamp, Acxiom)
- $2.4B+ data enrichment mkt
-
-
-
-
-
-
- 2
- Benefit platform builders
- PM, Engineer at screeners
- (Benefits screeners, fintech)
- API + feedback
-
-
-
-
-
-
-
-
- 2
- VC / Impact investors
- VC Partner, Impact Investor
- (Impact VCs, ESG funds)
- investment capital
-
-
-
-
-
-
- 2
- Quant finance / Econ sim
- Quant Researcher, Economist
- (Barclays, hedge funds, QURI)
- $500B+ quant finance
-
-
-
-
-
-
- compute + $
-
-
-
- Verifier API
-
-
-
- API $
-
-
-
- citations
-
-
-
- paid contracts
-
-
-
- auditable rules
-
-
-
- contracts
-
-
-
- eligibility engine
-
-
-
- $0.02/hh
-
-
-
- Rules API
-
-
-
- API $
-
-
-
- tax calcs
-
-
-
- API $
-
-
-
- Microplex
-
-
-
- API $
-
-
-
- ACA rules
-
-
-
- API $
-
-
-
- Microplex data
-
-
-
- $ + feedback
-
-
-
- eligibility API
-
-
-
- investment $
-
-
-
- impact metrics
-
-
-
- API $
-
-
-
- microsim
-
-
-
-
- Assumptions to test
- 1. Gov agencies buy OSS rules?
- 2. $0.02/hh beats internal build?
- 3. AI labs pay for verifier?
- 4. Microplex accurate enough?
-
-
-
-
- Business model
- Open source core (AGPL)
- Hosted API: pay-per-call
- Enterprise: contract pricing
-
-
-
-
- Two product lines
- Rules: statute → code (9 segments)
- Simulate: data → inference (3 segments)
- Most segments touch Rules
-
-
-
-
-
-
-
-
-
diff --git a/app/public/ecosystem-map-policyengine.html b/app/public/ecosystem-map-policyengine.html
deleted file mode 100644
index e5229fa..0000000
--- a/app/public/ecosystem-map-policyengine.html
+++ /dev/null
@@ -1,604 +0,0 @@
-
-
-
-
-
- PolicyEngine Ecosystem Map | POSE Winter 2026
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Arrow types
-
- ideas / feedback
-
- funding
-
- technical / code
-
- data / analysis
-
- influence
-
-
-
-
-
- Gov / Validation
-
-
-
- Funding
-
-
-
- Users / Research
-
-
-
- Influence
-
-
-
- Contributors / Team
-
-
-
- Risks
-
-
-
-
-
- Publish research papers
- (citations, tenure)
-
-
-
-
- Score reforms quickly
- (faster than competitors)
-
-
-
-
- Cite credible analysis
- (back advocacy)
-
-
-
-
- Validate against CBO/JCT
- (transparency)
-
-
-
-
- Foundations: policy research
- Clients: custom analysis
- NSF: infrastructure
- (different value exchanges)
-
-
-
-
- Fact-check policy claims
- (embeddable interactives)
-
-
-
-
- PolicyEngine
- 501(c)(3) / UK Charity — "Tell the story"
-
-
-
- policyengine-us
-
-
- policyengine-uk
-
-
- policyengine-core
-
-
-
- CPS microdata
-
-
- FRS microdata
-
- enhanced data
-
-
-
- policyengine.org (web app)
-
-
- custom interactives / dashboards
-
-
-
- REST API / policyengine.py
-
-
-
- NBER, Atlanta Fed, Brookings
-
-
-
- Policy analysis and reports (quantitative storytelling)
-
-
- Models · Data · Apps · API · Research
- Open source (AGPL) — uses Cosilico infrastructure
-
-
-
-
-
-
-
-
- 7
- Government Economists
- Budget Analyst, Policy Economist
- (CBO, JCT, Treasury, SSA)
- Validate, inform scoring
-
-
-
-
-
-
- 3
- Policymakers / Staffers
- Legislative Dir, Committee Staff
- (Hill offices, state leg)
- Quick what-if, constituent Qs
-
-
-
-
-
-
- 5
- Policy Foundations
- (Arnold Ventures, Pritzker)
- Grants for research output
-
-
-
-
-
-
- 3
- Research Clients
- (CRFB, Niskanen, think tanks)
- Paid analysis / custom tools
-
-
-
-
-
-
- 2
- Government Grants
- (NSF POSE)
- Infrastructure funding
-
-
-
-
-
-
- 3
- Data Journalists
- Reporter, Data Editor (NYT, WaPo)
- Fact-check, build interactives
-
-
-
-
-
-
- 7
- Academic Researchers
- Professor, PhD, Postdoc
- (Econ, Public Policy depts)
- Publications, grants, teaching
-
-
-
-
-
-
- 10
- Think Tank Analysts
- Policy Analyst, Fellow, Director
- (CRFB, Niskanen, Urban, AEI)
- Fast, credible methodology
-
-
-
-
-
-
- 5
- Policy Advocates
- Exec Director, Comms Dir
- (CBPP, advocacy orgs)
- Credible backing, media reach
-
-
-
-
-
-
- 2
- Policymakers (reach)
- Leg. Directors, Committee Staff
- (Congress, state legislatures)
- Legitimacy, use cases
-
-
-
-
-
-
- 2
- OSS Contributors
- Software Eng, Data Scientist
- (External volunteers)
- Portfolio, impact, learning
-
-
-
-
-
-
- 2
- PolicyEngine Team
- Analyst, Engineer (Core team)
- Build models, demo via research
-
-
-
-
-
-
- 1
- Data/Tool Partners
- CTO, Product Lead (Census, IRS)
- Data sharing, co-develop
-
-
-
-
-
-
- 2
- Non-Users (Potential)
- Using alternatives (TRIM3, etc)
- Why not? Cost? Trust?
-
-
-
-
-
-
- 1
- Competitors
- CEO/CTO of rival tools
- Collaborate?
-
-
-
-
-
-
- 0
- Misusers
- Bad-faith analysts
- Cherry-pick results
-
-
-
-
-
-
- $ grants
-
-
-
- research
-
-
-
- fees
-
-
-
- NSF $
-
-
-
- validate
-
-
- methodology
-
-
-
- what-if
-
-
-
- legitimacy
-
-
-
- ideas
-
-
- tools, docs
-
-
-
- reports
-
-
- feedback
-
-
-
- analysis
-
-
- amplify
-
-
-
- influence
-
-
-
- PRs, fixes
-
-
-
-
-
-
- data
-
-
-
- embeds
-
-
-
- barriers?
-
-
-
-
- Partnership model
- Certified Cosilico partner
- (Salesforce-style accreditation)
- Differentiates via research
-
-
-
-
- Assumptions to test:
- Think tanks want OSS?
- Gov economists engage?
- Clients pay for analysis?
- Contributors exist?
-
-
-
-
-
-
-
-
-
diff --git a/app/public/ecosystem-map-rules-foundation.html b/app/public/ecosystem-map-rules-foundation.html
deleted file mode 100644
index 8c77bdf..0000000
--- a/app/public/ecosystem-map-rules-foundation.html
+++ /dev/null
@@ -1,484 +0,0 @@
-
-
-
-
-
- Rules Foundation Ecosystem Map | POSE Winter 2026
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Arrow types
-
- ideas / feedback
-
- funding
-
- technical / code
-
- data / analysis
-
- influence
-
-
-
-
-
- Gov / Validation
-
-
-
- Funding
-
-
-
- Technical partners
-
-
-
- Research
-
-
-
- Contributors
-
-
-
- Integrators
-
-
-
-
-
- Validate statute encodings
- against official interpretations
- (transparency, auditability)
-
-
-
-
- RLVR training signals
- Policy reasoning benchmarks
- (verifiable ground truth)
-
-
-
-
- Computational law research
- Legal formalization papers
- (citations, datasets)
-
-
-
-
- Fund public legal infra
- Open-source civic tech
- (measurable coverage)
-
-
-
-
-
-
-
- Rules Foundation
-
-
-
- Source document archive
- Statutes, regulations → Akoma Ntoso XML
-
-
-
- .rac DSL
- Encoding format for law
-
-
- Reference compiler
- Open-source spec impl
-
-
-
- AutoRAC validation harness
- 3-tier: CI tests → Oracle checks → LLM reviewers
- Statute-linked test suites with citation chains
-
-
-
- Ground truth test data
- Open benchmarks for AI evaluation / RLVR
-
-
- Archive · DSL · Validation · Benchmarks
- All open source · Multi-stakeholder governance
-
-
-
-
-
-
-
-
- 5
- Gov standards bodies
- Tax Law Specialist, Reg. Counsel
- (IRS, SSA, CMS, Treasury OTA)
- Validate statute interpretations
- Give: official guidance
-
-
-
-
-
-
- 3
- AI labs
- Research Scientist, Product Lead
- (Anthropic, OpenAI, DeepMind)
- Policy-aware AI, no hallucination
- Give: compute, research collab
-
-
-
-
-
-
- 3
- Academic researchers
- Law Prof, Legal Informatics
- (Stanford CodeX, MIT, law schools)
- Computational law research
- Give: validation, citations
-
-
-
-
-
-
- 2
- Encoding community
- Lawyer, Policy Analyst, Engineer
- (Volunteers, pro bono, civic tech)
- Review and improve encodings
- Give: encoding review, interpretation
-
-
-
-
-
-
- 2
- Downstream consumers
- CTO, Product Lead, Eng Manager
- (Cosilico, PolicyEngine, others)
- Build on tested rule encodings
- Give: feedback, bug reports
-
-
-
-
-
-
- 2
- Grants / Foundations
- Program Manager, Foundation Dir
- (NSF, Sloan, Knight, Ford)
- Fund public legal infrastructure
- Give: grant funding
-
-
-
-
-
-
- interpretations
-
-
-
- audit trail
-
-
-
- compute
-
-
-
- ground truth
-
-
-
- validation
-
-
- datasets
-
-
-
- encoding PRs
-
-
-
- recognition
-
-
-
- encodings
-
-
-
- feedback
-
-
-
- $ grants
-
-
-
- impact reports
-
-
-
-
-
-
-
-
- Assumptions to test
- 1. Agencies share interpretations?
- 2. AI labs invest in ground truth?
- 3. Multi-stakeholder governance?
- 4. Automation vs community?
-
-
-
-
-
-
-
-
- Analogy
- "OpenStreetMap for law"
- Neutral, community-maintained
-
-
-
-
- Scope: literal and bounded
- Encode the law as written
- Tooling/products → Cosilico
-
-
-
-
- Downstream value chain
- Cosilico: production compiler
- PolicyEngine: policy analysis
- AI agents: grounded reasoning
- All consume RF encodings
-
-
-
-
-
-
-
-
-
diff --git a/app/public/ecosystem-map.html b/app/public/ecosystem-map.html
deleted file mode 100644
index b93a7af..0000000
--- a/app/public/ecosystem-map.html
+++ /dev/null
@@ -1,576 +0,0 @@
-
-
-
-
-
- PolicyEngine Ecosystem Map | POSE Winter 2026
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Arrow types
-
- ideas / feedback
-
- funding
-
- technical / code
-
- data / analysis
-
- influence
-
-
-
-
-
- Gov / Validation
-
-
-
- Funding
-
-
-
- Users / Research
-
-
-
- Integrators
-
-
-
- Contributors / Team
-
-
-
- Risks
-
-
-
-
-
- Publish research papers
- (citations, tenure)
-
-
-
-
- Score reforms quickly
- (faster than competitors)
-
-
-
-
- Embed eligibility calcs
- (no rules engine needed)
-
-
-
-
- Validate against CBO/JCT
- (transparency)
-
-
-
-
- Foundations: policy research
- Clients: custom analysis
- NSF: infrastructure
- (different value exchanges)
-
-
-
-
- Fact-check policy claims
- (interactives)
-
-
-
-
- PolicyEngine
-
-
-
- policyengine-us
-
-
- policyengine-uk
-
-
- policyengine-core
-
-
-
- us-data
-
-
- uk-data
-
- microdata
-
-
-
- policyengine.org (web app)
-
-
- custom interactives
-
-
-
- REST API / policyengine.py
-
-
- Models · Data · Apps · API
- All open source (AGPL)
-
-
-
-
-
-
-
-
- 10
- Government Economists
- Budget Analyst, Policy Economist
- (CBO, JCT, Treasury, SSA)
- Validate, inform scoring
-
-
-
-
-
-
- 5
- Policymakers / Staffers
- Legislative Dir, Committee Staff
- (Hill offices, state leg)
- Quick what-if, constituent Qs
-
-
-
-
-
-
- 10
- Policy Foundations
- (Arnold, Pritzker)
- Big grants → research back
-
-
-
-
-
-
- 5
- Research Clients
- (CRFB, Niskanen, think tanks)
- Paid analysis / custom tools
-
-
-
-
-
-
- 5
- Government Grants
- (NSF POSE)
- Infrastructure funding
-
-
-
-
-
-
- 5
- Data Journalists
- Reporter, Data Editor (NYT, WaPo)
- Fact-check, build interactives
-
-
-
-
-
-
- 10
- Academic Researchers
- Professor, PhD, Postdoc
- (Econ, Public Policy depts)
- Publications, grants, teaching
-
-
-
-
-
-
- 15
- Think Tank Analysts
- Policy Analyst, Fellow, Director
- (CRFB, Niskanen, Urban, AEI)
- Fast, credible methodology
-
-
-
-
-
-
- 5
- Benefit Platform Builders
- Product Manager, Engineer
- (Benefits screeners, fintech)
- API integration, auto-updates
-
-
-
-
-
-
- 10
- Policy Advocates
- Exec Director, Comms Dir
- (CBPP, advocacy orgs)
- Credible backing, media reach
-
-
-
-
-
-
- 5
- OSS Contributors
- Software Eng, Data Scientist
- (External volunteers)
- Portfolio, impact, learning
-
-
-
-
-
-
- 10
- PolicyEngine Team
- Analyst, Engineer (Core team)
- Build models, demo usage
-
-
-
-
-
-
- 5
- Data/Tool Partners
- CTO, Product Lead (Census, IRS)
- Data sharing, co-develop
-
-
-
-
-
-
- 10
- Non-Users (Potential)
- Using alternatives (TRIM3, etc)
- Why not? Cost? Trust?
-
-
-
-
-
-
- 3
- Competitors
- CEO/CTO of rival tools
- Collaborate?
-
-
-
-
-
-
- 0
- Misusers
- Bad-faith analysts
- Cherry-pick results
-
-
-
-
-
-
- $ grants
-
-
- research
-
-
- NSF $
-
-
-
- validate
-
-
- methodology
-
-
-
- what-if analysis
-
-
-
- ideas
-
-
- docs
-
-
-
- reports
-
-
- feedback
-
-
-
- API
-
-
-
- analysis
-
-
- amplify
-
-
-
- PRs, fixes
-
-
-
-
-
-
- data
-
-
-
- embeds
-
-
-
- barriers?
-
-
-
-
- How can we scale?
- • Champions / Evangelists
- • Citations as validation
- • Training workshops
-
-
-
-
- Assumptions to test:
- • Think tanks want OSS?
- • Platforms will use API?
- • Gov economists engage?
- • Contributors exist?
-
-
-
-
-
-
-
-
-
diff --git a/app/public/ecosystem-maps.html b/app/public/ecosystem-maps.html
deleted file mode 100644
index e354169..0000000
--- a/app/public/ecosystem-maps.html
+++ /dev/null
@@ -1,127 +0,0 @@
-
-
-
-
-
- Ecosystem maps | POSE Winter 2026
-
-
-
-
-
-
Ecosystem maps
-
NSF POSE Winter 2026 — three organizations, clear boundaries
-
-
-
- Rules Foundation 501(c)(3)
- Encode the law
- Neutral, multi-stakeholder foundation maintaining the authoritative encoding of law as code. Source document archive, .rac DSL, AutoRAC validation harness, ground truth test data.
-
-
-
- Cosilico Public Benefit Corp
- Run the infrastructure
- Production infrastructure with two product lines: Cosilico Rules (deterministic tax/benefit calculations) and Cosilico Simulate (microsimulation, imputation, Microplex). 11 market segments, $250B+ total addressable markets.
-
-
-
- PolicyEngine 501(c)(3) / UK Charity
- Tell the story
- Focused research organization: policy analysis, interactive tools, congressional district dashboards, research partnerships with NBER, Atlanta Fed, Brookings, NIESR. 12 segments, 100+ interview targets.
-
-
-
- Unified (original) Pre-split
- Combined ecosystem
- Original unified PolicyEngine ecosystem map before the three-org split. Kept for reference.
-
-
-
-
-
-
-
diff --git a/app/public/vite.svg b/app/public/vite.svg
deleted file mode 100644
index e7b8dfb..0000000
--- a/app/public/vite.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/app/src/App.css b/app/src/App.css
deleted file mode 100644
index b9d355d..0000000
--- a/app/src/App.css
+++ /dev/null
@@ -1,42 +0,0 @@
-#root {
- max-width: 1280px;
- margin: 0 auto;
- padding: 2rem;
- text-align: center;
-}
-
-.logo {
- height: 6em;
- padding: 1.5em;
- will-change: filter;
- transition: filter 300ms;
-}
-.logo:hover {
- filter: drop-shadow(0 0 2em #646cffaa);
-}
-.logo.react:hover {
- filter: drop-shadow(0 0 2em #61dafbaa);
-}
-
-@keyframes logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
-
-@media (prefers-reduced-motion: no-preference) {
- a:nth-of-type(2) .logo {
- animation: logo-spin infinite 20s linear;
- }
-}
-
-.card {
- padding: 2em;
-}
-
-.read-the-docs {
- color: #888;
-}
diff --git a/app/src/App.tsx b/app/src/App.tsx
index 2b6de32..da2051b 100644
--- a/app/src/App.tsx
+++ b/app/src/App.tsx
@@ -1,374 +1,66 @@
-import { useState } from 'react'
-import { motion, AnimatePresence } from 'framer-motion'
-import { Plus, RefreshCw, Calendar, Users, Target, ExternalLink, BookOpen, ClipboardList, ListChecks, Presentation, Mail } from 'lucide-react'
-import { useInterviews } from './hooks/useInterviews'
-import { ProgressRing } from './components/ProgressRing'
-import { MilestoneTracker } from './components/MilestoneTracker'
-import { SegmentChart } from './components/SegmentChart'
-import { InterviewTable } from './components/InterviewTable'
-import { AddInterviewModal } from './components/AddInterviewModal'
-import { Materials } from './components/Materials'
-import { Assignments } from './components/Assignments'
-import { Slides } from './components/Slides'
-import { Outreach } from './components/Outreach'
-import type { Interview } from './types/database'
-import { isSupabaseConfigured } from './lib/supabase'
-
-type Tab = 'interviews' | 'assignments' | 'materials' | 'slides' | 'outreach'
+import { ScrollStory } from './components/presentation/ScrollStory';
+import type { ScrollSection } from './lib/types';
+
+// Main slides
+import { TitleSlide } from './components/slides/TitleSlide';
+import { TeamSlide } from './components/slides/TeamSlide';
+import { ThesisSlide } from './components/slides/ThesisSlide';
+import { ImpactSlide } from './components/slides/ImpactSlide';
+import { InterviewLogSlide } from './components/slides/InterviewLogSlide';
+import { AssumptionsSlide } from './components/slides/AssumptionsSlide';
+import { AhaMomentSlide } from './components/slides/AhaMomentSlide';
+import { RulesEcosystemSlide, CosilicoEcosystemSlide, PEEcosystemSlide } from './components/slides/EcosystemSlide';
+import { GovernanceSlide } from './components/slides/GovernanceSlide';
+import { SustainabilitySlide } from './components/slides/SustainabilitySlide';
+import { TimelineSlide } from './components/slides/TimelineSlide';
+import { CloseSlide } from './components/slides/CloseSlide';
+
+// Ecosystem Evolution (interactive)
+import { EcosystemEvolutionSlide } from './components/ecosystem/EcosystemEvolution';
+
+// Appendix slides
+import { VoicesSlide } from './components/slides/appendix/VoicesSlide';
+import { ImpactGoalsSlide } from './components/slides/appendix/ImpactGoalsSlide';
+import { PartnersSlide } from './components/slides/appendix/PartnersSlide';
+import { CanvasSlide } from './components/slides/appendix/CanvasSlide';
+import { CanvasDetailSlide } from './components/slides/appendix/CanvasDetailSlide';
+import { GovernanceDetailSlide } from './components/slides/appendix/GovernanceDetailSlide';
+import { CompetitiveSlide } from './components/slides/appendix/CompetitiveSlide';
+import { HighlightsSlide } from './components/slides/appendix/HighlightsSlide';
+import { MarketSlide } from './components/slides/appendix/MarketSlide';
+
+const sections: ScrollSection[] = [
+ // Main presentation (15 sections)
+ { id: 'title', title: 'Title', component: TitleSlide },
+ { id: 'team', title: 'Team', component: TeamSlide },
+ { id: 'thesis', title: 'Thesis', component: ThesisSlide },
+ { id: 'impact', title: 'Impact', component: ImpactSlide },
+ { id: 'interviews', title: 'Interviews', component: InterviewLogSlide },
+ { id: 'assumptions', title: 'Assumptions', component: AssumptionsSlide },
+ { id: 'aha', title: 'A-ha Moment', component: AhaMomentSlide },
+ { id: 'eco-evolution', title: 'Ecosystem Evolution', component: EcosystemEvolutionSlide, stickyHeight: 3 },
+ { id: 'eco-rules', title: 'Rules Foundation', component: RulesEcosystemSlide },
+ { id: 'eco-cosilico', title: 'Cosilico', component: CosilicoEcosystemSlide },
+ { id: 'eco-pe', title: 'PolicyEngine', component: PEEcosystemSlide },
+ { id: 'governance', title: 'Governance', component: GovernanceSlide },
+ { id: 'sustainability', title: 'Sustainability', component: SustainabilitySlide },
+ { id: 'timeline', title: 'Timeline', component: TimelineSlide },
+ { id: 'close', title: 'Close', component: CloseSlide },
+
+ // Appendix (9 sections)
+ { id: 'voices', title: 'Voices', component: VoicesSlide, isAppendix: true },
+ { id: 'impact-goals', title: 'Impact Goals', component: ImpactGoalsSlide, isAppendix: true },
+ { id: 'partners', title: 'Partners', component: PartnersSlide, isAppendix: true },
+ { id: 'canvas', title: 'Canvas', component: CanvasSlide, isAppendix: true },
+ { id: 'canvas-detail', title: 'Canvas Detail', component: CanvasDetailSlide, isAppendix: true },
+ { id: 'gov-detail', title: 'Gov Detail', component: GovernanceDetailSlide, isAppendix: true },
+ { id: 'competitive', title: 'Competitive', component: CompetitiveSlide, isAppendix: true },
+ { id: 'highlights', title: 'Highlights', component: HighlightsSlide, isAppendix: true },
+ { id: 'market', title: 'Market', component: MarketSlide, isAppendix: true },
+];
function App() {
- const {
- interviews,
- loading,
- error,
- addInterview,
- updateInterview,
- deleteInterview,
- refresh,
- completedCount,
- scheduledCount,
- segmentCounts,
- } = useInterviews()
-
- const [isModalOpen, setIsModalOpen] = useState(false)
- const [editingInterview, setEditingInterview] = useState(null)
- const [activeTab, setActiveTab] = useState('interviews')
-
- const handleSave = async (interview: Omit) => {
- if (editingInterview) {
- await updateInterview(editingInterview.id, interview)
- } else {
- await addInterview(interview)
- }
- setEditingInterview(null)
- }
-
- const handleEdit = (interview: Interview) => {
- setEditingInterview(interview)
- setIsModalOpen(true)
- }
-
- const handleStatusChange = async (id: string, status: Interview['status']) => {
- const updates: Partial = { status }
- if (status === 'completed') {
- updates.completed_date = new Date().toISOString().split('T')[0]
- }
- await updateInterview(id, updates)
- }
-
- const handleCloseModal = () => {
- setIsModalOpen(false)
- setEditingInterview(null)
- }
-
- return (
-
- {/* Header */}
-
-
- {/* Main Content */}
-
-
- {activeTab === 'interviews' ? (
-
- {error && (
-
- {error}
-
- )}
-
- {/* Stats Overview */}
-
-
-
-
Total progress
-
- {completedCount}
- / 100
-
-
- {100 - completedCount} remaining
-
-
-
-
-
-
-
-
-
-
-
-
Scheduled
-
{scheduledCount}
-
-
-
- {scheduledCount > 0
- ? `${scheduledCount} interview${scheduledCount === 1 ? '' : 's'} coming up`
- : 'No interviews scheduled'}
-
-
-
-
-
-
-
-
-
-
Segments covered
-
- {Object.keys(segmentCounts).length}
- / 6
-
-
-
-
- {6 - Object.keys(segmentCounts).length > 0
- ? `${6 - Object.keys(segmentCounts).length} segment${6 - Object.keys(segmentCounts).length === 1 ? '' : 's'} to explore`
- : 'All segments covered!'}
-
-
-
-
- {/* Main Grid */}
-
-
- {/* Interview Log */}
-
-
-
Interview log
- {interviews.length} total
-
-
-
-
- ) : activeTab === 'assignments' ? (
-
-
-
- ) : activeTab === 'materials' ? (
-
-
-
- ) : activeTab === 'slides' ? (
-
-
-
- ) : (
-
-
-
- )}
-
-
- {/* Footer */}
-
-
-
- {/* Modal */}
-
-
- )
+ return ;
}
-export default App
+export default App;
diff --git a/app/src/assets/react.svg b/app/src/assets/react.svg
deleted file mode 100644
index 6c87de9..0000000
--- a/app/src/assets/react.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/app/src/components/AddInterviewModal.tsx b/app/src/components/AddInterviewModal.tsx
deleted file mode 100644
index 051b12a..0000000
--- a/app/src/components/AddInterviewModal.tsx
+++ /dev/null
@@ -1,380 +0,0 @@
-import { useState, useEffect } from 'react'
-import { motion, AnimatePresence } from 'framer-motion'
-import { X } from 'lucide-react'
-import type { Interview, Segment, PoseHypothesis } from '../types/database'
-import { SEGMENTS, POSE_HYPOTHESES } from '../types/database'
-import { InterviewGuidance } from './InterviewGuidance'
-
-interface AddInterviewModalProps {
- isOpen: boolean
- onClose: () => void
- onSave: (interview: Omit) => void
- editInterview?: Interview | null
-}
-
-const initialFormData = {
- name: '',
- organization: '',
- role: '',
- segment: 'user' as Segment,
- scheduled_date: '',
- completed_date: '',
- status: 'scheduled' as Interview['status'],
- notes: '',
- key_insights: [] as string[],
- // POSE interview log fields
- hypotheses_tested: [] as PoseHypothesis[],
- experiments: '',
- results: '',
- actions: '',
-}
-
-export function AddInterviewModal({ isOpen, onClose, onSave, editInterview }: AddInterviewModalProps) {
- const [formData, setFormData] = useState(initialFormData)
- const [insightInput, setInsightInput] = useState('')
-
- useEffect(() => {
- if (editInterview) {
- setFormData({
- name: editInterview.name,
- organization: editInterview.organization,
- role: editInterview.role || '',
- segment: editInterview.segment,
- scheduled_date: editInterview.scheduled_date || '',
- completed_date: editInterview.completed_date || '',
- status: editInterview.status,
- notes: editInterview.notes || '',
- key_insights: editInterview.key_insights || [],
- hypotheses_tested: editInterview.hypotheses_tested || [],
- experiments: editInterview.experiments || '',
- results: editInterview.results || '',
- actions: editInterview.actions || '',
- })
- } else {
- setFormData(initialFormData)
- }
- }, [editInterview, isOpen])
-
- const handleSubmit = (e: React.FormEvent) => {
- e.preventDefault()
- onSave(formData)
- onClose()
- setFormData(initialFormData)
- }
-
- const addInsight = () => {
- if (insightInput.trim()) {
- setFormData(prev => ({
- ...prev,
- key_insights: [...prev.key_insights, insightInput.trim()]
- }))
- setInsightInput('')
- }
- }
-
- const removeInsight = (index: number) => {
- setFormData(prev => ({
- ...prev,
- key_insights: prev.key_insights.filter((_, i) => i !== index)
- }))
- }
-
- return (
-
- {isOpen && (
- <>
-
-
-
-
- {editInterview ? 'Edit interview' : 'Add interview'}
-
-
-
-
-
-
-
-
- >
- )}
-
- )
-}
diff --git a/app/src/components/Assignments.tsx b/app/src/components/Assignments.tsx
deleted file mode 100644
index fad68ec..0000000
--- a/app/src/components/Assignments.tsx
+++ /dev/null
@@ -1,524 +0,0 @@
-import { useState, useMemo } from 'react'
-import { motion, AnimatePresence } from 'framer-motion'
-import {
- CheckCircle2,
- Circle,
- Clock,
- AlertTriangle,
- Calendar,
- BookOpen,
- Presentation,
- Target,
- ChevronDown,
- ChevronRight,
- Filter,
- ExternalLink,
-} from 'lucide-react'
-import { useAssignments } from '../hooks/useAssignments'
-import type { Assignment, AssignmentCategory } from '../types/database'
-
-// Category metadata
-const CATEGORY_INFO: Record<
- AssignmentCategory,
- { label: string; color: string; bgColor: string; Icon: React.ElementType }
-> = {
- prep: {
- label: 'Preparation',
- color: 'text-blue-600',
- bgColor: 'bg-blue-100',
- Icon: BookOpen,
- },
- presentation: {
- label: 'Presentations',
- color: 'text-purple-600',
- bgColor: 'bg-purple-100',
- Icon: Presentation,
- },
- interview_milestone: {
- label: 'Interview milestones',
- color: 'text-teal-600',
- bgColor: 'bg-teal-100',
- Icon: Target,
- },
-}
-
-type ViewMode = 'chronological' | 'category'
-type FilterMode = 'all' | 'pending' | 'completed'
-
-interface AssignmentCardProps {
- assignment: Assignment
- dueStatus: 'overdue' | 'due_soon' | 'upcoming'
- onToggle: () => void
-}
-
-function AssignmentCard({ assignment, dueStatus, onToggle }: AssignmentCardProps) {
- const categoryInfo = CATEGORY_INFO[assignment.category]
- const isCompleted = assignment.status === 'completed'
-
- const formatDate = (dateString: string) => {
- const date = new Date(dateString)
- return date.toLocaleDateString('en-US', {
- weekday: 'short',
- month: 'short',
- day: 'numeric',
- })
- }
-
- const getDueLabel = () => {
- if (isCompleted) return null
- switch (dueStatus) {
- case 'overdue':
- return (
-
-
- Overdue
-
- )
- case 'due_soon':
- return (
-
-
- Due soon
-
- )
- default:
- return null
- }
- }
-
- return (
-
-
- {/* Checkbox */}
-
- {isCompleted ? (
-
- ) : (
-
- )}
-
-
- {/* Content */}
-
-
-
-
- {assignment.description}
-
-
-
- {/* Category badge */}
-
-
- {categoryInfo.label}
-
-
- {/* Due date */}
-
-
- {formatDate(assignment.due_date)}
-
-
-
-
-
- )
-}
-
-interface CategoryGroupProps {
- category: AssignmentCategory
- assignments: Assignment[]
- getDueStatus: (date: string) => 'overdue' | 'due_soon' | 'upcoming'
- onToggle: (id: string) => void
- isExpanded: boolean
- onExpandToggle: () => void
-}
-
-function CategoryGroup({
- category,
- assignments,
- getDueStatus,
- onToggle,
- isExpanded,
- onExpandToggle,
-}: CategoryGroupProps) {
- const categoryInfo = CATEGORY_INFO[category]
- const completedCount = assignments.filter((a) => a.status === 'completed').length
-
- return (
-
-
-
- {isExpanded ? (
-
- ) : (
-
- )}
-
-
-
-
-
{categoryInfo.label}
-
- {completedCount} of {assignments.length} completed
-
-
-
-
-
-
- {Math.round((completedCount / assignments.length) * 100)}%
-
-
-
-
-
- {isExpanded && (
-
- {assignments.map((assignment) => (
- onToggle(assignment.id)}
- />
- ))}
-
- )}
-
-
- )
-}
-
-export function Assignments() {
- const {
- assignments,
- loading,
- toggleComplete,
- completedCount,
- totalCount,
- getDueStatus,
- } = useAssignments()
-
- const [viewMode, setViewMode] = useState('chronological')
- const [filterMode, setFilterMode] = useState('all')
- const [expandedCategories, setExpandedCategories] = useState>(
- new Set(['prep', 'presentation', 'interview_milestone'])
- )
-
- // Filter assignments
- const filteredAssignments = useMemo(() => {
- return assignments.filter((a) => {
- if (filterMode === 'pending') return a.status !== 'completed'
- if (filterMode === 'completed') return a.status === 'completed'
- return true
- })
- }, [assignments, filterMode])
-
- // Group by category
- const assignmentsByCategory = useMemo(() => {
- const grouped: Record = {
- prep: [],
- presentation: [],
- interview_milestone: [],
- }
- filteredAssignments.forEach((a) => {
- grouped[a.category].push(a)
- })
- return grouped
- }, [filteredAssignments])
-
- // Sort chronologically
- const chronologicalAssignments = useMemo(() => {
- return [...filteredAssignments].sort(
- (a, b) => new Date(a.due_date).getTime() - new Date(b.due_date).getTime()
- )
- }, [filteredAssignments])
-
- const toggleCategoryExpand = (category: AssignmentCategory) => {
- setExpandedCategories((prev) => {
- const next = new Set(prev)
- if (next.has(category)) {
- next.delete(category)
- } else {
- next.add(category)
- }
- return next
- })
- }
-
- // Count overdue and due soon
- const urgentCount = useMemo(() => {
- return assignments.filter((a) => {
- if (a.status === 'completed') return false
- const status = getDueStatus(a.due_date)
- return status === 'overdue' || status === 'due_soon'
- }).length
- }, [assignments, getDueStatus])
-
- if (loading) {
- return (
-
- )
- }
-
- return (
-
- {/* Summary stats */}
-
-
-
-
-
Progress
-
- {completedCount}
- / {totalCount}
-
-
-
-
-
-
-
-
- {Math.round((completedCount / totalCount) * 100)}%
-
-
-
-
-
-
-
-
-
-
-
-
Needs attention
-
{urgentCount}
-
-
-
- {urgentCount > 0
- ? `${urgentCount} assignment${urgentCount === 1 ? '' : 's'} overdue or due soon`
- : 'All caught up!'}
-
-
-
-
-
-
-
-
-
-
Completed
-
{completedCount}
-
-
-
- {totalCount - completedCount} remaining
-
-
-
-
- {/* Filters and view toggle */}
-
-
-
-
- {[
- { value: 'all', label: 'All' },
- { value: 'pending', label: 'Pending' },
- { value: 'completed', label: 'Completed' },
- ].map(({ value, label }) => (
- setFilterMode(value as FilterMode)}
- className={`px-3 py-1.5 rounded-full text-sm font-medium transition-colors ${
- filterMode === value
- ? 'bg-teal-600 text-white'
- : 'bg-white text-gray-600 border border-gray-200 hover:border-teal-300'
- }`}
- >
- {label}
-
- ))}
-
-
-
-
- setViewMode('chronological')}
- className={`px-3 py-1.5 rounded-md text-sm font-medium transition-colors ${
- viewMode === 'chronological'
- ? 'bg-white text-gray-900 shadow-sm'
- : 'text-gray-500 hover:text-gray-700'
- }`}
- >
- Timeline
-
- setViewMode('category')}
- className={`px-3 py-1.5 rounded-md text-sm font-medium transition-colors ${
- viewMode === 'category'
- ? 'bg-white text-gray-900 shadow-sm'
- : 'text-gray-500 hover:text-gray-700'
- }`}
- >
- By category
-
-
-
-
- {/* Assignment list */}
-
- {viewMode === 'category' ? (
-
- {(['prep', 'presentation', 'interview_milestone'] as AssignmentCategory[]).map(
- (category) =>
- assignmentsByCategory[category].length > 0 && (
- toggleCategoryExpand(category)}
- />
- )
- )}
-
- ) : (
-
- {chronologicalAssignments.map((assignment) => (
- toggleComplete(assignment.id)}
- />
- ))}
-
- )}
-
-
- {filteredAssignments.length === 0 && (
-
-
-
- {filterMode === 'pending'
- ? 'All assignments completed!'
- : filterMode === 'completed'
- ? 'No completed assignments yet.'
- : 'No assignments found.'}
-
-
- )}
-
- )
-}
diff --git a/app/src/components/InterviewGuidance.tsx b/app/src/components/InterviewGuidance.tsx
deleted file mode 100644
index 8f6a934..0000000
--- a/app/src/components/InterviewGuidance.tsx
+++ /dev/null
@@ -1,426 +0,0 @@
-import { motion, AnimatePresence } from 'framer-motion'
-import {
- Lightbulb,
- MessageSquare,
- Target,
- ChevronDown,
- ChevronUp,
- Users,
- Heart,
- Code,
- Swords,
- Building,
- Handshake
-} from 'lucide-react'
-import { useState } from 'react'
-import type { Segment } from '../types/database'
-
-interface InterviewGuidanceProps {
- segment: Segment
- collapsed?: boolean
-}
-
-interface GuidanceContent {
- icon: React.ElementType
- questions: string[]
- tips: string[]
- topics: string[]
- ecosystem: string[]
-}
-
-// Segment-specific guidance content for PolicyEngine POSE interviews
-const GUIDANCE_BY_SEGMENT: Record = {
- user: {
- icon: Users,
- questions: [
- "How did you first discover PolicyEngine, and what prompted you to try it?",
- "Walk me through a recent policy analysis you conducted with PolicyEngine.",
- "What's the most valuable insight PolicyEngine has helped you uncover?",
- "What tasks do you wish PolicyEngine could do that it can't today?",
- "How does PolicyEngine compare to your previous approach to policy analysis?",
- "Who else at your organization uses PolicyEngine, and how do you share findings?",
- "What would make you recommend PolicyEngine to a colleague?",
- "If PolicyEngine disappeared tomorrow, what would you use instead?",
- ],
- tips: [
- "Focus on specific use cases and real projects, not hypotheticals",
- "Ask for screen sharing to see their actual workflow",
- "Probe on pain points - what's frustrating, confusing, or missing?",
- "Understand their policy expertise level vs. technical comfort",
- "Ask about the decision-making context: who receives their analysis?",
- ],
- topics: [
- "Current workflow and frequency of use",
- "Primary policy areas of interest (tax, benefits, etc.)",
- "Technical skill level and data literacy",
- "Organizational context and stakeholder needs",
- "Alternative tools considered or previously used",
- ],
- ecosystem: [
- "What datasets do you use most for tax/benefit analysis? (CPS, ACS, SCF, admin data?)",
- "What programming languages do you code in? What's standard at your org?",
- "Where do you get policy parameters? (IRS docs, CCH, manually?)",
- "How do you validate your calculations against official sources?",
- "What tools do you use alongside your analysis? (Excel, Jupyter, etc.)",
- "How do you share results with colleagues and stakeholders?",
- "What conferences or journals matter in your field?",
- "What research would you do if the tools were better?",
- ],
- },
- supporter: {
- icon: Heart,
- questions: [
- "What originally drew you to support PolicyEngine's mission?",
- "How do you measure the impact of tools like PolicyEngine in the policy space?",
- "What would make you increase your investment or advocacy for PolicyEngine?",
- "Who else in your network should know about PolicyEngine?",
- "What concerns, if any, do you have about PolicyEngine's sustainability?",
- "How does PolicyEngine fit into the broader ecosystem of policy analysis tools you fund/support?",
- "What would success look like for PolicyEngine in 3-5 years?",
- "What risks do you see to PolicyEngine achieving its potential impact?",
- ],
- tips: [
- "Understand their theory of change and how PE fits in",
- "Learn about their other investments in the policy tech space",
- "Explore what metrics matter most to them",
- "Ask about their network and potential introductions",
- "Be transparent about sustainability challenges and plans",
- ],
- topics: [
- "Funding priorities and decision criteria",
- "Impact measurement frameworks",
- "Policy reform priorities they care about",
- "Other organizations in their portfolio",
- "Long-term vision for evidence-based policy",
- ],
- ecosystem: [
- "What other policy tech organizations do you fund or follow?",
- "How do grantees typically report impact to you?",
- "What data or evidence would help you make funding decisions?",
- "How do you track the policy analysis ecosystem landscape?",
- "What tools do your other grantees use for similar work?",
- ],
- },
- contributor: {
- icon: Code,
- questions: [
- "What motivated you to contribute to PolicyEngine?",
- "What's been the most rewarding part of contributing? The most frustrating?",
- "How did you learn the codebase? What documentation helped (or was missing)?",
- "What features or improvements would you like to work on next?",
- "How does contributing to PolicyEngine fit into your broader career goals?",
- "What would help you contribute more frequently or effectively?",
- "How do you explain PolicyEngine to other developers?",
- "What's your experience with the community and code review process?",
- ],
- tips: [
- "Distinguish between code contributors vs. data/parameter contributors",
- "Understand their technical background and interests",
- "Explore both intrinsic motivation and practical benefits",
- "Ask about onboarding experience and documentation gaps",
- "Discuss what recognition or support would be meaningful",
- ],
- topics: [
- "Technical skills and areas of expertise",
- "Time availability and contribution patterns",
- "Experience with open source communities",
- "Interest in governance and decision-making",
- "Career stage and professional context",
- ],
- ecosystem: [
- "What other open-source projects do you contribute to?",
- "What development tools and IDEs do you prefer?",
- "How do you stay current with policy/economics knowledge?",
- "What testing and validation approaches do you use?",
- "Where do you look for policy parameter data when contributing?",
- "What documentation formats work best for you?",
- "How do you prefer to communicate with other contributors?",
- ],
- },
- competitor: {
- icon: Swords,
- questions: [
- "How do you see PolicyEngine positioning in the policy analysis market?",
- "What do you think PolicyEngine does particularly well? Where does it fall short?",
- "How do users typically choose between your tool and PolicyEngine?",
- "What trends do you see in policy analysis and microsimulation?",
- "Where do you see potential for collaboration rather than competition?",
- "What user needs do you think are underserved in this space?",
- "How do you approach sustainability as an organization?",
- "What would the ideal policy analysis ecosystem look like?",
- ],
- tips: [
- "Frame as ecosystem mapping, not competitive intelligence",
- "Be genuinely curious about their approach and innovations",
- "Explore potential partnerships and complementary strengths",
- "Understand their funding model and sustainability approach",
- "Ask what they've learned from their users",
- ],
- topics: [
- "Market positioning and target users",
- "Technical approaches and differentiators",
- "Business model and sustainability strategy",
- "Potential collaboration opportunities",
- "Shared challenges in the policy tech space",
- ],
- ecosystem: [
- "What data sources do you use for microsimulation?",
- "What programming languages/frameworks does your tool use?",
- "How do you handle parameter updates when policy changes?",
- "What validation approaches do you use?",
- "How do your users typically access your tool? (API, web, desktop?)",
- "What's your tech stack for the frontend/backend?",
- ],
- },
- distributor: {
- icon: Building,
- questions: [
- "How did you discover PolicyEngine, and what led you to embed it?",
- "Walk me through your integration: what worked well and what was challenging?",
- "How do your users interact with PolicyEngine features? What feedback do you hear?",
- "What PolicyEngine improvements would most benefit your users?",
- "How does PolicyEngine fit into your broader product strategy?",
- "What would make you expand or deepen your PolicyEngine integration?",
- "How do you handle updates and changes to PolicyEngine?",
- "What concerns do you have about depending on PolicyEngine?",
- ],
- tips: [
- "Understand their business model and how PE creates value for them",
- "Explore technical integration challenges and API needs",
- "Learn about their end users and use cases",
- "Discuss what stability and support they need",
- "Ask about white-labeling, customization, or premium needs",
- ],
- topics: [
- "Integration architecture and technical requirements",
- "End user profiles and use cases",
- "Business model and value proposition",
- "Support and SLA expectations",
- "Roadmap alignment and feature priorities",
- ],
- ecosystem: [
- "What's your tech stack? (frontend, backend, hosting)",
- "How do you call PolicyEngine? (API, Python package, iframe?)",
- "What data do you pass to PolicyEngine? Where does it come from?",
- "How do you handle PolicyEngine version updates?",
- "What monitoring/logging do you use for the integration?",
- "What other third-party services do you integrate with?",
- ],
- },
- partner: {
- icon: Handshake,
- questions: [
- "How did the partnership with PolicyEngine come about?",
- "What value does the integration create for your users?",
- "What's been most successful about the partnership? What could improve?",
- "How do your users discover and use the PolicyEngine integration?",
- "What features or data would make the integration more valuable?",
- "How do you see the partnership evolving over time?",
- "What other integrations or partnerships inform how you think about this one?",
- "What would a deeper partnership with PolicyEngine look like?",
- ],
- tips: [
- "Map the data flows and touchpoints between systems",
- "Understand mutual benefits and value creation",
- "Explore both technical and business relationship aspects",
- "Ask about their other partnerships for comparison",
- "Discuss governance and decision-making on joint features",
- ],
- topics: [
- "Integration points and data exchange",
- "Mutual user base and use cases",
- "Joint go-to-market opportunities",
- "Technical roadmap alignment",
- "Partnership governance and communication",
- ],
- ecosystem: [
- "What data do you provide to PolicyEngine? What do you receive?",
- "What format/protocol do you use for data exchange? (API, files, etc.)",
- "How do you coordinate releases and breaking changes?",
- "What's your organization's tech stack?",
- "How do you handle authentication/authorization for the integration?",
- "What other partnerships do you have that work similarly?",
- ],
- },
-}
-
-const SEGMENT_LABELS: Record = {
- user: 'Users',
- supporter: 'Supporters',
- contributor: 'Contributors',
- competitor: 'Competitors',
- distributor: 'Distributors',
- partner: 'Partners',
-}
-
-export function InterviewGuidance({ segment, collapsed: initialCollapsed = false }: InterviewGuidanceProps) {
- const [isExpanded, setIsExpanded] = useState(!initialCollapsed)
- const [activeTab, setActiveTab] = useState<'questions' | 'tips' | 'topics'>('questions')
-
- const guidance = GUIDANCE_BY_SEGMENT[segment]
- const Icon = guidance.icon
-
- return (
-
- {/* Header */}
-
setIsExpanded(!isExpanded)}
- className="w-full flex items-center justify-between px-4 py-3 hover:bg-teal-50/50 transition-colors"
- >
-
-
-
-
-
-
Interview guidance
-
- {SEGMENT_LABELS[segment]} interview prep
-
-
-
- {isExpanded ? (
-
- ) : (
-
- )}
-
-
-
- {isExpanded && (
-
- {/* Segment indicator */}
-
-
-
- {SEGMENT_LABELS[segment]}
- segment
-
-
-
- {/* Tabs */}
-
- setActiveTab('questions')}
- className={`flex items-center gap-1.5 px-3 py-2 text-sm font-medium rounded-t-lg transition-colors ${
- activeTab === 'questions'
- ? 'bg-white text-teal-700 border border-teal-200 border-b-white -mb-px'
- : 'text-gray-500 hover:text-gray-700'
- }`}
- >
-
- Questions
-
- setActiveTab('tips')}
- className={`flex items-center gap-1.5 px-3 py-2 text-sm font-medium rounded-t-lg transition-colors ${
- activeTab === 'tips'
- ? 'bg-white text-teal-700 border border-teal-200 border-b-white -mb-px'
- : 'text-gray-500 hover:text-gray-700'
- }`}
- >
-
- Tips
-
- setActiveTab('topics')}
- className={`flex items-center gap-1.5 px-3 py-2 text-sm font-medium rounded-t-lg transition-colors ${
- activeTab === 'topics'
- ? 'bg-white text-teal-700 border border-teal-200 border-b-white -mb-px'
- : 'text-gray-500 hover:text-gray-700'
- }`}
- >
-
- Topics
-
-
-
- {/* Content */}
-
-
- {activeTab === 'questions' && (
-
-
- Discovery questions for {SEGMENT_LABELS[segment].toLowerCase()}:
-
-
- {guidance.questions.map((question, index) => (
-
-
- {index + 1}
-
- {question}
-
- ))}
-
-
- )}
-
- {activeTab === 'tips' && (
-
-
- Interview approach for {SEGMENT_LABELS[segment].toLowerCase()}:
-
-
- {guidance.tips.map((tip, index) => (
-
-
- {tip}
-
- ))}
-
-
- )}
-
- {activeTab === 'topics' && (
-
-
- Key areas to explore with {SEGMENT_LABELS[segment].toLowerCase()}:
-
-
- {guidance.topics.map((topic, index) => (
-
-
- {topic}
-
- ))}
-
-
- )}
-
-
-
- {/* Footer */}
-
-
- Customize questions based on the specific context and relationship
-
-
-
- )}
-
-
- )
-}
diff --git a/app/src/components/InterviewTable.tsx b/app/src/components/InterviewTable.tsx
deleted file mode 100644
index 63b72ac..0000000
--- a/app/src/components/InterviewTable.tsx
+++ /dev/null
@@ -1,293 +0,0 @@
-import { useState } from 'react'
-import { format } from 'date-fns'
-import { motion, AnimatePresence } from 'framer-motion'
-import {
- ChevronDown,
- ChevronUp,
- MoreHorizontal,
- Pencil,
- Trash2,
- CheckCircle,
- Clock,
- XCircle,
- AlertCircle
-} from 'lucide-react'
-import type { Interview, Segment } from '../types/database'
-import { SEGMENTS } from '../types/database'
-
-interface InterviewTableProps {
- interviews: Interview[]
- onEdit: (interview: Interview) => void
- onDelete: (id: string) => void
- onStatusChange: (id: string, status: Interview['status']) => void
-}
-
-const statusConfig = {
- completed: { icon: CheckCircle, color: 'text-green-600', bg: 'bg-green-50', label: 'Completed' },
- scheduled: { icon: Clock, color: 'text-blue-600', bg: 'bg-blue-50', label: 'Scheduled' },
- cancelled: { icon: XCircle, color: 'text-gray-500', bg: 'bg-gray-50', label: 'Cancelled' },
- no_show: { icon: AlertCircle, color: 'text-red-500', bg: 'bg-red-50', label: 'No show' },
-}
-
-export function InterviewTable({ interviews, onEdit, onDelete, onStatusChange }: InterviewTableProps) {
- const [sortField, setSortField] = useState<'name' | 'organization' | 'segment' | 'scheduled_date'>('scheduled_date')
- const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc')
- const [expandedId, setExpandedId] = useState(null)
- const [menuOpenId, setMenuOpenId] = useState(null)
-
- const handleSort = (field: typeof sortField) => {
- if (field === sortField) {
- setSortDirection(d => d === 'asc' ? 'desc' : 'asc')
- } else {
- setSortField(field)
- setSortDirection('asc')
- }
- }
-
- const sortedInterviews = [...interviews].sort((a, b) => {
- let aVal = a[sortField] || ''
- let bVal = b[sortField] || ''
- if (sortField === 'scheduled_date') {
- aVal = a.scheduled_date || a.completed_date || ''
- bVal = b.scheduled_date || b.completed_date || ''
- }
- const cmp = aVal < bVal ? -1 : aVal > bVal ? 1 : 0
- return sortDirection === 'asc' ? cmp : -cmp
- })
-
- const SortIcon = ({ field }: { field: typeof sortField }) => {
- if (field !== sortField) return
- return sortDirection === 'asc'
- ?
- :
- }
-
- const getSegmentColor = (segment: Segment) => {
- return SEGMENTS.find(s => s.value === segment)?.color || '#6b7280'
- }
-
- if (interviews.length === 0) {
- return (
-
-
-
-
-
No interviews yet
-
Add your first interview to start tracking progress
-
- )
- }
-
- return (
-
-
-
-
-
-
- handleSort('name')}
- className="flex items-center gap-1 text-xs font-semibold text-gray-600 uppercase tracking-wide hover:text-gray-900"
- >
- Name
-
-
-
- handleSort('organization')}
- className="flex items-center gap-1 text-xs font-semibold text-gray-600 uppercase tracking-wide hover:text-gray-900"
- >
- Organization
-
-
-
- handleSort('segment')}
- className="flex items-center gap-1 text-xs font-semibold text-gray-600 uppercase tracking-wide hover:text-gray-900"
- >
- Segment
-
-
-
- handleSort('scheduled_date')}
- className="flex items-center gap-1 text-xs font-semibold text-gray-600 uppercase tracking-wide hover:text-gray-900"
- >
- Date
-
-
-
- Status
-
-
-
-
-
-
- {sortedInterviews.map((interview) => {
- const status = statusConfig[interview.status]
- const StatusIcon = status.icon
- const isExpanded = expandedId === interview.id
- const isMenuOpen = menuOpenId === interview.id
-
- return (
- <>
- setExpandedId(isExpanded ? null : interview.id)}
- className={`border-b border-gray-100 hover:bg-gray-50 transition-colors cursor-pointer ${
- isExpanded ? 'bg-gray-50' : ''
- }`}
- >
-
-
- {interview.name}
- {interview.role && (
- {interview.role}
- )}
-
-
- {interview.organization}
-
-
-
- {SEGMENTS.find(s => s.value === interview.segment)?.label}
-
-
-
- {interview.completed_date
- ? format(new Date(interview.completed_date), 'MMM d, yyyy')
- : interview.scheduled_date
- ? format(new Date(interview.scheduled_date), 'MMM d, yyyy')
- : '—'}
-
-
-
-
- {status.label}
-
-
-
-
-
{
- e.stopPropagation()
- setMenuOpenId(isMenuOpen ? null : interview.id)
- }}
- className="p-1 rounded hover:bg-gray-100"
- >
-
-
-
- {isMenuOpen && (
- <>
-
setMenuOpenId(null)}
- />
-
- {interview.status === 'scheduled' && (
-
{
- onStatusChange(interview.id, 'completed')
- setMenuOpenId(null)
- }}
- className="w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-50 flex items-center gap-2"
- >
-
- Mark completed
-
- )}
-
{
- onEdit(interview)
- setMenuOpenId(null)
- }}
- className="w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-50 flex items-center gap-2"
- >
-
- Edit
-
-
{
- onDelete(interview.id)
- setMenuOpenId(null)
- }}
- className="w-full px-4 py-2 text-left text-sm text-red-600 hover:bg-red-50 flex items-center gap-2"
- >
-
- Delete
-
-
- >
- )}
-
-
-
- {isExpanded && (
-
-
-
- {interview.notes && (
-
-
Notes
-
- {interview.notes}
-
-
- )}
- {interview.key_insights && interview.key_insights.length > 0 && (
-
-
Key Insights
-
- {interview.key_insights.map((insight, i) => (
- {insight}
- ))}
-
-
- )}
- {interview.referrals && interview.referrals.length > 0 && (
-
-
Referrals
-
- {interview.referrals.map((referral, i) => (
- {referral}
- ))}
-
-
- )}
- {!interview.notes && (!interview.key_insights || interview.key_insights.length === 0) && (!interview.referrals || interview.referrals.length === 0) && (
-
No details recorded yet.
- )}
-
-
-
- )}
- >
- )
- })}
-
-
-
-
-
- )
-}
diff --git a/app/src/components/Materials.tsx b/app/src/components/Materials.tsx
deleted file mode 100644
index 9463031..0000000
--- a/app/src/components/Materials.tsx
+++ /dev/null
@@ -1,940 +0,0 @@
-import { useState, useMemo } from 'react'
-import { motion, AnimatePresence } from 'framer-motion'
-import {
- Search,
- BookOpen,
- Video,
- FileText,
- Calendar,
- ExternalLink,
- ChevronDown,
- ChevronRight,
- Download,
- Presentation,
- Table2,
-} from 'lucide-react'
-
-// Types for materials
-interface Material {
- id: string
- title: string
- description?: string
- type: 'document' | 'video' | 'pdf' | 'slides' | 'spreadsheet' | 'guide'
- category: string
- url?: string
- localPath?: string
- duration?: string
-}
-
-interface MaterialCategory {
- id: string
- name: string
- description: string
- materials: Material[]
-}
-
-// Material data based on the NovoEd course content
-const materialCategories: MaterialCategory[] = [
- {
- id: 'overview',
- name: 'Program overview',
- description: 'Introduction and course structure',
- materials: [
- {
- id: 'program-overview',
- title: 'Program overview',
- description: 'Course structure, requirements, and deliverables',
- type: 'document',
- category: 'overview',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858780',
- },
- {
- id: 'schedule',
- title: 'Course schedule',
- description: 'Complete timeline from January to March 2026',
- type: 'document',
- category: 'overview',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858780',
- },
- {
- id: 'interview-guide',
- title: 'Interview guide',
- description: 'Best practices for ecosystem discovery interviews',
- type: 'guide',
- category: 'overview',
- },
- ],
- },
- {
- id: 'sessions',
- name: 'Session materials',
- description: 'Kickoff, weekly sessions, and finale content',
- materials: [
- {
- id: 'pre-program',
- title: 'Pre-program orientation',
- description: 'January 6, 2026 - Program introduction and expectations',
- type: 'document',
- category: 'sessions',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858789',
- },
- {
- id: 'kickoff-day-1',
- title: 'Kickoff Day 1',
- description: 'January 20 - Introductions and ecosystem discovery lecture',
- type: 'document',
- category: 'sessions',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858791',
- },
- {
- id: 'kickoff-day-2',
- title: 'Kickoff Day 2',
- description: 'January 21 - Panel discussion and ecosystem canvas',
- type: 'document',
- category: 'sessions',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858794',
- },
- {
- id: 'kickoff-day-3',
- title: 'Kickoff Day 3',
- description: 'January 22 - Value propositions and ecosystem mapping',
- type: 'document',
- category: 'sessions',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858797',
- },
- {
- id: 'kickoff-day-4',
- title: 'Kickoff Day 4',
- description: 'January 23 - Team presentations and impact metrics',
- type: 'document',
- category: 'sessions',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858800',
- },
- {
- id: 'weekly-1',
- title: 'Weekly session 1',
- description: 'January 28 - First progress presentations',
- type: 'document',
- category: 'sessions',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858803',
- },
- {
- id: 'weekly-2',
- title: 'Weekly session 2',
- description: 'February 4 - Continued ecosystem discovery',
- type: 'document',
- category: 'sessions',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858806',
- },
- {
- id: 'weekly-3',
- title: 'Weekly session 3',
- description: 'February 11 - Mid-program check-in',
- type: 'document',
- category: 'sessions',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858809',
- },
- {
- id: 'weekly-4',
- title: 'Weekly session 4',
- description: 'February 18 - Advanced ecosystem insights',
- type: 'document',
- category: 'sessions',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858812',
- },
- {
- id: 'weekly-5',
- title: 'Weekly session 5',
- description: 'February 25 - Final preparation',
- type: 'document',
- category: 'sessions',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858815',
- },
- {
- id: 'finale-1',
- title: 'Finale session 1',
- description: 'March 4 - Final presentations and discussions',
- type: 'document',
- category: 'sessions',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858818',
- },
- {
- id: 'finale-2',
- title: 'Finale session 2',
- description: 'March 5 - Closing presentations and next steps',
- type: 'document',
- category: 'sessions',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858820',
- },
- ],
- },
- {
- id: 'templates',
- name: 'Templates',
- description: 'Slide decks and spreadsheet templates',
- materials: [
- {
- id: 'team-template',
- title: 'Team slide template',
- description: 'Introduce your team and roles',
- type: 'slides',
- category: 'templates',
- url: 'https://docs.google.com/presentation/d/1mYxhA_6Ff8NRVDxER-IBLxP897-Oq5uzwL8iO05K6pQ',
- },
- {
- id: 'thesis-template',
- title: 'Thesis template',
- description: 'Document your ecosystem thesis',
- type: 'slides',
- category: 'templates',
- url: 'https://docs.google.com/presentation/d/1WIlRJgz7WC6i6zhIa0BjL9P_MyGiWyHNS3ZcQBvaH2o',
- },
- {
- id: 'assumptions-template',
- title: 'Assumptions template',
- description: 'Track and test your assumptions',
- type: 'slides',
- category: 'templates',
- url: 'https://docs.google.com/presentation/d/1yz5t5B6GxPQlvY-idIQuFsbkv9i2CvAcJyxxDIi44IE',
- },
- {
- id: 'interview-records-template',
- title: 'Interview records template',
- description: 'Document individual interviews',
- type: 'slides',
- category: 'templates',
- url: 'https://docs.google.com/presentation/d/1nKPCgZ8B0Sg1SJpvoc5DZJY6WBRiKZkzp6A1oKDOi08',
- },
- {
- id: 'interview-summary-template',
- title: 'Interview summary template',
- description: 'Summarize interview insights',
- type: 'slides',
- category: 'templates',
- url: 'https://docs.google.com/presentation/d/1SEPSsrLJNZkW_SOYC_DKYR0uZzKGiwVJoyy2LTN711k',
- },
- {
- id: 'ecosystem-template',
- title: 'Ecosystem template',
- description: 'Map your ecosystem components',
- type: 'slides',
- category: 'templates',
- url: 'https://docs.google.com/presentation/d/1_S9FLohf7JqoeBfrpB3UIgLraAckxAsy9ia0Mp8xVhM',
- },
- {
- id: 'canvas-template',
- title: 'Open source ecosystem canvas',
- description: 'Visual framework for ecosystem strategy',
- type: 'slides',
- category: 'templates',
- url: 'https://docs.google.com/presentation/d/1B5x6T1_uZiyMU4_NvEh1r6ykfSJddBgtN9dKZXoKdo8',
- },
- {
- id: 'impact-template',
- title: 'Impact template',
- description: 'Define and measure impact metrics',
- type: 'slides',
- category: 'templates',
- url: 'https://docs.google.com/presentation/d/1Gym5ZTMYpj6Sp4SiCxrTrj9mI_y4xJiHb7wzTcQeLnE',
- },
- {
- id: 'governance-template',
- title: 'Governance template',
- description: 'Document governance structure',
- type: 'slides',
- category: 'templates',
- url: 'https://docs.google.com/presentation/d/1jiHABTcFV-yh3YhiwKoyVTsaVTkNLcV8oydcKULyofY',
- },
- {
- id: 'sustainability-template',
- title: 'Sustainability template',
- description: 'Plan for long-term sustainability',
- type: 'slides',
- category: 'templates',
- url: 'https://docs.google.com/presentation/d/12ZJRQLM1ur3cuLa_R-D-ehOpdKCy-7LKngyj4yH5AyU',
- },
- {
- id: 'partnerships-template',
- title: 'Partnerships template',
- description: 'Track potential partnerships',
- type: 'slides',
- category: 'templates',
- url: 'https://docs.google.com/presentation/d/1PUcYvlRcdyaciDVl-7ij0hUtD54D3XFTS03hsvPy9Rs',
- },
- {
- id: 'interview-log-sheet',
- title: 'Interview log spreadsheet',
- description: 'Track all interviews in a spreadsheet',
- type: 'spreadsheet',
- category: 'templates',
- url: 'https://docs.google.com/spreadsheets/d/14SS1vWtQD2htIDg24idy4p6XpTmtsMnA7Pv0dE5-IRI',
- },
- {
- id: 'timeline-sheet',
- title: 'Timeline spreadsheet',
- description: 'Plan your interview schedule',
- type: 'spreadsheet',
- category: 'templates',
- url: 'https://docs.google.com/spreadsheets/d/1NTkBzpVktIxrezeuob9mR49v_DLfKaffSFHPaPjF5-U',
- },
- ],
- },
- {
- id: 'videos',
- name: 'Training videos',
- description: 'I-Corps methodology and business model canvas',
- materials: [
- {
- id: 'entrepreneurship-1',
- title: 'Entrepreneurship - Part 1',
- type: 'video',
- category: 'videos',
- duration: '12 mins',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858788',
- },
- {
- id: 'entrepreneurship-2',
- title: 'Entrepreneurship - Part 2',
- type: 'video',
- category: 'videos',
- duration: '10 mins',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858788',
- },
- {
- id: 'canvas-right',
- title: 'The right side of the canvas',
- type: 'video',
- category: 'videos',
- duration: '4 mins',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858788',
- },
- {
- id: 'canvas-left',
- title: 'The left side of the canvas',
- type: 'video',
- category: 'videos',
- duration: '2 mins',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858788',
- },
- {
- id: 'market-sizing',
- title: 'Market sizing',
- type: 'video',
- category: 'videos',
- duration: '6 mins',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858788',
- },
- {
- id: 'customer-discovery-intro',
- title: 'Customer discovery - Introduction',
- type: 'video',
- category: 'videos',
- duration: '11 mins',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858788',
- },
- {
- id: 'customer-discovery-strategies',
- title: 'Customer discovery - Interview strategies',
- type: 'video',
- category: 'videos',
- duration: '10 mins',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858788',
- },
- {
- id: 'customer-discovery-tactics',
- title: 'Customer discovery - Interview tactics',
- type: 'video',
- category: 'videos',
- duration: '10 mins',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858788',
- },
- {
- id: 'customer-discovery-outreach',
- title: 'Customer discovery - Outreach',
- type: 'video',
- category: 'videos',
- duration: '7 mins',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858788',
- },
- {
- id: 'customer-discovery-analysis',
- title: 'Customer discovery - Analysis and evaluation',
- type: 'video',
- category: 'videos',
- duration: '10 mins',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858788',
- },
- {
- id: 'value-props-needs',
- title: 'Value propositions - Customer needs and jobs',
- type: 'video',
- category: 'videos',
- duration: '9 mins',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858788',
- },
- {
- id: 'value-props-context',
- title: 'Value propositions - Context drives criteria',
- type: 'video',
- category: 'videos',
- duration: '3 mins',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858788',
- },
- {
- id: 'value-props-testing',
- title: 'Value propositions - Testing propositions',
- type: 'video',
- category: 'videos',
- duration: '13 mins',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858788',
- },
- {
- id: 'customer-segments-1',
- title: 'Customer segments - Part 1',
- type: 'video',
- category: 'videos',
- duration: '14 mins',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858788',
- },
- {
- id: 'customer-segments-2',
- title: 'Customer segments - Part 2',
- type: 'video',
- category: 'videos',
- duration: '8 mins',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858788',
- },
- {
- id: 'customer-segments-3',
- title: 'Customer segments - Part 3',
- type: 'video',
- category: 'videos',
- duration: '8 mins',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858788',
- },
- {
- id: 'channels-intro',
- title: 'Channels - Introduction',
- type: 'video',
- category: 'videos',
- duration: '10 mins',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858788',
- },
- {
- id: 'channels-economics',
- title: 'Channels - Channel economics',
- type: 'video',
- category: 'videos',
- duration: '8 mins',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858788',
- },
- {
- id: 'relationships-intro',
- title: 'Customer relationships - Introduction',
- type: 'video',
- category: 'videos',
- duration: '5 mins',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858788',
- },
- {
- id: 'relationships-get',
- title: 'Customer relationships - "Get" activities',
- type: 'video',
- category: 'videos',
- duration: '12 mins',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858788',
- },
- {
- id: 'relationships-keep-grow',
- title: 'Customer relationships - "Keep" and "Grow" activities',
- type: 'video',
- category: 'videos',
- duration: '5 mins',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858788',
- },
- {
- id: 'relationships-economics',
- title: 'Customer relationships - Economics CAC and LTV',
- type: 'video',
- category: 'videos',
- duration: '7 mins',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858788',
- },
- {
- id: 'revenue-pricing',
- title: 'Revenue streams - Pricing models',
- type: 'video',
- category: 'videos',
- duration: '12 mins',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858788',
- },
- {
- id: 'revenue-models',
- title: 'Revenue streams - Revenue models',
- type: 'video',
- category: 'videos',
- duration: '6 mins',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858788',
- },
- {
- id: 'partners-intro',
- title: 'Partners - Introduction',
- type: 'video',
- category: 'videos',
- duration: '7 mins',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858788',
- },
- {
- id: 'partners-types',
- title: 'Partners - Partnership types',
- type: 'video',
- category: 'videos',
- duration: '10 mins',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858788',
- },
- {
- id: 'partners-risks',
- title: 'Partners - Partnership risks',
- type: 'video',
- category: 'videos',
- duration: '8 mins',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858788',
- },
- {
- id: 'key-activities',
- title: 'Key activities',
- type: 'video',
- category: 'videos',
- duration: '15 mins',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858788',
- },
- {
- id: 'cost-structure',
- title: 'Cost structure',
- type: 'video',
- category: 'videos',
- duration: '8 mins',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858788',
- },
- {
- id: 'key-resources',
- title: 'Key resources',
- type: 'video',
- category: 'videos',
- duration: '10 mins',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858788',
- },
- {
- id: 'discovery-tools',
- title: 'Discovery tools',
- type: 'video',
- category: 'videos',
- duration: '7 mins',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858788',
- },
- {
- id: 'the-pivot',
- title: 'The pivot',
- type: 'video',
- category: 'videos',
- duration: '3 mins',
- url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858788',
- },
- ],
- },
- {
- id: 'readings',
- name: 'Readings',
- description: 'Required books and supplementary resources',
- materials: [
- {
- id: 'book-talking-to-humans',
- title: 'Talking to Humans',
- description: 'Giff Constable and Frank Rimalovski - Required reading',
- type: 'document',
- category: 'readings',
- },
- {
- id: 'book-value-prop',
- title: 'Value Proposition Design',
- description: 'Osterwalder et al. - Required reading',
- type: 'document',
- category: 'readings',
- },
- {
- id: 'book-belonging',
- title: 'The Business of Belonging',
- description: 'David Spinks - Required reading',
- type: 'document',
- category: 'readings',
- },
- {
- id: 'pdf-harvard-governance',
- title: 'OSS Governance (Harvard)',
- description: 'Organization & structure of open source development initiatives',
- type: 'pdf',
- category: 'readings',
- localPath: '/materials/readings/harvard-oss-governance.pdf',
- },
- {
- id: 'pdf-mozilla-archetypes-v1',
- title: 'Open Source Archetypes v1',
- description: 'Mozilla & Open Tech Strategies report',
- type: 'pdf',
- category: 'readings',
- localPath: '/materials/readings/mozilla-oss-archetypes-v1.pdf',
- },
- {
- id: 'pdf-mozilla-archetypes-v2',
- title: 'Open Source Archetypes v2',
- description: 'Updated Mozilla archetypes report',
- type: 'pdf',
- category: 'readings',
- localPath: '/materials/readings/mozilla-oss-archetypes-v2.pdf',
- },
- {
- id: 'link-transaction-cost',
- title: 'Transaction cost perspective on open source',
- description: 'Baldwin & von Hippel (HBS)',
- type: 'document',
- category: 'readings',
- url: 'https://www.hbs.edu/faculty/Pages/item.aspx?num=36972',
- },
- {
- id: 'link-open-innovation',
- title: 'Open innovation, open data and new business models',
- description: 'ResearchGate publication',
- type: 'document',
- category: 'readings',
- url: 'https://www.researchgate.net/publication/280554903_Open_Innovation_Open_Data_and_new_Business_Models',
- },
- {
- id: 'link-scikit-governance',
- title: 'Scikit-image governance documentation',
- description: 'Example of project governance',
- type: 'document',
- category: 'readings',
- url: 'https://scikit-image.org/docs/stable/skips/1-governance.html',
- },
- {
- id: 'link-pysal-governance',
- title: 'PySAL governance',
- description: 'GitHub governance repository',
- type: 'document',
- category: 'readings',
- url: 'https://github.com/pysal/governance',
- },
- {
- id: 'link-community-rule',
- title: 'CommunityRule',
- description: 'Governance templates from CU Boulder',
- type: 'document',
- category: 'readings',
- url: 'https://communityrule.info/templates/',
- },
- {
- id: 'link-pyopensci',
- title: 'PyOpenSci',
- description: 'Open source for scientific software',
- type: 'document',
- category: 'readings',
- url: 'https://www.pyopensci.org/',
- },
- ],
- },
- {
- id: 'guides',
- name: 'Guides and references',
- description: 'Practical guides for the program',
- materials: [
- {
- id: 'team-charter',
- title: 'Team charter guide',
- description: 'Define team agreements and working norms',
- type: 'guide',
- category: 'guides',
- },
- {
- id: 'final-presentation',
- title: 'Final presentation guide',
- description: 'Guidelines for your finale presentation',
- type: 'guide',
- category: 'guides',
- },
- ],
- },
-]
-
-// Icon mapping
-const typeIcons: Record = {
- document: FileText,
- video: Video,
- pdf: Download,
- slides: Presentation,
- spreadsheet: Table2,
- guide: BookOpen,
-}
-
-const typeColors: Record = {
- document: 'bg-blue-100 text-blue-600',
- video: 'bg-red-100 text-red-600',
- pdf: 'bg-orange-100 text-orange-600',
- slides: 'bg-purple-100 text-purple-600',
- spreadsheet: 'bg-green-100 text-green-600',
- guide: 'bg-teal-100 text-teal-600',
-}
-
-interface MaterialCardProps {
- material: Material
-}
-
-function MaterialCard({ material }: MaterialCardProps) {
- const Icon = typeIcons[material.type]
- const colorClass = typeColors[material.type]
-
- return (
-
-
-
-
-
-
-
-
- {material.title}
-
- {(material.url || material.localPath) && (
-
- )}
-
- {material.description && (
-
{material.description}
- )}
- {material.duration && (
-
-
- {material.duration}
-
- )}
-
-
-
- )
-}
-
-interface CategorySectionProps {
- category: MaterialCategory
- searchQuery: string
- expandedCategories: Set
- onToggle: (id: string) => void
-}
-
-function CategorySection({ category, searchQuery, expandedCategories, onToggle }: CategorySectionProps) {
- const isExpanded = expandedCategories.has(category.id)
-
- const filteredMaterials = useMemo(() => {
- if (!searchQuery) return category.materials
- const query = searchQuery.toLowerCase()
- return category.materials.filter(
- (m) =>
- m.title.toLowerCase().includes(query) ||
- m.description?.toLowerCase().includes(query) ||
- m.type.toLowerCase().includes(query)
- )
- }, [category.materials, searchQuery])
-
- if (filteredMaterials.length === 0) return null
-
- return (
-
- onToggle(category.id)}
- className="w-full flex items-center justify-between p-4 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors mb-3"
- >
-
- {isExpanded ? (
-
- ) : (
-
- )}
-
-
{category.name}
-
{category.description}
-
-
-
- {filteredMaterials.length} item{filteredMaterials.length !== 1 ? 's' : ''}
-
-
-
-
- {isExpanded && (
-
-
- {filteredMaterials.map((material) => (
-
- ))}
-
-
- )}
-
-
- )
-}
-
-export function Materials() {
- const [searchQuery, setSearchQuery] = useState('')
- const [expandedCategories, setExpandedCategories] = useState>(
- new Set(materialCategories.map((c) => c.id))
- )
- const [selectedType, setSelectedType] = useState('all')
-
- const toggleCategory = (id: string) => {
- setExpandedCategories((prev) => {
- const next = new Set(prev)
- if (next.has(id)) {
- next.delete(id)
- } else {
- next.add(id)
- }
- return next
- })
- }
-
- const filteredCategories = useMemo(() => {
- return materialCategories.map((category) => ({
- ...category,
- materials: category.materials.filter((m) => {
- if (selectedType !== 'all' && m.type !== selectedType) return false
- if (!searchQuery) return true
- const query = searchQuery.toLowerCase()
- return (
- m.title.toLowerCase().includes(query) ||
- m.description?.toLowerCase().includes(query)
- )
- }),
- })).filter((c) => c.materials.length > 0)
- }, [searchQuery, selectedType])
-
- const totalMaterials = materialCategories.reduce((acc, c) => acc + c.materials.length, 0)
- const filteredCount = filteredCategories.reduce((acc, c) => acc + c.materials.length, 0)
-
- const typeFilters: { value: Material['type'] | 'all'; label: string; Icon: React.ElementType }[] = [
- { value: 'all', label: 'All', Icon: BookOpen },
- { value: 'document', label: 'Documents', Icon: FileText },
- { value: 'video', label: 'Videos', Icon: Video },
- { value: 'slides', label: 'Slides', Icon: Presentation },
- { value: 'pdf', label: 'PDFs', Icon: Download },
- { value: 'spreadsheet', label: 'Spreadsheets', Icon: Table2 },
- { value: 'guide', label: 'Guides', Icon: BookOpen },
- ]
-
- return (
-
- {/* Search and filters */}
-
-
-
- setSearchQuery(e.target.value)}
- className="w-full pl-10 pr-4 py-3 bg-white border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-teal-500 focus:border-transparent"
- />
-
-
-
- {typeFilters.map(({ value, label, Icon }) => (
- setSelectedType(value)}
- className={`flex items-center gap-2 px-3 py-1.5 rounded-full text-sm font-medium transition-colors ${
- selectedType === value
- ? 'bg-teal-600 text-white'
- : 'bg-white text-gray-600 border border-gray-200 hover:border-teal-300'
- }`}
- >
-
- {label}
-
- ))}
-
-
- {searchQuery && (
-
- Showing {filteredCount} of {totalMaterials} materials
-
- )}
-
-
- {/* Categories */}
-
- {filteredCategories.map((category) => (
-
- ))}
-
- {filteredCategories.length === 0 && (
-
-
-
No materials found matching your search.
-
{
- setSearchQuery('')
- setSelectedType('all')
- }}
- className="mt-2 text-teal-600 hover:text-teal-700 text-sm font-medium"
- >
- Clear filters
-
-
- )}
-
-
- {/* Course link */}
-
-
-
-
-
-
-
NovoEd course portal
-
- Access all session recordings, slides, and live materials on NovoEd.
-
-
-
- Open NovoEd
-
-
-
-
- )
-}
diff --git a/app/src/components/MilestoneTracker.tsx b/app/src/components/MilestoneTracker.tsx
deleted file mode 100644
index 57761f7..0000000
--- a/app/src/components/MilestoneTracker.tsx
+++ /dev/null
@@ -1,117 +0,0 @@
-import { format, isPast, isToday, differenceInDays } from 'date-fns'
-import { motion } from 'framer-motion'
-import { CheckCircle2, Circle, Clock } from 'lucide-react'
-import { POSE_MILESTONES } from '../types/database'
-
-interface MilestoneTrackerProps {
- completedCount: number
-}
-
-export function MilestoneTracker({ completedCount }: MilestoneTrackerProps) {
- const today = new Date()
-
- return (
-
-
Interview milestones
-
-
- {/* Vertical line */}
-
-
-
- {POSE_MILESTONES.map((milestone, index) => {
- const deadline = new Date(milestone.deadline)
- const isComplete = completedCount >= milestone.target_count
- const isCurrent = !isComplete && (index === 0 || completedCount >= POSE_MILESTONES[index - 1].target_count)
- const isPastDue = isPast(deadline) && !isComplete && !isToday(deadline)
- const daysUntil = differenceInDays(deadline, today)
-
- return (
-
- {/* Icon */}
-
- {isComplete ? (
-
- ) : isPastDue ? (
-
- ) : (
-
- )}
-
-
- {/* Content */}
-
-
-
- {milestone.name}
-
- {isCurrent && (
-
- Current
-
- )}
-
-
-
-
- {milestone.target_count} interviews
-
- •
-
- {format(deadline, 'MMM d')}
- {!isComplete && !isPast(deadline) && (
-
- ({daysUntil === 0 ? 'today' : `${daysUntil}d`})
-
- )}
-
-
-
- {/* Progress bar for current milestone */}
- {isCurrent && (
-
-
- 0 ? POSE_MILESTONES[index - 1].target_count : 0)) /
- (milestone.target_count - (index > 0 ? POSE_MILESTONES[index - 1].target_count : 0))) * 100,
- 100
- )}%`
- }}
- transition={{ duration: 0.5, delay: 0.3 }}
- />
-
-
- {milestone.target_count - completedCount} more to reach this milestone
-
-
- )}
-
-
- )
- })}
-
-
-
- )
-}
diff --git a/app/src/components/Outreach.tsx b/app/src/components/Outreach.tsx
deleted file mode 100644
index 8a068bc..0000000
--- a/app/src/components/Outreach.tsx
+++ /dev/null
@@ -1,424 +0,0 @@
-import { useState, useCallback } from 'react'
-import { motion } from 'framer-motion'
-import { Mail, Copy, Check, User, Building, Briefcase, RefreshCw } from 'lucide-react'
-import type { Segment } from '../types/database'
-import { SEGMENTS } from '../types/database'
-
-interface OutreachFormData {
- recipientName: string
- recipientOrg: string
- recipientRole: string
- segment: Segment
- context: string // How you know them / why reaching out
- senderName: 'Max' | 'Pavel' | 'Dan'
-}
-
-const EMAIL_TEMPLATES: Record = {
- user: {
- subject: 'Quick chat about PolicyEngine? (30 min)',
- body: `Hi {{name}},
-
-I'm reaching out because {{context}}.
-
-We're part of an NSF program focused on building sustainable open-source policy tools, and we're talking with users to learn:
-- How PolicyEngine fits into your work
-- What's working well and what's frustrating
-- Features or data that would help you most
-
-Would you have 30 minutes for a conversation? We're genuinely trying to build tools that serve your needs.
-
-Schedule a time: https://cal.com/max-ghenis-policyengine/pose-ecosystem-interview
-
-Or just reply and we'll find a time that works.
-
-Best,
-{{sender}}`,
- },
- supporter: {
- subject: 'PolicyEngine sustainability - would value your perspective',
- body: `Hi {{name}},
-
-PolicyEngine is part of an NSF POSE program exploring sustainable models for open-source policy tools.
-
-Given {{context}}, I'd love to hear your perspective on:
-- What makes open-source policy tools valuable to funders
-- Sustainability models you've seen work
-- How we can demonstrate impact effectively
-
-Would you have 30-45 minutes to chat? Your insights would be incredibly valuable.
-
-Schedule a time: https://cal.com/max-ghenis-policyengine/pose-ecosystem-interview
-
-Best,
-{{sender}}`,
- },
- contributor: {
- subject: 'Thanks for contributing to PolicyEngine - quick chat?',
- body: `Hi {{name}},
-
-I wanted to reach out because {{context}}.
-
-We're exploring how to build a sustainable contributor community for PolicyEngine as part of an NSF program. I'd love to hear:
-- What motivated you to contribute
-- What would make contributing easier or more rewarding
-- Ideas for growing the contributor community
-
-Would you have 30 minutes to chat? Your experience would be really valuable.
-
-Schedule a time: https://cal.com/max-ghenis-policyengine/pose-ecosystem-interview
-
-Best,
-{{sender}}`,
- },
- competitor: {
- subject: 'Learning from {{org}} - quick chat?',
- body: `Hi {{name}},
-
-I'm {{sender}} from PolicyEngine. We're researching the landscape of policy analysis tools as part of an NSF program.
-
-I've been following {{context}} and would love to understand:
-- How you approach policy microsimulation
-- What you've learned about user needs
-- Where you see the field heading
-
-This isn't a sales pitch - genuinely trying to learn from different approaches.
-
-Would you have 30 minutes to chat?
-
-Schedule a time: https://cal.com/max-ghenis-policyengine/pose-ecosystem-interview
-
-Best,
-{{sender}}`,
- },
- distributor: {
- subject: 'Exploring collaboration - PolicyEngine + {{org}}',
- body: `Hi {{name}},
-
-I'm {{sender}}, CEO of PolicyEngine. We build open-source microsimulation tools for tax and benefit policy analysis.
-
-{{context}}
-
-We're exploring partnerships with organizations who could benefit from embedded policy calculators or custom analysis tools.
-
-Would you have 30 minutes to explore potential collaboration? I'd love to understand your needs and share what we're building.
-
-Schedule a time: https://cal.com/max-ghenis-policyengine/pose-ecosystem-interview
-
-Best,
-{{sender}}`,
- },
- partner: {
- subject: 'Integration opportunity - PolicyEngine + {{org}}',
- body: `Hi {{name}},
-
-I'm {{sender}} from PolicyEngine. {{context}}
-
-We're exploring integrations and partnerships that could benefit both our users. I'd love to discuss:
-- How our tools might complement each other
-- Potential integration points
-- Joint opportunities to serve policy researchers
-
-Would you have 30 minutes to explore this?
-
-Schedule a time: https://cal.com/max-ghenis-policyengine/pose-ecosystem-interview
-
-Best,
-{{sender}}`,
- },
-}
-
-const SENDER_SIGNATURES: Record = {
- Max: `Max Ghenis
-CEO, PolicyEngine
-max@policyengine.org`,
- Pavel: `Pavel Makarchuk
-Chief of Staff, PolicyEngine
-pavel@policyengine.org`,
- Dan: `Daniel Feenberg
-+1 617-682-6204`,
-}
-
-export function Outreach() {
- const [formData, setFormData] = useState({
- recipientName: '',
- recipientOrg: '',
- recipientRole: '',
- segment: 'user',
- context: '',
- senderName: 'Max',
- })
- const [copied, setCopied] = useState<'subject' | 'body' | null>(null)
-
- const generateEmail = useCallback(() => {
- const template = EMAIL_TEMPLATES[formData.segment]
- const firstName = formData.recipientName.split(' ')[0] || formData.recipientName
-
- let subject = template.subject
- .replace('{{name}}', firstName)
- .replace('{{org}}', formData.recipientOrg || 'your organization')
-
- let body = template.body
- .replace(/\{\{name\}\}/g, firstName)
- .replace(/\{\{org\}\}/g, formData.recipientOrg || 'your organization')
- .replace(/\{\{context\}\}/g, formData.context || 'your work in this area')
- .replace(/\{\{sender\}\}/g, SENDER_SIGNATURES[formData.senderName])
-
- return { subject, body }
- }, [formData])
-
- const { subject, body } = generateEmail()
-
- const copyToClipboard = async (text: string, type: 'subject' | 'body') => {
- await navigator.clipboard.writeText(text)
- setCopied(type)
- setTimeout(() => setCopied(null), 2000)
- }
-
- const handleInputChange = (field: keyof OutreachFormData, value: string) => {
- setFormData((prev) => ({ ...prev, [field]: value }))
- }
-
- return (
-
- {/* Header */}
-
-
-
Email outreach generator
-
- Generate personalized interview request emails
-
-
-
-
-
- {/* Input Form */}
-
- Recipient details
-
-
- {/* Name */}
-
-
-
- Name
-
- handleInputChange('recipientName', e.target.value)}
- placeholder="Jane Smith"
- className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-teal-500 focus:border-teal-500"
- />
-
-
- {/* Organization */}
-
-
-
- Organization
-
- handleInputChange('recipientOrg', e.target.value)}
- placeholder="Tax Policy Center"
- className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-teal-500 focus:border-teal-500"
- />
-
-
- {/* Role */}
-
-
-
- Role (optional)
-
- handleInputChange('recipientRole', e.target.value)}
- placeholder="Senior Fellow"
- className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-teal-500 focus:border-teal-500"
- />
-
-
- {/* Segment */}
-
-
- Segment
-
-
- {SEGMENTS.map((seg) => (
- handleInputChange('segment', seg.value)}
- className={`px-3 py-2 text-sm rounded-lg border transition-all ${
- formData.segment === seg.value
- ? 'border-teal-500 bg-teal-50 text-teal-700'
- : 'border-gray-200 text-gray-600 hover:border-gray-300'
- }`}
- >
- {seg.label}
-
- ))}
-
-
- {SEGMENTS.find((s) => s.value === formData.segment)?.description}
-
-
-
- {/* Context */}
-
-
- Context / connection
-
-
handleInputChange('context', e.target.value)}
- placeholder="you've used PolicyEngine to analyze child tax credit proposals"
- rows={2}
- className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-teal-500 focus:border-teal-500"
- />
-
- How do you know them? Why are you reaching out?
-
-
-
- {/* Sender */}
-
-
- Send as
-
-
- {(['Max', 'Pavel', 'Dan'] as const).map((name) => (
- handleInputChange('senderName', name)}
- className={`px-4 py-2 text-sm rounded-lg border transition-all ${
- formData.senderName === name
- ? 'border-teal-500 bg-teal-50 text-teal-700'
- : 'border-gray-200 text-gray-600 hover:border-gray-300'
- }`}
- >
- {name}
-
- ))}
-
-
-
-
-
- {/* Generated Email */}
-
-
-
-
- Generated email
-
- setFormData({ ...formData })}
- className="p-2 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-lg"
- title="Regenerate"
- >
-
-
-
-
- {/* Subject */}
-
-
- Subject
- copyToClipboard(subject, 'subject')}
- className="text-xs text-teal-600 hover:text-teal-700 flex items-center gap-1"
- >
- {copied === 'subject' ? (
- <>
- Copied
- >
- ) : (
- <>
- Copy
- >
- )}
-
-
-
- {subject}
-
-
-
- {/* Body */}
-
-
- Body
- copyToClipboard(body, 'body')}
- className="text-xs text-teal-600 hover:text-teal-700 flex items-center gap-1"
- >
- {copied === 'body' ? (
- <>
- Copied
- >
- ) : (
- <>
- Copy
- >
- )}
-
-
-
- {body}
-
-
-
- {/* Quick actions */}
-
-
Quick actions
-
-
- Open in email client
-
-
{
- copyToClipboard(subject + '\n\n' + body, 'body')
- }}
- className="px-4 py-2 border border-gray-300 text-gray-700 text-sm font-medium rounded-lg hover:bg-gray-50 transition-colors"
- >
- Copy all
-
-
-
-
-
-
- {/* Tips */}
-
- Outreach tips
-
- • Personalize the context - Reference their specific work or how you found them
- • Send Tuesday-Thursday - Best response rates
- • Follow up once after 5 days if no response
- • Log in Interviews tab as "scheduled" once they respond
- • Ask for referrals at the end of each interview: "Who else should we talk to?"
-
-
-
- )
-}
diff --git a/app/src/components/ProgressRing.test.tsx b/app/src/components/ProgressRing.test.tsx
deleted file mode 100644
index 6b724e0..0000000
--- a/app/src/components/ProgressRing.test.tsx
+++ /dev/null
@@ -1,82 +0,0 @@
-import { describe, it, expect } from 'vitest'
-import { render, screen } from '@testing-library/react'
-import { ProgressRing } from './ProgressRing'
-
-describe('ProgressRing', () => {
- it('renders the current count', () => {
- render( )
- expect(screen.getByText('25')).toBeInTheDocument()
- })
-
- it('renders the target count with default label', () => {
- render( )
- expect(screen.getByText('/ 100 Completed')).toBeInTheDocument()
- })
-
- it('renders with custom label', () => {
- render( )
- expect(screen.getByText('/ 100 Interviews')).toBeInTheDocument()
- })
-
- it('calculates and displays correct percentage', () => {
- render( )
- expect(screen.getByText('25%')).toBeInTheDocument()
- })
-
- it('rounds percentage correctly', () => {
- render( )
- expect(screen.getByText('33%')).toBeInTheDocument()
- })
-
- it('caps percentage at 100% when current exceeds target', () => {
- render( )
- expect(screen.getByText('100%')).toBeInTheDocument()
- })
-
- it('displays 0% when current is 0', () => {
- render( )
- expect(screen.getByText('0%')).toBeInTheDocument()
- })
-
- it('renders an SVG element', () => {
- const { container } = render( )
- const svg = container.querySelector('svg')
- expect(svg).toBeInTheDocument()
- })
-
- it('renders two circle elements (background and progress)', () => {
- const { container } = render( )
- const circles = container.querySelectorAll('circle')
- expect(circles).toHaveLength(2)
- })
-
- it('uses default size of 180', () => {
- const { container } = render( )
- const svg = container.querySelector('svg')
- expect(svg).toHaveAttribute('width', '180')
- expect(svg).toHaveAttribute('height', '180')
- })
-
- it('respects custom size prop', () => {
- const { container } = render(
-
- )
- const svg = container.querySelector('svg')
- expect(svg).toHaveAttribute('width', '200')
- expect(svg).toHaveAttribute('height', '200')
- })
-
- it('applies the correct stroke color to progress ring', () => {
- const { container } = render( )
- const circles = container.querySelectorAll('circle')
- // First circle is background (gray), second is progress (teal)
- expect(circles[0]).toHaveAttribute('stroke', '#e5e7eb')
- expect(circles[1]).toHaveAttribute('stroke', '#319795')
- })
-
- it('sets strokeLinecap to round on progress ring', () => {
- const { container } = render( )
- const circles = container.querySelectorAll('circle')
- expect(circles[1]).toHaveAttribute('stroke-linecap', 'round')
- })
-})
diff --git a/app/src/components/ProgressRing.tsx b/app/src/components/ProgressRing.tsx
deleted file mode 100644
index 9b05ded..0000000
--- a/app/src/components/ProgressRing.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import { motion } from 'framer-motion'
-
-interface ProgressRingProps {
- current: number
- target: number
- size?: number
- strokeWidth?: number
- label?: string
-}
-
-export function ProgressRing({
- current,
- target,
- size = 180,
- strokeWidth = 12,
- label = 'Completed'
-}: ProgressRingProps) {
- const radius = (size - strokeWidth) / 2
- const circumference = 2 * Math.PI * radius
- const progress = Math.min(current / target, 1)
- const offset = circumference - progress * circumference
-
- return (
-
-
- {/* Background ring */}
-
- {/* Progress ring */}
-
-
-
-
- {current}
-
- / {target} {label}
-
- {Math.round(progress * 100)}%
-
-
-
- )
-}
diff --git a/app/src/components/SegmentChart.tsx b/app/src/components/SegmentChart.tsx
deleted file mode 100644
index 4d2ddad..0000000
--- a/app/src/components/SegmentChart.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import { motion } from 'framer-motion'
-import { SEGMENTS, type Segment } from '../types/database'
-
-interface SegmentChartProps {
- segmentCounts: Record
- totalCompleted: number
-}
-
-export function SegmentChart({ segmentCounts, totalCompleted }: SegmentChartProps) {
- const maxCount = Math.max(...Object.values(segmentCounts), 1)
-
- return (
-
-
Stakeholder segments
-
- Interview coverage across ecosystem segments
-
-
-
- {SEGMENTS.map((segment, index) => {
- const count = segmentCounts[segment.value] || 0
- const percentage = totalCompleted > 0 ? Math.round((count / totalCompleted) * 100) : 0
- const barWidth = maxCount > 0 ? (count / maxCount) * 100 : 0
-
- return (
-
-
-
-
- {count}
- ({percentage}%)
-
-
-
-
-
-
-
- {segment.description}
-
- )
- })}
-
-
- {totalCompleted === 0 && (
-
- Complete interviews to see segment distribution
-
- )}
-
- )
-}
diff --git a/app/src/components/Slides.tsx b/app/src/components/Slides.tsx
deleted file mode 100644
index e34d183..0000000
--- a/app/src/components/Slides.tsx
+++ /dev/null
@@ -1,670 +0,0 @@
-import { useState, useEffect, useCallback } from 'react'
-import { motion, AnimatePresence } from 'framer-motion'
-import {
- ChevronLeft,
- ChevronRight,
- Maximize2,
- Minimize2,
- Users,
- Lightbulb,
- HelpCircle,
- ClipboardList,
- Target,
- Download,
-} from 'lucide-react'
-import pptxgen from 'pptxgenjs'
-
-// Types
-interface SlideProps {
- isFullscreen: boolean
-}
-
-interface TeamMember {
- name: string
- role: string
- photo: string
- bio: string
-}
-
-// Team data
-const teamMembers: TeamMember[] = [
- {
- name: 'Max Ghenis',
- role: 'CEO',
- photo: '/assets/team/max-ghenis.png',
- bio: 'Founded UBI Center, MIT M.S. Development Economics, former Google',
- },
- {
- name: 'Pavel Makarchuk',
- role: 'Chief of Staff',
- photo: '/assets/team/pavel-makarchuk.jpeg',
- bio: 'Operations and strategy lead',
- },
- {
- name: 'Daniel Feenberg',
- role: 'Advisor',
- photo: '/assets/team/daniel-feenberg.jpg',
- bio: 'Former IT Director at NBER, created TAXSIM, Princeton Ph.D.',
- },
-]
-
-// Slide components
-function TeamSlide({ isFullscreen }: SlideProps) {
- return (
-
-
-
-
-
-
-
- PolicyEngine POSE Team
-
-
-
-
-
- {teamMembers.map((member) => (
-
-
-
-
-
- {member.name}
-
-
- {member.role}
-
-
- {member.bio}
-
-
- ))}
-
-
- )
-}
-
-function ThesisSlide({ isFullscreen }: SlideProps) {
- return (
-
-
-
-
-
-
- 4373 PolicyEngine | OSE Thesis
-
-
-
-
-
-
- FOR {' '}
-
- economists, policy researchers, think tanks, journalists, advocates, and developers building benefit access tools
-
-
-
- WHO NEED TO {' '}
-
- understand taxes and benefits for households or analyze policy impacts on populations
- ,
-
-
- THE STATUS QUO {' '}
-
- proprietary microsimulation tools
- {' '}
- FAILS DUE TO {' '}
-
- high cost, limited accessibility, and restrictions in government/secure environments
- , CAUSING {' '}
-
- policy decisions without rigorous distributional analysis
- .
-
-
- WE WILL ESTABLISH A MANAGING ORGANIZATION FOR {' '}
-
- open-source fiscal policy simulation
-
-
-
- TO DELIVER {' '}
-
- PolicyEngine models, web apps, and APIs
- {' '}
- WITH {' '}
-
- AGPL license and transparent governance
- .
-
-
- WE WILL GROW THE COMMUNITY THROUGH {' '}
-
- documentation and partnerships with universities and think tanks
- ,
-
-
- WE WILL ACHIEVE {' '}
-
- democratized access to sophisticated policy analysis
- ,
-
-
- MEASURE SUCCESS BY {' '}
-
- citations, applications built on PolicyEngine, their users, contributors, and funding
- ,
-
-
- AND SUSTAIN THE ECOSYSTEM VIA {' '}
-
- diversified foundation grants, government funding, and SaaS offerings
- .
-
-
-
-
- )
-}
-
-function AssumptionsSlide({ isFullscreen }: SlideProps) {
- const goalsAndAssumptions = [
- {
- goal: 'Grow adoption among policy analysts',
- assumption: 'Policy researchers will adopt open-source tools if they\'re accessible without programming expertise',
- color: 'teal',
- },
- {
- goal: 'Achieve sustainable, diversified funding',
- assumption: 'Funders value transparency and reproducibility enough to fund open-source over proprietary alternatives',
- color: 'purple',
- },
- {
- goal: 'Build active contributor community',
- assumption: 'Developers will contribute for policy impact without requiring competitive compensation',
- color: 'blue',
- },
- ]
-
- const colorClasses: Record = {
- teal: { bg: 'bg-teal-50', border: 'border-teal-200', text: 'text-teal-700', goalText: 'text-teal-800' },
- purple: { bg: 'bg-purple-50', border: 'border-purple-200', text: 'text-purple-700', goalText: 'text-purple-800' },
- blue: { bg: 'bg-blue-50', border: 'border-blue-200', text: 'text-blue-700', goalText: 'text-blue-800' },
- }
-
- return (
-
-
-
-
-
-
- Assumptions
-
-
-
-
- {goalsAndAssumptions.map((item, index) => {
- const colors = colorClasses[item.color]
- return (
-
-
- Goal: {item.goal}
-
-
- Assumption: {item.assumption}
-
-
- )
- })}
-
-
- )
-}
-
-function InterviewLogSlide({ isFullscreen }: SlideProps) {
- return (
-
-
-
-
-
-
- Interview log
-
-
-
-
-
-
-
-
- View full interview tracker
-
-
- Track all ecosystem discovery interviews, progress toward 100 interviews, and segment coverage.
-
-
-
- Navigate to the Interviews tab to access the full interview tracker with search, filtering, and analytics.
-
-
-
-
- )
-}
-
-function GoalsCharterSlide({ isFullscreen }: SlideProps) {
- const goals = [
- 'Complete 100 ecosystem discovery interviews across all 6 stakeholder segments',
- 'Identify 3+ sustainable funding models beyond traditional grants',
- 'Establish partnerships with 5+ policy think tanks and media organizations',
- 'Develop community governance structure with clear decision rights',
- ]
-
- const agreements = [
- 'Weekly team sync (Mondays)',
- '24-hour response time on Slack',
- 'Share interview notes within 24 hours',
- 'Consensus on strategic decisions, CEO decides operational matters',
- ]
-
- return (
-
-
-
-
-
-
- Team goals and charter
-
-
-
-
- {/* Goals */}
-
-
- Goals
-
-
- {goals.map((goal, index) => (
-
-
- {index + 1}
-
- {goal}
-
- ))}
-
-
-
- {/* Working Agreements */}
-
-
- Working agreements
-
-
- {agreements.map((agreement, index) => (
-
-
- {agreement}
-
- ))}
-
-
-
-
- )
-}
-
-// Slide configuration
-interface SlideConfig {
- id: string
- title: string
- component: React.FC
-}
-
-const slides: SlideConfig[] = [
- { id: 'team', title: 'Team', component: TeamSlide },
- { id: 'thesis', title: 'Thesis', component: ThesisSlide },
- { id: 'assumptions', title: 'Assumptions', component: AssumptionsSlide },
- { id: 'interview-log', title: 'Interview log', component: InterviewLogSlide },
- { id: 'goals-charter', title: 'Goals and charter', component: GoalsCharterSlide },
-]
-
-export function Slides() {
- const [currentSlide, setCurrentSlide] = useState(0)
- const [isFullscreen, setIsFullscreen] = useState(false)
-
- const goToSlide = useCallback((index: number) => {
- if (index >= 0 && index < slides.length) {
- setCurrentSlide(index)
- }
- }, [])
-
- const nextSlide = useCallback(() => {
- goToSlide(currentSlide + 1)
- }, [currentSlide, goToSlide])
-
- const prevSlide = useCallback(() => {
- goToSlide(currentSlide - 1)
- }, [currentSlide, goToSlide])
-
- const toggleFullscreen = useCallback(() => {
- if (!document.fullscreenElement) {
- document.documentElement.requestFullscreen()
- setIsFullscreen(true)
- } else {
- document.exitFullscreen()
- setIsFullscreen(false)
- }
- }, [])
-
- const handleExportPPT = useCallback(async () => {
- const pptx = new pptxgen()
- pptx.layout = 'LAYOUT_16x9'
- pptx.title = 'PolicyEngine POSE Presentation'
- pptx.author = 'PolicyEngine'
-
- // Colors
- const teal = '319795'
- const purple = '7C3AED'
- const blue = '2563EB'
- const orange = 'EA580C'
- const green = '16A34A'
-
- // Slide 1: Team
- const slide1 = pptx.addSlide()
- slide1.addText('PolicyEngine POSE Team', {
- x: 0.5,
- y: 0.5,
- w: '90%',
- fontSize: 36,
- bold: true,
- color: '1F2937',
- })
-
- const teamData = [
- { name: 'Max Ghenis', role: 'CEO', bio: 'Founded UBI Center, MIT M.S. Development Economics, former Google' },
- { name: 'Pavel Makarchuk', role: 'Chief of Staff', bio: 'Operations and strategy lead' },
- { name: 'Daniel Feenberg', role: 'Advisor', bio: 'Former IT Director at NBER, created TAXSIM, Princeton Ph.D.' },
- ]
-
- teamData.forEach((member, i) => {
- const xPos = 0.5 + i * 3.3
- slide1.addText(member.name, { x: xPos, y: 2.0, w: 3, fontSize: 18, bold: true, color: '1F2937', align: 'center' })
- slide1.addText(member.role, { x: xPos, y: 2.5, w: 3, fontSize: 14, color: teal, align: 'center' })
- slide1.addText(member.bio, { x: xPos, y: 3.0, w: 3, fontSize: 10, color: '6B7280', align: 'center' })
- })
-
- // Slide 2: Thesis (OSE AdLib format)
- const slide2 = pptx.addSlide()
- slide2.addText('4373 PolicyEngine | OSE Thesis', { x: 0.5, y: 0.3, w: '90%', fontSize: 28, bold: true, color: '1F2937' })
-
- const thesisLines = [
- { prefix: 'FOR', text: 'economists, policy researchers, think tanks, journalists, advocates, and developers building benefit access tools', color: teal },
- { prefix: 'WHO NEED TO', text: 'understand taxes and benefits for households or analyze policy impacts on populations', color: purple },
- { prefix: 'THE STATUS QUO', text: 'proprietary microsimulation tools', color: 'DC2626', suffix: 'FAILS DUE TO high cost, limited accessibility, and restrictions in government/secure environments, CAUSING policy decisions without rigorous distributional analysis.' },
- { prefix: 'WE WILL ESTABLISH A MANAGING ORGANIZATION FOR', text: 'open-source fiscal policy simulation', color: blue },
- { prefix: 'TO DELIVER', text: 'PolicyEngine models, web apps, and APIs', color: blue, suffix: 'WITH AGPL license and transparent governance.' },
- { prefix: 'WE WILL GROW THE COMMUNITY THROUGH', text: 'documentation and partnerships with universities and think tanks', color: green },
- { prefix: 'WE WILL ACHIEVE', text: 'democratized access to sophisticated policy analysis', color: green },
- { prefix: 'MEASURE SUCCESS BY', text: 'citations, applications built on PolicyEngine, their users, contributors, and funding', color: orange },
- { prefix: 'AND SUSTAIN THE ECOSYSTEM VIA', text: 'diversified foundation grants, government funding, and SaaS offerings', color: orange },
- ]
-
- thesisLines.forEach((line, i) => {
- const yPos = 0.9 + i * 0.5
- slide2.addText([
- { text: line.prefix + ' ', options: { bold: true, color: '374151' } },
- { text: line.text, options: { color: line.color } },
- ...(line.suffix ? [{ text: ' ' + line.suffix, options: { color: '374151' } }] : []),
- ], { x: 0.5, y: yPos, w: 9, fontSize: 12 })
- })
-
- // Slide 3: Assumptions
- const slide3 = pptx.addSlide()
- slide3.addText('Assumptions', { x: 0.5, y: 0.5, w: '90%', fontSize: 36, bold: true, color: '1F2937' })
-
- const goalsAndAssumptions = [
- { goal: 'Grow adoption among policy analysts', assumption: 'Policy researchers will adopt open-source tools if they\'re accessible without programming expertise', color: teal },
- { goal: 'Achieve sustainable, diversified funding', assumption: 'Funders value transparency and reproducibility enough to fund open-source over proprietary alternatives', color: purple },
- { goal: 'Build active contributor community', assumption: 'Developers will contribute for policy impact without requiring competitive compensation', color: blue },
- ]
- goalsAndAssumptions.forEach((item, i) => {
- const yPos = 1.5 + i * 1.4
- slide3.addShape('rect', { x: 0.5, y: yPos, w: 9, h: 1.2, fill: { color: item.color, transparency: 90 }, line: { color: item.color } })
- slide3.addText([{ text: 'Goal: ', options: { color: '6B7280' } }, { text: item.goal, options: { bold: true, color: item.color } }], { x: 0.7, y: yPos + 0.15, w: 8.5, fontSize: 12 })
- slide3.addText([{ text: 'Assumption: ', options: { color: '6B7280' } }, { text: item.assumption, options: { color: item.color } }], { x: 0.7, y: yPos + 0.6, w: 8.5, fontSize: 11 })
- })
-
- // Slide 4: Interview Log
- const slide4 = pptx.addSlide()
- slide4.addText('Interview log', { x: 0.5, y: 0.5, w: '90%', fontSize: 36, bold: true, color: '1F2937' })
- slide4.addText('View full interview tracker', { x: 0.5, y: 2, w: 9, fontSize: 24, bold: true, color: '1F2937', align: 'center' })
- slide4.addText(
- 'Track all ecosystem discovery interviews, progress toward 100 interviews, and segment coverage.\n\nNavigate to the Interviews tab to access the full interview tracker with search, filtering, and analytics.',
- { x: 1, y: 2.8, w: 8, fontSize: 14, color: '6B7280', align: 'center' }
- )
-
- // Slide 5: Goals and Charter
- const slide5 = pptx.addSlide()
- slide5.addText('Team goals and charter', { x: 0.5, y: 0.5, w: '90%', fontSize: 36, bold: true, color: '1F2937' })
-
- slide5.addText('Goals', { x: 0.5, y: 1.2, w: 4, fontSize: 18, bold: true, color: '1F2937' })
- const goals = [
- 'Complete 100 ecosystem discovery interviews across all 6 stakeholder segments',
- 'Identify 3+ sustainable funding models beyond traditional grants',
- 'Establish partnerships with 5+ policy think tanks and media organizations',
- 'Develop community governance structure with clear decision rights',
- ]
- goals.forEach((goal, i) => {
- slide5.addShape('rect', { x: 0.5, y: 1.7 + i * 0.7, w: 4.5, h: 0.6, fill: { color: green, transparency: 90 }, line: { color: green } })
- slide5.addText(`${i + 1}. ${goal}`, { x: 0.6, y: 1.8 + i * 0.7, w: 4.3, fontSize: 9, color: green })
- })
-
- slide5.addText('Working agreements', { x: 5.2, y: 1.2, w: 4, fontSize: 18, bold: true, color: '1F2937' })
- const agreements = [
- 'Weekly team sync (Mondays)',
- '24-hour response time on Slack',
- 'Share interview notes within 24 hours',
- 'Consensus on strategic decisions, CEO decides operational matters',
- ]
- agreements.forEach((agreement, i) => {
- slide5.addShape('rect', { x: 5.2, y: 1.7 + i * 0.7, w: 4.5, h: 0.6, fill: { color: teal, transparency: 90 }, line: { color: teal } })
- slide5.addText(`• ${agreement}`, { x: 5.3, y: 1.8 + i * 0.7, w: 4.3, fontSize: 9, color: teal })
- })
-
- // Generate and download with date-based naming
- const today = new Date()
- const dateStr = `${String(today.getMonth() + 1).padStart(2, '0')}${String(today.getDate()).padStart(2, '0')}${today.getFullYear()}`
- await pptx.writeFile({ fileName: `4373_PolicyEngine_${dateStr}.pptx` })
- }, [])
-
- // Keyboard navigation
- useEffect(() => {
- const handleKeyDown = (e: KeyboardEvent) => {
- if (e.key === 'ArrowRight' || e.key === ' ') {
- e.preventDefault()
- nextSlide()
- } else if (e.key === 'ArrowLeft') {
- e.preventDefault()
- prevSlide()
- } else if (e.key === 'Escape' && isFullscreen) {
- setIsFullscreen(false)
- } else if (e.key === 'f' || e.key === 'F') {
- toggleFullscreen()
- } else if (e.key === 'd' || e.key === 'D') {
- handleExportPPT()
- }
- }
-
- window.addEventListener('keydown', handleKeyDown)
- return () => window.removeEventListener('keydown', handleKeyDown)
- }, [nextSlide, prevSlide, isFullscreen, toggleFullscreen, handleExportPPT])
-
- // Listen for fullscreen changes
- useEffect(() => {
- const handleFullscreenChange = () => {
- setIsFullscreen(!!document.fullscreenElement)
- }
-
- document.addEventListener('fullscreenchange', handleFullscreenChange)
- return () => document.removeEventListener('fullscreenchange', handleFullscreenChange)
- }, [])
-
- const CurrentSlideComponent = slides[currentSlide].component
-
- return (
-
- {/* Slide thumbnail navigation */}
- {!isFullscreen && (
-
- {slides.map((slide, index) => (
- goToSlide(index)}
- className={`flex-shrink-0 px-3 py-2 rounded-lg text-sm font-medium transition-all ${
- currentSlide === index
- ? 'bg-teal-600 text-white shadow-md'
- : 'bg-white text-gray-600 border border-gray-200 hover:border-teal-300 hover:text-teal-600'
- }`}
- >
- {index + 1}. {slide.title}
-
- ))}
-
- )}
-
- {/* Main slide area */}
-
-
- {/* Slide content */}
-
-
- {/* Controls */}
-
-
-
-
-
-
- {currentSlide + 1} / {slides.length}
-
-
-
-
-
-
-
- {/* Slide dots */}
-
- {slides.map((_, index) => (
- goToSlide(index)}
- className={`rounded-full transition-all ${
- currentSlide === index
- ? 'bg-teal-500 w-3 h-3'
- : 'bg-gray-300 hover:bg-gray-400 w-2 h-2'
- }`}
- />
- ))}
-
-
-
-
-
-
-
- {isFullscreen ? (
-
- ) : (
-
- )}
-
-
-
-
-
-
- {/* Keyboard hints */}
- {!isFullscreen && (
-
- Use arrow keys to navigate, F for fullscreen, D to download PPT
-
- )}
-
- )
-}
diff --git a/app/src/components/ecosystem/EcosystemEdge.tsx b/app/src/components/ecosystem/EcosystemEdge.tsx
new file mode 100644
index 0000000..9454c6f
--- /dev/null
+++ b/app/src/components/ecosystem/EcosystemEdge.tsx
@@ -0,0 +1,56 @@
+import type { EcosystemEdge as EdgeType } from '../../lib/types';
+
+interface EcosystemEdgeProps {
+ edge: EdgeType;
+ fromPos: { x: number; y: number };
+ toPos: { x: number; y: number };
+ visible: boolean;
+}
+
+export function EcosystemEdgeComponent({ edge, fromPos, toPos, visible }: EcosystemEdgeProps) {
+ const midX = (fromPos.x + toPos.x) / 2;
+ const midY = (fromPos.y + toPos.y) / 2;
+ const dx = toPos.x - fromPos.x;
+ const dy = toPos.y - fromPos.y;
+ const offset = 25;
+ const len = Math.sqrt(dx * dx + dy * dy + 1);
+ const cx = midX - dy * offset / len;
+ const cy = midY + dx * offset / len;
+
+ const d = `M ${fromPos.x} ${fromPos.y} Q ${cx} ${cy} ${toPos.x} ${toPos.y}`;
+
+ return (
+
+
+ {edge.label && (
+
+ {edge.label.split('\n').map((line, i) => (
+ {line}
+ ))}
+
+ )}
+
+ );
+}
diff --git a/app/src/components/ecosystem/EcosystemEvolution.tsx b/app/src/components/ecosystem/EcosystemEvolution.tsx
new file mode 100644
index 0000000..859b6f0
--- /dev/null
+++ b/app/src/components/ecosystem/EcosystemEvolution.tsx
@@ -0,0 +1,88 @@
+import { useState } from 'react';
+import { EcosystemMapInteractive } from './EcosystemMapInteractive';
+import { EVOLUTION_STEPS } from '../../data/ecosystem';
+import { colors } from '../../lib/colors';
+import type { SlideProps } from '../../lib/types';
+import { SlideHeader } from '../ui/SlideHeader';
+import { useScrollProgress } from '../../hooks/useScrollProgress';
+
+const LEGEND: { label: string; org: 'rules' | 'cosilico' | 'pe' | 'all'; color: string; bg: string }[] = [
+ { label: 'Rules Foundation', org: 'rules', color: colors.rulesBlue, bg: '#EFF6FF' },
+ { label: 'Cosilico', org: 'cosilico', color: colors.cosilicoCyan, bg: '#ECFEFF' },
+ { label: 'PolicyEngine', org: 'pe', color: colors.peTeal, bg: '#F0FDFA' },
+ { label: 'Shared', org: 'all', color: '#94A3B8', bg: '#F8FAFC' },
+];
+
+export function EcosystemEvolutionSlide(_props: SlideProps) {
+ const { containerRef, currentStep } = useScrollProgress(3);
+ const stepData = EVOLUTION_STEPS[currentStep - 1];
+ const [selectedOrg, setSelectedOrg] = useState<'all' | 'rules' | 'cosilico' | 'pe'>('all');
+
+ return (
+
+
+
+
+ {/* Step indicators + legend row */}
+
+
+ {EVOLUTION_STEPS.map((s) => (
+
+ {s.step}. {s.title}
+
+ ))}
+
+
+ {/* Legend (clickable) */}
+
+ {LEGEND.map((item) => {
+ const isActive = selectedOrg === item.org;
+ return (
+
setSelectedOrg(isActive ? 'all' : item.org)}
+ className="flex items-center gap-1.5 px-2 py-1 rounded-md transition-all cursor-pointer"
+ style={{
+ backgroundColor: isActive ? item.bg : 'transparent',
+ border: `1.5px solid ${isActive ? item.color : 'transparent'}`,
+ opacity: selectedOrg === 'all' || isActive ? 1 : 0.4,
+ }}
+ >
+
+
+ {item.label}
+
+
+ );
+ })}
+
+
+
+
+ {stepData.description}
+
+
+ {/* Map */}
+
+
+
+
+
+ );
+}
diff --git a/app/src/components/ecosystem/EcosystemMapInteractive.tsx b/app/src/components/ecosystem/EcosystemMapInteractive.tsx
new file mode 100644
index 0000000..97072a6
--- /dev/null
+++ b/app/src/components/ecosystem/EcosystemMapInteractive.tsx
@@ -0,0 +1,143 @@
+import { useMemo } from 'react';
+import { ecosystemNodes, ecosystemEdges, RING_RADII, RING_LABELS, CENTER } from '../../data/ecosystem';
+import { EcosystemNodeComponent } from './EcosystemNode';
+import { EcosystemEdgeComponent } from './EcosystemEdge';
+import { EcosystemRing } from './EcosystemRing';
+import { colors } from '../../lib/colors';
+
+interface EcosystemMapInteractiveProps {
+ step: number;
+ filterOrg?: 'all' | 'rules' | 'cosilico' | 'pe';
+}
+
+const SPLIT_CORE_IDS = new Set(['rules', 'cosilico', 'pe']);
+const SPLIT_CORE_RADIUS = 130;
+
+function getNodePosition(id: string, ring: number, angle: number) {
+ const rad = (angle * Math.PI) / 180;
+ const r = ring === 0 && SPLIT_CORE_IDS.has(id) ? SPLIT_CORE_RADIUS : (RING_RADII[ring] || 0);
+ return {
+ x: CENTER.x + r * Math.cos(rad),
+ y: CENTER.y + r * Math.sin(rad),
+ };
+}
+
+// Unique arrow markers (deduplicated)
+const ARROW_COLORS = [...new Set([
+ colors.accentBlue, colors.accentTeal, colors.accentGreen, colors.accentPurple,
+ colors.accentOrange, colors.rulesBlue, colors.cosilicoCyan, colors.peTeal,
+ colors.highlight, colors.lightGray, colors.dimText,
+])];
+
+export function EcosystemMapInteractive({ step, filterOrg }: EcosystemMapInteractiveProps) {
+ const nodePositions = useMemo(() => {
+ const positions: Record = {};
+ ecosystemNodes.forEach((node) => {
+ positions[node.id] = getNodePosition(node.id, node.ring, node.angle);
+ });
+ return positions;
+ }, []);
+
+ const isNodeVisible = (node: typeof ecosystemNodes[0]) => {
+ // PE-unified only at step 1
+ if (node.id === 'pe-unified') return step === 1;
+ // Split core orgs appear at step 2+
+ if (SPLIT_CORE_IDS.has(node.id)) return step >= 2;
+ // Step 2: clean split — only show the 3 core orgs
+ if (step === 2) return false;
+ // Otherwise use visibleAtStep
+ if (node.visibleAtStep > step) return false;
+ if (filterOrg && filterOrg !== 'all' && node.org !== filterOrg && node.org !== 'all') return false;
+ return true;
+ };
+
+ const isNodeHighlighted = (node: typeof ecosystemNodes[0]) => {
+ if (!filterOrg || filterOrg === 'all') return true;
+ return node.org === filterOrg || node.org === 'all';
+ };
+
+ const isEdgeVisible = (edge: typeof ecosystemEdges[0]) => {
+ // Unified edges (step 1) only show at step 1
+ if (edge.visibleAtStep === 1) return step === 1;
+ // Split edges show at their step and beyond
+ if (step < edge.visibleAtStep) return false;
+ // If org filter active, only show edges touching the filtered org's nodes
+ if (filterOrg && filterOrg !== 'all') {
+ const fromNode = ecosystemNodes.find((n) => n.id === edge.from);
+ const toNode = ecosystemNodes.find((n) => n.id === edge.to);
+ const fromMatch = fromNode && (fromNode.org === filterOrg || fromNode.org === 'all');
+ const toMatch = toNode && (toNode.org === filterOrg || toNode.org === 'all');
+ return fromMatch || toMatch;
+ }
+ return true;
+ };
+
+ // Rings: step 1 shows rings 1-2, step 2 hides all, step 3 shows all
+ const visibleRings = step === 1 ? [1, 2] : step === 3 ? [1, 2, 3, 4] : [];
+
+ return (
+
+ {/* Arrow markers */}
+
+ {ARROW_COLORS.map((c) => (
+
+
+
+ ))}
+
+
+ {/* Concentric rings */}
+ {[1, 2, 3, 4].map((ringIdx, i) => (
+
+ ))}
+
+ {/* Edges */}
+ {ecosystemEdges.map((edge, i) => {
+ const fromPos = nodePositions[edge.from];
+ const toPos = nodePositions[edge.to];
+ if (!fromPos || !toPos) return null;
+ return (
+
+ );
+ })}
+
+ {/* Nodes */}
+ {ecosystemNodes.map((node) => {
+ const pos = nodePositions[node.id];
+ return (
+
+ );
+ })}
+
+ );
+}
diff --git a/app/src/components/ecosystem/EcosystemNode.tsx b/app/src/components/ecosystem/EcosystemNode.tsx
new file mode 100644
index 0000000..e99ef68
--- /dev/null
+++ b/app/src/components/ecosystem/EcosystemNode.tsx
@@ -0,0 +1,121 @@
+import type { EcosystemNode as NodeType } from '../../lib/types';
+
+interface EcosystemNodeProps {
+ node: NodeType;
+ x: number;
+ y: number;
+ visible: boolean;
+ highlighted: boolean;
+}
+
+// Org-specific background tints (light wash of the org color)
+const ORG_BG: Record = {
+ rules: '#EFF6FF', // light blue
+ cosilico: '#ECFEFF', // light cyan
+ pe: '#F0FDFA', // light teal
+ all: '#F8FAFC', // neutral gray
+};
+
+// Org-specific border colors (stronger than the tint)
+const ORG_BORDER: Record = {
+ rules: '#3B82F6',
+ cosilico: '#06B6D4',
+ pe: '#319795',
+ all: '#94A3B8',
+};
+
+// Core org nodes (ring 0) get solid colored backgrounds
+const CORE_IDS = new Set(['rules', 'cosilico', 'pe', 'pe-unified']);
+
+export function EcosystemNodeComponent({ node, x, y, visible, highlighted }: EcosystemNodeProps) {
+ const lines = node.label.split('\n');
+ const isCore = CORE_IDS.has(node.id);
+ const width = isCore ? 160 : 150;
+ const height = isCore
+ ? (lines.length > 1 ? 72 : 52)
+ : (lines.length > 1 ? 64 : 46);
+
+ const bgColor = isCore ? node.color : (ORG_BG[node.org] || ORG_BG.all);
+ const borderColor = isCore ? node.color : (ORG_BORDER[node.org] || ORG_BORDER.all);
+ const textColor = isCore ? '#FFFFFF' : '#000000';
+
+ return (
+
+ {/* Card shadow */}
+
+ {/* Card background */}
+
+ {/* Left accent bar for non-core nodes */}
+ {!isCore && (
+
+ )}
+ {lines.map((line, i) => (
+
+ {line}
+
+ ))}
+ {node.count && (
+
+
+
+ {node.count}
+
+
+ )}
+
+ );
+}
diff --git a/app/src/components/ecosystem/EcosystemRing.tsx b/app/src/components/ecosystem/EcosystemRing.tsx
new file mode 100644
index 0000000..e0cb16d
--- /dev/null
+++ b/app/src/components/ecosystem/EcosystemRing.tsx
@@ -0,0 +1,44 @@
+interface EcosystemRingProps {
+ cx: number;
+ cy: number;
+ radius: number;
+ label: string;
+ visible: boolean;
+ delay?: number;
+}
+
+export function EcosystemRing({ cx, cy, radius, label, visible, delay = 0 }: EcosystemRingProps) {
+ if (radius === 0) return null;
+
+ return (
+
+
+
+ {label}
+
+
+ );
+}
diff --git a/app/src/components/presentation/AppendixDivider.tsx b/app/src/components/presentation/AppendixDivider.tsx
new file mode 100644
index 0000000..da28f69
--- /dev/null
+++ b/app/src/components/presentation/AppendixDivider.tsx
@@ -0,0 +1,13 @@
+export function AppendixDivider() {
+ return (
+
+ );
+}
diff --git a/app/src/components/presentation/FloatingControls.tsx b/app/src/components/presentation/FloatingControls.tsx
new file mode 100644
index 0000000..7ab413b
--- /dev/null
+++ b/app/src/components/presentation/FloatingControls.tsx
@@ -0,0 +1,30 @@
+import { Download, Maximize, Minimize } from 'lucide-react';
+
+interface FloatingControlsProps {
+ isFullscreen: boolean;
+ onFullscreen: () => void;
+ onDownload: () => void;
+}
+
+export function FloatingControls({ isFullscreen, onFullscreen, onDownload }: FloatingControlsProps) {
+ return (
+
+
+
+
+
+ {isFullscreen ? : }
+
+
+ );
+}
diff --git a/app/src/components/presentation/ScrollSection.tsx b/app/src/components/presentation/ScrollSection.tsx
new file mode 100644
index 0000000..ee44fd6
--- /dev/null
+++ b/app/src/components/presentation/ScrollSection.tsx
@@ -0,0 +1,23 @@
+import type { ComponentType } from 'react';
+import { useScrollReveal } from '../../hooks/useScrollReveal';
+import type { SlideProps } from '../../lib/types';
+
+interface ScrollSectionProps {
+ id: string;
+ component: ComponentType;
+ isSticky?: boolean;
+}
+
+export function ScrollSection({ id, component: Component, isSticky }: ScrollSectionProps) {
+ const { ref, isVisible } = useScrollReveal();
+
+ return (
+
+ );
+}
diff --git a/app/src/components/presentation/ScrollStory.tsx b/app/src/components/presentation/ScrollStory.tsx
new file mode 100644
index 0000000..679dabb
--- /dev/null
+++ b/app/src/components/presentation/ScrollStory.tsx
@@ -0,0 +1,62 @@
+import { useCallback, useMemo } from 'react';
+import { useFullscreen } from '../../hooks/useFullscreen';
+import { useKeyboardNav } from '../../hooks/useKeyboardNav';
+import { useScrollNavigation } from '../../hooks/useScrollNavigation';
+import { ScrollSection } from './ScrollSection';
+import { SideProgressNav } from './SideProgressNav';
+import { FloatingControls } from './FloatingControls';
+import { AppendixDivider } from './AppendixDivider';
+import { exportPptx } from '../../lib/exportPptx';
+import type { ScrollSection as ScrollSectionType } from '../../lib/types';
+
+interface ScrollStoryProps {
+ sections: ScrollSectionType[];
+}
+
+export function ScrollStory({ sections }: ScrollStoryProps) {
+ const { isFullscreen, toggleFullscreen } = useFullscreen();
+
+ const sectionIds = useMemo(() => sections.map((s) => s.id), [sections]);
+ const { activeSection, scrollToSection } = useScrollNavigation(sectionIds);
+
+ const handleDownload = useCallback(() => {
+ exportPptx();
+ }, []);
+
+ useKeyboardNav({
+ onFullscreen: toggleFullscreen,
+ onDownload: handleDownload,
+ });
+
+ const firstAppendixIndex = sections.findIndex((s) => s.isAppendix);
+
+ return (
+
+
+
+ {sections.map((section, i) => (
+
+ {i === firstAppendixIndex &&
}
+ {i > 0 && i !== firstAppendixIndex && (
+
+ )}
+
+
+ ))}
+
+
+
+ );
+}
diff --git a/app/src/components/presentation/SideProgressNav.tsx b/app/src/components/presentation/SideProgressNav.tsx
new file mode 100644
index 0000000..2bdb100
--- /dev/null
+++ b/app/src/components/presentation/SideProgressNav.tsx
@@ -0,0 +1,35 @@
+import type { ScrollSection } from '../../lib/types';
+
+interface SideProgressNavProps {
+ sections: ScrollSection[];
+ activeSection: number;
+ onNavigate: (id: string) => void;
+}
+
+export function SideProgressNav({ sections, activeSection, onNavigate }: SideProgressNavProps) {
+ return (
+
+ {sections.map((section, i) => (
+ onNavigate(section.id)}
+ className="relative flex items-center gap-3"
+ title={section.title}
+ >
+
+
+ {section.title}
+
+
+ ))}
+
+ );
+}
diff --git a/app/src/components/slides/AhaMomentSlide.tsx b/app/src/components/slides/AhaMomentSlide.tsx
new file mode 100644
index 0000000..811bc56
--- /dev/null
+++ b/app/src/components/slides/AhaMomentSlide.tsx
@@ -0,0 +1,57 @@
+import type { SlideProps } from '../../lib/types';
+import { colors } from '../../lib/colors';
+import { SlideHeader } from '../ui/SlideHeader';
+import { orgs } from '../../data/orgs';
+
+export function AhaMomentSlide(_props: SlideProps) {
+ return (
+
+
+
+
+ Interviews revealed that serving everyone from one organization creates governance, funding, and mission conflicts.
+
+
+
+
+
+
PolicyEngine
+
(Unified)
+
+
+
+
+ {'\u2192'}
+
+
+
+ {orgs.map((org, i) => (
+
+
+
{org.name}
+
"{org.tagline}"
+
{org.entity}
+
{org.description}
+
+ ))}
+
+
+
+ );
+}
diff --git a/app/src/components/slides/AssumptionsSlide.tsx b/app/src/components/slides/AssumptionsSlide.tsx
new file mode 100644
index 0000000..6ccf3ca
--- /dev/null
+++ b/app/src/components/slides/AssumptionsSlide.tsx
@@ -0,0 +1,41 @@
+import type { SlideProps } from '../../lib/types';
+import { colors } from '../../lib/colors';
+import { SlideHeader } from '../ui/SlideHeader';
+import { assumptions } from '../../data/assumptions';
+
+export function AssumptionsSlide(_props: SlideProps) {
+ return (
+
+
+
+
+ {assumptions.map((a, i) => (
+
+
+
+
{a.status}
+
{a.title}
+
{a.learning}
+
+
+
{a.quote}
+
{a.source}
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/app/src/components/slides/CloseSlide.tsx b/app/src/components/slides/CloseSlide.tsx
new file mode 100644
index 0000000..1fe1040
--- /dev/null
+++ b/app/src/components/slides/CloseSlide.tsx
@@ -0,0 +1,65 @@
+import type { SlideProps } from '../../lib/types';
+import { colors } from '../../lib/colors';
+
+const closeOrgs = [
+ { name: 'Rules Foundation', tagline: 'Encode the law', color: colors.rulesBlue },
+ { name: 'Cosilico', tagline: 'Run the infrastructure', color: colors.cosilicoCyan },
+ { name: 'PolicyEngine', tagline: 'Tell the story', color: colors.peTeal },
+];
+
+export function CloseSlide(_props: SlideProps) {
+ return (
+
+
+
+
+ One ecosystem became three.
+
+
+ Each stronger for it.
+
+
+
+
+
+ {closeOrgs.map((org, i) => (
+
+
+
{org.name}
+
{org.tagline}
+
+ ))}
+
+
+
+
+ Looking for: foundation partners · agency pilot programs · AI lab collaborations
+
+
+
+
+ pose-ecosystem.vercel.app | github.com/PolicyEngine/pose | policyengine.org
+
+
+ );
+}
diff --git a/app/src/components/slides/EcosystemSlide.tsx b/app/src/components/slides/EcosystemSlide.tsx
new file mode 100644
index 0000000..b4fdc39
--- /dev/null
+++ b/app/src/components/slides/EcosystemSlide.tsx
@@ -0,0 +1,58 @@
+import type { SlideProps } from '../../lib/types';
+import { colors } from '../../lib/colors';
+import { SlideHeader } from '../ui/SlideHeader';
+
+interface EcosystemSlideProps extends SlideProps {
+ orgIndex: number;
+}
+
+const ecosystemOrgs = [
+ {
+ tag: 'ECOSYSTEM 1 OF 3',
+ title: 'Rules Foundation \u2014 "Encode the law"',
+ color: colors.rulesBlue,
+ image: '/assets/ecosystem/rules-foundation.png',
+ },
+ {
+ tag: 'ECOSYSTEM 2 OF 3',
+ title: 'Cosilico \u2014 "Run the infrastructure"',
+ color: colors.cosilicoCyan,
+ image: '/assets/ecosystem/cosilico.png',
+ },
+ {
+ tag: 'ECOSYSTEM 3 OF 3',
+ title: 'PolicyEngine \u2014 "Tell the story"',
+ color: colors.peTeal,
+ image: '/assets/ecosystem/policyengine.png',
+ },
+];
+
+export function EcosystemSlide({ orgIndex }: EcosystemSlideProps) {
+ const org = ecosystemOrgs[orgIndex];
+
+ return (
+
+
+
+
+
+
+ );
+}
+
+export function RulesEcosystemSlide(props: SlideProps) {
+ return ;
+}
+
+export function CosilicoEcosystemSlide(props: SlideProps) {
+ return ;
+}
+
+export function PEEcosystemSlide(props: SlideProps) {
+ return ;
+}
diff --git a/app/src/components/slides/GovernanceSlide.tsx b/app/src/components/slides/GovernanceSlide.tsx
new file mode 100644
index 0000000..ada1c8a
--- /dev/null
+++ b/app/src/components/slides/GovernanceSlide.tsx
@@ -0,0 +1,54 @@
+import type { SlideProps } from '../../lib/types';
+import { colors } from '../../lib/colors';
+import { SlideHeader } from '../ui/SlideHeader';
+import { Card } from '../ui/Card';
+import { governanceBefore, governanceAfter, governanceQuote, governanceNextSteps } from '../../data/governance';
+
+export function GovernanceSlide(_props: SlideProps) {
+ return (
+
+
+
+
+
+ Before
+ {governanceBefore.title}
+ {governanceBefore.items.map((item, i) => (
+ {'\u2022'} {item}
+ ))}
+
+
+
+ {'\u2192'}
+
+
+
+ After (informed by 100+ interviews)
+ {governanceAfter.title}
+ {governanceAfter.items.map((item, i) => (
+ {'\u2022'} {item.text}
+ ))}
+
+
+
+
+
+ {governanceQuote.label}
+ {governanceQuote.quote}
+ {governanceQuote.source}
+ {governanceQuote.detail}
+
+
+
+ Next Steps
+ {governanceNextSteps.map((step, i) => (
+ {'\u2022'} {step}
+ ))}
+
+
+
+ );
+}
diff --git a/app/src/components/slides/ImpactSlide.tsx b/app/src/components/slides/ImpactSlide.tsx
new file mode 100644
index 0000000..701c46d
--- /dev/null
+++ b/app/src/components/slides/ImpactSlide.tsx
@@ -0,0 +1,29 @@
+import type { SlideProps } from '../../lib/types';
+import { colors } from '../../lib/colors';
+import { SlideHeader } from '../ui/SlideHeader';
+import { StatCard } from '../ui/StatCard';
+import { stats, trustedBy } from '../../data/impact';
+
+export function ImpactSlide(_props: SlideProps) {
+ return (
+
+
+
+
+ {stats.map((stat, i) => (
+
+ ))}
+
+
+
+
Trusted by:
+
+ {trustedBy.line1}
+
+
+ {trustedBy.line2}
+
+
+
+ );
+}
diff --git a/app/src/components/slides/InterviewLogSlide.tsx b/app/src/components/slides/InterviewLogSlide.tsx
new file mode 100644
index 0000000..636b794
--- /dev/null
+++ b/app/src/components/slides/InterviewLogSlide.tsx
@@ -0,0 +1,55 @@
+import type { SlideProps } from '../../lib/types';
+import { colors } from '../../lib/colors';
+import { SlideHeader } from '../ui/SlideHeader';
+import { ProgressBar } from '../ui/ProgressBar';
+import { weeklyProgress, segments } from '../../data/interviews';
+
+export function InterviewLogSlide(_props: SlideProps) {
+ const maxSeg = Math.max(...segments.map(s => s.count));
+
+ return (
+
+
+
+
+
+
+ 100+ interviews
+
+
+ across 12 segments in 7 weeks
+
+
+
Weekly progress:
+
+ {weeklyProgress.map((w, i) => (
+
+ ))}
+
+
+
+
+ {segments.map((seg, i) => (
+
+ ))}
+
+
+
+ );
+}
diff --git a/app/src/components/slides/SustainabilitySlide.tsx b/app/src/components/slides/SustainabilitySlide.tsx
new file mode 100644
index 0000000..1cfe536
--- /dev/null
+++ b/app/src/components/slides/SustainabilitySlide.tsx
@@ -0,0 +1,41 @@
+import type { SlideProps } from '../../lib/types';
+import { colors } from '../../lib/colors';
+import { SlideHeader } from '../ui/SlideHeader';
+import { OrgBadge } from '../ui/OrgBadge';
+import { sustainability } from '../../data/sustainability';
+
+export function SustainabilitySlide(_props: SlideProps) {
+ return (
+
+
+
+
+ {sustainability.map((org, i) => (
+
+
+
+
{org.name}
+
+
{org.budget}
+
+ {org.items.map((item, j) => (
+
{'\u2022'} {item}
+ ))}
+
+
+ ))}
+
+
+ );
+}
diff --git a/app/src/components/slides/TeamSlide.tsx b/app/src/components/slides/TeamSlide.tsx
new file mode 100644
index 0000000..b32b577
--- /dev/null
+++ b/app/src/components/slides/TeamSlide.tsx
@@ -0,0 +1,40 @@
+import type { SlideProps } from '../../lib/types';
+import { team } from '../../data/team';
+import { SlideHeader } from '../ui/SlideHeader';
+import { colors } from '../../lib/colors';
+
+export function TeamSlide(_props: SlideProps) {
+ return (
+
+
+
+
+ {team.map((member, i) => (
+
+
+
+ {member.name}
+
+
+ {member.role}
+
+
+ {member.bio.map((line, j) => (
+
{line}
+ ))}
+
+
+ ))}
+
+
+ );
+}
diff --git a/app/src/components/slides/ThesisSlide.tsx b/app/src/components/slides/ThesisSlide.tsx
new file mode 100644
index 0000000..8e035be
--- /dev/null
+++ b/app/src/components/slides/ThesisSlide.tsx
@@ -0,0 +1,56 @@
+import type { SlideProps } from '../../lib/types';
+import { colors } from '../../lib/colors';
+import { SlideHeader } from '../ui/SlideHeader';
+
+const lines = [
+ { text: 'FOR economists, policy researchers, think tanks, journalists, advocates, and benefit access platforms', color: colors.textSecondary },
+ { text: 'WHO NEED TO understand taxes and benefits for households or analyze policy impacts on populations', color: colors.textPrimary, bold: true },
+ { text: 'THE STATUS QUO \u2014 proprietary microsimulation tools \u2014 FAILS DUE TO high cost, limited accessibility, and restrictions in government/secure environments', color: colors.accentOrange },
+ { text: 'CAUSING policy decisions without rigorous distributional analysis', color: colors.textPrimary, bold: true },
+ { text: 'WE WILL ESTABLISH three complementary organizations for open-source fiscal policy simulation:', color: colors.primary },
+];
+
+const orgLines = [
+ { text: '\u2022 Rules Foundation \u2014 Encode the law as open, auditable code', color: colors.rulesBlue },
+ { text: '\u2022 Cosilico \u2014 Run the production infrastructure as a public benefit corp', color: colors.cosilicoCyan },
+ { text: '\u2022 PolicyEngine \u2014 Tell the story through research and analysis', color: colors.peTeal },
+];
+
+export function ThesisSlide(_props: SlideProps) {
+ return (
+
+
+
+
+ {lines.map((line, i) => (
+
+ {line.text}
+
+ ))}
+
+
+ {orgLines.map((line, i) => (
+
+ {line.text}
+
+ ))}
+
+
+
+ TO DELIVER open models, web apps, and APIs {'\u2014'} democratizing access to sophisticated policy analysis
+
+
+
+ );
+}
diff --git a/app/src/components/slides/TimelineSlide.tsx b/app/src/components/slides/TimelineSlide.tsx
new file mode 100644
index 0000000..19e9e1b
--- /dev/null
+++ b/app/src/components/slides/TimelineSlide.tsx
@@ -0,0 +1,48 @@
+import type { SlideProps } from '../../lib/types';
+import { colors } from '../../lib/colors';
+import { SlideHeader } from '../ui/SlideHeader';
+import { milestones } from '../../data/timeline';
+
+export function TimelineSlide(_props: SlideProps) {
+ return (
+
+
+
+
+
+
+
+ {milestones.map((m, i) => (
+
+
{m.period}
+
{m.label}
+
+
+ {m.description.map((d, j) => (
+
{d}
+ ))}
+
+
+ ))}
+
+
+
+
+ Impact goals evolved: from "1 bill cites PolicyEngine" {'\u2192'} 3 org-specific milestones (see appendix)
+
+
+ );
+}
diff --git a/app/src/components/slides/TitleSlide.tsx b/app/src/components/slides/TitleSlide.tsx
new file mode 100644
index 0000000..968b5b2
--- /dev/null
+++ b/app/src/components/slides/TitleSlide.tsx
@@ -0,0 +1,50 @@
+import type { SlideProps } from '../../lib/types';
+import { colors } from '../../lib/colors';
+
+export function TitleSlide(_props: SlideProps) {
+ return (
+
+
+
+
+ One Ecosystem Became Three
+
+
+
+ How 100+ interviews reshaped our open-source strategy
+
+
+
+
+
+ NSF POSE | Award #4373 | Winter 2026
+
+
+
+ {[colors.rulesBlue, colors.cosilicoCyan, colors.peTeal].map((c, i) => (
+
+ ))}
+
+
+
+ pose-ecosystem.vercel.app
+
+
+ );
+}
diff --git a/app/src/components/slides/appendix/CanvasDetailSlide.tsx b/app/src/components/slides/appendix/CanvasDetailSlide.tsx
new file mode 100644
index 0000000..4d2ab32
--- /dev/null
+++ b/app/src/components/slides/appendix/CanvasDetailSlide.tsx
@@ -0,0 +1,38 @@
+import type { SlideProps } from '../../../lib/types';
+import { colors } from '../../../lib/colors';
+import { SlideHeader } from '../../ui/SlideHeader';
+import { canvasMembers } from '../../../data/appendix';
+
+export function CanvasDetailSlide(_props: SlideProps) {
+ return (
+
+
+
+
+
+
+ {canvasMembers.community.title}
+
+ {canvasMembers.community.items.map((item, i) => (
+
{'\u2022'} {item}
+ ))}
+
+
+
+ {canvasMembers.stakeholders.title}
+
+ {canvasMembers.stakeholders.items.map((item, i) => (
+
{'\u2022'} {item}
+ ))}
+
+
+ {canvasMembers.valueProps.title}
+
+ {canvasMembers.valueProps.items.map((item, i) => (
+
{'\u2022'} {item}
+ ))}
+
+
+
+ );
+}
diff --git a/app/src/components/slides/appendix/CanvasSlide.tsx b/app/src/components/slides/appendix/CanvasSlide.tsx
new file mode 100644
index 0000000..aae12da
--- /dev/null
+++ b/app/src/components/slides/appendix/CanvasSlide.tsx
@@ -0,0 +1,23 @@
+import type { SlideProps } from '../../../lib/types';
+import { colors } from '../../../lib/colors';
+import { SlideHeader } from '../../ui/SlideHeader';
+
+export function CanvasSlide(_props: SlideProps) {
+ return (
+
+
+
+
{
+ (e.target as HTMLImageElement).style.display = 'none';
+ }}
+ />
+
Canvas image (see extracted_images/)
+
+
+ );
+}
diff --git a/app/src/components/slides/appendix/CompetitiveSlide.tsx b/app/src/components/slides/appendix/CompetitiveSlide.tsx
new file mode 100644
index 0000000..883670d
--- /dev/null
+++ b/app/src/components/slides/appendix/CompetitiveSlide.tsx
@@ -0,0 +1,37 @@
+import type { SlideProps } from '../../../lib/types';
+import { colors } from '../../../lib/colors';
+import { SlideHeader } from '../../ui/SlideHeader';
+import { competitors } from '../../../data/appendix';
+
+export function CompetitiveSlide(_props: SlideProps) {
+ return (
+
+
+
+
+ Key gap: No one combines income tax + benefits + prediction + simulation in a single API
+
+
+
+ {competitors.map((c, i) => (
+
+ {c.name}
+ {c.metric}
+ {c.focus}
+
+ ))}
+
+
+
+ GPT-4 achieves only 67% accuracy on SARA tax benchmark {'\u2014'} deterministic tools always win on statutory precision
+
+
+ );
+}
diff --git a/app/src/components/slides/appendix/GovernanceDetailSlide.tsx b/app/src/components/slides/appendix/GovernanceDetailSlide.tsx
new file mode 100644
index 0000000..ef5601a
--- /dev/null
+++ b/app/src/components/slides/appendix/GovernanceDetailSlide.tsx
@@ -0,0 +1,40 @@
+import type { SlideProps } from '../../../lib/types';
+import { colors } from '../../../lib/colors';
+import { SlideHeader } from '../../ui/SlideHeader';
+import { OrgBadge } from '../../ui/OrgBadge';
+import { governanceDetail } from '../../../data/governance';
+
+export function GovernanceDetailSlide(_props: SlideProps) {
+ return (
+
+
+
+
+ {governanceDetail.map((org, i) => (
+
+
+
+
{org.name}
+
+
+ {org.details.map((d, j) => (
+
{d}
+ ))}
+
+
+ ))}
+
+
+ );
+}
diff --git a/app/src/components/slides/appendix/HighlightsSlide.tsx b/app/src/components/slides/appendix/HighlightsSlide.tsx
new file mode 100644
index 0000000..fc1709d
--- /dev/null
+++ b/app/src/components/slides/appendix/HighlightsSlide.tsx
@@ -0,0 +1,24 @@
+import type { SlideProps } from '../../../lib/types';
+import { colors } from '../../../lib/colors';
+import { SlideHeader } from '../../ui/SlideHeader';
+import { interviewHighlights } from '../../../data/appendix';
+
+export function HighlightsSlide(_props: SlideProps) {
+ return (
+
+
+
+
+ {interviewHighlights.map((h, i) => (
+
+ {h.name}: {h.insight}
+
+ ))}
+
+
+ );
+}
diff --git a/app/src/components/slides/appendix/ImpactGoalsSlide.tsx b/app/src/components/slides/appendix/ImpactGoalsSlide.tsx
new file mode 100644
index 0000000..2b58278
--- /dev/null
+++ b/app/src/components/slides/appendix/ImpactGoalsSlide.tsx
@@ -0,0 +1,44 @@
+import type { SlideProps } from '../../../lib/types';
+import { colors } from '../../../lib/colors';
+import { SlideHeader } from '../../ui/SlideHeader';
+import { Card } from '../../ui/Card';
+import { OrgBadge } from '../../ui/OrgBadge';
+import { impactGoalsEvolution, impactGoals } from '../../../data/appendix';
+
+export function ImpactGoalsSlide(_props: SlideProps) {
+ return (
+
+
+
+
+
+
WEEK 2
+
{impactGoalsEvolution.week2}
+
+
+
WEEK 3
+
{impactGoalsEvolution.week3}
+
+
+
+
{'\u2193'}
+
+
+ NOW
+
+
+
+ {impactGoals.map((goal, i) => (
+
+
+ {goal.condition}
+ {goal.impact}
+
+ ))}
+
+
+ );
+}
diff --git a/app/src/components/slides/appendix/MarketSlide.tsx b/app/src/components/slides/appendix/MarketSlide.tsx
new file mode 100644
index 0000000..151f6ac
--- /dev/null
+++ b/app/src/components/slides/appendix/MarketSlide.tsx
@@ -0,0 +1,28 @@
+import type { SlideProps } from '../../../lib/types';
+import { colors } from '../../../lib/colors';
+import { SlideHeader } from '../../ui/SlideHeader';
+import { markets } from '../../../data/appendix';
+
+export function MarketSlide(_props: SlideProps) {
+ return (
+
+
+
+
+ {markets.map((m, i) => (
+
+
{m.name}
+
TAM: {m.tam}
+
+ ))}
+
+
+ );
+}
diff --git a/app/src/components/slides/appendix/PartnersSlide.tsx b/app/src/components/slides/appendix/PartnersSlide.tsx
new file mode 100644
index 0000000..20f600e
--- /dev/null
+++ b/app/src/components/slides/appendix/PartnersSlide.tsx
@@ -0,0 +1,41 @@
+import type { SlideProps } from '../../../lib/types';
+import { colors } from '../../../lib/colors';
+import { SlideHeader } from '../../ui/SlideHeader';
+import { partners } from '../../../data/appendix';
+
+export function PartnersSlide(_props: SlideProps) {
+ return (
+
+
+
+
+ {partners.map((p, i) => (
+
+
{p.name}
+
{p.orgs}
+
{p.type}
+
+
What's in it for them:
+ {p.value.map((v, j) => (
+
{v}
+ ))}
+
+
Risk:
+
{p.risk}
+
+ ))}
+
+
+ );
+}
diff --git a/app/src/components/slides/appendix/VoicesSlide.tsx b/app/src/components/slides/appendix/VoicesSlide.tsx
new file mode 100644
index 0000000..07f721c
--- /dev/null
+++ b/app/src/components/slides/appendix/VoicesSlide.tsx
@@ -0,0 +1,18 @@
+import type { SlideProps } from '../../../lib/types';
+import { colors } from '../../../lib/colors';
+import { SlideHeader } from '../../ui/SlideHeader';
+import { QuoteCard } from '../../ui/QuoteCard';
+import { voicesQuotes } from '../../../data/appendix';
+
+export function VoicesSlide(_props: SlideProps) {
+ return (
+
+
+
+ {voicesQuotes.map((q, i) => (
+
+ ))}
+
+
+ );
+}
diff --git a/app/src/components/ui/AccentLine.tsx b/app/src/components/ui/AccentLine.tsx
new file mode 100644
index 0000000..72743ed
--- /dev/null
+++ b/app/src/components/ui/AccentLine.tsx
@@ -0,0 +1,8 @@
+interface AccentLineProps {
+ color: string;
+ width?: string;
+}
+
+export function AccentLine({ color, width = 'w-16' }: AccentLineProps) {
+ return
;
+}
diff --git a/app/src/components/ui/Card.tsx b/app/src/components/ui/Card.tsx
new file mode 100644
index 0000000..cf0e4b7
--- /dev/null
+++ b/app/src/components/ui/Card.tsx
@@ -0,0 +1,23 @@
+import type { ReactNode } from 'react';
+
+interface CardProps {
+ children: ReactNode;
+ borderColor?: string;
+ className?: string;
+ delay?: number;
+}
+
+export function Card({ children, borderColor, className = '', delay = 0 }: CardProps) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/app/src/components/ui/OrgBadge.tsx b/app/src/components/ui/OrgBadge.tsx
new file mode 100644
index 0000000..0b7dee0
--- /dev/null
+++ b/app/src/components/ui/OrgBadge.tsx
@@ -0,0 +1,13 @@
+interface OrgBadgeProps {
+ color: string;
+ size?: number;
+}
+
+export function OrgBadge({ color, size = 10 }: OrgBadgeProps) {
+ return (
+
+ );
+}
diff --git a/app/src/components/ui/ProgressBar.tsx b/app/src/components/ui/ProgressBar.tsx
new file mode 100644
index 0000000..c7f54fd
--- /dev/null
+++ b/app/src/components/ui/ProgressBar.tsx
@@ -0,0 +1,28 @@
+interface ProgressBarProps {
+ value: number;
+ max: number;
+ color: string;
+ label: string;
+ count: string | number;
+ delay?: number;
+}
+
+export function ProgressBar({ value, max, color, label, count, delay = 0 }: ProgressBarProps) {
+ return (
+
+ );
+}
diff --git a/app/src/components/ui/QuoteCard.tsx b/app/src/components/ui/QuoteCard.tsx
new file mode 100644
index 0000000..2acab9f
--- /dev/null
+++ b/app/src/components/ui/QuoteCard.tsx
@@ -0,0 +1,28 @@
+interface QuoteCardProps {
+ quote: string;
+ name: string;
+ title: string;
+ color: string;
+ delay?: number;
+}
+
+export function QuoteCard({ quote, name, title, color, delay = 0 }: QuoteCardProps) {
+ return (
+
+
{'\u201C'}
+
{quote}
+
{'\u2014'} {name}
+
{title}
+
+ );
+}
diff --git a/app/src/components/ui/SlideHeader.tsx b/app/src/components/ui/SlideHeader.tsx
new file mode 100644
index 0000000..271fb7e
--- /dev/null
+++ b/app/src/components/ui/SlideHeader.tsx
@@ -0,0 +1,32 @@
+import { AccentLine } from './AccentLine';
+
+interface SlideHeaderProps {
+ tag: string;
+ tagColor: string;
+ title?: string;
+ isAppendix?: boolean;
+}
+
+export function SlideHeader({ tag, tagColor, title, isAppendix }: SlideHeaderProps) {
+ return (
+
+ {isAppendix && (
+
+ Appendix
+
+ )}
+
+ {tag}
+
+
+ {title && (
+
+ {title}
+
+ )}
+
+ );
+}
diff --git a/app/src/components/ui/StatCard.tsx b/app/src/components/ui/StatCard.tsx
new file mode 100644
index 0000000..898adbb
--- /dev/null
+++ b/app/src/components/ui/StatCard.tsx
@@ -0,0 +1,25 @@
+interface StatCardProps {
+ number: string;
+ label: string;
+ color: string;
+ delay?: number;
+}
+
+export function StatCard({ number, label, color, delay = 0 }: StatCardProps) {
+ return (
+
+ {number}
+ {label}
+
+ );
+}
diff --git a/app/src/data/appendix.ts b/app/src/data/appendix.ts
new file mode 100644
index 0000000..3baa3dd
--- /dev/null
+++ b/app/src/data/appendix.ts
@@ -0,0 +1,158 @@
+import type { Quote, Partner, Competitor, MarketSegment, ImpactGoal, InterviewHighlight } from '../lib/types';
+import { colors } from '../lib/colors';
+
+export const voicesQuotes: Quote[] = [
+ {
+ text: 'Institutions like the Fed face strong IT/security barriers to external APIs \u2014 installable, low-dependency tools fit much better than cloud services.',
+ name: 'Jacob Walker',
+ title: 'Sr. Research Analyst, Atlanta Fed',
+ color: colors.rulesBlue,
+ },
+ {
+ text: 'PolicyEngine-style tools are ready for deployment; the blocker is institutional slowness, not technology.',
+ name: 'Martin Perron',
+ title: 'Rules as Code, Canadian Digital Services',
+ color: colors.cosilicoCyan,
+ },
+ {
+ text: 'Programs and tax rules in silos create severe unintended consequences \u2014 cliffs, penalties. Modeling these is influencing legislators.',
+ name: 'Ray Packer',
+ title: 'Georgia Center for Opportunity',
+ color: colors.peTeal,
+ },
+ {
+ text: 'Data and rules complexity create big gaps where better microsim tools and infrastructure are still missing.',
+ name: 'Jack Landry',
+ title: 'Jane Family Institute',
+ color: colors.accentOrange,
+ },
+];
+
+export const impactGoalsEvolution = {
+ week2: 'If this 1 Senate Bill cites PolicyEngine \u2192 unlock direct government contracting',
+ week3: 'If 10 congressional bills cite PolicyEngine \u2192 public deserves open policy estimates',
+};
+
+export const impactGoals: ImpactGoal[] = [
+ {
+ name: 'Rules Foundation',
+ color: colors.rulesBlue,
+ condition: 'If one AI lab evaluates its models against Rules Foundation benchmarks',
+ impact: 'It will provide society a shared, verifiable standard for legal code interpretation',
+ },
+ {
+ name: 'Cosilico',
+ color: colors.cosilicoCyan,
+ condition: 'If one state agency replaces a proprietary vendor with Cosilico Rules',
+ impact: 'It will prove that government will invest in open-source rules infrastructure',
+ },
+ {
+ name: 'PolicyEngine',
+ color: colors.peTeal,
+ condition: 'If 20 researchers use PolicyEngine in published papers',
+ impact: 'It will prove that open-source tools can replace proprietary licenses in policy research',
+ },
+];
+
+export const partners: Partner[] = [
+ {
+ name: 'AI Labs',
+ orgs: 'Anthropic, OpenAI, Google DeepMind',
+ type: 'Tech Development + Funding',
+ value: ['Verifiable ground truth for RLVR training'],
+ risk: 'Labs may build policy reasoning internally',
+ color: colors.accentBlue,
+ },
+ {
+ name: 'Policy Foundations',
+ orgs: 'Arnold Ventures, Pritzker',
+ type: 'Funding + Community Support',
+ value: ['Higher-quality policy research', 'Full transparency', 'One grant funds infra used by many orgs'],
+ risk: 'Foundation priorities shift with leadership cycles',
+ color: colors.accentGreen,
+ },
+ {
+ name: 'Major Think Tanks',
+ orgs: 'Brookings, CRFB, Niskanen, Urban',
+ type: 'Distribution + Funding',
+ value: ['Expert modeling without internal capacity', 'Auditable methodology for publications', 'Fast turnaround'],
+ risk: 'Could build in-house from open-source',
+ color: colors.accentPurple,
+ },
+];
+
+export const canvasMembers = {
+ community: {
+ title: 'Community Members (56 interviews)',
+ color: colors.accentBlue,
+ items: [
+ 'PE Team: 10 | Build core models',
+ 'Academic Researchers: 18 | Empirical questions',
+ 'Government Economists: 7 | Validate estimates',
+ 'Think Tank Analysts: 12 | Policy reports',
+ 'OSS Contributors: 4 | Code, fix bugs',
+ 'Data Journalists: 5 | Fact-check, interactives',
+ ],
+ },
+ stakeholders: {
+ title: 'Other Stakeholders (44 interviews)',
+ color: colors.accentTeal,
+ items: [
+ 'AI Labs: 10 | AI + policy research',
+ 'Funders: 10 | Fund development',
+ 'Non-Users: 8 | Understand barriers',
+ 'Gov Standards Bodies: 7 | Interoperability',
+ 'Policy Advocates: 6 | Shape narrative',
+ 'Competitors: 3 | Ecosystem mapping',
+ ],
+ },
+ valueProps: {
+ title: 'Value Propositions',
+ color: colors.accentGreen,
+ items: [
+ 'Transparency: Audit every calculation',
+ 'Speed: Seconds vs. weeks',
+ 'Cost: Free vs. $10K+ licenses',
+ 'Integration: API for existing workflows',
+ 'Credibility: Validated vs. IRS, SSA, CBO',
+ ],
+ },
+};
+
+export const competitors: Competitor[] = [
+ { name: 'Column Tax', metric: '$26.8M raised', focus: 'Filing, not calculation' },
+ { name: 'Symmetry', metric: '64M+ employees/yr', focus: 'Payroll tax only' },
+ { name: 'Benefit Kitchen', metric: '7 states', focus: '18 programs, healthcare focus' },
+ { name: 'Avalara', metric: 'Acquired $8.4B', focus: 'Sales tax only' },
+ { name: 'IMPLAN', metric: 'Acquired $100M+', focus: 'I-O multipliers, no household rules' },
+];
+
+export const markets: MarketSegment[] = [
+ { name: 'State Revenue Depts', tam: '$1B+' },
+ { name: 'Benefits Agencies', tam: '$500M+' },
+ { name: 'Tax Software Vendors', tam: '$90B+' },
+ { name: 'Financial Planners', tam: '$5B+' },
+ { name: 'Banks & Lenders', tam: '$100B+' },
+ { name: 'Insurance/Actuaries', tam: '$50B+' },
+ { name: 'AI Labs', tam: 'Strategic' },
+ { name: 'AI Agent Builders', tam: '$10B+' },
+ { name: 'Marketing/Data', tam: '$2.4B+' },
+ { name: 'Economic Analysts', tam: '$50-100M+' },
+ { name: 'Quant Finance', tam: '$500B+' },
+ { name: 'VC/Impact', tam: 'Growing' },
+];
+
+export const interviewHighlights: InterviewHighlight[] = [
+ { name: 'Nikhil Woodruff, CTO, PE', insight: 'Speed + open source + prototyping; encoding fast but review/debugging bottleneck' },
+ { name: 'Jason Morris, Thomson Reuters', insight: 'Most leverage is upstream: getting legislative drafters to author executable rules early' },
+ { name: 'Jacob Walker, Atlanta Fed', insight: 'Fed faces IT/security barriers to external APIs; installable tools fit better' },
+ { name: 'Martin Perron, Canadian Digital Services', insight: 'PE-style tools ready for deployment; blocker is institutional slowness' },
+ { name: 'Ray Packer, GA Center for Opportunity', insight: 'Programs in silos create cliffs/penalties; modeling these influences legislators' },
+ { name: 'Paul Huntsberger, Amplifi', insight: 'DMN-style rule engines were overkill; PE needs faster staged responses' },
+ { name: 'Andrew Lautz, BPC', insight: 'Fast open tools especially valuable vs. slow official scores; state-level data priority' },
+ { name: 'Kavya Vaghul, Living Wage Calculator', insight: 'Users want more granular local data; demand for \'thriving wage\' concept' },
+ { name: 'John Ricco, Yale Budget Lab', insight: 'Strong demand for AI research; humans no longer writing code; tariffs + childcare focus' },
+ { name: 'Alejandro Basalo, MSNBC', insight: 'Timing and momentum matter; household examples anchor reporting' },
+ { name: 'Jack Landry, Jane Family Institute', insight: 'Custom microsims for deep accuracy; PE for quick first-pass analyses' },
+ { name: 'Thomas Cintra, Outtake', insight: 'AI compresses dev cycles; ship to learn, not to perfect' },
+];
diff --git a/app/src/data/assumptions.ts b/app/src/data/assumptions.ts
new file mode 100644
index 0000000..4a85e84
--- /dev/null
+++ b/app/src/data/assumptions.ts
@@ -0,0 +1,41 @@
+import type { Assumption } from '../lib/types';
+import { colors } from '../lib/colors';
+
+export const assumptions: Assumption[] = [
+ {
+ status: '\u2713 CONFIRMED',
+ statusType: 'confirmed',
+ title: 'Researchers adopt OSS\nif accessible',
+ learning: 'But they also need validation against official sources before they\'ll cite it.',
+ quote: '\u201CFast, open tools are especially valuable for quick turnaround vs. slow official scores.\u201D',
+ source: '\u2014 Andrew Lautz, BPC',
+ color: colors.accentGreen,
+ },
+ {
+ status: '\u2713 CONFIRMED',
+ statusType: 'confirmed',
+ title: 'Funders value transparency\nenough to fund OSS',
+ learning: 'One grant funds infrastructure used by multiple orgs \u2014 leverage argument works.',
+ quote: '\u201CThink tanks want auditable methodology they can cite in publications.\u201D',
+ source: '\u2014 Think tank interviewees',
+ color: colors.accentGreen,
+ },
+ {
+ status: '\u2261 PARTIALLY',
+ statusType: 'partial',
+ title: 'Developers contribute for\npolicy impact alone',
+ learning: 'They also need portfolio value, learning opportunities, and community.',
+ quote: '\u201CAI is transforming developer onboarding, enabling faster ramp-up.\u201D',
+ source: '\u2014 Anthony Volk, PolicyEngine',
+ color: colors.accentOrange,
+ },
+ {
+ status: '\u2717 REJECTED',
+ statusType: 'rejected',
+ title: 'One organization can\nserve all segments',
+ learning: 'Infrastructure, standards, and research need different governance and funding.',
+ quote: '\u201CYou want this fresh start with clean governance from day one.\u201D',
+ source: '\u2014 Foundation governance advisor',
+ color: colors.highlight,
+ },
+];
diff --git a/app/src/data/ecosystem.ts b/app/src/data/ecosystem.ts
new file mode 100644
index 0000000..ef0c81e
--- /dev/null
+++ b/app/src/data/ecosystem.ts
@@ -0,0 +1,91 @@
+import type { EcosystemNode, EcosystemEdge } from '../lib/types';
+import { colors } from '../lib/colors';
+
+// Ring radii (used in the SVG viewBox 1800x900)
+export const RING_RADII = [0, 200, 330, 440, 540];
+export const RING_LABELS = ['Core', 'Direct Users', 'Channel Partners', 'Capabilities', 'Revenue'];
+export const CENTER = { x: 900, y: 470 };
+
+export const ecosystemNodes: EcosystemNode[] = [
+ // Ring 0: Core orgs
+ { id: 'pe-unified', label: 'PolicyEngine', ring: 0, angle: 270, color: colors.peTeal, org: 'all', description: 'Unified organization', visibleAtStep: 1 },
+ { id: 'rules', label: 'Rules\nFoundation', ring: 0, angle: 150, color: colors.rulesBlue, org: 'rules', description: '501(c)(3) - Encode the law', visibleAtStep: 2 },
+ { id: 'cosilico', label: 'Cosilico', ring: 0, angle: 270, color: colors.cosilicoCyan, org: 'cosilico', description: 'PBC - Run the infrastructure', visibleAtStep: 2 },
+ { id: 'pe', label: 'PolicyEngine', ring: 0, angle: 30, color: colors.peTeal, org: 'pe', description: '501(c)(3) - Tell the story', visibleAtStep: 2 },
+
+ // Ring 1: Direct users (7 nodes, spread ~51° apart)
+ { id: 'researchers', label: 'Academic\nResearchers', ring: 1, angle: 355, color: colors.accentBlue, org: 'pe', count: 18, description: 'Empirical policy questions', visibleAtStep: 1 },
+ { id: 'think-tanks', label: 'Think Tank\nAnalysts', ring: 1, angle: 50, color: colors.accentTeal, org: 'pe', count: 12, description: 'Policy reports & analysis', visibleAtStep: 1 },
+ { id: 'gov-econ', label: 'Government\nEconomists', ring: 1, angle: 105, color: colors.accentGreen, org: 'rules', count: 7, description: 'Validate estimates', visibleAtStep: 1 },
+ { id: 'journalists', label: 'Data\nJournalists', ring: 1, angle: 155, color: colors.lightGray, org: 'pe', count: 5, description: 'Fact-check & interactives', visibleAtStep: 1 },
+ { id: 'advocates', label: 'Policy\nAdvocates', ring: 1, angle: 205, color: colors.accentPurple, org: 'pe', count: 6, description: 'Shape policy narrative', visibleAtStep: 1 },
+ { id: 'contributors', label: 'OSS\nContributors', ring: 1, angle: 255, color: colors.accentGreen, org: 'rules', count: 4, description: 'Code & fix bugs', visibleAtStep: 1 },
+ { id: 'pe-team', label: 'PE Team', ring: 1, angle: 310, color: colors.peTeal, org: 'all', count: 10, description: 'Build core models', visibleAtStep: 1 },
+
+ // Ring 2: Channel partners (5 nodes, spread ~72° apart)
+ { id: 'ai-labs', label: 'AI Labs', ring: 2, angle: 20, color: colors.cosilicoCyan, org: 'cosilico', count: 10, description: 'AI + policy research', visibleAtStep: 1 },
+ { id: 'gov-standards', label: 'Gov Standards\nBodies', ring: 2, angle: 92, color: colors.rulesBlue, org: 'rules', count: 7, description: 'Interoperability', visibleAtStep: 1 },
+ { id: 'funders', label: 'Funders', ring: 2, angle: 164, color: colors.accentOrange, org: 'all', count: 10, description: 'Fund development', visibleAtStep: 1 },
+ { id: 'non-users', label: 'Non-Users', ring: 2, angle: 236, color: colors.highlight, org: 'all', count: 8, description: 'Understand barriers', visibleAtStep: 1 },
+ { id: 'competitors', label: 'Competitors', ring: 2, angle: 308, color: colors.dimText, org: 'all', count: 3, description: 'Ecosystem mapping', visibleAtStep: 1 },
+
+ // Ring 3: Capabilities (6 nodes, spread ~60° apart)
+ { id: 'tax-calc', label: 'Tax\nCalculation', ring: 3, angle: 5, color: colors.cosilicoCyan, org: 'cosilico', description: 'API endpoints', visibleAtStep: 3 },
+ { id: 'benefit-sim', label: 'Benefit\nSimulation', ring: 3, angle: 65, color: colors.cosilicoCyan, org: 'cosilico', description: 'Household analysis', visibleAtStep: 3 },
+ { id: 'law-encoding', label: 'Law\nEncoding', ring: 3, angle: 125, color: colors.rulesBlue, org: 'rules', description: 'Open statute code', visibleAtStep: 3 },
+ { id: 'research-tools', label: 'Research\nTools', ring: 3, angle: 185, color: colors.peTeal, org: 'pe', description: 'Analysis platform', visibleAtStep: 3 },
+ { id: 'ai-training', label: 'AI Training\nData', ring: 3, angle: 245, color: colors.cosilicoCyan, org: 'cosilico', description: 'RLVR benchmarks', visibleAtStep: 3 },
+ { id: 'data-enrichment', label: 'Data\nEnrichment', ring: 3, angle: 305, color: colors.cosilicoCyan, org: 'cosilico', description: '$0.10-1.00/record', visibleAtStep: 3 },
+
+ // Ring 4: Revenue (6 nodes, spread ~60° apart)
+ { id: 'state-rev', label: 'State Revenue\nDepts', ring: 4, angle: 35, color: colors.cosilicoCyan, org: 'cosilico', description: '$1B+ TAM', visibleAtStep: 3 },
+ { id: 'tax-software', label: 'Tax Software\nVendors', ring: 4, angle: 95, color: colors.cosilicoCyan, org: 'cosilico', description: '$90B+ TAM', visibleAtStep: 3 },
+ { id: 'fin-planners', label: 'Financial\nPlanners', ring: 4, angle: 155, color: colors.cosilicoCyan, org: 'cosilico', description: '$5B+ TAM', visibleAtStep: 3 },
+ { id: 'enterprise', label: 'Enterprise\nClients', ring: 4, angle: 215, color: colors.cosilicoCyan, org: 'cosilico', description: '$100K-1M+/year', visibleAtStep: 3 },
+ { id: 'nsf-grants', label: 'NSF &\nGrants', ring: 4, angle: 275, color: colors.accentOrange, org: 'all', description: 'Government funding', visibleAtStep: 3 },
+ { id: 'foundation-grants', label: 'Foundation\nGrants', ring: 4, angle: 335, color: colors.accentOrange, org: 'pe', description: 'Arnold, Pritzker, etc.', visibleAtStep: 3 },
+];
+
+export const ecosystemEdges: EcosystemEdge[] = [
+ // Step 1: unified relationships (PE-unified → ring 1-2)
+ { from: 'pe-unified', to: 'researchers', label: 'serves', color: colors.accentBlue, type: 'solid', visibleAtStep: 1 },
+ { from: 'pe-unified', to: 'think-tanks', label: 'serves', color: colors.accentTeal, type: 'solid', visibleAtStep: 1 },
+ { from: 'pe-unified', to: 'gov-econ', label: 'serves', color: colors.accentGreen, type: 'solid', visibleAtStep: 1 },
+ { from: 'pe-unified', to: 'journalists', color: colors.lightGray, type: 'solid', visibleAtStep: 1 },
+ { from: 'pe-unified', to: 'advocates', color: colors.accentPurple, type: 'solid', visibleAtStep: 1 },
+ { from: 'pe-unified', to: 'ai-labs', label: 'partners', color: colors.cosilicoCyan, type: 'dashed', visibleAtStep: 1 },
+ { from: 'funders', to: 'pe-unified', label: 'funds', color: colors.accentOrange, type: 'solid', visibleAtStep: 1 },
+ { from: 'contributors', to: 'pe-unified', label: 'contributes', color: colors.accentGreen, type: 'solid', visibleAtStep: 1 },
+
+ // Step 3: split relationships (each org → their nodes)
+ { from: 'rules', to: 'gov-standards', label: 'standards', color: colors.rulesBlue, type: 'solid', visibleAtStep: 3 },
+ { from: 'rules', to: 'contributors', label: 'open code', color: colors.rulesBlue, type: 'solid', visibleAtStep: 3 },
+ { from: 'rules', to: 'gov-econ', label: 'validates', color: colors.rulesBlue, type: 'solid', visibleAtStep: 3 },
+ { from: 'rules', to: 'law-encoding', color: colors.rulesBlue, type: 'solid', visibleAtStep: 3 },
+ { from: 'cosilico', to: 'ai-labs', label: 'API', color: colors.cosilicoCyan, type: 'solid', visibleAtStep: 3 },
+ { from: 'cosilico', to: 'tax-calc', color: colors.cosilicoCyan, type: 'solid', visibleAtStep: 3 },
+ { from: 'cosilico', to: 'benefit-sim', color: colors.cosilicoCyan, type: 'solid', visibleAtStep: 3 },
+ { from: 'cosilico', to: 'ai-training', color: colors.cosilicoCyan, type: 'solid', visibleAtStep: 3 },
+ { from: 'cosilico', to: 'data-enrichment', color: colors.cosilicoCyan, type: 'solid', visibleAtStep: 3 },
+ { from: 'cosilico', to: 'state-rev', color: colors.cosilicoCyan, type: 'dashed', visibleAtStep: 3 },
+ { from: 'cosilico', to: 'tax-software', color: colors.cosilicoCyan, type: 'dashed', visibleAtStep: 3 },
+ { from: 'cosilico', to: 'fin-planners', color: colors.cosilicoCyan, type: 'dashed', visibleAtStep: 3 },
+ { from: 'cosilico', to: 'enterprise', color: colors.cosilicoCyan, type: 'dashed', visibleAtStep: 3 },
+ { from: 'pe', to: 'researchers', label: 'research', color: colors.peTeal, type: 'solid', visibleAtStep: 3 },
+ { from: 'pe', to: 'think-tanks', label: 'analysis', color: colors.peTeal, type: 'solid', visibleAtStep: 3 },
+ { from: 'pe', to: 'journalists', color: colors.peTeal, type: 'solid', visibleAtStep: 3 },
+ { from: 'pe', to: 'advocates', color: colors.peTeal, type: 'solid', visibleAtStep: 3 },
+ { from: 'pe', to: 'research-tools', color: colors.peTeal, type: 'solid', visibleAtStep: 3 },
+ { from: 'pe', to: 'foundation-grants', color: colors.accentOrange, type: 'dashed', visibleAtStep: 3 },
+ { from: 'nsf-grants', to: 'rules', color: colors.accentOrange, type: 'dashed', visibleAtStep: 3 },
+ { from: 'nsf-grants', to: 'pe', color: colors.accentOrange, type: 'dashed', visibleAtStep: 3 },
+ // Inter-org connections
+ { from: 'rules', to: 'cosilico', label: 'code feeds\ninfra', color: colors.highlight, type: 'solid', visibleAtStep: 3 },
+ { from: 'cosilico', to: 'pe', label: 'API powers\nresearch', color: colors.highlight, type: 'solid', visibleAtStep: 3 },
+];
+
+export const EVOLUTION_STEPS = [
+ { step: 1, title: 'Unified Ecosystem', description: 'PolicyEngine serves all user segments as one organization' },
+ { step: 2, title: 'The Split', description: 'Three specialized organizations with distinct missions' },
+ { step: 3, title: 'Full Ecosystem', description: 'Three orgs with capabilities and revenue streams' },
+];
diff --git a/app/src/data/governance.ts b/app/src/data/governance.ts
new file mode 100644
index 0000000..89e2e5d
--- /dev/null
+++ b/app/src/data/governance.ts
@@ -0,0 +1,67 @@
+import type { GovernanceOrg } from '../lib/types';
+import { colors } from '../lib/colors';
+
+export const governanceBefore = {
+ title: 'BDFL model',
+ items: [
+ 'Founder makes all strategic decisions',
+ 'Single 501(c)(3) owns everything',
+ 'AGPL-3.0 license, informal governance',
+ ],
+};
+
+export const governanceAfter = {
+ title: 'Three orgs, tailored governance',
+ items: [
+ { text: 'Rules Foundation: multi-stakeholder 501(c)(3)', color: colors.rulesBlue },
+ { text: 'Cosilico: Public Benefit Corp, board mandate', color: colors.cosilicoCyan },
+ { text: 'PolicyEngine: 501(c)(3) + advisory board', color: colors.peTeal },
+ ],
+};
+
+export const governanceQuote = {
+ label: 'WHAT INTERVIEWS TOLD US',
+ quote: '\u201CFresh entity strongly recommended \u2014 you want this fresh start with clean governance from day one.\u201D',
+ source: '\u2014 Foundation governance advisor',
+ detail: 'Jason Morris, Martin Perron, and foundation advisors all pointed to separation of concerns.',
+};
+
+export const governanceNextSteps = [
+ 'Incorporate Rules Foundation as fresh 501(c)(3)',
+ 'Register Cosilico as Public Benefit Corp',
+ 'Recruit advisory board from interview network',
+ 'Open RFC process for governance docs',
+];
+
+export const governanceDetail: GovernanceOrg[] = [
+ {
+ name: 'Rules Foundation',
+ color: colors.rulesBlue,
+ details: [
+ 'Multi-stakeholder 501(c)(3)',
+ 'Technical steering committee + encoding standards board',
+ 'Partisan neutrality · Mandatory statute citations · Multi-reviewer validation',
+ 'Historical versioning of all encodings',
+ ],
+ },
+ {
+ name: 'Cosilico',
+ color: colors.cosilicoCyan,
+ details: [
+ 'Public Benefit Corp (mission-locked by charter)',
+ 'Board with public benefit mandate',
+ 'Open-source core (Apache 2.0) · Enterprise services layer',
+ 'Certified partner program (Salesforce model)',
+ ],
+ },
+ {
+ name: 'PolicyEngine',
+ color: colors.peTeal,
+ details: [
+ '501(c)(3) / UK Charity (AGPL licensed)',
+ 'Founder-led \u2192 Technical steering committee + Advisory board',
+ 'Contributor guidelines · Formal research partnership agreements',
+ 'Open roadmap with community input',
+ ],
+ },
+];
diff --git a/app/src/data/impact.ts b/app/src/data/impact.ts
new file mode 100644
index 0000000..668e03f
--- /dev/null
+++ b/app/src/data/impact.ts
@@ -0,0 +1,14 @@
+import type { StatItem } from '../lib/types';
+import { colors } from '../lib/colors';
+
+export const stats: StatItem[] = [
+ { number: '1M+', label: 'Simulations run', color: colors.accentBlue },
+ { number: '50+', label: 'State tax systems', color: colors.accentTeal },
+ { number: '100+', label: 'Benefit programs', color: colors.accentGreen },
+ { number: '50+', label: 'OSS contributors', color: colors.accentPurple },
+];
+
+export const trustedBy = {
+ line1: 'UK Government (budget scoring) · US Congress (distributional analysis)',
+ line2: 'Brookings · NBER · Atlanta Fed · CRFB · Niskanen Center · Yale Budget Lab · BPC',
+};
diff --git a/app/src/data/interviews.ts b/app/src/data/interviews.ts
new file mode 100644
index 0000000..813bc9f
--- /dev/null
+++ b/app/src/data/interviews.ts
@@ -0,0 +1,27 @@
+import type { WeekProgress, Segment } from '../lib/types';
+import { colors } from '../lib/colors';
+
+export const weeklyProgress: WeekProgress[] = [
+ { week: 'Week 1 (Jan 23)', count: 8 },
+ { week: 'Week 2 (Jan 30)', count: 28 },
+ { week: 'Week 3 (Feb 6)', count: 44 },
+ { week: 'Week 4 (Feb 13)', count: 62 },
+ { week: 'Week 5 (Feb 20)', count: 75 },
+ { week: 'Week 6 (Feb 27)', count: 87 },
+ { week: 'Final (Mar 6)', count: 100 },
+];
+
+export const segments: Segment[] = [
+ { name: 'Academic Researchers', count: 18, color: colors.accentBlue },
+ { name: 'Think Tank Analysts', count: 12, color: colors.accentTeal },
+ { name: 'AI Labs', count: 10, color: colors.cosilicoCyan },
+ { name: 'PE Team', count: 10, color: colors.peTeal },
+ { name: 'Funders', count: 10, color: colors.accentOrange },
+ { name: 'Non-Users', count: 8, color: colors.highlight },
+ { name: 'Gov Standards Bodies', count: 7, color: colors.rulesBlue },
+ { name: 'Government Economists', count: 7, color: colors.accentGreen },
+ { name: 'Policy Advocates', count: 6, color: colors.accentPurple },
+ { name: 'Data Journalists', count: 5, color: colors.lightGray },
+ { name: 'OSS Contributors', count: 4, color: colors.accentGreen },
+ { name: 'Competitors', count: 3, color: colors.dimText },
+];
diff --git a/app/src/data/orgs.ts b/app/src/data/orgs.ts
new file mode 100644
index 0000000..c0ca725
--- /dev/null
+++ b/app/src/data/orgs.ts
@@ -0,0 +1,26 @@
+import type { OrgInfo } from '../lib/types';
+import { colors } from '../lib/colors';
+
+export const orgs: OrgInfo[] = [
+ {
+ name: 'Rules\nFoundation',
+ tagline: 'Encode the law',
+ entity: '501(c)(3)',
+ description: 'Like OpenStreetMap\nfor law',
+ color: colors.rulesBlue,
+ },
+ {
+ name: 'Cosilico',
+ tagline: 'Run the infrastructure',
+ entity: 'Public Benefit Corp',
+ description: 'Society, in silico\n$250B+ TAM',
+ color: colors.cosilicoCyan,
+ },
+ {
+ name: 'PolicyEngine',
+ tagline: 'Tell the story',
+ entity: '501(c)(3) / Charity',
+ description: 'Like Urban/Mathematica\nbut open source',
+ color: colors.peTeal,
+ },
+];
diff --git a/app/src/data/sustainability.ts b/app/src/data/sustainability.ts
new file mode 100644
index 0000000..a086517
--- /dev/null
+++ b/app/src/data/sustainability.ts
@@ -0,0 +1,38 @@
+import type { SustainabilityOrg } from '../lib/types';
+import { colors } from '../lib/colors';
+
+export const sustainability: SustainabilityOrg[] = [
+ {
+ name: 'Rules Foundation',
+ color: colors.rulesBlue,
+ budget: '~$300K/year',
+ items: [
+ 'Government grants: 40%',
+ 'Foundation grants: 30%',
+ 'AI lab in-kind (compute): 20%',
+ 'Downstream contributions: 10%',
+ ],
+ },
+ {
+ name: 'Cosilico',
+ color: colors.cosilicoCyan,
+ budget: '$500K \u2192 $75M ARR (5yr)',
+ items: [
+ 'Open source (free, Apache 2.0)',
+ 'API: $0.001\u20130.01/call',
+ 'Data enrichment: $0.10\u20131.00/record',
+ 'Enterprise: $100K\u20131M+/year',
+ ],
+ },
+ {
+ name: 'PolicyEngine',
+ color: colors.peTeal,
+ budget: '~$500K/year',
+ items: [
+ 'Foundation grants: 60%',
+ 'Government grants (NSF): 20%',
+ 'Earned revenue: 20%',
+ 'Path to 40%+ earned revenue',
+ ],
+ },
+];
diff --git a/app/src/data/team.ts b/app/src/data/team.ts
new file mode 100644
index 0000000..66c28d0
--- /dev/null
+++ b/app/src/data/team.ts
@@ -0,0 +1,26 @@
+import type { TeamMember } from '../lib/types';
+import { colors } from '../lib/colors';
+
+export const team: TeamMember[] = [
+ {
+ name: 'Max Ghenis',
+ role: 'Co-Founder & CEO',
+ bio: ['MIT M.S. Development Economics', 'Former Google', 'Founded UBI Center'],
+ color: colors.accentBlue,
+ photo: '/assets/team/max.png',
+ },
+ {
+ name: 'Pavel Makarchuk',
+ role: 'Chief of Staff',
+ bio: ['Operations & strategy lead', 'Led development of US state-level', 'tax-benefit model'],
+ color: colors.accentTeal,
+ photo: '/assets/team/pavel.jpg',
+ },
+ {
+ name: 'Daniel Feenberg',
+ role: 'Advisor',
+ bio: ['Princeton Ph.D. Economics', 'Former IT Director at NBER', 'Created TAXSIM'],
+ color: colors.accentPurple,
+ photo: '/assets/team/daniel.jpg',
+ },
+];
diff --git a/app/src/data/timeline.ts b/app/src/data/timeline.ts
new file mode 100644
index 0000000..ea3f28d
--- /dev/null
+++ b/app/src/data/timeline.ts
@@ -0,0 +1,35 @@
+import type { Milestone } from '../lib/types';
+import { colors } from '../lib/colors';
+
+export const milestones: Milestone[] = [
+ {
+ period: 'Q1 2026',
+ label: 'NOW',
+ description: ['POSE complete', '100 interviews', '3-org structure defined'],
+ color: colors.highlight,
+ },
+ {
+ period: 'Q2-Q3 2026',
+ label: 'LAUNCH',
+ description: ['Rules Foundation incorporates', 'Cosilico beta launch', 'First certified partners'],
+ color: colors.accentBlue,
+ },
+ {
+ period: 'Q4 2026',
+ label: 'GROW',
+ description: ['First paying customers', 'First agency encodings', 'Research partnerships live'],
+ color: colors.accentTeal,
+ },
+ {
+ period: '2027',
+ label: 'SCALE',
+ description: ['Cosilico $3M ARR', '5+ agency partnerships', '10+ institutional partners'],
+ color: colors.accentGreen,
+ },
+ {
+ period: '2028',
+ label: 'SUSTAIN',
+ description: ['Cosilico $10M ARR', 'Self-sustaining operations', '40%+ earned revenue (PE)'],
+ color: colors.accentPurple,
+ },
+];
diff --git a/app/src/hooks/useAssignments.ts b/app/src/hooks/useAssignments.ts
deleted file mode 100644
index 12ce9f3..0000000
--- a/app/src/hooks/useAssignments.ts
+++ /dev/null
@@ -1,114 +0,0 @@
-import { useState, useEffect, useCallback, useMemo } from 'react'
-import type { Assignment, AssignmentStatus, AssignmentCategory } from '../types/database'
-import { POSE_ASSIGNMENTS } from '../types/database'
-
-const LOCAL_STORAGE_KEY = 'pose_assignments'
-
-export function useAssignments() {
- const [assignments, setAssignments] = useState([])
- const [loading, setLoading] = useState(true)
-
- // Load assignments from localStorage, merging with default assignments
- const loadAssignments = useCallback(() => {
- setLoading(true)
- try {
- const stored = localStorage.getItem(LOCAL_STORAGE_KEY)
- if (stored) {
- const storedAssignments: Assignment[] = JSON.parse(stored)
- // Merge stored statuses with default assignments
- // This ensures new assignments are added if the list is updated
- const merged = POSE_ASSIGNMENTS.map((defaultAssignment) => {
- const stored = storedAssignments.find((a) => a.id === defaultAssignment.id)
- return stored ? { ...defaultAssignment, status: stored.status } : defaultAssignment
- })
- setAssignments(merged)
- } else {
- setAssignments(POSE_ASSIGNMENTS)
- }
- } catch (err) {
- console.error('LocalStorage error:', err)
- setAssignments(POSE_ASSIGNMENTS)
- }
- setLoading(false)
- }, [])
-
- // Save assignments to localStorage
- const saveToLocalStorage = (data: Assignment[]) => {
- localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(data))
- }
-
- // Update assignment status
- const updateStatus = useCallback((id: string, status: AssignmentStatus) => {
- setAssignments((prev) => {
- const updated = prev.map((a) => (a.id === id ? { ...a, status } : a))
- saveToLocalStorage(updated)
- return updated
- })
- }, [])
-
- // Toggle assignment completion
- const toggleComplete = useCallback((id: string) => {
- setAssignments((prev) => {
- const assignment = prev.find((a) => a.id === id)
- if (!assignment) return prev
- const newStatus: AssignmentStatus = assignment.status === 'completed' ? 'pending' : 'completed'
- const updated = prev.map((a) => (a.id === id ? { ...a, status: newStatus } : a))
- saveToLocalStorage(updated)
- return updated
- })
- }, [])
-
- // Computed values
- const completedCount = useMemo(
- () => assignments.filter((a) => a.status === 'completed').length,
- [assignments]
- )
-
- const pendingCount = useMemo(
- () => assignments.filter((a) => a.status !== 'completed').length,
- [assignments]
- )
-
- const categoryCounts = useMemo(() => {
- const counts: Record = {
- prep: { total: 0, completed: 0 },
- presentation: { total: 0, completed: 0 },
- interview_milestone: { total: 0, completed: 0 },
- }
- assignments.forEach((a) => {
- counts[a.category].total++
- if (a.status === 'completed') {
- counts[a.category].completed++
- }
- })
- return counts
- }, [assignments])
-
- // Get assignments due soon (within 7 days)
- const getDueStatus = useCallback((dueDate: string): 'overdue' | 'due_soon' | 'upcoming' => {
- const now = new Date()
- const due = new Date(dueDate)
- const diffDays = Math.ceil((due.getTime() - now.getTime()) / (1000 * 60 * 60 * 24))
-
- if (diffDays < 0) return 'overdue'
- if (diffDays <= 3) return 'due_soon'
- return 'upcoming'
- }, [])
-
- useEffect(() => {
- loadAssignments()
- }, [loadAssignments])
-
- return {
- assignments,
- loading,
- updateStatus,
- toggleComplete,
- completedCount,
- pendingCount,
- categoryCounts,
- getDueStatus,
- refresh: loadAssignments,
- totalCount: assignments.length,
- }
-}
diff --git a/app/src/hooks/useFullscreen.ts b/app/src/hooks/useFullscreen.ts
new file mode 100644
index 0000000..0dfba8c
--- /dev/null
+++ b/app/src/hooks/useFullscreen.ts
@@ -0,0 +1,23 @@
+import { useState, useEffect, useCallback } from 'react';
+
+export function useFullscreen() {
+ const [isFullscreen, setIsFullscreen] = useState(false);
+
+ useEffect(() => {
+ const handler = () => {
+ setIsFullscreen(!!document.fullscreenElement);
+ };
+ document.addEventListener('fullscreenchange', handler);
+ return () => document.removeEventListener('fullscreenchange', handler);
+ }, []);
+
+ const toggleFullscreen = useCallback(() => {
+ if (document.fullscreenElement) {
+ document.exitFullscreen();
+ } else {
+ document.documentElement.requestFullscreen();
+ }
+ }, []);
+
+ return { isFullscreen, toggleFullscreen };
+}
diff --git a/app/src/hooks/useInterviews.test.ts b/app/src/hooks/useInterviews.test.ts
deleted file mode 100644
index 8a99a93..0000000
--- a/app/src/hooks/useInterviews.test.ts
+++ /dev/null
@@ -1,545 +0,0 @@
-import { describe, it, expect, vi, beforeEach } from 'vitest'
-import { renderHook, act, waitFor } from '@testing-library/react'
-import { useInterviews } from './useInterviews'
-import type { Interview } from '../types/database'
-
-// Mock the supabase module to ensure localStorage fallback
-vi.mock('../lib/supabase', () => ({
- supabase: null,
- isSupabaseConfigured: () => false,
-}))
-
-const LOCAL_STORAGE_KEY = 'pose_interviews'
-
-const createMockInterview = (
- overrides: Partial = {}
-): Omit => ({
- name: 'Test User',
- organization: 'Test Org',
- segment: 'user',
- status: 'completed',
- ...overrides,
-})
-
-describe('useInterviews', () => {
- beforeEach(() => {
- localStorage.clear()
- vi.clearAllMocks()
- })
-
- describe('initial state', () => {
- it('returns empty interviews array initially', async () => {
- const { result } = renderHook(() => useInterviews())
-
- await waitFor(() => {
- expect(result.current.loading).toBe(false)
- })
-
- expect(result.current.interviews).toEqual([])
- })
-
- it('sets loading to false after initialization', async () => {
- const { result } = renderHook(() => useInterviews())
- // Loading resolves to false after initial load completes
- await waitFor(() => {
- expect(result.current.loading).toBe(false)
- })
- })
-
- it('has no error initially', async () => {
- const { result } = renderHook(() => useInterviews())
-
- await waitFor(() => {
- expect(result.current.loading).toBe(false)
- })
-
- expect(result.current.error).toBeNull()
- })
- })
-
- describe('localStorage operations', () => {
- it('loads interviews from localStorage', async () => {
- const storedInterviews: Interview[] = [
- {
- id: 'int_1',
- name: 'Stored User',
- organization: 'Stored Org',
- segment: 'user',
- status: 'completed',
- created_at: '2024-01-01T00:00:00.000Z',
- updated_at: '2024-01-01T00:00:00.000Z',
- },
- ]
- localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(storedInterviews))
-
- const { result } = renderHook(() => useInterviews())
-
- await waitFor(() => {
- expect(result.current.loading).toBe(false)
- })
-
- expect(result.current.interviews).toHaveLength(1)
- expect(result.current.interviews[0].name).toBe('Stored User')
- })
-
- it('handles corrupted localStorage gracefully', async () => {
- localStorage.setItem(LOCAL_STORAGE_KEY, 'invalid json{')
-
- const { result } = renderHook(() => useInterviews())
-
- await waitFor(() => {
- expect(result.current.loading).toBe(false)
- })
-
- expect(result.current.interviews).toEqual([])
- })
- })
-
- describe('addInterview', () => {
- it('adds a new interview', async () => {
- const { result } = renderHook(() => useInterviews())
-
- await waitFor(() => {
- expect(result.current.loading).toBe(false)
- })
-
- const newInterview = createMockInterview()
-
- await act(async () => {
- await result.current.addInterview(newInterview)
- })
-
- expect(result.current.interviews).toHaveLength(1)
- expect(result.current.interviews[0].name).toBe('Test User')
- })
-
- it('generates unique id for new interview', async () => {
- const { result } = renderHook(() => useInterviews())
-
- await waitFor(() => {
- expect(result.current.loading).toBe(false)
- })
-
- await act(async () => {
- await result.current.addInterview(createMockInterview())
- })
-
- expect(result.current.interviews[0].id).toMatch(/^int_/)
- })
-
- it('sets created_at and updated_at timestamps', async () => {
- const { result } = renderHook(() => useInterviews())
-
- await waitFor(() => {
- expect(result.current.loading).toBe(false)
- })
-
- await act(async () => {
- await result.current.addInterview(createMockInterview())
- })
-
- const interview = result.current.interviews[0]
- expect(interview.created_at).toBeDefined()
- expect(interview.updated_at).toBeDefined()
- expect(interview.created_at).toBe(interview.updated_at)
- })
-
- it('saves to localStorage after adding', async () => {
- const { result } = renderHook(() => useInterviews())
-
- await waitFor(() => {
- expect(result.current.loading).toBe(false)
- })
-
- await act(async () => {
- await result.current.addInterview(createMockInterview())
- })
-
- const stored = localStorage.getItem(LOCAL_STORAGE_KEY)
- expect(stored).not.toBeNull()
- const parsed = JSON.parse(stored!)
- expect(parsed).toHaveLength(1)
- })
-
- it('prepends new interviews (newest first)', async () => {
- const { result } = renderHook(() => useInterviews())
-
- await waitFor(() => {
- expect(result.current.loading).toBe(false)
- })
-
- await act(async () => {
- await result.current.addInterview(
- createMockInterview({ name: 'First' })
- )
- })
-
- await act(async () => {
- await result.current.addInterview(
- createMockInterview({ name: 'Second' })
- )
- })
-
- expect(result.current.interviews[0].name).toBe('Second')
- expect(result.current.interviews[1].name).toBe('First')
- })
- })
-
- describe('updateInterview', () => {
- it('updates an existing interview', async () => {
- const storedInterviews: Interview[] = [
- {
- id: 'int_1',
- name: 'Original Name',
- organization: 'Org',
- segment: 'user',
- status: 'scheduled',
- created_at: '2024-01-01T00:00:00.000Z',
- updated_at: '2024-01-01T00:00:00.000Z',
- },
- ]
- localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(storedInterviews))
-
- const { result } = renderHook(() => useInterviews())
-
- await waitFor(() => {
- expect(result.current.loading).toBe(false)
- })
-
- await act(async () => {
- await result.current.updateInterview('int_1', { name: 'Updated Name' })
- })
-
- expect(result.current.interviews[0].name).toBe('Updated Name')
- })
-
- it('updates the updated_at timestamp', async () => {
- const originalDate = '2024-01-01T00:00:00.000Z'
- const storedInterviews: Interview[] = [
- {
- id: 'int_1',
- name: 'Test',
- organization: 'Org',
- segment: 'user',
- status: 'scheduled',
- created_at: originalDate,
- updated_at: originalDate,
- },
- ]
- localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(storedInterviews))
-
- const { result } = renderHook(() => useInterviews())
-
- await waitFor(() => {
- expect(result.current.loading).toBe(false)
- })
-
- await act(async () => {
- await result.current.updateInterview('int_1', {
- status: 'completed',
- })
- })
-
- expect(result.current.interviews[0].updated_at).not.toBe(originalDate)
- expect(result.current.interviews[0].created_at).toBe(originalDate)
- })
-
- it('saves updated interview to localStorage', async () => {
- const storedInterviews: Interview[] = [
- {
- id: 'int_1',
- name: 'Test',
- organization: 'Org',
- segment: 'user',
- status: 'scheduled',
- created_at: '2024-01-01T00:00:00.000Z',
- updated_at: '2024-01-01T00:00:00.000Z',
- },
- ]
- localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(storedInterviews))
-
- const { result } = renderHook(() => useInterviews())
-
- await waitFor(() => {
- expect(result.current.loading).toBe(false)
- })
-
- await act(async () => {
- await result.current.updateInterview('int_1', {
- status: 'completed',
- })
- })
-
- const stored = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY)!)
- expect(stored[0].status).toBe('completed')
- })
- })
-
- describe('deleteInterview', () => {
- it('removes an interview by id', async () => {
- const storedInterviews: Interview[] = [
- {
- id: 'int_1',
- name: 'To Delete',
- organization: 'Org',
- segment: 'user',
- status: 'completed',
- created_at: '2024-01-01T00:00:00.000Z',
- updated_at: '2024-01-01T00:00:00.000Z',
- },
- {
- id: 'int_2',
- name: 'To Keep',
- organization: 'Org',
- segment: 'user',
- status: 'completed',
- created_at: '2024-01-01T00:00:00.000Z',
- updated_at: '2024-01-01T00:00:00.000Z',
- },
- ]
- localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(storedInterviews))
-
- const { result } = renderHook(() => useInterviews())
-
- await waitFor(() => {
- expect(result.current.loading).toBe(false)
- })
-
- await act(async () => {
- await result.current.deleteInterview('int_1')
- })
-
- expect(result.current.interviews).toHaveLength(1)
- expect(result.current.interviews[0].name).toBe('To Keep')
- })
-
- it('returns true after successful deletion', async () => {
- const storedInterviews: Interview[] = [
- {
- id: 'int_1',
- name: 'To Delete',
- organization: 'Org',
- segment: 'user',
- status: 'completed',
- created_at: '2024-01-01T00:00:00.000Z',
- updated_at: '2024-01-01T00:00:00.000Z',
- },
- ]
- localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(storedInterviews))
-
- const { result } = renderHook(() => useInterviews())
-
- await waitFor(() => {
- expect(result.current.loading).toBe(false)
- })
-
- let deleteResult: boolean | undefined
- await act(async () => {
- deleteResult = await result.current.deleteInterview('int_1')
- })
-
- expect(deleteResult).toBe(true)
- })
-
- it('updates localStorage after deletion', async () => {
- const storedInterviews: Interview[] = [
- {
- id: 'int_1',
- name: 'To Delete',
- organization: 'Org',
- segment: 'user',
- status: 'completed',
- created_at: '2024-01-01T00:00:00.000Z',
- updated_at: '2024-01-01T00:00:00.000Z',
- },
- ]
- localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(storedInterviews))
-
- const { result } = renderHook(() => useInterviews())
-
- await waitFor(() => {
- expect(result.current.loading).toBe(false)
- })
-
- await act(async () => {
- await result.current.deleteInterview('int_1')
- })
-
- const stored = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY)!)
- expect(stored).toHaveLength(0)
- })
- })
-
- describe('computed values', () => {
- it('calculates completedCount correctly', async () => {
- const storedInterviews: Interview[] = [
- {
- id: 'int_1',
- name: 'Completed 1',
- organization: 'Org',
- segment: 'user',
- status: 'completed',
- created_at: '2024-01-01T00:00:00.000Z',
- updated_at: '2024-01-01T00:00:00.000Z',
- },
- {
- id: 'int_2',
- name: 'Scheduled 1',
- organization: 'Org',
- segment: 'user',
- status: 'scheduled',
- created_at: '2024-01-01T00:00:00.000Z',
- updated_at: '2024-01-01T00:00:00.000Z',
- },
- {
- id: 'int_3',
- name: 'Completed 2',
- organization: 'Org',
- segment: 'supporter',
- status: 'completed',
- created_at: '2024-01-01T00:00:00.000Z',
- updated_at: '2024-01-01T00:00:00.000Z',
- },
- ]
- localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(storedInterviews))
-
- const { result } = renderHook(() => useInterviews())
-
- await waitFor(() => {
- expect(result.current.loading).toBe(false)
- })
-
- expect(result.current.completedCount).toBe(2)
- })
-
- it('calculates scheduledCount correctly', async () => {
- const storedInterviews: Interview[] = [
- {
- id: 'int_1',
- name: 'Completed',
- organization: 'Org',
- segment: 'user',
- status: 'completed',
- created_at: '2024-01-01T00:00:00.000Z',
- updated_at: '2024-01-01T00:00:00.000Z',
- },
- {
- id: 'int_2',
- name: 'Scheduled 1',
- organization: 'Org',
- segment: 'user',
- status: 'scheduled',
- created_at: '2024-01-01T00:00:00.000Z',
- updated_at: '2024-01-01T00:00:00.000Z',
- },
- {
- id: 'int_3',
- name: 'Scheduled 2',
- organization: 'Org',
- segment: 'supporter',
- status: 'scheduled',
- created_at: '2024-01-01T00:00:00.000Z',
- updated_at: '2024-01-01T00:00:00.000Z',
- },
- ]
- localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(storedInterviews))
-
- const { result } = renderHook(() => useInterviews())
-
- await waitFor(() => {
- expect(result.current.loading).toBe(false)
- })
-
- expect(result.current.scheduledCount).toBe(2)
- })
-
- it('calculates segmentCounts correctly for completed interviews only', async () => {
- const storedInterviews: Interview[] = [
- {
- id: 'int_1',
- name: 'User 1',
- organization: 'Org',
- segment: 'user',
- status: 'completed',
- created_at: '2024-01-01T00:00:00.000Z',
- updated_at: '2024-01-01T00:00:00.000Z',
- },
- {
- id: 'int_2',
- name: 'User 2',
- organization: 'Org',
- segment: 'user',
- status: 'completed',
- created_at: '2024-01-01T00:00:00.000Z',
- updated_at: '2024-01-01T00:00:00.000Z',
- },
- {
- id: 'int_3',
- name: 'User Scheduled',
- organization: 'Org',
- segment: 'user',
- status: 'scheduled',
- created_at: '2024-01-01T00:00:00.000Z',
- updated_at: '2024-01-01T00:00:00.000Z',
- },
- {
- id: 'int_4',
- name: 'Supporter 1',
- organization: 'Org',
- segment: 'supporter',
- status: 'completed',
- created_at: '2024-01-01T00:00:00.000Z',
- updated_at: '2024-01-01T00:00:00.000Z',
- },
- ]
- localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(storedInterviews))
-
- const { result } = renderHook(() => useInterviews())
-
- await waitFor(() => {
- expect(result.current.loading).toBe(false)
- })
-
- expect(result.current.segmentCounts['user']).toBe(2)
- expect(result.current.segmentCounts['supporter']).toBe(1)
- expect(result.current.segmentCounts['contributor']).toBeUndefined()
- })
- })
-
- describe('refresh', () => {
- it('reloads data from localStorage', async () => {
- const { result } = renderHook(() => useInterviews())
-
- await waitFor(() => {
- expect(result.current.loading).toBe(false)
- })
-
- expect(result.current.interviews).toHaveLength(0)
-
- // Manually add to localStorage
- const newData: Interview[] = [
- {
- id: 'int_new',
- name: 'New Interview',
- organization: 'Org',
- segment: 'user',
- status: 'completed',
- created_at: '2024-01-01T00:00:00.000Z',
- updated_at: '2024-01-01T00:00:00.000Z',
- },
- ]
- localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(newData))
-
- await act(async () => {
- await result.current.refresh()
- })
-
- await waitFor(() => {
- expect(result.current.loading).toBe(false)
- })
-
- expect(result.current.interviews).toHaveLength(1)
- expect(result.current.interviews[0].name).toBe('New Interview')
- })
- })
-})
diff --git a/app/src/hooks/useInterviews.ts b/app/src/hooks/useInterviews.ts
deleted file mode 100644
index bacbf4e..0000000
--- a/app/src/hooks/useInterviews.ts
+++ /dev/null
@@ -1,169 +0,0 @@
-import { useState, useEffect, useCallback } from 'react'
-import { supabase, isSupabaseConfigured } from '../lib/supabase'
-import type { Interview, Segment } from '../types/database'
-
-const LOCAL_STORAGE_KEY = 'pose_interviews'
-
-// Generate a simple unique ID
-const generateId = () => `int_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
-
-export function useInterviews() {
- const [interviews, setInterviews] = useState([])
- const [loading, setLoading] = useState(true)
- const [error, setError] = useState(null)
-
- // Load interviews
- const loadInterviews = useCallback(async () => {
- setLoading(true)
- setError(null)
-
- if (isSupabaseConfigured() && supabase) {
- try {
- const { data, error: fetchError } = await supabase
- .from('pose_interviews')
- .select('*')
- .order('created_at', { ascending: false })
-
- if (fetchError) throw fetchError
- setInterviews(data || [])
- } catch (err) {
- console.error('Supabase fetch error:', err)
- setError('Failed to load from Supabase, using local storage')
- loadFromLocalStorage()
- }
- } else {
- loadFromLocalStorage()
- }
-
- setLoading(false)
- }, [])
-
- const loadFromLocalStorage = () => {
- try {
- const stored = localStorage.getItem(LOCAL_STORAGE_KEY)
- if (stored) {
- setInterviews(JSON.parse(stored))
- }
- } catch (err) {
- console.error('LocalStorage error:', err)
- setInterviews([])
- }
- }
-
- const saveToLocalStorage = (data: Interview[]) => {
- localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(data))
- }
-
- // Add interview
- const addInterview = async (interview: Omit) => {
- const now = new Date().toISOString()
- const newInterview: Interview = {
- ...interview,
- id: generateId(),
- created_at: now,
- updated_at: now,
- }
-
- if (isSupabaseConfigured() && supabase) {
- try {
- const { data, error: insertError } = await supabase
- .from('pose_interviews')
- .insert(newInterview)
- .select()
- .single()
-
- if (insertError) throw insertError
- setInterviews(prev => [data, ...prev])
- return data
- } catch (err) {
- console.error('Supabase insert error:', err)
- // Fall back to local storage
- }
- }
-
- const updated = [newInterview, ...interviews]
- setInterviews(updated)
- saveToLocalStorage(updated)
- return newInterview
- }
-
- // Update interview
- const updateInterview = async (id: string, updates: Partial) => {
- const now = new Date().toISOString()
-
- if (isSupabaseConfigured() && supabase) {
- try {
- const { data, error: updateError } = await supabase
- .from('pose_interviews')
- .update({ ...updates, updated_at: now })
- .eq('id', id)
- .select()
- .single()
-
- if (updateError) throw updateError
- setInterviews(prev => prev.map(i => i.id === id ? data : i))
- return data
- } catch (err) {
- console.error('Supabase update error:', err)
- }
- }
-
- const updated = interviews.map(i =>
- i.id === id ? { ...i, ...updates, updated_at: now } : i
- )
- setInterviews(updated)
- saveToLocalStorage(updated)
- return updated.find(i => i.id === id)
- }
-
- // Delete interview
- const deleteInterview = async (id: string) => {
- if (isSupabaseConfigured() && supabase) {
- try {
- const { error: deleteError } = await supabase
- .from('pose_interviews')
- .delete()
- .eq('id', id)
-
- if (deleteError) throw deleteError
- setInterviews(prev => prev.filter(i => i.id !== id))
- return true
- } catch (err) {
- console.error('Supabase delete error:', err)
- }
- }
-
- const updated = interviews.filter(i => i.id !== id)
- setInterviews(updated)
- saveToLocalStorage(updated)
- return true
- }
-
- // Computed values
- const completedCount = interviews.filter(i => i.status === 'completed').length
- const scheduledCount = interviews.filter(i => i.status === 'scheduled').length
-
- const segmentCounts = interviews
- .filter(i => i.status === 'completed')
- .reduce((acc, i) => {
- acc[i.segment] = (acc[i.segment] || 0) + 1
- return acc
- }, {} as Record)
-
- useEffect(() => {
- loadInterviews()
- }, [loadInterviews])
-
- return {
- interviews,
- loading,
- error,
- addInterview,
- updateInterview,
- deleteInterview,
- refresh: loadInterviews,
- completedCount,
- scheduledCount,
- segmentCounts,
- }
-}
diff --git a/app/src/hooks/useKeyboardNav.ts b/app/src/hooks/useKeyboardNav.ts
new file mode 100644
index 0000000..bd2fb5c
--- /dev/null
+++ b/app/src/hooks/useKeyboardNav.ts
@@ -0,0 +1,35 @@
+import { useEffect } from 'react';
+
+interface UseKeyboardNavProps {
+ onFullscreen: () => void;
+ onDownload: () => void;
+}
+
+export function useKeyboardNav({ onFullscreen, onDownload }: UseKeyboardNavProps) {
+ useEffect(() => {
+ const handler = (e: KeyboardEvent) => {
+ if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
+
+ switch (e.key) {
+ case 'f':
+ case 'F':
+ e.preventDefault();
+ onFullscreen();
+ break;
+ case 'd':
+ case 'D':
+ e.preventDefault();
+ onDownload();
+ break;
+ case 'Escape':
+ if (document.fullscreenElement) {
+ document.exitFullscreen();
+ }
+ break;
+ }
+ };
+
+ window.addEventListener('keydown', handler);
+ return () => window.removeEventListener('keydown', handler);
+ }, [onFullscreen, onDownload]);
+}
diff --git a/app/src/hooks/useScrollNavigation.ts b/app/src/hooks/useScrollNavigation.ts
new file mode 100644
index 0000000..637ec47
--- /dev/null
+++ b/app/src/hooks/useScrollNavigation.ts
@@ -0,0 +1,37 @@
+import { useState, useEffect, useCallback } from 'react';
+
+export function useScrollNavigation(sectionIds: string[]) {
+ const [activeSection, setActiveSection] = useState(0);
+
+ useEffect(() => {
+ const observers: IntersectionObserver[] = [];
+
+ sectionIds.forEach((id, index) => {
+ const el = document.getElementById(id);
+ if (!el) return;
+
+ const observer = new IntersectionObserver(
+ ([entry]) => {
+ if (entry.isIntersecting) {
+ setActiveSection(index);
+ }
+ },
+ { threshold: 0.15, rootMargin: '-10% 0px -60% 0px' }
+ );
+
+ observer.observe(el);
+ observers.push(observer);
+ });
+
+ return () => observers.forEach((o) => o.disconnect());
+ }, [sectionIds]);
+
+ const scrollToSection = useCallback((id: string) => {
+ const el = document.getElementById(id);
+ if (el) {
+ el.scrollIntoView({ behavior: 'smooth' });
+ }
+ }, []);
+
+ return { activeSection, scrollToSection };
+}
diff --git a/app/src/hooks/useScrollProgress.ts b/app/src/hooks/useScrollProgress.ts
new file mode 100644
index 0000000..abe3fd6
--- /dev/null
+++ b/app/src/hooks/useScrollProgress.ts
@@ -0,0 +1,38 @@
+import { useRef, useState, useEffect } from 'react';
+
+export function useScrollProgress(steps: number) {
+ const containerRef = useRef(null);
+ const [currentStep, setCurrentStep] = useState(1);
+
+ useEffect(() => {
+ const container = containerRef.current;
+ if (!container) return;
+
+ const handleScroll = () => {
+ const rect = container.getBoundingClientRect();
+ const containerHeight = container.offsetHeight;
+ const viewportHeight = window.innerHeight;
+
+ // How far through the container we've scrolled
+ // scrolled = 0 when top of container hits top of viewport
+ // scrolled = containerHeight - viewportHeight when bottom of container hits bottom of viewport
+ const scrolled = -rect.top;
+ const scrollableDistance = containerHeight - viewportHeight;
+
+ if (scrollableDistance <= 0) {
+ setCurrentStep(1);
+ return;
+ }
+
+ const progress = Math.max(0, Math.min(1, scrolled / scrollableDistance));
+ const step = Math.min(steps, Math.floor(progress * steps) + 1);
+ setCurrentStep(step);
+ };
+
+ window.addEventListener('scroll', handleScroll, { passive: true });
+ handleScroll();
+ return () => window.removeEventListener('scroll', handleScroll);
+ }, [steps]);
+
+ return { containerRef, currentStep };
+}
diff --git a/app/src/hooks/useScrollReveal.ts b/app/src/hooks/useScrollReveal.ts
new file mode 100644
index 0000000..12da71b
--- /dev/null
+++ b/app/src/hooks/useScrollReveal.ts
@@ -0,0 +1,26 @@
+import { useRef, useState, useEffect } from 'react';
+
+export function useScrollReveal(threshold = 0.15) {
+ const ref = useRef(null);
+ const [isVisible, setIsVisible] = useState(false);
+
+ useEffect(() => {
+ const el = ref.current;
+ if (!el) return;
+
+ const observer = new IntersectionObserver(
+ ([entry]) => {
+ if (entry.isIntersecting) {
+ setIsVisible(true);
+ observer.unobserve(el);
+ }
+ },
+ { threshold }
+ );
+
+ observer.observe(el);
+ return () => observer.disconnect();
+ }, [threshold]);
+
+ return { ref, isVisible };
+}
diff --git a/app/src/index.css b/app/src/index.css
index b6ed269..f5bec9c 100644
--- a/app/src/index.css
+++ b/app/src/index.css
@@ -1,35 +1,109 @@
-@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
+@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
+@import "tailwindcss";
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
+@theme {
+ --color-page-bg: #FFFFFF;
+ --color-card-bg: #F5F9FF;
+ --color-card-bg-alt: #F1F5F9;
+ --color-text-primary: #000000;
+ --color-text-secondary: #5A5A5A;
+ --color-text-tertiary: #9CA3AF;
+ --color-border-light: #E2E8F0;
+ --color-border-medium: #CBD5E1;
+ --color-primary: #319795;
+ --color-primary-light: rgba(49, 151, 149, 0.12);
+ --color-success: #22C55E;
+ --color-warning: #FEC601;
+ --color-error: #EF4444;
+ --color-rules-blue: #3B82F6;
+ --color-cosilico-cyan: #06B6D4;
+ --color-pe-teal: #319795;
+ --color-accent-purple: #7C3AED;
+ --color-accent-orange: #EA580C;
+ --color-highlight: #D97706;
+ --font-sans: 'Inter', system-ui, sans-serif;
-:root {
- --pe-teal-500: #319795;
- --pe-teal-400: #2dd4bf;
- --pe-teal-600: #0d9488;
- --pe-gray-700: #344054;
- --pe-gray-100: #f3f4f6;
+ --shadow-card: 0px 1px 3px rgba(16, 24, 40, 0.06), 0px 1px 2px rgba(16, 24, 40, 0.04);
+ --shadow-card-hover: 0px 4px 6px -1px rgba(16, 24, 40, 0.08), 0px 2px 4px -1px rgba(16, 24, 40, 0.04);
+ --shadow-float: 0px 10px 15px -3px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.04);
}
-body {
+html {
+ scroll-behavior: smooth;
+}
+
+html, body, #root {
+ margin: 0;
+ padding: 0;
+ background-color: #FFFFFF;
+ color: #000000;
font-family: 'Inter', system-ui, sans-serif;
- background-color: #f9fafb;
- color: var(--pe-gray-700);
+ -webkit-font-smoothing: antialiased;
+}
+
+/* Scroll-reveal transitions */
+.scroll-reveal {
+ opacity: 0;
+ transform: translateY(16px);
+ transition: opacity 0.5s cubic-bezier(0.16, 1, 0.3, 1), transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
+}
+
+.section-visible .scroll-reveal {
+ opacity: 1;
+ transform: translateY(0);
}
-/* Custom scrollbar */
+.scroll-reveal-left {
+ opacity: 0;
+ transform: translateX(-16px);
+ transition: opacity 0.5s cubic-bezier(0.16, 1, 0.3, 1), transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
+}
+
+.section-visible .scroll-reveal-left {
+ opacity: 1;
+ transform: translateX(0);
+}
+
+.scroll-reveal-scale {
+ opacity: 0;
+ transform: scale(0.85);
+ transition: opacity 0.5s cubic-bezier(0.16, 1, 0.3, 1), transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
+}
+
+.section-visible .scroll-reveal-scale {
+ opacity: 1;
+ transform: scale(1);
+}
+
+.scroll-reveal-width {
+ transform: scaleX(0);
+ transition: transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
+}
+
+.section-visible .scroll-reveal-width {
+ transform: scaleX(1);
+}
+
+/* Scrollbar */
::-webkit-scrollbar {
width: 6px;
- height: 6px;
}
::-webkit-scrollbar-track {
- background: var(--pe-gray-100);
+ background: #F1F5F9;
}
::-webkit-scrollbar-thumb {
- background: #d1d5db;
+ background: #CBD5E1;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
- background: #9ca3af;
+ background: #94A3B8;
+}
+
+/* Card hover lift */
+.card-hover {
+ transition: box-shadow 0.2s ease, transform 0.2s ease;
+}
+.card-hover:hover {
+ box-shadow: var(--shadow-card-hover);
+ transform: translateY(-1px);
}
diff --git a/app/src/lib/colors.ts b/app/src/lib/colors.ts
new file mode 100644
index 0000000..080499b
--- /dev/null
+++ b/app/src/lib/colors.ts
@@ -0,0 +1,47 @@
+export const colors = {
+ // Backgrounds (light theme)
+ pageBg: '#FFFFFF',
+ cardBg: '#F5F9FF',
+ cardBgAlt: '#F1F5F9',
+
+ // Text
+ textPrimary: '#000000',
+ textSecondary: '#5A5A5A',
+ textTertiary: '#9CA3AF',
+
+ // Borders
+ borderLight: '#E2E8F0',
+ borderMedium: '#CBD5E1',
+
+ // Primary accent (PolicyEngine teal)
+ primary: '#319795',
+ primaryLight: '#319795CC',
+
+ // Semantic
+ success: '#22C55E',
+ warning: '#FEC601',
+ error: '#EF4444',
+ info: '#1890FF',
+
+ // Organization colors
+ rulesBlue: '#3B82F6',
+ cosilicoCyan: '#06B6D4',
+ peTeal: '#319795',
+
+ // Chart / accent
+ accentPurple: '#7C3AED',
+ accentOrange: '#EA580C',
+ highlight: '#D97706',
+
+ // Legacy aliases for data files that reference these
+ white: '#FFFFFF',
+ accentBlue: '#319795',
+ accentTeal: '#2DD4BF',
+ accentGreen: '#22C55E',
+ lightGray: '#5A5A5A',
+ dimText: '#9CA3AF',
+ darkBg: '#FFFFFF',
+ cardBgLegacy: '#F5F9FF',
+} as const;
+
+export type ColorKey = keyof typeof colors;
diff --git a/app/src/lib/exportPptx.ts b/app/src/lib/exportPptx.ts
new file mode 100644
index 0000000..15d4aad
--- /dev/null
+++ b/app/src/lib/exportPptx.ts
@@ -0,0 +1,345 @@
+import PptxGenJS from 'pptxgenjs';
+import { colors } from './colors';
+import { team } from '../data/team';
+import { stats, trustedBy } from '../data/impact';
+import { weeklyProgress, segments } from '../data/interviews';
+import { assumptions } from '../data/assumptions';
+import { orgs } from '../data/orgs';
+import { governanceBefore, governanceAfter } from '../data/governance';
+import { sustainability } from '../data/sustainability';
+import { milestones } from '../data/timeline';
+import { voicesQuotes, impactGoalsEvolution, impactGoals, partners, canvasMembers, competitors, markets, interviewHighlights } from '../data/appendix';
+import { governanceDetail } from '../data/governance';
+
+const BG = 'FFFFFF';
+const CARD = 'F5F9FF';
+const TEXT_PRIMARY = '000000';
+const TEXT_SECONDARY = '5A5A5A';
+const TEXT_TERTIARY = '9CA3AF';
+const BORDER = 'E2E8F0';
+
+function hexToRgb(hex: string) {
+ return hex.replace('#', '');
+}
+
+function addSlideHeader(slide: PptxGenJS.Slide, tag: string, tagColor: string, title?: string, isAppendix = false) {
+ if (isAppendix) {
+ slide.addText('APPENDIX', { x: 0.8, y: 0.3, w: 5, h: 0.3, fontSize: 10, color: TEXT_TERTIARY, fontFace: 'Calibri' });
+ }
+ const yTag = 0.5;
+ slide.addText(tag, { x: 0.8, y: yTag, w: 5, h: 0.4, fontSize: 12, color: hexToRgb(tagColor), bold: true, fontFace: 'Calibri' });
+ slide.addShape('rect', { x: 0.8, y: yTag + 0.4, w: 2, h: 0.02, fill: { color: hexToRgb(tagColor) } });
+ if (title) {
+ slide.addText(title, { x: 0.8, y: yTag + 0.6, w: 11, h: 0.6, fontSize: 28, color: TEXT_PRIMARY, bold: true, fontFace: 'Calibri' });
+ }
+}
+
+export function exportPptx() {
+ const pptx = new PptxGenJS();
+ pptx.layout = 'LAYOUT_16x9';
+
+ // ---- SLIDE 1: TITLE ----
+ const s1 = pptx.addSlide();
+ s1.background = { fill: BG };
+ s1.addShape('rect', { x: 0, y: 0, w: '100%', h: 0.03, fill: { color: hexToRgb(colors.primary) } });
+ s1.addText('One Ecosystem Became Three', { x: 0.5, y: 1.2, w: 9, h: 1, fontSize: 44, color: TEXT_PRIMARY, bold: true, align: 'center', fontFace: 'Calibri' });
+ s1.addText('How 100+ interviews reshaped our open-source strategy', { x: 0.5, y: 2.3, w: 9, h: 0.6, fontSize: 20, color: hexToRgb(colors.primary), align: 'center', fontFace: 'Calibri' });
+ s1.addText('NSF POSE | Award #4373 | Winter 2026', { x: 0.5, y: 3.5, w: 9, h: 0.4, fontSize: 14, color: TEXT_TERTIARY, align: 'center', fontFace: 'Calibri' });
+ [colors.rulesBlue, colors.cosilicoCyan, colors.peTeal].forEach((c, i) => {
+ s1.addShape('ellipse', { x: 3.8 + i * 0.6, y: 4.2, w: 0.35, h: 0.35, fill: { color: hexToRgb(c) } });
+ });
+ s1.addText('pose-ecosystem.vercel.app', { x: 0.5, y: 5, w: 9, h: 0.3, fontSize: 11, color: TEXT_TERTIARY, align: 'center', fontFace: 'Calibri' });
+
+ // ---- SLIDE 2: TEAM ----
+ const s2 = pptx.addSlide();
+ s2.background = { fill: BG };
+ addSlideHeader(s2, 'THE TEAM', colors.primary, 'PolicyEngine POSE Team');
+ team.forEach((m, i) => {
+ const left = 0.8 + i * 3.2;
+ s2.addText(m.name, { x: left, y: 2.2, w: 2.8, h: 0.4, fontSize: 18, color: TEXT_PRIMARY, bold: true, align: 'center', fontFace: 'Calibri' });
+ s2.addText(m.role, { x: left, y: 2.6, w: 2.8, h: 0.3, fontSize: 12, color: hexToRgb(m.color), bold: true, align: 'center', fontFace: 'Calibri' });
+ s2.addText(m.bio.join('\n'), { x: left, y: 3, w: 2.8, h: 1, fontSize: 10, color: TEXT_SECONDARY, align: 'center', fontFace: 'Calibri' });
+ });
+
+ // ---- SLIDE 3: THESIS ----
+ const s3 = pptx.addSlide();
+ s3.background = { fill: BG };
+ addSlideHeader(s3, 'THESIS', colors.accentOrange);
+ const thesisLines = [
+ { text: 'FOR economists, policy researchers, think tanks, journalists, advocates, and benefit access platforms', color: TEXT_SECONDARY },
+ { text: 'WHO NEED TO understand taxes and benefits for households or analyze policy impacts on populations', color: TEXT_PRIMARY, bold: true },
+ { text: 'THE STATUS QUO \u2014 proprietary microsimulation tools \u2014 FAILS DUE TO high cost, limited accessibility, and restrictions', color: colors.accentOrange },
+ { text: 'CAUSING policy decisions without rigorous distributional analysis', color: TEXT_PRIMARY, bold: true },
+ { text: 'WE WILL ESTABLISH three complementary organizations:', color: colors.primary },
+ { text: '\u2022 Rules Foundation \u2014 Encode the law as open, auditable code', color: colors.rulesBlue },
+ { text: '\u2022 Cosilico \u2014 Run the production infrastructure as a public benefit corp', color: colors.cosilicoCyan },
+ { text: '\u2022 PolicyEngine \u2014 Tell the story through research and analysis', color: colors.peTeal },
+ { text: 'TO DELIVER open models, web apps, and APIs', color: TEXT_PRIMARY },
+ ];
+ thesisLines.forEach((line, i) => {
+ s3.addText(line.text, { x: 0.8, y: 1.3 + i * 0.45, w: 8.5, h: 0.4, fontSize: 13, color: typeof line.color === 'string' && line.color.startsWith('#') ? hexToRgb(line.color) : line.color, bold: line.bold, fontFace: 'Calibri' });
+ });
+
+ // ---- SLIDE 4: IMPACT ----
+ const s4 = pptx.addSlide();
+ s4.background = { fill: BG };
+ addSlideHeader(s4, 'IMPACT', colors.success, 'What we\'ve built \u2014 and who\'s using it');
+ stats.forEach((stat, i) => {
+ const left = 0.4 + i * 2.4;
+ s4.addShape('roundRect', { x: left, y: 2.2, w: 2.1, h: 1.4, fill: { color: CARD }, line: { color: BORDER, width: 1 }, rectRadius: 0.08 });
+ s4.addShape('rect', { x: left, y: 2.2, w: 0.06, h: 1.4, fill: { color: hexToRgb(stat.color) } });
+ s4.addText(stat.number, { x: left, y: 2.3, w: 2.1, h: 0.7, fontSize: 30, color: hexToRgb(stat.color), bold: true, align: 'center', fontFace: 'Calibri' });
+ s4.addText(stat.label, { x: left, y: 3, w: 2.1, h: 0.4, fontSize: 10, color: TEXT_SECONDARY, align: 'center', fontFace: 'Calibri' });
+ });
+ s4.addText(trustedBy.line1, { x: 0.5, y: 4, w: 9, h: 0.3, fontSize: 13, color: TEXT_PRIMARY, bold: true, align: 'center', fontFace: 'Calibri' });
+ s4.addText(trustedBy.line2, { x: 0.5, y: 4.3, w: 9, h: 0.3, fontSize: 11, color: hexToRgb(colors.primary), align: 'center', fontFace: 'Calibri' });
+
+ // ---- SLIDE 5: INTERVIEW LOG ----
+ const s5 = pptx.addSlide();
+ s5.background = { fill: BG };
+ addSlideHeader(s5, 'INTERVIEW LOG', colors.primary);
+ s5.addText('100+ interviews', { x: 0.8, y: 1.1, w: 4, h: 0.6, fontSize: 32, color: TEXT_PRIMARY, bold: true, fontFace: 'Calibri' });
+ s5.addText('across 14 segments in 7 weeks', { x: 0.8, y: 1.7, w: 4, h: 0.3, fontSize: 14, color: hexToRgb(colors.primary), fontFace: 'Calibri' });
+ weeklyProgress.forEach((w, i) => {
+ const y = 2.3 + i * 0.32;
+ s5.addText(w.week, { x: 0.5, y, w: 1.8, h: 0.25, fontSize: 9, color: TEXT_TERTIARY, fontFace: 'Calibri' });
+ s5.addShape('roundRect', { x: 2.5, y: y + 0.03, w: 2 * w.count / 100, h: 0.18, fill: { color: hexToRgb(colors.primary) }, rectRadius: 0.05 });
+ s5.addText(String(w.count), { x: 2.5 + 2 * w.count / 100 + 0.1, y, w: 0.4, h: 0.25, fontSize: 9, color: hexToRgb(colors.primary), bold: true, fontFace: 'Calibri' });
+ });
+ segments.forEach((seg, i) => {
+ const y = 1.0 + i * 0.35;
+ s5.addText(seg.name, { x: 5, y, w: 2.2, h: 0.3, fontSize: 9, color: TEXT_TERTIARY, align: 'right', fontFace: 'Calibri' });
+ s5.addShape('roundRect', { x: 7.4, y: y + 0.04, w: 2 * seg.count / 18, h: 0.2, fill: { color: hexToRgb(seg.color) }, rectRadius: 0.05 });
+ s5.addText(String(seg.count), { x: 7.5 + 2 * seg.count / 18, y, w: 0.3, h: 0.3, fontSize: 9, color: hexToRgb(seg.color), bold: true, fontFace: 'Calibri' });
+ });
+
+ // ---- SLIDE 6: ASSUMPTIONS ----
+ const s6 = pptx.addSlide();
+ s6.background = { fill: BG };
+ addSlideHeader(s6, 'ASSUMPTIONS', colors.accentOrange, 'What we assumed vs. what we learned');
+ assumptions.forEach((a, i) => {
+ const col = i % 2;
+ const row = Math.floor(i / 2);
+ const left = 0.4 + col * 5;
+ const top = 2 + row * 2;
+ s6.addShape('roundRect', { x: left, y: top, w: 4.6, h: 1.8, fill: { color: CARD }, line: { color: BORDER, width: 1 }, rectRadius: 0.08 });
+ s6.addShape('rect', { x: left, y: top, w: 0.06, h: 1.8, fill: { color: hexToRgb(a.color) } });
+ s6.addText(a.status, { x: left + 0.2, y: top + 0.1, w: 2, h: 0.25, fontSize: 10, color: hexToRgb(a.color), bold: true, fontFace: 'Calibri' });
+ s6.addText(a.title.replace('\n', ' '), { x: left + 0.2, y: top + 0.35, w: 2, h: 0.3, fontSize: 12, color: TEXT_PRIMARY, bold: true, fontFace: 'Calibri' });
+ s6.addText(a.learning, { x: left + 0.2, y: top + 0.7, w: 2, h: 0.5, fontSize: 9, color: TEXT_SECONDARY, fontFace: 'Calibri' });
+ s6.addText(a.quote, { x: left + 2.4, y: top + 0.3, w: 2, h: 0.8, fontSize: 9, color: TEXT_SECONDARY, fontFace: 'Calibri' });
+ s6.addText(a.source, { x: left + 2.4, y: top + 1.2, w: 2, h: 0.25, fontSize: 8, color: hexToRgb(a.color), fontFace: 'Calibri' });
+ });
+
+ // ---- SLIDE 7: A-HA MOMENT ----
+ const s7 = pptx.addSlide();
+ s7.background = { fill: BG };
+ addSlideHeader(s7, 'THE A-HA MOMENT', colors.highlight, 'One ecosystem became three');
+ s7.addText('Interviews revealed that serving everyone from one organization creates governance, funding, and mission conflicts.', { x: 0.5, y: 1.8, w: 9, h: 0.4, fontSize: 12, color: TEXT_SECONDARY, align: 'center', fontFace: 'Calibri' });
+ s7.addShape('ellipse', { x: 1, y: 2.7, w: 1.5, h: 1.5, fill: { color: CARD }, line: { color: BORDER, width: 1 } });
+ s7.addText('PolicyEngine\n(Unified)', { x: 1, y: 3, w: 1.5, h: 0.8, fontSize: 12, color: TEXT_SECONDARY, bold: true, align: 'center', fontFace: 'Calibri' });
+ s7.addText('\u2192', { x: 2.8, y: 3.1, w: 0.8, h: 0.5, fontSize: 36, color: hexToRgb(colors.highlight), align: 'center', fontFace: 'Calibri' });
+ orgs.forEach((org, i) => {
+ const left = 4 + i * 2;
+ s7.addShape('roundRect', { x: left, y: 2.5, w: 1.8, h: 2.5, fill: { color: CARD }, line: { color: BORDER, width: 1 }, rectRadius: 0.08 });
+ s7.addShape('rect', { x: left, y: 2.5, w: 1.8, h: 0.05, fill: { color: hexToRgb(org.color) } });
+ s7.addShape('ellipse', { x: left + 0.7, y: 2.75, w: 0.4, h: 0.4, fill: { color: hexToRgb(org.color) } });
+ s7.addText(org.name.replace('\n', ' '), { x: left + 0.1, y: 3.2, w: 1.6, h: 0.4, fontSize: 12, color: TEXT_PRIMARY, bold: true, align: 'center', fontFace: 'Calibri' });
+ s7.addText(`"${org.tagline}"`, { x: left + 0.1, y: 3.6, w: 1.6, h: 0.3, fontSize: 9, color: hexToRgb(org.color), align: 'center', fontFace: 'Calibri' });
+ s7.addText(org.entity, { x: left + 0.1, y: 3.9, w: 1.6, h: 0.2, fontSize: 8, color: TEXT_SECONDARY, align: 'center', fontFace: 'Calibri' });
+ });
+
+ // ---- SLIDES 8-10: ECOSYSTEM MAPS ----
+ const ecoOrgs = [
+ { tag: 'ECOSYSTEM 1 OF 3', title: 'Rules Foundation \u2014 "Encode the law"', color: colors.rulesBlue },
+ { tag: 'ECOSYSTEM 2 OF 3', title: 'Cosilico \u2014 "Run the infrastructure"', color: colors.cosilicoCyan },
+ { tag: 'ECOSYSTEM 3 OF 3', title: 'PolicyEngine \u2014 "Tell the story"', color: colors.peTeal },
+ ];
+ ecoOrgs.forEach((eco) => {
+ const s = pptx.addSlide();
+ s.background = { fill: BG };
+ addSlideHeader(s, eco.tag, eco.color, eco.title);
+ s.addText('(See interactive ecosystem map at pose-ecosystem.vercel.app)', { x: 1, y: 3, w: 8, h: 0.5, fontSize: 14, color: TEXT_TERTIARY, align: 'center', fontFace: 'Calibri' });
+ });
+
+ // ---- SLIDE 11: GOVERNANCE ----
+ const s11 = pptx.addSlide();
+ s11.background = { fill: BG };
+ addSlideHeader(s11, 'GOVERNANCE', colors.accentPurple, 'From founder-led to multi-stakeholder');
+ s11.addShape('roundRect', { x: 0.4, y: 2, w: 4.2, h: 1.6, fill: { color: CARD }, line: { color: BORDER, width: 1 }, rectRadius: 0.08 });
+ s11.addShape('rect', { x: 0.4, y: 2, w: 4.2, h: 0.05, fill: { color: hexToRgb(colors.accentOrange) } });
+ s11.addText('BEFORE', { x: 0.6, y: 2.1, w: 2, h: 0.25, fontSize: 11, color: hexToRgb(colors.accentOrange), bold: true, fontFace: 'Calibri' });
+ s11.addText(governanceBefore.title, { x: 0.6, y: 2.35, w: 3.8, h: 0.3, fontSize: 15, color: TEXT_PRIMARY, bold: true, fontFace: 'Calibri' });
+ governanceBefore.items.forEach((item, i) => {
+ s11.addText(`\u2022 ${item}`, { x: 0.6, y: 2.7 + i * 0.22, w: 3.8, h: 0.2, fontSize: 10, color: TEXT_SECONDARY, fontFace: 'Calibri' });
+ });
+ s11.addText('\u2192', { x: 4.6, y: 2.5, w: 0.6, h: 0.5, fontSize: 30, color: hexToRgb(colors.highlight), align: 'center', fontFace: 'Calibri' });
+ s11.addShape('roundRect', { x: 5.4, y: 2, w: 4.2, h: 1.6, fill: { color: CARD }, line: { color: BORDER, width: 1 }, rectRadius: 0.08 });
+ s11.addShape('rect', { x: 5.4, y: 2, w: 4.2, h: 0.05, fill: { color: hexToRgb(colors.success) } });
+ s11.addText('AFTER', { x: 5.6, y: 2.1, w: 2, h: 0.25, fontSize: 11, color: hexToRgb(colors.success), bold: true, fontFace: 'Calibri' });
+ s11.addText(governanceAfter.title, { x: 5.6, y: 2.35, w: 3.8, h: 0.3, fontSize: 15, color: TEXT_PRIMARY, bold: true, fontFace: 'Calibri' });
+ governanceAfter.items.forEach((item, i) => {
+ s11.addText(`\u2022 ${item.text}`, { x: 5.6, y: 2.7 + i * 0.22, w: 3.8, h: 0.2, fontSize: 10, color: hexToRgb(item.color), fontFace: 'Calibri' });
+ });
+
+ // ---- SLIDE 12: SUSTAINABILITY ----
+ const s12 = pptx.addSlide();
+ s12.background = { fill: BG };
+ addSlideHeader(s12, 'SUSTAINABILITY', colors.success, 'Three models, each self-sustaining');
+ sustainability.forEach((org, i) => {
+ const left = 0.3 + i * 3.3;
+ s12.addShape('roundRect', { x: left, y: 2, w: 3, h: 3.2, fill: { color: CARD }, line: { color: BORDER, width: 1 }, rectRadius: 0.08 });
+ s12.addShape('rect', { x: left, y: 2, w: 3, h: 0.05, fill: { color: hexToRgb(org.color) } });
+ s12.addText(org.name, { x: left + 0.3, y: 2.15, w: 2.5, h: 0.3, fontSize: 15, color: TEXT_PRIMARY, bold: true, fontFace: 'Calibri' });
+ s12.addText(org.budget, { x: left + 0.3, y: 2.5, w: 2.5, h: 0.3, fontSize: 13, color: hexToRgb(org.color), bold: true, fontFace: 'Calibri' });
+ org.items.forEach((item, j) => {
+ s12.addText(`\u2022 ${item}`, { x: left + 0.3, y: 3 + j * 0.3, w: 2.5, h: 0.25, fontSize: 10, color: TEXT_SECONDARY, fontFace: 'Calibri' });
+ });
+ });
+
+ // ---- SLIDE 13: TIMELINE ----
+ const s13 = pptx.addSlide();
+ s13.background = { fill: BG };
+ addSlideHeader(s13, 'TIMELINE', colors.primary, 'Next 24 months');
+ s13.addShape('rect', { x: 0.5, y: 2.8, w: 9, h: 0.02, fill: { color: BORDER } });
+ milestones.forEach((m, i) => {
+ const left = 0.3 + i * 1.9;
+ s13.addShape('ellipse', { x: left + 0.7, y: 2.65, w: 0.25, h: 0.25, fill: { color: hexToRgb(m.color) } });
+ s13.addText(m.period, { x: left, y: 2.1, w: 1.8, h: 0.25, fontSize: 10, color: hexToRgb(m.color), bold: true, align: 'center', fontFace: 'Calibri' });
+ s13.addText(m.label, { x: left, y: 2.35, w: 1.8, h: 0.2, fontSize: 10, color: TEXT_PRIMARY, bold: true, align: 'center', fontFace: 'Calibri' });
+ s13.addText(m.description.join('\n'), { x: left, y: 3.1, w: 1.8, h: 1.2, fontSize: 8, color: TEXT_SECONDARY, align: 'center', fontFace: 'Calibri' });
+ });
+
+ // ---- SLIDE 14: CLOSE ----
+ const s14 = pptx.addSlide();
+ s14.background = { fill: BG };
+ s14.addShape('rect', { x: 0, y: 0, w: '100%', h: 0.03, fill: { color: hexToRgb(colors.primary) } });
+ s14.addText('One ecosystem became three.', { x: 0.5, y: 1, w: 9, h: 0.8, fontSize: 36, color: TEXT_PRIMARY, bold: true, align: 'center', fontFace: 'Calibri' });
+ s14.addText('Each stronger for it.', { x: 0.5, y: 1.8, w: 9, h: 0.6, fontSize: 26, color: hexToRgb(colors.primary), bold: true, align: 'center', fontFace: 'Calibri' });
+ [{ n: 'Rules Foundation', t: 'Encode the law', c: colors.rulesBlue },
+ { n: 'Cosilico', t: 'Run the infrastructure', c: colors.cosilicoCyan },
+ { n: 'PolicyEngine', t: 'Tell the story', c: colors.peTeal }].forEach((org, i) => {
+ const left = 1.2 + i * 2.8;
+ s14.addShape('ellipse', { x: left + 0.9, y: 2.8, w: 0.4, h: 0.4, fill: { color: hexToRgb(org.c) } });
+ s14.addText(org.n, { x: left, y: 3.3, w: 2.2, h: 0.3, fontSize: 14, color: TEXT_PRIMARY, bold: true, align: 'center', fontFace: 'Calibri' });
+ s14.addText(org.t, { x: left, y: 3.6, w: 2.2, h: 0.2, fontSize: 11, color: hexToRgb(org.c), align: 'center', fontFace: 'Calibri' });
+ });
+ s14.addShape('roundRect', { x: 2, y: 4.2, w: 6, h: 0.6, fill: { color: CARD }, line: { color: hexToRgb(colors.primary), width: 1 }, rectRadius: 0.08 });
+ s14.addText('Looking for: foundation partners · agency pilot programs · AI lab collaborations', { x: 2.2, y: 4.25, w: 5.6, h: 0.5, fontSize: 11, color: hexToRgb(colors.primary), bold: true, align: 'center', fontFace: 'Calibri' });
+
+ // ---- APPENDIX A: VOICES ----
+ const sA = pptx.addSlide();
+ sA.background = { fill: BG };
+ addSlideHeader(sA, 'VOICES FROM THE FIELD', colors.primary, undefined, true);
+ voicesQuotes.forEach((q, i) => {
+ const col = i % 2;
+ const row = Math.floor(i / 2);
+ const left = 0.4 + col * 5;
+ const top = 1.5 + row * 2;
+ sA.addShape('roundRect', { x: left, y: top, w: 4.6, h: 1.7, fill: { color: CARD }, line: { color: BORDER, width: 1 }, rectRadius: 0.08 });
+ sA.addShape('rect', { x: left, y: top, w: 0.06, h: 1.7, fill: { color: hexToRgb(q.color) } });
+ sA.addText(q.text, { x: left + 0.2, y: top + 0.2, w: 4.2, h: 0.8, fontSize: 10, color: TEXT_SECONDARY, fontFace: 'Calibri' });
+ sA.addText(`\u2014 ${q.name}`, { x: left + 0.2, y: top + 1, w: 4.2, h: 0.2, fontSize: 10, color: hexToRgb(q.color), bold: true, fontFace: 'Calibri' });
+ sA.addText(q.title, { x: left + 0.2, y: top + 1.25, w: 4.2, h: 0.2, fontSize: 8, color: TEXT_TERTIARY, fontFace: 'Calibri' });
+ });
+
+ // ---- APPENDIX B: IMPACT GOALS ----
+ const sB = pptx.addSlide();
+ sB.background = { fill: BG };
+ addSlideHeader(sB, 'IMPACT GOALS', colors.success, undefined, true);
+ sB.addText(impactGoalsEvolution.week2, { x: 1.5, y: 1.5, w: 7.5, h: 0.3, fontSize: 10, color: TEXT_SECONDARY, fontFace: 'Calibri' });
+ sB.addText(impactGoalsEvolution.week3, { x: 1.5, y: 2, w: 7.5, h: 0.3, fontSize: 10, color: TEXT_SECONDARY, fontFace: 'Calibri' });
+ impactGoals.forEach((goal, i) => {
+ const left = 0.3 + i * 3.3;
+ sB.addShape('roundRect', { x: left, y: 3, w: 3, h: 2, fill: { color: CARD }, line: { color: BORDER, width: 1 }, rectRadius: 0.08 });
+ sB.addShape('rect', { x: left, y: 3, w: 3, h: 0.05, fill: { color: hexToRgb(goal.color) } });
+ sB.addText(goal.name, { x: left + 0.2, y: 3.15, w: 2.6, h: 0.3, fontSize: 12, color: TEXT_PRIMARY, bold: true, fontFace: 'Calibri' });
+ sB.addText(goal.condition, { x: left + 0.2, y: 3.5, w: 2.6, h: 0.5, fontSize: 9, color: hexToRgb(goal.color), bold: true, fontFace: 'Calibri' });
+ sB.addText(goal.impact, { x: left + 0.2, y: 4.1, w: 2.6, h: 0.6, fontSize: 9, color: TEXT_SECONDARY, fontFace: 'Calibri' });
+ });
+
+ // ---- APPENDIX C: PARTNERS ----
+ const sC = pptx.addSlide();
+ sC.background = { fill: BG };
+ addSlideHeader(sC, 'STRATEGIC PARTNERS', colors.accentPurple, undefined, true);
+ partners.forEach((p, i) => {
+ const left = 0.3 + i * 3.3;
+ sC.addShape('roundRect', { x: left, y: 1.5, w: 3, h: 3.5, fill: { color: CARD }, line: { color: BORDER, width: 1 }, rectRadius: 0.08 });
+ sC.addShape('rect', { x: left, y: 1.5, w: 3, h: 0.05, fill: { color: hexToRgb(p.color) } });
+ sC.addText(p.name, { x: left + 0.2, y: 1.65, w: 2.6, h: 0.3, fontSize: 14, color: TEXT_PRIMARY, bold: true, fontFace: 'Calibri' });
+ sC.addText(p.orgs, { x: left + 0.2, y: 1.95, w: 2.6, h: 0.2, fontSize: 8, color: hexToRgb(p.color), fontFace: 'Calibri' });
+ sC.addText(p.type, { x: left + 0.2, y: 2.2, w: 2.6, h: 0.2, fontSize: 9, color: TEXT_TERTIARY, bold: true, fontFace: 'Calibri' });
+ sC.addText(p.value.join('\n'), { x: left + 0.2, y: 2.6, w: 2.6, h: 0.8, fontSize: 9, color: TEXT_SECONDARY, fontFace: 'Calibri' });
+ sC.addText(`Risk: ${p.risk}`, { x: left + 0.2, y: 3.6, w: 2.6, h: 0.4, fontSize: 8, color: TEXT_TERTIARY, fontFace: 'Calibri' });
+ });
+
+ // ---- APPENDIX D: CANVAS (placeholder) ----
+ const sD = pptx.addSlide();
+ sD.background = { fill: BG };
+ addSlideHeader(sD, 'OSE CANVAS', colors.highlight, undefined, true);
+ sD.addText('(See OSE Canvas image in full presentation)', { x: 1, y: 3, w: 8, h: 0.5, fontSize: 14, color: TEXT_TERTIARY, align: 'center', fontFace: 'Calibri' });
+
+ // ---- APPENDIX E: CANVAS DETAIL ----
+ const sE = pptx.addSlide();
+ sE.background = { fill: BG };
+ addSlideHeader(sE, 'OSE CANVAS (DETAIL)', colors.primary, undefined, true);
+ sE.addText(canvasMembers.community.title, { x: 0.5, y: 1.3, w: 4.5, h: 0.3, fontSize: 12, color: hexToRgb(canvasMembers.community.color), bold: true, fontFace: 'Calibri' });
+ canvasMembers.community.items.forEach((item, i) => {
+ sE.addText(`\u2022 ${item}`, { x: 0.5, y: 1.7 + i * 0.22, w: 4.5, h: 0.2, fontSize: 9, color: TEXT_SECONDARY, fontFace: 'Calibri' });
+ });
+ sE.addText(canvasMembers.stakeholders.title, { x: 5.5, y: 1.3, w: 4.5, h: 0.3, fontSize: 12, color: hexToRgb(canvasMembers.stakeholders.color), bold: true, fontFace: 'Calibri' });
+ canvasMembers.stakeholders.items.forEach((item, i) => {
+ sE.addText(`\u2022 ${item}`, { x: 5.5, y: 1.7 + i * 0.22, w: 4.5, h: 0.2, fontSize: 9, color: TEXT_SECONDARY, fontFace: 'Calibri' });
+ });
+
+ // ---- APPENDIX F: GOVERNANCE DETAIL ----
+ const sF = pptx.addSlide();
+ sF.background = { fill: BG };
+ addSlideHeader(sF, 'GOVERNANCE DETAIL', colors.accentPurple, undefined, true);
+ governanceDetail.forEach((org, i) => {
+ const top = 1.3 + i * 1.4;
+ sF.addShape('roundRect', { x: 0.5, y: top, w: 9, h: 1.2, fill: { color: CARD }, line: { color: BORDER, width: 1 }, rectRadius: 0.08 });
+ sF.addShape('rect', { x: 0.5, y: top, w: 0.06, h: 1.2, fill: { color: hexToRgb(org.color) } });
+ sF.addText(org.name, { x: 0.8, y: top + 0.1, w: 3, h: 0.3, fontSize: 14, color: TEXT_PRIMARY, bold: true, fontFace: 'Calibri' });
+ sF.addText(org.details.join('\n'), { x: 0.8, y: top + 0.4, w: 8.5, h: 0.7, fontSize: 9, color: TEXT_SECONDARY, fontFace: 'Calibri' });
+ });
+
+ // ---- APPENDIX G: COMPETITIVE ----
+ const sG = pptx.addSlide();
+ sG.background = { fill: BG };
+ addSlideHeader(sG, 'COMPETITIVE LANDSCAPE', colors.cosilicoCyan, undefined, true);
+ competitors.forEach((c, i) => {
+ const y = 1.5 + i * 0.6;
+ sG.addShape('roundRect', { x: 0.5, y, w: 9, h: 0.5, fill: { color: CARD }, line: { color: BORDER, width: 1 }, rectRadius: 0.05 });
+ sG.addText(c.name, { x: 0.7, y, w: 2, h: 0.4, fontSize: 12, color: TEXT_PRIMARY, bold: true, fontFace: 'Calibri' });
+ sG.addText(c.metric, { x: 3, y, w: 2, h: 0.4, fontSize: 10, color: hexToRgb(colors.accentOrange), fontFace: 'Calibri' });
+ sG.addText(c.focus, { x: 5.2, y, w: 4, h: 0.4, fontSize: 10, color: TEXT_SECONDARY, fontFace: 'Calibri' });
+ });
+
+ // ---- APPENDIX H: HIGHLIGHTS ----
+ const sH = pptx.addSlide();
+ sH.background = { fill: BG };
+ addSlideHeader(sH, 'KEY INTERVIEW HIGHLIGHTS', colors.primary, undefined, true);
+ interviewHighlights.forEach((h, i) => {
+ sH.addText(`${h.name}: ${h.insight}`, { x: 0.5, y: 1.3 + i * 0.35, w: 9, h: 0.3, fontSize: 9, color: TEXT_SECONDARY, fontFace: 'Calibri' });
+ });
+
+ // ---- APPENDIX I: MARKETS ----
+ const sI = pptx.addSlide();
+ sI.background = { fill: BG };
+ addSlideHeader(sI, 'COSILICO: 12 MARKET SEGMENTS, $250B+ TAM', colors.cosilicoCyan, undefined, true);
+ markets.forEach((m, i) => {
+ const col = i % 3;
+ const row = Math.floor(i / 3);
+ const left = 0.3 + col * 3.3;
+ const top = 1.3 + row * 1;
+ sI.addShape('roundRect', { x: left, y: top, w: 3, h: 0.8, fill: { color: CARD }, line: { color: BORDER, width: 1 }, rectRadius: 0.05 });
+ sI.addText(m.name, { x: left + 0.15, y: top + 0.05, w: 2.7, h: 0.3, fontSize: 10, color: TEXT_PRIMARY, bold: true, fontFace: 'Calibri' });
+ sI.addText(`TAM: ${m.tam}`, { x: left + 0.15, y: top + 0.4, w: 2.7, h: 0.25, fontSize: 9, color: hexToRgb(colors.cosilicoCyan), fontFace: 'Calibri' });
+ });
+
+ // Generate and download
+ const now = new Date();
+ const dateStr = `${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}${now.getFullYear()}`;
+ pptx.writeFile({ fileName: `NSF_POSE_Final_${dateStr}.pptx` });
+}
diff --git a/app/src/lib/supabase.ts b/app/src/lib/supabase.ts
deleted file mode 100644
index 180368c..0000000
--- a/app/src/lib/supabase.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { createClient, SupabaseClient } from '@supabase/supabase-js'
-import type { Database } from '../types/database'
-
-const supabaseUrl = import.meta.env.VITE_SUPABASE_URL || ''
-const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY || ''
-
-// Check if Supabase is configured
-export const isSupabaseConfigured = () => {
- return Boolean(supabaseUrl && supabaseAnonKey)
-}
-
-// Only create the client if configured
-export const supabase: SupabaseClient | null = isSupabaseConfigured()
- ? createClient(supabaseUrl, supabaseAnonKey)
- : null
diff --git a/app/src/lib/types.ts b/app/src/lib/types.ts
new file mode 100644
index 0000000..1d3090a
--- /dev/null
+++ b/app/src/lib/types.ts
@@ -0,0 +1,136 @@
+import type { ComponentType } from 'react';
+
+export interface SlideProps {
+ isVisible: boolean;
+}
+
+export interface ScrollSection {
+ id: string;
+ title: string;
+ component: ComponentType;
+ isAppendix?: boolean;
+ stickyHeight?: number;
+}
+
+export interface TeamMember {
+ name: string;
+ role: string;
+ bio: string[];
+ color: string;
+ photo: string;
+}
+
+export interface StatItem {
+ number: string;
+ label: string;
+ color: string;
+}
+
+export interface WeekProgress {
+ week: string;
+ count: number;
+}
+
+export interface Segment {
+ name: string;
+ count: number;
+ color: string;
+}
+
+export interface Assumption {
+ status: string;
+ statusType: 'confirmed' | 'partial' | 'rejected';
+ title: string;
+ learning: string;
+ quote: string;
+ source: string;
+ color: string;
+}
+
+export interface OrgInfo {
+ name: string;
+ tagline: string;
+ entity: string;
+ description: string;
+ color: string;
+}
+
+export interface SustainabilityOrg {
+ name: string;
+ color: string;
+ budget: string;
+ items: string[];
+}
+
+export interface Milestone {
+ period: string;
+ label: string;
+ description: string[];
+ color: string;
+}
+
+export interface Quote {
+ text: string;
+ name: string;
+ title: string;
+ color: string;
+}
+
+export interface Partner {
+ name: string;
+ orgs: string;
+ type: string;
+ value: string[];
+ risk: string;
+ color: string;
+}
+
+export interface GovernanceOrg {
+ name: string;
+ color: string;
+ details: string[];
+}
+
+export interface Competitor {
+ name: string;
+ metric: string;
+ focus: string;
+}
+
+export interface MarketSegment {
+ name: string;
+ tam: string;
+}
+
+export interface ImpactGoal {
+ name: string;
+ color: string;
+ condition: string;
+ impact: string;
+}
+
+export interface InterviewHighlight {
+ name: string;
+ insight: string;
+}
+
+export interface EcosystemNode {
+ id: string;
+ label: string;
+ ring: number;
+ angle: number;
+ color: string;
+ org: 'all' | 'rules' | 'cosilico' | 'pe';
+ description?: string;
+ count?: number;
+ visibleAtStep: number;
+}
+
+export interface EcosystemEdge {
+ from: string;
+ to: string;
+ label?: string;
+ color: string;
+ type: 'solid' | 'dashed';
+ visibleAtStep: number;
+}
diff --git a/app/src/test/Outreach.test.tsx b/app/src/test/Outreach.test.tsx
deleted file mode 100644
index f62b576..0000000
--- a/app/src/test/Outreach.test.tsx
+++ /dev/null
@@ -1,101 +0,0 @@
-import { describe, it, expect, vi } from 'vitest'
-import { render, screen } from '@testing-library/react'
-import userEvent from '@testing-library/user-event'
-import { Outreach } from '../components/Outreach'
-
-describe('Outreach', () => {
- it('renders the email generator form', () => {
- render( )
-
- expect(screen.getByText('Email outreach generator')).toBeInTheDocument()
- expect(screen.getByPlaceholderText('Jane Smith')).toBeInTheDocument()
- expect(screen.getByPlaceholderText('Tax Policy Center')).toBeInTheDocument()
- })
-
- it('shows all 6 segment options', () => {
- render( )
-
- expect(screen.getByText('Users')).toBeInTheDocument()
- expect(screen.getByText('Supporters')).toBeInTheDocument()
- expect(screen.getByText('Contributors')).toBeInTheDocument()
- expect(screen.getByText('Competitors')).toBeInTheDocument()
- expect(screen.getByText('Distributors')).toBeInTheDocument()
- expect(screen.getByText('Partners')).toBeInTheDocument()
- })
-
- it('shows sender options for Max, Pavel, and Dan', () => {
- render( )
-
- expect(screen.getByRole('button', { name: 'Max' })).toBeInTheDocument()
- expect(screen.getByRole('button', { name: 'Pavel' })).toBeInTheDocument()
- expect(screen.getByRole('button', { name: 'Dan' })).toBeInTheDocument()
- })
-
- it('generates email with recipient name', async () => {
- const user = userEvent.setup()
- render( )
-
- const nameInput = screen.getByPlaceholderText('Jane Smith')
- await user.type(nameInput, 'John Doe')
-
- // Check generated email contains the first name
- expect(screen.getByText(/Hi John/)).toBeInTheDocument()
- })
-
- it('changes email template when segment changes', async () => {
- const user = userEvent.setup()
- render( )
-
- // Default is 'user' segment
- expect(screen.getByText(/Quick chat about PolicyEngine/)).toBeInTheDocument()
-
- // Switch to 'supporter' segment
- const supporterButton = screen.getByRole('button', { name: 'Supporters' })
- await user.click(supporterButton)
-
- expect(screen.getByText(/PolicyEngine sustainability/)).toBeInTheDocument()
- })
-
- it('has working copy buttons', async () => {
- const user = userEvent.setup()
-
- // Mock clipboard API using vi.stubGlobal
- const writeText = vi.fn().mockResolvedValue(undefined)
- vi.stubGlobal('navigator', {
- ...navigator,
- clipboard: { writeText },
- })
-
- render( )
-
- // Find and click the "Copy all" button
- const copyAllButton = screen.getByRole('button', { name: 'Copy all' })
- await user.click(copyAllButton)
-
- expect(writeText).toHaveBeenCalled()
-
- vi.unstubAllGlobals()
- })
-
- it('includes scheduling link in generated emails', () => {
- render( )
-
- expect(screen.getByText(/cal\.com\/max-ghenis-policyengine\/pose-ecosystem-interview/)).toBeInTheDocument()
- })
-
- it('shows outreach tips', () => {
- render( )
-
- expect(screen.getByText('Outreach tips')).toBeInTheDocument()
- expect(screen.getByText(/Personalize the context/)).toBeInTheDocument()
- expect(screen.getByText(/Send Tuesday-Thursday/)).toBeInTheDocument()
- })
-
- it('has mailto link for opening email client', () => {
- render( )
-
- const mailtoLink = screen.getByRole('link', { name: 'Open in email client' })
- expect(mailtoLink).toHaveAttribute('href')
- expect(mailtoLink.getAttribute('href')).toMatch(/^mailto:/)
- })
-})
diff --git a/app/src/test/setup.tsx b/app/src/test/setup.tsx
deleted file mode 100644
index bc5c760..0000000
--- a/app/src/test/setup.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import '@testing-library/jest-dom'
-import { vi, beforeEach, afterEach } from 'vitest'
-
-// Mock localStorage
-const localStorageMock = (() => {
- let store: Record = {}
- return {
- getItem: vi.fn((key: string) => store[key] || null),
- setItem: vi.fn((key: string, value: string) => {
- store[key] = value
- }),
- removeItem: vi.fn((key: string) => {
- delete store[key]
- }),
- clear: vi.fn(() => {
- store = {}
- }),
- get length() {
- return Object.keys(store).length
- },
- key: vi.fn((index: number) => Object.keys(store)[index] || null),
- }
-})()
-
-Object.defineProperty(window, 'localStorage', {
- value: localStorageMock,
-})
-
-// Reset mocks between tests
-beforeEach(() => {
- vi.clearAllMocks()
- localStorageMock.clear()
-})
-
-afterEach(() => {
- vi.restoreAllMocks()
-})
-
-// Mock framer-motion to avoid animation issues in tests
-vi.mock('framer-motion', () => ({
- motion: {
- div: ({ children, ...props }: React.PropsWithChildren>) => {
- const { initial, animate, transition, whileHover, whileTap, ...validProps } = props
- return {children}
- },
- span: ({ children, ...props }: React.PropsWithChildren>) => {
- const { initial, animate, transition, whileHover, whileTap, ...validProps } = props
- return {children}
- },
- circle: (props: Record) => {
- const { initial, animate, transition, ...validProps } = props
- return
- },
- },
- AnimatePresence: ({ children }: React.PropsWithChildren) => <>{children}>,
-}))
diff --git a/app/src/types/database.test.ts b/app/src/types/database.test.ts
deleted file mode 100644
index 4ecd0f0..0000000
--- a/app/src/types/database.test.ts
+++ /dev/null
@@ -1,105 +0,0 @@
-import { describe, it, expect } from 'vitest'
-import { SEGMENTS, POSE_MILESTONES, type Segment } from './database'
-
-describe('SEGMENTS', () => {
- it('has exactly 6 segments', () => {
- expect(SEGMENTS).toHaveLength(6)
- })
-
- it('contains all expected segment values', () => {
- const segmentValues = SEGMENTS.map((s) => s.value)
- expect(segmentValues).toContain('user')
- expect(segmentValues).toContain('supporter')
- expect(segmentValues).toContain('contributor')
- expect(segmentValues).toContain('competitor')
- expect(segmentValues).toContain('distributor')
- expect(segmentValues).toContain('partner')
- })
-
- it('each segment has required properties', () => {
- for (const segment of SEGMENTS) {
- expect(segment).toHaveProperty('value')
- expect(segment).toHaveProperty('label')
- expect(segment).toHaveProperty('color')
- expect(segment).toHaveProperty('description')
- expect(typeof segment.value).toBe('string')
- expect(typeof segment.label).toBe('string')
- expect(typeof segment.color).toBe('string')
- expect(typeof segment.description).toBe('string')
- }
- })
-
- it('colors are valid hex codes', () => {
- const hexColorRegex = /^#[0-9a-fA-F]{6}$/
- for (const segment of SEGMENTS) {
- expect(segment.color).toMatch(hexColorRegex)
- }
- })
-
- it('user segment has teal color', () => {
- const userSegment = SEGMENTS.find((s) => s.value === 'user')
- expect(userSegment?.color).toBe('#319795')
- })
-})
-
-describe('POSE_MILESTONES', () => {
- it('has 7 milestones', () => {
- expect(POSE_MILESTONES).toHaveLength(7)
- })
-
- it('starts at 15 and ends at 100', () => {
- expect(POSE_MILESTONES[0].target_count).toBe(15)
- expect(POSE_MILESTONES[POSE_MILESTONES.length - 1].target_count).toBe(100)
- })
-
- it('milestones are in ascending order', () => {
- for (let i = 1; i < POSE_MILESTONES.length; i++) {
- expect(POSE_MILESTONES[i].target_count).toBeGreaterThan(
- POSE_MILESTONES[i - 1].target_count
- )
- }
- })
-
- it('deadlines are in ascending order', () => {
- for (let i = 1; i < POSE_MILESTONES.length; i++) {
- const prevDate = new Date(POSE_MILESTONES[i - 1].deadline)
- const currDate = new Date(POSE_MILESTONES[i].deadline)
- expect(currDate.getTime()).toBeGreaterThan(prevDate.getTime())
- }
- })
-
- it('each milestone has required properties', () => {
- for (const milestone of POSE_MILESTONES) {
- expect(milestone).toHaveProperty('id')
- expect(milestone).toHaveProperty('name')
- expect(milestone).toHaveProperty('target_count')
- expect(milestone).toHaveProperty('deadline')
- expect(typeof milestone.id).toBe('string')
- expect(typeof milestone.name).toBe('string')
- expect(typeof milestone.target_count).toBe('number')
- expect(typeof milestone.deadline).toBe('string')
- }
- })
-
- it('first milestone is Kickoff Week', () => {
- expect(POSE_MILESTONES[0].name).toBe('Kickoff Week')
- })
-
- it('last milestone is Finale', () => {
- expect(POSE_MILESTONES[POSE_MILESTONES.length - 1].name).toBe('Finale')
- })
-})
-
-describe('Segment type', () => {
- it('accepts valid segment values', () => {
- const validSegments: Segment[] = [
- 'user',
- 'supporter',
- 'contributor',
- 'competitor',
- 'distributor',
- 'partner',
- ]
- expect(validSegments).toHaveLength(6)
- })
-})
diff --git a/app/src/types/database.ts b/app/src/types/database.ts
deleted file mode 100644
index fab3354..0000000
--- a/app/src/types/database.ts
+++ /dev/null
@@ -1,281 +0,0 @@
-export type Segment = 'user' | 'supporter' | 'contributor' | 'competitor' | 'distributor' | 'partner'
-
-export type InterviewStatus = 'scheduled' | 'completed' | 'cancelled' | 'no_show'
-
-// POSE program assumptions to test
-export const POSE_HYPOTHESES = [
- 'Policy researchers will adopt open-source tools if they are accessible without programming expertise',
- 'Funders value transparency and reproducibility enough to fund open-source over proprietary alternatives',
- 'Developers will contribute for policy impact without requiring competitive compensation',
-] as const
-
-export type PoseHypothesis = typeof POSE_HYPOTHESES[number]
-
-export interface Interview {
- id: string
- contact_id?: string
- name: string
- organization: string
- role?: string
- segment: Segment
- scheduled_date?: string
- completed_date?: string
- status: InterviewStatus
- notes?: string
- key_insights?: string[]
- referrals?: string[]
- // POSE interview log fields
- hypotheses_tested?: PoseHypothesis[]
- experiments?: string // What questions we asked
- results?: string // What we learned
- actions?: string // What we'll do next
- created_at: string
- updated_at: string
-}
-
-export interface Milestone {
- id: string
- name: string
- target_count: number
- deadline: string
- description?: string
-}
-
-export interface Contact {
- id: string
- display_name: string
- email?: string
- organization?: string
- job_title?: string
- notes?: string
- contact_status?: string
-}
-
-// Supabase database types - using 'any' to simplify and avoid complex generic inference
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-export type Database = any
-
-// Milestones are fixed for the POSE program
-export const POSE_MILESTONES: Milestone[] = [
- { id: '1', name: 'Kickoff Week', target_count: 15, deadline: '2026-01-23', description: 'End of 4-day kickoff' },
- { id: '2', name: 'Weekly Session 1', target_count: 25, deadline: '2026-01-28', description: 'First weekly check-in' },
- { id: '3', name: 'Weekly Session 2', target_count: 40, deadline: '2026-02-04', description: 'Sustainability focus' },
- { id: '4', name: 'Weekly Session 3', target_count: 55, deadline: '2026-02-11', description: 'Partnerships focus' },
- { id: '5', name: 'Weekly Session 4', target_count: 70, deadline: '2026-02-18', description: 'Governance focus' },
- { id: '6', name: 'Weekly Session 5', target_count: 85, deadline: '2026-02-25', description: 'Timeline focus' },
- { id: '7', name: 'Finale', target_count: 100, deadline: '2026-03-04', description: 'Final presentations' },
-]
-
-export const SEGMENTS: { value: Segment; label: string; color: string; description: string }[] = [
- { value: 'user', label: 'Users', color: '#319795', description: 'People who use PolicyEngine directly' },
- { value: 'supporter', label: 'Supporters', color: '#2563eb', description: 'Funders, advocates, policy champions' },
- { value: 'contributor', label: 'Contributors', color: '#7c3aed', description: 'Developers, data contributors' },
- { value: 'competitor', label: 'Competitors', color: '#dc2626', description: 'Alternative tools and approaches' },
- { value: 'distributor', label: 'Distributors', color: '#ea580c', description: 'Organizations that embed PolicyEngine' },
- { value: 'partner', label: 'Partners', color: '#16a34a', description: 'Integrations and complements' },
-]
-
-// Assignment types
-export type AssignmentStatus = 'pending' | 'in_progress' | 'completed'
-export type AssignmentCategory = 'prep' | 'presentation' | 'interview_milestone'
-
-export interface Assignment {
- id: string
- title: string
- description: string
- due_date: string
- status: AssignmentStatus
- category: AssignmentCategory
- source_url?: string
-}
-
-// Pre-populated POSE program assignments
-export const POSE_ASSIGNMENTS: Assignment[] = [
- // Pre-program
- {
- id: 'prep-1',
- title: 'Orientation prep',
- description: 'Watch orientation videos and read required materials before program start',
- due_date: '2026-01-06',
- status: 'pending',
- category: 'prep',
- source_url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858784',
- },
-
- // Kickoff week (Jan 20-23, 2026)
- {
- id: 'kickoff-1a',
- title: 'Day 1: Upload presentation slides',
- description: 'Slide deck with TEAM, THESIS, ASSUMPTIONS, INTERVIEW LOG slides (PPT format)',
- due_date: '2026-01-20',
- status: 'pending',
- category: 'presentation',
- source_url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858791',
- },
- {
- id: 'kickoff-1b',
- title: 'Day 1: Office hours slides',
- description: 'Slides for office hours: TEAM, THESIS, TEAM GOALS AND CHARTER',
- due_date: '2026-01-20',
- status: 'pending',
- category: 'presentation',
- source_url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858791',
- },
- {
- id: 'kickoff-2',
- title: 'Day 2: Interview prep',
- description: 'Prepare interview questions and outreach strategy',
- due_date: '2026-01-21',
- status: 'pending',
- category: 'prep',
- source_url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858794',
- },
- {
- id: 'kickoff-3',
- title: 'Update slides with learnings',
- description: 'Day 3: Update presentation slides with initial interview learnings',
- due_date: '2026-01-22',
- status: 'pending',
- category: 'presentation',
- source_url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858795',
- },
- {
- id: 'kickoff-4',
- title: 'Update slides',
- description: 'Day 4: Final kickoff week slide updates and presentation',
- due_date: '2026-01-23',
- status: 'pending',
- category: 'presentation',
- source_url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858796',
- },
- {
- id: 'kickoff-interviews',
- title: 'Complete 15 interviews',
- description: 'Kickoff week interview target (schedule at least 15)',
- due_date: '2026-01-23',
- status: 'pending',
- category: 'interview_milestone',
- source_url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858796',
- },
-
- // Weekly sessions
- {
- id: 'weekly-1-slides',
- title: 'Session 1: Update slides',
- description: 'Update presentation with ecosystem discovery progress',
- due_date: '2026-01-28',
- status: 'pending',
- category: 'presentation',
- source_url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858800',
- },
- {
- id: 'weekly-1-interviews',
- title: 'Complete 25 interviews',
- description: 'Session 1 cumulative interview target',
- due_date: '2026-01-28',
- status: 'pending',
- category: 'interview_milestone',
- source_url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858800',
- },
- {
- id: 'weekly-2-slides',
- title: 'Session 2: Update slides',
- description: 'Update presentation with sustainability insights',
- due_date: '2026-02-04',
- status: 'pending',
- category: 'presentation',
- source_url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858803',
- },
- {
- id: 'weekly-2-interviews',
- title: 'Complete 40 interviews',
- description: 'Session 2 cumulative interview target',
- due_date: '2026-02-04',
- status: 'pending',
- category: 'interview_milestone',
- source_url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858803',
- },
- {
- id: 'weekly-3-slides',
- title: 'Session 3: Update slides',
- description: 'Update presentation with partnership learnings',
- due_date: '2026-02-11',
- status: 'pending',
- category: 'presentation',
- source_url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858806',
- },
- {
- id: 'weekly-3-interviews',
- title: 'Complete 55 interviews',
- description: 'Session 3 cumulative interview target',
- due_date: '2026-02-11',
- status: 'pending',
- category: 'interview_milestone',
- source_url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858806',
- },
- {
- id: 'weekly-4-slides',
- title: 'Session 4: Update slides',
- description: 'Update presentation with governance insights',
- due_date: '2026-02-18',
- status: 'pending',
- category: 'presentation',
- source_url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858809',
- },
- {
- id: 'weekly-4-interviews',
- title: 'Complete 70 interviews',
- description: 'Session 4 cumulative interview target',
- due_date: '2026-02-18',
- status: 'pending',
- category: 'interview_milestone',
- source_url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858809',
- },
- {
- id: 'weekly-5-slides',
- title: 'Session 5: Update slides',
- description: 'Update presentation with timeline and roadmap',
- due_date: '2026-02-25',
- status: 'pending',
- category: 'presentation',
- source_url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858815',
- },
- {
- id: 'weekly-5-interviews',
- title: 'Complete 85 interviews',
- description: 'Session 5 cumulative interview target',
- due_date: '2026-02-25',
- status: 'pending',
- category: 'interview_milestone',
- source_url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858815',
- },
-
- // Finale (Mar 4-5, 2026)
- {
- id: 'finale-prep',
- title: 'Final presentation prep',
- description: 'Prepare final presentation materials and practice',
- due_date: '2026-03-04',
- status: 'pending',
- category: 'prep',
- source_url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858818',
- },
- {
- id: 'finale-presentation',
- title: 'Final presentation delivery',
- description: 'Deliver final POSE program presentation',
- due_date: '2026-03-05',
- status: 'pending',
- category: 'presentation',
- source_url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858820',
- },
- {
- id: 'finale-interviews',
- title: 'Complete 100 interviews',
- description: 'Final program interview target',
- due_date: '2026-03-05',
- status: 'pending',
- category: 'interview_milestone',
- source_url: 'https://venturewell.novoed.com/#!/courses/i-corps_pose_winter_2026/lecture_pages/3858820',
- },
-]
diff --git a/app/tailwind.config.js b/app/tailwind.config.js
deleted file mode 100644
index 462dada..0000000
--- a/app/tailwind.config.js
+++ /dev/null
@@ -1,43 +0,0 @@
-/** @type {import('tailwindcss').Config} */
-export default {
- content: [
- "./index.html",
- "./src/**/*.{js,ts,jsx,tsx}",
- ],
- theme: {
- extend: {
- colors: {
- teal: {
- 50: '#f0fdfa',
- 100: '#ccfbf1',
- 200: '#99f6e4',
- 300: '#5eead4',
- 400: '#2dd4bf',
- 500: '#319795', // PolicyEngine primary
- 600: '#0d9488',
- 700: '#0f766e',
- 800: '#115e59',
- 900: '#134e4a',
- },
- gray: {
- 50: '#f9fafb',
- 100: '#f3f4f6',
- 200: '#e5e7eb',
- 300: '#d1d5db',
- 400: '#9ca3af',
- 500: '#6b7280',
- 600: '#4b5563',
- 700: '#344054', // PolicyEngine gray
- 800: '#1f2937',
- 900: '#111827',
- }
- },
- fontFamily: {
- sans: ['Inter', 'system-ui', 'sans-serif'],
- },
- },
- },
- plugins: [
- require('@tailwindcss/forms'),
- ],
-}
diff --git a/app/tsconfig.app.json b/app/tsconfig.app.json
index 71c131a..a9b5a59 100644
--- a/app/tsconfig.app.json
+++ b/app/tsconfig.app.json
@@ -5,7 +5,7 @@
"useDefineForClassFields": true,
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
- "types": ["vite/client", "vitest/globals"],
+ "types": ["vite/client"],
"skipLibCheck": true,
/* Bundler mode */
diff --git a/app/vercel.json b/app/vercel.json
new file mode 100644
index 0000000..7531625
--- /dev/null
+++ b/app/vercel.json
@@ -0,0 +1,8 @@
+{
+ "buildCommand": "npm run build",
+ "outputDirectory": "dist",
+ "framework": "vite",
+ "rewrites": [
+ { "source": "/(.*)", "destination": "/index.html" }
+ ]
+}
diff --git a/app/vite.config.ts b/app/vite.config.ts
index 8b0f57b..0616e59 100644
--- a/app/vite.config.ts
+++ b/app/vite.config.ts
@@ -1,7 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
+import tailwindcss from '@tailwindcss/vite'
-// https://vite.dev/config/
export default defineConfig({
- plugins: [react()],
+ plugins: [react(), tailwindcss()],
})
diff --git a/app/vitest.config.ts b/app/vitest.config.ts
deleted file mode 100644
index 304ddfd..0000000
--- a/app/vitest.config.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { defineConfig } from 'vitest/config'
-import react from '@vitejs/plugin-react'
-
-export default defineConfig({
- plugins: [react()],
- test: {
- environment: 'jsdom',
- globals: true,
- setupFiles: ['./src/test/setup.tsx'],
- include: ['src/**/*.{test,spec}.{ts,tsx}'],
- coverage: {
- reporter: ['text', 'json', 'html'],
- exclude: ['node_modules/', 'src/test/'],
- },
- },
-})