diff --git a/package-lock.json b/package-lock.json
index bba4f14..a2a225c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,11 +16,15 @@
"@types/node": "^16.18.126",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2",
+ "clsx": "^2.1.1",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-scripts": "5.0.1",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4"
+ },
+ "devDependencies": {
+ "sass": "^1.93.2"
}
},
"node_modules/@adobe/css-tools": {
@@ -3019,6 +3023,316 @@
"node": ">= 8"
}
},
+ "node_modules/@parcel/watcher": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
+ "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "detect-libc": "^1.0.3",
+ "is-glob": "^4.0.3",
+ "micromatch": "^4.0.5",
+ "node-addon-api": "^7.0.0"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "@parcel/watcher-android-arm64": "2.5.1",
+ "@parcel/watcher-darwin-arm64": "2.5.1",
+ "@parcel/watcher-darwin-x64": "2.5.1",
+ "@parcel/watcher-freebsd-x64": "2.5.1",
+ "@parcel/watcher-linux-arm-glibc": "2.5.1",
+ "@parcel/watcher-linux-arm-musl": "2.5.1",
+ "@parcel/watcher-linux-arm64-glibc": "2.5.1",
+ "@parcel/watcher-linux-arm64-musl": "2.5.1",
+ "@parcel/watcher-linux-x64-glibc": "2.5.1",
+ "@parcel/watcher-linux-x64-musl": "2.5.1",
+ "@parcel/watcher-win32-arm64": "2.5.1",
+ "@parcel/watcher-win32-ia32": "2.5.1",
+ "@parcel/watcher-win32-x64": "2.5.1"
+ }
+ },
+ "node_modules/@parcel/watcher-android-arm64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz",
+ "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-darwin-arm64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz",
+ "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-darwin-x64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz",
+ "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-freebsd-x64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz",
+ "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm-glibc": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz",
+ "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm-musl": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz",
+ "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm64-glibc": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz",
+ "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm64-musl": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz",
+ "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-x64-glibc": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz",
+ "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-x64-musl": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz",
+ "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-win32-arm64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz",
+ "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-win32-ia32": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz",
+ "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-win32-x64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
+ "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -5678,6 +5992,15 @@
"wrap-ansi": "^7.0.0"
}
},
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@@ -6621,6 +6944,20 @@
"npm": "1.2.8000 || >= 1.4.16"
}
},
+ "node_modules/detect-libc": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
+ "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "bin": {
+ "detect-libc": "bin/detect-libc.js"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
"node_modules/detect-newline": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
@@ -9211,6 +9548,13 @@
"url": "https://opencollective.com/immer"
}
},
+ "node_modules/immutable": {
+ "version": "5.1.4",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz",
+ "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==",
+ "devOptional": true,
+ "license": "MIT"
+ },
"node_modules/import-fresh": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
@@ -11614,6 +11958,14 @@
"tslib": "^2.0.3"
}
},
+ "node_modules/node-addon-api": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
+ "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/node-forge": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
@@ -14522,6 +14874,27 @@
"integrity": "sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA==",
"license": "CC0-1.0"
},
+ "node_modules/sass": {
+ "version": "1.93.2",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.93.2.tgz",
+ "integrity": "sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^4.0.0",
+ "immutable": "^5.0.2",
+ "source-map-js": ">=0.6.2 <2.0.0"
+ },
+ "bin": {
+ "sass": "sass.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "optionalDependencies": {
+ "@parcel/watcher": "^2.4.1"
+ }
+ },
"node_modules/sass-loader": {
"version": "12.6.0",
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz",
@@ -14560,6 +14933,36 @@
}
}
},
+ "node_modules/sass/node_modules/chokidar": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
+ "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "readdirp": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 14.16.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/sass/node_modules/readdirp": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
+ "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.18.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
"node_modules/sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
diff --git a/package.json b/package.json
index 4b8db03..b80a350 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,7 @@
"@types/node": "^16.18.126",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2",
+ "clsx": "^2.1.1",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-scripts": "5.0.1",
@@ -40,5 +41,8 @@
"last 1 firefox version",
"last 1 safari version"
]
+ },
+ "devDependencies": {
+ "sass": "^1.93.2"
}
}
diff --git a/src/App.tsx b/src/App.tsx
index a53698a..4765be0 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,26 +1,8 @@
-import React from 'react';
-import logo from './logo.svg';
-import './App.css';
+import SceneView from "./pages/SceneView";
+import "./styles/main.scss";
function App() {
- return (
-
- );
+ return ;
}
export default App;
diff --git a/src/components/outlinePanel/OutlineList .tsx b/src/components/outlinePanel/OutlineList .tsx
new file mode 100644
index 0000000..934774a
--- /dev/null
+++ b/src/components/outlinePanel/OutlineList .tsx
@@ -0,0 +1,487 @@
+import React, { useState } from "react";
+import {
+ AddIcon,
+ ChevronIcon,
+ CollapseAllIcon,
+ CubeIcon,
+ EyeIcon,
+ FocusIcon,
+ FolderIcon,
+ KebebIcon,
+ LockIcon,
+} from "../../icons/ExportIcons";
+import { OutlinePanelProps } from "./OutlinePanel";
+import clsx from "clsx";
+
+interface AssetGroupChild {
+ groupUuid?: string;
+ groupName?: string;
+ isExpanded?: boolean;
+ children?: AssetGroupChild[];
+ modelUuid?: string;
+ modelName?: string;
+ isVisible?: boolean;
+ isLocked?: boolean;
+}
+
+interface TreeNodeProps {
+ item: AssetGroupChild;
+ level?: number;
+ textColor?: string;
+ eyeIconColor?: string;
+ lockIconColor?: string;
+ onDragStart: (item: AssetGroupChild) => void;
+ onDrop: (item: AssetGroupChild, parent: AssetGroupChild | null, e: any) => void;
+ draggingItem: AssetGroupChild | null;
+ selectedObject: string | null;
+ onDragOver: (item: AssetGroupChild, parent: AssetGroupChild | null, e: React.DragEvent) => void;
+ setSelectedObject: React.Dispatch>;
+}
+
+type DropAction = "above" | "child" | "below" | "none";
+
+const DEFAULT_PRAMS = {
+ backgroundColor: "linear-gradient(to bottom, #1e1e2f, #12121a)",
+ panelSide: "left",
+ textColor: "linear-gradient(to bottom, rgba(231, 231, 255, 1), #cacad6ff)",
+ addIconColor: "white",
+ lockIconColor: "white",
+ eyeIconColor: "white",
+};
+
+const TreeNode: React.FC = ({
+ item,
+ level = 0,
+ textColor,
+ eyeIconColor,
+ lockIconColor,
+ onDragStart,
+ onDrop,
+ draggingItem,
+ setSelectedObject,
+ selectedObject,
+ onDragOver,
+}) => {
+ const isGroupNode =
+ Array.isArray(item.children) && item.children.length > 0 ? item.children : false;
+ const [isExpanded, setIsExpanded] = useState(item.isExpanded || false);
+ const [isVisible, setIsVisible] = useState(item.isVisible ?? true);
+ const [isLocked, setIsLocked] = useState(item.isLocked ?? false);
+ const [isEditing, setIsEditing] = useState(false);
+ const [name, setName] = useState(isGroupNode ? item.groupName : item.modelName);
+
+ const handleDragStart = (e: React.DragEvent) => {
+ e.stopPropagation();
+ onDragStart(item);
+ };
+
+ const handleDragOver = (e: React.DragEvent) => {
+ e.preventDefault();
+ onDragOver(item, draggingItem, e);
+ };
+
+ const handleDrop = (e: React.DragEvent) => {
+ e.preventDefault();
+ onDrop(item, draggingItem, e);
+ };
+
+ const isBeingDragged =
+ draggingItem?.groupUuid === item.groupUuid || draggingItem?.modelUuid === item.modelUuid;
+
+ const handleDoubleClick = () => {
+ setIsEditing(true);
+ };
+
+ const handleBlur = () => {
+ setIsEditing(false);
+ };
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === "Enter") {
+ setIsEditing(false);
+ }
+ };
+
+ // Toggle selection (used by mouse click, keyboard and touch)
+ const toggleSelect = () => {
+ const currentId = item.modelUuid || item.groupUuid || null;
+ setSelectedObject((prev) => (prev === currentId ? null : currentId));
+ };
+
+ const handleDivKeyDown = (e: React.KeyboardEvent) => {
+ // support Enter and Space for accessibility
+ if (e.key === "Enter" || e.key === " ") {
+ e.preventDefault();
+ toggleSelect();
+ }
+ };
+
+ const handleTouchStart = () => {
+ toggleSelect();
+ };
+
+ return (
+
+
+
+ {isGroupNode && (
+
+ )}
+
+
+ {isGroupNode ? : }
+
+
+
+ {isEditing ? (
+ setName(e.target.value)}
+ onBlur={handleBlur}
+ onKeyDown={handleKeyDown}
+ />
+ ) : (
+
+ {name}
+
+ )}
+
+
+
+
+ {isGroupNode && item.children?.length ? (
+
+ ) : null}
+
+ {isGroupNode ? (
+
+ ) : null}
+
+
+
+ {isExpanded && item.children?.length ? (
+
+ {item.children.map((child) => (
+
+ ))}
+
+ ) : null}
+
+ );
+};
+
+export const OutlineList: React.FC = ({
+ backgroundColor = "linear-gradient(to bottom, #1e1e2f, #12121a)",
+ panelSide = "left",
+ textColor = "linear-gradient(to bottom, rgba(231, 231, 255, 1), #cacad6ff)",
+ addIconColor,
+ lockIconColor,
+ eyeIconColor,
+}) => {
+ const [selectedObject, setSelectedObject] = useState(null); // store UUID
+
+ const [isOpen, setIsOpen] = useState(true);
+ const [draggingItem, setDraggingItem] = useState(null);
+ const [groupHierarchy, setGroupHierarchy] = useState([
+ { modelUuid: "a1", modelName: "Asset 1", isVisible: true, isLocked: false, children: [] },
+ { modelUuid: "a2", modelName: "Asset 2", isVisible: true, isLocked: false, children: [] },
+ { modelUuid: "a3", modelName: "Asset 3", isVisible: true, isLocked: false, children: [] },
+ { modelUuid: "a4", modelName: "Asset 4", isVisible: true, isLocked: false, children: [] },
+ { modelUuid: "a5", modelName: "Asset 5", isVisible: true, isLocked: false, children: [] },
+ { modelUuid: "a6", modelName: "Asset 6", isVisible: true, isLocked: false, children: [] },
+ ]);
+
+ const handleDragStart = (item: AssetGroupChild) => {
+ setDraggingItem(item); // Set the dragged item when dragging starts
+ };
+
+ const handleDrop = (
+ targetItem: AssetGroupChild,
+ draggedItem: AssetGroupChild | null,
+ event: DragEvent | React.DragEvent
+ ) => {
+ if (!draggedItem) return;
+
+ const targetId = targetItem.modelUuid;
+ if (!targetId) return;
+
+ const hoveredDiv = document.getElementById(targetId);
+ if (!hoveredDiv) return;
+
+ // Calculate drop position
+ const rect = hoveredDiv.getBoundingClientRect();
+ const parentScrollTop = hoveredDiv.parentElement?.scrollTop || 0;
+ const y = (event as any).clientY - (rect.top + parentScrollTop);
+
+ // Determine drop action
+ const action = getDropAction(y);
+ if (action === "none") {
+ hoveredDiv.style.borderTop = "none";
+ hoveredDiv.style.borderBottom = "none";
+ hoveredDiv.style.outline = "none";
+ hoveredDiv.style.border = "none";
+ setDraggingItem(null);
+ return;
+ }
+
+ // Update hierarchy
+ const updatedHierarchy = [...groupHierarchy];
+
+ if (!removeItemFromHierarchy(draggedItem, updatedHierarchy)) {
+ return;
+ }
+
+ if (!insertItemByAction(draggedItem, targetId, action, updatedHierarchy)) {
+ updatedHierarchy.push(draggedItem);
+ }
+
+ // Commit changes
+ setGroupHierarchy(updatedHierarchy);
+ setDraggingItem(null);
+ hoveredDiv.style.borderTop = "none";
+ hoveredDiv.style.borderBottom = "none";
+ hoveredDiv.style.outline = "none";
+ hoveredDiv.style.border = "none";
+ };
+
+ const getDropAction = (y: number): DropAction => {
+ if (y >= 0 && y < 7) return "above";
+ if (y >= 7 && y < 19) return "child";
+ if (y >= 19 && y < 32) return "below";
+ return "none";
+ };
+
+ const removeItemFromHierarchy = (
+ item: AssetGroupChild,
+ hierarchy: AssetGroupChild[]
+ ): boolean => {
+ for (let i = 0; i < hierarchy.length; i++) {
+ const current = hierarchy[i];
+ if (current.modelUuid === item.modelUuid) {
+ hierarchy.splice(i, 1);
+ return true;
+ }
+ if (current.children?.length) {
+ const removed = removeItemFromHierarchy(item, current.children);
+ if (removed) return true;
+ }
+ }
+ return false;
+ };
+
+ const insertItemByAction = (
+ item: AssetGroupChild,
+ targetId: string,
+ action: DropAction,
+ hierarchy: AssetGroupChild[]
+ ): boolean => {
+ switch (action) {
+ case "above":
+ return insertAsSibling(item, targetId, hierarchy, 0);
+ case "below":
+ return insertAsSibling(item, targetId, hierarchy, 1);
+ case "child":
+ return addAsChild(targetId, item, hierarchy);
+ default:
+ return false;
+ }
+ };
+
+ const insertAsSibling = (
+ item: AssetGroupChild,
+ targetId: string,
+ hierarchy: AssetGroupChild[],
+ offset: number // 0 for above, 1 for below
+ ): boolean => {
+ for (let i = 0; i < hierarchy.length; i++) {
+ if (hierarchy[i].modelUuid === targetId) {
+ hierarchy.splice(i + offset, 0, item);
+ return true;
+ }
+ if (hierarchy[i].children?.length) {
+ const inserted = insertAsSibling(item, targetId, hierarchy[i].children!, offset);
+ if (inserted) return true;
+ }
+ }
+ return false;
+ };
+
+ const addAsChild = (
+ parentId: string,
+ childItem: AssetGroupChild,
+ hierarchy: AssetGroupChild[]
+ ): boolean => {
+ for (let i = 0; i < hierarchy.length; i++) {
+ if (hierarchy[i].modelUuid === parentId) {
+ if (!hierarchy[i].children) hierarchy[i].children = [];
+ hierarchy[i].children!.push(childItem);
+ return true;
+ }
+ if (hierarchy[i].children?.length) {
+ const added = addAsChild(parentId, childItem, hierarchy[i].children!);
+ if (added) return true;
+ }
+ }
+ return false;
+ };
+
+ const handleDragOver = (
+ targetItem: AssetGroupChild,
+ draggedItem: AssetGroupChild | null,
+ event: DragEvent | React.DragEvent
+ ) => {
+ event.preventDefault();
+ const targetId = targetItem?.modelUuid || targetItem?.groupUuid;
+ if (!targetId) return;
+
+ const hoveredDiv = document.getElementById(targetId);
+ if (!hoveredDiv) return;
+
+ // Remove previous outlines before applying new one
+ hoveredDiv.style.outline = "none";
+ hoveredDiv.style.borderTop = "none";
+ hoveredDiv.style.borderBottom = "none";
+
+ const rect = hoveredDiv.getBoundingClientRect();
+ const y = (event as any).clientY - rect.top;
+
+ // Determine where the user is hovering
+ if (y >= 0 && y < 7) {
+ // Top region
+ console.log("Top: ");
+
+ hoveredDiv.style.borderTop = "2px solid purple";
+ return "above";
+ } else if (y >= 19 && y < 32) {
+ // Bottom region
+ console.log("Bottom: ");
+ hoveredDiv.style.borderBottom = "2px solid purple";
+ return "below";
+ } else {
+ // Middle region (child)
+ console.log("Middle: ");
+ hoveredDiv.style.outline = "2px solid #b188ff";
+ return "child";
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {isOpen && (
+
+ {groupHierarchy.map((item) => (
+
+ ))}
+
+ )}
+
+
+ );
+};
diff --git a/src/components/outlinePanel/OutlinePanel.tsx b/src/components/outlinePanel/OutlinePanel.tsx
new file mode 100644
index 0000000..5de1e82
--- /dev/null
+++ b/src/components/outlinePanel/OutlinePanel.tsx
@@ -0,0 +1,17 @@
+import React from "react";
+import { OutlineList } from "./OutlineList ";
+
+export interface OutlinePanelProps {
+ textColor?: string;
+ panelSide?: "left" | "right";
+ backgroundColor?: string;
+ addIconColor?: string;
+ eyeIconColor?: string;
+ lockIconColor?: string;
+}
+
+const OutlinePanel: React.FC = (props) => {
+ return ;
+};
+
+export default OutlinePanel;
diff --git a/src/icons/ExportIcons.tsx b/src/icons/ExportIcons.tsx
new file mode 100644
index 0000000..64d5283
--- /dev/null
+++ b/src/icons/ExportIcons.tsx
@@ -0,0 +1,1742 @@
+export function SearchIcon() {
+ return (
+
+ );
+}
+
+export function ArrowIcon() {
+ return (
+
+ );
+}
+
+export function FocusIcon() {
+ return (
+
+ );
+}
+
+export function KebebIcon() {
+ return (
+
+ );
+}
+interface IconProps {
+ color?: string;
+ size?: number; // optional for width/height
+ isClosed?: boolean;
+ isLocked?: boolean;
+}
+
+export function AddIcon({ color = "white", size = 12 }: IconProps) {
+ return (
+
+ );
+}
+
+export function EyeIcon({ color = "white", isClosed }: IconProps) {
+ return isClosed ? (
+
+ ) : (
+
+ );
+}
+
+export function LockIcon({ color = "white", isLocked }: IconProps) {
+ return isLocked ? (
+
+ ) : (
+
+ );
+}
+export function CloseIcon() {
+ return (
+
+ );
+}
+
+export function SettingsIcon() {
+ return (
+
+ );
+}
+
+export function HelpIcon() {
+ return (
+
+ );
+}
+
+export function TrashIcon() {
+ return (
+
+ );
+}
+
+export function FilterIcon() {
+ return (
+
+ );
+}
+
+export function EyeDroperIcon({ isActive }: { isActive: boolean }) {
+ return (
+
+ );
+}
+
+export function TickIcon() {
+ return (
+
+ );
+}
+
+export function UndoIcon() {
+ return (
+
+ );
+}
+
+export function RedoIcon() {
+ return (
+
+ );
+}
+
+export function ResizeHeightIcon() {
+ return (
+
+ );
+}
+
+export function RemoveIcon() {
+ return (
+
+ );
+}
+
+export function InfoIcon() {
+ return (
+
+ );
+}
+
+export function AIIcon() {
+ return (
+
+ );
+}
+
+export const KebabIcon = () => {
+ return (
+
+ );
+};
+
+export const DublicateIcon = () => {
+ return (
+
+ );
+};
+
+export const DeleteIcon = () => {
+ return (
+
+ );
+};
+
+export const HourlySimulationIcon = () => {
+ return (
+
+ );
+};
+
+export const DailyProductionIcon = () => {
+ return (
+
+ );
+};
+
+export const MonthlyROI = () => {
+ return (
+
+ );
+};
+
+export const ExpandIcon = ({ isActive }: { isActive: boolean }) => {
+ return isActive ? (
+
+ ) : (
+
+ );
+};
+
+export const ExpandIcon2 = () => {
+ return (
+
+ );
+};
+
+export const StartIcon = () => {
+ return (
+
+ );
+};
+
+export const EndIcon = () => {
+ return (
+
+ );
+};
+
+export const SpeedIcon = () => {
+ return (
+
+ );
+};
+
+export const LogListIcon = () => {
+ return (
+
+ );
+};
+
+export const LogTickIcon = () => {
+ return (
+
+ );
+};
+
+export const LogInfoIcon = () => {
+ return (
+
+ );
+};
+
+export const WarningIcon = () => {
+ return (
+
+ );
+};
+
+export const ErrorIcon = () => {
+ return (
+
+ );
+};
+
+export const LocationIcon = () => {
+ return (
+
+ );
+};
+export const SaveDiskIcon = () => {
+ return (
+
+ );
+};
+export const WalkIcon = () => {
+ return (
+
+ );
+};
+export const EyeCloseIcon = () => {
+ return (
+
+ );
+};
+
+export const SaveIcon = () => {
+ return (
+
+ );
+};
+
+export const FolderIcon = ({ isOpen }: { isOpen: boolean }) => (
+
+);
+
+export const CubeIcon = () => (
+
+);
+
+export const ChevronIcon = ({ isOpen }: { isOpen: boolean }) => {
+ return isOpen ? (
+
+ ) : (
+
+ );
+};
+
+export const CollapseAllIcon = () => {
+ return (
+
+ );
+};
+
+export const SaveVersionIcon = () => {
+ return (
+
+ );
+};
+
+export const RenameVersionIcon = () => {
+ return (
+
+ );
+};
+
+export const FinishEditIcon = () => {
+ return (
+
+ );
+};
+
+export const PerformanceIcon = () => {
+ return (
+
+ );
+};
+
+export const GreenTickIcon = () => {
+ return (
+
+ );
+};
+
+export const SuccessIcon = () => {
+ return (
+
+ );
+};
+
+export const AlertIcon = () => {
+ return (
+
+ );
+};
+export const NavigationIcon = () => {
+ return (
+
+ );
+};
+export const HangTagIcon = () => {
+ return (
+
+ );
+};
+export const DecalInfoIcon = () => {
+ return (
+
+ );
+};
+
+export const LayeringBottomIcon = () => {
+ return (
+
+ );
+};
+
+export const LayeringTopIcon = () => {
+ return (
+
+ );
+};
+
+export const ValueUpdateIcon = () => {
+ return (
+
+ );
+};
+
+export const ListTaskIcon = () => {
+ return (
+
+ );
+};
+
+export const LocationPinIcon = () => {
+ return (
+
+ );
+};
+
+export const ClockThreeIcon = () => {
+ return (
+
+ );
+};
+
+export const SlectedTickIcon = () => {
+ return (
+
+ );
+};
+
+export const HourGlassIcon = () => {
+ return (
+
+ );
+};
+
+export const TargetIcon = () => {
+ return (
+
+ );
+};
+
+export const RightHalfFillCircleIcon = () => {
+ return (
+
+ );
+};
+
+export const PerformanceStatsIcon = () => {
+ return (
+
+ );
+};
diff --git a/src/pages/SceneView.tsx b/src/pages/SceneView.tsx
new file mode 100644
index 0000000..b701242
--- /dev/null
+++ b/src/pages/SceneView.tsx
@@ -0,0 +1,16 @@
+import React from "react";
+import OutlinePanel from "../components/outlinePanel/OutlinePanel";
+
+const SceneView = () => {
+ return (
+
+ );
+};
+
+export default SceneView;
diff --git a/src/styles/abstracts/_functions.scss b/src/styles/abstracts/_functions.scss
new file mode 100644
index 0000000..6f0745d
--- /dev/null
+++ b/src/styles/abstracts/_functions.scss
@@ -0,0 +1,5 @@
+// get rem from pixels
+
+@function get_rem($px) {
+ @return calc($px / 16 * 1rem);
+}
diff --git a/src/styles/abstracts/_mixins.scss b/src/styles/abstracts/_mixins.scss
new file mode 100644
index 0000000..4133dc1
--- /dev/null
+++ b/src/styles/abstracts/_mixins.scss
@@ -0,0 +1,33 @@
+@mixin flex-center {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+@mixin flex-space-between {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+// Array of base colors
+$colors: (
+ #f5550b,
+ #1bac1b,
+ #0099ff,
+ #d4c927,
+ #8400ff,
+ #13e9b3,
+ #df1dcf
+);
+
+@mixin gradient-by-child($index) {
+ // Get the color based on the index passed
+ $base-color: nth($colors, $index);
+ // Apply gradient using the same color with different alpha values
+ background: linear-gradient(
+ 144.19deg,
+ rgba($base-color, 0.2) 16.62%, // 20% opacity
+ rgba($base-color, 0.08) 85.81% // 80% opacity
+ );
+}
diff --git a/src/styles/abstracts/_variables.scss b/src/styles/abstracts/_variables.scss
new file mode 100644
index 0000000..2ddbfc9
--- /dev/null
+++ b/src/styles/abstracts/_variables.scss
@@ -0,0 +1,171 @@
+@use "functions";
+
+@import url("https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Josefin+Sans:ital,wght@0,100..700;1,100..700&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Roboto:ital,wght@0,100..900;1,100..900&display=swap");
+
+// new variables
+
+// text colors
+// ---------- light mode ----------
+$text-color: #2b3344;
+$text-disabled: #b7b7c6;
+$input-text-color: #595965;
+$highlight-text-color: #6f42c1;
+$text-button-color: #f3f3fd;
+
+// ---------- dark mode ----------
+$text-color-dark: #f3f3fd;
+$text-disabled-dark: #6f6f7a;
+$input-text-color-dark: #b5b5c8;
+$highlight-text-color-dark: #d2baff;
+$text-button-color-dark: #f3f3fd;
+
+// background colors
+// ---------- light mode ----------
+$background-color: linear-gradient(-45deg, #fcfdfd71 0%, #fcfdfd79 100%);
+$background-color-solid-gradient: linear-gradient(-45deg, #fcfdfd 0%, #fcfdfd 100%);
+$background-color-solid: #fcfdfd;
+$background-color-secondary: #fcfdfd4d;
+$background-color-accent: #6f42c1;
+$background-color-button: #6f42c1;
+$background-color-drop-down: #6f42c14d;
+$background-color-input: #ffffff4d;
+$background-color-input-focus: #f2f2f7;
+$background-color-drop-down-gradient: linear-gradient(-45deg, #75649366 0%, #40257266 100%);
+$background-color-selected: #e0dfff;
+$background-radial-gray-gradient: radial-gradient(circle, #bfe0f8 0%, #e9ebff 46%, #e2acff 100%);
+$background-model: #ffffff80;
+
+// ---------- dark mode ----------
+$background-color-dark: linear-gradient(-45deg, #333333b3 0%, #2d2437b3 100%);
+$background-color-solid-gradient-dark: linear-gradient(-45deg, #333333 0%, #2d2437 100%);
+$background-color-solid-dark: #19191d;
+$background-color-secondary-dark: #19191d99;
+$background-color-accent-dark: #6f42c1;
+$background-color-button-dark: #6f42c1;
+$background-color-drop-down-dark: #50505080;
+$background-color-input-dark: #ffffff33;
+$background-color-input-focus-dark: #333333;
+$background-color-drop-down-gradient-dark: linear-gradient(-45deg, #8973b166 0%, #53427366 100%);
+$background-color-selected-dark: #403e66;
+$background-radial-gray-gradient-dark: radial-gradient(
+ circle,
+ #31373b 0%,
+ #48494b 46%,
+ #52415c 100%
+);
+$background-model-dark: #00000080;
+
+// border colors
+// ---------- light mode ----------
+$border-color: #e0dfff;
+$input-border-color: #d5dddd80;
+$border-color-accent: #6f42c1;
+
+// ---------- dark mode ----------
+$border-color-dark: #564b69;
+$input-border-color-dark: #d5dddd80;
+$border-color-accent-dark: #6f42c1;
+
+// highlight colors
+// ---------- light mode ----------
+$highlight-accent-color: #e0dfff;
+$highlight-secondary-color: #6f42c1;
+
+// ---------- dark mode ----------
+$highlight-accent-color-dark: #403e6a;
+$highlight-secondary-color-dark: #c4abf1;
+
+// icon colors
+// ---------- light mode ----------
+$icon-default-color: #6f42c1;
+$icon-default-color-hover: #7f4ddb;
+$icon-default-color-active: #f2f2f7;
+
+// ---------- dark mode ----------
+$icon-default-color-dark: #6f42c1;
+$icon-default-color-hover-dark: #7f4ddb;
+$icon-default-color-active-dark: #f2f2f7;
+
+// colors
+$color1: #a392cd;
+$color2: #7b4cd3;
+$color3: #b186ff;
+$color4: #8752e8;
+$color5: #c7a8ff;
+
+// log indication colors
+// ------------ text -------------
+$log-default-text-color: #6f42c1;
+$log-info-text-color: #1773fd;
+$log-warn-text-color: #f3a50c;
+$log-error-text-color: #fc230f;
+$log-success-text-color: #23a84f;
+// ----------- dark ---------------
+$log-default-text-color-dark: #b18ef1;
+$log-info-text-color-dark: #7eb0fa;
+$log-warn-text-color-dark: #ffaa00;
+$log-error-text-color-dark: #ff887d;
+$log-success-text-color-dark: #43ff81;
+
+// ------------ background -------------
+$log-default-backgroung-color: #6e42c133;
+$log-info-background-color: #1773fd5d;
+$log-warn-background-color: #f3a50c33;
+$log-error-background-color: #fc230f33;
+$log-success-background-color: #0ef75b33;
+
+// old variables
+$accent-color: #6f42c1;
+$accent-color-dark: #c4abf1;
+
+$background-color-gray: #f3f3f3;
+$background-color-gray-dark: #232323;
+
+$shadow-color: #3c3c431a;
+$shadow-color-dark: #8f8f8f1a;
+
+$acent-gradient-dark: linear-gradient(90deg, #b392f0 0%, #a676ff 100%);
+$acent-gradient: linear-gradient(90deg, #6f42c1 0%, #925df3 100%);
+
+$faint-gradient: radial-gradient(circle, #bfe0f8 0%, #e9ebff 46%, #e2acff 100%);
+$faint-gradient-dark: radial-gradient(circle, #31373b 0%, #48494b 46%, #52415c 100%);
+
+$font-inter: "Inter", sans-serif;
+$font-josefin-sans: "Josefin Sans", sans-serif;
+$font-poppins: "Poppins", sans-serif;
+$font-roboto: "Roboto", sans-serif;
+
+$tiny: 0.625rem;
+$small: 0.75rem;
+$regular: 0.8rem;
+$large: 1rem;
+$xlarge: 1.125rem;
+$xxlarge: 1.5rem;
+$xxxlarge: 2rem;
+
+$thin-weight: 300;
+$regular-weight: 400;
+$medium-weight: 500;
+$bold-weight: 600;
+
+$z-index-drei-html: 1;
+$z-index-default: 1;
+$z-index-marketplace: 2;
+$z-index-tools: 3;
+$z-index-negative: -1;
+$z-index-ui-base: 10;
+$z-index-ui-overlay: 20;
+$z-index-ui-popup: 30;
+$z-index-ui-highest: 50;
+
+$box-shadow-light: 0px 2px 4px $shadow-color;
+$box-shadow-medium: 0px 4px 8px $shadow-color;
+$box-shadow-heavy: 0px 8px 16px $shadow-color;
+
+$border-radius-small: 4px;
+$border-radius-medium: 6px;
+$border-radius-large: 12px;
+$border-radius-circle: 50%;
+$border-radius-xlarge: 16px;
+$border-radius-extra-large: 20px;
+$border-radius-xxx: 30px;
diff --git a/src/styles/components/outlinePanel.scss b/src/styles/components/outlinePanel.scss
new file mode 100644
index 0000000..f0704fc
--- /dev/null
+++ b/src/styles/components/outlinePanel.scss
@@ -0,0 +1,580 @@
+// .outline-overlay {
+// padding: 0 4px;
+// font-family: "Segoe UI", sans-serif;
+// display: flex;
+// position: absolute;
+// top: 0;
+// bottom: 0;
+// width: 100vw;
+// pointer-events: none;
+// .outline-card {
+// pointer-events: all;
+// }
+// }
+
+// .outline-header {
+// display: flex;
+// align-items: center;
+// justify-content: space-between;
+// padding: 10px 16px;
+// border-bottom: 1px solid #2e2e3e;
+
+// .header-title {
+// p {
+// margin: 0;
+// font-weight: 600;
+// font-size: 14px;
+// color: #ffffff;
+// }
+// }
+
+// .outline-toolbar {
+// display: flex;
+// gap: 8px;
+
+// .toolbar-button {
+// background: none;
+// border: none;
+// color: #aaa;
+// cursor: pointer;
+// width: 24px;
+// height: 24px;
+// border-radius: 4px;
+// transition: background 0.3s;
+
+// &:hover {
+// background-color: rgba(255, 255, 255, 0.1);
+// color: #a855f7;
+// }
+
+// svg {
+// width: 100%;
+// height: 100%;
+// }
+// }
+
+// .close-button {
+// background: none;
+// border: none;
+// color: #aaa;
+// cursor: pointer;
+// width: 24px;
+// height: 24px;
+// border-radius: 4px;
+// transition: transform 0.3s ease;
+
+// &:hover {
+// background-color: rgba(255, 255, 255, 0.1);
+// transform: rotate(-90deg);
+// color: #a855f7;
+// }
+
+// svg {
+// width: 100%;
+// height: 100%;
+// }
+// }
+// }
+// }
+
+// .outline-content {
+// max-height: 52vh;
+// overflow-y: auto;
+// padding: 8px 0;
+
+// &::-webkit-scrollbar {
+// width: 6px;
+// }
+
+// &::-webkit-scrollbar-thumb {
+// background: #a855f7;
+// border-radius: 10px;
+// }
+// }
+// .tree-node {
+// display: flex;
+// flex-direction: column;
+// width: 100%;
+// outline: 2px solid transparent;
+// transition: all 0.3s ease;
+
+// .tree-node-content {
+// display: flex;
+// align-items: center;
+// width: 100%;
+// padding: 6px 10px;
+// border-radius: 8px;
+// background: transparent;
+// transition: background 0.2s ease, outline 0.2s ease;
+// box-sizing: border-box;
+// position: relative;
+
+// &:hover {
+// background: rgba(255, 255, 255, 0.05);
+// }
+
+// &.selected {
+// background: var(--background-color-accent, #6f42c1);
+// }
+
+// &.multi-selected {
+// background: rgba(167, 139, 250, 0.2);
+// border-left: 3px solid var(--highlight-secondary-color, #a855f7);
+// }
+
+// .expand-button {
+// background: none;
+// border: none;
+// color: #aaa;
+// cursor: pointer;
+// width: 18px;
+// height: 18px;
+// flex-shrink: 0;
+// display: flex;
+// align-items: center;
+// justify-content: center;
+
+// &:hover {
+// color: #a855f7;
+// }
+// }
+
+// .node-wrapper {
+// display: flex;
+// align-items: center;
+// flex: 1;
+// min-width: 0;
+// gap: 8px;
+// }
+
+// .node-icon {
+// color: #a855f7;
+// flex-shrink: 0;
+// width: 16px;
+// height: 16px;
+// display: flex;
+// align-items: center;
+// justify-content: center;
+// }
+
+// .rename-input {
+// flex: 1;
+// min-width: 0;
+// overflow: hidden;
+// white-space: nowrap;
+// text-overflow: ellipsis;
+// font-size: 14px;
+// color: #fff;
+
+// input.renaming {
+// width: 100%;
+// border: none;
+// outline: none;
+// border-radius: 6px;
+// background: rgba(255, 255, 255, 0.1);
+// color: #fff;
+// padding: 3px 6px;
+// font-size: 14px;
+// box-sizing: border-box;
+// }
+// }
+
+// .node-controls {
+// display: flex;
+// align-items: center;
+// gap: 4px;
+// flex-shrink: 0;
+
+// .control-button {
+// background: none;
+// border: none;
+// color: #aaa;
+// cursor: pointer;
+// border-radius: 4px;
+// padding: 4px;
+// display: flex;
+// align-items: center;
+// justify-content: center;
+
+// &:hover {
+// background: rgba(255, 255, 255, 0.1);
+// color: #a855f7;
+// }
+
+// svg {
+// width: 14px;
+// height: 14px;
+// }
+// }
+// }
+// }
+
+// .tree-children {
+// padding-left: 16px;
+// border-left: 1px dashed rgba(255, 255, 255, 0.1);
+// margin-left: 6px;
+// }
+// }
+
+// // .tree-node {
+// // display: flex;
+// // flex-direction: column;
+// // outline: 2px solid transparent;
+// // transition: background 0.3s;
+
+// // .tree-node-content {
+// // display: flex;
+// // align-items: center;
+// // padding: 8px 9px;
+// // gap: 6px;
+// // transition: background 0.2s ease;
+// // position: relative;
+// // &.selected {
+// // background: #6f42c1;
+// // border-radius: 45px;
+// // }
+
+// // &:hover {
+// // outline: 1px solid pink;
+// // border-radius: 80px;
+// // }
+// // .node-wrapper {
+// // display: flex;
+// // gap: 40px;
+// // }
+
+// // .expand-button {
+// // background: none;
+// // border: none;
+// // cursor: pointer;
+// // width: 20px;
+// // height: 20px;
+// // padding: 0;
+// // color: #aaa;
+
+// // &:hover {
+// // color: #a855f7;
+// // }
+// // }
+
+// // .node-icon {
+// // color: white;
+// // width: 18px;
+// // flex-shrink: 0;
+// // }
+
+// // .rename-input {
+// // background: transparent;
+// // border: none;
+// // color: #fff;
+// // font-size: 14px;
+// // flex: 1;
+// // outline: none;
+// // padding: 2px 4px;
+// // &:focus {
+// // border-bottom: 1px solid #a855f7;
+// // }
+// // .renaming {
+// // outline: none;
+// // border: none;
+// // border-radius: 15px;
+// // height: 20px;
+// // }
+// // }
+
+// // .node-controls {
+// // display: flex;
+// // gap: 6px;
+
+// // .control-button {
+// // background: none;
+// // border: none;
+// // color: #888;
+// // cursor: pointer;
+// // // padding: 4px;
+// // border-radius: 4px;
+
+// // &:hover {
+// // background-color: rgba(255, 255, 255, 0.1);
+// // color: #a855f7;
+// // }
+
+// // svg {
+// // width: 16px;
+// // height: 16px;
+// // }
+// // }
+// // }
+// // }
+// // }
+
+// // .tree-children {
+// // padding-left: 16px;
+// // border-left: 1px dashed rgba(255, 255, 255, 0.05);
+// // }
+
+// Outline Component Styles
+// Professional tree/hierarchy view with drag-and-drop support
+
+.outline-overlay {
+ padding: 0 4px;
+ font-family: "Segoe UI", sans-serif;
+ display: flex;
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ width: 100%;
+ pointer-events: none;
+ z-index: 1000;
+}
+
+.outline-card {
+ pointer-events: all;
+ width: 280px;
+ max-width: 100%;
+ border-radius: 10px;
+ border: 1px solid #2e2e3e;
+ background: linear-gradient(to bottom, #1e1e2f, #12121a);
+ box-shadow: 0 0 8px rgba(168, 85, 247, 0.2);
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+}
+
+.outline-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 10px 16px;
+ border-bottom: 1px solid #2e2e3e;
+}
+
+.header-title {
+ p {
+ margin: 0;
+ font-weight: 600;
+ font-size: 14px;
+ color: #ffffff;
+ }
+}
+
+.outline-toolbar {
+ display: flex;
+ gap: 8px;
+
+ .toolbar-button {
+ background: none;
+ border: none;
+ color: #aaa;
+ cursor: pointer;
+ width: 24px;
+ height: 24px;
+ border-radius: 4px;
+ transition: background 0.3s;
+
+ &:hover {
+ background-color: rgba(255, 255, 255, 0.1);
+ color: #a855f7;
+ }
+
+ svg {
+ width: 100%;
+ height: 100%;
+ }
+ }
+
+ .close-button {
+ background: none;
+ border: none;
+ color: #aaa;
+ cursor: pointer;
+ width: 24px;
+ height: 24px;
+ border-radius: 4px;
+ transition: transform 0.3s ease;
+
+ &:hover {
+ background-color: rgba(255, 255, 255, 0.1);
+ transform: rotate(-90deg);
+ color: #a855f7;
+ }
+
+ svg {
+ width: 100%;
+ height: 100%;
+ }
+ }
+}
+
+.outline-content {
+ max-height: 52vh;
+ overflow-y: auto;
+ padding: 8px 0;
+
+ &::-webkit-scrollbar {
+ width: 6px;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background: #a855f7;
+ border-radius: 10px;
+ }
+}
+
+.tree-node {
+ display: flex;
+ flex-direction: column;
+ transition: background 0.3s;
+
+ &.dragging {
+ opacity: 0.5;
+ }
+}
+
+.tree-node-content {
+ display: flex;
+ align-items: center;
+ padding: 8px 12px;
+ gap: 8px;
+ transition: background 0.2s ease;
+ position: relative;
+ cursor: pointer;
+ border-radius: 6px;
+ margin: 2px 8px;
+
+ &.selected {
+ background: #6f42c1;
+ border-radius: 50px;
+ }
+
+ &:hover {
+ outline: 2px solid #b188ff;
+ border-radius: 50px;
+ }
+ > div {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ flex: 1;
+ }
+
+ &.locked {
+ opacity: 0.6;
+ cursor: not-allowed;
+ transform: scale(0.98);
+ background-color: transparent;
+ outline: none;
+ border: none;
+ }
+
+ &.hidden {
+ opacity: 0.4;
+ }
+}
+
+.expand-button {
+ background: none;
+ border: none;
+ cursor: pointer;
+ width: 20px;
+ height: 20px;
+ padding: 0;
+ color: #aaa;
+
+ &:hover {
+ color: #a855f7;
+ }
+}
+
+.node-icon {
+ color: #fff;
+ width: 18px;
+ flex-shrink: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ svg {
+ width: 18px;
+ height: 18px;
+ }
+}
+
+.rename-input {
+ background: transparent;
+ border: none;
+ color: #fff;
+ font-size: 14px;
+ flex: 1;
+ outline: none;
+ padding: 2px 4px;
+
+ &:focus {
+ border-bottom: 1px solid #a855f7;
+ }
+
+ input {
+ background: white;
+ border: none;
+ color: black;
+ font-size: 14px;
+ width: 100%;
+ outline: none;
+
+ &:focus {
+ border-bottom: 1px solid white;
+ border-radius: 30px;
+ height: 20px;
+ }
+ }
+
+ span {
+ display: block;
+ }
+}
+
+.node-controls {
+ display: flex;
+ gap: 6px;
+ margin-left: auto;
+
+ .control-button {
+ background: none;
+ border: none;
+ color: #888;
+ cursor: pointer;
+ padding: 4px;
+ border-radius: 4px;
+ transition: all 0.2s;
+
+ &:hover {
+ background-color: rgba(255, 255, 255, 0.1);
+ color: #a855f7;
+ }
+
+ svg {
+ width: 16px;
+ height: 16px;
+ }
+ }
+}
+
+.tree-children {
+ padding-left: 16px;
+ border-left: 1px dashed rgba(255, 255, 255, 0.05);
+}
+
+// Responsive adjustments
+@media (max-width: 768px) {
+ .outline-card {
+ width: 240px;
+ }
+
+ .outline-overlay {
+ padding: 0 2px;
+ }
+
+ .rename-input {
+ font-size: 13px;
+ }
+}
diff --git a/src/styles/main.scss b/src/styles/main.scss
new file mode 100644
index 0000000..7ddf49e
--- /dev/null
+++ b/src/styles/main.scss
@@ -0,0 +1 @@
+@use "components/outlinePanel.scss";