feat: Added frontend for The Catalyst tab (#10)
Added frontend for "The Catalyst" tab. Co-authored-by: Sayan Datta <sayan@Sayans-MacBook-Air.local> Reviewed-on: #10
This commit was merged in pull request #10.
This commit is contained in:
4176
app/dist/assets/index-Cs3AYztb.js
vendored
4176
app/dist/assets/index-Cs3AYztb.js
vendored
File diff suppressed because one or more lines are too long
1
app/dist/assets/index-J4PPDOa9.css
vendored
1
app/dist/assets/index-J4PPDOa9.css
vendored
File diff suppressed because one or more lines are too long
4
app/dist/index.html
vendored
4
app/dist/index.html
vendored
@@ -4,8 +4,8 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Velocity WebOS</title>
|
<title>Velocity WebOS</title>
|
||||||
<script type="module" crossorigin src="./assets/index-Cs3AYztb.js"></script>
|
<script type="module" crossorigin src="./assets/index-DWXNQJq4.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="./assets/index-J4PPDOa9.css">
|
<link rel="stylesheet" crossorigin href="./assets/index-y9vEdTGy.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
0
app/node_modules/.bin/tsc
generated
vendored
Normal file → Executable file
0
app/node_modules/.bin/tsc
generated
vendored
Normal file → Executable file
0
app/node_modules/.bin/vite
generated
vendored
Normal file → Executable file
0
app/node_modules/.bin/vite
generated
vendored
Normal file → Executable file
106
app/node_modules/.package-lock.json
generated
vendored
106
app/node_modules/.package-lock.json
generated
vendored
@@ -1835,18 +1835,18 @@
|
|||||||
"react": ">=16.8.0"
|
"react": ">=16.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@esbuild/win32-x64": {
|
"node_modules/@esbuild/darwin-arm64": {
|
||||||
"version": "0.27.2",
|
"version": "0.27.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
|
||||||
"integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
|
"integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"darwin"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
@@ -3922,32 +3922,18 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||||
"version": "4.55.1",
|
"version": "4.55.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz",
|
||||||
"integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==",
|
"integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"darwin"
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
|
||||||
"version": "4.55.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz",
|
|
||||||
"integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@standard-schema/utils": {
|
"node_modules/@standard-schema/utils": {
|
||||||
@@ -5008,6 +4994,19 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/cookie": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/core-js-compat": {
|
"node_modules/core-js-compat": {
|
||||||
"version": "3.48.0",
|
"version": "3.48.0",
|
||||||
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.48.0.tgz",
|
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.48.0.tgz",
|
||||||
@@ -5777,6 +5776,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fsevents": {
|
||||||
|
"version": "2.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
|
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/function-bind": {
|
"node_modules/function-bind": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
@@ -6961,6 +6975,44 @@
|
|||||||
"react-dom": "^18.0.0 || ^19.0.0"
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-router": {
|
||||||
|
"version": "7.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.1.tgz",
|
||||||
|
"integrity": "sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cookie": "^1.0.1",
|
||||||
|
"set-cookie-parser": "^2.6.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=18",
|
||||||
|
"react-dom": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-router-dom": {
|
||||||
|
"version": "7.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.1.tgz",
|
||||||
|
"integrity": "sha512-UJnV3Rxc5TgUPJt2KJpo1Jpy0OKQr0AjgbZzBFjaPJcFOb2Y8jA5H3LT8HUJAiRLlWrEXWHbF1Z4SCZaQjWDHw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"react-router": "7.13.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=18",
|
||||||
|
"react-dom": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-smooth": {
|
"node_modules/react-smooth": {
|
||||||
"version": "4.0.4",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",
|
||||||
@@ -7291,6 +7343,12 @@
|
|||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/set-cookie-parser": {
|
||||||
|
"version": "2.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
|
||||||
|
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/shebang-command": {
|
"node_modules/shebang-command": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
|
|||||||
2
app/node_modules/.tmp/tsconfig.app.tsbuildinfo
generated
vendored
2
app/node_modules/.tmp/tsconfig.app.tsbuildinfo
generated
vendored
@@ -1 +1 @@
|
|||||||
{"root":["../../src/app.tsx","../../src/main.tsx","../../src/app/oracle/page.tsx","../../src/components/layout/loginscreen.tsx","../../src/components/layout/sidebar.tsx","../../src/components/modules/dashboard.tsx","../../src/components/modules/inventory.tsx","../../src/components/modules/oracle.tsx","../../src/components/modules/sentinel.tsx","../../src/components/modules/settings.tsx","../../src/components/oracle/leadinspector.tsx","../../src/components/oracle/pipelineview.tsx","../../src/components/oracle/mockleads.ts","../../src/components/sentinel/journeyriver/inspectorpanel.tsx","../../src/components/sentinel/journeyriver/riverpath.tsx","../../src/components/sentinel/journeyriver/index.tsx","../../src/components/ui/accordion.tsx","../../src/components/ui/alert-dialog.tsx","../../src/components/ui/alert.tsx","../../src/components/ui/aspect-ratio.tsx","../../src/components/ui/avatar.tsx","../../src/components/ui/badge.tsx","../../src/components/ui/breadcrumb.tsx","../../src/components/ui/button-group.tsx","../../src/components/ui/button.tsx","../../src/components/ui/calendar.tsx","../../src/components/ui/card.tsx","../../src/components/ui/carousel.tsx","../../src/components/ui/chart.tsx","../../src/components/ui/checkbox.tsx","../../src/components/ui/collapsible.tsx","../../src/components/ui/command.tsx","../../src/components/ui/context-menu.tsx","../../src/components/ui/dialog.tsx","../../src/components/ui/drawer.tsx","../../src/components/ui/dropdown-menu.tsx","../../src/components/ui/empty.tsx","../../src/components/ui/field.tsx","../../src/components/ui/form.tsx","../../src/components/ui/hover-card.tsx","../../src/components/ui/input-group.tsx","../../src/components/ui/input-otp.tsx","../../src/components/ui/input.tsx","../../src/components/ui/item.tsx","../../src/components/ui/kbd.tsx","../../src/components/ui/label.tsx","../../src/components/ui/menubar.tsx","../../src/components/ui/navigation-menu.tsx","../../src/components/ui/pagination.tsx","../../src/components/ui/popover.tsx","../../src/components/ui/progress.tsx","../../src/components/ui/radio-group.tsx","../../src/components/ui/resizable.tsx","../../src/components/ui/scroll-area.tsx","../../src/components/ui/select.tsx","../../src/components/ui/separator.tsx","../../src/components/ui/sheet.tsx","../../src/components/ui/sidebar.tsx","../../src/components/ui/skeleton.tsx","../../src/components/ui/slider.tsx","../../src/components/ui/sonner.tsx","../../src/components/ui/spinner.tsx","../../src/components/ui/switch.tsx","../../src/components/ui/table.tsx","../../src/components/ui/tabs.tsx","../../src/components/ui/textarea.tsx","../../src/components/ui/toggle-group.tsx","../../src/components/ui/toggle.tsx","../../src/components/ui/tooltip.tsx","../../src/hooks/use-mobile.ts","../../src/lib/oraclequeryclient.ts","../../src/lib/utils.ts","../../src/store/usestore.ts","../../src/types/crm.ts","../../src/types/index.ts","../../src/utils/curvegenerator.ts"],"version":"5.9.3"}
|
{"root":["../../src/app.tsx","../../src/main.tsx","../../src/app/oracle/page.tsx","../../src/components/layout/loginscreen.tsx","../../src/components/layout/sidebar.tsx","../../src/components/modules/catalyst.tsx","../../src/components/modules/dashboard.tsx","../../src/components/modules/inventory.tsx","../../src/components/modules/oracle.tsx","../../src/components/modules/sentinel.tsx","../../src/components/modules/settings.tsx","../../src/components/oracle/leadinspector.tsx","../../src/components/oracle/pipelineview.tsx","../../src/components/oracle/mockleads.ts","../../src/components/sentinel/journeyriver/inspectorpanel.tsx","../../src/components/sentinel/journeyriver/riverpath.tsx","../../src/components/sentinel/journeyriver/index.tsx","../../src/components/ui/accordion.tsx","../../src/components/ui/alert-dialog.tsx","../../src/components/ui/alert.tsx","../../src/components/ui/aspect-ratio.tsx","../../src/components/ui/avatar.tsx","../../src/components/ui/badge.tsx","../../src/components/ui/breadcrumb.tsx","../../src/components/ui/button-group.tsx","../../src/components/ui/button.tsx","../../src/components/ui/calendar.tsx","../../src/components/ui/card.tsx","../../src/components/ui/carousel.tsx","../../src/components/ui/chart.tsx","../../src/components/ui/checkbox.tsx","../../src/components/ui/collapsible.tsx","../../src/components/ui/command.tsx","../../src/components/ui/context-menu.tsx","../../src/components/ui/dialog.tsx","../../src/components/ui/drawer.tsx","../../src/components/ui/dropdown-menu.tsx","../../src/components/ui/empty.tsx","../../src/components/ui/field.tsx","../../src/components/ui/form.tsx","../../src/components/ui/hover-card.tsx","../../src/components/ui/input-group.tsx","../../src/components/ui/input-otp.tsx","../../src/components/ui/input.tsx","../../src/components/ui/item.tsx","../../src/components/ui/kbd.tsx","../../src/components/ui/label.tsx","../../src/components/ui/menubar.tsx","../../src/components/ui/navigation-menu.tsx","../../src/components/ui/pagination.tsx","../../src/components/ui/popover.tsx","../../src/components/ui/progress.tsx","../../src/components/ui/radio-group.tsx","../../src/components/ui/resizable.tsx","../../src/components/ui/scroll-area.tsx","../../src/components/ui/select.tsx","../../src/components/ui/separator.tsx","../../src/components/ui/sheet.tsx","../../src/components/ui/sidebar.tsx","../../src/components/ui/skeleton.tsx","../../src/components/ui/slider.tsx","../../src/components/ui/sonner.tsx","../../src/components/ui/spinner.tsx","../../src/components/ui/switch.tsx","../../src/components/ui/table.tsx","../../src/components/ui/tabs.tsx","../../src/components/ui/textarea.tsx","../../src/components/ui/toggle-group.tsx","../../src/components/ui/toggle.tsx","../../src/components/ui/tooltip.tsx","../../src/hooks/use-mobile.ts","../../src/lib/oraclequeryclient.ts","../../src/lib/utils.ts","../../src/store/usemarketingstore.ts","../../src/store/usestore.ts","../../src/types/crm.ts","../../src/types/index.ts","../../src/utils/curvegenerator.ts"],"version":"5.9.3"}
|
||||||
3591
app/node_modules/.vite/deps/@dnd-kit_core.js
generated
vendored
3591
app/node_modules/.vite/deps/@dnd-kit_core.js
generated
vendored
File diff suppressed because it is too large
Load Diff
7
app/node_modules/.vite/deps/@dnd-kit_core.js.map
generated
vendored
7
app/node_modules/.vite/deps/@dnd-kit_core.js.map
generated
vendored
File diff suppressed because one or more lines are too long
56
app/node_modules/.vite/deps/@dnd-kit_utilities.js
generated
vendored
56
app/node_modules/.vite/deps/@dnd-kit_utilities.js
generated
vendored
@@ -1,56 +0,0 @@
|
|||||||
import {
|
|
||||||
CSS,
|
|
||||||
add,
|
|
||||||
canUseDOM,
|
|
||||||
findFirstFocusableNode,
|
|
||||||
getEventCoordinates,
|
|
||||||
getOwnerDocument,
|
|
||||||
getWindow,
|
|
||||||
hasViewportRelativeCoordinates,
|
|
||||||
isDocument,
|
|
||||||
isHTMLElement,
|
|
||||||
isKeyboardEvent,
|
|
||||||
isNode,
|
|
||||||
isSVGElement,
|
|
||||||
isTouchEvent,
|
|
||||||
isWindow,
|
|
||||||
subtract,
|
|
||||||
useCombinedRefs,
|
|
||||||
useEvent,
|
|
||||||
useInterval,
|
|
||||||
useIsomorphicLayoutEffect,
|
|
||||||
useLatestValue,
|
|
||||||
useLazyMemo,
|
|
||||||
useNodeRef,
|
|
||||||
usePrevious,
|
|
||||||
useUniqueId
|
|
||||||
} from "./chunk-YJZCGBGU.js";
|
|
||||||
import "./chunk-WUR7D6NS.js";
|
|
||||||
import "./chunk-G3PMV62Z.js";
|
|
||||||
export {
|
|
||||||
CSS,
|
|
||||||
add,
|
|
||||||
canUseDOM,
|
|
||||||
findFirstFocusableNode,
|
|
||||||
getEventCoordinates,
|
|
||||||
getOwnerDocument,
|
|
||||||
getWindow,
|
|
||||||
hasViewportRelativeCoordinates,
|
|
||||||
isDocument,
|
|
||||||
isHTMLElement,
|
|
||||||
isKeyboardEvent,
|
|
||||||
isNode,
|
|
||||||
isSVGElement,
|
|
||||||
isTouchEvent,
|
|
||||||
isWindow,
|
|
||||||
subtract,
|
|
||||||
useCombinedRefs,
|
|
||||||
useEvent,
|
|
||||||
useInterval,
|
|
||||||
useIsomorphicLayoutEffect,
|
|
||||||
useLatestValue,
|
|
||||||
useLazyMemo,
|
|
||||||
useNodeRef,
|
|
||||||
usePrevious,
|
|
||||||
useUniqueId
|
|
||||||
};
|
|
||||||
7
app/node_modules/.vite/deps/@dnd-kit_utilities.js.map
generated
vendored
7
app/node_modules/.vite/deps/@dnd-kit_utilities.js.map
generated
vendored
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"sources": [],
|
|
||||||
"sourcesContent": [],
|
|
||||||
"mappings": "",
|
|
||||||
"names": []
|
|
||||||
}
|
|
||||||
12
app/node_modules/.vite/deps/@radix-ui_react-avatar.js
generated
vendored
12
app/node_modules/.vite/deps/@radix-ui_react-avatar.js
generated
vendored
@@ -1,18 +1,18 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import {
|
import {
|
||||||
require_shim
|
createSlot
|
||||||
} from "./chunk-TXHHHGR3.js";
|
} from "./chunk-YWBEB5PG.js";
|
||||||
import {
|
import {
|
||||||
useCallbackRef,
|
useCallbackRef,
|
||||||
useLayoutEffect2
|
useLayoutEffect2
|
||||||
} from "./chunk-23FVUG5N.js";
|
} from "./chunk-23FVUG5N.js";
|
||||||
|
import "./chunk-2VUH7NEY.js";
|
||||||
|
import {
|
||||||
|
require_shim
|
||||||
|
} from "./chunk-TXHHHGR3.js";
|
||||||
import {
|
import {
|
||||||
require_react_dom
|
require_react_dom
|
||||||
} from "./chunk-YF4B4G2L.js";
|
} from "./chunk-YF4B4G2L.js";
|
||||||
import {
|
|
||||||
createSlot
|
|
||||||
} from "./chunk-YWBEB5PG.js";
|
|
||||||
import "./chunk-2VUH7NEY.js";
|
|
||||||
import {
|
import {
|
||||||
require_jsx_runtime
|
require_jsx_runtime
|
||||||
} from "./chunk-2YVA4HRZ.js";
|
} from "./chunk-2YVA4HRZ.js";
|
||||||
|
|||||||
4929
app/node_modules/.vite/deps/@radix-ui_react-dropdown-menu.js
generated
vendored
4929
app/node_modules/.vite/deps/@radix-ui_react-dropdown-menu.js
generated
vendored
File diff suppressed because it is too large
Load Diff
8
app/node_modules/.vite/deps/@radix-ui_react-dropdown-menu.js.map
generated
vendored
8
app/node_modules/.vite/deps/@radix-ui_react-dropdown-menu.js.map
generated
vendored
File diff suppressed because one or more lines are too long
755
app/node_modules/.vite/deps/@radix-ui_react-scroll-area.js
generated
vendored
755
app/node_modules/.vite/deps/@radix-ui_react-scroll-area.js
generated
vendored
@@ -1,755 +0,0 @@
|
|||||||
"use client";
|
|
||||||
import {
|
|
||||||
clamp
|
|
||||||
} from "./chunk-6ZMM2PAV.js";
|
|
||||||
import {
|
|
||||||
Presence
|
|
||||||
} from "./chunk-SMEXDMMQ.js";
|
|
||||||
import {
|
|
||||||
Primitive,
|
|
||||||
composeEventHandlers,
|
|
||||||
createContextScope,
|
|
||||||
useDirection
|
|
||||||
} from "./chunk-VPWBNV4W.js";
|
|
||||||
import {
|
|
||||||
useCallbackRef,
|
|
||||||
useLayoutEffect2
|
|
||||||
} from "./chunk-23FVUG5N.js";
|
|
||||||
import "./chunk-YF4B4G2L.js";
|
|
||||||
import {
|
|
||||||
useComposedRefs
|
|
||||||
} from "./chunk-2VUH7NEY.js";
|
|
||||||
import {
|
|
||||||
require_jsx_runtime
|
|
||||||
} from "./chunk-2YVA4HRZ.js";
|
|
||||||
import {
|
|
||||||
require_react
|
|
||||||
} from "./chunk-WUR7D6NS.js";
|
|
||||||
import {
|
|
||||||
__toESM
|
|
||||||
} from "./chunk-G3PMV62Z.js";
|
|
||||||
|
|
||||||
// node_modules/@radix-ui/react-scroll-area/dist/index.mjs
|
|
||||||
var React2 = __toESM(require_react(), 1);
|
|
||||||
var React = __toESM(require_react(), 1);
|
|
||||||
var import_jsx_runtime = __toESM(require_jsx_runtime(), 1);
|
|
||||||
function useStateMachine(initialState, machine) {
|
|
||||||
return React.useReducer((state, event) => {
|
|
||||||
const nextState = machine[state][event];
|
|
||||||
return nextState ?? state;
|
|
||||||
}, initialState);
|
|
||||||
}
|
|
||||||
var SCROLL_AREA_NAME = "ScrollArea";
|
|
||||||
var [createScrollAreaContext, createScrollAreaScope] = createContextScope(SCROLL_AREA_NAME);
|
|
||||||
var [ScrollAreaProvider, useScrollAreaContext] = createScrollAreaContext(SCROLL_AREA_NAME);
|
|
||||||
var ScrollArea = React2.forwardRef(
|
|
||||||
(props, forwardedRef) => {
|
|
||||||
const {
|
|
||||||
__scopeScrollArea,
|
|
||||||
type = "hover",
|
|
||||||
dir,
|
|
||||||
scrollHideDelay = 600,
|
|
||||||
...scrollAreaProps
|
|
||||||
} = props;
|
|
||||||
const [scrollArea, setScrollArea] = React2.useState(null);
|
|
||||||
const [viewport, setViewport] = React2.useState(null);
|
|
||||||
const [content, setContent] = React2.useState(null);
|
|
||||||
const [scrollbarX, setScrollbarX] = React2.useState(null);
|
|
||||||
const [scrollbarY, setScrollbarY] = React2.useState(null);
|
|
||||||
const [cornerWidth, setCornerWidth] = React2.useState(0);
|
|
||||||
const [cornerHeight, setCornerHeight] = React2.useState(0);
|
|
||||||
const [scrollbarXEnabled, setScrollbarXEnabled] = React2.useState(false);
|
|
||||||
const [scrollbarYEnabled, setScrollbarYEnabled] = React2.useState(false);
|
|
||||||
const composedRefs = useComposedRefs(forwardedRef, (node) => setScrollArea(node));
|
|
||||||
const direction = useDirection(dir);
|
|
||||||
return (0, import_jsx_runtime.jsx)(
|
|
||||||
ScrollAreaProvider,
|
|
||||||
{
|
|
||||||
scope: __scopeScrollArea,
|
|
||||||
type,
|
|
||||||
dir: direction,
|
|
||||||
scrollHideDelay,
|
|
||||||
scrollArea,
|
|
||||||
viewport,
|
|
||||||
onViewportChange: setViewport,
|
|
||||||
content,
|
|
||||||
onContentChange: setContent,
|
|
||||||
scrollbarX,
|
|
||||||
onScrollbarXChange: setScrollbarX,
|
|
||||||
scrollbarXEnabled,
|
|
||||||
onScrollbarXEnabledChange: setScrollbarXEnabled,
|
|
||||||
scrollbarY,
|
|
||||||
onScrollbarYChange: setScrollbarY,
|
|
||||||
scrollbarYEnabled,
|
|
||||||
onScrollbarYEnabledChange: setScrollbarYEnabled,
|
|
||||||
onCornerWidthChange: setCornerWidth,
|
|
||||||
onCornerHeightChange: setCornerHeight,
|
|
||||||
children: (0, import_jsx_runtime.jsx)(
|
|
||||||
Primitive.div,
|
|
||||||
{
|
|
||||||
dir: direction,
|
|
||||||
...scrollAreaProps,
|
|
||||||
ref: composedRefs,
|
|
||||||
style: {
|
|
||||||
position: "relative",
|
|
||||||
// Pass corner sizes as CSS vars to reduce re-renders of context consumers
|
|
||||||
["--radix-scroll-area-corner-width"]: cornerWidth + "px",
|
|
||||||
["--radix-scroll-area-corner-height"]: cornerHeight + "px",
|
|
||||||
...props.style
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
ScrollArea.displayName = SCROLL_AREA_NAME;
|
|
||||||
var VIEWPORT_NAME = "ScrollAreaViewport";
|
|
||||||
var ScrollAreaViewport = React2.forwardRef(
|
|
||||||
(props, forwardedRef) => {
|
|
||||||
const { __scopeScrollArea, children, nonce, ...viewportProps } = props;
|
|
||||||
const context = useScrollAreaContext(VIEWPORT_NAME, __scopeScrollArea);
|
|
||||||
const ref = React2.useRef(null);
|
|
||||||
const composedRefs = useComposedRefs(forwardedRef, ref, context.onViewportChange);
|
|
||||||
return (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
||||||
(0, import_jsx_runtime.jsx)(
|
|
||||||
"style",
|
|
||||||
{
|
|
||||||
dangerouslySetInnerHTML: {
|
|
||||||
__html: `[data-radix-scroll-area-viewport]{scrollbar-width:none;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;}[data-radix-scroll-area-viewport]::-webkit-scrollbar{display:none}`
|
|
||||||
},
|
|
||||||
nonce
|
|
||||||
}
|
|
||||||
),
|
|
||||||
(0, import_jsx_runtime.jsx)(
|
|
||||||
Primitive.div,
|
|
||||||
{
|
|
||||||
"data-radix-scroll-area-viewport": "",
|
|
||||||
...viewportProps,
|
|
||||||
ref: composedRefs,
|
|
||||||
style: {
|
|
||||||
/**
|
|
||||||
* We don't support `visible` because the intention is to have at least one scrollbar
|
|
||||||
* if this component is used and `visible` will behave like `auto` in that case
|
|
||||||
* https://developer.mozilla.org/en-US/docs/Web/CSS/overflow#description
|
|
||||||
*
|
|
||||||
* We don't handle `auto` because the intention is for the native implementation
|
|
||||||
* to be hidden if using this component. We just want to ensure the node is scrollable
|
|
||||||
* so could have used either `scroll` or `auto` here. We picked `scroll` to prevent
|
|
||||||
* the browser from having to work out whether to render native scrollbars or not,
|
|
||||||
* we tell it to with the intention of hiding them in CSS.
|
|
||||||
*/
|
|
||||||
overflowX: context.scrollbarXEnabled ? "scroll" : "hidden",
|
|
||||||
overflowY: context.scrollbarYEnabled ? "scroll" : "hidden",
|
|
||||||
...props.style
|
|
||||||
},
|
|
||||||
children: (0, import_jsx_runtime.jsx)("div", { ref: context.onContentChange, style: { minWidth: "100%", display: "table" }, children })
|
|
||||||
}
|
|
||||||
)
|
|
||||||
] });
|
|
||||||
}
|
|
||||||
);
|
|
||||||
ScrollAreaViewport.displayName = VIEWPORT_NAME;
|
|
||||||
var SCROLLBAR_NAME = "ScrollAreaScrollbar";
|
|
||||||
var ScrollAreaScrollbar = React2.forwardRef(
|
|
||||||
(props, forwardedRef) => {
|
|
||||||
const { forceMount, ...scrollbarProps } = props;
|
|
||||||
const context = useScrollAreaContext(SCROLLBAR_NAME, props.__scopeScrollArea);
|
|
||||||
const { onScrollbarXEnabledChange, onScrollbarYEnabledChange } = context;
|
|
||||||
const isHorizontal = props.orientation === "horizontal";
|
|
||||||
React2.useEffect(() => {
|
|
||||||
isHorizontal ? onScrollbarXEnabledChange(true) : onScrollbarYEnabledChange(true);
|
|
||||||
return () => {
|
|
||||||
isHorizontal ? onScrollbarXEnabledChange(false) : onScrollbarYEnabledChange(false);
|
|
||||||
};
|
|
||||||
}, [isHorizontal, onScrollbarXEnabledChange, onScrollbarYEnabledChange]);
|
|
||||||
return context.type === "hover" ? (0, import_jsx_runtime.jsx)(ScrollAreaScrollbarHover, { ...scrollbarProps, ref: forwardedRef, forceMount }) : context.type === "scroll" ? (0, import_jsx_runtime.jsx)(ScrollAreaScrollbarScroll, { ...scrollbarProps, ref: forwardedRef, forceMount }) : context.type === "auto" ? (0, import_jsx_runtime.jsx)(ScrollAreaScrollbarAuto, { ...scrollbarProps, ref: forwardedRef, forceMount }) : context.type === "always" ? (0, import_jsx_runtime.jsx)(ScrollAreaScrollbarVisible, { ...scrollbarProps, ref: forwardedRef }) : null;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
ScrollAreaScrollbar.displayName = SCROLLBAR_NAME;
|
|
||||||
var ScrollAreaScrollbarHover = React2.forwardRef((props, forwardedRef) => {
|
|
||||||
const { forceMount, ...scrollbarProps } = props;
|
|
||||||
const context = useScrollAreaContext(SCROLLBAR_NAME, props.__scopeScrollArea);
|
|
||||||
const [visible, setVisible] = React2.useState(false);
|
|
||||||
React2.useEffect(() => {
|
|
||||||
const scrollArea = context.scrollArea;
|
|
||||||
let hideTimer = 0;
|
|
||||||
if (scrollArea) {
|
|
||||||
const handlePointerEnter = () => {
|
|
||||||
window.clearTimeout(hideTimer);
|
|
||||||
setVisible(true);
|
|
||||||
};
|
|
||||||
const handlePointerLeave = () => {
|
|
||||||
hideTimer = window.setTimeout(() => setVisible(false), context.scrollHideDelay);
|
|
||||||
};
|
|
||||||
scrollArea.addEventListener("pointerenter", handlePointerEnter);
|
|
||||||
scrollArea.addEventListener("pointerleave", handlePointerLeave);
|
|
||||||
return () => {
|
|
||||||
window.clearTimeout(hideTimer);
|
|
||||||
scrollArea.removeEventListener("pointerenter", handlePointerEnter);
|
|
||||||
scrollArea.removeEventListener("pointerleave", handlePointerLeave);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}, [context.scrollArea, context.scrollHideDelay]);
|
|
||||||
return (0, import_jsx_runtime.jsx)(Presence, { present: forceMount || visible, children: (0, import_jsx_runtime.jsx)(
|
|
||||||
ScrollAreaScrollbarAuto,
|
|
||||||
{
|
|
||||||
"data-state": visible ? "visible" : "hidden",
|
|
||||||
...scrollbarProps,
|
|
||||||
ref: forwardedRef
|
|
||||||
}
|
|
||||||
) });
|
|
||||||
});
|
|
||||||
var ScrollAreaScrollbarScroll = React2.forwardRef((props, forwardedRef) => {
|
|
||||||
const { forceMount, ...scrollbarProps } = props;
|
|
||||||
const context = useScrollAreaContext(SCROLLBAR_NAME, props.__scopeScrollArea);
|
|
||||||
const isHorizontal = props.orientation === "horizontal";
|
|
||||||
const debounceScrollEnd = useDebounceCallback(() => send("SCROLL_END"), 100);
|
|
||||||
const [state, send] = useStateMachine("hidden", {
|
|
||||||
hidden: {
|
|
||||||
SCROLL: "scrolling"
|
|
||||||
},
|
|
||||||
scrolling: {
|
|
||||||
SCROLL_END: "idle",
|
|
||||||
POINTER_ENTER: "interacting"
|
|
||||||
},
|
|
||||||
interacting: {
|
|
||||||
SCROLL: "interacting",
|
|
||||||
POINTER_LEAVE: "idle"
|
|
||||||
},
|
|
||||||
idle: {
|
|
||||||
HIDE: "hidden",
|
|
||||||
SCROLL: "scrolling",
|
|
||||||
POINTER_ENTER: "interacting"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
React2.useEffect(() => {
|
|
||||||
if (state === "idle") {
|
|
||||||
const hideTimer = window.setTimeout(() => send("HIDE"), context.scrollHideDelay);
|
|
||||||
return () => window.clearTimeout(hideTimer);
|
|
||||||
}
|
|
||||||
}, [state, context.scrollHideDelay, send]);
|
|
||||||
React2.useEffect(() => {
|
|
||||||
const viewport = context.viewport;
|
|
||||||
const scrollDirection = isHorizontal ? "scrollLeft" : "scrollTop";
|
|
||||||
if (viewport) {
|
|
||||||
let prevScrollPos = viewport[scrollDirection];
|
|
||||||
const handleScroll = () => {
|
|
||||||
const scrollPos = viewport[scrollDirection];
|
|
||||||
const hasScrollInDirectionChanged = prevScrollPos !== scrollPos;
|
|
||||||
if (hasScrollInDirectionChanged) {
|
|
||||||
send("SCROLL");
|
|
||||||
debounceScrollEnd();
|
|
||||||
}
|
|
||||||
prevScrollPos = scrollPos;
|
|
||||||
};
|
|
||||||
viewport.addEventListener("scroll", handleScroll);
|
|
||||||
return () => viewport.removeEventListener("scroll", handleScroll);
|
|
||||||
}
|
|
||||||
}, [context.viewport, isHorizontal, send, debounceScrollEnd]);
|
|
||||||
return (0, import_jsx_runtime.jsx)(Presence, { present: forceMount || state !== "hidden", children: (0, import_jsx_runtime.jsx)(
|
|
||||||
ScrollAreaScrollbarVisible,
|
|
||||||
{
|
|
||||||
"data-state": state === "hidden" ? "hidden" : "visible",
|
|
||||||
...scrollbarProps,
|
|
||||||
ref: forwardedRef,
|
|
||||||
onPointerEnter: composeEventHandlers(props.onPointerEnter, () => send("POINTER_ENTER")),
|
|
||||||
onPointerLeave: composeEventHandlers(props.onPointerLeave, () => send("POINTER_LEAVE"))
|
|
||||||
}
|
|
||||||
) });
|
|
||||||
});
|
|
||||||
var ScrollAreaScrollbarAuto = React2.forwardRef((props, forwardedRef) => {
|
|
||||||
const context = useScrollAreaContext(SCROLLBAR_NAME, props.__scopeScrollArea);
|
|
||||||
const { forceMount, ...scrollbarProps } = props;
|
|
||||||
const [visible, setVisible] = React2.useState(false);
|
|
||||||
const isHorizontal = props.orientation === "horizontal";
|
|
||||||
const handleResize = useDebounceCallback(() => {
|
|
||||||
if (context.viewport) {
|
|
||||||
const isOverflowX = context.viewport.offsetWidth < context.viewport.scrollWidth;
|
|
||||||
const isOverflowY = context.viewport.offsetHeight < context.viewport.scrollHeight;
|
|
||||||
setVisible(isHorizontal ? isOverflowX : isOverflowY);
|
|
||||||
}
|
|
||||||
}, 10);
|
|
||||||
useResizeObserver(context.viewport, handleResize);
|
|
||||||
useResizeObserver(context.content, handleResize);
|
|
||||||
return (0, import_jsx_runtime.jsx)(Presence, { present: forceMount || visible, children: (0, import_jsx_runtime.jsx)(
|
|
||||||
ScrollAreaScrollbarVisible,
|
|
||||||
{
|
|
||||||
"data-state": visible ? "visible" : "hidden",
|
|
||||||
...scrollbarProps,
|
|
||||||
ref: forwardedRef
|
|
||||||
}
|
|
||||||
) });
|
|
||||||
});
|
|
||||||
var ScrollAreaScrollbarVisible = React2.forwardRef((props, forwardedRef) => {
|
|
||||||
const { orientation = "vertical", ...scrollbarProps } = props;
|
|
||||||
const context = useScrollAreaContext(SCROLLBAR_NAME, props.__scopeScrollArea);
|
|
||||||
const thumbRef = React2.useRef(null);
|
|
||||||
const pointerOffsetRef = React2.useRef(0);
|
|
||||||
const [sizes, setSizes] = React2.useState({
|
|
||||||
content: 0,
|
|
||||||
viewport: 0,
|
|
||||||
scrollbar: { size: 0, paddingStart: 0, paddingEnd: 0 }
|
|
||||||
});
|
|
||||||
const thumbRatio = getThumbRatio(sizes.viewport, sizes.content);
|
|
||||||
const commonProps = {
|
|
||||||
...scrollbarProps,
|
|
||||||
sizes,
|
|
||||||
onSizesChange: setSizes,
|
|
||||||
hasThumb: Boolean(thumbRatio > 0 && thumbRatio < 1),
|
|
||||||
onThumbChange: (thumb) => thumbRef.current = thumb,
|
|
||||||
onThumbPointerUp: () => pointerOffsetRef.current = 0,
|
|
||||||
onThumbPointerDown: (pointerPos) => pointerOffsetRef.current = pointerPos
|
|
||||||
};
|
|
||||||
function getScrollPosition(pointerPos, dir) {
|
|
||||||
return getScrollPositionFromPointer(pointerPos, pointerOffsetRef.current, sizes, dir);
|
|
||||||
}
|
|
||||||
if (orientation === "horizontal") {
|
|
||||||
return (0, import_jsx_runtime.jsx)(
|
|
||||||
ScrollAreaScrollbarX,
|
|
||||||
{
|
|
||||||
...commonProps,
|
|
||||||
ref: forwardedRef,
|
|
||||||
onThumbPositionChange: () => {
|
|
||||||
if (context.viewport && thumbRef.current) {
|
|
||||||
const scrollPos = context.viewport.scrollLeft;
|
|
||||||
const offset = getThumbOffsetFromScroll(scrollPos, sizes, context.dir);
|
|
||||||
thumbRef.current.style.transform = `translate3d(${offset}px, 0, 0)`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onWheelScroll: (scrollPos) => {
|
|
||||||
if (context.viewport) context.viewport.scrollLeft = scrollPos;
|
|
||||||
},
|
|
||||||
onDragScroll: (pointerPos) => {
|
|
||||||
if (context.viewport) {
|
|
||||||
context.viewport.scrollLeft = getScrollPosition(pointerPos, context.dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (orientation === "vertical") {
|
|
||||||
return (0, import_jsx_runtime.jsx)(
|
|
||||||
ScrollAreaScrollbarY,
|
|
||||||
{
|
|
||||||
...commonProps,
|
|
||||||
ref: forwardedRef,
|
|
||||||
onThumbPositionChange: () => {
|
|
||||||
if (context.viewport && thumbRef.current) {
|
|
||||||
const scrollPos = context.viewport.scrollTop;
|
|
||||||
const offset = getThumbOffsetFromScroll(scrollPos, sizes);
|
|
||||||
thumbRef.current.style.transform = `translate3d(0, ${offset}px, 0)`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onWheelScroll: (scrollPos) => {
|
|
||||||
if (context.viewport) context.viewport.scrollTop = scrollPos;
|
|
||||||
},
|
|
||||||
onDragScroll: (pointerPos) => {
|
|
||||||
if (context.viewport) context.viewport.scrollTop = getScrollPosition(pointerPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
var ScrollAreaScrollbarX = React2.forwardRef((props, forwardedRef) => {
|
|
||||||
const { sizes, onSizesChange, ...scrollbarProps } = props;
|
|
||||||
const context = useScrollAreaContext(SCROLLBAR_NAME, props.__scopeScrollArea);
|
|
||||||
const [computedStyle, setComputedStyle] = React2.useState();
|
|
||||||
const ref = React2.useRef(null);
|
|
||||||
const composeRefs = useComposedRefs(forwardedRef, ref, context.onScrollbarXChange);
|
|
||||||
React2.useEffect(() => {
|
|
||||||
if (ref.current) setComputedStyle(getComputedStyle(ref.current));
|
|
||||||
}, [ref]);
|
|
||||||
return (0, import_jsx_runtime.jsx)(
|
|
||||||
ScrollAreaScrollbarImpl,
|
|
||||||
{
|
|
||||||
"data-orientation": "horizontal",
|
|
||||||
...scrollbarProps,
|
|
||||||
ref: composeRefs,
|
|
||||||
sizes,
|
|
||||||
style: {
|
|
||||||
bottom: 0,
|
|
||||||
left: context.dir === "rtl" ? "var(--radix-scroll-area-corner-width)" : 0,
|
|
||||||
right: context.dir === "ltr" ? "var(--radix-scroll-area-corner-width)" : 0,
|
|
||||||
["--radix-scroll-area-thumb-width"]: getThumbSize(sizes) + "px",
|
|
||||||
...props.style
|
|
||||||
},
|
|
||||||
onThumbPointerDown: (pointerPos) => props.onThumbPointerDown(pointerPos.x),
|
|
||||||
onDragScroll: (pointerPos) => props.onDragScroll(pointerPos.x),
|
|
||||||
onWheelScroll: (event, maxScrollPos) => {
|
|
||||||
if (context.viewport) {
|
|
||||||
const scrollPos = context.viewport.scrollLeft + event.deltaX;
|
|
||||||
props.onWheelScroll(scrollPos);
|
|
||||||
if (isScrollingWithinScrollbarBounds(scrollPos, maxScrollPos)) {
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onResize: () => {
|
|
||||||
if (ref.current && context.viewport && computedStyle) {
|
|
||||||
onSizesChange({
|
|
||||||
content: context.viewport.scrollWidth,
|
|
||||||
viewport: context.viewport.offsetWidth,
|
|
||||||
scrollbar: {
|
|
||||||
size: ref.current.clientWidth,
|
|
||||||
paddingStart: toInt(computedStyle.paddingLeft),
|
|
||||||
paddingEnd: toInt(computedStyle.paddingRight)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
var ScrollAreaScrollbarY = React2.forwardRef((props, forwardedRef) => {
|
|
||||||
const { sizes, onSizesChange, ...scrollbarProps } = props;
|
|
||||||
const context = useScrollAreaContext(SCROLLBAR_NAME, props.__scopeScrollArea);
|
|
||||||
const [computedStyle, setComputedStyle] = React2.useState();
|
|
||||||
const ref = React2.useRef(null);
|
|
||||||
const composeRefs = useComposedRefs(forwardedRef, ref, context.onScrollbarYChange);
|
|
||||||
React2.useEffect(() => {
|
|
||||||
if (ref.current) setComputedStyle(getComputedStyle(ref.current));
|
|
||||||
}, [ref]);
|
|
||||||
return (0, import_jsx_runtime.jsx)(
|
|
||||||
ScrollAreaScrollbarImpl,
|
|
||||||
{
|
|
||||||
"data-orientation": "vertical",
|
|
||||||
...scrollbarProps,
|
|
||||||
ref: composeRefs,
|
|
||||||
sizes,
|
|
||||||
style: {
|
|
||||||
top: 0,
|
|
||||||
right: context.dir === "ltr" ? 0 : void 0,
|
|
||||||
left: context.dir === "rtl" ? 0 : void 0,
|
|
||||||
bottom: "var(--radix-scroll-area-corner-height)",
|
|
||||||
["--radix-scroll-area-thumb-height"]: getThumbSize(sizes) + "px",
|
|
||||||
...props.style
|
|
||||||
},
|
|
||||||
onThumbPointerDown: (pointerPos) => props.onThumbPointerDown(pointerPos.y),
|
|
||||||
onDragScroll: (pointerPos) => props.onDragScroll(pointerPos.y),
|
|
||||||
onWheelScroll: (event, maxScrollPos) => {
|
|
||||||
if (context.viewport) {
|
|
||||||
const scrollPos = context.viewport.scrollTop + event.deltaY;
|
|
||||||
props.onWheelScroll(scrollPos);
|
|
||||||
if (isScrollingWithinScrollbarBounds(scrollPos, maxScrollPos)) {
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onResize: () => {
|
|
||||||
if (ref.current && context.viewport && computedStyle) {
|
|
||||||
onSizesChange({
|
|
||||||
content: context.viewport.scrollHeight,
|
|
||||||
viewport: context.viewport.offsetHeight,
|
|
||||||
scrollbar: {
|
|
||||||
size: ref.current.clientHeight,
|
|
||||||
paddingStart: toInt(computedStyle.paddingTop),
|
|
||||||
paddingEnd: toInt(computedStyle.paddingBottom)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
var [ScrollbarProvider, useScrollbarContext] = createScrollAreaContext(SCROLLBAR_NAME);
|
|
||||||
var ScrollAreaScrollbarImpl = React2.forwardRef((props, forwardedRef) => {
|
|
||||||
const {
|
|
||||||
__scopeScrollArea,
|
|
||||||
sizes,
|
|
||||||
hasThumb,
|
|
||||||
onThumbChange,
|
|
||||||
onThumbPointerUp,
|
|
||||||
onThumbPointerDown,
|
|
||||||
onThumbPositionChange,
|
|
||||||
onDragScroll,
|
|
||||||
onWheelScroll,
|
|
||||||
onResize,
|
|
||||||
...scrollbarProps
|
|
||||||
} = props;
|
|
||||||
const context = useScrollAreaContext(SCROLLBAR_NAME, __scopeScrollArea);
|
|
||||||
const [scrollbar, setScrollbar] = React2.useState(null);
|
|
||||||
const composeRefs = useComposedRefs(forwardedRef, (node) => setScrollbar(node));
|
|
||||||
const rectRef = React2.useRef(null);
|
|
||||||
const prevWebkitUserSelectRef = React2.useRef("");
|
|
||||||
const viewport = context.viewport;
|
|
||||||
const maxScrollPos = sizes.content - sizes.viewport;
|
|
||||||
const handleWheelScroll = useCallbackRef(onWheelScroll);
|
|
||||||
const handleThumbPositionChange = useCallbackRef(onThumbPositionChange);
|
|
||||||
const handleResize = useDebounceCallback(onResize, 10);
|
|
||||||
function handleDragScroll(event) {
|
|
||||||
if (rectRef.current) {
|
|
||||||
const x = event.clientX - rectRef.current.left;
|
|
||||||
const y = event.clientY - rectRef.current.top;
|
|
||||||
onDragScroll({ x, y });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
React2.useEffect(() => {
|
|
||||||
const handleWheel = (event) => {
|
|
||||||
const element = event.target;
|
|
||||||
const isScrollbarWheel = scrollbar?.contains(element);
|
|
||||||
if (isScrollbarWheel) handleWheelScroll(event, maxScrollPos);
|
|
||||||
};
|
|
||||||
document.addEventListener("wheel", handleWheel, { passive: false });
|
|
||||||
return () => document.removeEventListener("wheel", handleWheel, { passive: false });
|
|
||||||
}, [viewport, scrollbar, maxScrollPos, handleWheelScroll]);
|
|
||||||
React2.useEffect(handleThumbPositionChange, [sizes, handleThumbPositionChange]);
|
|
||||||
useResizeObserver(scrollbar, handleResize);
|
|
||||||
useResizeObserver(context.content, handleResize);
|
|
||||||
return (0, import_jsx_runtime.jsx)(
|
|
||||||
ScrollbarProvider,
|
|
||||||
{
|
|
||||||
scope: __scopeScrollArea,
|
|
||||||
scrollbar,
|
|
||||||
hasThumb,
|
|
||||||
onThumbChange: useCallbackRef(onThumbChange),
|
|
||||||
onThumbPointerUp: useCallbackRef(onThumbPointerUp),
|
|
||||||
onThumbPositionChange: handleThumbPositionChange,
|
|
||||||
onThumbPointerDown: useCallbackRef(onThumbPointerDown),
|
|
||||||
children: (0, import_jsx_runtime.jsx)(
|
|
||||||
Primitive.div,
|
|
||||||
{
|
|
||||||
...scrollbarProps,
|
|
||||||
ref: composeRefs,
|
|
||||||
style: { position: "absolute", ...scrollbarProps.style },
|
|
||||||
onPointerDown: composeEventHandlers(props.onPointerDown, (event) => {
|
|
||||||
const mainPointer = 0;
|
|
||||||
if (event.button === mainPointer) {
|
|
||||||
const element = event.target;
|
|
||||||
element.setPointerCapture(event.pointerId);
|
|
||||||
rectRef.current = scrollbar.getBoundingClientRect();
|
|
||||||
prevWebkitUserSelectRef.current = document.body.style.webkitUserSelect;
|
|
||||||
document.body.style.webkitUserSelect = "none";
|
|
||||||
if (context.viewport) context.viewport.style.scrollBehavior = "auto";
|
|
||||||
handleDragScroll(event);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
onPointerMove: composeEventHandlers(props.onPointerMove, handleDragScroll),
|
|
||||||
onPointerUp: composeEventHandlers(props.onPointerUp, (event) => {
|
|
||||||
const element = event.target;
|
|
||||||
if (element.hasPointerCapture(event.pointerId)) {
|
|
||||||
element.releasePointerCapture(event.pointerId);
|
|
||||||
}
|
|
||||||
document.body.style.webkitUserSelect = prevWebkitUserSelectRef.current;
|
|
||||||
if (context.viewport) context.viewport.style.scrollBehavior = "";
|
|
||||||
rectRef.current = null;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
var THUMB_NAME = "ScrollAreaThumb";
|
|
||||||
var ScrollAreaThumb = React2.forwardRef(
|
|
||||||
(props, forwardedRef) => {
|
|
||||||
const { forceMount, ...thumbProps } = props;
|
|
||||||
const scrollbarContext = useScrollbarContext(THUMB_NAME, props.__scopeScrollArea);
|
|
||||||
return (0, import_jsx_runtime.jsx)(Presence, { present: forceMount || scrollbarContext.hasThumb, children: (0, import_jsx_runtime.jsx)(ScrollAreaThumbImpl, { ref: forwardedRef, ...thumbProps }) });
|
|
||||||
}
|
|
||||||
);
|
|
||||||
var ScrollAreaThumbImpl = React2.forwardRef(
|
|
||||||
(props, forwardedRef) => {
|
|
||||||
const { __scopeScrollArea, style, ...thumbProps } = props;
|
|
||||||
const scrollAreaContext = useScrollAreaContext(THUMB_NAME, __scopeScrollArea);
|
|
||||||
const scrollbarContext = useScrollbarContext(THUMB_NAME, __scopeScrollArea);
|
|
||||||
const { onThumbPositionChange } = scrollbarContext;
|
|
||||||
const composedRef = useComposedRefs(
|
|
||||||
forwardedRef,
|
|
||||||
(node) => scrollbarContext.onThumbChange(node)
|
|
||||||
);
|
|
||||||
const removeUnlinkedScrollListenerRef = React2.useRef(void 0);
|
|
||||||
const debounceScrollEnd = useDebounceCallback(() => {
|
|
||||||
if (removeUnlinkedScrollListenerRef.current) {
|
|
||||||
removeUnlinkedScrollListenerRef.current();
|
|
||||||
removeUnlinkedScrollListenerRef.current = void 0;
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
React2.useEffect(() => {
|
|
||||||
const viewport = scrollAreaContext.viewport;
|
|
||||||
if (viewport) {
|
|
||||||
const handleScroll = () => {
|
|
||||||
debounceScrollEnd();
|
|
||||||
if (!removeUnlinkedScrollListenerRef.current) {
|
|
||||||
const listener = addUnlinkedScrollListener(viewport, onThumbPositionChange);
|
|
||||||
removeUnlinkedScrollListenerRef.current = listener;
|
|
||||||
onThumbPositionChange();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
onThumbPositionChange();
|
|
||||||
viewport.addEventListener("scroll", handleScroll);
|
|
||||||
return () => viewport.removeEventListener("scroll", handleScroll);
|
|
||||||
}
|
|
||||||
}, [scrollAreaContext.viewport, debounceScrollEnd, onThumbPositionChange]);
|
|
||||||
return (0, import_jsx_runtime.jsx)(
|
|
||||||
Primitive.div,
|
|
||||||
{
|
|
||||||
"data-state": scrollbarContext.hasThumb ? "visible" : "hidden",
|
|
||||||
...thumbProps,
|
|
||||||
ref: composedRef,
|
|
||||||
style: {
|
|
||||||
width: "var(--radix-scroll-area-thumb-width)",
|
|
||||||
height: "var(--radix-scroll-area-thumb-height)",
|
|
||||||
...style
|
|
||||||
},
|
|
||||||
onPointerDownCapture: composeEventHandlers(props.onPointerDownCapture, (event) => {
|
|
||||||
const thumb = event.target;
|
|
||||||
const thumbRect = thumb.getBoundingClientRect();
|
|
||||||
const x = event.clientX - thumbRect.left;
|
|
||||||
const y = event.clientY - thumbRect.top;
|
|
||||||
scrollbarContext.onThumbPointerDown({ x, y });
|
|
||||||
}),
|
|
||||||
onPointerUp: composeEventHandlers(props.onPointerUp, scrollbarContext.onThumbPointerUp)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
ScrollAreaThumb.displayName = THUMB_NAME;
|
|
||||||
var CORNER_NAME = "ScrollAreaCorner";
|
|
||||||
var ScrollAreaCorner = React2.forwardRef(
|
|
||||||
(props, forwardedRef) => {
|
|
||||||
const context = useScrollAreaContext(CORNER_NAME, props.__scopeScrollArea);
|
|
||||||
const hasBothScrollbarsVisible = Boolean(context.scrollbarX && context.scrollbarY);
|
|
||||||
const hasCorner = context.type !== "scroll" && hasBothScrollbarsVisible;
|
|
||||||
return hasCorner ? (0, import_jsx_runtime.jsx)(ScrollAreaCornerImpl, { ...props, ref: forwardedRef }) : null;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
ScrollAreaCorner.displayName = CORNER_NAME;
|
|
||||||
var ScrollAreaCornerImpl = React2.forwardRef((props, forwardedRef) => {
|
|
||||||
const { __scopeScrollArea, ...cornerProps } = props;
|
|
||||||
const context = useScrollAreaContext(CORNER_NAME, __scopeScrollArea);
|
|
||||||
const [width, setWidth] = React2.useState(0);
|
|
||||||
const [height, setHeight] = React2.useState(0);
|
|
||||||
const hasSize = Boolean(width && height);
|
|
||||||
useResizeObserver(context.scrollbarX, () => {
|
|
||||||
const height2 = context.scrollbarX?.offsetHeight || 0;
|
|
||||||
context.onCornerHeightChange(height2);
|
|
||||||
setHeight(height2);
|
|
||||||
});
|
|
||||||
useResizeObserver(context.scrollbarY, () => {
|
|
||||||
const width2 = context.scrollbarY?.offsetWidth || 0;
|
|
||||||
context.onCornerWidthChange(width2);
|
|
||||||
setWidth(width2);
|
|
||||||
});
|
|
||||||
return hasSize ? (0, import_jsx_runtime.jsx)(
|
|
||||||
Primitive.div,
|
|
||||||
{
|
|
||||||
...cornerProps,
|
|
||||||
ref: forwardedRef,
|
|
||||||
style: {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
position: "absolute",
|
|
||||||
right: context.dir === "ltr" ? 0 : void 0,
|
|
||||||
left: context.dir === "rtl" ? 0 : void 0,
|
|
||||||
bottom: 0,
|
|
||||||
...props.style
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) : null;
|
|
||||||
});
|
|
||||||
function toInt(value) {
|
|
||||||
return value ? parseInt(value, 10) : 0;
|
|
||||||
}
|
|
||||||
function getThumbRatio(viewportSize, contentSize) {
|
|
||||||
const ratio = viewportSize / contentSize;
|
|
||||||
return isNaN(ratio) ? 0 : ratio;
|
|
||||||
}
|
|
||||||
function getThumbSize(sizes) {
|
|
||||||
const ratio = getThumbRatio(sizes.viewport, sizes.content);
|
|
||||||
const scrollbarPadding = sizes.scrollbar.paddingStart + sizes.scrollbar.paddingEnd;
|
|
||||||
const thumbSize = (sizes.scrollbar.size - scrollbarPadding) * ratio;
|
|
||||||
return Math.max(thumbSize, 18);
|
|
||||||
}
|
|
||||||
function getScrollPositionFromPointer(pointerPos, pointerOffset, sizes, dir = "ltr") {
|
|
||||||
const thumbSizePx = getThumbSize(sizes);
|
|
||||||
const thumbCenter = thumbSizePx / 2;
|
|
||||||
const offset = pointerOffset || thumbCenter;
|
|
||||||
const thumbOffsetFromEnd = thumbSizePx - offset;
|
|
||||||
const minPointerPos = sizes.scrollbar.paddingStart + offset;
|
|
||||||
const maxPointerPos = sizes.scrollbar.size - sizes.scrollbar.paddingEnd - thumbOffsetFromEnd;
|
|
||||||
const maxScrollPos = sizes.content - sizes.viewport;
|
|
||||||
const scrollRange = dir === "ltr" ? [0, maxScrollPos] : [maxScrollPos * -1, 0];
|
|
||||||
const interpolate = linearScale([minPointerPos, maxPointerPos], scrollRange);
|
|
||||||
return interpolate(pointerPos);
|
|
||||||
}
|
|
||||||
function getThumbOffsetFromScroll(scrollPos, sizes, dir = "ltr") {
|
|
||||||
const thumbSizePx = getThumbSize(sizes);
|
|
||||||
const scrollbarPadding = sizes.scrollbar.paddingStart + sizes.scrollbar.paddingEnd;
|
|
||||||
const scrollbar = sizes.scrollbar.size - scrollbarPadding;
|
|
||||||
const maxScrollPos = sizes.content - sizes.viewport;
|
|
||||||
const maxThumbPos = scrollbar - thumbSizePx;
|
|
||||||
const scrollClampRange = dir === "ltr" ? [0, maxScrollPos] : [maxScrollPos * -1, 0];
|
|
||||||
const scrollWithoutMomentum = clamp(scrollPos, scrollClampRange);
|
|
||||||
const interpolate = linearScale([0, maxScrollPos], [0, maxThumbPos]);
|
|
||||||
return interpolate(scrollWithoutMomentum);
|
|
||||||
}
|
|
||||||
function linearScale(input, output) {
|
|
||||||
return (value) => {
|
|
||||||
if (input[0] === input[1] || output[0] === output[1]) return output[0];
|
|
||||||
const ratio = (output[1] - output[0]) / (input[1] - input[0]);
|
|
||||||
return output[0] + ratio * (value - input[0]);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function isScrollingWithinScrollbarBounds(scrollPos, maxScrollPos) {
|
|
||||||
return scrollPos > 0 && scrollPos < maxScrollPos;
|
|
||||||
}
|
|
||||||
var addUnlinkedScrollListener = (node, handler = () => {
|
|
||||||
}) => {
|
|
||||||
let prevPosition = { left: node.scrollLeft, top: node.scrollTop };
|
|
||||||
let rAF = 0;
|
|
||||||
(function loop() {
|
|
||||||
const position = { left: node.scrollLeft, top: node.scrollTop };
|
|
||||||
const isHorizontalScroll = prevPosition.left !== position.left;
|
|
||||||
const isVerticalScroll = prevPosition.top !== position.top;
|
|
||||||
if (isHorizontalScroll || isVerticalScroll) handler();
|
|
||||||
prevPosition = position;
|
|
||||||
rAF = window.requestAnimationFrame(loop);
|
|
||||||
})();
|
|
||||||
return () => window.cancelAnimationFrame(rAF);
|
|
||||||
};
|
|
||||||
function useDebounceCallback(callback, delay) {
|
|
||||||
const handleCallback = useCallbackRef(callback);
|
|
||||||
const debounceTimerRef = React2.useRef(0);
|
|
||||||
React2.useEffect(() => () => window.clearTimeout(debounceTimerRef.current), []);
|
|
||||||
return React2.useCallback(() => {
|
|
||||||
window.clearTimeout(debounceTimerRef.current);
|
|
||||||
debounceTimerRef.current = window.setTimeout(handleCallback, delay);
|
|
||||||
}, [handleCallback, delay]);
|
|
||||||
}
|
|
||||||
function useResizeObserver(element, onResize) {
|
|
||||||
const handleResize = useCallbackRef(onResize);
|
|
||||||
useLayoutEffect2(() => {
|
|
||||||
let rAF = 0;
|
|
||||||
if (element) {
|
|
||||||
const resizeObserver = new ResizeObserver(() => {
|
|
||||||
cancelAnimationFrame(rAF);
|
|
||||||
rAF = window.requestAnimationFrame(handleResize);
|
|
||||||
});
|
|
||||||
resizeObserver.observe(element);
|
|
||||||
return () => {
|
|
||||||
window.cancelAnimationFrame(rAF);
|
|
||||||
resizeObserver.unobserve(element);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}, [element, handleResize]);
|
|
||||||
}
|
|
||||||
var Root = ScrollArea;
|
|
||||||
var Viewport = ScrollAreaViewport;
|
|
||||||
var Scrollbar = ScrollAreaScrollbar;
|
|
||||||
var Thumb = ScrollAreaThumb;
|
|
||||||
var Corner = ScrollAreaCorner;
|
|
||||||
export {
|
|
||||||
Corner,
|
|
||||||
Root,
|
|
||||||
ScrollArea,
|
|
||||||
ScrollAreaCorner,
|
|
||||||
ScrollAreaScrollbar,
|
|
||||||
ScrollAreaThumb,
|
|
||||||
ScrollAreaViewport,
|
|
||||||
Scrollbar,
|
|
||||||
Thumb,
|
|
||||||
Viewport,
|
|
||||||
createScrollAreaScope
|
|
||||||
};
|
|
||||||
//# sourceMappingURL=@radix-ui_react-scroll-area.js.map
|
|
||||||
7
app/node_modules/.vite/deps/@radix-ui_react-scroll-area.js.map
generated
vendored
7
app/node_modules/.vite/deps/@radix-ui_react-scroll-area.js.map
generated
vendored
File diff suppressed because one or more lines are too long
1359
app/node_modules/.vite/deps/@radix-ui_react-select.js
generated
vendored
1359
app/node_modules/.vite/deps/@radix-ui_react-select.js
generated
vendored
File diff suppressed because it is too large
Load Diff
7
app/node_modules/.vite/deps/@radix-ui_react-select.js.map
generated
vendored
7
app/node_modules/.vite/deps/@radix-ui_react-select.js.map
generated
vendored
File diff suppressed because one or more lines are too long
76836
app/node_modules/.vite/deps/@react-three_drei.js
generated
vendored
76836
app/node_modules/.vite/deps/@react-three_drei.js
generated
vendored
File diff suppressed because one or more lines are too long
8
app/node_modules/.vite/deps/@react-three_drei.js.map
generated
vendored
8
app/node_modules/.vite/deps/@react-three_drei.js.map
generated
vendored
File diff suppressed because one or more lines are too long
4
app/node_modules/.vite/deps/@react-three_fiber.js
generated
vendored
4
app/node_modules/.vite/deps/@react-three_fiber.js
generated
vendored
@@ -28,9 +28,9 @@ import {
|
|||||||
useLoader,
|
useLoader,
|
||||||
useStore,
|
useStore,
|
||||||
useThree
|
useThree
|
||||||
} from "./chunk-KOYBCGV3.js";
|
} from "./chunk-O5V7GNMB.js";
|
||||||
import "./chunk-L3Z576C2.js";
|
|
||||||
import "./chunk-GUQHL3N7.js";
|
import "./chunk-GUQHL3N7.js";
|
||||||
|
import "./chunk-L3Z576C2.js";
|
||||||
import "./chunk-NJ4V5H3P.js";
|
import "./chunk-NJ4V5H3P.js";
|
||||||
import "./chunk-TXHHHGR3.js";
|
import "./chunk-TXHHHGR3.js";
|
||||||
import "./chunk-2YVA4HRZ.js";
|
import "./chunk-2YVA4HRZ.js";
|
||||||
|
|||||||
272
app/node_modules/.vite/deps/_metadata.json
generated
vendored
272
app/node_modules/.vite/deps/_metadata.json
generated
vendored
@@ -1,151 +1,127 @@
|
|||||||
{
|
{
|
||||||
"hash": "947d6a4d",
|
"hash": "b2c5007d",
|
||||||
"configHash": "dc08391c",
|
"configHash": "d9a82a01",
|
||||||
"lockfileHash": "de25a101",
|
"lockfileHash": "8a04eea8",
|
||||||
"browserHash": "961f5b3a",
|
"browserHash": "5d6343ae",
|
||||||
"optimized": {
|
"optimized": {
|
||||||
"react": {
|
"react": {
|
||||||
"src": "../../react/index.js",
|
"src": "../../react/index.js",
|
||||||
"file": "react.js",
|
"file": "react.js",
|
||||||
"fileHash": "9864076c",
|
"fileHash": "4ea9824e",
|
||||||
"needsInterop": true
|
"needsInterop": true
|
||||||
},
|
},
|
||||||
"react-dom": {
|
"react-dom": {
|
||||||
"src": "../../react-dom/index.js",
|
"src": "../../react-dom/index.js",
|
||||||
"file": "react-dom.js",
|
"file": "react-dom.js",
|
||||||
"fileHash": "2f8641cc",
|
"fileHash": "5b549105",
|
||||||
"needsInterop": true
|
"needsInterop": true
|
||||||
},
|
},
|
||||||
"react/jsx-dev-runtime": {
|
"react/jsx-dev-runtime": {
|
||||||
"src": "../../react/jsx-dev-runtime.js",
|
"src": "../../react/jsx-dev-runtime.js",
|
||||||
"file": "react_jsx-dev-runtime.js",
|
"file": "react_jsx-dev-runtime.js",
|
||||||
"fileHash": "0d6d4c83",
|
"fileHash": "41193e59",
|
||||||
"needsInterop": true
|
"needsInterop": true
|
||||||
},
|
},
|
||||||
"react/jsx-runtime": {
|
"react/jsx-runtime": {
|
||||||
"src": "../../react/jsx-runtime.js",
|
"src": "../../react/jsx-runtime.js",
|
||||||
"file": "react_jsx-runtime.js",
|
"file": "react_jsx-runtime.js",
|
||||||
"fileHash": "a2c255cd",
|
"fileHash": "ad8008f8",
|
||||||
"needsInterop": true
|
"needsInterop": true
|
||||||
},
|
},
|
||||||
"@dnd-kit/core": {
|
|
||||||
"src": "../../@dnd-kit/core/dist/core.esm.js",
|
|
||||||
"file": "@dnd-kit_core.js",
|
|
||||||
"fileHash": "c90d47ee",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"@dnd-kit/utilities": {
|
|
||||||
"src": "../../@dnd-kit/utilities/dist/utilities.esm.js",
|
|
||||||
"file": "@dnd-kit_utilities.js",
|
|
||||||
"fileHash": "434ef86c",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"@radix-ui/react-avatar": {
|
"@radix-ui/react-avatar": {
|
||||||
"src": "../../@radix-ui/react-avatar/dist/index.mjs",
|
"src": "../../@radix-ui/react-avatar/dist/index.mjs",
|
||||||
"file": "@radix-ui_react-avatar.js",
|
"file": "@radix-ui_react-avatar.js",
|
||||||
"fileHash": "2805441c",
|
"fileHash": "e6d8d406",
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"@radix-ui/react-scroll-area": {
|
|
||||||
"src": "../../@radix-ui/react-scroll-area/dist/index.mjs",
|
|
||||||
"file": "@radix-ui_react-scroll-area.js",
|
|
||||||
"fileHash": "6c708018",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"@radix-ui/react-slot": {
|
|
||||||
"src": "../../@radix-ui/react-slot/dist/index.mjs",
|
|
||||||
"file": "@radix-ui_react-slot.js",
|
|
||||||
"fileHash": "5329088f",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"class-variance-authority": {
|
|
||||||
"src": "../../class-variance-authority/dist/index.mjs",
|
|
||||||
"file": "class-variance-authority.js",
|
|
||||||
"fileHash": "ec62709c",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"clsx": {
|
|
||||||
"src": "../../clsx/dist/clsx.mjs",
|
|
||||||
"file": "clsx.js",
|
|
||||||
"fileHash": "5559ec83",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"framer-motion": {
|
|
||||||
"src": "../../framer-motion/dist/es/index.mjs",
|
|
||||||
"file": "framer-motion.js",
|
|
||||||
"fileHash": "2b7c361b",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"lucide-react": {
|
|
||||||
"src": "../../lucide-react/dist/esm/lucide-react.js",
|
|
||||||
"file": "lucide-react.js",
|
|
||||||
"fileHash": "51875cd4",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"react-dom/client": {
|
|
||||||
"src": "../../react-dom/client.js",
|
|
||||||
"file": "react-dom_client.js",
|
|
||||||
"fileHash": "45de9104",
|
|
||||||
"needsInterop": true
|
|
||||||
},
|
|
||||||
"recharts": {
|
|
||||||
"src": "../../recharts/es6/index.js",
|
|
||||||
"file": "recharts.js",
|
|
||||||
"fileHash": "64a83b89",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"tailwind-merge": {
|
|
||||||
"src": "../../tailwind-merge/dist/bundle-mjs.mjs",
|
|
||||||
"file": "tailwind-merge.js",
|
|
||||||
"fileHash": "f691afe8",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"zustand": {
|
|
||||||
"src": "../../zustand/esm/index.mjs",
|
|
||||||
"file": "zustand.js",
|
|
||||||
"fileHash": "fe0603c2",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"zustand/middleware": {
|
|
||||||
"src": "../../zustand/esm/middleware.mjs",
|
|
||||||
"file": "zustand_middleware.js",
|
|
||||||
"fileHash": "66783eb7",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"@radix-ui/react-select": {
|
|
||||||
"src": "../../@radix-ui/react-select/dist/index.mjs",
|
|
||||||
"file": "@radix-ui_react-select.js",
|
|
||||||
"fileHash": "36896aae",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"@react-three/fiber": {
|
|
||||||
"src": "../../@react-three/fiber/dist/react-three-fiber.esm.js",
|
|
||||||
"file": "@react-three_fiber.js",
|
|
||||||
"fileHash": "12c15e20",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"@react-three/drei": {
|
|
||||||
"src": "../../@react-three/drei/index.js",
|
|
||||||
"file": "@react-three_drei.js",
|
|
||||||
"fileHash": "cb36fa64",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"three-stdlib": {
|
|
||||||
"src": "../../three-stdlib/index.js",
|
|
||||||
"file": "three-stdlib.js",
|
|
||||||
"fileHash": "6c880e7a",
|
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"three": {
|
|
||||||
"src": "../../three/build/three.module.js",
|
|
||||||
"file": "three.js",
|
|
||||||
"fileHash": "bcfd9037",
|
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"@radix-ui/react-dropdown-menu": {
|
"@radix-ui/react-dropdown-menu": {
|
||||||
"src": "../../@radix-ui/react-dropdown-menu/dist/index.mjs",
|
"src": "../../@radix-ui/react-dropdown-menu/dist/index.mjs",
|
||||||
"file": "@radix-ui_react-dropdown-menu.js",
|
"file": "@radix-ui_react-dropdown-menu.js",
|
||||||
"fileHash": "44e29247",
|
"fileHash": "26caaf75",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"@radix-ui/react-slot": {
|
||||||
|
"src": "../../@radix-ui/react-slot/dist/index.mjs",
|
||||||
|
"file": "@radix-ui_react-slot.js",
|
||||||
|
"fileHash": "e034d698",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"@react-three/drei": {
|
||||||
|
"src": "../../@react-three/drei/index.js",
|
||||||
|
"file": "@react-three_drei.js",
|
||||||
|
"fileHash": "d81d1332",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"@react-three/fiber": {
|
||||||
|
"src": "../../@react-three/fiber/dist/react-three-fiber.esm.js",
|
||||||
|
"file": "@react-three_fiber.js",
|
||||||
|
"fileHash": "dcc73392",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"class-variance-authority": {
|
||||||
|
"src": "../../class-variance-authority/dist/index.mjs",
|
||||||
|
"file": "class-variance-authority.js",
|
||||||
|
"fileHash": "5fa3c3f8",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"clsx": {
|
||||||
|
"src": "../../clsx/dist/clsx.mjs",
|
||||||
|
"file": "clsx.js",
|
||||||
|
"fileHash": "1428051e",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"framer-motion": {
|
||||||
|
"src": "../../framer-motion/dist/es/index.mjs",
|
||||||
|
"file": "framer-motion.js",
|
||||||
|
"fileHash": "dd6ef86d",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"lucide-react": {
|
||||||
|
"src": "../../lucide-react/dist/esm/lucide-react.js",
|
||||||
|
"file": "lucide-react.js",
|
||||||
|
"fileHash": "df6c668f",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"react-dom/client": {
|
||||||
|
"src": "../../react-dom/client.js",
|
||||||
|
"file": "react-dom_client.js",
|
||||||
|
"fileHash": "23087df0",
|
||||||
|
"needsInterop": true
|
||||||
|
},
|
||||||
|
"react-router-dom": {
|
||||||
|
"src": "../../react-router-dom/dist/index.mjs",
|
||||||
|
"file": "react-router-dom.js",
|
||||||
|
"fileHash": "48fbca8f",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"recharts": {
|
||||||
|
"src": "../../recharts/es6/index.js",
|
||||||
|
"file": "recharts.js",
|
||||||
|
"fileHash": "9faf094d",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"tailwind-merge": {
|
||||||
|
"src": "../../tailwind-merge/dist/bundle-mjs.mjs",
|
||||||
|
"file": "tailwind-merge.js",
|
||||||
|
"fileHash": "fc48e7d8",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"three": {
|
||||||
|
"src": "../../three/build/three.module.js",
|
||||||
|
"file": "three.js",
|
||||||
|
"fileHash": "a0b83871",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"zustand": {
|
||||||
|
"src": "../../zustand/esm/index.mjs",
|
||||||
|
"file": "zustand.js",
|
||||||
|
"fileHash": "ad25a55f",
|
||||||
|
"needsInterop": false
|
||||||
|
},
|
||||||
|
"zustand/middleware": {
|
||||||
|
"src": "../../zustand/esm/middleware.mjs",
|
||||||
|
"file": "zustand_middleware.js",
|
||||||
|
"fileHash": "b84fa2e5",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -156,35 +132,35 @@
|
|||||||
"vision_bundle-CRIBZNUI": {
|
"vision_bundle-CRIBZNUI": {
|
||||||
"file": "vision_bundle-CRIBZNUI.js"
|
"file": "vision_bundle-CRIBZNUI.js"
|
||||||
},
|
},
|
||||||
"chunk-OAEA5FZL": {
|
"chunk-U7P2NEEE": {
|
||||||
"file": "chunk-OAEA5FZL.js"
|
"file": "chunk-U7P2NEEE.js"
|
||||||
},
|
},
|
||||||
"chunk-KOYBCGV3": {
|
"chunk-YWBEB5PG": {
|
||||||
"file": "chunk-KOYBCGV3.js"
|
"file": "chunk-YWBEB5PG.js"
|
||||||
},
|
},
|
||||||
"chunk-TGAC7IAX": {
|
"chunk-23FVUG5N": {
|
||||||
"file": "chunk-TGAC7IAX.js"
|
"file": "chunk-23FVUG5N.js"
|
||||||
},
|
},
|
||||||
"chunk-YJZCGBGU": {
|
"chunk-2VUH7NEY": {
|
||||||
"file": "chunk-YJZCGBGU.js"
|
"file": "chunk-2VUH7NEY.js"
|
||||||
},
|
|
||||||
"chunk-6ZMM2PAV": {
|
|
||||||
"file": "chunk-6ZMM2PAV.js"
|
|
||||||
},
|
|
||||||
"chunk-L3Z576C2": {
|
|
||||||
"file": "chunk-L3Z576C2.js"
|
|
||||||
},
|
},
|
||||||
"chunk-6MXH2QM6": {
|
"chunk-6MXH2QM6": {
|
||||||
"file": "chunk-6MXH2QM6.js"
|
"file": "chunk-6MXH2QM6.js"
|
||||||
},
|
},
|
||||||
|
"chunk-O4L7C4YS": {
|
||||||
|
"file": "chunk-O4L7C4YS.js"
|
||||||
|
},
|
||||||
|
"chunk-OAEA5FZL": {
|
||||||
|
"file": "chunk-OAEA5FZL.js"
|
||||||
|
},
|
||||||
|
"chunk-O5V7GNMB": {
|
||||||
|
"file": "chunk-O5V7GNMB.js"
|
||||||
|
},
|
||||||
"chunk-GUQHL3N7": {
|
"chunk-GUQHL3N7": {
|
||||||
"file": "chunk-GUQHL3N7.js"
|
"file": "chunk-GUQHL3N7.js"
|
||||||
},
|
},
|
||||||
"chunk-EQCCHGRT": {
|
"chunk-L3Z576C2": {
|
||||||
"file": "chunk-EQCCHGRT.js"
|
"file": "chunk-L3Z576C2.js"
|
||||||
},
|
|
||||||
"chunk-O4L7C4YS": {
|
|
||||||
"file": "chunk-O4L7C4YS.js"
|
|
||||||
},
|
},
|
||||||
"chunk-7GZ4CI6Q": {
|
"chunk-7GZ4CI6Q": {
|
||||||
"file": "chunk-7GZ4CI6Q.js"
|
"file": "chunk-7GZ4CI6Q.js"
|
||||||
@@ -192,39 +168,21 @@
|
|||||||
"chunk-NJ4V5H3P": {
|
"chunk-NJ4V5H3P": {
|
||||||
"file": "chunk-NJ4V5H3P.js"
|
"file": "chunk-NJ4V5H3P.js"
|
||||||
},
|
},
|
||||||
"chunk-SMEXDMMQ": {
|
"chunk-EQCCHGRT": {
|
||||||
"file": "chunk-SMEXDMMQ.js"
|
"file": "chunk-EQCCHGRT.js"
|
||||||
},
|
|
||||||
"chunk-M3NXY72O": {
|
|
||||||
"file": "chunk-M3NXY72O.js"
|
|
||||||
},
|
|
||||||
"chunk-VPWBNV4W": {
|
|
||||||
"file": "chunk-VPWBNV4W.js"
|
|
||||||
},
|
},
|
||||||
"chunk-TXHHHGR3": {
|
"chunk-TXHHHGR3": {
|
||||||
"file": "chunk-TXHHHGR3.js"
|
"file": "chunk-TXHHHGR3.js"
|
||||||
},
|
},
|
||||||
"chunk-23FVUG5N": {
|
|
||||||
"file": "chunk-23FVUG5N.js"
|
|
||||||
},
|
|
||||||
"chunk-YF4B4G2L": {
|
"chunk-YF4B4G2L": {
|
||||||
"file": "chunk-YF4B4G2L.js"
|
"file": "chunk-YF4B4G2L.js"
|
||||||
},
|
},
|
||||||
"chunk-YWBEB5PG": {
|
|
||||||
"file": "chunk-YWBEB5PG.js"
|
|
||||||
},
|
|
||||||
"chunk-2VUH7NEY": {
|
|
||||||
"file": "chunk-2VUH7NEY.js"
|
|
||||||
},
|
|
||||||
"chunk-2YVA4HRZ": {
|
"chunk-2YVA4HRZ": {
|
||||||
"file": "chunk-2YVA4HRZ.js"
|
"file": "chunk-2YVA4HRZ.js"
|
||||||
},
|
},
|
||||||
"chunk-WUR7D6NS": {
|
"chunk-WUR7D6NS": {
|
||||||
"file": "chunk-WUR7D6NS.js"
|
"file": "chunk-WUR7D6NS.js"
|
||||||
},
|
},
|
||||||
"chunk-U7P2NEEE": {
|
|
||||||
"file": "chunk-U7P2NEEE.js"
|
|
||||||
},
|
|
||||||
"chunk-G3PMV62Z": {
|
"chunk-G3PMV62Z": {
|
||||||
"file": "chunk-G3PMV62Z.js"
|
"file": "chunk-G3PMV62Z.js"
|
||||||
}
|
}
|
||||||
|
|||||||
9
app/node_modules/.vite/deps/chunk-6ZMM2PAV.js
generated
vendored
9
app/node_modules/.vite/deps/chunk-6ZMM2PAV.js
generated
vendored
@@ -1,9 +0,0 @@
|
|||||||
// node_modules/@radix-ui/number/dist/index.mjs
|
|
||||||
function clamp(value, [min, max]) {
|
|
||||||
return Math.min(max, Math.max(min, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
clamp
|
|
||||||
};
|
|
||||||
//# sourceMappingURL=chunk-6ZMM2PAV.js.map
|
|
||||||
7
app/node_modules/.vite/deps/chunk-6ZMM2PAV.js.map
generated
vendored
7
app/node_modules/.vite/deps/chunk-6ZMM2PAV.js.map
generated
vendored
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"sources": ["../../@radix-ui/number/src/number.ts"],
|
|
||||||
"sourcesContent": ["function clamp(value: number, [min, max]: [number, number]): number {\n return Math.min(max, Math.max(min, value));\n}\n\nexport { clamp };\n"],
|
|
||||||
"mappings": ";AAAA,SAAS,MAAM,OAAe,CAAC,KAAK,GAAG,GAA6B;AAClE,SAAO,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,CAAC;AAC3C;",
|
|
||||||
"names": []
|
|
||||||
}
|
|
||||||
10153
app/node_modules/.vite/deps/chunk-KOYBCGV3.js
generated
vendored
10153
app/node_modules/.vite/deps/chunk-KOYBCGV3.js
generated
vendored
File diff suppressed because one or more lines are too long
7
app/node_modules/.vite/deps/chunk-KOYBCGV3.js.map
generated
vendored
7
app/node_modules/.vite/deps/chunk-KOYBCGV3.js.map
generated
vendored
File diff suppressed because one or more lines are too long
3841
app/node_modules/.vite/deps/chunk-M3NXY72O.js
generated
vendored
3841
app/node_modules/.vite/deps/chunk-M3NXY72O.js
generated
vendored
File diff suppressed because it is too large
Load Diff
7
app/node_modules/.vite/deps/chunk-M3NXY72O.js.map
generated
vendored
7
app/node_modules/.vite/deps/chunk-M3NXY72O.js.map
generated
vendored
File diff suppressed because one or more lines are too long
143
app/node_modules/.vite/deps/chunk-SMEXDMMQ.js
generated
vendored
143
app/node_modules/.vite/deps/chunk-SMEXDMMQ.js
generated
vendored
@@ -1,143 +0,0 @@
|
|||||||
import {
|
|
||||||
useLayoutEffect2
|
|
||||||
} from "./chunk-23FVUG5N.js";
|
|
||||||
import {
|
|
||||||
useComposedRefs
|
|
||||||
} from "./chunk-2VUH7NEY.js";
|
|
||||||
import {
|
|
||||||
require_react
|
|
||||||
} from "./chunk-WUR7D6NS.js";
|
|
||||||
import {
|
|
||||||
__toESM
|
|
||||||
} from "./chunk-G3PMV62Z.js";
|
|
||||||
|
|
||||||
// node_modules/@radix-ui/react-presence/dist/index.mjs
|
|
||||||
var React2 = __toESM(require_react(), 1);
|
|
||||||
var React = __toESM(require_react(), 1);
|
|
||||||
function useStateMachine(initialState, machine) {
|
|
||||||
return React.useReducer((state, event) => {
|
|
||||||
const nextState = machine[state][event];
|
|
||||||
return nextState ?? state;
|
|
||||||
}, initialState);
|
|
||||||
}
|
|
||||||
var Presence = (props) => {
|
|
||||||
const { present, children } = props;
|
|
||||||
const presence = usePresence(present);
|
|
||||||
const child = typeof children === "function" ? children({ present: presence.isPresent }) : React2.Children.only(children);
|
|
||||||
const ref = useComposedRefs(presence.ref, getElementRef(child));
|
|
||||||
const forceMount = typeof children === "function";
|
|
||||||
return forceMount || presence.isPresent ? React2.cloneElement(child, { ref }) : null;
|
|
||||||
};
|
|
||||||
Presence.displayName = "Presence";
|
|
||||||
function usePresence(present) {
|
|
||||||
const [node, setNode] = React2.useState();
|
|
||||||
const stylesRef = React2.useRef(null);
|
|
||||||
const prevPresentRef = React2.useRef(present);
|
|
||||||
const prevAnimationNameRef = React2.useRef("none");
|
|
||||||
const initialState = present ? "mounted" : "unmounted";
|
|
||||||
const [state, send] = useStateMachine(initialState, {
|
|
||||||
mounted: {
|
|
||||||
UNMOUNT: "unmounted",
|
|
||||||
ANIMATION_OUT: "unmountSuspended"
|
|
||||||
},
|
|
||||||
unmountSuspended: {
|
|
||||||
MOUNT: "mounted",
|
|
||||||
ANIMATION_END: "unmounted"
|
|
||||||
},
|
|
||||||
unmounted: {
|
|
||||||
MOUNT: "mounted"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
React2.useEffect(() => {
|
|
||||||
const currentAnimationName = getAnimationName(stylesRef.current);
|
|
||||||
prevAnimationNameRef.current = state === "mounted" ? currentAnimationName : "none";
|
|
||||||
}, [state]);
|
|
||||||
useLayoutEffect2(() => {
|
|
||||||
const styles = stylesRef.current;
|
|
||||||
const wasPresent = prevPresentRef.current;
|
|
||||||
const hasPresentChanged = wasPresent !== present;
|
|
||||||
if (hasPresentChanged) {
|
|
||||||
const prevAnimationName = prevAnimationNameRef.current;
|
|
||||||
const currentAnimationName = getAnimationName(styles);
|
|
||||||
if (present) {
|
|
||||||
send("MOUNT");
|
|
||||||
} else if (currentAnimationName === "none" || styles?.display === "none") {
|
|
||||||
send("UNMOUNT");
|
|
||||||
} else {
|
|
||||||
const isAnimating = prevAnimationName !== currentAnimationName;
|
|
||||||
if (wasPresent && isAnimating) {
|
|
||||||
send("ANIMATION_OUT");
|
|
||||||
} else {
|
|
||||||
send("UNMOUNT");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
prevPresentRef.current = present;
|
|
||||||
}
|
|
||||||
}, [present, send]);
|
|
||||||
useLayoutEffect2(() => {
|
|
||||||
if (node) {
|
|
||||||
let timeoutId;
|
|
||||||
const ownerWindow = node.ownerDocument.defaultView ?? window;
|
|
||||||
const handleAnimationEnd = (event) => {
|
|
||||||
const currentAnimationName = getAnimationName(stylesRef.current);
|
|
||||||
const isCurrentAnimation = currentAnimationName.includes(CSS.escape(event.animationName));
|
|
||||||
if (event.target === node && isCurrentAnimation) {
|
|
||||||
send("ANIMATION_END");
|
|
||||||
if (!prevPresentRef.current) {
|
|
||||||
const currentFillMode = node.style.animationFillMode;
|
|
||||||
node.style.animationFillMode = "forwards";
|
|
||||||
timeoutId = ownerWindow.setTimeout(() => {
|
|
||||||
if (node.style.animationFillMode === "forwards") {
|
|
||||||
node.style.animationFillMode = currentFillMode;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const handleAnimationStart = (event) => {
|
|
||||||
if (event.target === node) {
|
|
||||||
prevAnimationNameRef.current = getAnimationName(stylesRef.current);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
node.addEventListener("animationstart", handleAnimationStart);
|
|
||||||
node.addEventListener("animationcancel", handleAnimationEnd);
|
|
||||||
node.addEventListener("animationend", handleAnimationEnd);
|
|
||||||
return () => {
|
|
||||||
ownerWindow.clearTimeout(timeoutId);
|
|
||||||
node.removeEventListener("animationstart", handleAnimationStart);
|
|
||||||
node.removeEventListener("animationcancel", handleAnimationEnd);
|
|
||||||
node.removeEventListener("animationend", handleAnimationEnd);
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
send("ANIMATION_END");
|
|
||||||
}
|
|
||||||
}, [node, send]);
|
|
||||||
return {
|
|
||||||
isPresent: ["mounted", "unmountSuspended"].includes(state),
|
|
||||||
ref: React2.useCallback((node2) => {
|
|
||||||
stylesRef.current = node2 ? getComputedStyle(node2) : null;
|
|
||||||
setNode(node2);
|
|
||||||
}, [])
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function getAnimationName(styles) {
|
|
||||||
return styles?.animationName || "none";
|
|
||||||
}
|
|
||||||
function getElementRef(element) {
|
|
||||||
let getter = Object.getOwnPropertyDescriptor(element.props, "ref")?.get;
|
|
||||||
let mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
|
|
||||||
if (mayWarn) {
|
|
||||||
return element.ref;
|
|
||||||
}
|
|
||||||
getter = Object.getOwnPropertyDescriptor(element, "ref")?.get;
|
|
||||||
mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
|
|
||||||
if (mayWarn) {
|
|
||||||
return element.props.ref;
|
|
||||||
}
|
|
||||||
return element.props.ref || element.ref;
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
Presence
|
|
||||||
};
|
|
||||||
//# sourceMappingURL=chunk-SMEXDMMQ.js.map
|
|
||||||
7
app/node_modules/.vite/deps/chunk-SMEXDMMQ.js.map
generated
vendored
7
app/node_modules/.vite/deps/chunk-SMEXDMMQ.js.map
generated
vendored
File diff suppressed because one or more lines are too long
158486
app/node_modules/.vite/deps/chunk-TGAC7IAX.js
generated
vendored
158486
app/node_modules/.vite/deps/chunk-TGAC7IAX.js
generated
vendored
File diff suppressed because one or more lines are too long
7
app/node_modules/.vite/deps/chunk-TGAC7IAX.js.map
generated
vendored
7
app/node_modules/.vite/deps/chunk-TGAC7IAX.js.map
generated
vendored
File diff suppressed because one or more lines are too long
241
app/node_modules/.vite/deps/chunk-VPWBNV4W.js
generated
vendored
241
app/node_modules/.vite/deps/chunk-VPWBNV4W.js
generated
vendored
@@ -1,241 +0,0 @@
|
|||||||
import {
|
|
||||||
require_react_dom
|
|
||||||
} from "./chunk-YF4B4G2L.js";
|
|
||||||
import {
|
|
||||||
composeRefs
|
|
||||||
} from "./chunk-2VUH7NEY.js";
|
|
||||||
import {
|
|
||||||
require_jsx_runtime
|
|
||||||
} from "./chunk-2YVA4HRZ.js";
|
|
||||||
import {
|
|
||||||
require_react
|
|
||||||
} from "./chunk-WUR7D6NS.js";
|
|
||||||
import {
|
|
||||||
__toESM
|
|
||||||
} from "./chunk-G3PMV62Z.js";
|
|
||||||
|
|
||||||
// node_modules/@radix-ui/primitive/dist/index.mjs
|
|
||||||
var canUseDOM = !!(typeof window !== "undefined" && window.document && window.document.createElement);
|
|
||||||
function composeEventHandlers(originalEventHandler, ourEventHandler, { checkForDefaultPrevented = true } = {}) {
|
|
||||||
return function handleEvent(event) {
|
|
||||||
originalEventHandler?.(event);
|
|
||||||
if (checkForDefaultPrevented === false || !event.defaultPrevented) {
|
|
||||||
return ourEventHandler?.(event);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// node_modules/@radix-ui/react-context/dist/index.mjs
|
|
||||||
var React = __toESM(require_react(), 1);
|
|
||||||
var import_jsx_runtime = __toESM(require_jsx_runtime(), 1);
|
|
||||||
function createContextScope(scopeName, createContextScopeDeps = []) {
|
|
||||||
let defaultContexts = [];
|
|
||||||
function createContext3(rootComponentName, defaultContext) {
|
|
||||||
const BaseContext = React.createContext(defaultContext);
|
|
||||||
const index = defaultContexts.length;
|
|
||||||
defaultContexts = [...defaultContexts, defaultContext];
|
|
||||||
const Provider = (props) => {
|
|
||||||
const { scope, children, ...context } = props;
|
|
||||||
const Context = scope?.[scopeName]?.[index] || BaseContext;
|
|
||||||
const value = React.useMemo(() => context, Object.values(context));
|
|
||||||
return (0, import_jsx_runtime.jsx)(Context.Provider, { value, children });
|
|
||||||
};
|
|
||||||
Provider.displayName = rootComponentName + "Provider";
|
|
||||||
function useContext22(consumerName, scope) {
|
|
||||||
const Context = scope?.[scopeName]?.[index] || BaseContext;
|
|
||||||
const context = React.useContext(Context);
|
|
||||||
if (context) return context;
|
|
||||||
if (defaultContext !== void 0) return defaultContext;
|
|
||||||
throw new Error(`\`${consumerName}\` must be used within \`${rootComponentName}\``);
|
|
||||||
}
|
|
||||||
return [Provider, useContext22];
|
|
||||||
}
|
|
||||||
const createScope = () => {
|
|
||||||
const scopeContexts = defaultContexts.map((defaultContext) => {
|
|
||||||
return React.createContext(defaultContext);
|
|
||||||
});
|
|
||||||
return function useScope(scope) {
|
|
||||||
const contexts = scope?.[scopeName] || scopeContexts;
|
|
||||||
return React.useMemo(
|
|
||||||
() => ({ [`__scope${scopeName}`]: { ...scope, [scopeName]: contexts } }),
|
|
||||||
[scope, contexts]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
createScope.scopeName = scopeName;
|
|
||||||
return [createContext3, composeContextScopes(createScope, ...createContextScopeDeps)];
|
|
||||||
}
|
|
||||||
function composeContextScopes(...scopes) {
|
|
||||||
const baseScope = scopes[0];
|
|
||||||
if (scopes.length === 1) return baseScope;
|
|
||||||
const createScope = () => {
|
|
||||||
const scopeHooks = scopes.map((createScope2) => ({
|
|
||||||
useScope: createScope2(),
|
|
||||||
scopeName: createScope2.scopeName
|
|
||||||
}));
|
|
||||||
return function useComposedScopes(overrideScopes) {
|
|
||||||
const nextScopes = scopeHooks.reduce((nextScopes2, { useScope, scopeName }) => {
|
|
||||||
const scopeProps = useScope(overrideScopes);
|
|
||||||
const currentScope = scopeProps[`__scope${scopeName}`];
|
|
||||||
return { ...nextScopes2, ...currentScope };
|
|
||||||
}, {});
|
|
||||||
return React.useMemo(() => ({ [`__scope${baseScope.scopeName}`]: nextScopes }), [nextScopes]);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
createScope.scopeName = baseScope.scopeName;
|
|
||||||
return createScope;
|
|
||||||
}
|
|
||||||
|
|
||||||
// node_modules/@radix-ui/react-primitive/dist/index.mjs
|
|
||||||
var React3 = __toESM(require_react(), 1);
|
|
||||||
var ReactDOM = __toESM(require_react_dom(), 1);
|
|
||||||
|
|
||||||
// node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot/dist/index.mjs
|
|
||||||
var React2 = __toESM(require_react(), 1);
|
|
||||||
var import_jsx_runtime2 = __toESM(require_jsx_runtime(), 1);
|
|
||||||
function createSlot(ownerName) {
|
|
||||||
const SlotClone = createSlotClone(ownerName);
|
|
||||||
const Slot2 = React2.forwardRef((props, forwardedRef) => {
|
|
||||||
const { children, ...slotProps } = props;
|
|
||||||
const childrenArray = React2.Children.toArray(children);
|
|
||||||
const slottable = childrenArray.find(isSlottable);
|
|
||||||
if (slottable) {
|
|
||||||
const newElement = slottable.props.children;
|
|
||||||
const newChildren = childrenArray.map((child) => {
|
|
||||||
if (child === slottable) {
|
|
||||||
if (React2.Children.count(newElement) > 1) return React2.Children.only(null);
|
|
||||||
return React2.isValidElement(newElement) ? newElement.props.children : null;
|
|
||||||
} else {
|
|
||||||
return child;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return (0, import_jsx_runtime2.jsx)(SlotClone, { ...slotProps, ref: forwardedRef, children: React2.isValidElement(newElement) ? React2.cloneElement(newElement, void 0, newChildren) : null });
|
|
||||||
}
|
|
||||||
return (0, import_jsx_runtime2.jsx)(SlotClone, { ...slotProps, ref: forwardedRef, children });
|
|
||||||
});
|
|
||||||
Slot2.displayName = `${ownerName}.Slot`;
|
|
||||||
return Slot2;
|
|
||||||
}
|
|
||||||
var Slot = createSlot("Slot");
|
|
||||||
function createSlotClone(ownerName) {
|
|
||||||
const SlotClone = React2.forwardRef((props, forwardedRef) => {
|
|
||||||
const { children, ...slotProps } = props;
|
|
||||||
if (React2.isValidElement(children)) {
|
|
||||||
const childrenRef = getElementRef(children);
|
|
||||||
const props2 = mergeProps(slotProps, children.props);
|
|
||||||
if (children.type !== React2.Fragment) {
|
|
||||||
props2.ref = forwardedRef ? composeRefs(forwardedRef, childrenRef) : childrenRef;
|
|
||||||
}
|
|
||||||
return React2.cloneElement(children, props2);
|
|
||||||
}
|
|
||||||
return React2.Children.count(children) > 1 ? React2.Children.only(null) : null;
|
|
||||||
});
|
|
||||||
SlotClone.displayName = `${ownerName}.SlotClone`;
|
|
||||||
return SlotClone;
|
|
||||||
}
|
|
||||||
var SLOTTABLE_IDENTIFIER = /* @__PURE__ */ Symbol("radix.slottable");
|
|
||||||
function createSlottable(ownerName) {
|
|
||||||
const Slottable2 = ({ children }) => {
|
|
||||||
return (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
|
|
||||||
};
|
|
||||||
Slottable2.displayName = `${ownerName}.Slottable`;
|
|
||||||
Slottable2.__radixId = SLOTTABLE_IDENTIFIER;
|
|
||||||
return Slottable2;
|
|
||||||
}
|
|
||||||
var Slottable = createSlottable("Slottable");
|
|
||||||
function isSlottable(child) {
|
|
||||||
return React2.isValidElement(child) && typeof child.type === "function" && "__radixId" in child.type && child.type.__radixId === SLOTTABLE_IDENTIFIER;
|
|
||||||
}
|
|
||||||
function mergeProps(slotProps, childProps) {
|
|
||||||
const overrideProps = { ...childProps };
|
|
||||||
for (const propName in childProps) {
|
|
||||||
const slotPropValue = slotProps[propName];
|
|
||||||
const childPropValue = childProps[propName];
|
|
||||||
const isHandler = /^on[A-Z]/.test(propName);
|
|
||||||
if (isHandler) {
|
|
||||||
if (slotPropValue && childPropValue) {
|
|
||||||
overrideProps[propName] = (...args) => {
|
|
||||||
const result = childPropValue(...args);
|
|
||||||
slotPropValue(...args);
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
} else if (slotPropValue) {
|
|
||||||
overrideProps[propName] = slotPropValue;
|
|
||||||
}
|
|
||||||
} else if (propName === "style") {
|
|
||||||
overrideProps[propName] = { ...slotPropValue, ...childPropValue };
|
|
||||||
} else if (propName === "className") {
|
|
||||||
overrideProps[propName] = [slotPropValue, childPropValue].filter(Boolean).join(" ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { ...slotProps, ...overrideProps };
|
|
||||||
}
|
|
||||||
function getElementRef(element) {
|
|
||||||
let getter = Object.getOwnPropertyDescriptor(element.props, "ref")?.get;
|
|
||||||
let mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
|
|
||||||
if (mayWarn) {
|
|
||||||
return element.ref;
|
|
||||||
}
|
|
||||||
getter = Object.getOwnPropertyDescriptor(element, "ref")?.get;
|
|
||||||
mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
|
|
||||||
if (mayWarn) {
|
|
||||||
return element.props.ref;
|
|
||||||
}
|
|
||||||
return element.props.ref || element.ref;
|
|
||||||
}
|
|
||||||
|
|
||||||
// node_modules/@radix-ui/react-primitive/dist/index.mjs
|
|
||||||
var import_jsx_runtime3 = __toESM(require_jsx_runtime(), 1);
|
|
||||||
var NODES = [
|
|
||||||
"a",
|
|
||||||
"button",
|
|
||||||
"div",
|
|
||||||
"form",
|
|
||||||
"h2",
|
|
||||||
"h3",
|
|
||||||
"img",
|
|
||||||
"input",
|
|
||||||
"label",
|
|
||||||
"li",
|
|
||||||
"nav",
|
|
||||||
"ol",
|
|
||||||
"p",
|
|
||||||
"select",
|
|
||||||
"span",
|
|
||||||
"svg",
|
|
||||||
"ul"
|
|
||||||
];
|
|
||||||
var Primitive = NODES.reduce((primitive, node) => {
|
|
||||||
const Slot2 = createSlot(`Primitive.${node}`);
|
|
||||||
const Node = React3.forwardRef((props, forwardedRef) => {
|
|
||||||
const { asChild, ...primitiveProps } = props;
|
|
||||||
const Comp = asChild ? Slot2 : node;
|
|
||||||
if (typeof window !== "undefined") {
|
|
||||||
window[/* @__PURE__ */ Symbol.for("radix-ui")] = true;
|
|
||||||
}
|
|
||||||
return (0, import_jsx_runtime3.jsx)(Comp, { ...primitiveProps, ref: forwardedRef });
|
|
||||||
});
|
|
||||||
Node.displayName = `Primitive.${node}`;
|
|
||||||
return { ...primitive, [node]: Node };
|
|
||||||
}, {});
|
|
||||||
function dispatchDiscreteCustomEvent(target, event) {
|
|
||||||
if (target) ReactDOM.flushSync(() => target.dispatchEvent(event));
|
|
||||||
}
|
|
||||||
|
|
||||||
// node_modules/@radix-ui/react-direction/dist/index.mjs
|
|
||||||
var React4 = __toESM(require_react(), 1);
|
|
||||||
var import_jsx_runtime4 = __toESM(require_jsx_runtime(), 1);
|
|
||||||
var DirectionContext = React4.createContext(void 0);
|
|
||||||
function useDirection(localDir) {
|
|
||||||
const globalDir = React4.useContext(DirectionContext);
|
|
||||||
return localDir || globalDir || "ltr";
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
composeEventHandlers,
|
|
||||||
createContextScope,
|
|
||||||
Primitive,
|
|
||||||
dispatchDiscreteCustomEvent,
|
|
||||||
useDirection
|
|
||||||
};
|
|
||||||
//# sourceMappingURL=chunk-VPWBNV4W.js.map
|
|
||||||
7
app/node_modules/.vite/deps/chunk-VPWBNV4W.js.map
generated
vendored
7
app/node_modules/.vite/deps/chunk-VPWBNV4W.js.map
generated
vendored
File diff suppressed because one or more lines are too long
311
app/node_modules/.vite/deps/chunk-YJZCGBGU.js
generated
vendored
311
app/node_modules/.vite/deps/chunk-YJZCGBGU.js
generated
vendored
@@ -1,311 +0,0 @@
|
|||||||
import {
|
|
||||||
require_react
|
|
||||||
} from "./chunk-WUR7D6NS.js";
|
|
||||||
import {
|
|
||||||
__toESM
|
|
||||||
} from "./chunk-G3PMV62Z.js";
|
|
||||||
|
|
||||||
// node_modules/@dnd-kit/utilities/dist/utilities.esm.js
|
|
||||||
var import_react = __toESM(require_react());
|
|
||||||
function useCombinedRefs() {
|
|
||||||
for (var _len = arguments.length, refs = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
||||||
refs[_key] = arguments[_key];
|
|
||||||
}
|
|
||||||
return (0, import_react.useMemo)(
|
|
||||||
() => (node) => {
|
|
||||||
refs.forEach((ref) => ref(node));
|
|
||||||
},
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
refs
|
|
||||||
);
|
|
||||||
}
|
|
||||||
var canUseDOM = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined";
|
|
||||||
function isWindow(element) {
|
|
||||||
const elementString = Object.prototype.toString.call(element);
|
|
||||||
return elementString === "[object Window]" || // In Electron context the Window object serializes to [object global]
|
|
||||||
elementString === "[object global]";
|
|
||||||
}
|
|
||||||
function isNode(node) {
|
|
||||||
return "nodeType" in node;
|
|
||||||
}
|
|
||||||
function getWindow(target) {
|
|
||||||
var _target$ownerDocument, _target$ownerDocument2;
|
|
||||||
if (!target) {
|
|
||||||
return window;
|
|
||||||
}
|
|
||||||
if (isWindow(target)) {
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
if (!isNode(target)) {
|
|
||||||
return window;
|
|
||||||
}
|
|
||||||
return (_target$ownerDocument = (_target$ownerDocument2 = target.ownerDocument) == null ? void 0 : _target$ownerDocument2.defaultView) != null ? _target$ownerDocument : window;
|
|
||||||
}
|
|
||||||
function isDocument(node) {
|
|
||||||
const {
|
|
||||||
Document
|
|
||||||
} = getWindow(node);
|
|
||||||
return node instanceof Document;
|
|
||||||
}
|
|
||||||
function isHTMLElement(node) {
|
|
||||||
if (isWindow(node)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return node instanceof getWindow(node).HTMLElement;
|
|
||||||
}
|
|
||||||
function isSVGElement(node) {
|
|
||||||
return node instanceof getWindow(node).SVGElement;
|
|
||||||
}
|
|
||||||
function getOwnerDocument(target) {
|
|
||||||
if (!target) {
|
|
||||||
return document;
|
|
||||||
}
|
|
||||||
if (isWindow(target)) {
|
|
||||||
return target.document;
|
|
||||||
}
|
|
||||||
if (!isNode(target)) {
|
|
||||||
return document;
|
|
||||||
}
|
|
||||||
if (isDocument(target)) {
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
if (isHTMLElement(target) || isSVGElement(target)) {
|
|
||||||
return target.ownerDocument;
|
|
||||||
}
|
|
||||||
return document;
|
|
||||||
}
|
|
||||||
var useIsomorphicLayoutEffect = canUseDOM ? import_react.useLayoutEffect : import_react.useEffect;
|
|
||||||
function useEvent(handler) {
|
|
||||||
const handlerRef = (0, import_react.useRef)(handler);
|
|
||||||
useIsomorphicLayoutEffect(() => {
|
|
||||||
handlerRef.current = handler;
|
|
||||||
});
|
|
||||||
return (0, import_react.useCallback)(function() {
|
|
||||||
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
||||||
args[_key] = arguments[_key];
|
|
||||||
}
|
|
||||||
return handlerRef.current == null ? void 0 : handlerRef.current(...args);
|
|
||||||
}, []);
|
|
||||||
}
|
|
||||||
function useInterval() {
|
|
||||||
const intervalRef = (0, import_react.useRef)(null);
|
|
||||||
const set = (0, import_react.useCallback)((listener, duration) => {
|
|
||||||
intervalRef.current = setInterval(listener, duration);
|
|
||||||
}, []);
|
|
||||||
const clear = (0, import_react.useCallback)(() => {
|
|
||||||
if (intervalRef.current !== null) {
|
|
||||||
clearInterval(intervalRef.current);
|
|
||||||
intervalRef.current = null;
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
return [set, clear];
|
|
||||||
}
|
|
||||||
function useLatestValue(value, dependencies) {
|
|
||||||
if (dependencies === void 0) {
|
|
||||||
dependencies = [value];
|
|
||||||
}
|
|
||||||
const valueRef = (0, import_react.useRef)(value);
|
|
||||||
useIsomorphicLayoutEffect(() => {
|
|
||||||
if (valueRef.current !== value) {
|
|
||||||
valueRef.current = value;
|
|
||||||
}
|
|
||||||
}, dependencies);
|
|
||||||
return valueRef;
|
|
||||||
}
|
|
||||||
function useLazyMemo(callback, dependencies) {
|
|
||||||
const valueRef = (0, import_react.useRef)();
|
|
||||||
return (0, import_react.useMemo)(
|
|
||||||
() => {
|
|
||||||
const newValue = callback(valueRef.current);
|
|
||||||
valueRef.current = newValue;
|
|
||||||
return newValue;
|
|
||||||
},
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
[...dependencies]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
function useNodeRef(onChange) {
|
|
||||||
const onChangeHandler = useEvent(onChange);
|
|
||||||
const node = (0, import_react.useRef)(null);
|
|
||||||
const setNodeRef = (0, import_react.useCallback)(
|
|
||||||
(element) => {
|
|
||||||
if (element !== node.current) {
|
|
||||||
onChangeHandler == null ? void 0 : onChangeHandler(element, node.current);
|
|
||||||
}
|
|
||||||
node.current = element;
|
|
||||||
},
|
|
||||||
//eslint-disable-next-line
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
return [node, setNodeRef];
|
|
||||||
}
|
|
||||||
function usePrevious(value) {
|
|
||||||
const ref = (0, import_react.useRef)();
|
|
||||||
(0, import_react.useEffect)(() => {
|
|
||||||
ref.current = value;
|
|
||||||
}, [value]);
|
|
||||||
return ref.current;
|
|
||||||
}
|
|
||||||
var ids = {};
|
|
||||||
function useUniqueId(prefix, value) {
|
|
||||||
return (0, import_react.useMemo)(() => {
|
|
||||||
if (value) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
const id = ids[prefix] == null ? 0 : ids[prefix] + 1;
|
|
||||||
ids[prefix] = id;
|
|
||||||
return prefix + "-" + id;
|
|
||||||
}, [prefix, value]);
|
|
||||||
}
|
|
||||||
function createAdjustmentFn(modifier) {
|
|
||||||
return function(object) {
|
|
||||||
for (var _len = arguments.length, adjustments = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
|
||||||
adjustments[_key - 1] = arguments[_key];
|
|
||||||
}
|
|
||||||
return adjustments.reduce((accumulator, adjustment) => {
|
|
||||||
const entries = Object.entries(adjustment);
|
|
||||||
for (const [key, valueAdjustment] of entries) {
|
|
||||||
const value = accumulator[key];
|
|
||||||
if (value != null) {
|
|
||||||
accumulator[key] = value + modifier * valueAdjustment;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return accumulator;
|
|
||||||
}, {
|
|
||||||
...object
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
var add = createAdjustmentFn(1);
|
|
||||||
var subtract = createAdjustmentFn(-1);
|
|
||||||
function hasViewportRelativeCoordinates(event) {
|
|
||||||
return "clientX" in event && "clientY" in event;
|
|
||||||
}
|
|
||||||
function isKeyboardEvent(event) {
|
|
||||||
if (!event) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const {
|
|
||||||
KeyboardEvent
|
|
||||||
} = getWindow(event.target);
|
|
||||||
return KeyboardEvent && event instanceof KeyboardEvent;
|
|
||||||
}
|
|
||||||
function isTouchEvent(event) {
|
|
||||||
if (!event) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const {
|
|
||||||
TouchEvent
|
|
||||||
} = getWindow(event.target);
|
|
||||||
return TouchEvent && event instanceof TouchEvent;
|
|
||||||
}
|
|
||||||
function getEventCoordinates(event) {
|
|
||||||
if (isTouchEvent(event)) {
|
|
||||||
if (event.touches && event.touches.length) {
|
|
||||||
const {
|
|
||||||
clientX: x,
|
|
||||||
clientY: y
|
|
||||||
} = event.touches[0];
|
|
||||||
return {
|
|
||||||
x,
|
|
||||||
y
|
|
||||||
};
|
|
||||||
} else if (event.changedTouches && event.changedTouches.length) {
|
|
||||||
const {
|
|
||||||
clientX: x,
|
|
||||||
clientY: y
|
|
||||||
} = event.changedTouches[0];
|
|
||||||
return {
|
|
||||||
x,
|
|
||||||
y
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (hasViewportRelativeCoordinates(event)) {
|
|
||||||
return {
|
|
||||||
x: event.clientX,
|
|
||||||
y: event.clientY
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
var CSS = Object.freeze({
|
|
||||||
Translate: {
|
|
||||||
toString(transform) {
|
|
||||||
if (!transform) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const {
|
|
||||||
x,
|
|
||||||
y
|
|
||||||
} = transform;
|
|
||||||
return "translate3d(" + (x ? Math.round(x) : 0) + "px, " + (y ? Math.round(y) : 0) + "px, 0)";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Scale: {
|
|
||||||
toString(transform) {
|
|
||||||
if (!transform) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const {
|
|
||||||
scaleX,
|
|
||||||
scaleY
|
|
||||||
} = transform;
|
|
||||||
return "scaleX(" + scaleX + ") scaleY(" + scaleY + ")";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Transform: {
|
|
||||||
toString(transform) {
|
|
||||||
if (!transform) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return [CSS.Translate.toString(transform), CSS.Scale.toString(transform)].join(" ");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Transition: {
|
|
||||||
toString(_ref) {
|
|
||||||
let {
|
|
||||||
property,
|
|
||||||
duration,
|
|
||||||
easing
|
|
||||||
} = _ref;
|
|
||||||
return property + " " + duration + "ms " + easing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
var SELECTOR = "a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]";
|
|
||||||
function findFirstFocusableNode(element) {
|
|
||||||
if (element.matches(SELECTOR)) {
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
return element.querySelector(SELECTOR);
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
useCombinedRefs,
|
|
||||||
canUseDOM,
|
|
||||||
isWindow,
|
|
||||||
isNode,
|
|
||||||
getWindow,
|
|
||||||
isDocument,
|
|
||||||
isHTMLElement,
|
|
||||||
isSVGElement,
|
|
||||||
getOwnerDocument,
|
|
||||||
useIsomorphicLayoutEffect,
|
|
||||||
useEvent,
|
|
||||||
useInterval,
|
|
||||||
useLatestValue,
|
|
||||||
useLazyMemo,
|
|
||||||
useNodeRef,
|
|
||||||
usePrevious,
|
|
||||||
useUniqueId,
|
|
||||||
add,
|
|
||||||
subtract,
|
|
||||||
hasViewportRelativeCoordinates,
|
|
||||||
isKeyboardEvent,
|
|
||||||
isTouchEvent,
|
|
||||||
getEventCoordinates,
|
|
||||||
CSS,
|
|
||||||
findFirstFocusableNode
|
|
||||||
};
|
|
||||||
//# sourceMappingURL=chunk-YJZCGBGU.js.map
|
|
||||||
7
app/node_modules/.vite/deps/chunk-YJZCGBGU.js.map
generated
vendored
7
app/node_modules/.vite/deps/chunk-YJZCGBGU.js.map
generated
vendored
File diff suppressed because one or more lines are too long
6
app/node_modules/.vite/deps/recharts.js
generated
vendored
6
app/node_modules/.vite/deps/recharts.js
generated
vendored
@@ -1,3 +1,6 @@
|
|||||||
|
import {
|
||||||
|
clsx_default
|
||||||
|
} from "./chunk-U7P2NEEE.js";
|
||||||
import {
|
import {
|
||||||
_extends
|
_extends
|
||||||
} from "./chunk-EQCCHGRT.js";
|
} from "./chunk-EQCCHGRT.js";
|
||||||
@@ -7,9 +10,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
require_react
|
require_react
|
||||||
} from "./chunk-WUR7D6NS.js";
|
} from "./chunk-WUR7D6NS.js";
|
||||||
import {
|
|
||||||
clsx_default
|
|
||||||
} from "./chunk-U7P2NEEE.js";
|
|
||||||
import {
|
import {
|
||||||
__commonJS,
|
__commonJS,
|
||||||
__export,
|
__export,
|
||||||
|
|||||||
711
app/node_modules/.vite/deps/three-stdlib.js
generated
vendored
711
app/node_modules/.vite/deps/three-stdlib.js
generated
vendored
@@ -1,711 +0,0 @@
|
|||||||
import {
|
|
||||||
ACESFilmicToneMappingShader,
|
|
||||||
AMFLoader,
|
|
||||||
ARButton,
|
|
||||||
AdaptiveToneMappingPass,
|
|
||||||
AfterimagePass,
|
|
||||||
AfterimageShader,
|
|
||||||
AmmoPhysics,
|
|
||||||
AnaglyphEffect,
|
|
||||||
AnimationClipCreator,
|
|
||||||
ArcballControls,
|
|
||||||
AsciiEffect,
|
|
||||||
AssimpLoader,
|
|
||||||
BVHLoader,
|
|
||||||
BasicShader,
|
|
||||||
BasisTextureLoader,
|
|
||||||
BatchedMesh,
|
|
||||||
BleachBypassShader,
|
|
||||||
BlendShader,
|
|
||||||
BloomPass,
|
|
||||||
BlurShaderUtils,
|
|
||||||
BokehDepthShader,
|
|
||||||
BokehPass,
|
|
||||||
BokehShader,
|
|
||||||
BokehShader2,
|
|
||||||
BoxLineGeometry,
|
|
||||||
BrightnessContrastShader,
|
|
||||||
CCDIKHelper,
|
|
||||||
CCDIKSolver,
|
|
||||||
CSM,
|
|
||||||
CSMFrustum,
|
|
||||||
CSMHelper,
|
|
||||||
CSMShader,
|
|
||||||
CSS2DObject,
|
|
||||||
CSS2DRenderer,
|
|
||||||
CSS3DObject,
|
|
||||||
CSS3DRenderer,
|
|
||||||
CSS3DSprite,
|
|
||||||
CameraControls,
|
|
||||||
Capsule,
|
|
||||||
CinematicCamera,
|
|
||||||
CinquefoilKnot,
|
|
||||||
ClearMaskPass,
|
|
||||||
ClearPass,
|
|
||||||
ColladaExporter,
|
|
||||||
ColladaLoader,
|
|
||||||
ColorConverter,
|
|
||||||
ColorCorrectionShader,
|
|
||||||
ColorMapKeywords,
|
|
||||||
ColorifyShader,
|
|
||||||
ConvexGeometry,
|
|
||||||
ConvexHull,
|
|
||||||
ConvexObjectBreaker,
|
|
||||||
ConvolutionShader,
|
|
||||||
CopyShader,
|
|
||||||
CubeTexturePass,
|
|
||||||
DDSLoader,
|
|
||||||
DOFMipMapShader,
|
|
||||||
DRACOExporter,
|
|
||||||
DRACOLoader,
|
|
||||||
DecalGeometry,
|
|
||||||
DecalVertex,
|
|
||||||
DecoratedTorusKnot4a,
|
|
||||||
DecoratedTorusKnot4b,
|
|
||||||
DecoratedTorusKnot5a,
|
|
||||||
DecoratedTorusKnot5c,
|
|
||||||
DepthLimitedBlurShader,
|
|
||||||
DeviceOrientationControls,
|
|
||||||
DigitalGlitch,
|
|
||||||
DotScreenPass,
|
|
||||||
DotScreenShader,
|
|
||||||
DragControls,
|
|
||||||
EXRLoader,
|
|
||||||
EdgeSplitModifier,
|
|
||||||
EffectComposer,
|
|
||||||
FBXLoader,
|
|
||||||
FXAAShader,
|
|
||||||
Face,
|
|
||||||
Face3,
|
|
||||||
FigureEightPolynomialKnot,
|
|
||||||
FilmPass,
|
|
||||||
FilmShader,
|
|
||||||
FirstPersonControls,
|
|
||||||
FlakesTexture,
|
|
||||||
Flow,
|
|
||||||
FlyControls,
|
|
||||||
FocusShader,
|
|
||||||
Font,
|
|
||||||
FontLoader,
|
|
||||||
FreiChenShader,
|
|
||||||
FresnelShader,
|
|
||||||
FullScreenQuad,
|
|
||||||
GCodeLoader,
|
|
||||||
GLTFExporter,
|
|
||||||
GLTFLoader,
|
|
||||||
GPUComputationRenderer,
|
|
||||||
GammaCorrectionShader,
|
|
||||||
Geometry,
|
|
||||||
GeometryCompressionUtils,
|
|
||||||
GeometryUtils,
|
|
||||||
GlitchPass,
|
|
||||||
GodRaysCombineShader,
|
|
||||||
GodRaysDepthMaskShader,
|
|
||||||
GodRaysFakeSunShader,
|
|
||||||
GodRaysGenerateShader,
|
|
||||||
GrannyKnot,
|
|
||||||
GroundProjectedEnv,
|
|
||||||
Gyroscope,
|
|
||||||
HDRCubeTextureLoader,
|
|
||||||
HTMLMesh,
|
|
||||||
HalfEdge,
|
|
||||||
HalftonePass,
|
|
||||||
HalftoneShader,
|
|
||||||
HeartCurve,
|
|
||||||
HelixCurve,
|
|
||||||
HorizontalBlurShader,
|
|
||||||
HorizontalTiltShiftShader,
|
|
||||||
HueSaturationShader,
|
|
||||||
ImprovedNoise,
|
|
||||||
InstancedFlow,
|
|
||||||
InteractiveGroup,
|
|
||||||
KMZLoader,
|
|
||||||
KTX2Loader,
|
|
||||||
KTXLoader,
|
|
||||||
KaleidoShader,
|
|
||||||
KnotCurve,
|
|
||||||
LDrawLoader,
|
|
||||||
LUT3dlLoader,
|
|
||||||
LUTCubeLoader,
|
|
||||||
LUTPass,
|
|
||||||
LWOLoader,
|
|
||||||
Lensflare,
|
|
||||||
LensflareElement,
|
|
||||||
LightProbeGenerator,
|
|
||||||
LightProbeHelper,
|
|
||||||
LightningStorm,
|
|
||||||
LightningStrike,
|
|
||||||
Line2,
|
|
||||||
LineGeometry,
|
|
||||||
LineMaterial,
|
|
||||||
LineSegments2,
|
|
||||||
LineSegmentsGeometry,
|
|
||||||
LottieLoader,
|
|
||||||
LuminosityHighPassShader,
|
|
||||||
LuminosityShader,
|
|
||||||
Lut,
|
|
||||||
MD2Character,
|
|
||||||
MD2CharacterComplex,
|
|
||||||
MD2Loader,
|
|
||||||
MDDLoader,
|
|
||||||
MMDAnimationHelper,
|
|
||||||
MMDExporter,
|
|
||||||
MMDLoader,
|
|
||||||
MMDPhysics,
|
|
||||||
MTLLoader,
|
|
||||||
MapControls,
|
|
||||||
MapControlsExp,
|
|
||||||
MarchingCubes,
|
|
||||||
MaskPass,
|
|
||||||
MeshSurfaceSampler,
|
|
||||||
MeshoptDecoder,
|
|
||||||
MirrorShader,
|
|
||||||
MorphAnimMesh,
|
|
||||||
MorphBlendMesh,
|
|
||||||
MotionController,
|
|
||||||
MotionControllerConstants,
|
|
||||||
NRRDLoader,
|
|
||||||
NURBSCurve,
|
|
||||||
NURBSSurface,
|
|
||||||
NormalMapShader,
|
|
||||||
OBB,
|
|
||||||
OBJExporter,
|
|
||||||
OBJLoader,
|
|
||||||
Octree,
|
|
||||||
OculusHandModel,
|
|
||||||
OculusHandPointerModel,
|
|
||||||
OrbitControls,
|
|
||||||
OrbitControlsExp,
|
|
||||||
OutlineEffect,
|
|
||||||
OutlinePass,
|
|
||||||
PCDLoader,
|
|
||||||
PDBLoader,
|
|
||||||
PLYExporter,
|
|
||||||
PLYLoader,
|
|
||||||
PRWMLoader,
|
|
||||||
PVRLoader,
|
|
||||||
PackedPhongMaterial,
|
|
||||||
ParallaxBarrierEffect,
|
|
||||||
ParallaxShader,
|
|
||||||
ParametricGeometries,
|
|
||||||
ParametricGeometry,
|
|
||||||
Pass,
|
|
||||||
PeppersGhostEffect,
|
|
||||||
PixelShader,
|
|
||||||
PointerLockControls,
|
|
||||||
PositionalAudioHelper,
|
|
||||||
ProgressiveLightMap,
|
|
||||||
Projector,
|
|
||||||
RGBELoader,
|
|
||||||
RGBMLoader,
|
|
||||||
RGBShiftShader,
|
|
||||||
RaycasterHelper,
|
|
||||||
RectAreaLightHelper,
|
|
||||||
RectAreaLightUniformsLib,
|
|
||||||
Reflector,
|
|
||||||
ReflectorForSSRPass,
|
|
||||||
ReflectorRTT,
|
|
||||||
Refractor,
|
|
||||||
RenderPass,
|
|
||||||
RenderPixelatedPass,
|
|
||||||
RenderableFace,
|
|
||||||
RenderableLine,
|
|
||||||
RenderableObject,
|
|
||||||
RenderableSprite,
|
|
||||||
RenderableVertex,
|
|
||||||
Rhino3dmLoader,
|
|
||||||
RollerCoasterGeometry,
|
|
||||||
RollerCoasterLiftersGeometry,
|
|
||||||
RollerCoasterShadowGeometry,
|
|
||||||
RoomEnvironment,
|
|
||||||
RoughnessMipmapper,
|
|
||||||
RoundedBoxGeometry,
|
|
||||||
SAOPass,
|
|
||||||
SAOShader,
|
|
||||||
SMAABlendShader,
|
|
||||||
SMAAEdgesShader,
|
|
||||||
SMAAPass,
|
|
||||||
SMAAWeightsShader,
|
|
||||||
SSAARenderPass,
|
|
||||||
SSAOBlurShader,
|
|
||||||
SSAODepthShader,
|
|
||||||
SSAOPass,
|
|
||||||
SSAOShader,
|
|
||||||
SSRBlurShader,
|
|
||||||
SSRDepthShader,
|
|
||||||
SSRPass,
|
|
||||||
SSRShader,
|
|
||||||
STATE,
|
|
||||||
STLExporter,
|
|
||||||
STLLoader,
|
|
||||||
SVGLoader,
|
|
||||||
SVGObject,
|
|
||||||
SVGRenderer,
|
|
||||||
SavePass,
|
|
||||||
SceneUtils,
|
|
||||||
SelectionBox,
|
|
||||||
SelectionHelper,
|
|
||||||
SepiaShader,
|
|
||||||
ShaderPass,
|
|
||||||
ShadowMapViewer,
|
|
||||||
ShadowMesh,
|
|
||||||
SimplexNoise,
|
|
||||||
SimplifyModifier,
|
|
||||||
SkeletonUtils,
|
|
||||||
Sky,
|
|
||||||
SkyGeometry,
|
|
||||||
SobelOperatorShader,
|
|
||||||
StereoEffect,
|
|
||||||
SubsurfaceScatteringShader,
|
|
||||||
TAARenderPass,
|
|
||||||
TDSLoader,
|
|
||||||
TGALoader,
|
|
||||||
TTFLoader,
|
|
||||||
TeapotGeometry,
|
|
||||||
TechnicolorShader,
|
|
||||||
TessellateModifier,
|
|
||||||
TextGeometry,
|
|
||||||
TexturePass,
|
|
||||||
ThreeMFLoader,
|
|
||||||
TiltLoader,
|
|
||||||
Timer,
|
|
||||||
ToneMapShader,
|
|
||||||
ToonShader1,
|
|
||||||
ToonShader2,
|
|
||||||
ToonShaderDotted,
|
|
||||||
ToonShaderHatching,
|
|
||||||
TorusKnot,
|
|
||||||
TrackballControls,
|
|
||||||
TrackballControlsExp,
|
|
||||||
TransformControls,
|
|
||||||
TransformControlsGizmo,
|
|
||||||
TransformControlsPlane,
|
|
||||||
TreesGeometry,
|
|
||||||
TrefoilKnot,
|
|
||||||
TrefoilPolynomialKnot,
|
|
||||||
TriangleBlurShader,
|
|
||||||
TubePainter,
|
|
||||||
USDZExporter,
|
|
||||||
UVsDebug,
|
|
||||||
UnpackDepthRGBAShader,
|
|
||||||
UnrealBloomPass,
|
|
||||||
VOXData3DTexture,
|
|
||||||
VOXLoader,
|
|
||||||
VOXMesh,
|
|
||||||
VRButton,
|
|
||||||
VRMLLoader,
|
|
||||||
VRMLoader,
|
|
||||||
VTKLoader,
|
|
||||||
VertexList,
|
|
||||||
VertexNode,
|
|
||||||
VertexNormalsHelper,
|
|
||||||
VertexTangentsHelper,
|
|
||||||
VerticalBlurShader,
|
|
||||||
VerticalTiltShiftShader,
|
|
||||||
VignetteShader,
|
|
||||||
VivianiCurve,
|
|
||||||
Volume,
|
|
||||||
VolumeRenderShader1,
|
|
||||||
VolumeSlice,
|
|
||||||
Water,
|
|
||||||
Water2,
|
|
||||||
WaterPass,
|
|
||||||
WaterRefractionShader,
|
|
||||||
Wireframe,
|
|
||||||
WireframeGeometry2,
|
|
||||||
XLoader,
|
|
||||||
XRControllerModelFactory,
|
|
||||||
XREstimatedLight,
|
|
||||||
XRHandMeshModel,
|
|
||||||
XRHandModelFactory,
|
|
||||||
XRHandPrimitiveModel,
|
|
||||||
XYZLoader,
|
|
||||||
calcBSplineDerivatives,
|
|
||||||
calcBSplinePoint,
|
|
||||||
calcBasisFunctionDerivatives,
|
|
||||||
calcBasisFunctions,
|
|
||||||
calcKoverI,
|
|
||||||
calcNURBSDerivatives,
|
|
||||||
calcRationalCurveDerivatives,
|
|
||||||
calcSurfacePoint,
|
|
||||||
computeMorphedAttributes,
|
|
||||||
createText,
|
|
||||||
edgeTable,
|
|
||||||
estimateBytesUsed,
|
|
||||||
fetchProfile,
|
|
||||||
fetchProfilesList,
|
|
||||||
findSpan,
|
|
||||||
getErrorMessage,
|
|
||||||
getUniforms,
|
|
||||||
getWebGL2ErrorMessage,
|
|
||||||
getWebGLErrorMessage,
|
|
||||||
initSplineTexture,
|
|
||||||
interleaveAttributes,
|
|
||||||
isWebGL2Available,
|
|
||||||
isWebGLAvailable,
|
|
||||||
mergeBufferAttributes,
|
|
||||||
mergeBufferGeometries,
|
|
||||||
mergeVertices,
|
|
||||||
modifyShader,
|
|
||||||
toCreasedNormals,
|
|
||||||
toTrianglesDrawMode,
|
|
||||||
triTable,
|
|
||||||
updateSplineTexture
|
|
||||||
} from "./chunk-TGAC7IAX.js";
|
|
||||||
import "./chunk-L3Z576C2.js";
|
|
||||||
import "./chunk-G3PMV62Z.js";
|
|
||||||
export {
|
|
||||||
ACESFilmicToneMappingShader,
|
|
||||||
AMFLoader,
|
|
||||||
ARButton,
|
|
||||||
AdaptiveToneMappingPass,
|
|
||||||
AfterimagePass,
|
|
||||||
AfterimageShader,
|
|
||||||
AmmoPhysics,
|
|
||||||
AnaglyphEffect,
|
|
||||||
AnimationClipCreator,
|
|
||||||
ArcballControls,
|
|
||||||
AsciiEffect,
|
|
||||||
AssimpLoader,
|
|
||||||
BVHLoader,
|
|
||||||
BasicShader,
|
|
||||||
BasisTextureLoader,
|
|
||||||
BatchedMesh,
|
|
||||||
BleachBypassShader,
|
|
||||||
BlendShader,
|
|
||||||
BloomPass,
|
|
||||||
BlurShaderUtils,
|
|
||||||
BokehDepthShader,
|
|
||||||
BokehPass,
|
|
||||||
BokehShader,
|
|
||||||
BokehShader2,
|
|
||||||
BoxLineGeometry,
|
|
||||||
BrightnessContrastShader,
|
|
||||||
CCDIKHelper,
|
|
||||||
CCDIKSolver,
|
|
||||||
CSM,
|
|
||||||
CSMFrustum,
|
|
||||||
CSMHelper,
|
|
||||||
CSMShader,
|
|
||||||
CSS2DObject,
|
|
||||||
CSS2DRenderer,
|
|
||||||
CSS3DObject,
|
|
||||||
CSS3DRenderer,
|
|
||||||
CSS3DSprite,
|
|
||||||
CameraControls,
|
|
||||||
Capsule,
|
|
||||||
CinematicCamera,
|
|
||||||
CinquefoilKnot,
|
|
||||||
ClearMaskPass,
|
|
||||||
ClearPass,
|
|
||||||
ColladaExporter,
|
|
||||||
ColladaLoader,
|
|
||||||
ColorConverter,
|
|
||||||
ColorCorrectionShader,
|
|
||||||
ColorMapKeywords,
|
|
||||||
ColorifyShader,
|
|
||||||
ConvexGeometry,
|
|
||||||
ConvexHull,
|
|
||||||
ConvexObjectBreaker,
|
|
||||||
ConvolutionShader,
|
|
||||||
CopyShader,
|
|
||||||
CubeTexturePass,
|
|
||||||
DDSLoader,
|
|
||||||
DOFMipMapShader,
|
|
||||||
DRACOExporter,
|
|
||||||
DRACOLoader,
|
|
||||||
DecalGeometry,
|
|
||||||
DecalVertex,
|
|
||||||
DecoratedTorusKnot4a,
|
|
||||||
DecoratedTorusKnot4b,
|
|
||||||
DecoratedTorusKnot5a,
|
|
||||||
DecoratedTorusKnot5c,
|
|
||||||
DepthLimitedBlurShader,
|
|
||||||
DeviceOrientationControls,
|
|
||||||
DigitalGlitch,
|
|
||||||
DotScreenPass,
|
|
||||||
DotScreenShader,
|
|
||||||
DragControls,
|
|
||||||
EXRLoader,
|
|
||||||
EdgeSplitModifier,
|
|
||||||
EffectComposer,
|
|
||||||
FBXLoader,
|
|
||||||
FXAAShader,
|
|
||||||
Face,
|
|
||||||
Face3,
|
|
||||||
FigureEightPolynomialKnot,
|
|
||||||
FilmPass,
|
|
||||||
FilmShader,
|
|
||||||
FirstPersonControls,
|
|
||||||
FlakesTexture,
|
|
||||||
Flow,
|
|
||||||
FlyControls,
|
|
||||||
FocusShader,
|
|
||||||
Font,
|
|
||||||
FontLoader,
|
|
||||||
FreiChenShader,
|
|
||||||
FresnelShader,
|
|
||||||
FullScreenQuad,
|
|
||||||
GCodeLoader,
|
|
||||||
GLTFExporter,
|
|
||||||
GLTFLoader,
|
|
||||||
GPUComputationRenderer,
|
|
||||||
GammaCorrectionShader,
|
|
||||||
Geometry,
|
|
||||||
GeometryCompressionUtils,
|
|
||||||
GeometryUtils,
|
|
||||||
GlitchPass,
|
|
||||||
GodRaysCombineShader,
|
|
||||||
GodRaysDepthMaskShader,
|
|
||||||
GodRaysFakeSunShader,
|
|
||||||
GodRaysGenerateShader,
|
|
||||||
GrannyKnot,
|
|
||||||
GroundProjectedEnv,
|
|
||||||
Gyroscope,
|
|
||||||
HDRCubeTextureLoader,
|
|
||||||
HTMLMesh,
|
|
||||||
HalfEdge,
|
|
||||||
HalftonePass,
|
|
||||||
HalftoneShader,
|
|
||||||
HeartCurve,
|
|
||||||
HelixCurve,
|
|
||||||
HorizontalBlurShader,
|
|
||||||
HorizontalTiltShiftShader,
|
|
||||||
HueSaturationShader,
|
|
||||||
ImprovedNoise,
|
|
||||||
InstancedFlow,
|
|
||||||
InteractiveGroup,
|
|
||||||
KMZLoader,
|
|
||||||
KTX2Loader,
|
|
||||||
KTXLoader,
|
|
||||||
KaleidoShader,
|
|
||||||
KnotCurve,
|
|
||||||
LDrawLoader,
|
|
||||||
LUT3dlLoader,
|
|
||||||
LUTCubeLoader,
|
|
||||||
LUTPass,
|
|
||||||
LWOLoader,
|
|
||||||
Lensflare,
|
|
||||||
LensflareElement,
|
|
||||||
LightProbeGenerator,
|
|
||||||
LightProbeHelper,
|
|
||||||
LightningStorm,
|
|
||||||
LightningStrike,
|
|
||||||
Line2,
|
|
||||||
LineGeometry,
|
|
||||||
LineMaterial,
|
|
||||||
LineSegments2,
|
|
||||||
LineSegmentsGeometry,
|
|
||||||
LottieLoader,
|
|
||||||
LuminosityHighPassShader,
|
|
||||||
LuminosityShader,
|
|
||||||
Lut,
|
|
||||||
MD2Character,
|
|
||||||
MD2CharacterComplex,
|
|
||||||
MD2Loader,
|
|
||||||
MDDLoader,
|
|
||||||
MMDAnimationHelper,
|
|
||||||
MMDExporter,
|
|
||||||
MMDLoader,
|
|
||||||
MMDPhysics,
|
|
||||||
MTLLoader,
|
|
||||||
MapControls,
|
|
||||||
MapControlsExp,
|
|
||||||
MarchingCubes,
|
|
||||||
MaskPass,
|
|
||||||
MeshSurfaceSampler,
|
|
||||||
MeshoptDecoder,
|
|
||||||
MirrorShader,
|
|
||||||
MorphAnimMesh,
|
|
||||||
MorphBlendMesh,
|
|
||||||
MotionController,
|
|
||||||
MotionControllerConstants,
|
|
||||||
NRRDLoader,
|
|
||||||
NURBSCurve,
|
|
||||||
NURBSSurface,
|
|
||||||
NormalMapShader,
|
|
||||||
OBB,
|
|
||||||
OBJExporter,
|
|
||||||
OBJLoader,
|
|
||||||
Octree,
|
|
||||||
OculusHandModel,
|
|
||||||
OculusHandPointerModel,
|
|
||||||
OrbitControls,
|
|
||||||
OrbitControlsExp,
|
|
||||||
OutlineEffect,
|
|
||||||
OutlinePass,
|
|
||||||
PCDLoader,
|
|
||||||
PDBLoader,
|
|
||||||
PLYExporter,
|
|
||||||
PLYLoader,
|
|
||||||
PRWMLoader,
|
|
||||||
PVRLoader,
|
|
||||||
PackedPhongMaterial,
|
|
||||||
ParallaxBarrierEffect,
|
|
||||||
ParallaxShader,
|
|
||||||
ParametricGeometries,
|
|
||||||
ParametricGeometry,
|
|
||||||
Pass,
|
|
||||||
PeppersGhostEffect,
|
|
||||||
PixelShader,
|
|
||||||
PointerLockControls,
|
|
||||||
PositionalAudioHelper,
|
|
||||||
ProgressiveLightMap,
|
|
||||||
Projector,
|
|
||||||
RGBELoader,
|
|
||||||
RGBMLoader,
|
|
||||||
RGBShiftShader,
|
|
||||||
RaycasterHelper,
|
|
||||||
RectAreaLightHelper,
|
|
||||||
RectAreaLightUniformsLib,
|
|
||||||
Reflector,
|
|
||||||
ReflectorForSSRPass,
|
|
||||||
ReflectorRTT,
|
|
||||||
Refractor,
|
|
||||||
RenderPass,
|
|
||||||
RenderPixelatedPass,
|
|
||||||
RenderableFace,
|
|
||||||
RenderableLine,
|
|
||||||
RenderableObject,
|
|
||||||
RenderableSprite,
|
|
||||||
RenderableVertex,
|
|
||||||
Rhino3dmLoader,
|
|
||||||
RollerCoasterGeometry,
|
|
||||||
RollerCoasterLiftersGeometry,
|
|
||||||
RollerCoasterShadowGeometry,
|
|
||||||
RoomEnvironment,
|
|
||||||
RoughnessMipmapper,
|
|
||||||
RoundedBoxGeometry,
|
|
||||||
SAOPass,
|
|
||||||
SAOShader,
|
|
||||||
SMAABlendShader,
|
|
||||||
SMAAEdgesShader,
|
|
||||||
SMAAPass,
|
|
||||||
SMAAWeightsShader,
|
|
||||||
SSAARenderPass,
|
|
||||||
SSAOBlurShader,
|
|
||||||
SSAODepthShader,
|
|
||||||
SSAOPass,
|
|
||||||
SSAOShader,
|
|
||||||
SSRBlurShader,
|
|
||||||
SSRDepthShader,
|
|
||||||
SSRPass,
|
|
||||||
SSRShader,
|
|
||||||
STATE,
|
|
||||||
STLExporter,
|
|
||||||
STLLoader,
|
|
||||||
SVGLoader,
|
|
||||||
SVGObject,
|
|
||||||
SVGRenderer,
|
|
||||||
SavePass,
|
|
||||||
SceneUtils,
|
|
||||||
SelectionBox,
|
|
||||||
SelectionHelper,
|
|
||||||
SepiaShader,
|
|
||||||
ShaderPass,
|
|
||||||
ShadowMapViewer,
|
|
||||||
ShadowMesh,
|
|
||||||
SimplexNoise,
|
|
||||||
SimplifyModifier,
|
|
||||||
SkeletonUtils,
|
|
||||||
Sky,
|
|
||||||
SkyGeometry,
|
|
||||||
SobelOperatorShader,
|
|
||||||
StereoEffect,
|
|
||||||
SubsurfaceScatteringShader,
|
|
||||||
TAARenderPass,
|
|
||||||
TDSLoader,
|
|
||||||
TGALoader,
|
|
||||||
TTFLoader,
|
|
||||||
TeapotGeometry,
|
|
||||||
TechnicolorShader,
|
|
||||||
TessellateModifier,
|
|
||||||
TextGeometry as TextBufferGeometry,
|
|
||||||
TextGeometry,
|
|
||||||
TexturePass,
|
|
||||||
ThreeMFLoader,
|
|
||||||
TiltLoader,
|
|
||||||
Timer,
|
|
||||||
ToneMapShader,
|
|
||||||
ToonShader1,
|
|
||||||
ToonShader2,
|
|
||||||
ToonShaderDotted,
|
|
||||||
ToonShaderHatching,
|
|
||||||
TorusKnot,
|
|
||||||
TrackballControls,
|
|
||||||
TrackballControlsExp,
|
|
||||||
TransformControls,
|
|
||||||
TransformControlsGizmo,
|
|
||||||
TransformControlsPlane,
|
|
||||||
TreesGeometry,
|
|
||||||
TrefoilKnot,
|
|
||||||
TrefoilPolynomialKnot,
|
|
||||||
TriangleBlurShader,
|
|
||||||
TubePainter,
|
|
||||||
USDZExporter,
|
|
||||||
UVsDebug,
|
|
||||||
UnpackDepthRGBAShader,
|
|
||||||
UnrealBloomPass,
|
|
||||||
VOXData3DTexture,
|
|
||||||
VOXLoader,
|
|
||||||
VOXMesh,
|
|
||||||
VRButton,
|
|
||||||
VRMLLoader,
|
|
||||||
VRMLoader,
|
|
||||||
VTKLoader,
|
|
||||||
VertexList,
|
|
||||||
VertexNode,
|
|
||||||
VertexNormalsHelper,
|
|
||||||
VertexTangentsHelper,
|
|
||||||
VerticalBlurShader,
|
|
||||||
VerticalTiltShiftShader,
|
|
||||||
VignetteShader,
|
|
||||||
VivianiCurve,
|
|
||||||
Volume,
|
|
||||||
VolumeRenderShader1,
|
|
||||||
VolumeSlice,
|
|
||||||
Water,
|
|
||||||
Water2,
|
|
||||||
WaterPass,
|
|
||||||
WaterRefractionShader,
|
|
||||||
Wireframe,
|
|
||||||
WireframeGeometry2,
|
|
||||||
XLoader,
|
|
||||||
XRControllerModelFactory,
|
|
||||||
XREstimatedLight,
|
|
||||||
XRHandMeshModel,
|
|
||||||
XRHandModelFactory,
|
|
||||||
XRHandPrimitiveModel,
|
|
||||||
XYZLoader,
|
|
||||||
calcBSplineDerivatives,
|
|
||||||
calcBSplinePoint,
|
|
||||||
calcBasisFunctionDerivatives,
|
|
||||||
calcBasisFunctions,
|
|
||||||
calcKoverI,
|
|
||||||
calcNURBSDerivatives,
|
|
||||||
calcRationalCurveDerivatives,
|
|
||||||
calcSurfacePoint,
|
|
||||||
computeMorphedAttributes,
|
|
||||||
createText,
|
|
||||||
edgeTable,
|
|
||||||
estimateBytesUsed,
|
|
||||||
fetchProfile,
|
|
||||||
fetchProfilesList,
|
|
||||||
findSpan,
|
|
||||||
getErrorMessage,
|
|
||||||
getUniforms,
|
|
||||||
getWebGL2ErrorMessage,
|
|
||||||
getWebGLErrorMessage,
|
|
||||||
initSplineTexture,
|
|
||||||
interleaveAttributes,
|
|
||||||
isWebGL2Available,
|
|
||||||
isWebGLAvailable,
|
|
||||||
mergeBufferAttributes,
|
|
||||||
mergeBufferGeometries,
|
|
||||||
mergeVertices,
|
|
||||||
modifyShader,
|
|
||||||
toCreasedNormals,
|
|
||||||
toTrianglesDrawMode,
|
|
||||||
triTable,
|
|
||||||
updateSplineTexture
|
|
||||||
};
|
|
||||||
7
app/node_modules/.vite/deps/three-stdlib.js.map
generated
vendored
7
app/node_modules/.vite/deps/three-stdlib.js.map
generated
vendored
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"sources": [],
|
|
||||||
"sourcesContent": [],
|
|
||||||
"mappings": "",
|
|
||||||
"names": []
|
|
||||||
}
|
|
||||||
3
app/node_modules/@esbuild/win32-x64/README.md
generated
vendored
3
app/node_modules/@esbuild/win32-x64/README.md
generated
vendored
@@ -1,3 +0,0 @@
|
|||||||
# esbuild
|
|
||||||
|
|
||||||
This is the Windows 64-bit binary for esbuild, a JavaScript bundler and minifier. See https://github.com/evanw/esbuild for details.
|
|
||||||
BIN
app/node_modules/@esbuild/win32-x64/esbuild.exe
generated
vendored
BIN
app/node_modules/@esbuild/win32-x64/esbuild.exe
generated
vendored
Binary file not shown.
20
app/node_modules/@esbuild/win32-x64/package.json
generated
vendored
20
app/node_modules/@esbuild/win32-x64/package.json
generated
vendored
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@esbuild/win32-x64",
|
|
||||||
"version": "0.27.2",
|
|
||||||
"description": "The Windows 64-bit binary for esbuild, a JavaScript bundler.",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git+https://github.com/evanw/esbuild.git"
|
|
||||||
},
|
|
||||||
"license": "MIT",
|
|
||||||
"preferUnplugged": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
],
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
3
app/node_modules/@rollup/rollup-win32-x64-gnu/README.md
generated
vendored
3
app/node_modules/@rollup/rollup-win32-x64-gnu/README.md
generated
vendored
@@ -1,3 +0,0 @@
|
|||||||
# `@rollup/rollup-win32-x64-gnu`
|
|
||||||
|
|
||||||
This is the **x86_64-pc-windows-gnu** binary for `rollup`
|
|
||||||
22
app/node_modules/@rollup/rollup-win32-x64-gnu/package.json
generated
vendored
22
app/node_modules/@rollup/rollup-win32-x64-gnu/package.json
generated
vendored
@@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@rollup/rollup-win32-x64-gnu",
|
|
||||||
"version": "4.55.1",
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
],
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"files": [
|
|
||||||
"rollup.win32-x64-gnu.node"
|
|
||||||
],
|
|
||||||
"description": "Native bindings for Rollup",
|
|
||||||
"author": "Lukas Taegert-Atkinson",
|
|
||||||
"homepage": "https://rollupjs.org/",
|
|
||||||
"license": "MIT",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git+https://github.com/rollup/rollup.git"
|
|
||||||
},
|
|
||||||
"main": "./rollup.win32-x64-gnu.node"
|
|
||||||
}
|
|
||||||
BIN
app/node_modules/@rollup/rollup-win32-x64-gnu/rollup.win32-x64-gnu.node
generated
vendored
BIN
app/node_modules/@rollup/rollup-win32-x64-gnu/rollup.win32-x64-gnu.node
generated
vendored
Binary file not shown.
3
app/node_modules/@rollup/rollup-win32-x64-msvc/README.md
generated
vendored
3
app/node_modules/@rollup/rollup-win32-x64-msvc/README.md
generated
vendored
@@ -1,3 +0,0 @@
|
|||||||
# `@rollup/rollup-win32-x64-msvc`
|
|
||||||
|
|
||||||
This is the **x86_64-pc-windows-msvc** binary for `rollup`
|
|
||||||
22
app/node_modules/@rollup/rollup-win32-x64-msvc/package.json
generated
vendored
22
app/node_modules/@rollup/rollup-win32-x64-msvc/package.json
generated
vendored
@@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@rollup/rollup-win32-x64-msvc",
|
|
||||||
"version": "4.55.1",
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
],
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"files": [
|
|
||||||
"rollup.win32-x64-msvc.node"
|
|
||||||
],
|
|
||||||
"description": "Native bindings for Rollup",
|
|
||||||
"author": "Lukas Taegert-Atkinson",
|
|
||||||
"homepage": "https://rollupjs.org/",
|
|
||||||
"license": "MIT",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git+https://github.com/rollup/rollup.git"
|
|
||||||
},
|
|
||||||
"main": "./rollup.win32-x64-msvc.node"
|
|
||||||
}
|
|
||||||
BIN
app/node_modules/@rollup/rollup-win32-x64-msvc/rollup.win32-x64-msvc.node
generated
vendored
BIN
app/node_modules/@rollup/rollup-win32-x64-msvc/rollup.win32-x64-msvc.node
generated
vendored
Binary file not shown.
58
app/package-lock.json
generated
58
app/package-lock.json
generated
@@ -54,6 +54,7 @@
|
|||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"react-hook-form": "^7.70.0",
|
"react-hook-form": "^7.70.0",
|
||||||
"react-resizable-panels": "^4.2.2",
|
"react-resizable-panels": "^4.2.2",
|
||||||
|
"react-router-dom": "^7.13.1",
|
||||||
"recharts": "^2.15.4",
|
"recharts": "^2.15.4",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
@@ -5834,6 +5835,19 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/cookie": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/core-js-compat": {
|
"node_modules/core-js-compat": {
|
||||||
"version": "3.48.0",
|
"version": "3.48.0",
|
||||||
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.48.0.tgz",
|
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.48.0.tgz",
|
||||||
@@ -7802,6 +7816,44 @@
|
|||||||
"react-dom": "^18.0.0 || ^19.0.0"
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-router": {
|
||||||
|
"version": "7.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.1.tgz",
|
||||||
|
"integrity": "sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cookie": "^1.0.1",
|
||||||
|
"set-cookie-parser": "^2.6.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=18",
|
||||||
|
"react-dom": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-router-dom": {
|
||||||
|
"version": "7.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.1.tgz",
|
||||||
|
"integrity": "sha512-UJnV3Rxc5TgUPJt2KJpo1Jpy0OKQr0AjgbZzBFjaPJcFOb2Y8jA5H3LT8HUJAiRLlWrEXWHbF1Z4SCZaQjWDHw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"react-router": "7.13.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=18",
|
||||||
|
"react-dom": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-smooth": {
|
"node_modules/react-smooth": {
|
||||||
"version": "4.0.4",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",
|
||||||
@@ -8132,6 +8184,12 @@
|
|||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/set-cookie-parser": {
|
||||||
|
"version": "2.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
|
||||||
|
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/shebang-command": {
|
"node_modules/shebang-command": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
|
|||||||
@@ -56,6 +56,7 @@
|
|||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"react-hook-form": "^7.70.0",
|
"react-hook-form": "^7.70.0",
|
||||||
"react-resizable-panels": "^4.2.2",
|
"react-resizable-panels": "^4.2.2",
|
||||||
|
"react-router-dom": "^7.13.1",
|
||||||
"recharts": "^2.15.4",
|
"recharts": "^2.15.4",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
|
|||||||
117
app/src/App.tsx
117
app/src/App.tsx
@@ -1,3 +1,5 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import { Routes, Route, Navigate, useNavigate, useLocation } from 'react-router-dom';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import { useStore } from '@/store/useStore';
|
import { useStore } from '@/store/useStore';
|
||||||
import { Sidebar } from '@/components/layout/Sidebar';
|
import { Sidebar } from '@/components/layout/Sidebar';
|
||||||
@@ -7,6 +9,7 @@ import { Oracle } from '@/components/modules/Oracle';
|
|||||||
import { Sentinel } from '@/components/modules/Sentinel';
|
import { Sentinel } from '@/components/modules/Sentinel';
|
||||||
import { Inventory } from '@/components/modules/Inventory';
|
import { Inventory } from '@/components/modules/Inventory';
|
||||||
import { Settings } from '@/components/modules/Settings';
|
import { Settings } from '@/components/modules/Settings';
|
||||||
|
import { Catalyst } from '@/components/modules/Catalyst';
|
||||||
import type { ModuleId } from '@/types';
|
import type { ModuleId } from '@/types';
|
||||||
import {
|
import {
|
||||||
MoreVertical,
|
MoreVertical,
|
||||||
@@ -19,30 +22,75 @@ import {
|
|||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu"
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
|
||||||
const moduleComponents: Record<ModuleId, React.ComponentType> = {
|
// ── Route map ─────────────────────────────────────────────────────────────────
|
||||||
dashboard: Dashboard,
|
// Single source of truth: module id → URL path → page title → component
|
||||||
oracle: Oracle,
|
|
||||||
sentinel: Sentinel,
|
|
||||||
inventory: Inventory,
|
|
||||||
settings: Settings,
|
|
||||||
};
|
|
||||||
|
|
||||||
const moduleTitles: Record<ModuleId, string> = {
|
export const MODULE_ROUTES: Array<{
|
||||||
dashboard: 'Dashboard',
|
id: ModuleId;
|
||||||
oracle: 'The Oracle',
|
path: string;
|
||||||
sentinel: 'The Sentinel',
|
title: string;
|
||||||
inventory: 'Inventory',
|
component: React.ComponentType;
|
||||||
settings: 'Settings',
|
}> = [
|
||||||
};
|
{ id: 'dashboard', path: '/dashboard', title: 'Dashboard', component: Dashboard },
|
||||||
|
{ id: 'oracle', path: '/oracle', title: 'The Oracle', component: Oracle },
|
||||||
|
{ id: 'sentinel', path: '/sentinel', title: 'The Sentinel', component: Sentinel },
|
||||||
|
{ id: 'inventory', path: '/inventory', title: 'Inventory', component: Inventory },
|
||||||
|
{ id: 'catalyst', path: '/catalyst', title: 'The Catalyst', component: Catalyst },
|
||||||
|
{ id: 'settings', path: '/settings', title: 'Settings', component: Settings },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const PATH_TO_MODULE = Object.fromEntries(
|
||||||
|
MODULE_ROUTES.map((r) => [r.path, r.id])
|
||||||
|
) as Record<string, ModuleId>;
|
||||||
|
|
||||||
|
// ── Protected Route guard ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function ProtectedRoute({ children }: { children: React.ReactNode }) {
|
||||||
|
const { isAuthenticated } = useStore();
|
||||||
|
if (!isAuthenticated) return <Navigate to="/login" replace />;
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Sync URL → Zustand activeModule ──────────────────────────────────────────
|
||||||
|
// This keeps the Zustand store in sync with the URL so existing module
|
||||||
|
// components that read `activeModule` from the store continue to work.
|
||||||
|
|
||||||
|
function RouteModuleSync() {
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
const { setActiveModule } = useStore();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const moduleId = PATH_TO_MODULE[pathname];
|
||||||
|
if (moduleId) setActiveModule(moduleId);
|
||||||
|
}, [pathname, setActiveModule]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Main authenticated layout ─────────────────────────────────────────────────
|
||||||
|
|
||||||
function MainLayout() {
|
function MainLayout() {
|
||||||
const { activeModule, setActiveModule, sidebarExpanded, logout } = useStore();
|
const { activeModule, setActiveModule, sidebarExpanded, logout } = useStore();
|
||||||
const ActiveComponent = moduleComponents[activeModule];
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
// Current route title
|
||||||
|
const currentRoute = MODULE_ROUTES.find((r) => r.path === location.pathname);
|
||||||
|
const pageTitle = currentRoute?.title ?? 'Velocity';
|
||||||
|
|
||||||
|
// Navigate to settings from dropdown (keeps router in sync)
|
||||||
|
const goToSettings = () => {
|
||||||
|
setActiveModule('settings');
|
||||||
|
navigate('/settings');
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex" style={{ background: '#000' }}>
|
<div className="min-h-screen flex" style={{ background: '#000' }}>
|
||||||
|
{/* Sync URL → store */}
|
||||||
|
<RouteModuleSync />
|
||||||
|
|
||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
|
|
||||||
@@ -73,7 +121,7 @@ function MainLayout() {
|
|||||||
animate={{ opacity: 1, x: 0 }}
|
animate={{ opacity: 1, x: 0 }}
|
||||||
transition={{ duration: 0.25 }}
|
transition={{ duration: 0.25 }}
|
||||||
>
|
>
|
||||||
<h1 className="text-base font-semibold text-white tracking-tight">{moduleTitles[activeModule]}</h1>
|
<h1 className="text-base font-semibold text-white tracking-tight">{pageTitle}</h1>
|
||||||
<p className="text-xs" style={{ color: 'hsl(var(--muted-fg))' }}>Project Velocity · v.1.1</p>
|
<p className="text-xs" style={{ color: 'hsl(var(--muted-fg))' }}>Project Velocity · v.1.1</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
@@ -101,7 +149,7 @@ function MainLayout() {
|
|||||||
<DropdownMenuContent align="end" className="w-48 bg-[#0A0B10] border-white/10 text-zinc-200">
|
<DropdownMenuContent align="end" className="w-48 bg-[#0A0B10] border-white/10 text-zinc-200">
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className="cursor-pointer focus:bg-white/5"
|
className="cursor-pointer focus:bg-white/5"
|
||||||
onClick={() => setActiveModule('settings')}
|
onClick={goToSettings}
|
||||||
>
|
>
|
||||||
<Settings2 className="mr-2 h-4 w-4 text-zinc-400" />
|
<Settings2 className="mr-2 h-4 w-4 text-zinc-400" />
|
||||||
<span>Settings</span>
|
<span>Settings</span>
|
||||||
@@ -117,11 +165,11 @@ function MainLayout() {
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{/* Module Content */}
|
{/* Module Content — animated on route change */}
|
||||||
<div className="px-8 pb-8">
|
<div className="px-8 pb-8">
|
||||||
<AnimatePresence mode="wait">
|
<AnimatePresence mode="wait">
|
||||||
<motion.div
|
<motion.div
|
||||||
key={activeModule}
|
key={location.pathname}
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
exit={{ opacity: 0, y: -20 }}
|
exit={{ opacity: 0, y: -20 }}
|
||||||
@@ -130,7 +178,16 @@ function MainLayout() {
|
|||||||
ease: [0.4, 0, 0.2, 1]
|
ease: [0.4, 0, 0.2, 1]
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ActiveComponent />
|
{/* Nested module routes rendered here */}
|
||||||
|
<Routes>
|
||||||
|
{MODULE_ROUTES.map(({ path, component: Component }) => (
|
||||||
|
<Route key={path} path={path} element={<Component />} />
|
||||||
|
))}
|
||||||
|
{/* Default: redirect / → /dashboard */}
|
||||||
|
<Route path="/" element={<Navigate to="/dashboard" replace />} />
|
||||||
|
{/* Catch-all: any unknown path → dashboard */}
|
||||||
|
<Route path="*" element={<Navigate to="/dashboard" replace />} />
|
||||||
|
</Routes>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
@@ -139,6 +196,8 @@ function MainLayout() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Root App ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const { isAuthenticated } = useStore();
|
const { isAuthenticated } = useStore();
|
||||||
|
|
||||||
@@ -152,7 +211,10 @@ function App() {
|
|||||||
exit={{ opacity: 0 }}
|
exit={{ opacity: 0 }}
|
||||||
transition={{ duration: 0.5 }}
|
transition={{ duration: 0.5 }}
|
||||||
>
|
>
|
||||||
<LoginScreen />
|
<Routes>
|
||||||
|
<Route path="/login" element={<LoginScreen />} />
|
||||||
|
<Route path="*" element={<LoginScreen />} />
|
||||||
|
</Routes>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
) : (
|
) : (
|
||||||
<motion.div
|
<motion.div
|
||||||
@@ -162,7 +224,16 @@ function App() {
|
|||||||
exit={{ opacity: 0 }}
|
exit={{ opacity: 0 }}
|
||||||
transition={{ duration: 0.5 }}
|
transition={{ duration: 0.5 }}
|
||||||
>
|
>
|
||||||
<MainLayout />
|
<Routes>
|
||||||
|
<Route
|
||||||
|
path="/*"
|
||||||
|
element={
|
||||||
|
<ProtectedRoute>
|
||||||
|
<MainLayout />
|
||||||
|
</ProtectedRoute>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Routes>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { NavLink } from 'react-router-dom';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import {
|
import {
|
||||||
LayoutGrid,
|
LayoutGrid,
|
||||||
@@ -5,21 +6,23 @@ import {
|
|||||||
ScanFace,
|
ScanFace,
|
||||||
Building2,
|
Building2,
|
||||||
Sliders,
|
Sliders,
|
||||||
|
Megaphone,
|
||||||
type LucideIcon,
|
type LucideIcon,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useStore } from '@/store/useStore';
|
import { useStore } from '@/store/useStore';
|
||||||
import type { ModuleId } from '@/types';
|
import { MODULE_ROUTES } from '@/App';
|
||||||
|
|
||||||
const navItems: { id: ModuleId; label: string; icon: LucideIcon }[] = [
|
const NAV_ICONS: Record<string, LucideIcon> = {
|
||||||
{ id: 'dashboard', label: 'Dashboard', icon: LayoutGrid },
|
'/dashboard': LayoutGrid,
|
||||||
{ id: 'oracle', label: 'The Oracle', icon: MessageSquarePlus },
|
'/oracle': MessageSquarePlus,
|
||||||
{ id: 'sentinel', label: 'The Sentinel', icon: ScanFace },
|
'/sentinel': ScanFace,
|
||||||
{ id: 'inventory', label: 'Inventory', icon: Building2 },
|
'/inventory': Building2,
|
||||||
{ id: 'settings', label: 'Settings', icon: Sliders },
|
'/catalyst': Megaphone,
|
||||||
];
|
'/settings': Sliders,
|
||||||
|
};
|
||||||
|
|
||||||
export function Sidebar() {
|
export function Sidebar() {
|
||||||
const { activeModule, setActiveModule, sidebarExpanded, setSidebarExpanded, status } = useStore();
|
const { sidebarExpanded, setSidebarExpanded, status } = useStore();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.aside
|
<motion.aside
|
||||||
@@ -46,7 +49,6 @@ export function Sidebar() {
|
|||||||
|
|
||||||
{/* Brand Name */}
|
{/* Brand Name */}
|
||||||
<div className="flex items-center px-4 mb-8 overflow-hidden" style={{ height: 36 }}>
|
<div className="flex items-center px-4 mb-8 overflow-hidden" style={{ height: 36 }}>
|
||||||
{/* Expanded: full name */}
|
|
||||||
<motion.div
|
<motion.div
|
||||||
className="overflow-hidden whitespace-nowrap"
|
className="overflow-hidden whitespace-nowrap"
|
||||||
initial={{ opacity: 0, width: 0 }}
|
initial={{ opacity: 0, width: 0 }}
|
||||||
@@ -58,55 +60,59 @@ export function Sidebar() {
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{/* Nav */}
|
{/* Nav */}
|
||||||
<nav className="flex-1 px-3 space-y-1">
|
<nav className="flex-1 px-3 space-y-1">
|
||||||
{navItems.map((item) => {
|
{MODULE_ROUTES.map((route) => {
|
||||||
const Icon = item.icon;
|
const Icon = NAV_ICONS[route.path] ?? LayoutGrid;
|
||||||
const isActive = activeModule === item.id;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.button
|
<NavLink
|
||||||
key={item.id}
|
key={route.path}
|
||||||
onClick={() => setActiveModule(item.id)}
|
to={route.path}
|
||||||
className="relative w-full flex items-center rounded-xl transition-colors duration-150"
|
className="block"
|
||||||
style={{
|
|
||||||
height: 44,
|
|
||||||
background: isActive ? 'hsl(var(--accent) / 0.12)' : 'transparent',
|
|
||||||
color: isActive ? 'hsl(var(--accent))' : 'hsl(var(--muted-fg))',
|
|
||||||
}}
|
|
||||||
whileHover={{ x: 2 }}
|
|
||||||
whileTap={{ scale: 0.97 }}
|
|
||||||
onHoverStart={(e) => {
|
|
||||||
if (!isActive) (e.target as HTMLElement).style.background = 'hsl(var(--surface-2))';
|
|
||||||
}}
|
|
||||||
onHoverEnd={(e) => {
|
|
||||||
if (!isActive) (e.target as HTMLElement).style.background = 'transparent';
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{/* Active left bar */}
|
{({ isActive }) => (
|
||||||
{isActive && (
|
|
||||||
<motion.div
|
<motion.div
|
||||||
layoutId="activeBar"
|
className="relative w-full flex items-center rounded-xl transition-colors duration-150 cursor-pointer"
|
||||||
className="absolute left-0 top-2 bottom-2 w-0.5 rounded-full"
|
style={{
|
||||||
style={{ background: 'hsl(var(--accent))' }}
|
height: 44,
|
||||||
transition={{ type: 'spring', stiffness: 400, damping: 30 }}
|
background: isActive ? 'hsl(var(--accent) / 0.12)' : 'transparent',
|
||||||
/>
|
color: isActive ? 'hsl(var(--accent))' : 'hsl(var(--muted-fg))',
|
||||||
|
}}
|
||||||
|
whileHover={{ x: 2 }}
|
||||||
|
whileTap={{ scale: 0.97 }}
|
||||||
|
onHoverStart={(e) => {
|
||||||
|
if (!isActive) (e.target as HTMLElement).style.background = 'hsl(var(--surface-2))';
|
||||||
|
}}
|
||||||
|
onHoverEnd={(e) => {
|
||||||
|
if (!isActive) (e.target as HTMLElement).style.background = 'transparent';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Active left bar */}
|
||||||
|
{isActive && (
|
||||||
|
<motion.div
|
||||||
|
layoutId="activeBar"
|
||||||
|
className="absolute left-0 top-2 bottom-2 w-0.5 rounded-full"
|
||||||
|
style={{ background: 'hsl(var(--accent))' }}
|
||||||
|
transition={{ type: 'spring', stiffness: 400, damping: 30 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="w-11 flex justify-center flex-shrink-0">
|
||||||
|
<Icon className="w-[18px] h-[18px]" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<motion.span
|
||||||
|
className="whitespace-nowrap font-medium text-sm overflow-hidden"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: sidebarExpanded ? 1 : 0 }}
|
||||||
|
transition={{ duration: 0.15, delay: sidebarExpanded ? 0.08 : 0 }}
|
||||||
|
>
|
||||||
|
{route.title}
|
||||||
|
</motion.span>
|
||||||
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
|
</NavLink>
|
||||||
<div className="w-11 flex justify-center flex-shrink-0">
|
|
||||||
<Icon className="w-[18px] h-[18px]" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<motion.span
|
|
||||||
className="whitespace-nowrap font-medium text-sm overflow-hidden"
|
|
||||||
initial={{ opacity: 0 }}
|
|
||||||
animate={{ opacity: sidebarExpanded ? 1 : 0 }}
|
|
||||||
transition={{ duration: 0.15, delay: sidebarExpanded ? 0.08 : 0 }}
|
|
||||||
>
|
|
||||||
{item.label}
|
|
||||||
</motion.span>
|
|
||||||
</motion.button>
|
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
873
app/src/components/modules/Catalyst.tsx
Normal file
873
app/src/components/modules/Catalyst.tsx
Normal file
@@ -0,0 +1,873 @@
|
|||||||
|
import { useState, useEffect, useRef } from 'react';
|
||||||
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
import {
|
||||||
|
Megaphone, Clapperboard, BarChart3, Globe, Settings2,
|
||||||
|
Zap, TrendingUp, Eye, MousePointerClick, DollarSign,
|
||||||
|
Upload, Play, Image, Film, RefreshCw, ArrowRight, Plus,
|
||||||
|
AlertTriangle, ArrowRightLeft, PlusCircle, SlidersHorizontal,
|
||||||
|
Activity, Check, Link2,
|
||||||
|
type LucideIcon,
|
||||||
|
} from 'lucide-react';
|
||||||
|
import {
|
||||||
|
AreaChart, Area, BarChart, Bar,
|
||||||
|
XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer,
|
||||||
|
} from 'recharts';
|
||||||
|
import { useMarketingStore } from '@/store/useMarketingStore';
|
||||||
|
import type { Campaign, MarketingAsset, LiveOptimizationEvent, LiveEventType } from '@/types';
|
||||||
|
|
||||||
|
// ── Design tokens ─────────────────────────────────────────────────────────────
|
||||||
|
const GLASS = {
|
||||||
|
background: 'rgba(8, 10, 18, 0.82)',
|
||||||
|
border: '1px solid rgba(59,130,246,0.14)',
|
||||||
|
backdropFilter: 'blur(24px)',
|
||||||
|
WebkitBackdropFilter: 'blur(24px)',
|
||||||
|
boxShadow: '0 0 0 1px rgba(255,255,255,0.04), 0 4px 32px rgba(0,0,0,0.55)',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
const INNER = {
|
||||||
|
background: 'rgba(255,255,255,0.04)',
|
||||||
|
border: '1px solid rgba(255,255,255,0.07)',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// ── Shared primitives ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function Widget({ children, className = '', delay = 0, colSpan = 1, rowSpan = 1, style }: {
|
||||||
|
children: React.ReactNode; className?: string; delay?: number; colSpan?: number; rowSpan?: number; style?: React.CSSProperties;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
className={`relative rounded-2xl p-5 overflow-hidden ${className}`}
|
||||||
|
style={{ gridColumn: `span ${colSpan}`, gridRow: `span ${rowSpan}`, ...GLASS, ...style }}
|
||||||
|
initial={{ opacity: 0, y: 16, scale: 0.97 }}
|
||||||
|
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||||
|
transition={{ duration: 0.35, delay, ease: [0.4, 0, 0.2, 1] }}
|
||||||
|
>
|
||||||
|
<div className="absolute inset-x-0 top-0 h-px pointer-events-none"
|
||||||
|
style={{ background: 'linear-gradient(90deg, transparent, rgba(255,255,255,0.10), transparent)' }} />
|
||||||
|
{children}
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function GhostButton({ children, onClick, danger = false, className = '' }: {
|
||||||
|
children: React.ReactNode; onClick?: () => void; danger?: boolean; className?: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<motion.button
|
||||||
|
type="button"
|
||||||
|
onClick={onClick}
|
||||||
|
className={`px-4 py-2 rounded-xl text-sm font-medium transition-colors ${className}`}
|
||||||
|
style={danger
|
||||||
|
? { background: 'rgba(239,68,68,0.12)', color: '#f87171', border: '1px solid rgba(239,68,68,0.2)' }
|
||||||
|
: { ...INNER, color: 'rgba(255,255,255,0.8)' }
|
||||||
|
}
|
||||||
|
whileHover={{ scale: 1.02 }}
|
||||||
|
whileTap={{ scale: 0.97 }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</motion.button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DarkInput({ value, onChange, placeholder, type = 'text', readOnly = false }: {
|
||||||
|
value: string; onChange?: (v: string) => void; placeholder?: string; type?: string; readOnly?: boolean;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
type={type}
|
||||||
|
value={value}
|
||||||
|
readOnly={readOnly}
|
||||||
|
onChange={(e) => onChange?.(e.target.value)}
|
||||||
|
placeholder={placeholder}
|
||||||
|
className="rounded-xl px-3 py-2 text-sm text-white w-full focus:outline-none transition-all"
|
||||||
|
style={{ ...INNER, caretColor: 'hsl(var(--accent))' }}
|
||||||
|
onFocus={(e) => { if (!readOnly) e.currentTarget.style.border = '1px solid rgba(59,130,246,0.4)'; }}
|
||||||
|
onBlur={(e) => { e.currentTarget.style.border = '1px solid rgba(255,255,255,0.07)'; }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function LiveBadge() {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-1.5 text-xs font-medium" style={{ color: '#4ade80' }}>
|
||||||
|
<motion.div className="w-1.5 h-1.5 rounded-full bg-green-400"
|
||||||
|
animate={{ opacity: [1, 0.3, 1] }} transition={{ repeat: Infinity, duration: 1.6 }} />
|
||||||
|
Live
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── KPI Stat card ─────────────────────────────────────────────────────────────
|
||||||
|
function StatCard({ icon: Icon, label, value, sub, glowColor = 'rgba(59,130,246,0.2)', delay = 0 }: {
|
||||||
|
icon: LucideIcon; label: string; value: string; sub: string; glowColor?: string; delay?: number;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Widget delay={delay} className="flex flex-col justify-between min-h-[130px]">
|
||||||
|
<div className="absolute -bottom-6 -right-6 w-44 h-44 pointer-events-none"
|
||||||
|
style={{ background: `radial-gradient(ellipse at 70% 80%, ${glowColor} 0%, transparent 65%)`, filter: 'blur(18px)' }} />
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="w-8 h-8 rounded-xl flex items-center justify-center"
|
||||||
|
style={{ background: `${glowColor.replace('0.2', '0.12')}`, border: `1px solid ${glowColor.replace('0.2', '0.25')}` }}>
|
||||||
|
<Icon className="w-4 h-4" style={{ color: glowColor.includes('59,130,246') ? '#60a5fa' : glowColor.includes('34,211,238') ? '#22d3ee' : glowColor.includes('251,191,36') ? '#fbbf24' : '#a78bfa' }} />
|
||||||
|
</div>
|
||||||
|
<LiveBadge />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-xs font-medium uppercase tracking-widest mb-1" style={{ color: 'rgba(148,163,184,0.65)' }}>{label}</p>
|
||||||
|
<motion.p className="text-3xl font-semibold text-white leading-none"
|
||||||
|
initial={{ opacity: 0, scale: 0.7 }} animate={{ opacity: 1, scale: 1 }}
|
||||||
|
transition={{ duration: 0.4, delay: delay + 0.12, type: 'spring', stiffness: 200 }}>
|
||||||
|
{value}
|
||||||
|
</motion.p>
|
||||||
|
<p className="text-xs mt-1" style={{ color: 'rgba(148,163,184,0.5)' }}>{sub}</p>
|
||||||
|
</div>
|
||||||
|
</Widget>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Campaign status badge ─────────────────────────────────────────────────────
|
||||||
|
function CampaignStatusBadge({ status }: { status: Campaign['status'] }) {
|
||||||
|
const map: Record<Campaign['status'], { label: string; color: string; bg: string }> = {
|
||||||
|
ACTIVE: { label: 'Active', color: '#4ade80', bg: 'rgba(74,222,128,0.12)' },
|
||||||
|
PAUSED: { label: 'Paused', color: '#fbbf24', bg: 'rgba(251,191,36,0.12)' },
|
||||||
|
DELETED: { label: 'Deleted', color: '#f87171', bg: 'rgba(248,113,113,0.12)' },
|
||||||
|
ARCHIVED: { label: 'Archived', color: '#94a3b8', bg: 'rgba(148,163,184,0.12)' },
|
||||||
|
};
|
||||||
|
const { label, color, bg } = map[status];
|
||||||
|
return (
|
||||||
|
<span className="px-2 py-0.5 rounded-full text-[11px] font-medium" style={{ color, background: bg }}>{label}</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// TAB 1 — The Studio
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const RENDER_MESSAGES: Record<string, string[]> = {
|
||||||
|
rendering: [
|
||||||
|
'Wan 2.2 is rendering cinematic lighting...',
|
||||||
|
'Wan 2.2 is compositing the infinity pool reflection...',
|
||||||
|
'Qwen-Image 2512 is synthesizing Arabic typography...',
|
||||||
|
'Flux-Schnell is upscaling to 4K resolution...',
|
||||||
|
],
|
||||||
|
queued: [
|
||||||
|
'Qwen-Image 2512 queued for cinematic poster render...',
|
||||||
|
'Wan 2.2 14B queued — GPU resources freeing up...',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
function AssetCard({ asset }: { asset: MarketingAsset }) {
|
||||||
|
const isProcessing = asset.status === 'rendering' || asset.status === 'queued';
|
||||||
|
const [msgIdx, setMsgIdx] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isProcessing) return;
|
||||||
|
const msgs = RENDER_MESSAGES[asset.status] ?? [];
|
||||||
|
if (msgs.length === 0) return;
|
||||||
|
const t = setInterval(() => setMsgIdx((i) => (i + 1) % msgs.length), 2800);
|
||||||
|
return () => clearInterval(t);
|
||||||
|
}, [isProcessing, asset.status]);
|
||||||
|
|
||||||
|
const statusColor: Record<typeof asset.status, string> = {
|
||||||
|
queued: 'rgba(148,163,184,0.8)',
|
||||||
|
rendering: '#fbbf24',
|
||||||
|
ready: '#4ade80',
|
||||||
|
uploaded: '#60a5fa',
|
||||||
|
failed: '#f87171',
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
className="relative rounded-2xl overflow-hidden flex flex-col"
|
||||||
|
style={{ ...INNER, minHeight: 200, border: `1px solid ${statusColor[asset.status]}22` }}
|
||||||
|
whileHover={{ scale: 1.015 }}
|
||||||
|
transition={{ duration: 0.2 }}
|
||||||
|
>
|
||||||
|
{/* Thumbnail / shimmer area */}
|
||||||
|
<div className="relative flex-1 flex items-center justify-center min-h-[120px]"
|
||||||
|
style={{ background: 'rgba(255,255,255,0.02)' }}>
|
||||||
|
{isProcessing ? (
|
||||||
|
<div className="w-full h-full absolute inset-0">
|
||||||
|
<motion.div className="absolute inset-0"
|
||||||
|
style={{ background: 'linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.06) 50%, transparent 100%)' }}
|
||||||
|
animate={{ x: ['-100%', '200%'] }}
|
||||||
|
transition={{ repeat: Infinity, duration: 1.6, ease: 'linear' }} />
|
||||||
|
<div className="absolute inset-0 flex flex-col items-center justify-center gap-2 p-3 text-center">
|
||||||
|
{asset.type === 'video'
|
||||||
|
? <Film className="w-8 h-8 text-amber-400 opacity-60" />
|
||||||
|
: <Image className="w-8 h-8 text-blue-400 opacity-60" />}
|
||||||
|
<motion.p className="text-xs font-medium leading-relaxed"
|
||||||
|
style={{ color: statusColor[asset.status] }}
|
||||||
|
key={msgIdx}
|
||||||
|
initial={{ opacity: 0, y: 4 }} animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.4 }}>
|
||||||
|
{(RENDER_MESSAGES[asset.status] ?? [])[msgIdx] ?? asset.renderMessage}
|
||||||
|
</motion.p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex flex-col items-center justify-center gap-2 py-6">
|
||||||
|
{asset.type === 'video'
|
||||||
|
? <Film className="w-10 h-10 text-blue-400" />
|
||||||
|
: <Image className="w-10 h-10 text-blue-400" />}
|
||||||
|
<span className="text-xs" style={{ color: 'rgba(148,163,184,0.5)' }}>
|
||||||
|
{asset.metaAssetId ? `Meta ID: ${asset.metaAssetId}` : 'Ready for upload'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Info footer */}
|
||||||
|
<div className="px-3 py-2.5" style={{ borderTop: '1px solid rgba(255,255,255,0.05)' }}>
|
||||||
|
<div className="flex items-start justify-between gap-2 mb-1.5">
|
||||||
|
<p className="text-xs font-medium text-white leading-snug line-clamp-2">{asset.name}</p>
|
||||||
|
<span className="text-[10px] font-medium px-1.5 py-0.5 rounded-full flex-shrink-0"
|
||||||
|
style={{ color: statusColor[asset.status], background: `${statusColor[asset.status]}18` }}>
|
||||||
|
{asset.status.toUpperCase()}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-[10px] px-1.5 py-0.5 rounded"
|
||||||
|
style={{ background: 'rgba(255,255,255,0.06)', color: 'rgba(148,163,184,0.6)' }}>
|
||||||
|
{asset.type === 'video' ? 'Wan 2.2' : 'Qwen-2512'}
|
||||||
|
</span>
|
||||||
|
{asset.language && (
|
||||||
|
<span className="text-[10px] px-1.5 py-0.5 rounded uppercase"
|
||||||
|
style={{ background: 'rgba(255,255,255,0.06)', color: 'rgba(148,163,184,0.6)' }}>
|
||||||
|
{asset.language}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function WorkflowInput() {
|
||||||
|
const [mode, setMode] = useState<'image' | 'video'>('image');
|
||||||
|
const [prompt, setPrompt] = useState('');
|
||||||
|
const [keywords, setKeywords] = useState('');
|
||||||
|
const [textCopy, setTextCopy] = useState('');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Widget delay={0.04} colSpan={1} className="!p-6" style={{ background: '#111216', borderRadius: '28px' }}>
|
||||||
|
<div className="flex flex-col gap-6">
|
||||||
|
{/* Top Section: Ground Truth & References */}
|
||||||
|
<div className="flex items-end gap-6">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<button title="Select Image from Gallery or Dream Weaver / Click Image from iPad" className="w-[60px] h-[60px] rounded-2xl flex items-center justify-center transition-colors hover:bg-white/5" style={{ background: 'rgba(255,255,255,0.03)', border: '1px solid rgba(255,255,255,0.08)' }}>
|
||||||
|
<span className="text-xs font-medium" style={{ color: 'rgba(255,255,255,0.2)' }}>Start</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<span className="text-xs font-semibold text-white tracking-wide">Ground Truth</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center h-[60px] mb-[26px]">
|
||||||
|
<Plus className="w-5 h-5 text-white" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<button title="Add reference image" className="w-[60px] h-[60px] rounded-2xl flex items-center justify-center transition-colors hover:bg-white/5" style={{ background: 'rgba(255,255,255,0.03)', border: '1px solid rgba(255,255,255,0.08)' }} />
|
||||||
|
<button title="Add reference image" className="w-[60px] h-[60px] rounded-2xl flex items-center justify-center transition-colors hover:bg-white/5" style={{ background: 'rgba(255,255,255,0.03)', border: '1px solid rgba(255,255,255,0.08)' }} />
|
||||||
|
<button title="Add more references" className="w-[60px] h-[60px] rounded-2xl flex items-center justify-center transition-colors hover:bg-white/5" style={{ background: 'rgba(255,255,255,0.03)', border: '1px solid rgba(255,255,255,0.08)' }}>
|
||||||
|
<Plus className="w-5 h-5 text-white/40" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<span className="text-xs font-semibold text-white tracking-wide">References</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Bottom Section: Input & Controls */}
|
||||||
|
<div className="flex items-end justify-between">
|
||||||
|
<div className="flex-1 flex flex-col gap-3">
|
||||||
|
<textarea
|
||||||
|
value={prompt}
|
||||||
|
onChange={(e) => setPrompt(e.target.value)}
|
||||||
|
placeholder="What do you want to create?"
|
||||||
|
className="w-full bg-transparent text-xl font-medium text-white placeholder-white/20 resize-none outline-none leading-relaxed"
|
||||||
|
rows={1}
|
||||||
|
/>
|
||||||
|
{/* Extra inputs for keywords and text copy as requested */}
|
||||||
|
<div className="flex items-center gap-3 pt-2" style={{ borderTop: '1px solid rgba(255,255,255,0.05)' }}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={keywords}
|
||||||
|
onChange={(e) => setKeywords(e.target.value)}
|
||||||
|
placeholder="Keywords (comma separated)"
|
||||||
|
className="bg-transparent text-sm text-white/80 placeholder-white/20 outline-none flex-1 min-w-0"
|
||||||
|
/>
|
||||||
|
<span className="text-white/10">|</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={textCopy}
|
||||||
|
onChange={(e) => setTextCopy(e.target.value)}
|
||||||
|
placeholder='Text copy inside ""'
|
||||||
|
className="bg-transparent text-sm text-white/80 placeholder-white/20 outline-none flex-1 min-w-0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-4 ml-6 pl-4">
|
||||||
|
{/* Toggle */}
|
||||||
|
<div className="flex items-center rounded-full p-1" style={{ background: 'rgba(255,255,255,0.03)', border: '1px solid rgba(255,255,255,0.08)' }}>
|
||||||
|
<button
|
||||||
|
onClick={() => setMode('image')}
|
||||||
|
className={`px-5 py-2.5 rounded-full text-sm font-semibold transition-all ${mode === 'image' ? 'text-black shadow-lg' : 'text-white/60'}`}
|
||||||
|
style={{ background: mode === 'image' ? '#22c55e' : 'transparent' }}
|
||||||
|
>
|
||||||
|
Image
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setMode('video')}
|
||||||
|
className={`px-5 py-2.5 rounded-full text-sm font-semibold transition-all ${mode === 'video' ? 'text-black shadow-lg' : 'text-white/60'}`}
|
||||||
|
style={{ background: mode === 'video' ? '#22c55e' : 'transparent' }}
|
||||||
|
>
|
||||||
|
Video
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Send Button */}
|
||||||
|
<button className="w-[44px] h-[44px] rounded-full bg-zinc-200 flex items-center justify-center hover:bg-white transition-colors flex-shrink-0">
|
||||||
|
<ArrowRight className="w-5 h-5 text-black" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Widget>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TheStudio() {
|
||||||
|
const { activeAssets } = useMarketingStore();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* Header row */}
|
||||||
|
<Widget delay={0} colSpan={1}>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-sm font-semibold text-white mb-0.5 flex items-center gap-2">
|
||||||
|
<Clapperboard className="w-4 h-4 text-blue-400" /> Visual AI Engine
|
||||||
|
</h3>
|
||||||
|
<p className="text-xs" style={{ color: 'rgba(148,163,184,0.55)' }}>
|
||||||
|
ComfyUI workflows (Wan 2.2 & Qwen-Image 2512) feed directly into the Meta Asset Library
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<GhostButton>
|
||||||
|
<span className="flex items-center gap-1.5"><Upload className="w-3.5 h-3.5" /> Sync to Meta</span>
|
||||||
|
</GhostButton>
|
||||||
|
<GhostButton>
|
||||||
|
<span className="flex items-center gap-1.5"><Play className="w-3.5 h-3.5 text-blue-400" /> Trigger Render</span>
|
||||||
|
</GhostButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Widget>
|
||||||
|
|
||||||
|
{/* ComfyUI Workflow Input Panel */}
|
||||||
|
<WorkflowInput />
|
||||||
|
|
||||||
|
{/* Asset grid */}
|
||||||
|
<div className="grid grid-cols-2 xl:grid-cols-4 gap-4">
|
||||||
|
{activeAssets.map((asset, i) => (
|
||||||
|
<motion.div key={asset.id}
|
||||||
|
initial={{ opacity: 0, y: 14 }} animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.3, delay: i * 0.07 }}>
|
||||||
|
<AssetCard asset={asset} />
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// TAB 2 — Campaign Command
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function CampaignCommand() {
|
||||||
|
const { campaigns, adInsights } = useMarketingStore();
|
||||||
|
|
||||||
|
const totalSpend = campaigns.reduce((s, c) => s + c.lifetimeSpend / 100, 0);
|
||||||
|
const totalImpr = campaigns.reduce((s, c) => s + c.impressions, 0);
|
||||||
|
const avgCtr = campaigns.reduce((s, c) => s + c.ctr, 0) / campaigns.length;
|
||||||
|
const avgCpa = campaigns.reduce((s, c) => s + c.cpa, 0) / campaigns.length;
|
||||||
|
|
||||||
|
// Build spend-over-time from insights (last 14 days)
|
||||||
|
const spendData = adInsights.slice(0, 14).map((d) => ({
|
||||||
|
date: d.date.slice(5), // MM-DD
|
||||||
|
spend: d.spend,
|
||||||
|
impressions: Math.round(d.impressions / 1000),
|
||||||
|
})).reverse();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-5">
|
||||||
|
{/* KPI row */}
|
||||||
|
<div className="grid grid-cols-2 xl:grid-cols-4 gap-4">
|
||||||
|
<StatCard icon={Megaphone} label="Active Campaigns" value={String(campaigns.filter(c => c.status === 'ACTIVE').length)} sub={`${campaigns.length} total campaigns`} glowColor="rgba(59,130,246,0.2)" delay={0} />
|
||||||
|
<StatCard icon={DollarSign} label="Total Spend" value={`AED ${(totalSpend / 1000).toFixed(1)}K`} sub="Lifetime across all campaigns" glowColor="rgba(34,211,238,0.2)" delay={0.06} />
|
||||||
|
<StatCard icon={Eye} label="Impressions" value={`${(totalImpr / 1000).toFixed(0)}K`} sub="Total ad impressions" glowColor="rgba(251,191,36,0.2)" delay={0.12} />
|
||||||
|
<StatCard icon={MousePointerClick} label="Avg CTR" value={`${avgCtr.toFixed(2)}%`} sub={`Avg CPA: AED ${avgCpa.toFixed(0)}`} glowColor="rgba(167,139,250,0.2)" delay={0.18} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Spend chart + campaign list */}
|
||||||
|
<div className="grid grid-cols-1 xl:grid-cols-2 gap-4">
|
||||||
|
{/* Spend over time chart */}
|
||||||
|
<Widget delay={0.24} colSpan={1}>
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<h3 className="text-sm font-semibold text-white flex items-center gap-2">
|
||||||
|
<TrendingUp className="w-4 h-4 text-blue-400" /> Spend vs Impressions
|
||||||
|
</h3>
|
||||||
|
<LiveBadge />
|
||||||
|
</div>
|
||||||
|
<div className="h-52">
|
||||||
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
|
<AreaChart data={spendData} margin={{ top: 4, right: 4, left: -20, bottom: 0 }}>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="gSpend" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="5%" stopColor="#3b82f6" stopOpacity={0.35} />
|
||||||
|
<stop offset="95%" stopColor="#3b82f6" stopOpacity={0} />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="gImpr" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="5%" stopColor="#22d3ee" stopOpacity={0.28} />
|
||||||
|
<stop offset="95%" stopColor="#22d3ee" stopOpacity={0} />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<CartesianGrid strokeDasharray="3 3" stroke="rgba(255,255,255,0.04)" />
|
||||||
|
<XAxis dataKey="date" stroke="rgba(148,163,184,0.3)" fontSize={10} tickLine={false} axisLine={false} />
|
||||||
|
<YAxis stroke="rgba(148,163,184,0.3)" fontSize={10} tickLine={false} axisLine={false} />
|
||||||
|
<Tooltip contentStyle={{ backgroundColor: 'rgba(8,10,18,0.92)', border: '1px solid rgba(59,130,246,0.2)', borderRadius: 10 }} labelStyle={{ color: 'rgba(148,163,184,0.8)', fontSize: 11 }} itemStyle={{ color: '#93c5fd', fontSize: 12 }} />
|
||||||
|
<Area type="monotone" dataKey="spend" stroke="#3b82f6" strokeWidth={2} fillOpacity={1} fill="url(#gSpend)" />
|
||||||
|
<Area type="monotone" dataKey="impressions" stroke="#22d3ee" strokeWidth={2} fillOpacity={1} fill="url(#gImpr)" />
|
||||||
|
</AreaChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
</Widget>
|
||||||
|
|
||||||
|
{/* Campaign list */}
|
||||||
|
<Widget delay={0.28} colSpan={1}>
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<h3 className="text-sm font-semibold text-white flex items-center gap-2">
|
||||||
|
<SlidersHorizontal className="w-4 h-4 text-blue-400" /> Campaigns
|
||||||
|
</h3>
|
||||||
|
<GhostButton>
|
||||||
|
<span className="flex items-center gap-1.5 text-xs"><PlusCircle className="w-3.5 h-3.5" /> New</span>
|
||||||
|
</GhostButton>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
{campaigns.map((c) => (
|
||||||
|
<motion.div key={c.id} className="flex items-center justify-between p-3 rounded-xl" style={INNER}
|
||||||
|
whileHover={{ scale: 1.01 }} transition={{ duration: 0.15 }}>
|
||||||
|
<div className="flex-1 min-w-0 mr-3">
|
||||||
|
<p className="text-sm font-medium text-white truncate">{c.name}</p>
|
||||||
|
<p className="text-xs mt-0.5" style={{ color: 'rgba(148,163,184,0.5)' }}>
|
||||||
|
AED {(c.lifetimeSpend / 100).toLocaleString()} · {c.impressions.toLocaleString()} impr.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 flex-shrink-0">
|
||||||
|
<span className="text-xs font-medium" style={{ color: c.roi > 20 ? '#4ade80' : '#fbbf24' }}>
|
||||||
|
{c.roi.toFixed(1)}% ROI
|
||||||
|
</span>
|
||||||
|
<CampaignStatusBadge status={c.status} />
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Widget>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// TAB 3 — Intelligence & ROI
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function IntelligenceROI() {
|
||||||
|
const { campaigns, adInsights } = useMarketingStore();
|
||||||
|
|
||||||
|
const cpaData = adInsights.slice(0, 7).map((d) => ({
|
||||||
|
date: d.date.slice(5),
|
||||||
|
cpa: d.cpa,
|
||||||
|
roi: d.roi,
|
||||||
|
})).reverse();
|
||||||
|
|
||||||
|
const adSetPerf = [
|
||||||
|
{ name: '3BHK Dubai', ctr: 2.1, cpa: 210, spend: 3400 },
|
||||||
|
{ name: 'PH Retarget', ctr: 3.2, cpa: 2100, spend: 5800 },
|
||||||
|
{ name: '1BHK Lookalike', ctr: 1.8, cpa: 270, spend: 980 },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-5">
|
||||||
|
{/* CPA / ROI KPIs */}
|
||||||
|
<div className="grid grid-cols-2 xl:grid-cols-4 gap-4">
|
||||||
|
<StatCard icon={DollarSign} label="Avg CPA" value={`AED ${(campaigns.reduce((s,c)=>s+c.cpa,0)/campaigns.length).toFixed(0)}`} sub="Cost per acquisition" glowColor="rgba(34,211,238,0.2)" delay={0} />
|
||||||
|
<StatCard icon={TrendingUp} label="Portfolio ROI" value={`${(campaigns.reduce((s,c)=>s+c.roi,0)/campaigns.length).toFixed(1)}%`} sub="Blended across all ad sets" glowColor="rgba(74,222,128,0.2)" delay={0.06} />
|
||||||
|
<StatCard icon={Activity} label="Avg CTR" value={`${(campaigns.reduce((s,c)=>s+c.ctr,0)/campaigns.length).toFixed(2)}%`} sub="Click-through rate" glowColor="rgba(167,139,250,0.2)" delay={0.12} />
|
||||||
|
<StatCard icon={Zap} label="Optimization Runs" value="47" sub="Agent actions today" glowColor="rgba(251,191,36,0.2)" delay={0.18} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 xl:grid-cols-2 gap-4">
|
||||||
|
{/* CPA trend */}
|
||||||
|
<Widget delay={0.24}>
|
||||||
|
<h3 className="text-sm font-semibold text-white mb-4 flex items-center gap-2">
|
||||||
|
<BarChart3 className="w-4 h-4 text-cyan-400" /> CPA Trend (7 Days)
|
||||||
|
</h3>
|
||||||
|
<div className="h-52">
|
||||||
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
|
<AreaChart data={cpaData} margin={{ top: 4, right: 4, left: -20, bottom: 0 }}>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="gCpa" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="5%" stopColor="#22d3ee" stopOpacity={0.35} />
|
||||||
|
<stop offset="95%" stopColor="#22d3ee" stopOpacity={0} />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<CartesianGrid strokeDasharray="3 3" stroke="rgba(255,255,255,0.04)" />
|
||||||
|
<XAxis dataKey="date" stroke="rgba(148,163,184,0.3)" fontSize={10} tickLine={false} axisLine={false} />
|
||||||
|
<YAxis stroke="rgba(148,163,184,0.3)" fontSize={10} tickLine={false} axisLine={false} />
|
||||||
|
<Tooltip contentStyle={{ backgroundColor: 'rgba(8,10,18,0.92)', border: '1px solid rgba(34,211,238,0.2)', borderRadius: 10 }} labelStyle={{ color: 'rgba(148,163,184,0.8)', fontSize: 11 }} itemStyle={{ color: '#67e8f9', fontSize: 12 }} />
|
||||||
|
<Area type="monotone" dataKey="cpa" stroke="#22d3ee" strokeWidth={2} fillOpacity={1} fill="url(#gCpa)" name="CPA (AED)" />
|
||||||
|
</AreaChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
</Widget>
|
||||||
|
|
||||||
|
{/* Ad-set CPA bar chart */}
|
||||||
|
<Widget delay={0.28}>
|
||||||
|
<h3 className="text-sm font-semibold text-white mb-4 flex items-center gap-2">
|
||||||
|
<BarChart3 className="w-4 h-4 text-blue-400" /> Ad Set Performance
|
||||||
|
</h3>
|
||||||
|
<div className="h-52">
|
||||||
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
|
<BarChart data={adSetPerf} margin={{ top: 4, right: 4, left: -20, bottom: 0 }}>
|
||||||
|
<CartesianGrid strokeDasharray="3 3" stroke="rgba(255,255,255,0.04)" />
|
||||||
|
<XAxis dataKey="name" stroke="rgba(148,163,184,0.3)" fontSize={10} tickLine={false} axisLine={false} />
|
||||||
|
<YAxis stroke="rgba(148,163,184,0.3)" fontSize={10} tickLine={false} axisLine={false} />
|
||||||
|
<Tooltip contentStyle={{ backgroundColor: 'rgba(8,10,18,0.92)', border: '1px solid rgba(59,130,246,0.2)', borderRadius: 10 }} labelStyle={{ color: 'rgba(148,163,184,0.8)', fontSize: 11 }} itemStyle={{ color: '#93c5fd', fontSize: 12 }} />
|
||||||
|
<Bar dataKey="spend" fill="#3b82f6" radius={[4, 4, 0, 0]} name="Spend (AED)" opacity={0.8} />
|
||||||
|
</BarChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
</Widget>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Live Optimization Feed (shared across tabs)
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const EVENT_ICON: Record<LiveEventType, { icon: LucideIcon; color: string; bg: string; label: string }> = {
|
||||||
|
pause: { icon: AlertTriangle, color: '#fbbf24', bg: 'rgba(251,191,36,0.12)', label: 'Paused' },
|
||||||
|
shift: { icon: ArrowRightLeft, color: '#60a5fa', bg: 'rgba(96,165,250,0.12)', label: 'Budget Shift' },
|
||||||
|
create: { icon: PlusCircle, color: '#4ade80', bg: 'rgba(74,222,128,0.12)', label: 'Created' },
|
||||||
|
optimize: { icon: Zap, color: '#a78bfa', bg: 'rgba(167,139,250,0.12)', label: 'Optimized' },
|
||||||
|
alert: { icon: AlertTriangle, color: '#f87171', bg: 'rgba(248,113,113,0.12)', label: 'Alert' },
|
||||||
|
rotate: { icon: RefreshCw, color: '#22d3ee', bg: 'rgba(34,211,238,0.12)', label: 'Rotated' },
|
||||||
|
};
|
||||||
|
|
||||||
|
function LiveEventItem({ event }: { event: LiveOptimizationEvent }) {
|
||||||
|
const cfg = EVENT_ICON[event.type];
|
||||||
|
const Icon = cfg.icon;
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
className="flex items-start gap-3 p-3 rounded-xl"
|
||||||
|
style={INNER}
|
||||||
|
initial={{ opacity: 0, x: -12, scale: 0.97 }}
|
||||||
|
animate={{ opacity: 1, x: 0, scale: 1 }}
|
||||||
|
exit={{ opacity: 0, x: 12, scale: 0.95 }}
|
||||||
|
transition={{ duration: 0.3, ease: [0.4, 0, 0.2, 1] }}
|
||||||
|
layout
|
||||||
|
>
|
||||||
|
<div className="w-7 h-7 rounded-lg flex items-center justify-center flex-shrink-0"
|
||||||
|
style={{ background: cfg.bg }}>
|
||||||
|
<Icon className="w-3.5 h-3.5" style={{ color: cfg.color }} />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<div className="flex items-center gap-2 mb-0.5">
|
||||||
|
<span className="text-[10px] font-semibold uppercase tracking-wider" style={{ color: cfg.color }}>{cfg.label}</span>
|
||||||
|
{event.campaignName && (
|
||||||
|
<span className="text-[10px] px-1.5 py-px rounded" style={{ background: 'rgba(255,255,255,0.05)', color: 'rgba(148,163,184,0.6)' }}>
|
||||||
|
{event.campaignName}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{event.value && (
|
||||||
|
<span className="text-[10px] font-medium ml-auto" style={{ color: cfg.color }}>{event.value}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<p className="text-xs leading-relaxed" style={{ color: 'rgba(148,163,184,0.75)' }}>{event.message}</p>
|
||||||
|
<p className="text-[10px] mt-1" style={{ color: 'rgba(148,163,184,0.35)' }}>
|
||||||
|
{event.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const MOCK_STREAM: Array<{ type: LiveEventType; message: string; campaign: string; value?: string }> = [
|
||||||
|
{ type: 'optimize', message: 'Expanded 3BHK audience targeting — added "Property Investment" interest layer.', campaign: '3BHK Prestige Launch', value: '+22k reach' },
|
||||||
|
{ type: 'rotate', message: 'Rotated in Arabic Poster (Qwen-2512) as new creative variant for A/B test.', campaign: 'Penthouse Whale Retarget' },
|
||||||
|
{ type: 'shift', message: 'Shifted AED 150 from underperforming Ad Set C to Ad Set A (CTR 3.2%).', campaign: '1BHK Investment', value: '+AED 150' },
|
||||||
|
{ type: 'pause', message: 'Paused Ad Set D — CPA crossed AED 480 threshold (target: AED 400).', campaign: 'Penthouse Whale Retarget', value: 'CPA: AED 481' },
|
||||||
|
{ type: 'create', message: 'Created new Custom Audience from 18 Closed/Won CRM leads (hashed emails).', campaign: '3BHK Prestige Launch' },
|
||||||
|
];
|
||||||
|
|
||||||
|
function LiveOptimizationFeed() {
|
||||||
|
const { liveEvents, pushLiveEvent } = useMarketingStore();
|
||||||
|
const streamIdx = useRef(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const t = setInterval(() => {
|
||||||
|
const item = MOCK_STREAM[streamIdx.current % MOCK_STREAM.length];
|
||||||
|
streamIdx.current++;
|
||||||
|
pushLiveEvent({
|
||||||
|
id: `ev_${Date.now()}`,
|
||||||
|
type: item.type,
|
||||||
|
message: item.message,
|
||||||
|
campaignName: item.campaign,
|
||||||
|
timestamp: new Date(),
|
||||||
|
value: item.value,
|
||||||
|
});
|
||||||
|
}, 4000);
|
||||||
|
return () => clearInterval(t);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Widget delay={0.4} colSpan={1}>
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<h3 className="text-sm font-semibold text-white flex items-center gap-2">
|
||||||
|
<Activity className="w-4 h-4 text-blue-400" /> Live Optimization Feed
|
||||||
|
</h3>
|
||||||
|
<LiveBadge />
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2 max-h-72 overflow-y-auto custom-scrollbar pr-1">
|
||||||
|
<AnimatePresence mode="popLayout" initial={false}>
|
||||||
|
{liveEvents.slice(0, 8).map((ev) => (
|
||||||
|
<LiveEventItem key={ev.id} event={ev} />
|
||||||
|
))}
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
|
</Widget>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// TAB 4 — War Room (Meta Graph Settings)
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function WarRoom() {
|
||||||
|
const { settings, setMetaSettings } = useMarketingStore();
|
||||||
|
const [saved, setSaved] = useState(false);
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
setMetaSettings({ isConnected: true });
|
||||||
|
setSaved(true);
|
||||||
|
setTimeout(() => setSaved(false), 2500);
|
||||||
|
};
|
||||||
|
|
||||||
|
const fields: Array<{ key: keyof typeof settings; label: string; desc: string; placeholder: string; type?: string }> = [
|
||||||
|
{ key: 'metaAccessToken', label: 'Meta System User Access Token', desc: 'Long-lived system user token from Meta Business Manager', placeholder: 'EAAxxxxxxxx...' , type: 'password' },
|
||||||
|
{ key: 'metaAdAccountId', label: 'Ad Account ID', desc: 'Format: act_XXXXXXXXXX', placeholder: 'act_123456789' },
|
||||||
|
{ key: 'metaBusinessId', label: 'Business Portfolio ID', desc: 'Found in Meta Business Settings → Business Info', placeholder: '1234567890' },
|
||||||
|
{ key: 'metaAppId', label: 'App ID', desc: 'From Meta Developers → Your App', placeholder: '9876543210' },
|
||||||
|
{ key: 'whatsappPhoneNumberId',label: 'WhatsApp Phone Number ID', desc: 'Required for WhatsApp Business API integration', placeholder: '1098765432' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const assetLinks = [
|
||||||
|
{ label: 'Instagram Business Page', href: '#', icon: Link2 },
|
||||||
|
{ label: 'Facebook Business Page', href: '#', icon: Link2 },
|
||||||
|
{ label: 'WhatsApp Business Account', href: '#', icon: Link2 },
|
||||||
|
{ label: 'Ad Library (Creative Hub)', href: '#', icon: Link2 },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-5">
|
||||||
|
{/* Connection status banner */}
|
||||||
|
<Widget delay={0} colSpan={1}>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="relative">
|
||||||
|
<div className={`w-3 h-3 rounded-full ${settings.isConnected ? 'bg-green-500' : 'bg-zinc-600'}`} />
|
||||||
|
{settings.isConnected && <div className="absolute inset-0 rounded-full bg-green-500 status-pulse" />}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-medium text-white">Meta Graph API</p>
|
||||||
|
<p className="text-xs" style={{ color: 'rgba(148,163,184,0.5)' }}>
|
||||||
|
{settings.isConnected ? 'Connected — System User Token Active' : 'Not connected — enter credentials below'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Globe className="w-4 h-4" style={{ color: 'rgba(148,163,184,0.4)' }} />
|
||||||
|
<span className="text-xs font-medium" style={{ color: settings.isConnected ? '#4ade80' : 'rgba(148,163,184,0.4)' }}>
|
||||||
|
{settings.isConnected ? 'ONLINE' : 'OFFLINE'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Widget>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 xl:grid-cols-2 gap-4">
|
||||||
|
{/* API Credentials */}
|
||||||
|
<Widget delay={0.08}>
|
||||||
|
<div className="flex items-center gap-2 mb-4">
|
||||||
|
<Settings2 className="w-4 h-4 text-blue-400" />
|
||||||
|
<h3 className="text-sm font-semibold text-white">API Credentials</h3>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-3">
|
||||||
|
{fields.map(({ key, label, desc, placeholder, type }) => (
|
||||||
|
<div key={key}>
|
||||||
|
<p className="text-xs font-medium text-white mb-0.5">{label}</p>
|
||||||
|
<p className="text-[10px] mb-1.5" style={{ color: 'rgba(148,163,184,0.5)' }}>{desc}</p>
|
||||||
|
<DarkInput
|
||||||
|
type={type ?? 'text'}
|
||||||
|
value={String(settings[key])}
|
||||||
|
onChange={(v) => setMetaSettings({ [key]: v } as Partial<typeof settings>)}
|
||||||
|
placeholder={placeholder}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<motion.button
|
||||||
|
type="button"
|
||||||
|
className="w-full py-2.5 mt-2 rounded-xl text-sm font-semibold flex items-center justify-center gap-2 transition-all"
|
||||||
|
style={{ background: saved ? 'rgba(74,222,128,0.15)' : 'hsl(var(--accent))', color: saved ? '#4ade80' : 'hsl(var(--accent-fg))' }}
|
||||||
|
onClick={handleSave}
|
||||||
|
whileHover={{ scale: 1.02 }}
|
||||||
|
whileTap={{ scale: 0.97 }}
|
||||||
|
>
|
||||||
|
{saved ? <><Check className="w-4 h-4" /> Saved & Connected</> : 'Save & Connect to Meta'}
|
||||||
|
</motion.button>
|
||||||
|
</div>
|
||||||
|
</Widget>
|
||||||
|
|
||||||
|
{/* Business Asset Links */}
|
||||||
|
<Widget delay={0.12}>
|
||||||
|
<div className="flex items-center gap-2 mb-4">
|
||||||
|
<Link2 className="w-4 h-4 text-blue-400" />
|
||||||
|
<h3 className="text-sm font-semibold text-white">Business Asset Management</h3>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs mb-4" style={{ color: 'rgba(148,163,184,0.55)' }}>
|
||||||
|
Link WhatsApp, Instagram, and Facebook Pages to your Meta Business Portfolio for unified campaign management.
|
||||||
|
</p>
|
||||||
|
<div className="space-y-2">
|
||||||
|
{assetLinks.map(({ label, icon: Icon }) => (
|
||||||
|
<div key={label} className="flex items-center justify-between p-3 rounded-xl" style={INNER}>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Icon className="w-4 h-4 text-blue-400" />
|
||||||
|
<span className="text-sm text-white">{label}</span>
|
||||||
|
</div>
|
||||||
|
<GhostButton>
|
||||||
|
<span className="text-xs">Configure</span>
|
||||||
|
</GhostButton>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Permissions */}
|
||||||
|
<div className="mt-4 p-3 rounded-xl" style={{ background: 'rgba(59,130,246,0.06)', border: '1px solid rgba(59,130,246,0.15)' }}>
|
||||||
|
<p className="text-xs font-medium text-blue-300 mb-1">Required Scopes</p>
|
||||||
|
{['ads_management', 'ads_read', 'business_management', 'pages_manage_ads', 'whatsapp_business_management'].map((scope) => (
|
||||||
|
<div key={scope} className="flex items-center gap-2 py-0.5">
|
||||||
|
<Check className="w-3 h-3 text-blue-400" />
|
||||||
|
<span className="text-[11px] font-mono" style={{ color: 'rgba(148,163,184,0.7)' }}>{scope}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Widget>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Tab Bar
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
type TabId = 'studio' | 'command' | 'intelligence' | 'war-room';
|
||||||
|
|
||||||
|
const TABS: Array<{ id: TabId; label: string; icon: LucideIcon }> = [
|
||||||
|
{ id: 'studio', label: 'The Studio', icon: Clapperboard },
|
||||||
|
{ id: 'command', label: 'Campaign Command', icon: Megaphone },
|
||||||
|
{ id: 'intelligence', label: 'Intelligence & ROI', icon: BarChart3 },
|
||||||
|
{ id: 'war-room', label: 'War Room', icon: Globe },
|
||||||
|
];
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Root export
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export function Catalyst() {
|
||||||
|
const { activeTab, setActiveTab } = useMarketingStore();
|
||||||
|
|
||||||
|
const panels: Record<TabId, React.ReactNode> = {
|
||||||
|
'studio': <TheStudio />,
|
||||||
|
'command': <CampaignCommand />,
|
||||||
|
'intelligence': <IntelligenceROI />,
|
||||||
|
'war-room': <WarRoom />,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="space-y-5 pb-8">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-xl font-semibold text-white tracking-tight flex items-center gap-2">
|
||||||
|
<Megaphone className="w-5 h-5 text-blue-400" /> The Catalyst
|
||||||
|
</h2>
|
||||||
|
<p className="text-xs mt-0.5" style={{ color: 'rgba(148,163,184,0.5)' }}>
|
||||||
|
Autonomous Deployment Engine · Meta Marketing API
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<motion.div animate={{ opacity: [1, 0.4, 1] }} transition={{ repeat: Infinity, duration: 2 }}
|
||||||
|
className="w-1.5 h-1.5 rounded-full bg-blue-400" />
|
||||||
|
<span className="text-xs font-medium" style={{ color: '#60a5fa' }}>Claw Agent Active</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Tab bar */}
|
||||||
|
<div className="flex items-center gap-1 p-1 rounded-2xl w-fit" style={{ background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(255,255,255,0.07)' }}>
|
||||||
|
{TABS.map((tab) => {
|
||||||
|
const Icon = tab.icon;
|
||||||
|
const isActive = activeTab === tab.id;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={tab.id}
|
||||||
|
onClick={() => setActiveTab(tab.id)}
|
||||||
|
className="relative flex items-center gap-2 px-4 py-2 rounded-xl text-sm font-medium transition-colors outline-none"
|
||||||
|
style={{ color: isActive ? 'white' : 'rgba(148,163,184,0.6)' }}
|
||||||
|
>
|
||||||
|
{isActive && (
|
||||||
|
<motion.div layoutId="catalyst-tab-bg" className="absolute inset-0 rounded-xl"
|
||||||
|
style={{ background: 'rgba(59,130,246,0.15)', border: '1px solid rgba(59,130,246,0.25)' }}
|
||||||
|
transition={{ type: 'spring', stiffness: 380, damping: 30 }} />
|
||||||
|
)}
|
||||||
|
<Icon className="w-4 h-4 relative z-10" style={{ color: isActive ? '#60a5fa' : 'rgba(148,163,184,0.5)' }} />
|
||||||
|
<span className="relative z-10">{tab.label}</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Tab panel */}
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
<motion.div
|
||||||
|
key={activeTab}
|
||||||
|
initial={{ opacity: 0, y: 10 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, y: -8 }}
|
||||||
|
transition={{ duration: 0.25, ease: [0.4, 0, 0.2, 1] }}
|
||||||
|
>
|
||||||
|
{panels[activeTab]}
|
||||||
|
</motion.div>
|
||||||
|
</AnimatePresence>
|
||||||
|
|
||||||
|
{/* Live Optimization Feed — always visible */}
|
||||||
|
<LiveOptimizationFeed />
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
import { StrictMode } from 'react'
|
import { StrictMode } from 'react'
|
||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from 'react-dom/client'
|
||||||
|
import { BrowserRouter } from 'react-router-dom'
|
||||||
import './index.css'
|
import './index.css'
|
||||||
import App from './App.tsx'
|
import App from './App.tsx'
|
||||||
|
|
||||||
createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById('root')!).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<App />
|
<BrowserRouter>
|
||||||
|
<App />
|
||||||
|
</BrowserRouter>
|
||||||
</StrictMode>,
|
</StrictMode>,
|
||||||
)
|
)
|
||||||
|
|||||||
214
app/src/store/useMarketingStore.ts
Normal file
214
app/src/store/useMarketingStore.ts
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
import type {
|
||||||
|
Campaign,
|
||||||
|
MarketingAsset,
|
||||||
|
AdInsight,
|
||||||
|
LiveOptimizationEvent,
|
||||||
|
CatalystSettings,
|
||||||
|
LiveEventType,
|
||||||
|
} from '@/types';
|
||||||
|
|
||||||
|
// ── Mock Data ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const mockCampaigns: Campaign[] = [
|
||||||
|
{
|
||||||
|
id: 'c1',
|
||||||
|
name: '3BHK Prestige Launch — Dubai Marina',
|
||||||
|
objective: 'OUTCOME_LEADS',
|
||||||
|
status: 'ACTIVE',
|
||||||
|
dailyBudget: 50000, // AED 500
|
||||||
|
lifetimeSpend: 2340000,
|
||||||
|
impressions: 487200,
|
||||||
|
clicks: 9744,
|
||||||
|
ctr: 2.0,
|
||||||
|
cpa: 240,
|
||||||
|
roi: 18.5,
|
||||||
|
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 7),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'c2',
|
||||||
|
name: 'Penthouse Whale Retarget — Instagram',
|
||||||
|
objective: 'OUTCOME_SALES',
|
||||||
|
status: 'ACTIVE',
|
||||||
|
dailyBudget: 100000, // AED 1000
|
||||||
|
lifetimeSpend: 5800000,
|
||||||
|
impressions: 92400,
|
||||||
|
clicks: 2772,
|
||||||
|
ctr: 3.0,
|
||||||
|
cpa: 2094,
|
||||||
|
roi: 42.1,
|
||||||
|
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 14),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'c3',
|
||||||
|
name: '1BHK Investment — Lookalike Audience',
|
||||||
|
objective: 'OUTCOME_TRAFFIC',
|
||||||
|
status: 'PAUSED',
|
||||||
|
dailyBudget: 25000, // AED 250
|
||||||
|
lifetimeSpend: 980000,
|
||||||
|
impressions: 213000,
|
||||||
|
clicks: 4260,
|
||||||
|
ctr: 2.0,
|
||||||
|
cpa: 230,
|
||||||
|
roi: 8.2,
|
||||||
|
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 3),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockAssets: MarketingAsset[] = [
|
||||||
|
{
|
||||||
|
id: 'a1',
|
||||||
|
name: 'Penthouse Cinematic — Sea View',
|
||||||
|
type: 'video',
|
||||||
|
status: 'ready',
|
||||||
|
localUrl: '/assets/renders/penthouse_wan22_001.mp4',
|
||||||
|
metaAssetId: 'meta_vid_83920',
|
||||||
|
language: 'en',
|
||||||
|
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 2),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'a2',
|
||||||
|
name: 'Arabic Poster — 3BHK (Qwen-2512)',
|
||||||
|
type: 'image',
|
||||||
|
status: 'uploaded',
|
||||||
|
localUrl: '/assets/renders/3bhk_qwen_ar.png',
|
||||||
|
metaAssetId: 'meta_img_74811',
|
||||||
|
language: 'ar',
|
||||||
|
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 5),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'a3',
|
||||||
|
name: 'Amenity Deck Reel — Wan 2.2 14B',
|
||||||
|
type: 'video',
|
||||||
|
status: 'rendering',
|
||||||
|
renderMessage: 'Wan 2.2 is compositing the infinity pool reflection...',
|
||||||
|
language: 'en',
|
||||||
|
createdAt: new Date(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'a4',
|
||||||
|
name: 'English Poster — Penthouse Launch',
|
||||||
|
type: 'image',
|
||||||
|
status: 'queued',
|
||||||
|
renderMessage: 'Qwen-Image 2512 queued for cinematic poster render...',
|
||||||
|
language: 'en',
|
||||||
|
createdAt: new Date(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockInsights: AdInsight[] = Array.from({ length: 14 }, (_, i) => {
|
||||||
|
const d = new Date(Date.now() - 1000 * 60 * 60 * 24 * (13 - i));
|
||||||
|
return {
|
||||||
|
adSetId: `as_${i}`,
|
||||||
|
adSetName: i % 2 === 0 ? '3BHK — Dubai Marina' : 'Penthouse Retarget',
|
||||||
|
spend: 800 + Math.floor(Math.random() * 400),
|
||||||
|
impressions: 18000 + Math.floor(Math.random() * 10000),
|
||||||
|
clicks: 360 + Math.floor(Math.random() * 200),
|
||||||
|
ctr: 1.8 + Math.random() * 1.5,
|
||||||
|
cpa: 190 + Math.floor(Math.random() * 120),
|
||||||
|
roi: 14 + Math.random() * 20,
|
||||||
|
date: d.toISOString().split('T')[0],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
function makeLiveEvent(
|
||||||
|
type: LiveEventType,
|
||||||
|
message: string,
|
||||||
|
campaignName?: string,
|
||||||
|
value?: string
|
||||||
|
): LiveOptimizationEvent {
|
||||||
|
return {
|
||||||
|
id: `ev_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`,
|
||||||
|
type,
|
||||||
|
message,
|
||||||
|
campaignName,
|
||||||
|
timestamp: new Date(),
|
||||||
|
value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockLiveEvents: LiveOptimizationEvent[] = [
|
||||||
|
makeLiveEvent('pause', 'Paused Ad Set B due to CPA exceeding AED 500 threshold.', '3BHK Prestige Launch', 'CPA: AED 512'),
|
||||||
|
makeLiveEvent('shift', 'Shifted AED 200/day budget from Ad Set B to Ad Set A (lower CPA).', '3BHK Prestige Launch', '+AED 200'),
|
||||||
|
makeLiveEvent('rotate', 'Rotated in Penthouse Cinematic (sea view) as new creative for A/B test.', 'Penthouse Whale Retarget'),
|
||||||
|
makeLiveEvent('optimize', 'Expanded Lookalike Audience to 2% similarity — 48k new reach.', '1BHK Investment', '+48k reach'),
|
||||||
|
makeLiveEvent('create', 'Created new Ad Set targeting High-Net-Worth Lookalike from 23 new CRM Closed/Won leads.', 'Penthouse Whale Retarget'),
|
||||||
|
];
|
||||||
|
|
||||||
|
// ── Store Types ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
interface MarketingState {
|
||||||
|
campaigns: Campaign[];
|
||||||
|
activeAssets: MarketingAsset[];
|
||||||
|
adInsights: AdInsight[];
|
||||||
|
liveEvents: LiveOptimizationEvent[];
|
||||||
|
settings: CatalystSettings;
|
||||||
|
activeTab: 'studio' | 'command' | 'intelligence' | 'war-room';
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
addCampaign: (campaign: Campaign) => void;
|
||||||
|
updateCampaign: (id: string, updates: Partial<Campaign>) => void;
|
||||||
|
addAsset: (asset: MarketingAsset) => void;
|
||||||
|
updateAsset: (id: string, updates: Partial<MarketingAsset>) => void;
|
||||||
|
updateInsights: (insights: AdInsight[]) => void;
|
||||||
|
pushLiveEvent: (event: LiveOptimizationEvent) => void;
|
||||||
|
setMetaSettings: (settings: Partial<CatalystSettings>) => void;
|
||||||
|
setActiveTab: (tab: MarketingState['activeTab']) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Store ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export const useMarketingStore = create<MarketingState>()((set) => ({
|
||||||
|
campaigns: mockCampaigns,
|
||||||
|
activeAssets: mockAssets,
|
||||||
|
adInsights: mockInsights,
|
||||||
|
liveEvents: mockLiveEvents,
|
||||||
|
activeTab: 'studio',
|
||||||
|
|
||||||
|
settings: {
|
||||||
|
metaAccessToken: '',
|
||||||
|
metaAdAccountId: '',
|
||||||
|
metaBusinessId: '',
|
||||||
|
metaAppId: '',
|
||||||
|
whatsappPhoneNumberId: '',
|
||||||
|
isConnected: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
addCampaign: (campaign) =>
|
||||||
|
set((state) => ({ campaigns: [...state.campaigns, campaign] })),
|
||||||
|
|
||||||
|
updateCampaign: (id, updates) =>
|
||||||
|
set((state) => ({
|
||||||
|
campaigns: state.campaigns.map((c) =>
|
||||||
|
c.id === id ? { ...c, ...updates, updatedAt: new Date() } : c
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
|
||||||
|
addAsset: (asset) =>
|
||||||
|
set((state) => ({ activeAssets: [...state.activeAssets, asset] })),
|
||||||
|
|
||||||
|
updateAsset: (id, updates) =>
|
||||||
|
set((state) => ({
|
||||||
|
activeAssets: state.activeAssets.map((a) =>
|
||||||
|
a.id === id ? { ...a, ...updates } : a
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
|
||||||
|
updateInsights: (insights) => set({ adInsights: insights }),
|
||||||
|
|
||||||
|
pushLiveEvent: (event) =>
|
||||||
|
set((state) => ({
|
||||||
|
// Keep a rolling window of the latest 30 events
|
||||||
|
liveEvents: [event, ...state.liveEvents].slice(0, 30),
|
||||||
|
})),
|
||||||
|
|
||||||
|
setMetaSettings: (updates) =>
|
||||||
|
set((state) => ({
|
||||||
|
settings: { ...state.settings, ...updates },
|
||||||
|
})),
|
||||||
|
|
||||||
|
setActiveTab: (tab) => set({ activeTab: tab }),
|
||||||
|
}));
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// Navigation Module Types
|
// Navigation Module Types
|
||||||
export type ModuleId = 'dashboard' | 'oracle' | 'sentinel' | 'inventory' | 'settings';
|
export type ModuleId = 'dashboard' | 'oracle' | 'sentinel' | 'inventory' | 'settings' | 'catalyst';
|
||||||
|
|
||||||
export interface NavItem {
|
export interface NavItem {
|
||||||
id: ModuleId;
|
id: ModuleId;
|
||||||
@@ -121,3 +121,78 @@ export interface JourneyClient {
|
|||||||
overallScore: number;
|
overallScore: number;
|
||||||
events: JourneyEvent[];
|
events: JourneyEvent[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Catalyst Types (Meta Marketing Agency Module) ───────────────────────────
|
||||||
|
|
||||||
|
export type CampaignObjective =
|
||||||
|
| 'OUTCOME_LEADS'
|
||||||
|
| 'OUTCOME_SALES'
|
||||||
|
| 'OUTCOME_AWARENESS'
|
||||||
|
| 'OUTCOME_TRAFFIC'
|
||||||
|
| 'OUTCOME_ENGAGEMENT';
|
||||||
|
|
||||||
|
export type CampaignStatus = 'ACTIVE' | 'PAUSED' | 'DELETED' | 'ARCHIVED';
|
||||||
|
|
||||||
|
export interface Campaign {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
objective: CampaignObjective;
|
||||||
|
status: CampaignStatus;
|
||||||
|
dailyBudget: number; // in cents (Meta format)
|
||||||
|
lifetimeSpend: number;
|
||||||
|
impressions: number;
|
||||||
|
clicks: number;
|
||||||
|
ctr: number; // percentage
|
||||||
|
cpa: number; // cost per acquisition
|
||||||
|
roi: number; // percentage
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AssetType = 'image' | 'video';
|
||||||
|
export type AssetStatus = 'queued' | 'rendering' | 'ready' | 'uploaded' | 'failed';
|
||||||
|
|
||||||
|
export interface MarketingAsset {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
type: AssetType;
|
||||||
|
status: AssetStatus;
|
||||||
|
renderMessage?: string; // e.g. "Wan 2.2 is rendering cinematic lighting..."
|
||||||
|
localUrl?: string; // ComfyUI output path
|
||||||
|
metaAssetId?: string; // Meta Ad Library ID after upload
|
||||||
|
thumbnailUrl?: string;
|
||||||
|
language?: 'en' | 'ar';
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdInsight {
|
||||||
|
adSetId: string;
|
||||||
|
adSetName: string;
|
||||||
|
spend: number;
|
||||||
|
impressions: number;
|
||||||
|
clicks: number;
|
||||||
|
ctr: number;
|
||||||
|
cpa: number;
|
||||||
|
roi: number;
|
||||||
|
date: string; // ISO date string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LiveEventType = 'pause' | 'shift' | 'create' | 'optimize' | 'alert' | 'rotate';
|
||||||
|
|
||||||
|
export interface LiveOptimizationEvent {
|
||||||
|
id: string;
|
||||||
|
type: LiveEventType;
|
||||||
|
message: string;
|
||||||
|
campaignName?: string;
|
||||||
|
timestamp: Date;
|
||||||
|
value?: string; // e.g. CPA amount, budget shift amount
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CatalystSettings {
|
||||||
|
metaAccessToken: string;
|
||||||
|
metaAdAccountId: string;
|
||||||
|
metaBusinessId: string;
|
||||||
|
metaAppId: string;
|
||||||
|
whatsappPhoneNumberId: string;
|
||||||
|
isConnected: boolean;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user