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:
2026-03-27 22:35:25 +05:30
parent 023ba48da2
commit 5478f2815e
56 changed files with 80532 additions and 187100 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
app/dist/index.html vendored
View File

@@ -4,8 +4,8 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Velocity WebOS</title>
<script type="module" crossorigin src="./assets/index-Cs3AYztb.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-J4PPDOa9.css">
<script type="module" crossorigin src="./assets/index-DWXNQJq4.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-y9vEdTGy.css">
</head>
<body>
<div id="root"></div>

0
app/node_modules/.bin/tsc generated vendored Normal file → Executable file
View File

0
app/node_modules/.bin/vite generated vendored Normal file → Executable file
View File

106
app/node_modules/.package-lock.json generated vendored
View File

@@ -1835,18 +1835,18 @@
"react": ">=16.8.0"
}
},
"node_modules/@esbuild/win32-x64": {
"node_modules/@esbuild/darwin-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
"integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
"integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
"cpu": [
"x64"
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
"darwin"
],
"engines": {
"node": ">=18"
@@ -3922,32 +3922,18 @@
"dev": true,
"license": "MIT"
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.55.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz",
"integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz",
"integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==",
"cpu": [
"x64"
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"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"
"darwin"
]
},
"node_modules/@standard-schema/utils": {
@@ -5008,6 +4994,19 @@
"dev": true,
"license": "MIT"
},
"node_modules/cookie": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
"integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/core-js-compat": {
"version": "3.48.0",
"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": {
"version": "1.1.2",
"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"
}
},
"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": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",
@@ -7291,6 +7343,12 @@
"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": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",

View File

@@ -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"}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -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
};

View File

@@ -1,7 +0,0 @@
{
"version": 3,
"sources": [],
"sourcesContent": [],
"mappings": "",
"names": []
}

View File

@@ -1,18 +1,18 @@
"use client";
import {
require_shim
} from "./chunk-TXHHHGR3.js";
createSlot
} from "./chunk-YWBEB5PG.js";
import {
useCallbackRef,
useLayoutEffect2
} from "./chunk-23FVUG5N.js";
import "./chunk-2VUH7NEY.js";
import {
require_shim
} from "./chunk-TXHHHGR3.js";
import {
require_react_dom
} from "./chunk-YF4B4G2L.js";
import {
createSlot
} from "./chunk-YWBEB5PG.js";
import "./chunk-2VUH7NEY.js";
import {
require_jsx_runtime
} from "./chunk-2YVA4HRZ.js";

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -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

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -28,9 +28,9 @@ import {
useLoader,
useStore,
useThree
} from "./chunk-KOYBCGV3.js";
import "./chunk-L3Z576C2.js";
} from "./chunk-O5V7GNMB.js";
import "./chunk-GUQHL3N7.js";
import "./chunk-L3Z576C2.js";
import "./chunk-NJ4V5H3P.js";
import "./chunk-TXHHHGR3.js";
import "./chunk-2YVA4HRZ.js";

View File

@@ -1,151 +1,127 @@
{
"hash": "947d6a4d",
"configHash": "dc08391c",
"lockfileHash": "de25a101",
"browserHash": "961f5b3a",
"hash": "b2c5007d",
"configHash": "d9a82a01",
"lockfileHash": "8a04eea8",
"browserHash": "5d6343ae",
"optimized": {
"react": {
"src": "../../react/index.js",
"file": "react.js",
"fileHash": "9864076c",
"fileHash": "4ea9824e",
"needsInterop": true
},
"react-dom": {
"src": "../../react-dom/index.js",
"file": "react-dom.js",
"fileHash": "2f8641cc",
"fileHash": "5b549105",
"needsInterop": true
},
"react/jsx-dev-runtime": {
"src": "../../react/jsx-dev-runtime.js",
"file": "react_jsx-dev-runtime.js",
"fileHash": "0d6d4c83",
"fileHash": "41193e59",
"needsInterop": true
},
"react/jsx-runtime": {
"src": "../../react/jsx-runtime.js",
"file": "react_jsx-runtime.js",
"fileHash": "a2c255cd",
"fileHash": "ad8008f8",
"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": {
"src": "../../@radix-ui/react-avatar/dist/index.mjs",
"file": "@radix-ui_react-avatar.js",
"fileHash": "2805441c",
"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",
"fileHash": "e6d8d406",
"needsInterop": false
},
"@radix-ui/react-dropdown-menu": {
"src": "../../@radix-ui/react-dropdown-menu/dist/index.mjs",
"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
}
},
@@ -156,35 +132,35 @@
"vision_bundle-CRIBZNUI": {
"file": "vision_bundle-CRIBZNUI.js"
},
"chunk-OAEA5FZL": {
"file": "chunk-OAEA5FZL.js"
"chunk-U7P2NEEE": {
"file": "chunk-U7P2NEEE.js"
},
"chunk-KOYBCGV3": {
"file": "chunk-KOYBCGV3.js"
"chunk-YWBEB5PG": {
"file": "chunk-YWBEB5PG.js"
},
"chunk-TGAC7IAX": {
"file": "chunk-TGAC7IAX.js"
"chunk-23FVUG5N": {
"file": "chunk-23FVUG5N.js"
},
"chunk-YJZCGBGU": {
"file": "chunk-YJZCGBGU.js"
},
"chunk-6ZMM2PAV": {
"file": "chunk-6ZMM2PAV.js"
},
"chunk-L3Z576C2": {
"file": "chunk-L3Z576C2.js"
"chunk-2VUH7NEY": {
"file": "chunk-2VUH7NEY.js"
},
"chunk-6MXH2QM6": {
"file": "chunk-6MXH2QM6.js"
},
"chunk-O4L7C4YS": {
"file": "chunk-O4L7C4YS.js"
},
"chunk-OAEA5FZL": {
"file": "chunk-OAEA5FZL.js"
},
"chunk-O5V7GNMB": {
"file": "chunk-O5V7GNMB.js"
},
"chunk-GUQHL3N7": {
"file": "chunk-GUQHL3N7.js"
},
"chunk-EQCCHGRT": {
"file": "chunk-EQCCHGRT.js"
},
"chunk-O4L7C4YS": {
"file": "chunk-O4L7C4YS.js"
"chunk-L3Z576C2": {
"file": "chunk-L3Z576C2.js"
},
"chunk-7GZ4CI6Q": {
"file": "chunk-7GZ4CI6Q.js"
@@ -192,39 +168,21 @@
"chunk-NJ4V5H3P": {
"file": "chunk-NJ4V5H3P.js"
},
"chunk-SMEXDMMQ": {
"file": "chunk-SMEXDMMQ.js"
},
"chunk-M3NXY72O": {
"file": "chunk-M3NXY72O.js"
},
"chunk-VPWBNV4W": {
"file": "chunk-VPWBNV4W.js"
"chunk-EQCCHGRT": {
"file": "chunk-EQCCHGRT.js"
},
"chunk-TXHHHGR3": {
"file": "chunk-TXHHHGR3.js"
},
"chunk-23FVUG5N": {
"file": "chunk-23FVUG5N.js"
},
"chunk-YF4B4G2L": {
"file": "chunk-YF4B4G2L.js"
},
"chunk-YWBEB5PG": {
"file": "chunk-YWBEB5PG.js"
},
"chunk-2VUH7NEY": {
"file": "chunk-2VUH7NEY.js"
},
"chunk-2YVA4HRZ": {
"file": "chunk-2YVA4HRZ.js"
},
"chunk-WUR7D6NS": {
"file": "chunk-WUR7D6NS.js"
},
"chunk-U7P2NEEE": {
"file": "chunk-U7P2NEEE.js"
},
"chunk-G3PMV62Z": {
"file": "chunk-G3PMV62Z.js"
}

View File

@@ -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

View File

@@ -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": []
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -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

File diff suppressed because one or more lines are too long

View File

@@ -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

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,6 @@
import {
clsx_default
} from "./chunk-U7P2NEEE.js";
import {
_extends
} from "./chunk-EQCCHGRT.js";
@@ -7,9 +10,6 @@ import {
import {
require_react
} from "./chunk-WUR7D6NS.js";
import {
clsx_default
} from "./chunk-U7P2NEEE.js";
import {
__commonJS,
__export,

View File

@@ -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
};

View File

@@ -1,7 +0,0 @@
{
"version": 3,
"sources": [],
"sourcesContent": [],
"mappings": "",
"names": []
}

View File

@@ -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.

Binary file not shown.

View File

@@ -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"
]
}

View File

@@ -1,3 +0,0 @@
# `@rollup/rollup-win32-x64-gnu`
This is the **x86_64-pc-windows-gnu** binary for `rollup`

View File

@@ -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"
}

View File

@@ -1,3 +0,0 @@
# `@rollup/rollup-win32-x64-msvc`
This is the **x86_64-pc-windows-msvc** binary for `rollup`

View File

@@ -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"
}

58
app/package-lock.json generated
View File

@@ -54,6 +54,7 @@
"react-dom": "^19.2.0",
"react-hook-form": "^7.70.0",
"react-resizable-panels": "^4.2.2",
"react-router-dom": "^7.13.1",
"recharts": "^2.15.4",
"sonner": "^2.0.7",
"tailwind-merge": "^3.4.0",
@@ -5834,6 +5835,19 @@
"dev": true,
"license": "MIT"
},
"node_modules/cookie": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
"integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/core-js-compat": {
"version": "3.48.0",
"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"
}
},
"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": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",
@@ -8132,6 +8184,12 @@
"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": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",

View File

@@ -56,6 +56,7 @@
"react-dom": "^19.2.0",
"react-hook-form": "^7.70.0",
"react-resizable-panels": "^4.2.2",
"react-router-dom": "^7.13.1",
"recharts": "^2.15.4",
"sonner": "^2.0.7",
"tailwind-merge": "^3.4.0",

View File

@@ -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 { useStore } from '@/store/useStore';
import { Sidebar } from '@/components/layout/Sidebar';
@@ -7,6 +9,7 @@ import { Oracle } from '@/components/modules/Oracle';
import { Sentinel } from '@/components/modules/Sentinel';
import { Inventory } from '@/components/modules/Inventory';
import { Settings } from '@/components/modules/Settings';
import { Catalyst } from '@/components/modules/Catalyst';
import type { ModuleId } from '@/types';
import {
MoreVertical,
@@ -19,30 +22,75 @@ import {
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
} from "@/components/ui/dropdown-menu";
const moduleComponents: Record<ModuleId, React.ComponentType> = {
dashboard: Dashboard,
oracle: Oracle,
sentinel: Sentinel,
inventory: Inventory,
settings: Settings,
};
// ── Route map ─────────────────────────────────────────────────────────────────
// Single source of truth: module id → URL path → page title → component
const moduleTitles: Record<ModuleId, string> = {
dashboard: 'Dashboard',
oracle: 'The Oracle',
sentinel: 'The Sentinel',
inventory: 'Inventory',
settings: 'Settings',
};
export const MODULE_ROUTES: Array<{
id: ModuleId;
path: string;
title: string;
component: React.ComponentType;
}> = [
{ 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() {
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 (
<div className="min-h-screen flex" style={{ background: '#000' }}>
{/* Sync URL → store */}
<RouteModuleSync />
{/* Sidebar */}
<Sidebar />
@@ -73,7 +121,7 @@ function MainLayout() {
animate={{ opacity: 1, x: 0 }}
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>
</motion.div>
@@ -101,7 +149,7 @@ function MainLayout() {
<DropdownMenuContent align="end" className="w-48 bg-[#0A0B10] border-white/10 text-zinc-200">
<DropdownMenuItem
className="cursor-pointer focus:bg-white/5"
onClick={() => setActiveModule('settings')}
onClick={goToSettings}
>
<Settings2 className="mr-2 h-4 w-4 text-zinc-400" />
<span>Settings</span>
@@ -117,11 +165,11 @@ function MainLayout() {
</div>
</header>
{/* Module Content */}
{/* Module Content — animated on route change */}
<div className="px-8 pb-8">
<AnimatePresence mode="wait">
<motion.div
key={activeModule}
key={location.pathname}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
@@ -130,7 +178,16 @@ function MainLayout() {
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>
</AnimatePresence>
</div>
@@ -139,6 +196,8 @@ function MainLayout() {
);
}
// ── Root App ──────────────────────────────────────────────────────────────────
function App() {
const { isAuthenticated } = useStore();
@@ -152,7 +211,10 @@ function App() {
exit={{ opacity: 0 }}
transition={{ duration: 0.5 }}
>
<LoginScreen />
<Routes>
<Route path="/login" element={<LoginScreen />} />
<Route path="*" element={<LoginScreen />} />
</Routes>
</motion.div>
) : (
<motion.div
@@ -162,7 +224,16 @@ function App() {
exit={{ opacity: 0 }}
transition={{ duration: 0.5 }}
>
<MainLayout />
<Routes>
<Route
path="/*"
element={
<ProtectedRoute>
<MainLayout />
</ProtectedRoute>
}
/>
</Routes>
</motion.div>
)}
</AnimatePresence>

View File

@@ -1,3 +1,4 @@
import { NavLink } from 'react-router-dom';
import { motion, AnimatePresence } from 'framer-motion';
import {
LayoutGrid,
@@ -5,21 +6,23 @@ import {
ScanFace,
Building2,
Sliders,
Megaphone,
type LucideIcon,
} from 'lucide-react';
import { useStore } from '@/store/useStore';
import type { ModuleId } from '@/types';
import { MODULE_ROUTES } from '@/App';
const navItems: { id: ModuleId; label: string; icon: LucideIcon }[] = [
{ id: 'dashboard', label: 'Dashboard', icon: LayoutGrid },
{ id: 'oracle', label: 'The Oracle', icon: MessageSquarePlus },
{ id: 'sentinel', label: 'The Sentinel', icon: ScanFace },
{ id: 'inventory', label: 'Inventory', icon: Building2 },
{ id: 'settings', label: 'Settings', icon: Sliders },
];
const NAV_ICONS: Record<string, LucideIcon> = {
'/dashboard': LayoutGrid,
'/oracle': MessageSquarePlus,
'/sentinel': ScanFace,
'/inventory': Building2,
'/catalyst': Megaphone,
'/settings': Sliders,
};
export function Sidebar() {
const { activeModule, setActiveModule, sidebarExpanded, setSidebarExpanded, status } = useStore();
const { sidebarExpanded, setSidebarExpanded, status } = useStore();
return (
<motion.aside
@@ -46,7 +49,6 @@ export function Sidebar() {
{/* Brand Name */}
<div className="flex items-center px-4 mb-8 overflow-hidden" style={{ height: 36 }}>
{/* Expanded: full name */}
<motion.div
className="overflow-hidden whitespace-nowrap"
initial={{ opacity: 0, width: 0 }}
@@ -58,55 +60,59 @@ export function Sidebar() {
</motion.div>
</div>
{/* Nav */}
<nav className="flex-1 px-3 space-y-1">
{navItems.map((item) => {
const Icon = item.icon;
const isActive = activeModule === item.id;
{MODULE_ROUTES.map((route) => {
const Icon = NAV_ICONS[route.path] ?? LayoutGrid;
return (
<motion.button
key={item.id}
onClick={() => setActiveModule(item.id)}
className="relative w-full flex items-center rounded-xl transition-colors duration-150"
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';
}}
<NavLink
key={route.path}
to={route.path}
className="block"
>
{/* Active left bar */}
{isActive && (
{({ 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 }}
/>
className="relative w-full flex items-center rounded-xl transition-colors duration-150 cursor-pointer"
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 && (
<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>
)}
<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>
</NavLink>
);
})}
</nav>

View 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 &amp; 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>
);
}

View File

@@ -1,10 +1,13 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import './index.css'
import App from './App.tsx'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
<BrowserRouter>
<App />
</BrowserRouter>
</StrictMode>,
)

View 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 }),
}));

View File

@@ -1,5 +1,5 @@
// Navigation Module Types
export type ModuleId = 'dashboard' | 'oracle' | 'sentinel' | 'inventory' | 'settings';
export type ModuleId = 'dashboard' | 'oracle' | 'sentinel' | 'inventory' | 'settings' | 'catalyst';
export interface NavItem {
id: ModuleId;
@@ -121,3 +121,78 @@ export interface JourneyClient {
overallScore: number;
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;
}