Finishline #122
@@ -0,0 +1,10 @@
|
||||
node_modules
|
||||
dist
|
||||
.git
|
||||
.github
|
||||
.vscode
|
||||
.env
|
||||
.env.local
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
@@ -0,0 +1,9 @@
|
||||
# GEMINI_API_KEY: Required for Gemini AI API calls.
|
||||
# AI Studio automatically injects this at runtime from user secrets.
|
||||
# Users configure this via the Secrets panel in the AI Studio UI.
|
||||
GEMINI_API_KEY="MY_GEMINI_API_KEY"
|
||||
|
||||
# APP_URL: The URL where this applet is hosted.
|
||||
# AI Studio automatically injects this at runtime with the Cloud Run service URL.
|
||||
# Used for self-referential links, OAuth callbacks, and API endpoints.
|
||||
APP_URL="MY_APP_URL"
|
||||
@@ -0,0 +1,8 @@
|
||||
node_modules/
|
||||
build/
|
||||
dist/
|
||||
coverage/
|
||||
.DS_Store
|
||||
*.log
|
||||
.env*
|
||||
!.env.example
|
||||
@@ -0,0 +1,20 @@
|
||||
<div align="center">
|
||||
<img width="1200" height="475" alt="GHBanner" src="https://ai.google.dev/static/site-assets/images/share-ais-513315318.png" />
|
||||
</div>
|
||||
|
||||
# Run and deploy your AI Studio app
|
||||
|
||||
This contains everything you need to run your app locally.
|
||||
|
||||
View your app in AI Studio: https://ai.studio/apps/c16c81de-cc09-4ace-8d83-d85f93c19114
|
||||
|
||||
## Run Locally
|
||||
|
||||
**Prerequisites:** Node.js
|
||||
|
||||
|
||||
1. Install dependencies:
|
||||
`npm install`
|
||||
2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
|
||||
3. Run the app:
|
||||
`npm run dev`
|
||||
@@ -0,0 +1,799 @@
|
||||
{
|
||||
"lockfileVersion": 2,
|
||||
"configVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "react-example",
|
||||
"dependencies": {
|
||||
"@google/genai": "^2.4.0",
|
||||
"@hookform/resolvers": "^5.9.1",
|
||||
"@tailwindcss/vite": "^4.3.3",
|
||||
"@tanstack/react-query": "^5.104.0",
|
||||
"@vitejs/plugin-react": "^6.1.1",
|
||||
"axios": "^1.20.0",
|
||||
"dotenv": "^17.2.3",
|
||||
"express": "^4.21.2",
|
||||
"hashids": "^2.3.0",
|
||||
"jspdf": "^4.2.1",
|
||||
"jspdf-autotable": "^5.0.8",
|
||||
"lucide-react": "^0.546.0",
|
||||
"motion": "^12.23.24",
|
||||
"ogl": "^1.0.11",
|
||||
"oidc-client-ts": "^3.5.0",
|
||||
"react": "^19.0.1",
|
||||
"react-dom": "^19.0.1",
|
||||
"react-hook-form": "^7.89.0",
|
||||
"react-router-dom": "^7.18.4",
|
||||
"vite": "^8.3.0",
|
||||
"xlsx": "^0.18.5",
|
||||
"zod": "^4.6.5",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/node": "^22.14.0",
|
||||
"@types/react": "^19.3.0",
|
||||
"@types/react-dom": "^19.3.0",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"esbuild": "^0.25.0",
|
||||
"tailwindcss": "^4.3.3",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "^7.0.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@babel/runtime": ["@babel/runtime@7.29.7", "", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="],
|
||||
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
|
||||
|
||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
|
||||
|
||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
|
||||
|
||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
|
||||
|
||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
|
||||
|
||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
|
||||
|
||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
|
||||
|
||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
|
||||
|
||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
|
||||
|
||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
|
||||
|
||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
|
||||
|
||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
|
||||
|
||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
|
||||
|
||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
|
||||
|
||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
|
||||
|
||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
|
||||
|
||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
|
||||
|
||||
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
|
||||
|
||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
|
||||
|
||||
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
|
||||
|
||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
|
||||
|
||||
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="],
|
||||
|
||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
|
||||
|
||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
|
||||
|
||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
|
||||
|
||||
"@google/genai": ["@google/genai@2.24.0", "", { "dependencies": { "google-auth-library": "^10.3.0", "p-retry": "^4.6.2", "protobufjs": "^7.5.4", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.25.2" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-bIu5eoxF1AaPWs9ivmUJGp1RpDHyapoODvWvnkzRDx2CTIW6UJXFhDdjj0BlRve3ZHMMuEkQBVrA/ALqpW4apA=="],
|
||||
|
||||
"@hookform/resolvers": ["@hookform/resolvers@5.9.1", "", { "dependencies": { "@standard-schema/utils": "^0.3.0" }, "peerDependencies": { "@sinclair/typebox": ">=0.25.24", "@standard-schema/spec": "^1.0.0", "@typeschema/main": ">=0.13.7", "@vinejs/vine": "^2.0.0 || ^3.0.0 || ^4.0.0", "ajv": "^8.12.0", "ajv-errors": "^3.0.0", "ajv-formats": "^2.1.1", "arktype": "^2.0.0", "ata-validator": "^1.2.0", "class-transformer": ">=0.4.0", "class-validator": ">=0.12.0", "computed-types": "^1.0.0", "effect": "^3.10.3", "fluentvalidation-ts": "^3.0.0", "fp-ts": "^2.7.0", "io-ts": "^2.0.0", "joi": "^17.0.0 || ^18.0.0", "nope-validator": ">=0.12.0", "react-hook-form": "^7.55.0", "superstruct": ">=0.12.0", "typanion": "^3.3.2", "valibot": ">=0.31.0 || ^1.0.0-beta.4 || ^1.0.0-rc", "vest": ">=6.0.0", "yup": "^1.0.0", "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["@sinclair/typebox", "@standard-schema/spec", "@typeschema/main", "@vinejs/vine", "ajv", "ajv-errors", "ajv-formats", "arktype", "ata-validator", "class-transformer", "class-validator", "computed-types", "effect", "fluentvalidation-ts", "fp-ts", "io-ts", "joi", "nope-validator", "superstruct", "typanion", "valibot", "vest", "yup", "zod"] }, "sha512-7b7vsbraJxKgjVSA1Nur9tLwj539WGJUBLA7QNvXnFoT2pM5Z7G+6rlukk4B2/QrTZy6huRtH6wKeESPKuIr6w=="],
|
||||
|
||||
"@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.6.0", "", {}, "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
"@oxc-project/types": ["@oxc-project/types@0.151.0", "", {}, "sha512-J1yXrIlNDZVzE3ada310xeAw7nH8yCAyLPuUIsjKatFPmfn5bS1oW+cM+QsGOtVWd5nhSpbwZWx/rue+r5Z+PA=="],
|
||||
|
||||
"@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="],
|
||||
|
||||
"@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="],
|
||||
|
||||
"@protobufjs/codegen": ["@protobufjs/codegen@2.0.5", "", {}, "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g=="],
|
||||
|
||||
"@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.1", "", {}, "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg=="],
|
||||
|
||||
"@protobufjs/fetch": ["@protobufjs/fetch@1.1.1", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1" } }, "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw=="],
|
||||
|
||||
"@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="],
|
||||
|
||||
"@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="],
|
||||
|
||||
"@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="],
|
||||
|
||||
"@protobufjs/utf8": ["@protobufjs/utf8@1.1.2", "", {}, "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug=="],
|
||||
|
||||
"@rolldown/binding-android-arm-eabi": ["@rolldown/binding-android-arm-eabi@1.2.11", "", { "os": "android", "cpu": "arm" }, "sha512-A5kXfGKvKWWZE0TtPrfsvT+q4Y5d1QG8gGUzpYjGydM+fARM9MuX90PrXYXe0XbsDVgyxxNzHo6giCj90bsFNw=="],
|
||||
|
||||
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.11", "", { "os": "android", "cpu": "arm64" }, "sha512-z6cTycz+iJ4PVkuL4HHW4DfTfoeU/2nqYYuSOrTmH7yHK5Y0LCOnA03V4ZNxavyVaU1oOqUgIg2klN/s+USGOA=="],
|
||||
|
||||
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-jShvqNtP6vDC6/A5JOAzbVV+DkgHqhl/ScVCJEbt+TUY6QYz7YnXcrg3sLtFBniro0f/Ld50ZwCWA6f7KYD1nQ=="],
|
||||
|
||||
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-f2i2xiNWq1Z1l2++q2fuhZRdLAT3aqxD6vRNm1RAxpUoBcdqNB3C0s1Bt+K+PbEx2F5F4gQp6hqKkphCY/xF9w=="],
|
||||
|
||||
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.11", "", { "os": "freebsd", "cpu": "x64" }, "sha512-4Ir5FSOKIAMr4r0kExpt1s3bMgzJU3rA45AYOHtQpls0oNeqcYBKrWMlckrYH4KCfGLfkfn1tN1dmZPMVsdXow=="],
|
||||
|
||||
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.11", "", { "os": "linux", "cpu": "arm" }, "sha512-/gnRDM+39BROzAN/k1OZjDPnDMcZxB/0EUxKjONO5yVkNEvlsoMDrxGNKgZi/ttFriS2gwlDNzB65pvNbFOXIQ=="],
|
||||
|
||||
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-PFaK8HwvAHbaKbBcDNQihjMKYvFnA5hiENx/l5tphTDz1E0WFp32l0A7aq7lyUwGsRw/xSrNIy/gIK4thrSCrw=="],
|
||||
|
||||
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-AskzJUIKRLPxkruR1wLKewGbOw+EYfU/9lOrBFj4AFrEA8hPpKFnODWNu2WLaNs0QNkEb9QIJufmVZZIL/bJlg=="],
|
||||
|
||||
"@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.11", "", { "os": "linux", "cpu": "ppc64" }, "sha512-qlUGAheh2yh8afH7QBgx0PrRHN85hKnNd78x8MeMhXivuevgd8vgf6/CstOzmNKY/lLTHvNTrPy98cLnAugzJw=="],
|
||||
|
||||
"@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.11", "", { "os": "linux", "cpu": "s390x" }, "sha512-secpEad+0vCbSfn8upFySkDskv+bGPk3THSDS9Y89yc4rb4kzqHp8Dmyd9BkQW4SnhNXBZCl/6CrO//hZahNJQ=="],
|
||||
|
||||
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.11", "", { "os": "linux", "cpu": "x64" }, "sha512-mOVBT3dPpkWm8XBWPmU4bf+U6dYDLeMo/9ojUmis4N0L5uu10qra5vOyngZ7/PSdoE4G9KvRt4bloRxNjLas7A=="],
|
||||
|
||||
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.11", "", { "os": "linux", "cpu": "x64" }, "sha512-Is78i9A8Ui4SqcxUwFJ9uMmjDn58IbVTjFWYdQestFEgeuEmHMLGNriXnVJKkwG2YiZjw8cP0zCTyDMdDGtOOg=="],
|
||||
|
||||
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.11", "", { "os": "none", "cpu": "arm64" }, "sha512-dUCXneZ87INUMyQ0D+C0HrEBNUPNXHaPmU5GTjyKTJEiussw9Kaj5Ln8UztPe4epV/ffvgNBEadksdYhmW6xJA=="],
|
||||
|
||||
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-jByxb6qfd+bH1xUd0qnfFnb17i9sWBPY2tOavJ0l3tdr3OTu+Kvtm8cd/JV5nFt657b1VqGltxg9olOEfofXWw=="],
|
||||
|
||||
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.11", "", { "os": "win32", "cpu": "x64" }, "sha512-/PzKqzAJ03i19oy2ItPvyvaVjOjBCNnfaJs8yvUdGBKmiESgnrJSQ2awd81QzFbbnAmu7YO9ZnJrDCb9VSJPRA=="],
|
||||
|
||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="],
|
||||
|
||||
"@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="],
|
||||
|
||||
"@tailwindcss/node": ["@tailwindcss/node@4.3.3", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.24.1", "jiti": "^2.7.0", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.3.3" } }, "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg=="],
|
||||
|
||||
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.3.3", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.3.3", "@tailwindcss/oxide-darwin-arm64": "4.3.3", "@tailwindcss/oxide-darwin-x64": "4.3.3", "@tailwindcss/oxide-freebsd-x64": "4.3.3", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", "@tailwindcss/oxide-linux-x64-musl": "4.3.3", "@tailwindcss/oxide-wasm32-wasi": "4.3.3", "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" } }, "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA=="],
|
||||
|
||||
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.3.3", "", { "os": "android", "cpu": "arm64" }, "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw=="],
|
||||
|
||||
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.3.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw=="],
|
||||
|
||||
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.3.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw=="],
|
||||
|
||||
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.3.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3", "", { "os": "linux", "cpu": "arm" }, "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.3.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.3.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.3.3", "", { "dependencies": { "@emnapi/core": "^1.11.1", "@emnapi/runtime": "^1.11.1", "@emnapi/wasi-threads": "^1.2.2", "@napi-rs/wasm-runtime": "^1.1.4", "@tybys/wasm-util": "^0.10.2", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ=="],
|
||||
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.3.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ=="],
|
||||
|
||||
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.3.3", "", { "os": "win32", "cpu": "x64" }, "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw=="],
|
||||
|
||||
"@tailwindcss/vite": ["@tailwindcss/vite@4.3.3", "", { "dependencies": { "@tailwindcss/node": "4.3.3", "@tailwindcss/oxide": "4.3.3", "tailwindcss": "4.3.3" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw=="],
|
||||
|
||||
"@tanstack/query-core": ["@tanstack/query-core@5.104.0", "", {}, "sha512-JrC2r/JQlXt7khBSdUpsgxNvybzOg+aITa+ARRMlP2AFo93Y8vIqz077rp+e61mJetSZ0T6AJbwW1JHer1vrPQ=="],
|
||||
|
||||
"@tanstack/react-query": ["@tanstack/react-query@5.104.0", "", { "dependencies": { "@tanstack/query-core": "5.104.0" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-e1TZmDCQnWIfiDVryIHeA6Idj+Lfx1hORLOhXS/l5wcgvtVD4yYqbTDl378sGQKIi2cSgb0TEMC9cKK8GGoJEw=="],
|
||||
|
||||
"@types/body-parser": ["@types/body-parser@1.19.6", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g=="],
|
||||
|
||||
"@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="],
|
||||
|
||||
"@types/express": ["@types/express@4.17.25", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", "@types/serve-static": "^1" } }, "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw=="],
|
||||
|
||||
"@types/express-serve-static-core": ["@types/express-serve-static-core@4.19.9", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-QP2ESEe/ImWY0HDwNAnK9PvEffUyhLTnWkk7KXzHfyeWAnlrDe1fN77bXl6ia8KT3wPlmA7t9/VPRpnf4Ex9sg=="],
|
||||
|
||||
"@types/http-errors": ["@types/http-errors@2.0.5", "", {}, "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="],
|
||||
|
||||
"@types/mime": ["@types/mime@1.3.5", "", {}, "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="],
|
||||
|
||||
"@types/node": ["@types/node@22.20.4", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-zJRE40jpHtKqE/C4fgHrAKQLJuSpzEnP9ff9Y7YtoR3Wd2pwqzlekDeEuUQXjRd+QCYnVnNwuJYmhdk9XV8gvA=="],
|
||||
|
||||
"@types/pako": ["@types/pako@2.0.4", "", {}, "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw=="],
|
||||
|
||||
"@types/qs": ["@types/qs@6.15.1", "", {}, "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw=="],
|
||||
|
||||
"@types/raf": ["@types/raf@3.4.3", "", {}, "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw=="],
|
||||
|
||||
"@types/range-parser": ["@types/range-parser@1.2.7", "", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="],
|
||||
|
||||
"@types/react": ["@types/react@19.3.0", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg=="],
|
||||
|
||||
"@types/react-dom": ["@types/react-dom@19.3.0", "", { "peerDependencies": { "@types/react": "^19.3.0" } }, "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q=="],
|
||||
|
||||
"@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="],
|
||||
|
||||
"@types/send": ["@types/send@1.2.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="],
|
||||
|
||||
"@types/serve-static": ["@types/serve-static@1.15.10", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw=="],
|
||||
|
||||
"@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
|
||||
|
||||
"@typescript/typescript-aix-ppc64": ["@typescript/typescript-aix-ppc64@7.0.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ=="],
|
||||
|
||||
"@typescript/typescript-darwin-arm64": ["@typescript/typescript-darwin-arm64@7.0.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA=="],
|
||||
|
||||
"@typescript/typescript-darwin-x64": ["@typescript/typescript-darwin-x64@7.0.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA=="],
|
||||
|
||||
"@typescript/typescript-freebsd-arm64": ["@typescript/typescript-freebsd-arm64@7.0.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ=="],
|
||||
|
||||
"@typescript/typescript-freebsd-x64": ["@typescript/typescript-freebsd-x64@7.0.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw=="],
|
||||
|
||||
"@typescript/typescript-linux-arm": ["@typescript/typescript-linux-arm@7.0.2", "", { "os": "linux", "cpu": "arm" }, "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ=="],
|
||||
|
||||
"@typescript/typescript-linux-arm64": ["@typescript/typescript-linux-arm64@7.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ=="],
|
||||
|
||||
"@typescript/typescript-linux-loong64": ["@typescript/typescript-linux-loong64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ=="],
|
||||
|
||||
"@typescript/typescript-linux-mips64el": ["@typescript/typescript-linux-mips64el@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA=="],
|
||||
|
||||
"@typescript/typescript-linux-ppc64": ["@typescript/typescript-linux-ppc64@7.0.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA=="],
|
||||
|
||||
"@typescript/typescript-linux-riscv64": ["@typescript/typescript-linux-riscv64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ=="],
|
||||
|
||||
"@typescript/typescript-linux-s390x": ["@typescript/typescript-linux-s390x@7.0.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw=="],
|
||||
|
||||
"@typescript/typescript-linux-x64": ["@typescript/typescript-linux-x64@7.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A=="],
|
||||
|
||||
"@typescript/typescript-netbsd-arm64": ["@typescript/typescript-netbsd-arm64@7.0.2", "", { "os": "none", "cpu": "arm64" }, "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA=="],
|
||||
|
||||
"@typescript/typescript-netbsd-x64": ["@typescript/typescript-netbsd-x64@7.0.2", "", { "os": "none", "cpu": "x64" }, "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA=="],
|
||||
|
||||
"@typescript/typescript-openbsd-arm64": ["@typescript/typescript-openbsd-arm64@7.0.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ=="],
|
||||
|
||||
"@typescript/typescript-openbsd-x64": ["@typescript/typescript-openbsd-x64@7.0.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg=="],
|
||||
|
||||
"@typescript/typescript-sunos-x64": ["@typescript/typescript-sunos-x64@7.0.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g=="],
|
||||
|
||||
"@typescript/typescript-win32-arm64": ["@typescript/typescript-win32-arm64@7.0.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ=="],
|
||||
|
||||
"@typescript/typescript-win32-x64": ["@typescript/typescript-win32-x64@7.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g=="],
|
||||
|
||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@6.1.1", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "oxc-transform-react": "^0.145.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler", "oxc-transform-react"] }, "sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw=="],
|
||||
|
||||
"accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
|
||||
|
||||
"adler-32": ["adler-32@1.3.1", "", {}, "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A=="],
|
||||
|
||||
"agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="],
|
||||
|
||||
"array-flatten": ["array-flatten@1.1.1", "", {}, "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="],
|
||||
|
||||
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
|
||||
|
||||
"autoprefixer": ["autoprefixer@10.6.1", "", { "dependencies": { "browserslist": "^4.28.9", "caniuse-lite": "^1.0.30001810", "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-cL1Qz6ADZhcEbny/8HPfe99J6HhNoYtpX2LFLIbhgGE7Q1hlQVkYFdetDN7Id3KiQxhDrHwzlHr/YQCnZ8+xSA=="],
|
||||
|
||||
"axios": ["axios@1.20.0", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.6", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg=="],
|
||||
|
||||
"base64-arraybuffer": ["base64-arraybuffer@1.0.2", "", {}, "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ=="],
|
||||
|
||||
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
||||
|
||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.11.26", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-GLQdD3y6UF8iVuMJl5fHgE4jdn/ua7n+toKfLgNlg3BqQtOZjpy68T8Tup8/wGWZCDlm7KMg7tPb4MPn7oN0TQ=="],
|
||||
|
||||
"bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
|
||||
|
||||
"body-parser": ["body-parser@1.20.8", "", { "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "~1.2.0", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", "qs": "~6.16.0", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" } }, "sha512-JNcyFQ64OiijEkPzUBTCe+hyPXUD/3LEldGQ6iF5LR1w00mx9o7xtDWHXBY2iItjdCFGoilOLNQbH943ut7pHA=="],
|
||||
|
||||
"browserslist": ["browserslist@4.29.2", "", { "dependencies": { "baseline-browser-mapping": "^2.11.26", "caniuse-lite": "^1.0.30001812", "electron-to-chromium": "^1.5.439", "node-releases": "^2.0.57", "update-browserslist-db": "^1.3.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-/u9r8k8ue4ZhHCw9ovDtltpWdXeXzjhdxCGj6vm1RGLPXb8lu33EMdoCHF5Sx8oYBS+Q/FYYNmAqlHncxR3RmA=="],
|
||||
|
||||
"buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
|
||||
|
||||
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
|
||||
|
||||
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
||||
|
||||
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001812", "", {}, "sha512-qN+QNNBr93TCmFrmte0bBCjSDMuRvt78VlHT99qIGPszm4QsqCX8lnyWUFkHi8B7ZBAPq8SH+UqyXNy/odMdng=="],
|
||||
|
||||
"canvg": ["canvg@3.0.11", "", { "dependencies": { "@babel/runtime": "^7.12.5", "@types/raf": "^3.4.0", "core-js": "^3.8.3", "raf": "^3.4.1", "regenerator-runtime": "^0.13.7", "rgbcolor": "^1.0.1", "stackblur-canvas": "^2.0.0", "svg-pathdata": "^6.0.3" } }, "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA=="],
|
||||
|
||||
"cfb": ["cfb@1.2.2", "", { "dependencies": { "adler-32": "~1.3.0", "crc-32": "~1.2.0" } }, "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA=="],
|
||||
|
||||
"codepage": ["codepage@1.15.0", "", {}, "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA=="],
|
||||
|
||||
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
|
||||
|
||||
"content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="],
|
||||
|
||||
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
|
||||
|
||||
"cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
|
||||
|
||||
"cookie-signature": ["cookie-signature@1.0.7", "", {}, "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="],
|
||||
|
||||
"core-js": ["core-js@3.50.0", "", {}, "sha512-BRWgOLKkFeCgRudR6zrs8p9XJZcE14grzKMMssoYrk6krtuEZ7MTKPIY5RzOnqsEKIR9kst7wNzphttraT+Yqw=="],
|
||||
|
||||
"crc-32": ["crc-32@1.2.2", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="],
|
||||
|
||||
"css-line-break": ["css-line-break@2.1.0", "", { "dependencies": { "utrie": "^1.0.2" } }, "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w=="],
|
||||
|
||||
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
||||
|
||||
"data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
|
||||
|
||||
"debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||
|
||||
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
|
||||
|
||||
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
|
||||
|
||||
"destroy": ["destroy@1.2.0", "", {}, "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
|
||||
"dompurify": ["dompurify@3.4.16", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-sqo+pNp3qRhCIpbgRi1y8Tgk27Bo2Ry7w0dC1NBeNTdZChWjz9Xb/KOoZbRP/R6pQZ80Qw8YhXw13hWWBbMRnQ=="],
|
||||
|
||||
"dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="],
|
||||
|
||||
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||
|
||||
"ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
|
||||
|
||||
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.439", "", {}, "sha512-qu6QIPXhsb+CRcAiTMNjR4A1y/7tCYKkKjr5CZXRVih6qkDf79peZ2BpEU3qoDBNCRrcy3Mra3X9nG5oruuA7Q=="],
|
||||
|
||||
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
|
||||
|
||||
"enhanced-resolve": ["enhanced-resolve@5.25.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-nGXts5znJzmWPu+mIE9izCOzdg63oJca2mDzGWWTth7sr4aCToKcoyFVBQwN75Ij5Pf6p510EwkTqViTRzDV+w=="],
|
||||
|
||||
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
||||
|
||||
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
||||
|
||||
"es-object-atoms": ["es-object-atoms@1.1.2", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="],
|
||||
|
||||
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
|
||||
|
||||
"esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
|
||||
|
||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||
|
||||
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
|
||||
|
||||
"etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
|
||||
|
||||
"express": ["express@4.22.3", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.5", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.13", "proxy-addr": "~2.0.7", "qs": "~6.16.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-Bdcs4+3qlpVlx2NRn6fgX2Ue2/gGRaPeawebgclM0ERSCqDpA+owF1fdPwjJUTAJWMTuAaxjDf+hzb0/4eKvvw=="],
|
||||
|
||||
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
|
||||
|
||||
"fast-png": ["fast-png@6.4.0", "", { "dependencies": { "@types/pako": "^2.0.3", "iobuffer": "^5.3.2", "pako": "^2.1.0" } }, "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q=="],
|
||||
|
||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
"fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
|
||||
|
||||
"fflate": ["fflate@0.8.3", "", {}, "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA=="],
|
||||
|
||||
"finalhandler": ["finalhandler@1.3.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "statuses": "~2.0.2", "unpipe": "~1.0.0" } }, "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg=="],
|
||||
|
||||
"follow-redirects": ["follow-redirects@1.16.0", "", { "peerDependencies": { "debug": "*" }, "optionalPeers": ["debug"] }, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="],
|
||||
|
||||
"form-data": ["form-data@4.0.6", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.4", "mime-types": "^2.1.35" } }, "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ=="],
|
||||
|
||||
"formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
|
||||
|
||||
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
|
||||
|
||||
"frac": ["frac@1.1.2", "", {}, "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA=="],
|
||||
|
||||
"fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="],
|
||||
|
||||
"framer-motion": ["framer-motion@12.43.0", "", { "dependencies": { "motion-dom": "^12.43.0", "motion-utils": "^12.39.0", "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-1eaL3RvR/kAlbG7UYcpMptEyzPoENO0c6w7ZnB3/hh2vSAz/6uGAFn6fdoqTBguNstf3MsFhJHsD/0DHiclG+g=="],
|
||||
|
||||
"fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="],
|
||||
|
||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||
|
||||
"gaxios": ["gaxios@7.3.1", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-kB3rzJV7d9juLZh8/56QTXCwQfxyhdOMdyYk1HdQKFtF8TJTDTZQJtixWIwXdE9Jji91mC41DUNpjleo4L4eAQ=="],
|
||||
|
||||
"gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="],
|
||||
|
||||
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
||||
|
||||
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
||||
|
||||
"google-auth-library": ["google-auth-library@10.9.1", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.1.4", "gcp-metadata": "8.1.2", "google-logging-utils": "1.1.3", "jws": "^4.0.0" } }, "sha512-i1ydyHrqcIxXkWh/uBmVkzCvIuq5yiK2ATndIe5XxKholrG/MTYP9xGYka4sQhrbIAgGjL2B6NOE7rFaiF3fXw=="],
|
||||
|
||||
"google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="],
|
||||
|
||||
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||
|
||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||
|
||||
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
||||
|
||||
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
|
||||
|
||||
"hashids": ["hashids@2.3.0", "", {}, "sha512-ljM73TE/avEhNnazxaj0Dw3BbEUuLC5yYCQ9RSkSUcT4ZSU6ZebdKCIBJ+xT/DnSYW36E9k82GH1Q6MydSIosQ=="],
|
||||
|
||||
"hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="],
|
||||
|
||||
"html2canvas": ["html2canvas@1.4.1", "", { "dependencies": { "css-line-break": "^2.1.0", "text-segmentation": "^1.0.3" } }, "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA=="],
|
||||
|
||||
"http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
|
||||
|
||||
"https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="],
|
||||
|
||||
"iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
|
||||
|
||||
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||
|
||||
"iobuffer": ["iobuffer@5.4.0", "", {}, "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA=="],
|
||||
|
||||
"ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
|
||||
|
||||
"jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="],
|
||||
|
||||
"json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="],
|
||||
|
||||
"jspdf": ["jspdf@4.2.1", "", { "dependencies": { "@babel/runtime": "^7.28.6", "fast-png": "^6.2.0", "fflate": "^0.8.1" }, "optionalDependencies": { "canvg": "^3.0.11", "core-js": "^3.6.0", "dompurify": "^3.3.1", "html2canvas": "^1.0.0-rc.5" } }, "sha512-YyAXyvnmjTbR4bHQRLzex3CuINCDlQnBqoSYyjJwTP2x9jDLuKDzy7aKUl0hgx3uhcl7xzg32agn5vlie6HIlQ=="],
|
||||
|
||||
"jspdf-autotable": ["jspdf-autotable@5.0.8", "", { "peerDependencies": { "jspdf": "^2 || ^3 || ^4" } }, "sha512-Hy05N86yBO7CXBrnSLOge7i1ZYpKH2DjQ94iybaP7vBhSInjvRBgDc99ngKzSbSO8Jc98ZCally8I6n0tj2RJQ=="],
|
||||
|
||||
"jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
|
||||
|
||||
"jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="],
|
||||
|
||||
"jwt-decode": ["jwt-decode@4.0.0", "", {}, "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA=="],
|
||||
|
||||
"lightningcss": ["lightningcss@1.33.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="],
|
||||
|
||||
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.33.0", "", { "os": "android", "cpu": "arm64" }, "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg=="],
|
||||
|
||||
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.33.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg=="],
|
||||
|
||||
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.33.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ=="],
|
||||
|
||||
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.33.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg=="],
|
||||
|
||||
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.33.0", "", { "os": "linux", "cpu": "arm" }, "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ=="],
|
||||
|
||||
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg=="],
|
||||
|
||||
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ=="],
|
||||
|
||||
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg=="],
|
||||
|
||||
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw=="],
|
||||
|
||||
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.33.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA=="],
|
||||
|
||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.33.0", "", { "os": "win32", "cpu": "x64" }, "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA=="],
|
||||
|
||||
"long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="],
|
||||
|
||||
"lucide-react": ["lucide-react@0.546.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Z94u6fKT43lKeYHiVyvyR8fT7pwCzDu7RyMPpTvh054+xahSgj4HFQ+NmflvzdXsoAjYGdCguGaFKYuvq0ThCQ=="],
|
||||
|
||||
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||
|
||||
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||
|
||||
"media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="],
|
||||
|
||||
"merge-descriptors": ["merge-descriptors@1.0.3", "", {}, "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="],
|
||||
|
||||
"methods": ["methods@1.1.2", "", {}, "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="],
|
||||
|
||||
"mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="],
|
||||
|
||||
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"motion": ["motion@12.43.0", "", { "dependencies": { "framer-motion": "^12.43.0", "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-BQgQbSa9Hn3/mtbib0MK53y6JSANa+YKUKlaYnWzAVDH424RYQ5LVpV3pNiWH00BA2z4ojsSdMzqT7g2FQwjuQ=="],
|
||||
|
||||
"motion-dom": ["motion-dom@12.43.0", "", { "dependencies": { "motion-utils": "^12.39.0" } }, "sha512-azKON4d9S65PEoFUiQTMTgPheEmzf2QngdRc50AKfJp9Q9mmcBVw22c8eMq9k8kxOFHdL7+WZY7N/5F/lwiDag=="],
|
||||
|
||||
"motion-utils": ["motion-utils@12.39.0", "", {}, "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ=="],
|
||||
|
||||
"ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
||||
|
||||
"nanoid": ["nanoid@3.3.19", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug=="],
|
||||
|
||||
"negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
|
||||
|
||||
"node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
|
||||
|
||||
"node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
|
||||
|
||||
"node-releases": ["node-releases@2.0.57", "", {}, "sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw=="],
|
||||
|
||||
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
|
||||
|
||||
"ogl": ["ogl@1.0.11", "", {}, "sha512-kUpC154AFfxi16pmZUK4jk3J+8zxwTWGPo03EoYA8QPbzikHoaC82n6pNTbd+oEaJonaE8aPWBlX7ad9zrqLsA=="],
|
||||
|
||||
"oidc-client-ts": ["oidc-client-ts@3.5.0", "", { "dependencies": { "jwt-decode": "^4.0.0" } }, "sha512-l2q8l9CTCTOlbX+AnK4p3M+4CEpKpyQhle6blQkdFhm0IsBqsxm15bYaSa11G7pWdsYr6epdsRZxJpCyCRbT8A=="],
|
||||
|
||||
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
|
||||
|
||||
"p-retry": ["p-retry@4.6.2", "", { "dependencies": { "@types/retry": "0.12.0", "retry": "^0.13.1" } }, "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ=="],
|
||||
|
||||
"pako": ["pako@2.2.0", "", {}, "sha512-zJq6RP/5q+TO2OpFV3FHzlPnFjmkb7Nc99a5SNjJE+uu/PkpChs+NIZSSzbBoD+6kjiISXjfYdwj1ZRQ81dz/w=="],
|
||||
|
||||
"parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
|
||||
|
||||
"path-to-regexp": ["path-to-regexp@0.1.13", "", {}, "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA=="],
|
||||
|
||||
"performance-now": ["performance-now@2.1.0", "", {}, "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow=="],
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="],
|
||||
|
||||
"postcss": ["postcss@8.5.28", "", { "dependencies": { "nanoid": "^3.3.18", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A=="],
|
||||
|
||||
"postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="],
|
||||
|
||||
"protobufjs": ["protobufjs@7.6.6", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.5", "@protobufjs/eventemitter": "^1.1.1", "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", "long": "^5.3.2" } }, "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg=="],
|
||||
|
||||
"proxy-addr": ["proxy-addr@2.0.8", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ=="],
|
||||
|
||||
"proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="],
|
||||
|
||||
"qs": ["qs@6.16.0", "", { "dependencies": { "es-define-property": "^1.0.1", "side-channel": "^1.1.1" } }, "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA=="],
|
||||
|
||||
"raf": ["raf@3.4.1", "", { "dependencies": { "performance-now": "^2.1.0" } }, "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA=="],
|
||||
|
||||
"range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
|
||||
|
||||
"raw-body": ["raw-body@2.5.3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="],
|
||||
|
||||
"react": ["react@19.3.0", "", {}, "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog=="],
|
||||
|
||||
"react-dom": ["react-dom@19.3.0", "", { "dependencies": { "scheduler": "^0.28.0" }, "peerDependencies": { "react": "^19.3.0" } }, "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q=="],
|
||||
|
||||
"react-hook-form": ["react-hook-form@7.89.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-vKcoCfy8RKZDrhSdqsRFp0uEOdS1AhLjOL6hR7Wmfzg1akNxEhaPf5WORx8SgEQPVXakdDyDkqQp+KNxVnnPHg=="],
|
||||
|
||||
"react-router": ["react-router@7.18.4", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-PUPQcMhMGRAslLcvtlPz/kmzBEWPhLdgLFrL7pLNepBL6dX0lWj4WD2cUYVgYCuT3jxvghYFg81cDTj44DhetQ=="],
|
||||
|
||||
"react-router-dom": ["react-router-dom@7.18.4", "", { "dependencies": { "react-router": "7.18.4" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-yrfmJHIpDG7taCpqKjT1G5B6q3O2K+RN8/fgNf0lTjCwiPbQ0ei6vXX9ZjQR+7ld8Tr7Z5xmyMnZ8YJrphWQUw=="],
|
||||
|
||||
"regenerator-runtime": ["regenerator-runtime@0.13.11", "", {}, "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="],
|
||||
|
||||
"retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="],
|
||||
|
||||
"rgbcolor": ["rgbcolor@1.0.1", "", {}, "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw=="],
|
||||
|
||||
"rolldown": ["rolldown@1.2.11", "", { "dependencies": { "@oxc-project/types": "=0.151.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm-eabi": "1.2.11", "@rolldown/binding-android-arm64": "1.2.11", "@rolldown/binding-darwin-arm64": "1.2.11", "@rolldown/binding-darwin-x64": "1.2.11", "@rolldown/binding-freebsd-x64": "1.2.11", "@rolldown/binding-linux-arm-gnueabihf": "1.2.11", "@rolldown/binding-linux-arm64-gnu": "1.2.11", "@rolldown/binding-linux-arm64-musl": "1.2.11", "@rolldown/binding-linux-ppc64-gnu": "1.2.11", "@rolldown/binding-linux-s390x-gnu": "1.2.11", "@rolldown/binding-linux-x64-gnu": "1.2.11", "@rolldown/binding-linux-x64-musl": "1.2.11", "@rolldown/binding-openharmony-arm64": "1.2.11", "@rolldown/binding-win32-arm64-msvc": "1.2.11", "@rolldown/binding-win32-x64-msvc": "1.2.11" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-qpSwIyz0jHQq5qXBTNxFmE6664rJ7O+4TvPFOiOaBSrz8IOHc1koKKSqTM2H6u1UG1+TveuC6vaDHKXFOvb1Kw=="],
|
||||
|
||||
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||
|
||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||
|
||||
"scheduler": ["scheduler@0.28.0", "", {}, "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw=="],
|
||||
|
||||
"send": ["send@0.19.2", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "~0.5.2", "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "~2.4.1", "range-parser": "~1.2.1", "statuses": "~2.0.2" } }, "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg=="],
|
||||
|
||||
"serve-static": ["serve-static@1.16.3", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "~0.19.1" } }, "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA=="],
|
||||
|
||||
"set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="],
|
||||
|
||||
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
|
||||
|
||||
"side-channel": ["side-channel@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4", "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ=="],
|
||||
|
||||
"side-channel-list": ["side-channel-list@1.0.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" } }, "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w=="],
|
||||
|
||||
"side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
|
||||
|
||||
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
"ssf": ["ssf@0.11.2", "", { "dependencies": { "frac": "~1.1.2" } }, "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g=="],
|
||||
|
||||
"stackblur-canvas": ["stackblur-canvas@2.7.0", "", {}, "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ=="],
|
||||
|
||||
"statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
|
||||
|
||||
"svg-pathdata": ["svg-pathdata@6.0.3", "", {}, "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw=="],
|
||||
|
||||
"tailwindcss": ["tailwindcss@4.3.3", "", {}, "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ=="],
|
||||
|
||||
"tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="],
|
||||
|
||||
"text-segmentation": ["text-segmentation@1.0.3", "", { "dependencies": { "utrie": "^1.0.2" } }, "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="],
|
||||
|
||||
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"tsx": ["tsx@4.23.15", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw=="],
|
||||
|
||||
"type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="],
|
||||
|
||||
"typescript": ["typescript@7.0.2", "", { "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.2", "@typescript/typescript-darwin-arm64": "7.0.2", "@typescript/typescript-darwin-x64": "7.0.2", "@typescript/typescript-freebsd-arm64": "7.0.2", "@typescript/typescript-freebsd-x64": "7.0.2", "@typescript/typescript-linux-arm": "7.0.2", "@typescript/typescript-linux-arm64": "7.0.2", "@typescript/typescript-linux-loong64": "7.0.2", "@typescript/typescript-linux-mips64el": "7.0.2", "@typescript/typescript-linux-ppc64": "7.0.2", "@typescript/typescript-linux-riscv64": "7.0.2", "@typescript/typescript-linux-s390x": "7.0.2", "@typescript/typescript-linux-x64": "7.0.2", "@typescript/typescript-netbsd-arm64": "7.0.2", "@typescript/typescript-netbsd-x64": "7.0.2", "@typescript/typescript-openbsd-arm64": "7.0.2", "@typescript/typescript-openbsd-x64": "7.0.2", "@typescript/typescript-sunos-x64": "7.0.2", "@typescript/typescript-win32-arm64": "7.0.2", "@typescript/typescript-win32-x64": "7.0.2" }, "bin": { "tsc": "bin/tsc" } }, "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA=="],
|
||||
|
||||
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||
|
||||
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
|
||||
|
||||
"update-browserslist-db": ["update-browserslist-db@1.3.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ=="],
|
||||
|
||||
"utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="],
|
||||
|
||||
"utrie": ["utrie@1.0.2", "", { "dependencies": { "base64-arraybuffer": "^1.0.2" } }, "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw=="],
|
||||
|
||||
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
|
||||
|
||||
"vite": ["vite@8.3.1", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.7", "postcss": "^8.5.28", "rolldown": "~1.2.9", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.7.1", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.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", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-/bvH9E9tmCXRGp2uXY3WbOldqpTwFkbha/8ANaEQ6VkxhH60KyqLwgZq6lG2y+4uT55x9+9eUHMpQ7uGnOCKjA=="],
|
||||
|
||||
"web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
|
||||
|
||||
"wmf": ["wmf@1.0.2", "", {}, "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw=="],
|
||||
|
||||
"word": ["word@0.3.0", "", {}, "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA=="],
|
||||
|
||||
"ws": ["ws@8.22.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg=="],
|
||||
|
||||
"xlsx": ["xlsx@0.18.5", "", { "dependencies": { "adler-32": "~1.3.0", "cfb": "~1.2.1", "codepage": "~1.15.0", "crc-32": "~1.2.1", "ssf": "~0.11.2", "wmf": "~1.0.1", "word": "~0.3.0" }, "bin": { "xlsx": "bin/xlsx.njs" } }, "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ=="],
|
||||
|
||||
"zod": ["zod@4.6.5", "", {}, "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q=="],
|
||||
|
||||
"@tailwindcss/node/lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.3", "tslib": "^2.4.0" }, "bundled": true }, "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.2.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" }, "bundled": true }, "sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.4", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"@types/body-parser/@types/node": ["@types/node@26.6.3", "", { "dependencies": { "undici-types": "~8.9.0" } }, "sha512-dsqMQQoeTLqu9wynDD00q573mNzso3IdQOAfHRJqLCcmCFPoGo9A1bDpUcv/9tnKpErQWv9uKeGfl37EIS02Yg=="],
|
||||
|
||||
"@types/connect/@types/node": ["@types/node@26.6.3", "", { "dependencies": { "undici-types": "~8.9.0" } }, "sha512-dsqMQQoeTLqu9wynDD00q573mNzso3IdQOAfHRJqLCcmCFPoGo9A1bDpUcv/9tnKpErQWv9uKeGfl37EIS02Yg=="],
|
||||
|
||||
"@types/express-serve-static-core/@types/node": ["@types/node@26.6.3", "", { "dependencies": { "undici-types": "~8.9.0" } }, "sha512-dsqMQQoeTLqu9wynDD00q573mNzso3IdQOAfHRJqLCcmCFPoGo9A1bDpUcv/9tnKpErQWv9uKeGfl37EIS02Yg=="],
|
||||
|
||||
"@types/send/@types/node": ["@types/node@26.6.3", "", { "dependencies": { "undici-types": "~8.9.0" } }, "sha512-dsqMQQoeTLqu9wynDD00q573mNzso3IdQOAfHRJqLCcmCFPoGo9A1bDpUcv/9tnKpErQWv9uKeGfl37EIS02Yg=="],
|
||||
|
||||
"@types/serve-static/@types/node": ["@types/node@26.6.3", "", { "dependencies": { "undici-types": "~8.9.0" } }, "sha512-dsqMQQoeTLqu9wynDD00q573mNzso3IdQOAfHRJqLCcmCFPoGo9A1bDpUcv/9tnKpErQWv9uKeGfl37EIS02Yg=="],
|
||||
|
||||
"@types/serve-static/@types/send": ["@types/send@0.17.6", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="],
|
||||
|
||||
"agent-base/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"gaxios/https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
|
||||
|
||||
"https-proxy-agent/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"protobufjs/@types/node": ["@types/node@26.6.3", "", { "dependencies": { "undici-types": "~8.9.0" } }, "sha512-dsqMQQoeTLqu9wynDD00q573mNzso3IdQOAfHRJqLCcmCFPoGo9A1bDpUcv/9tnKpErQWv9uKeGfl37EIS02Yg=="],
|
||||
|
||||
"react-router/cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
|
||||
|
||||
"send/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"tsx/esbuild": ["esbuild@0.28.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.2", "@esbuild/android-arm": "0.28.2", "@esbuild/android-arm64": "0.28.2", "@esbuild/android-x64": "0.28.2", "@esbuild/darwin-arm64": "0.28.2", "@esbuild/darwin-x64": "0.28.2", "@esbuild/freebsd-arm64": "0.28.2", "@esbuild/freebsd-x64": "0.28.2", "@esbuild/linux-arm": "0.28.2", "@esbuild/linux-arm64": "0.28.2", "@esbuild/linux-ia32": "0.28.2", "@esbuild/linux-loong64": "0.28.2", "@esbuild/linux-mips64el": "0.28.2", "@esbuild/linux-ppc64": "0.28.2", "@esbuild/linux-riscv64": "0.28.2", "@esbuild/linux-s390x": "0.28.2", "@esbuild/linux-x64": "0.28.2", "@esbuild/netbsd-arm64": "0.28.2", "@esbuild/netbsd-x64": "0.28.2", "@esbuild/openbsd-arm64": "0.28.2", "@esbuild/openbsd-x64": "0.28.2", "@esbuild/openharmony-arm64": "0.28.2", "@esbuild/sunos-x64": "0.28.2", "@esbuild/win32-arm64": "0.28.2", "@esbuild/win32-ia32": "0.28.2", "@esbuild/win32-x64": "0.28.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA=="],
|
||||
|
||||
"@tailwindcss/node/lightningcss/lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
|
||||
|
||||
"@tailwindcss/node/lightningcss/lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="],
|
||||
|
||||
"@tailwindcss/node/lightningcss/lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="],
|
||||
|
||||
"@tailwindcss/node/lightningcss/lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="],
|
||||
|
||||
"@tailwindcss/node/lightningcss/lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="],
|
||||
|
||||
"@tailwindcss/node/lightningcss/lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="],
|
||||
|
||||
"@tailwindcss/node/lightningcss/lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="],
|
||||
|
||||
"@tailwindcss/node/lightningcss/lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="],
|
||||
|
||||
"@tailwindcss/node/lightningcss/lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="],
|
||||
|
||||
"@tailwindcss/node/lightningcss/lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="],
|
||||
|
||||
"@tailwindcss/node/lightningcss/lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
|
||||
|
||||
"@types/body-parser/@types/node/undici-types": ["undici-types@8.9.0", "", {}, "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg=="],
|
||||
|
||||
"@types/connect/@types/node/undici-types": ["undici-types@8.9.0", "", {}, "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg=="],
|
||||
|
||||
"@types/express-serve-static-core/@types/node/undici-types": ["undici-types@8.9.0", "", {}, "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg=="],
|
||||
|
||||
"@types/send/@types/node/undici-types": ["undici-types@8.9.0", "", {}, "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg=="],
|
||||
|
||||
"@types/serve-static/@types/node/undici-types": ["undici-types@8.9.0", "", {}, "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg=="],
|
||||
|
||||
"agent-base/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"gaxios/https-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
|
||||
|
||||
"gaxios/https-proxy-agent/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"https-proxy-agent/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"protobufjs/@types/node/undici-types": ["undici-types@8.9.0", "", {}, "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg=="],
|
||||
|
||||
"tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ=="],
|
||||
|
||||
"tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.2", "", { "os": "android", "cpu": "arm" }, "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg=="],
|
||||
|
||||
"tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.2", "", { "os": "android", "cpu": "arm64" }, "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A=="],
|
||||
|
||||
"tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.28.2", "", { "os": "android", "cpu": "x64" }, "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q=="],
|
||||
|
||||
"tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw=="],
|
||||
|
||||
"tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw=="],
|
||||
|
||||
"tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw=="],
|
||||
|
||||
"tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg=="],
|
||||
|
||||
"tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.2", "", { "os": "linux", "cpu": "arm" }, "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w=="],
|
||||
|
||||
"tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug=="],
|
||||
|
||||
"tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ=="],
|
||||
|
||||
"tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ=="],
|
||||
|
||||
"tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA=="],
|
||||
|
||||
"tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ=="],
|
||||
|
||||
"tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA=="],
|
||||
|
||||
"tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg=="],
|
||||
|
||||
"tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.2", "", { "os": "linux", "cpu": "x64" }, "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ=="],
|
||||
|
||||
"tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw=="],
|
||||
|
||||
"tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.2", "", { "os": "none", "cpu": "x64" }, "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw=="],
|
||||
|
||||
"tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ=="],
|
||||
|
||||
"tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw=="],
|
||||
|
||||
"tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q=="],
|
||||
|
||||
"tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g=="],
|
||||
|
||||
"tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ=="],
|
||||
|
||||
"tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA=="],
|
||||
|
||||
"tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g=="],
|
||||
|
||||
"gaxios/https-proxy-agent/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# Target directory for Nginx static assets in container
|
||||
TARGET_DIR="/usr/share/nginx/html"
|
||||
|
||||
# If not in standard nginx container, fallback to ./dist or ./public if present
|
||||
if [ ! -d "$TARGET_DIR" ]; then
|
||||
if [ -d "./dist" ]; then
|
||||
TARGET_DIR="./dist"
|
||||
elif [ -d "./public" ]; then
|
||||
TARGET_DIR="./public"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Prioritize deployment environment variables from all common naming patterns:
|
||||
# 1. API Base URL
|
||||
API_BASE_URL="${VITE_API_BASE_URL:-${API_BASE_URL:-${ApiSettings__BaseUrl:-${ApiSettings_BaseUrl:-${PFM_API_BASE_URL:-}}}}}"
|
||||
|
||||
# 2. OIDC Security Authority
|
||||
OIDC_AUTHORITY="${VITE_OIDC_AUTHORITY:-${OIDC_AUTHORITY:-${SecuritySettings__Authority:-${SecuritySettings_Authority:-${AUTHORITY:-}}}}}"
|
||||
|
||||
# 3. OIDC Audience
|
||||
OIDC_AUDIENCE="${VITE_OIDC_AUDIENCE:-${OIDC_AUDIENCE:-${SecuritySettings__Audience:-${SecuritySettings_Audience:-${AUDIENCE:-}}}}}"
|
||||
|
||||
# 4. OIDC Client ID
|
||||
OIDC_CLIENT_ID="${VITE_OIDC_CLIENT_ID:-${OIDC_CLIENT_ID:-${SecuritySettings__ClientId:-${SecuritySettings_ClientId:-${CLIENT_ID:-}}}}}"
|
||||
|
||||
# 5. OIDC Client Secret
|
||||
OIDC_CLIENT_SECRET="${VITE_OIDC_CLIENT_SECRET:-${OIDC_CLIENT_SECRET:-${SecuritySettings__ClientSecret:-${SecuritySettings_ClientSecret:-${CLIENT_SECRET:-}}}}}"
|
||||
|
||||
# 6. OIDC Scope
|
||||
OIDC_SCOPE="${VITE_OIDC_SCOPE:-${OIDC_SCOPE:-${SecuritySettings__Scope:-${SecuritySettings_Scope:-${SCOPE:-}}}}}"
|
||||
|
||||
# 7. Hasher Settings
|
||||
HASHER_SALT="${VITE_HASHER_SALT:-${HASHER_SALT:-${HasherSettings__Salt:-${HasherSettings_Salt:-}}}}}"
|
||||
HASHER_MIN_LENGTH="${VITE_HASHER_MIN_LENGTH:-${HASHER_MIN_LENGTH:-${HasherSettings__MinLength:-${HasherSettings_MinLength:-}}}}}"
|
||||
|
||||
if [ -d "$TARGET_DIR" ]; then
|
||||
echo "[docker-entrypoint] Generating runtime configuration files in $TARGET_DIR..."
|
||||
|
||||
# Generate env-config.js for instant, synchronous window.__RUNTIME_CONFIG__ initialization
|
||||
cat <<EOF > "$TARGET_DIR/env-config.js"
|
||||
// Generated dynamically at container startup from deployment environment variables
|
||||
window.__RUNTIME_CONFIG__ = {
|
||||
API_BASE_URL: "${API_BASE_URL}",
|
||||
VITE_API_BASE_URL: "${API_BASE_URL}",
|
||||
OIDC_AUTHORITY: "${OIDC_AUTHORITY}",
|
||||
VITE_OIDC_AUTHORITY: "${OIDC_AUTHORITY}",
|
||||
OIDC_AUDIENCE: "${OIDC_AUDIENCE}",
|
||||
VITE_OIDC_AUDIENCE: "${OIDC_AUDIENCE}",
|
||||
OIDC_CLIENT_ID: "${OIDC_CLIENT_ID}",
|
||||
VITE_OIDC_CLIENT_ID: "${OIDC_CLIENT_ID}",
|
||||
OIDC_CLIENT_SECRET: "${OIDC_CLIENT_SECRET}",
|
||||
VITE_OIDC_CLIENT_SECRET: "${OIDC_CLIENT_SECRET}",
|
||||
OIDC_SCOPE: "${OIDC_SCOPE}",
|
||||
VITE_OIDC_SCOPE: "${OIDC_SCOPE}",
|
||||
HASHER_SALT: "${HASHER_SALT}",
|
||||
VITE_HASHER_SALT: "${HASHER_SALT}",
|
||||
HASHER_MIN_LENGTH: "${HASHER_MIN_LENGTH}",
|
||||
VITE_HASHER_MIN_LENGTH: "${HASHER_MIN_LENGTH}",
|
||||
ApiSettings: {
|
||||
BaseUrl: "${API_BASE_URL}"
|
||||
},
|
||||
SecuritySettings: {
|
||||
Authority: "${OIDC_AUTHORITY}",
|
||||
Audience: "${OIDC_AUDIENCE}",
|
||||
ClientId: "${OIDC_CLIENT_ID}",
|
||||
ClientSecret: "${OIDC_CLIENT_SECRET}",
|
||||
Scope: "${OIDC_SCOPE}"
|
||||
},
|
||||
HasherSettings: {
|
||||
Salt: "${HASHER_SALT}",
|
||||
MinHashLength: "${HASHER_MIN_LENGTH}"
|
||||
}
|
||||
};
|
||||
EOF
|
||||
|
||||
# Generate config.json for REST-based runtime retrieval
|
||||
cat <<EOF > "$TARGET_DIR/config.json"
|
||||
{
|
||||
"ApiSettings": {
|
||||
"BaseUrl": "${API_BASE_URL}"
|
||||
},
|
||||
"SecuritySettings": {
|
||||
"Authority": "${OIDC_AUTHORITY}",
|
||||
"Audience": "${OIDC_AUDIENCE}",
|
||||
"ClientId": "${OIDC_CLIENT_ID}",
|
||||
"ClientSecret": "${OIDC_CLIENT_SECRET}",
|
||||
"Scope": "${OIDC_SCOPE}"
|
||||
},
|
||||
"HasherSettings": {
|
||||
"Salt": "${HASHER_SALT}",
|
||||
"MinHashLength": "${HASHER_MIN_LENGTH}"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "[docker-entrypoint] Successfully wrote $TARGET_DIR/env-config.js and $TARGET_DIR/config.json."
|
||||
fi
|
||||
|
||||
exec "$@"
|
||||
@@ -0,0 +1,18 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="h-full bg-slate-950 text-slate-100">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>PFM Partner</title>
|
||||
<meta name="description" content="PFM Partner Portal" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
|
||||
</head>
|
||||
<body class="h-full bg-slate-950 font-sans text-slate-100 antialiased selection:bg-emerald-500/20 selection:text-emerald-300">
|
||||
<div id="root" class="min-h-full flex flex-col"></div>
|
||||
<script src="/env-config.js"></script>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "Post Fund Management Partner",
|
||||
"description": "PFM Partner portal with single sign-on authentication.",
|
||||
"requestFramePermissions": [],
|
||||
"majorCapabilities": [
|
||||
"MAJOR_CAPABILITY_SERVER_SIDE_GEMINI_API"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
server {
|
||||
listen 3000;
|
||||
server_name localhost;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# Gzip compression
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_min_length 1024;
|
||||
gzip_proxied expired no-cache no-store private auth;
|
||||
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml application/javascript application/json image/svg+xml;
|
||||
gzip_disable "MSIE [1-6]\.";
|
||||
|
||||
# Prevent caching for runtime configuration and entry html
|
||||
location ~* (env-config\.js|config\.json|index\.html)$ {
|
||||
expires -1;
|
||||
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
|
||||
}
|
||||
|
||||
# Static assets caching with hash
|
||||
location /assets/ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, max-age=31536000, immutable";
|
||||
}
|
||||
|
||||
# Single Page Application fallback routing
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "react-example",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --port=3000 --host=0.0.0.0",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"clean": "rm -rf dist server.js",
|
||||
"lint": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@google/genai": "^2.4.0",
|
||||
"@hookform/resolvers": "^5.9.1",
|
||||
"@tailwindcss/vite": "^4.3.3",
|
||||
"@tanstack/react-query": "^5.104.0",
|
||||
"@vitejs/plugin-react": "^6.1.1",
|
||||
"axios": "^1.20.0",
|
||||
"dotenv": "^17.2.3",
|
||||
"express": "^4.21.2",
|
||||
"hashids": "^2.3.0",
|
||||
"jspdf": "^4.2.1",
|
||||
"jspdf-autotable": "^5.0.8",
|
||||
"lucide-react": "^0.546.0",
|
||||
"motion": "^12.23.24",
|
||||
"ogl": "^1.0.11",
|
||||
"oidc-client-ts": "^3.5.0",
|
||||
"react": "^19.0.1",
|
||||
"react-dom": "^19.0.1",
|
||||
"react-hook-form": "^7.89.0",
|
||||
"react-router-dom": "^7.18.4",
|
||||
"vite": "^8.3.0",
|
||||
"xlsx": "^0.18.5",
|
||||
"zod": "^4.6.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.14.0",
|
||||
"@types/react": "^19.3.0",
|
||||
"@types/react-dom": "^19.3.0",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"esbuild": "^0.25.0",
|
||||
"tailwindcss": "^4.3.3",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "^7.0.2",
|
||||
"@types/express": "^4.17.21"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"ApiSettings": {
|
||||
"BaseUrl": "https://api.ifri.khongisa.co.za/"
|
||||
},
|
||||
"SecuritySettings": {
|
||||
"Authority": "https://sts.security.khongisa.co.za",
|
||||
"Audience": "pfm-api-dev",
|
||||
"ClientId": "pfm-scalar-dev",
|
||||
"ClientSecret": "secret_71f88570a46eb22eb5270b47f69e3332c84d8db85d008f0276cb7431c8872d27",
|
||||
"Scope": "openid profile roles email address pfm-api-dev"
|
||||
},
|
||||
"HasherSettings": {
|
||||
"Salt": "TGlnaHQgQ2hhcm1zLCBNaWRyYW5kQm9va3MgbWFrZSBsb3RzIG9mIG1vbmV5IGFuZCBhcmUgYSBzdWNjZXNzZnVsIHZpcmFsIHN0b3J5IGluIFNvdXRoIEFmcmljYQ==",
|
||||
"MinHashLength": 11
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
window.__RUNTIME_CONFIG__ = window.__RUNTIME_CONFIG__ || {};
|
||||
@@ -0,0 +1,112 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="none">
|
||||
<defs>
|
||||
<!-- Anisotropic Brushed Metal Linear Reflection (Multi-band specular reflection of milled titanium/platinum) -->
|
||||
<linearGradient id="brush-metal-primary" x1="0" y1="0" x2="100" y2="100" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0%" stop-color="#FFFFFF" />
|
||||
<stop offset="12%" stop-color="#CBD5E1" />
|
||||
<stop offset="22%" stop-color="#F8FAFC" />
|
||||
<stop offset="38%" stop-color="#64748B" />
|
||||
<stop offset="52%" stop-color="#E2E8F0" />
|
||||
<stop offset="68%" stop-color="#475569" />
|
||||
<stop offset="82%" stop-color="#F1F5F9" />
|
||||
<stop offset="92%" stop-color="#94A3B8" />
|
||||
<stop offset="100%" stop-color="#E2E8F0" />
|
||||
</linearGradient>
|
||||
|
||||
<!-- Cross-cut Brushed Metal Reflection for Intersecting Ribbon Strand -->
|
||||
<linearGradient id="brush-metal-cross" x1="100" y1="0" x2="0" y2="100" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0%" stop-color="#F8FAFC" />
|
||||
<stop offset="18%" stop-color="#94A3B8" />
|
||||
<stop offset="32%" stop-color="#FFFFFF" />
|
||||
<stop offset="48%" stop-color="#475569" />
|
||||
<stop offset="65%" stop-color="#CBD5E1" />
|
||||
<stop offset="80%" stop-color="#334155" />
|
||||
<stop offset="92%" stop-color="#E2E8F0" />
|
||||
<stop offset="100%" stop-color="#64748B" />
|
||||
</linearGradient>
|
||||
|
||||
<!-- Micro-Chamfer Bevel Highlight (Precision CNC milled diamond-cut edge) -->
|
||||
<linearGradient id="brush-metal-bevel" x1="0" y1="50" x2="100" y2="50" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0%" stop-color="#FFFFFF" stop-opacity="0.95" />
|
||||
<stop offset="25%" stop-color="#94A3B8" stop-opacity="0.6" />
|
||||
<stop offset="50%" stop-color="#FFFFFF" stop-opacity="0.9" />
|
||||
<stop offset="75%" stop-color="#64748B" stop-opacity="0.5" />
|
||||
<stop offset="100%" stop-color="#FFFFFF" stop-opacity="0.95" />
|
||||
</linearGradient>
|
||||
|
||||
<!-- Subtle Brushed Texture Grain Pattern -->
|
||||
<pattern id="metal-grain-brush" width="4" height="4" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse">
|
||||
<line x1="0" y1="0" x2="0" y2="4" stroke="#FFFFFF" stroke-width="0.75" stroke-opacity="0.12" />
|
||||
<line x1="2" y1="0" x2="2" y2="4" stroke="#000000" stroke-width="0.75" stroke-opacity="0.1" />
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<!-- Precision Engineered Diagonal Infinity Symbol (Rotated 45°) -->
|
||||
<g transform="rotate(-45 50 50)">
|
||||
<!-- Base Chamfer / Shadow Bevel Core -->
|
||||
<path
|
||||
d="M50 50 C42.5 38 34.5 28 24 28 C12.5 28 4.5 37.5 4.5 50 C4.5 62.5 12.5 72 24 72 C34.5 72 42.5 62 50 50 C57.5 38 65.5 28 76 28 C87.5 28 95.5 37.5 95.5 50 C95.5 62.5 87.5 72 76 72 C65.5 72 57.5 62 50 50"
|
||||
stroke="#1E293B"
|
||||
stroke-width="11"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
|
||||
<!-- Main Milled Titanium Ribbon (Underpass Strand) -->
|
||||
<path
|
||||
d="M50 50 C42.5 38 34.5 28 24 28 C12.5 28 4.5 37.5 4.5 50 C4.5 62.5 12.5 72 24 72 C34.5 72 42.5 62 50 50 C57.5 38 65.5 28 76 28 C87.5 28 95.5 37.5 95.5 50 C95.5 62.5 87.5 72 76 72 C65.5 72 57.5 62 50 50"
|
||||
stroke="url(#brush-metal-primary)"
|
||||
stroke-width="8.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
|
||||
<!-- Brushed Grain Texture Overlay on Base -->
|
||||
<path
|
||||
d="M50 50 C42.5 38 34.5 28 24 28 C12.5 28 4.5 37.5 4.5 50 C4.5 62.5 12.5 72 24 72 C34.5 72 42.5 62 50 50 C57.5 38 65.5 28 76 28 C87.5 28 95.5 37.5 95.5 50 C95.5 62.5 87.5 72 76 72 C65.5 72 57.5 62 50 50"
|
||||
stroke="url(#metal-grain-brush)"
|
||||
stroke-width="8.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
|
||||
<!-- Precision Laser Separation Gap at Center Intersection -->
|
||||
<path
|
||||
d="M41 41 L59 59"
|
||||
stroke="#020617"
|
||||
stroke-width="13"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
|
||||
<!-- Polished Overpass Ribbon Segment (Brushed Stainless / Palladium Finish) -->
|
||||
<path
|
||||
d="M63 37 C57 43 53.5 47 50 50 C46.5 53 43 57 37 63"
|
||||
stroke="url(#brush-metal-cross)"
|
||||
stroke-width="8.5"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
|
||||
<!-- Brushed Grain Texture Overlay on Overpass -->
|
||||
<path
|
||||
d="M63 37 C57 43 53.5 47 50 50 C46.5 53 43 57 37 63"
|
||||
stroke="url(#metal-grain-brush)"
|
||||
stroke-width="8.5"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
|
||||
<!-- Precision Diamond-Milled Specular Ridge (Hairline Chamfer) -->
|
||||
<path
|
||||
d="M24 30 C14 30 7 38.5 7 50 C7 61.5 14 70 24 70 C33.5 70 41 61 48 52 M52 48 C59 39 66.5 30 76 30 C86 30 93 38.5 93 50 C93 61.5 86 70 76 70 C66.5 70 59 61 52 52"
|
||||
stroke="url(#brush-metal-bevel)"
|
||||
stroke-width="1.2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
fill="none"
|
||||
/>
|
||||
|
||||
<!-- Exclusive Watchmaker Specular Glints -->
|
||||
<circle cx="24" cy="28" r="1.6" fill="#FFFFFF" />
|
||||
<circle cx="76" cy="72" r="1.6" fill="#FFFFFF" />
|
||||
<circle cx="50" cy="50" r="1.2" fill="#FFFFFF" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import { QueryClientProvider } from '@tanstack/react-query';
|
||||
import { queryClient } from './lib/queryClient';
|
||||
import { AuthProvider, useAuth } from './auth/AuthProvider';
|
||||
import { LandingPage } from './components/landing/LandingPage';
|
||||
import { AppNavbar } from './components/layout/AppNavbar';
|
||||
|
||||
function AppContent() {
|
||||
const { isAuthenticated, isLoading, login } = useAuth();
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-slate-950 text-slate-400 text-xs">
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<div className="h-6 w-6 animate-spin rounded-full border-2 border-emerald-500 border-t-transparent" />
|
||||
<span>Loading session...</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!isAuthenticated) {
|
||||
return <LandingPage onLogin={login} isLoading={isLoading} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col bg-slate-950 text-slate-100">
|
||||
<AppNavbar />
|
||||
<main className="flex-1 mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8 py-8">
|
||||
{/* Clean empty workspace ready for user instructions */}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AuthProvider>
|
||||
<AppContent />
|
||||
</AuthProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
import React, { createContext, useContext, useEffect, useState, ReactNode } from 'react';
|
||||
import { UserManager, WebStorageStateStore } from 'oidc-client-ts';
|
||||
import { getSecuritySettings, loadRuntimeConfig } from '../config';
|
||||
import { parseJwtClaims } from './jwt';
|
||||
import { syncUserAccount, clearUserSyncInBrowser, isActivationPending } from '../services/userSyncService';
|
||||
import { onSessionExpired, clearSessionAndRedirectToLanding } from '../services/sessionExpiration';
|
||||
|
||||
export interface AuthUserProfile {
|
||||
id: string;
|
||||
sub: string;
|
||||
name: string;
|
||||
email: string;
|
||||
role: string;
|
||||
tenant: string;
|
||||
tenant_id: number | string;
|
||||
}
|
||||
|
||||
interface AuthContextType {
|
||||
user: AuthUserProfile | null;
|
||||
sub: string;
|
||||
claims: Record<string, any>;
|
||||
accessToken: string | null;
|
||||
isAuthenticated: boolean;
|
||||
isLoading: boolean;
|
||||
isPendingActivation: boolean;
|
||||
login: () => Promise<void>;
|
||||
logout: () => Promise<void>;
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||
|
||||
/**
|
||||
* Derives the user profile strictly from JWT token claims.
|
||||
* If name, email, tenant, or tenant_id are not present, returns null (user is signed out).
|
||||
*/
|
||||
export function deriveUserProfileFromTokenClaims(claims: Record<string, any>, rawSub?: string): AuthUserProfile | null {
|
||||
if (!claims || typeof claims !== 'object' || Object.keys(claims).length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Derive name
|
||||
const name =
|
||||
claims.name ||
|
||||
claims.given_name ||
|
||||
claims.preferred_username ||
|
||||
claims.nickname ||
|
||||
(claims.first_name && claims.last_name ? `${claims.first_name} ${claims.last_name}` : undefined);
|
||||
|
||||
// Derive email
|
||||
const email =
|
||||
claims.email ||
|
||||
claims.upn ||
|
||||
claims.email_address ||
|
||||
claims['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'];
|
||||
|
||||
// Derive tenant (tenant name)
|
||||
const tenant =
|
||||
claims.tenant ||
|
||||
claims.tenant_name ||
|
||||
claims.tenantName ||
|
||||
claims.organisation ||
|
||||
claims.org ||
|
||||
claims.organization;
|
||||
|
||||
// Derive tenant_id
|
||||
const rawTenantId =
|
||||
claims.tenant_id ??
|
||||
claims.tenantId ??
|
||||
claims.tenantid ??
|
||||
claims.org_id ??
|
||||
claims.organisation_id ??
|
||||
claims.organization_id ??
|
||||
claims.tid;
|
||||
|
||||
// If any required property is missing, user is signed out
|
||||
if (!name || !email || !tenant || rawTenantId === undefined || rawTenantId === null || String(rawTenantId).trim() === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const sub = claims.sub || rawSub || String(rawTenantId);
|
||||
const role =
|
||||
claims.role ||
|
||||
(Array.isArray(claims.roles) ? claims.roles[0] : claims.roles) ||
|
||||
'Partner';
|
||||
|
||||
return {
|
||||
id: String(sub),
|
||||
sub: String(sub),
|
||||
name: String(name).trim(),
|
||||
email: String(email).trim(),
|
||||
role: String(role).trim(),
|
||||
tenant: String(tenant).trim(),
|
||||
tenant_id: typeof rawTenantId === 'number' ? rawTenantId : String(rawTenantId).trim(),
|
||||
};
|
||||
}
|
||||
|
||||
export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const [user, setUser] = useState<AuthUserProfile | null>(null);
|
||||
const [accessToken, setAccessToken] = useState<string | null>(() => {
|
||||
return typeof window !== 'undefined' ? localStorage.getItem('pfm_access_token') : null;
|
||||
});
|
||||
const [claims, setClaims] = useState<Record<string, any>>({});
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [userManager, setUserManager] = useState<UserManager | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
|
||||
async function initOidc() {
|
||||
try {
|
||||
await loadRuntimeConfig();
|
||||
const sec = getSecuritySettings();
|
||||
const authorityUrl = sec.Authority.replace(/\/+$/, '');
|
||||
|
||||
const mgr = new UserManager({
|
||||
authority: authorityUrl,
|
||||
client_id: sec.ClientId,
|
||||
client_secret: sec.ClientSecret,
|
||||
redirect_uri: window.location.origin,
|
||||
post_logout_redirect_uri: window.location.origin,
|
||||
response_type: 'code',
|
||||
scope: sec.Scope || 'openid profile email roles pfm-api-dev',
|
||||
userStore: new WebStorageStateStore({ store: window.localStorage }),
|
||||
metadata: {
|
||||
issuer: authorityUrl,
|
||||
authorization_endpoint: `${authorityUrl}/connect/authorize`,
|
||||
token_endpoint: `${authorityUrl}/connect/token`,
|
||||
userinfo_endpoint: `${authorityUrl}/connect/userinfo`,
|
||||
end_session_endpoint: `${authorityUrl}/connect/endsession`,
|
||||
jwks_uri: `${authorityUrl}/.well-known/openid-configuration/jwks`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!active) return;
|
||||
setUserManager(mgr);
|
||||
|
||||
// 1. Process OIDC redirect code if returning from STS
|
||||
if (window.location.search.includes('code=')) {
|
||||
try {
|
||||
const oidcUser = await mgr.signinCallback();
|
||||
if (oidcUser && oidcUser.access_token) {
|
||||
const tokenClaims = parseJwtClaims(oidcUser.access_token);
|
||||
const combinedClaims = { ...tokenClaims, ...(oidcUser.profile || {}) };
|
||||
const derivedProfile = deriveUserProfileFromTokenClaims(combinedClaims, oidcUser.profile?.sub);
|
||||
|
||||
if (derivedProfile) {
|
||||
localStorage.setItem('pfm_access_token', oidcUser.access_token);
|
||||
localStorage.setItem('pfm_user_sub', derivedProfile.sub);
|
||||
localStorage.setItem('pfm_auth_user', JSON.stringify(derivedProfile));
|
||||
localStorage.setItem('pfm_user_claims', JSON.stringify(combinedClaims));
|
||||
|
||||
setAccessToken(oidcUser.access_token);
|
||||
setClaims(combinedClaims);
|
||||
setUser(derivedProfile);
|
||||
|
||||
try {
|
||||
await syncUserAccount(derivedProfile.sub, oidcUser.access_token, derivedProfile);
|
||||
} catch {}
|
||||
} else {
|
||||
clearUserSyncInBrowser();
|
||||
localStorage.removeItem('pfm_access_token');
|
||||
localStorage.removeItem('pfm_auth_user');
|
||||
setAccessToken(null);
|
||||
setUser(null);
|
||||
}
|
||||
}
|
||||
window.history.replaceState({}, document.title, window.location.pathname);
|
||||
} catch (err) {
|
||||
console.error('[Auth] Callback error:', err);
|
||||
}
|
||||
} else {
|
||||
// 2. Evaluate existing stored token
|
||||
const storedToken = localStorage.getItem('pfm_access_token');
|
||||
if (storedToken) {
|
||||
const tokenClaims = parseJwtClaims(storedToken);
|
||||
const derivedProfile = deriveUserProfileFromTokenClaims(tokenClaims);
|
||||
|
||||
if (derivedProfile) {
|
||||
setAccessToken(storedToken);
|
||||
setClaims(tokenClaims);
|
||||
setUser(derivedProfile);
|
||||
localStorage.setItem('pfm_auth_user', JSON.stringify(derivedProfile));
|
||||
} else {
|
||||
clearUserSyncInBrowser();
|
||||
localStorage.removeItem('pfm_access_token');
|
||||
localStorage.removeItem('pfm_auth_user');
|
||||
setAccessToken(null);
|
||||
setUser(null);
|
||||
}
|
||||
} else {
|
||||
setUser(null);
|
||||
setAccessToken(null);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('[Auth] OIDC initialization notice:', err);
|
||||
setUser(null);
|
||||
setAccessToken(null);
|
||||
} finally {
|
||||
if (active) setIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
initOidc();
|
||||
const unsubSession = onSessionExpired(() => clearSessionAndRedirectToLanding());
|
||||
return () => {
|
||||
active = false;
|
||||
unsubSession();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const login = async () => {
|
||||
if (userManager) {
|
||||
try {
|
||||
await userManager.signinRedirect();
|
||||
} catch (err) {
|
||||
console.error('[Auth] Redirect error:', err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const logout = async () => {
|
||||
clearUserSyncInBrowser();
|
||||
clearSessionAndRedirectToLanding();
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthContext.Provider
|
||||
value={{
|
||||
user,
|
||||
sub: user?.sub || '',
|
||||
claims,
|
||||
accessToken,
|
||||
isAuthenticated: !!user && !!accessToken,
|
||||
isLoading,
|
||||
isPendingActivation: isActivationPending(),
|
||||
login,
|
||||
logout,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useAuth = () => {
|
||||
const ctx = useContext(AuthContext);
|
||||
if (!ctx) throw new Error('useAuth must be used within AuthProvider');
|
||||
return ctx;
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
export function parseJwtClaims(token: string): Record<string, any> {
|
||||
if (!token || typeof token !== 'string') return {};
|
||||
try {
|
||||
const parts = token.split('.');
|
||||
if (parts.length < 2) return {};
|
||||
const base64Url = parts[1];
|
||||
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
|
||||
const jsonPayload = decodeURIComponent(
|
||||
window
|
||||
.atob(base64)
|
||||
.split('')
|
||||
.map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
|
||||
.join('')
|
||||
);
|
||||
return JSON.parse(jsonPayload);
|
||||
} catch (err) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
.border-glow-card {
|
||||
--edge-proximity: 0;
|
||||
--cursor-angle: 45deg;
|
||||
--edge-sensitivity: 30;
|
||||
--color-sensitivity: calc(var(--edge-sensitivity) + 20);
|
||||
--border-radius: 28px;
|
||||
--glow-padding: 40px;
|
||||
--cone-spread: 25;
|
||||
|
||||
position: relative;
|
||||
border-radius: var(--border-radius);
|
||||
isolation: isolate;
|
||||
transform: translate3d(0, 0, 0.01px);
|
||||
display: grid;
|
||||
border: 1px solid rgb(255 255 255 / 15%);
|
||||
background: var(--card-bg, #120F17);
|
||||
overflow: visible;
|
||||
box-shadow:
|
||||
rgba(0, 0, 0, 0.1) 0px 1px 2px,
|
||||
rgba(0, 0, 0, 0.1) 0px 2px 4px,
|
||||
rgba(0, 0, 0, 0.1) 0px 4px 8px,
|
||||
rgba(0, 0, 0, 0.1) 0px 8px 16px,
|
||||
rgba(0, 0, 0, 0.1) 0px 16px 32px,
|
||||
rgba(0, 0, 0, 0.1) 0px 32px 64px;
|
||||
}
|
||||
|
||||
.border-glow-card--light {
|
||||
border-color: rgb(24 24 27 / 12%);
|
||||
box-shadow:
|
||||
rgb(24 24 27 / 4%) 0 1px 2px,
|
||||
rgb(24 24 27 / 5%) 0 8px 24px;
|
||||
}
|
||||
|
||||
.border-glow-card--light::after,
|
||||
.border-glow-card--light > .edge-light {
|
||||
mix-blend-mode: normal;
|
||||
}
|
||||
|
||||
.border-glow-card::before,
|
||||
.border-glow-card::after,
|
||||
.border-glow-card > .edge-light {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
transition: opacity 0.25s ease-out;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.border-glow-card:not(:hover):not(.sweep-active)::before,
|
||||
.border-glow-card:not(:hover):not(.sweep-active)::after,
|
||||
.border-glow-card:not(:hover):not(.sweep-active) > .edge-light {
|
||||
opacity: 0;
|
||||
transition: opacity 0.75s ease-in-out;
|
||||
}
|
||||
|
||||
/* colored mesh-gradient border */
|
||||
.border-glow-card::before {
|
||||
border: 1px solid transparent;
|
||||
background:
|
||||
linear-gradient(var(--card-bg, #120F17) 0 100%) padding-box,
|
||||
linear-gradient(rgb(255 255 255 / 0%) 0% 100%) border-box,
|
||||
var(--gradient-one, radial-gradient(at 80% 55%, hsla(268, 100%, 76%, 1) 0px, transparent 50%)) border-box,
|
||||
var(--gradient-two, radial-gradient(at 69% 34%, hsla(349, 100%, 74%, 1) 0px, transparent 50%)) border-box,
|
||||
var(--gradient-three, radial-gradient(at 8% 6%, hsla(136, 100%, 78%, 1) 0px, transparent 50%)) border-box,
|
||||
var(--gradient-four, radial-gradient(at 41% 38%, hsla(192, 100%, 64%, 1) 0px, transparent 50%)) border-box,
|
||||
var(--gradient-five, radial-gradient(at 86% 85%, hsla(186, 100%, 74%, 1) 0px, transparent 50%)) border-box,
|
||||
var(--gradient-six, radial-gradient(at 82% 18%, hsla(52, 100%, 65%, 1) 0px, transparent 50%)) border-box,
|
||||
var(--gradient-seven, radial-gradient(at 51% 4%, hsla(12, 100%, 72%, 1) 0px, transparent 50%)) border-box,
|
||||
var(--gradient-base, linear-gradient(#c299ff 0 100%)) border-box;
|
||||
|
||||
opacity: calc((var(--edge-proximity) - var(--color-sensitivity)) / (100 - var(--color-sensitivity)));
|
||||
|
||||
mask-image:
|
||||
conic-gradient(
|
||||
from var(--cursor-angle) at center,
|
||||
black calc(var(--cone-spread) * 1%),
|
||||
transparent calc((var(--cone-spread) + 15) * 1%),
|
||||
transparent calc((100 - var(--cone-spread) - 15) * 1%),
|
||||
black calc((100 - var(--cone-spread)) * 1%)
|
||||
);
|
||||
}
|
||||
|
||||
/* colored mesh-gradient background fill near edges */
|
||||
.border-glow-card::after {
|
||||
border: 1px solid transparent;
|
||||
background:
|
||||
var(--gradient-one, radial-gradient(at 80% 55%, hsla(268, 100%, 76%, 1) 0px, transparent 50%)) padding-box,
|
||||
var(--gradient-two, radial-gradient(at 69% 34%, hsla(349, 100%, 74%, 1) 0px, transparent 50%)) padding-box,
|
||||
var(--gradient-three, radial-gradient(at 8% 6%, hsla(136, 100%, 78%, 1) 0px, transparent 50%)) padding-box,
|
||||
var(--gradient-four, radial-gradient(at 41% 38%, hsla(192, 100%, 64%, 1) 0px, transparent 50%)) padding-box,
|
||||
var(--gradient-five, radial-gradient(at 86% 85%, hsla(186, 100%, 74%, 1) 0px, transparent 50%)) padding-box,
|
||||
var(--gradient-six, radial-gradient(at 82% 18%, hsla(52, 100%, 65%, 1) 0px, transparent 50%)) padding-box,
|
||||
var(--gradient-seven, radial-gradient(at 51% 4%, hsla(12, 100%, 72%, 1) 0px, transparent 50%)) padding-box,
|
||||
var(--gradient-base, linear-gradient(#c299ff 0 100%)) padding-box;
|
||||
|
||||
mask-image:
|
||||
linear-gradient(to bottom, black, black),
|
||||
radial-gradient(ellipse at 50% 50%, black 40%, transparent 65%),
|
||||
radial-gradient(ellipse at 66% 66%, black 5%, transparent 40%),
|
||||
radial-gradient(ellipse at 33% 33%, black 5%, transparent 40%),
|
||||
radial-gradient(ellipse at 66% 33%, black 5%, transparent 40%),
|
||||
radial-gradient(ellipse at 33% 66%, black 5%, transparent 40%),
|
||||
conic-gradient(from var(--cursor-angle) at center, transparent 5%, black 15%, black 85%, transparent 95%);
|
||||
|
||||
mask-composite: subtract, add, add, add, add, add;
|
||||
opacity: calc(var(--fill-opacity, 0.5) * (var(--edge-proximity) - var(--color-sensitivity)) / (100 - var(--color-sensitivity)));
|
||||
mix-blend-mode: soft-light;
|
||||
}
|
||||
|
||||
/* outer glow layer */
|
||||
.border-glow-card > .edge-light {
|
||||
inset: calc(var(--glow-padding) * -1);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
|
||||
mask-image:
|
||||
conic-gradient(
|
||||
from var(--cursor-angle) at center, black 2.5%, transparent 10%, transparent 90%, black 97.5%
|
||||
);
|
||||
|
||||
opacity: calc((var(--edge-proximity) - var(--edge-sensitivity)) / (100 - var(--edge-sensitivity)));
|
||||
mix-blend-mode: plus-lighter;
|
||||
}
|
||||
|
||||
.border-glow-card > .edge-light::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: var(--glow-padding);
|
||||
border-radius: inherit;
|
||||
box-shadow:
|
||||
inset 0 0 0 1px var(--glow-color, hsl(40deg 80% 80% / 100%)),
|
||||
inset 0 0 1px 0 var(--glow-color-60, hsl(40deg 80% 80% / 60%)),
|
||||
inset 0 0 3px 0 var(--glow-color-50, hsl(40deg 80% 80% / 50%)),
|
||||
inset 0 0 6px 0 var(--glow-color-40, hsl(40deg 80% 80% / 40%)),
|
||||
inset 0 0 15px 0 var(--glow-color-30, hsl(40deg 80% 80% / 30%)),
|
||||
inset 0 0 25px 2px var(--glow-color-20, hsl(40deg 80% 80% / 20%)),
|
||||
inset 0 0 50px 2px var(--glow-color-10, hsl(40deg 80% 80% / 10%)),
|
||||
0 0 1px 0 var(--glow-color-60, hsl(40deg 80% 80% / 60%)),
|
||||
0 0 3px 0 var(--glow-color-50, hsl(40deg 80% 80% / 50%)),
|
||||
0 0 6px 0 var(--glow-color-40, hsl(40deg 80% 80% / 40%)),
|
||||
0 0 15px 0 var(--glow-color-30, hsl(40deg 80% 80% / 30%)),
|
||||
0 0 25px 2px var(--glow-color-20, hsl(40deg 80% 80% / 20%)),
|
||||
0 0 50px 2px var(--glow-color-10, hsl(40deg 80% 80% / 10%));
|
||||
}
|
||||
|
||||
.border-glow-inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
z-index: 1;
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
import React, { useRef, useCallback, useEffect, ReactNode } from 'react';
|
||||
import './BorderGlow.css';
|
||||
|
||||
export interface BorderGlowProps {
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
edgeSensitivity?: number;
|
||||
glowColor?: string;
|
||||
backgroundColor?: string;
|
||||
borderRadius?: number;
|
||||
glowRadius?: number;
|
||||
glowIntensity?: number;
|
||||
coneSpread?: number;
|
||||
animated?: boolean;
|
||||
colors?: string[];
|
||||
fillOpacity?: number;
|
||||
}
|
||||
|
||||
function parseHSL(hslStr: string) {
|
||||
const match = hslStr.match(/([\d.]+)\s*([\d.]+)%?\s*([\d.]+)%?/);
|
||||
if (!match) return { h: 40, s: 80, l: 80 };
|
||||
return { h: parseFloat(match[1]), s: parseFloat(match[2]), l: parseFloat(match[3]) };
|
||||
}
|
||||
|
||||
function buildGlowVars(glowColor: string, intensity: number) {
|
||||
const { h, s, l } = parseHSL(glowColor);
|
||||
const base = `${h}deg ${s}% ${l}%`;
|
||||
const opacities = [100, 60, 50, 40, 30, 20, 10];
|
||||
const keys = ['', '-60', '-50', '-40', '-30', '-20', '-10'];
|
||||
const vars: Record<string, string> = {};
|
||||
for (let i = 0; i < opacities.length; i++) {
|
||||
vars[`--glow-color${keys[i]}`] = `hsl(${base} / ${Math.min(opacities[i] * intensity, 100)}%)`;
|
||||
}
|
||||
return vars;
|
||||
}
|
||||
|
||||
const GRADIENT_POSITIONS = ['80% 55%', '69% 34%', '8% 6%', '41% 38%', '86% 85%', '82% 18%', '51% 4%'];
|
||||
const GRADIENT_KEYS = ['--gradient-one', '--gradient-two', '--gradient-three', '--gradient-four', '--gradient-five', '--gradient-six', '--gradient-seven'];
|
||||
const COLOR_MAP = [0, 1, 2, 0, 1, 2, 1];
|
||||
|
||||
function buildGradientVars(colors: string[]) {
|
||||
const vars: Record<string, string> = {};
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const c = colors[Math.min(COLOR_MAP[i], colors.length - 1)];
|
||||
vars[GRADIENT_KEYS[i]] = `radial-gradient(at ${GRADIENT_POSITIONS[i]}, ${c} 0px, transparent 50%)`;
|
||||
}
|
||||
vars['--gradient-base'] = `linear-gradient(${colors[0]} 0 100%)`;
|
||||
return vars;
|
||||
}
|
||||
|
||||
function isLightColor(color: string) {
|
||||
const value = color.trim().replace('#', '');
|
||||
if (!/^[\da-f]{3}([\da-f]{3})?$/i.test(value)) return false;
|
||||
const hex = value.length === 3 ? value.split('').map(char => char + char).join('') : value;
|
||||
const red = parseInt(hex.slice(0, 2), 16);
|
||||
const green = parseInt(hex.slice(2, 4), 16);
|
||||
const blue = parseInt(hex.slice(4, 6), 16);
|
||||
return red * 0.2126 + green * 0.7152 + blue * 0.0722 > 180;
|
||||
}
|
||||
|
||||
function easeOutCubic(x: number) { return 1 - Math.pow(1 - x, 3); }
|
||||
function easeInCubic(x: number) { return x * x * x; }
|
||||
|
||||
interface AnimateOptions {
|
||||
start?: number;
|
||||
end?: number;
|
||||
duration?: number;
|
||||
delay?: number;
|
||||
ease?: (x: number) => number;
|
||||
onUpdate: (val: number) => void;
|
||||
onEnd?: () => void;
|
||||
}
|
||||
|
||||
function animateValue({ start = 0, end = 100, duration = 1000, delay = 0, ease = easeOutCubic, onUpdate, onEnd }: AnimateOptions) {
|
||||
const t0 = performance.now() + delay;
|
||||
function tick() {
|
||||
const elapsed = performance.now() - t0;
|
||||
const t = Math.min(elapsed / duration, 1);
|
||||
onUpdate(start + (end - start) * ease(t));
|
||||
if (t < 1) requestAnimationFrame(tick);
|
||||
else if (onEnd) onEnd();
|
||||
}
|
||||
setTimeout(() => requestAnimationFrame(tick), delay);
|
||||
}
|
||||
|
||||
export const BorderGlow: React.FC<BorderGlowProps> = ({
|
||||
children,
|
||||
className = '',
|
||||
edgeSensitivity = 30,
|
||||
glowColor = '160 80 70',
|
||||
backgroundColor = '#0f172a',
|
||||
borderRadius = 20,
|
||||
glowRadius = 35,
|
||||
glowIntensity = 1.0,
|
||||
coneSpread = 25,
|
||||
animated = false,
|
||||
colors = ['#34d399', '#2dd4bf', '#38bdf8'],
|
||||
fillOpacity = 0.4,
|
||||
}) => {
|
||||
const cardRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const getCenterOfElement = useCallback((el: HTMLElement) => {
|
||||
const { width, height } = el.getBoundingClientRect();
|
||||
return [width / 2, height / 2];
|
||||
}, []);
|
||||
|
||||
const getEdgeProximity = useCallback((el: HTMLElement, x: number, y: number) => {
|
||||
const [cx, cy] = getCenterOfElement(el);
|
||||
const dx = x - cx;
|
||||
const dy = y - cy;
|
||||
let kx = Infinity;
|
||||
let ky = Infinity;
|
||||
if (dx !== 0) kx = cx / Math.abs(dx);
|
||||
if (dy !== 0) ky = cy / Math.abs(dy);
|
||||
return Math.min(Math.max(1 / Math.min(kx, ky), 0), 1);
|
||||
}, [getCenterOfElement]);
|
||||
|
||||
const getCursorAngle = useCallback((el: HTMLElement, x: number, y: number) => {
|
||||
const [cx, cy] = getCenterOfElement(el);
|
||||
const dx = x - cx;
|
||||
const dy = y - cy;
|
||||
if (dx === 0 && dy === 0) return 0;
|
||||
const radians = Math.atan2(dy, dx);
|
||||
let degrees = radians * (180 / Math.PI) + 90;
|
||||
if (degrees < 0) degrees += 360;
|
||||
return degrees;
|
||||
}, [getCenterOfElement]);
|
||||
|
||||
const handlePointerMove = useCallback((e: React.PointerEvent<HTMLDivElement>) => {
|
||||
const card = cardRef.current;
|
||||
if (!card) return;
|
||||
|
||||
const rect = card.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const y = e.clientY - rect.top;
|
||||
|
||||
const edge = getEdgeProximity(card, x, y);
|
||||
const angle = getCursorAngle(card, x, y);
|
||||
|
||||
card.style.setProperty('--edge-proximity', `${(edge * 100).toFixed(3)}`);
|
||||
card.style.setProperty('--cursor-angle', `${angle.toFixed(3)}deg`);
|
||||
}, [getEdgeProximity, getCursorAngle]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!animated || !cardRef.current) return;
|
||||
const card = cardRef.current;
|
||||
const angleStart = 110;
|
||||
const angleEnd = 465;
|
||||
card.classList.add('sweep-active');
|
||||
card.style.setProperty('--cursor-angle', `${angleStart}deg`);
|
||||
|
||||
animateValue({ duration: 500, onUpdate: v => card.style.setProperty('--edge-proximity', String(v)) });
|
||||
animateValue({ ease: easeInCubic, duration: 1500, end: 50, onUpdate: v => {
|
||||
card.style.setProperty('--cursor-angle', `${(angleEnd - angleStart) * (v / 100) + angleStart}deg`);
|
||||
}});
|
||||
animateValue({ ease: easeOutCubic, delay: 1500, duration: 2250, start: 50, end: 100, onUpdate: v => {
|
||||
card.style.setProperty('--cursor-angle', `${(angleEnd - angleStart) * (v / 100) + angleStart}deg`);
|
||||
}});
|
||||
animateValue({ ease: easeInCubic, delay: 2500, duration: 1500, start: 100, end: 0,
|
||||
onUpdate: v => card.style.setProperty('--edge-proximity', String(v)),
|
||||
onEnd: () => card.classList.remove('sweep-active'),
|
||||
});
|
||||
}, [animated]);
|
||||
|
||||
const glowVars = buildGlowVars(glowColor, glowIntensity);
|
||||
const lightSurface = isLightColor(backgroundColor);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={cardRef}
|
||||
onPointerMove={handlePointerMove}
|
||||
className={`border-glow-card${lightSurface ? ' border-glow-card--light' : ''} ${className}`}
|
||||
style={{
|
||||
'--card-bg': backgroundColor,
|
||||
'--edge-sensitivity': edgeSensitivity,
|
||||
'--border-radius': `${borderRadius}px`,
|
||||
'--glow-padding': `${glowRadius}px`,
|
||||
'--cone-spread': coneSpread,
|
||||
'--fill-opacity': fillOpacity,
|
||||
...glowVars,
|
||||
...buildGradientVars(colors),
|
||||
} as React.CSSProperties}
|
||||
>
|
||||
<span className="edge-light" />
|
||||
<div className="border-glow-inner">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BorderGlow;
|
||||
@@ -0,0 +1,6 @@
|
||||
.molten-metal-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { Renderer, Program, Mesh, Triangle } from 'ogl';
|
||||
import './MoltenMetal.css';
|
||||
|
||||
interface MoltenMetalProps {
|
||||
color1?: string;
|
||||
color2?: string;
|
||||
color3?: string;
|
||||
speed?: number;
|
||||
scale?: number;
|
||||
detail?: number;
|
||||
glow?: number;
|
||||
coreSize?: number;
|
||||
swirl?: number;
|
||||
fold?: number;
|
||||
blackPoint?: number;
|
||||
brightness?: number;
|
||||
colorMode?: 'molten' | 'ember' | 'frost';
|
||||
grain?: boolean;
|
||||
grainIntensity?: number;
|
||||
mouseInteraction?: boolean;
|
||||
mouseStrength?: number;
|
||||
opacity?: number;
|
||||
backgroundColor?: string;
|
||||
lightMode?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const hexToRgb = (hex: string): [number, number, number] => {
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
if (!result) return [1, 1, 1];
|
||||
return [parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255];
|
||||
};
|
||||
|
||||
const colorModeToFloat = (mode: string) => (mode === 'ember' ? 1 : mode === 'frost' ? 2 : 0);
|
||||
|
||||
const vertex = `#version 300 es
|
||||
in vec2 position;
|
||||
void main() {
|
||||
gl_Position = vec4(position, 0.0, 1.0);
|
||||
}
|
||||
`;
|
||||
|
||||
const fragment = `#version 300 es
|
||||
precision highp float;
|
||||
uniform vec2 iResolution;
|
||||
uniform float iTime;
|
||||
uniform float uSpeed;
|
||||
uniform float uScale;
|
||||
uniform float uDetail;
|
||||
uniform float uGlow;
|
||||
uniform float uCoreSize;
|
||||
uniform float uSwirl;
|
||||
uniform float uFold;
|
||||
uniform float uBlackPoint;
|
||||
uniform float uBrightness;
|
||||
uniform float uColorMode;
|
||||
uniform float uGrain;
|
||||
uniform float uGrainIntensity;
|
||||
uniform float uOpacity;
|
||||
uniform vec2 uMouse;
|
||||
uniform float uMouseStrength;
|
||||
uniform bool uEnableMouse;
|
||||
uniform vec3 uColor1;
|
||||
uniform vec3 uColor2;
|
||||
uniform vec3 uColor3;
|
||||
uniform vec3 uBackgroundColor;
|
||||
uniform bool uLightMode;
|
||||
out vec4 fragColor;
|
||||
|
||||
float hash(vec2 p) {
|
||||
return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453);
|
||||
}
|
||||
|
||||
void main() {
|
||||
float time = iTime * uSpeed;
|
||||
vec2 p = uScale * ((gl_FragCoord.xy - 0.5 * iResolution.xy) / iResolution.y) - 0.5;
|
||||
|
||||
vec2 drift = vec2(0.0);
|
||||
if (uEnableMouse) {
|
||||
drift = (uMouse - 0.5) * uMouseStrength * 2.0;
|
||||
}
|
||||
p += drift;
|
||||
|
||||
vec2 i = p;
|
||||
float c = 0.0;
|
||||
float r = length(p + vec2(sin(time), sin(time * 0.3 + 5.0)) * 0.5);
|
||||
float d = length(p);
|
||||
float rot = d + time + p.x * uSwirl;
|
||||
|
||||
float cosRot = cos(rot);
|
||||
mat2 warp = mat2(cos(rot - sin(time / 5.0)), sin(rot), -sin(cosRot - time), cosRot) * uFold;
|
||||
float glowCore = uGlow * uCoreSize;
|
||||
|
||||
for (float n = 0.0; n < 8.0; n++) {
|
||||
if (n >= uDetail) break;
|
||||
p *= warp;
|
||||
float t = r - time / (n + 3.0);
|
||||
i -= p + vec2(cos(t - i.x - r) + sin(t + i.y), sin(t - i.y) + cos(t + i.x) + r);
|
||||
c += glowCore / length(vec2(sin(i.x + t), cos(i.y + t)));
|
||||
}
|
||||
|
||||
c /= 6.0;
|
||||
|
||||
float intensity = max(c - uBlackPoint, 0.0) * uBrightness;
|
||||
|
||||
float g = clamp(intensity, 0.0, 1.0);
|
||||
|
||||
float mid = 0.5;
|
||||
if (uColorMode > 1.5) {
|
||||
mid = 0.65;
|
||||
} else if (uColorMode > 0.5) {
|
||||
mid = 0.35;
|
||||
}
|
||||
|
||||
vec3 col = mix(uColor1, uColor2, smoothstep(0.0, mid, g));
|
||||
col = mix(col, uColor3, smoothstep(mid, 1.0, g));
|
||||
|
||||
float a = g;
|
||||
if (uGrain > 0.5) {
|
||||
float gr = hash(gl_FragCoord.xy + iTime);
|
||||
a += (gr - 0.5) * uGrainIntensity;
|
||||
}
|
||||
a = clamp(a, 0.0, 1.0) * uOpacity;
|
||||
if (uLightMode) {
|
||||
float signal = 1.0 - exp(-max(c, 0.0) * 6.5);
|
||||
float body = smoothstep(0.075, 0.68, signal);
|
||||
float ridge = smoothstep(0.42, 0.92, signal);
|
||||
|
||||
vec3 lightCol = mix(uColor1, uColor2, smoothstep(0.08, 0.52, signal));
|
||||
lightCol = mix(lightCol, uColor3, smoothstep(0.52, 0.96, signal));
|
||||
lightCol = mix(lightCol, lightCol * 0.72, ridge * 0.24);
|
||||
|
||||
float coverage = body * mix(0.2, 0.86, signal) * uOpacity;
|
||||
if (uGrain > 0.5) {
|
||||
float gr = hash(gl_FragCoord.xy + iTime);
|
||||
coverage += (gr - 0.5) * uGrainIntensity * body * 0.16;
|
||||
}
|
||||
fragColor = vec4(mix(uBackgroundColor, lightCol, clamp(coverage, 0.0, 0.92)), 1.0);
|
||||
} else {
|
||||
fragColor = vec4(col * a, a);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const ctxMap = new WeakMap<HTMLElement, any>();
|
||||
|
||||
export const MoltenMetal: React.FC<MoltenMetalProps> = ({
|
||||
color1 = '#047857',
|
||||
color2 = '#10B981',
|
||||
color3 = '#A7F3D0',
|
||||
speed = 0.35,
|
||||
scale = 4,
|
||||
detail = 3,
|
||||
glow = 1.6,
|
||||
coreSize = 0.1,
|
||||
swirl = 1,
|
||||
fold = -0.2,
|
||||
blackPoint = 0.05,
|
||||
brightness = 1.3,
|
||||
colorMode = 'molten',
|
||||
grain = true,
|
||||
grainIntensity = 0.05,
|
||||
mouseInteraction = true,
|
||||
mouseStrength = 0.3,
|
||||
opacity = 1.0,
|
||||
backgroundColor = '#020617',
|
||||
lightMode = false,
|
||||
className = ''
|
||||
}) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
if (!container) return;
|
||||
|
||||
const renderer = new Renderer({
|
||||
webgl: 2,
|
||||
alpha: true,
|
||||
premultipliedAlpha: true,
|
||||
antialias: false,
|
||||
dpr: Math.min(typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1, 2)
|
||||
});
|
||||
|
||||
const gl = renderer.gl;
|
||||
gl.clearColor(0, 0, 0, 0);
|
||||
const canvas = gl.canvas;
|
||||
canvas.style.width = '100%';
|
||||
canvas.style.height = '100%';
|
||||
canvas.style.display = 'block';
|
||||
container.appendChild(canvas);
|
||||
|
||||
const geometry = new Triangle(gl);
|
||||
const program = new Program(gl, {
|
||||
vertex,
|
||||
fragment,
|
||||
uniforms: {
|
||||
iTime: { value: 0 },
|
||||
iResolution: { value: new Float32Array([1, 1]) },
|
||||
uSpeed: { value: 0.35 },
|
||||
uScale: { value: 4 },
|
||||
uDetail: { value: 3 },
|
||||
uGlow: { value: 1.6 },
|
||||
uCoreSize: { value: 0.1 },
|
||||
uSwirl: { value: 1 },
|
||||
uFold: { value: -0.2 },
|
||||
uBlackPoint: { value: 0.05 },
|
||||
uBrightness: { value: 1.3 },
|
||||
uColorMode: { value: 0 },
|
||||
uGrain: { value: 1 },
|
||||
uGrainIntensity: { value: 0.05 },
|
||||
uOpacity: { value: 1.0 },
|
||||
uMouse: { value: new Float32Array([0.5, 0.5]) },
|
||||
uMouseStrength: { value: 0.3 },
|
||||
uEnableMouse: { value: true },
|
||||
uColor1: { value: new Float32Array([1, 1, 1]) },
|
||||
uColor2: { value: new Float32Array([1, 1, 1]) },
|
||||
uColor3: { value: new Float32Array([1, 1, 1]) },
|
||||
uBackgroundColor: { value: new Float32Array([1, 1, 1]) },
|
||||
uLightMode: { value: false }
|
||||
}
|
||||
});
|
||||
|
||||
const mesh = new Mesh(gl, { geometry, program });
|
||||
ctxMap.set(container, { renderer, program, mesh });
|
||||
|
||||
const setSize = () => {
|
||||
const rect = container.getBoundingClientRect();
|
||||
const w = Math.max(1, Math.floor(rect.width));
|
||||
const h = Math.max(1, Math.floor(rect.height));
|
||||
renderer.setSize(w, h);
|
||||
const res = program.uniforms.iResolution.value;
|
||||
res[0] = gl.drawingBufferWidth;
|
||||
res[1] = gl.drawingBufferHeight;
|
||||
renderer.render({ scene: mesh });
|
||||
};
|
||||
|
||||
const ro = new ResizeObserver(setSize);
|
||||
ro.observe(container);
|
||||
setSize();
|
||||
|
||||
const targetMouse = [0.5, 0.5];
|
||||
const currentMouse = [0.5, 0.5];
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
targetMouse[0] = (e.clientX - rect.left) / rect.width;
|
||||
targetMouse[1] = 1.0 - (e.clientY - rect.top) / rect.height;
|
||||
};
|
||||
const handleMouseLeave = () => {
|
||||
targetMouse[0] = 0.5;
|
||||
targetMouse[1] = 0.5;
|
||||
};
|
||||
canvas.addEventListener('mousemove', handleMouseMove);
|
||||
canvas.addEventListener('mouseleave', handleMouseLeave);
|
||||
|
||||
let raf = 0;
|
||||
let isVisible = true;
|
||||
let isPageVisible = !document.hidden;
|
||||
const t0 = performance.now();
|
||||
|
||||
const loop = (t: number) => {
|
||||
program.uniforms.iTime.value = (t - t0) * 0.001;
|
||||
currentMouse[0] += 0.05 * (targetMouse[0] - currentMouse[0]);
|
||||
currentMouse[1] += 0.05 * (targetMouse[1] - currentMouse[1]);
|
||||
program.uniforms.uMouse.value[0] = currentMouse[0];
|
||||
program.uniforms.uMouse.value[1] = currentMouse[1];
|
||||
renderer.render({ scene: mesh });
|
||||
raf = requestAnimationFrame(loop);
|
||||
};
|
||||
|
||||
const tryStart = () => {
|
||||
if (isVisible && isPageVisible && raf === 0) raf = requestAnimationFrame(loop);
|
||||
};
|
||||
const tryStop = () => {
|
||||
if (raf !== 0) {
|
||||
cancelAnimationFrame(raf);
|
||||
raf = 0;
|
||||
}
|
||||
};
|
||||
|
||||
const io = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
isVisible = entry.isIntersecting;
|
||||
isVisible ? tryStart() : tryStop();
|
||||
},
|
||||
{ threshold: 0 }
|
||||
);
|
||||
io.observe(container);
|
||||
|
||||
const onVisibility = () => {
|
||||
isPageVisible = !document.hidden;
|
||||
isPageVisible ? tryStart() : tryStop();
|
||||
};
|
||||
document.addEventListener('visibilitychange', onVisibility);
|
||||
|
||||
tryStart();
|
||||
|
||||
return () => {
|
||||
tryStop();
|
||||
ro.disconnect();
|
||||
io.disconnect();
|
||||
document.removeEventListener('visibilitychange', onVisibility);
|
||||
canvas.removeEventListener('mousemove', handleMouseMove);
|
||||
canvas.removeEventListener('mouseleave', handleMouseLeave);
|
||||
ctxMap.delete(container);
|
||||
if (canvas.parentNode === container) container.removeChild(canvas);
|
||||
gl.getExtension('WEBGL_lose_context')?.loseContext();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
if (!container) return;
|
||||
const ctx = ctxMap.get(container);
|
||||
if (!ctx) return;
|
||||
const u = ctx.program.uniforms;
|
||||
|
||||
u.uSpeed.value = speed;
|
||||
u.uScale.value = scale;
|
||||
u.uDetail.value = detail;
|
||||
u.uGlow.value = glow;
|
||||
u.uCoreSize.value = Math.max(coreSize, 0.001);
|
||||
u.uSwirl.value = swirl;
|
||||
u.uFold.value = fold;
|
||||
u.uBlackPoint.value = blackPoint;
|
||||
u.uBrightness.value = brightness;
|
||||
u.uColorMode.value = colorModeToFloat(colorMode);
|
||||
u.uGrain.value = grain ? 1 : 0;
|
||||
u.uGrainIntensity.value = grainIntensity;
|
||||
u.uOpacity.value = opacity;
|
||||
u.uMouseStrength.value = mouseStrength;
|
||||
u.uEnableMouse.value = mouseInteraction;
|
||||
u.uLightMode.value = lightMode;
|
||||
const c1 = hexToRgb(color1);
|
||||
const c2 = hexToRgb(color2);
|
||||
const c3 = hexToRgb(color3);
|
||||
const bg = hexToRgb(backgroundColor);
|
||||
const uc1 = u.uColor1.value;
|
||||
const uc2 = u.uColor2.value;
|
||||
const uc3 = u.uColor3.value;
|
||||
uc1[0] = c1[0];
|
||||
uc1[1] = c1[1];
|
||||
uc1[2] = c1[2];
|
||||
uc2[0] = c2[0];
|
||||
uc2[1] = c2[1];
|
||||
uc2[2] = c2[2];
|
||||
uc3[0] = c3[0];
|
||||
uc3[1] = c3[1];
|
||||
uc3[2] = c3[2];
|
||||
u.uBackgroundColor.value[0] = bg[0];
|
||||
u.uBackgroundColor.value[1] = bg[1];
|
||||
u.uBackgroundColor.value[2] = bg[2];
|
||||
}, [
|
||||
color1,
|
||||
color2,
|
||||
color3,
|
||||
speed,
|
||||
scale,
|
||||
detail,
|
||||
glow,
|
||||
coreSize,
|
||||
swirl,
|
||||
fold,
|
||||
blackPoint,
|
||||
brightness,
|
||||
colorMode,
|
||||
grain,
|
||||
grainIntensity,
|
||||
mouseInteraction,
|
||||
mouseStrength,
|
||||
opacity,
|
||||
backgroundColor,
|
||||
lightMode
|
||||
]);
|
||||
|
||||
return <div ref={containerRef} className={`molten-metal-container ${className}`.trim()} />;
|
||||
};
|
||||
|
||||
export default MoltenMetal;
|
||||
@@ -0,0 +1,127 @@
|
||||
import React from 'react';
|
||||
|
||||
interface PfmPartnerLogoProps {
|
||||
className?: string;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export const PfmPartnerLogo: React.FC<PfmPartnerLogoProps> = ({ className = 'h-8 w-8', size }) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 100 100"
|
||||
fill="none"
|
||||
className={className}
|
||||
style={size ? { width: size, height: size } : undefined}
|
||||
>
|
||||
<defs>
|
||||
{/* Anisotropic Brushed Platinum & Titanium Multi-Band Reflections */}
|
||||
<linearGradient id="pfm-brush-metal-prim" x1="0" y1="0" x2="100" y2="100" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0%" stopColor="#FFFFFF" />
|
||||
<stop offset="12%" stopColor="#CBD5E1" />
|
||||
<stop offset="22%" stopColor="#F8FAFC" />
|
||||
<stop offset="38%" stopColor="#64748B" />
|
||||
<stop offset="52%" stopColor="#E2E8F0" />
|
||||
<stop offset="68%" stopColor="#475569" />
|
||||
<stop offset="82%" stopColor="#F1F5F9" />
|
||||
<stop offset="92%" stopColor="#94A3B8" />
|
||||
<stop offset="100%" stopColor="#E2E8F0" />
|
||||
</linearGradient>
|
||||
|
||||
<linearGradient id="pfm-brush-metal-cross" x1="100" y1="0" x2="0" y2="100" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0%" stopColor="#F8FAFC" />
|
||||
<stop offset="18%" stopColor="#94A3B8" />
|
||||
<stop offset="32%" stopColor="#FFFFFF" />
|
||||
<stop offset="48%" stopColor="#475569" />
|
||||
<stop offset="65%" stopColor="#CBD5E1" />
|
||||
<stop offset="80%" stopColor="#334155" />
|
||||
<stop offset="92%" stopColor="#E2E8F0" />
|
||||
<stop offset="100%" stopColor="#64748B" />
|
||||
</linearGradient>
|
||||
|
||||
<linearGradient id="pfm-brush-metal-bevel" x1="0" y1="50" x2="100" y2="50" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0%" stopColor="#FFFFFF" stopOpacity="0.95" />
|
||||
<stop offset="25%" stopColor="#94A3B8" stopOpacity="0.6" />
|
||||
<stop offset="50%" stopColor="#FFFFFF" stopOpacity="0.9" />
|
||||
<stop offset="75%" stopColor="#64748B" stopOpacity="0.5" />
|
||||
<stop offset="100%" stopColor="#FFFFFF" stopOpacity="0.95" />
|
||||
</linearGradient>
|
||||
|
||||
{/* Micro-Linear Brushed Surface Grain */}
|
||||
<pattern id="pfm-metal-grain" width="4" height="4" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse">
|
||||
<line x1="0" y1="0" x2="0" y2="4" stroke="#FFFFFF" strokeWidth="0.75" strokeOpacity="0.12" />
|
||||
<line x1="2" y1="0" x2="2" y2="4" stroke="#000000" strokeWidth="0.75" strokeOpacity="0.1" />
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
{/* Milled Brushed Metal Diagonal Infinity Lemniscate Rotated 45° */}
|
||||
<g transform="rotate(-45 50 50)">
|
||||
{/* Milled Base Chamfer Housing */}
|
||||
<path
|
||||
d="M50 50 C42.5 38 34.5 28 24 28 C12.5 28 4.5 37.5 4.5 50 C4.5 62.5 12.5 72 24 72 C34.5 72 42.5 62 50 50 C57.5 38 65.5 28 76 28 C87.5 28 95.5 37.5 95.5 50 C95.5 62.5 87.5 72 76 72 C65.5 72 57.5 62 50 50"
|
||||
stroke="#1E293B"
|
||||
strokeWidth="11"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
|
||||
{/* Main Brushed Titanium Ribbon (Underpass Loop) */}
|
||||
<path
|
||||
d="M50 50 C42.5 38 34.5 28 24 28 C12.5 28 4.5 37.5 4.5 50 C4.5 62.5 12.5 72 24 72 C34.5 72 42.5 62 50 50 C57.5 38 65.5 28 76 28 C87.5 28 95.5 37.5 95.5 50 C95.5 62.5 87.5 72 76 72 C65.5 72 57.5 62 50 50"
|
||||
stroke="url(#pfm-brush-metal-prim)"
|
||||
strokeWidth="8.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
|
||||
{/* Grain Texture On Underpass */}
|
||||
<path
|
||||
d="M50 50 C42.5 38 34.5 28 24 28 C12.5 28 4.5 37.5 4.5 50 C4.5 62.5 12.5 72 24 72 C34.5 72 42.5 62 50 50 C57.5 38 65.5 28 76 28 C87.5 28 95.5 37.5 95.5 50 C95.5 62.5 87.5 72 76 72 C65.5 72 57.5 62 50 50"
|
||||
stroke="url(#pfm-metal-grain)"
|
||||
strokeWidth="8.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
|
||||
{/* Laser Precision Crossover Separation Gap */}
|
||||
<path
|
||||
d="M41 41 L59 59"
|
||||
stroke="#020617"
|
||||
strokeWidth="13"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
|
||||
{/* Forefront Overpass Ribbon (Cross-milled Palladium Finish) */}
|
||||
<path
|
||||
d="M63 37 C57 43 53.5 47 50 50 C46.5 53 43 57 37 63"
|
||||
stroke="url(#pfm-brush-metal-cross)"
|
||||
strokeWidth="8.5"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
|
||||
{/* Grain Texture On Overpass */}
|
||||
<path
|
||||
d="M63 37 C57 43 53.5 47 50 50 C46.5 53 43 57 37 63"
|
||||
stroke="url(#pfm-metal-grain)"
|
||||
strokeWidth="8.5"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
|
||||
{/* Precision Diamond-Milled Specular Ridge (Hairline Chamfer) */}
|
||||
<path
|
||||
d="M24 30 C14 30 7 38.5 7 50 C7 61.5 14 70 24 70 C33.5 70 41 61 48 52 M52 48 C59 39 66.5 30 76 30 C86 30 93 38.5 93 50 C93 61.5 86 70 76 70 C66.5 70 59 61 52 52"
|
||||
stroke="url(#pfm-brush-metal-bevel)"
|
||||
strokeWidth="1.2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
fill="none"
|
||||
/>
|
||||
|
||||
{/* Specular Glints */}
|
||||
<circle cx="24" cy="28" r="1.6" fill="#FFFFFF" />
|
||||
<circle cx="76" cy="72" r="1.6" fill="#FFFFFF" />
|
||||
<circle cx="50" cy="50" r="1.2" fill="#FFFFFF" />
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
.specular-button {
|
||||
--sb-radius: 18px;
|
||||
--sb-tint: #ffffff;
|
||||
--sb-tint-opacity: 0;
|
||||
--sb-blur: 0px;
|
||||
--sb-text-color: #f5f5f5;
|
||||
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.01em;
|
||||
line-height: 1;
|
||||
color: var(--sb-text-color);
|
||||
background: color-mix(in srgb, var(--sb-tint) calc(var(--sb-tint-opacity) * 100%), transparent);
|
||||
border-radius: var(--sb-radius);
|
||||
backdrop-filter: blur(var(--sb-blur));
|
||||
-webkit-backdrop-filter: blur(var(--sb-blur));
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.12),
|
||||
0 8px 32px rgba(0, 0, 0, 0.35);
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
transition: transform 0.15s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.specular-button:active {
|
||||
transform: scale(0.97);
|
||||
}
|
||||
|
||||
.specular-button:focus-visible {
|
||||
outline: 2px solid color-mix(in srgb, var(--sb-text-color) 60%, transparent);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
.specular-button:disabled {
|
||||
opacity: 0.55;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.specular-button:disabled:active {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.specular-button--sm {
|
||||
font-size: 0.85rem;
|
||||
padding: 10px 22px;
|
||||
}
|
||||
|
||||
.specular-button--md {
|
||||
font-size: 0.95rem;
|
||||
padding: 14px 30px;
|
||||
}
|
||||
|
||||
.specular-button--lg {
|
||||
font-size: 1.05rem;
|
||||
padding: 16px 36px;
|
||||
}
|
||||
|
||||
/* Canvas extends past the button so the rim glow can bleed outside the edge */
|
||||
.specular-button__fx {
|
||||
position: absolute;
|
||||
inset: -20px;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.specular-button__fx canvas {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.specular-button__label {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.625rem;
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
import React, { useRef, useEffect, ReactNode, MouseEventHandler } from 'react';
|
||||
import { Renderer, Program, Mesh, Triangle, Color } from 'ogl';
|
||||
import './SpecularButton.css';
|
||||
|
||||
const PAD = 20;
|
||||
|
||||
const VERT = `#version 300 es
|
||||
in vec2 position;
|
||||
void main() {
|
||||
gl_Position = vec4(position, 0.0, 1.0);
|
||||
}
|
||||
`;
|
||||
|
||||
const FRAG = `#version 300 es
|
||||
precision highp float;
|
||||
|
||||
uniform vec2 uCenter;
|
||||
uniform vec2 uHalfSize;
|
||||
uniform float uRadius;
|
||||
uniform float uAngle;
|
||||
uniform float uPx;
|
||||
uniform vec3 uLineColor;
|
||||
uniform vec3 uBaseColor;
|
||||
uniform float uIntensity;
|
||||
uniform float uShineSize;
|
||||
uniform float uShineFade;
|
||||
uniform float uThickness;
|
||||
uniform float uBaseWidth;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
float sdRoundedRect(vec2 p, vec2 b, float r) {
|
||||
vec2 q = abs(p) - b + r;
|
||||
return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - r;
|
||||
}
|
||||
|
||||
float shapeSDF(vec2 p) { return sdRoundedRect(p, uHalfSize, uRadius); }
|
||||
|
||||
float gaussianLine(float d, float sigma) {
|
||||
float x = d / (sigma + 1e-6);
|
||||
float k = mix(1.0, 1.6, smoothstep(0.0, 1.5, x));
|
||||
return exp(-k * x * x);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 p = gl_FragCoord.xy - uCenter;
|
||||
float d = shapeSDF(p);
|
||||
vec2 L = vec2(cos(uAngle), sin(uAngle));
|
||||
|
||||
// Dark base stroke hugging the edge for a sense of thickness
|
||||
float base = (1.0 - smoothstep(0.0, uBaseWidth, abs(d))) * 0.45;
|
||||
|
||||
// Symmetric specular
|
||||
vec2 nEll = normalize(p / (uHalfSize * uHalfSize) + 1e-6);
|
||||
float phi = acos(clamp(abs(dot(nEll, L)), 0.0, 1.0));
|
||||
float rim = 1.0 - smoothstep(uShineSize - uShineFade, uShineSize + uShineFade + 1e-4, phi);
|
||||
float line = gaussianLine(d, uThickness);
|
||||
float edgeClamp = 1.0 - smoothstep(0.5 * uPx, 3.0 * uPx, abs(d));
|
||||
float hi = line * rim * edgeClamp * uIntensity;
|
||||
|
||||
vec3 col = uBaseColor * base + uLineColor * hi;
|
||||
float a = clamp(base + hi, 0.0, 1.0);
|
||||
fragColor = vec4(col, a);
|
||||
}
|
||||
`;
|
||||
|
||||
export interface SpecularButtonProps {
|
||||
children?: ReactNode;
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
radius?: number;
|
||||
tint?: string;
|
||||
tintOpacity?: number;
|
||||
blur?: number;
|
||||
textColor?: string;
|
||||
lineColor?: string;
|
||||
baseColor?: string;
|
||||
intensity?: number;
|
||||
shineSize?: number;
|
||||
shineFade?: number;
|
||||
thickness?: number;
|
||||
speed?: number;
|
||||
followMouse?: boolean;
|
||||
proximity?: number;
|
||||
autoAnimate?: boolean;
|
||||
disabled?: boolean;
|
||||
onClick?: MouseEventHandler<HTMLButtonElement>;
|
||||
className?: string;
|
||||
type?: 'button' | 'submit' | 'reset';
|
||||
}
|
||||
|
||||
export const SpecularButton: React.FC<SpecularButtonProps> = ({
|
||||
children = 'Get Started',
|
||||
size = 'lg',
|
||||
radius = 18,
|
||||
tint = '#ffffff',
|
||||
tintOpacity = 0,
|
||||
blur = 0,
|
||||
textColor = '#f5f5f5',
|
||||
lineColor = '#ffffff',
|
||||
baseColor = '#525252',
|
||||
intensity = 1,
|
||||
shineSize = 10,
|
||||
shineFade = 40,
|
||||
thickness = 1,
|
||||
speed = 0.35,
|
||||
followMouse = true,
|
||||
proximity = 250,
|
||||
autoAnimate = false,
|
||||
disabled = false,
|
||||
onClick,
|
||||
className = '',
|
||||
type = 'button'
|
||||
}) => {
|
||||
const btnRef = useRef<HTMLButtonElement>(null);
|
||||
const fxRef = useRef<HTMLSpanElement>(null);
|
||||
const propsRef = useRef<any>({});
|
||||
|
||||
propsRef.current = { radius, lineColor, baseColor, intensity, shineSize, shineFade, thickness, speed, followMouse, proximity, autoAnimate };
|
||||
|
||||
useEffect(() => {
|
||||
const btn = btnRef.current;
|
||||
const fx = fxRef.current;
|
||||
if (!btn || !fx) return;
|
||||
|
||||
const dpr = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;
|
||||
const renderer = new Renderer({ alpha: true, premultipliedAlpha: true, antialias: true, dpr });
|
||||
const gl = renderer.gl;
|
||||
gl.clearColor(0, 0, 0, 0);
|
||||
gl.enable(gl.BLEND);
|
||||
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
const geometry = new Triangle(gl);
|
||||
if ((geometry.attributes as any).uv) delete (geometry.attributes as any).uv;
|
||||
|
||||
const program = new Program(gl, {
|
||||
vertex: VERT,
|
||||
fragment: FRAG,
|
||||
uniforms: {
|
||||
uCenter: { value: [0, 0] },
|
||||
uHalfSize: { value: [1, 1] },
|
||||
uRadius: { value: 0 },
|
||||
uAngle: { value: 2.4 },
|
||||
uPx: { value: dpr },
|
||||
uLineColor: { value: [1, 1, 1] },
|
||||
uBaseColor: { value: [0.32, 0.32, 0.32] },
|
||||
uIntensity: { value: 1 },
|
||||
uShineSize: { value: 0.17 },
|
||||
uShineFade: { value: 0.7 },
|
||||
uThickness: { value: 1 },
|
||||
uBaseWidth: { value: dpr }
|
||||
}
|
||||
});
|
||||
|
||||
const mesh = new Mesh(gl, { geometry, program });
|
||||
fx.appendChild(gl.canvas);
|
||||
|
||||
const sizeRef = { w: 1, h: 1 };
|
||||
const resize = () => {
|
||||
const rect = btn.getBoundingClientRect();
|
||||
const w = rect.width;
|
||||
const h = rect.height;
|
||||
sizeRef.w = w;
|
||||
sizeRef.h = h;
|
||||
renderer.setSize(w + PAD * 2, h + PAD * 2);
|
||||
program.uniforms.uCenter.value = [(PAD + w / 2) * dpr, (PAD + h / 2) * dpr];
|
||||
program.uniforms.uHalfSize.value = [(w / 2) * dpr, (h / 2) * dpr];
|
||||
};
|
||||
const ro = new ResizeObserver(resize);
|
||||
ro.observe(btn);
|
||||
resize();
|
||||
|
||||
let pointerAngle: number | null = null;
|
||||
let proximityT = 0;
|
||||
const onPointerMove = (e: PointerEvent) => {
|
||||
const rect = btn.getBoundingClientRect();
|
||||
const cx = rect.left + rect.width / 2;
|
||||
const cy = rect.top + rect.height / 2;
|
||||
const dx = Math.max(rect.left - e.clientX, 0, e.clientX - rect.right);
|
||||
const dy = Math.max(rect.top - e.clientY, 0, e.clientY - rect.bottom);
|
||||
const dist = Math.hypot(dx, dy);
|
||||
|
||||
if (dist === 0) {
|
||||
const nx = (e.clientX - cx) / (rect.width / 2);
|
||||
const ny = (cy - e.clientY) / (rect.height / 2);
|
||||
pointerAngle = Math.atan2(2 / rect.height, -2 / rect.width) + nx * 0.3 + ny * 0.15;
|
||||
} else {
|
||||
pointerAngle = Math.atan2(cy - e.clientY, e.clientX - cx);
|
||||
}
|
||||
const t = Math.max(0, 1 - dist / Math.max(propsRef.current.proximity, 1));
|
||||
proximityT = t * t * (3 - 2 * t);
|
||||
};
|
||||
window.addEventListener('pointermove', onPointerMove);
|
||||
|
||||
let angle = 2.4;
|
||||
let idleAngle = 2.4;
|
||||
let bright = 0;
|
||||
let last = performance.now();
|
||||
let raf = 0;
|
||||
|
||||
const lineC = new Color();
|
||||
const baseC = new Color();
|
||||
|
||||
const update = (now: number) => {
|
||||
raf = requestAnimationFrame(update);
|
||||
const dt = Math.min((now - last) / 1000, 0.05);
|
||||
last = now;
|
||||
const p = propsRef.current;
|
||||
|
||||
idleAngle += p.speed * dt;
|
||||
const steer = p.followMouse && pointerAngle != null && (!p.autoAnimate || proximityT > 0);
|
||||
const target = (steer && pointerAngle != null) ? pointerAngle : idleAngle;
|
||||
const diff = ((target - angle + Math.PI * 3) % (Math.PI * 2)) - Math.PI;
|
||||
angle += diff * (1 - Math.exp(-dt * 7));
|
||||
|
||||
const brightTarget = p.autoAnimate ? 1 : proximityT;
|
||||
bright += (brightTarget - bright) * (1 - Math.exp(-dt * 8));
|
||||
|
||||
lineC.set(p.lineColor);
|
||||
baseC.set(p.baseColor);
|
||||
program.uniforms.uAngle.value = angle;
|
||||
program.uniforms.uRadius.value = Math.min(p.radius, Math.min(sizeRef.w, sizeRef.h) / 2) * dpr;
|
||||
program.uniforms.uLineColor.value = [lineC.r, lineC.g, lineC.b];
|
||||
program.uniforms.uBaseColor.value = [baseC.r, baseC.g, baseC.b];
|
||||
program.uniforms.uIntensity.value = p.intensity * bright;
|
||||
program.uniforms.uShineSize.value = (p.shineSize * Math.PI) / 180;
|
||||
program.uniforms.uShineFade.value = (p.shineFade * Math.PI) / 180;
|
||||
program.uniforms.uThickness.value = p.thickness * dpr;
|
||||
renderer.render({ scene: mesh });
|
||||
};
|
||||
raf = requestAnimationFrame(update);
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(raf);
|
||||
ro.disconnect();
|
||||
window.removeEventListener('pointermove', onPointerMove);
|
||||
if (gl.canvas.parentNode === fx) fx.removeChild(gl.canvas);
|
||||
gl.getExtension('WEBGL_lose_context')?.loseContext();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<button
|
||||
ref={btnRef}
|
||||
type={type}
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
className={`specular-button specular-button--${size}${className ? ` ${className}` : ''}`}
|
||||
style={{
|
||||
'--sb-radius': `${radius}px`,
|
||||
'--sb-tint': tint,
|
||||
'--sb-tint-opacity': tintOpacity,
|
||||
'--sb-blur': `${blur}px`,
|
||||
'--sb-text-color': textColor
|
||||
} as React.CSSProperties}
|
||||
>
|
||||
<span ref={fxRef} className="specular-button__fx" aria-hidden="true" />
|
||||
<span className="specular-button__label">{children}</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default SpecularButton;
|
||||
@@ -0,0 +1,175 @@
|
||||
import React from 'react';
|
||||
import { PfmPartnerLogo } from '../common/PfmPartnerLogo';
|
||||
import { MoltenMetal } from '../common/MoltenMetal';
|
||||
import { SpecularButton } from '../common/SpecularButton';
|
||||
import { BorderGlow } from '../common/BorderGlow';
|
||||
import { ArrowRight, ShieldCheck, Zap, Landmark } from 'lucide-react';
|
||||
|
||||
interface LandingPageProps {
|
||||
onLogin: () => void;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
export const LandingPage: React.FC<LandingPageProps> = ({ onLogin, isLoading }) => {
|
||||
return (
|
||||
<div className="relative min-h-screen flex flex-col justify-center bg-slate-950 text-slate-100 overflow-hidden selection:bg-emerald-500/30">
|
||||
{/* Dynamic MoltenMetal Fluid Caustic Background */}
|
||||
<div className="pointer-events-none absolute inset-0 z-0 opacity-55 mix-blend-screen">
|
||||
<MoltenMetal
|
||||
color1="#022c22"
|
||||
color2="#059669"
|
||||
color3="#6ee7b7"
|
||||
speed={0.28}
|
||||
scale={3.6}
|
||||
detail={4}
|
||||
glow={1.6}
|
||||
coreSize={0.09}
|
||||
swirl={1.15}
|
||||
fold={-0.18}
|
||||
blackPoint={0.07}
|
||||
brightness={1.25}
|
||||
colorMode="molten"
|
||||
grain={true}
|
||||
grainIntensity={0.035}
|
||||
mouseInteraction={true}
|
||||
mouseStrength={0.3}
|
||||
opacity={0.9}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Main Hero Showcase */}
|
||||
<main className="relative z-10 flex-1 flex items-center justify-center px-4 sm:px-6 py-12">
|
||||
<div className="mx-auto max-w-4xl text-center space-y-10">
|
||||
|
||||
{/* Brand Headline Lockup with Pure Calligraphic Partner Icon */}
|
||||
<div className="space-y-4 max-w-3xl mx-auto flex flex-col items-center">
|
||||
<div className="inline-flex items-center justify-center gap-3 sm:gap-4 flex-wrap sm:flex-nowrap">
|
||||
<PfmPartnerLogo className="h-12 w-12 sm:h-16 sm:w-16 lg:h-20 lg:w-20 drop-shadow-[0_8px_24px_rgba(255,255,255,0.18)] filter transition-transform hover:scale-105 duration-300" />
|
||||
<h1 className="text-4xl sm:text-6xl lg:text-7xl font-black tracking-tight leading-none text-left">
|
||||
<span className="bg-gradient-to-r from-emerald-400 via-teal-200 via-cyan-300 to-indigo-300 bg-clip-text text-transparent drop-shadow-[0_10px_30px_rgba(16,185,129,0.25)]">
|
||||
PFM Partner
|
||||
</span>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<p className="text-xs sm:text-sm font-bold text-teal-300/90 tracking-[0.2em] uppercase pt-1">
|
||||
Post Fund Management Platform
|
||||
</p>
|
||||
<p className="text-base sm:text-lg text-slate-200 font-normal leading-relaxed text-balance drop-shadow max-w-2xl mx-auto pt-2">
|
||||
Institutional-grade orchestration for high-stakes capital allocations, milestone verification, and multi-tenant disbursement governance.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Specular High-Value SSO CTA */}
|
||||
<div className="pt-2 flex flex-col items-center gap-3">
|
||||
<SpecularButton
|
||||
size="lg"
|
||||
radius={18}
|
||||
tint="#064e3b"
|
||||
tintOpacity={0.85}
|
||||
blur={20}
|
||||
textColor="#ffffff"
|
||||
lineColor="#6ee7b7"
|
||||
baseColor="#022c22"
|
||||
intensity={1.5}
|
||||
shineSize={18}
|
||||
shineFade={45}
|
||||
thickness={1.6}
|
||||
speed={0.35}
|
||||
followMouse={true}
|
||||
proximity={280}
|
||||
disabled={isLoading}
|
||||
onClick={onLogin}
|
||||
className="shadow-[0_20px_50px_rgba(4,120,87,0.45),inset_0_1px_2px_rgba(255,255,255,0.4)] hover:shadow-[0_25px_60px_rgba(4,120,87,0.6)]"
|
||||
>
|
||||
<span className="font-bold tracking-wide text-base">Authenticate via SSO</span>
|
||||
<ArrowRight className="h-5 w-5 text-emerald-200" />
|
||||
</SpecularButton>
|
||||
<span className="text-[11px] text-slate-400 font-mono tracking-tight">
|
||||
Single Sign-On enabled for authorized institutional tenants
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Premium Value Pillars Grid with BorderGlow */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 pt-4 text-left">
|
||||
<BorderGlow
|
||||
edgeSensitivity={30}
|
||||
glowColor="160 85 70"
|
||||
backgroundColor="rgba(15, 23, 42, 0.45)"
|
||||
borderRadius={20}
|
||||
glowRadius={36}
|
||||
glowIntensity={1.2}
|
||||
coneSpread={28}
|
||||
colors={['#10b981', '#34d399', '#38bdf8']}
|
||||
fillOpacity={0.35}
|
||||
className="backdrop-blur-xl transition-all duration-300"
|
||||
>
|
||||
<div className="p-5">
|
||||
<div className="flex items-center gap-3 mb-2.5">
|
||||
<div className="p-2 rounded-xl bg-emerald-950/70 border border-emerald-500/30 text-emerald-400 shadow-inner">
|
||||
<Landmark className="h-4 w-4" />
|
||||
</div>
|
||||
<h3 className="text-sm font-bold text-slate-100">Capital Deployment</h3>
|
||||
</div>
|
||||
<p className="text-xs text-slate-300 leading-relaxed">
|
||||
Automated tranche releases tied directly to verified milestone deliverables and audit covenants.
|
||||
</p>
|
||||
</div>
|
||||
</BorderGlow>
|
||||
|
||||
<BorderGlow
|
||||
edgeSensitivity={30}
|
||||
glowColor="174 85 70"
|
||||
backgroundColor="rgba(15, 23, 42, 0.45)"
|
||||
borderRadius={20}
|
||||
glowRadius={36}
|
||||
glowIntensity={1.2}
|
||||
coneSpread={28}
|
||||
colors={['#14b8a6', '#2dd4bf', '#38bdf8']}
|
||||
fillOpacity={0.35}
|
||||
className="backdrop-blur-xl transition-all duration-300"
|
||||
>
|
||||
<div className="p-5">
|
||||
<div className="flex items-center gap-3 mb-2.5">
|
||||
<div className="p-2 rounded-xl bg-teal-950/70 border border-teal-500/30 text-teal-400 shadow-inner">
|
||||
<Zap className="h-4 w-4" />
|
||||
</div>
|
||||
<h3 className="text-sm font-bold text-slate-100">Milestone Velocity</h3>
|
||||
</div>
|
||||
<p className="text-xs text-slate-300 leading-relaxed">
|
||||
Streamlined evidence submissions and frictionless review cycles for implementing partners.
|
||||
</p>
|
||||
</div>
|
||||
</BorderGlow>
|
||||
|
||||
<BorderGlow
|
||||
edgeSensitivity={30}
|
||||
glowColor="190 90 70"
|
||||
backgroundColor="rgba(15, 23, 42, 0.45)"
|
||||
borderRadius={20}
|
||||
glowRadius={36}
|
||||
glowIntensity={1.2}
|
||||
coneSpread={28}
|
||||
colors={['#06b6d4', '#38bdf8', '#818cf8']}
|
||||
fillOpacity={0.35}
|
||||
className="backdrop-blur-xl transition-all duration-300"
|
||||
>
|
||||
<div className="p-5">
|
||||
<div className="flex items-center gap-3 mb-2.5">
|
||||
<div className="p-2 rounded-xl bg-cyan-950/70 border border-cyan-500/30 text-cyan-400 shadow-inner">
|
||||
<ShieldCheck className="h-4 w-4" />
|
||||
</div>
|
||||
<h3 className="text-sm font-bold text-slate-100">Strict Governance</h3>
|
||||
</div>
|
||||
<p className="text-xs text-slate-300 leading-relaxed">
|
||||
Encrypted multi-tenant isolation, hashed identity records, and immutable audit logs.
|
||||
</p>
|
||||
</div>
|
||||
</BorderGlow>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
import React from 'react';
|
||||
import { useAuth } from '../../auth/AuthProvider';
|
||||
import { PfmPartnerLogo } from '../common/PfmPartnerLogo';
|
||||
import { encodeHashIdSync } from '../../services/toolsService';
|
||||
import { LogOut, Building2 } from 'lucide-react';
|
||||
|
||||
export const AppNavbar: React.FC = () => {
|
||||
const { user, logout } = useAuth();
|
||||
|
||||
const hashedTenantId = user?.tenant_id !== undefined && user?.tenant_id !== null
|
||||
? encodeHashIdSync(user.tenant_id)
|
||||
: '';
|
||||
|
||||
return (
|
||||
<header className="sticky top-7 z-40 w-full px-4 sm:px-6 pointer-events-none">
|
||||
<div className="mx-auto max-w-4xl rounded-2xl border border-white/15 border-t-white/25 border-b-white/5 bg-slate-900/40 backdrop-blur-2xl backdrop-saturate-150 shadow-[0_20px_50px_rgba(0,0,0,0.75),inset_0_1px_1px_rgba(255,255,255,0.25),inset_0_-1px_1px_rgba(0,0,0,0.5)] px-4 sm:px-6 py-2.5 flex items-center justify-between pointer-events-auto transition-all">
|
||||
{/* Zone 1: Brand & Logo */}
|
||||
<div className="flex items-center gap-3">
|
||||
<PfmPartnerLogo className="h-7 w-7 shrink-0 drop-shadow-[0_2px_8px_rgba(255,255,255,0.18)]" />
|
||||
<div className="flex flex-col">
|
||||
<span className="text-sm font-extrabold tracking-tight bg-gradient-to-r from-emerald-300 via-teal-200 to-cyan-300 bg-clip-text text-transparent drop-shadow-sm">
|
||||
PFM Partner
|
||||
</span>
|
||||
<span className="text-[10px] text-slate-300 font-medium tracking-wider uppercase">
|
||||
Post Fund Management
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Zone 2: Contextual Tenant Badge */}
|
||||
<div className="hidden md:flex items-center gap-2">
|
||||
{user?.tenant && (
|
||||
<div className="flex items-center gap-2 px-3 py-1 rounded-xl bg-slate-950/40 border border-white/10 text-xs shadow-inner">
|
||||
<Building2 className="h-3.5 w-3.5 text-emerald-400 shrink-0" />
|
||||
<span className="text-slate-200 font-medium">{user.tenant}</span>
|
||||
{hashedTenantId && (
|
||||
<span className="font-mono text-[11px] text-emerald-300/90 bg-emerald-950/60 border border-emerald-500/20 px-1.5 py-0.5 rounded-md">
|
||||
#{hashedTenantId}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Zone 3: User Details & Logout */}
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="text-right hidden sm:block">
|
||||
<div className="text-xs font-semibold text-slate-200">{user?.name || 'Partner User'}</div>
|
||||
<div className="text-[11px] text-slate-400 font-mono">{user?.email || ''}</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={logout}
|
||||
className="flex items-center gap-1.5 rounded-xl border border-white/15 bg-white/5 hover:bg-white/10 px-3.5 py-1.5 text-xs font-semibold text-slate-200 hover:text-white transition-all shadow-[0_4px_12px_rgba(0,0,0,0.3)] hover:shadow-[0_6px_16px_rgba(0,0,0,0.5)] active:scale-95 cursor-pointer"
|
||||
>
|
||||
<LogOut className="h-3.5 w-3.5 text-slate-300" />
|
||||
<span>Sign Out</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,156 @@
|
||||
import { updateHasherSettings } from './services/toolsService';
|
||||
|
||||
export interface SecuritySettings {
|
||||
Authority: string;
|
||||
Audience: string;
|
||||
ClientId: string;
|
||||
ClientSecret: string;
|
||||
Scope?: string;
|
||||
}
|
||||
|
||||
export interface ApiSettings {
|
||||
BaseUrl: string;
|
||||
}
|
||||
|
||||
export interface HasherSettings {
|
||||
Salt: string;
|
||||
MinHashLength: number;
|
||||
}
|
||||
|
||||
export type ConfigSource = 'runtime-injected' | 'runtime-fetched' | 'build-env' | 'fallback-defaults';
|
||||
|
||||
export interface AppConfig {
|
||||
SecuritySettings: SecuritySettings;
|
||||
ApiSettings: ApiSettings;
|
||||
HasherSettings: HasherSettings;
|
||||
ConfigSource: ConfigSource;
|
||||
LoadedAt?: string;
|
||||
}
|
||||
|
||||
export const defaultSecuritySettings: SecuritySettings = {
|
||||
Authority: 'https://sts.security.khongisa.co.za',
|
||||
Audience: 'pfm-api-dev',
|
||||
ClientId: 'pfm-scalar-dev',
|
||||
ClientSecret: 'secret_71f88570a46eb22eb5270b47f69e3332c84d8db85d008f0276cb7431c8872d27',
|
||||
Scope: 'openid profile roles email address pfm-api-dev',
|
||||
};
|
||||
|
||||
export const defaultApiSettings: ApiSettings = {
|
||||
BaseUrl: 'https://api.ifri.khongisa.co.za/',
|
||||
};
|
||||
|
||||
export const defaultHasherSettings: HasherSettings = {
|
||||
Salt: 'TGlnaHQgQ2hhcm1zLCBNaWRyYW5kQm9va3MgbWFrZSBsb3RzIG9mIG1vbmV5IGFuZCBhcmUgYSBzdWNjZXNzZnVsIHZpcmFsIHN0b3J5IGluIFNvdXRoIEFmcmljYQ==',
|
||||
MinHashLength: 11,
|
||||
};
|
||||
|
||||
function pickFirstNonEmpty(...candidates: any[]): string | undefined {
|
||||
for (const c of candidates) {
|
||||
if (c !== undefined && c !== null) {
|
||||
const s = String(c).trim();
|
||||
if (s !== '' && s !== 'undefined' && s !== 'null') return s;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function normalizeBaseUrl(url: string): string {
|
||||
let trimmed = url.trim();
|
||||
if (!/^https?:\/\//i.test(trimmed)) trimmed = `https://${trimmed}`;
|
||||
return trimmed.endsWith('/') ? trimmed : `${trimmed}/`;
|
||||
}
|
||||
|
||||
function normalizeAuthorityUrl(url: string): string {
|
||||
let trimmed = url.trim();
|
||||
if (!/^https?:\/\//i.test(trimmed)) trimmed = `https://${trimmed}`;
|
||||
return trimmed.replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
function getWindowRuntimeConfig(): Record<string, any> {
|
||||
if (typeof window === 'undefined') return {};
|
||||
const win = window as any;
|
||||
return win.__RUNTIME_CONFIG__ || win.APP_CONFIG || win._env_ || {};
|
||||
}
|
||||
|
||||
function extractSourceConfig(src: Record<string, any>) {
|
||||
if (!src || typeof src !== 'object') return {};
|
||||
return {
|
||||
authority: pickFirstNonEmpty(src.VITE_OIDC_AUTHORITY, src.OIDC_AUTHORITY, src.SecuritySettings?.Authority, src.SecuritySettings__Authority),
|
||||
audience: pickFirstNonEmpty(src.VITE_OIDC_AUDIENCE, src.OIDC_AUDIENCE, src.SecuritySettings?.Audience, src.SecuritySettings__Audience),
|
||||
clientId: pickFirstNonEmpty(src.VITE_OIDC_CLIENT_ID, src.OIDC_CLIENT_ID, src.SecuritySettings?.ClientId, src.SecuritySettings__ClientId),
|
||||
clientSecret: pickFirstNonEmpty(src.VITE_OIDC_CLIENT_SECRET, src.OIDC_CLIENT_SECRET, src.SecuritySettings?.ClientSecret, src.SecuritySettings__ClientSecret),
|
||||
scope: pickFirstNonEmpty(src.VITE_OIDC_SCOPE, src.OIDC_SCOPE, src.SecuritySettings?.Scope, src.SecuritySettings__Scope),
|
||||
baseUrl: pickFirstNonEmpty(src.VITE_API_BASE_URL, src.API_BASE_URL, src.ApiSettings?.BaseUrl, src.ApiSettings__BaseUrl),
|
||||
hasherSalt: pickFirstNonEmpty(src.VITE_HASHER_SALT, src.HASHER_SALT, src.HasherSettings?.Salt, src.HasherSettings__Salt),
|
||||
hasherMinLength: Number(pickFirstNonEmpty(src.VITE_HASHER_MIN_LENGTH, src.HASHER_MIN_LENGTH, src.HasherSettings?.MinHashLength)) || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveConfig(fetchedJson?: Record<string, any>): AppConfig {
|
||||
const winCfg = extractSourceConfig(getWindowRuntimeConfig());
|
||||
const fetchCfg = fetchedJson ? extractSourceConfig(fetchedJson) : {};
|
||||
const envCfg = extractSourceConfig((typeof import.meta !== 'undefined' && (import.meta as any).env ? (import.meta as any).env : {}) as Record<string, any>);
|
||||
|
||||
let source: ConfigSource = 'fallback-defaults';
|
||||
if (Object.values(winCfg).some((v) => v !== undefined)) source = 'runtime-injected';
|
||||
else if (Object.values(fetchCfg).some((v) => v !== undefined)) source = 'runtime-fetched';
|
||||
else if (Object.values(envCfg).some((v) => v !== undefined)) source = 'build-env';
|
||||
|
||||
return {
|
||||
SecuritySettings: {
|
||||
Authority: normalizeAuthorityUrl(pickFirstNonEmpty(winCfg.authority, fetchCfg.authority, envCfg.authority, defaultSecuritySettings.Authority)!),
|
||||
Audience: pickFirstNonEmpty(winCfg.audience, fetchCfg.audience, envCfg.audience, defaultSecuritySettings.Audience)!,
|
||||
ClientId: pickFirstNonEmpty(winCfg.clientId, fetchCfg.clientId, envCfg.clientId, defaultSecuritySettings.ClientId)!,
|
||||
ClientSecret: pickFirstNonEmpty(winCfg.clientSecret, fetchCfg.clientSecret, envCfg.clientSecret, defaultSecuritySettings.ClientSecret)!,
|
||||
Scope: pickFirstNonEmpty(winCfg.scope, fetchCfg.scope, envCfg.scope, defaultSecuritySettings.Scope),
|
||||
},
|
||||
ApiSettings: {
|
||||
BaseUrl: normalizeBaseUrl(pickFirstNonEmpty(winCfg.baseUrl, fetchCfg.baseUrl, envCfg.baseUrl, defaultApiSettings.BaseUrl)!),
|
||||
},
|
||||
HasherSettings: {
|
||||
Salt: pickFirstNonEmpty(winCfg.hasherSalt, fetchCfg.hasherSalt, envCfg.hasherSalt, defaultHasherSettings.Salt)!,
|
||||
MinHashLength: winCfg.hasherMinLength || fetchCfg.hasherMinLength || envCfg.hasherMinLength || defaultHasherSettings.MinHashLength,
|
||||
},
|
||||
ConfigSource: source,
|
||||
LoadedAt: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
let runtimeConfig: AppConfig = resolveConfig();
|
||||
updateHasherSettings(runtimeConfig.HasherSettings.Salt, runtimeConfig.HasherSettings.MinHashLength);
|
||||
|
||||
const configListeners: Array<(config: AppConfig) => void> = [];
|
||||
|
||||
export function onConfigChanged(listener: (config: AppConfig) => void): () => void {
|
||||
configListeners.push(listener);
|
||||
return () => {
|
||||
const idx = configListeners.indexOf(listener);
|
||||
if (idx !== -1) configListeners.splice(idx, 1);
|
||||
};
|
||||
}
|
||||
|
||||
export async function loadRuntimeConfig(): Promise<AppConfig> {
|
||||
let fetchedJson: Record<string, any> | undefined;
|
||||
if (typeof window !== 'undefined') {
|
||||
try {
|
||||
const res = await fetch(`/config.json?_t=${Date.now()}`, {
|
||||
cache: 'no-store',
|
||||
headers: { Accept: 'application/json' },
|
||||
}).catch(() => null);
|
||||
if (res && res.ok) {
|
||||
fetchedJson = await res.json().catch(() => undefined);
|
||||
}
|
||||
} catch {
|
||||
// Safe fallback
|
||||
}
|
||||
}
|
||||
runtimeConfig = resolveConfig(fetchedJson);
|
||||
updateHasherSettings(runtimeConfig.HasherSettings.Salt, runtimeConfig.HasherSettings.MinHashLength);
|
||||
configListeners.forEach((fn) => fn(runtimeConfig));
|
||||
return runtimeConfig;
|
||||
}
|
||||
|
||||
export const getAppConfig = () => runtimeConfig;
|
||||
export const getSecuritySettings = () => runtimeConfig.SecuritySettings;
|
||||
export const getApiBaseUrl = () => runtimeConfig.ApiSettings.BaseUrl;
|
||||
export const getHasherSettings = () => runtimeConfig.HasherSettings;
|
||||
@@ -0,0 +1,11 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@layer base {
|
||||
body {
|
||||
font-family: 'Plus Jakarta Sans', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
}
|
||||
|
||||
code, pre, .font-mono {
|
||||
font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { QueryClient } from '@tanstack/react-query';
|
||||
|
||||
export const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
refetchOnWindowFocus: false,
|
||||
retry: 1,
|
||||
staleTime: 1000 * 60 * 5, // 5 minutes
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
import {StrictMode} from 'react';
|
||||
import {createRoot} from 'react-dom/client';
|
||||
import App from './App.tsx';
|
||||
import './index.css';
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
);
|
||||
@@ -0,0 +1,54 @@
|
||||
import axios, { InternalAxiosRequestConfig } from 'axios';
|
||||
import { getApiBaseUrl } from '../config';
|
||||
import { triggerSessionExpired, isUserSyncEndpoint, isTenantEndpoint } from './sessionExpiration';
|
||||
import { isUserSyncedInBrowser, isActivationPending, syncUserAccount } from './userSyncService';
|
||||
|
||||
export const apiClient = axios.create({
|
||||
baseURL: getApiBaseUrl(),
|
||||
headers: {
|
||||
'Content-Type': 'application/json; version=1',
|
||||
Accept: 'application/json; version=1',
|
||||
},
|
||||
timeout: 20000,
|
||||
});
|
||||
|
||||
apiClient.interceptors.request.use(
|
||||
async (config: InternalAxiosRequestConfig) => {
|
||||
config.baseURL = getApiBaseUrl();
|
||||
const token = typeof window !== 'undefined' ? localStorage.getItem('pfm_access_token') : null;
|
||||
if (token && config.headers) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
const isLifecycleRequest = isUserSyncEndpoint(config.url) || isTenantEndpoint(config.url);
|
||||
if (token && !isLifecycleRequest) {
|
||||
if (isActivationPending()) {
|
||||
return Promise.reject(new Error('User account is pending administrator activation'));
|
||||
}
|
||||
const storedSub = typeof window !== 'undefined' ? (localStorage.getItem('pfm_user_sub') || '') : '';
|
||||
if (!isUserSyncedInBrowser(storedSub, token)) {
|
||||
const syncRes = await syncUserAccount(storedSub, token);
|
||||
if (!syncRes.success) {
|
||||
return Promise.reject(new Error(syncRes.errorMessage || 'User account sync pending activation'));
|
||||
}
|
||||
}
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => Promise.reject(error)
|
||||
);
|
||||
|
||||
apiClient.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
if (error?.response?.status === 401) {
|
||||
const requestUrl = error.config?.url;
|
||||
const isLifecycleEndpoint = isUserSyncEndpoint(requestUrl) || isTenantEndpoint(requestUrl);
|
||||
if (!isLifecycleEndpoint) {
|
||||
const detailMsg = error?.response?.data?.detail || error?.response?.data?.message || 'Session expired (HTTP 401).';
|
||||
triggerSessionExpired(detailMsg, requestUrl);
|
||||
}
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,64 @@
|
||||
import { queryClient } from '../lib/queryClient';
|
||||
|
||||
export const LOCAL_STORAGE_TOKEN_KEY = 'pfm_access_token';
|
||||
export const LOCAL_STORAGE_USER_KEY = 'pfm_auth_user';
|
||||
export const LOCAL_STORAGE_SUB_KEY = 'pfm_user_sub';
|
||||
export const LOCAL_STORAGE_CLAIMS_KEY = 'pfm_user_claims';
|
||||
export const LOCAL_STORAGE_USER_SYNC_KEY = 'pfm_user_sync_record';
|
||||
export const LOCAL_STORAGE_ACTIVATION_PENDING_KEY = 'pfm_user_pending_activation';
|
||||
export const LOCAL_STORAGE_ACTIVATION_REASON_KEY = 'pfm_user_pending_activation_reason';
|
||||
|
||||
export function isUserSyncEndpoint(url?: string): boolean {
|
||||
if (!url) return false;
|
||||
const clean = url.toLowerCase().split('?')[0];
|
||||
return clean.includes('/users/sync') || clean.includes('/integration/sync') || clean.includes('/user/sync');
|
||||
}
|
||||
|
||||
export function isTenantEndpoint(url?: string): boolean {
|
||||
if (!url) return false;
|
||||
return url.toLowerCase().includes('/governance/tenants');
|
||||
}
|
||||
|
||||
type SessionExpiredListener = (reason?: string) => void;
|
||||
const listeners = new Set<SessionExpiredListener>();
|
||||
let hasTriggeredSessionExpired = false;
|
||||
|
||||
export function triggerSessionExpired(reason?: string, requestUrl?: string): void {
|
||||
if (requestUrl && isUserSyncEndpoint(requestUrl)) return;
|
||||
if (typeof window !== 'undefined' && localStorage.getItem(LOCAL_STORAGE_ACTIVATION_PENDING_KEY)) return;
|
||||
if (hasTriggeredSessionExpired) return;
|
||||
|
||||
const hasExistingToken = typeof window !== 'undefined' && !!localStorage.getItem(LOCAL_STORAGE_TOKEN_KEY);
|
||||
if (!hasExistingToken) return;
|
||||
|
||||
hasTriggeredSessionExpired = true;
|
||||
if (typeof window !== 'undefined') {
|
||||
window.dispatchEvent(new CustomEvent('pfm:session-expired', { detail: { reason: reason || 'HTTP 401 Unauthorized' } }));
|
||||
}
|
||||
listeners.forEach((cb) => cb(reason));
|
||||
}
|
||||
|
||||
export function onSessionExpired(listener: SessionExpiredListener): () => void {
|
||||
listeners.add(listener);
|
||||
return () => { listeners.delete(listener); };
|
||||
}
|
||||
|
||||
export function resetSessionExpiredState(): void {
|
||||
hasTriggeredSessionExpired = false;
|
||||
}
|
||||
|
||||
export function clearSessionAndRedirectToLanding(): void {
|
||||
if (typeof window === 'undefined') return;
|
||||
try {
|
||||
localStorage.removeItem(LOCAL_STORAGE_TOKEN_KEY);
|
||||
localStorage.removeItem(LOCAL_STORAGE_USER_KEY);
|
||||
localStorage.removeItem(LOCAL_STORAGE_SUB_KEY);
|
||||
localStorage.removeItem(LOCAL_STORAGE_CLAIMS_KEY);
|
||||
localStorage.removeItem(LOCAL_STORAGE_USER_SYNC_KEY);
|
||||
localStorage.removeItem(LOCAL_STORAGE_ACTIVATION_PENDING_KEY);
|
||||
localStorage.removeItem(LOCAL_STORAGE_ACTIVATION_REASON_KEY);
|
||||
} catch {}
|
||||
try { queryClient.clear(); } catch {}
|
||||
resetSessionExpiredState();
|
||||
window.location.replace('/');
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import Hashids from 'hashids';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
export const HASHER_SETTINGS = {
|
||||
minHashLength: 11,
|
||||
salt: 'TGlnaHQgQ2hhcm1zLCBNaWRyYW5kQm9va3MgbWFrZSBsb3RzIG9mIG1vbmV5IGFuZCBhcmUgYSBzdWNjZXNzZnVsIHZpcmFsIHN0b3J5IGluIFNvdXRoIEFmcmljYQ==',
|
||||
};
|
||||
|
||||
let hasher = new Hashids(HASHER_SETTINGS.salt, HASHER_SETTINGS.minHashLength);
|
||||
|
||||
export function updateHasherSettings(salt?: string, minLength?: number | string): void {
|
||||
let updated = false;
|
||||
if (salt && salt.trim() !== '') {
|
||||
HASHER_SETTINGS.salt = salt.trim();
|
||||
updated = true;
|
||||
}
|
||||
if (minLength !== undefined && minLength !== null && minLength !== '') {
|
||||
const parsed = Number(minLength);
|
||||
if (!isNaN(parsed) && parsed > 0) {
|
||||
HASHER_SETTINGS.minHashLength = parsed;
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
if (updated) {
|
||||
hasher = new Hashids(HASHER_SETTINGS.salt, HASHER_SETTINGS.minHashLength);
|
||||
}
|
||||
}
|
||||
|
||||
export function encodeHashIdSync(id: number | string | null | undefined): string {
|
||||
if (id === undefined || id === null || id === '') return '';
|
||||
if (typeof id === 'number') {
|
||||
if (isNaN(id) || !isFinite(id) || id < 0) return '';
|
||||
return hasher.encode(id);
|
||||
}
|
||||
const str = String(id).trim();
|
||||
if (!str) return '';
|
||||
try {
|
||||
const decoded = hasher.decode(str);
|
||||
if (Array.isArray(decoded) && decoded.length > 0) return str;
|
||||
} catch {}
|
||||
if (/^\d+$/.test(str)) {
|
||||
const num = parseInt(str, 10);
|
||||
if (!isNaN(num) && num >= 0) return hasher.encode(num);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
export function decodeHashIdSync(hashId: string | number | null | undefined): number | string {
|
||||
if (hashId === undefined || hashId === null || hashId === '') return '';
|
||||
if (typeof hashId === 'number') return hashId;
|
||||
const str = String(hashId).trim();
|
||||
if (/^-?\d+$/.test(str)) {
|
||||
const parsed = parseInt(str, 10);
|
||||
return isNaN(parsed) ? str : parsed;
|
||||
}
|
||||
try {
|
||||
const decoded = hasher.decode(str);
|
||||
if (Array.isArray(decoded) && decoded.length > 0) return Number(decoded[0]);
|
||||
} catch {}
|
||||
return str;
|
||||
}
|
||||
|
||||
export function decodeToNumberSync(hashId: number | string | null | undefined): number {
|
||||
if (!hashId) return 0;
|
||||
if (typeof hashId === 'number') return isNaN(hashId) ? 0 : Math.trunc(hashId);
|
||||
const str = String(hashId).trim();
|
||||
if (/^-?\d+$/.test(str)) {
|
||||
const parsed = parseInt(str, 10);
|
||||
return isNaN(parsed) ? 0 : parsed;
|
||||
}
|
||||
try {
|
||||
const decoded = hasher.decode(str);
|
||||
if (Array.isArray(decoded) && decoded.length > 0) return Number(decoded[0]);
|
||||
} catch {}
|
||||
const fallback = Number(str);
|
||||
return isNaN(fallback) ? 0 : Math.trunc(fallback);
|
||||
}
|
||||
|
||||
export const toolsService = {
|
||||
encodeIdSync: encodeHashIdSync,
|
||||
decodeIdSync: decodeHashIdSync,
|
||||
decodeToNumberSync,
|
||||
async encodeId(id: number | string | null | undefined) { return encodeHashIdSync(id); },
|
||||
async decodeId(hashId: string | null | undefined) { return decodeHashIdSync(hashId); },
|
||||
async resolveProjectId(id: number | string) { return encodeHashIdSync(id); },
|
||||
async resolveOrganisationId(id: number | string) { return encodeHashIdSync(id); },
|
||||
async resolveActivityId(id: number | string) { return encodeHashIdSync(id); },
|
||||
async resolveMilestoneId(id: number | string) { return encodeHashIdSync(id); },
|
||||
};
|
||||
|
||||
export function useHashId(id?: number | string | null, context = 'general') {
|
||||
return useQuery({
|
||||
queryKey: ['hashId', context, id !== undefined && id !== null ? String(id) : ''],
|
||||
queryFn: () => Promise.resolve(encodeHashIdSync(id)),
|
||||
enabled: id !== undefined && id !== null && id !== '',
|
||||
staleTime: Infinity,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
import { apiClient } from './httpClient';
|
||||
|
||||
export const LOCAL_STORAGE_USER_SYNC_KEY = 'pfm_user_sync_record';
|
||||
export const LOCAL_STORAGE_ACTIVATION_PENDING_KEY = 'pfm_user_pending_activation';
|
||||
export const LOCAL_STORAGE_ACTIVATION_REASON_KEY = 'pfm_user_pending_activation_reason';
|
||||
|
||||
export interface UserSyncRecord {
|
||||
sub: string;
|
||||
syncedAt: number;
|
||||
syncedEndpoint: string;
|
||||
tokenSnippet: string;
|
||||
status: 'synced';
|
||||
userData?: any;
|
||||
}
|
||||
|
||||
export interface UserSyncProfile {
|
||||
id?: string;
|
||||
sub?: string;
|
||||
email?: string | null;
|
||||
name?: string | null;
|
||||
role?: string | null;
|
||||
tenant?: string | null;
|
||||
tenant_id?: number | string | null;
|
||||
organisationId?: number | string | null;
|
||||
}
|
||||
|
||||
export interface UserSyncResult {
|
||||
success: boolean;
|
||||
isUnauthorized?: boolean;
|
||||
isActivationPending?: boolean;
|
||||
endpoint?: string;
|
||||
data?: any;
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
let inFlightSyncPromise: Promise<UserSyncResult> | null = null;
|
||||
|
||||
export function isActivationPending(): boolean {
|
||||
try { return localStorage.getItem(LOCAL_STORAGE_ACTIVATION_PENDING_KEY) === 'true'; } catch { return false; }
|
||||
}
|
||||
|
||||
export function getActivationPendingReason(): string | null {
|
||||
try { return localStorage.getItem(LOCAL_STORAGE_ACTIVATION_REASON_KEY); } catch { return null; }
|
||||
}
|
||||
|
||||
export function setActivationPending(pending: boolean, reason?: string): void {
|
||||
try {
|
||||
if (pending) {
|
||||
localStorage.setItem(LOCAL_STORAGE_ACTIVATION_PENDING_KEY, 'true');
|
||||
if (reason) localStorage.setItem(LOCAL_STORAGE_ACTIVATION_REASON_KEY, reason);
|
||||
} else {
|
||||
localStorage.removeItem(LOCAL_STORAGE_ACTIVATION_PENDING_KEY);
|
||||
localStorage.removeItem(LOCAL_STORAGE_ACTIVATION_REASON_KEY);
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
export function isUserSyncedInBrowser(currentSub?: string, currentToken?: string | null): boolean {
|
||||
try {
|
||||
if (isActivationPending()) return false;
|
||||
const raw = localStorage.getItem(LOCAL_STORAGE_USER_SYNC_KEY);
|
||||
if (!raw) return false;
|
||||
const record: UserSyncRecord = JSON.parse(raw);
|
||||
if (!record || record.status !== 'synced') return false;
|
||||
if (currentSub && record.sub && record.sub !== currentSub) return false;
|
||||
if (currentToken && record.tokenSnippet && record.tokenSnippet !== currentToken.slice(-16)) return false;
|
||||
return true;
|
||||
} catch { return false; }
|
||||
}
|
||||
|
||||
export function saveUserSyncInBrowser(sub: string, token: string | null | undefined, endpoint: string, userData?: any): void {
|
||||
try {
|
||||
const record: UserSyncRecord = {
|
||||
sub: sub || 'authenticated_user',
|
||||
syncedAt: Date.now(),
|
||||
syncedEndpoint: endpoint,
|
||||
tokenSnippet: token ? token.slice(-16) : '',
|
||||
status: 'synced',
|
||||
userData,
|
||||
};
|
||||
localStorage.setItem(LOCAL_STORAGE_USER_SYNC_KEY, JSON.stringify(record));
|
||||
} catch {}
|
||||
}
|
||||
|
||||
export function clearUserSyncInBrowser(): void {
|
||||
try {
|
||||
localStorage.removeItem(LOCAL_STORAGE_USER_SYNC_KEY);
|
||||
localStorage.removeItem(LOCAL_STORAGE_ACTIVATION_PENDING_KEY);
|
||||
localStorage.removeItem(LOCAL_STORAGE_ACTIVATION_REASON_KEY);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
export const USER_SYNC_CANDIDATE_ENDPOINTS = [
|
||||
'/v1/api/integration/sync',
|
||||
'/v1/api/users/sync',
|
||||
'/v1/api/user/sync',
|
||||
];
|
||||
|
||||
async function executeBackendSync(userSub?: string, profile?: UserSyncProfile): Promise<{ endpoint: string; data?: any }> {
|
||||
const payload = {
|
||||
id: userSub || profile?.id || profile?.sub,
|
||||
sub: userSub || profile?.sub,
|
||||
email: profile?.email || undefined,
|
||||
name: profile?.name || undefined,
|
||||
role: profile?.role || undefined,
|
||||
tenant_id: profile?.tenant_id ? Number(profile.tenant_id) : undefined,
|
||||
organisationId: profile?.organisationId ? Number(profile.organisationId) : undefined,
|
||||
};
|
||||
|
||||
let lastError: any = null;
|
||||
for (const endpoint of USER_SYNC_CANDIDATE_ENDPOINTS) {
|
||||
try {
|
||||
const res = await apiClient.post(endpoint, payload);
|
||||
return { endpoint, data: res?.data };
|
||||
} catch (err: any) {
|
||||
lastError = err;
|
||||
const status = err?.response?.status;
|
||||
if (status === 401 || status === 403 || status === 423) throw err;
|
||||
}
|
||||
}
|
||||
throw lastError || new Error('Failed to reach user sync endpoint');
|
||||
}
|
||||
|
||||
export async function syncUserAccount(userSub?: string, token?: string | null, profile?: UserSyncProfile, force = false): Promise<UserSyncResult> {
|
||||
const effectiveToken = token || (typeof window !== 'undefined' ? localStorage.getItem('pfm_access_token') : null);
|
||||
const effectiveSub = userSub || (typeof window !== 'undefined' ? localStorage.getItem('pfm_user_sub') || '' : '');
|
||||
|
||||
if (!force && isUserSyncedInBrowser(effectiveSub, effectiveToken) && !isActivationPending()) {
|
||||
return { success: true, endpoint: '/v1/api/users/sync' };
|
||||
}
|
||||
|
||||
if (inFlightSyncPromise) return inFlightSyncPromise;
|
||||
|
||||
inFlightSyncPromise = (async (): Promise<UserSyncResult> => {
|
||||
try {
|
||||
const result = await executeBackendSync(effectiveSub, profile);
|
||||
setActivationPending(false);
|
||||
saveUserSyncInBrowser(effectiveSub, effectiveToken, result.endpoint, result.data);
|
||||
return { success: true, endpoint: result.endpoint, data: result.data };
|
||||
} catch (err: any) {
|
||||
const status = err?.response?.status;
|
||||
if (status === 401) {
|
||||
clearUserSyncInBrowser();
|
||||
setActivationPending(false);
|
||||
return { success: false, isUnauthorized: true, errorMessage: 'Session expired (HTTP 401).' };
|
||||
}
|
||||
const serverMsg = err?.response?.data?.detail || err?.response?.data?.message || 'Account activation pending.';
|
||||
setActivationPending(true, serverMsg);
|
||||
return { success: false, isUnauthorized: false, isActivationPending: true, errorMessage: serverMsg };
|
||||
} finally {
|
||||
inFlightSyncPromise = null;
|
||||
}
|
||||
})();
|
||||
|
||||
return inFlightSyncPromise;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"experimentalDecorators": true,
|
||||
"useDefineForClassFields": false,
|
||||
"module": "ESNext",
|
||||
"types": ["vite/client"],
|
||||
"lib": [
|
||||
"ES2022",
|
||||
"DOM",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "bundler",
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"allowJs": true,
|
||||
"jsx": "react-jsx",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./*"
|
||||
]
|
||||
},
|
||||
"allowImportingTsExtensions": true,
|
||||
"noEmit": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import path from 'path';
|
||||
import {defineConfig} from 'vite';
|
||||
|
||||
export default defineConfig(() => {
|
||||
return {
|
||||
plugins: [react(), tailwindcss()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, '.'),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
// HMR is disabled in AI Studio via DISABLE_HMR env var.
|
||||
// Do not modify—file watching is disabled to prevent flickering during agent edits.
|
||||
hmr: process.env.DISABLE_HMR !== 'true',
|
||||
// Disable file watching when DISABLE_HMR is true to save CPU during agent edits.
|
||||
watch: process.env.DISABLE_HMR === 'true' ? null : {},
|
||||
},
|
||||
};
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -81,6 +81,26 @@ services:
|
||||
- VITE_HASHER_MIN_LENGTH=${HASHER_MIN_LENGTH:-11}
|
||||
- VITE_HASHER_SALT=${HASHER_SALT}
|
||||
|
||||
# Backend API Configuration
|
||||
- VITE_API_BASE_URL=${API_BASE_URL:-https://api.ifri.khongisa.co.za/}
|
||||
restart: unless-stopped
|
||||
pfm-partner:
|
||||
image: pfm-partner
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./PostFundManagement.PartnerUI/Dockerfile
|
||||
container_name: pfm-partner
|
||||
ports:
|
||||
- "8077:3000"
|
||||
environment:
|
||||
# OIDC Identity Provider Configuration
|
||||
- VITE_OIDC_AUTHORITY=${OIDC_AUTHORITY:-https://}
|
||||
- VITE_OIDC_AUDIENCE=${OIDC_AUDIENCE:-aud}
|
||||
- VITE_OIDC_CLIENT_ID=${PARTNER_VITE_OIDC_CLIENT_ID:-pfm-partner-dev}
|
||||
- VITE_OIDC_CLIENT_SECRET=${OIDC_CLIENT_SECRET:-yoursecret}
|
||||
- VITE_HASHER_MIN_LENGTH=${HASHER_MIN_LENGTH:-11}
|
||||
- VITE_HASHER_SALT=${HASHER_SALT}
|
||||
|
||||
# Backend API Configuration
|
||||
- VITE_API_BASE_URL=${API_BASE_URL:-https://api.ifri.khongisa.co.za/}
|
||||
restart: unless-stopped
|
||||
Reference in New Issue
Block a user