tyle: refresh editor controls with monochrome UI
This commit is contained in:
Generated
+13
-15
@@ -14,6 +14,7 @@
|
||||
"@react-three/rapier": "^2.2.0",
|
||||
"gsap": "^3.15.0",
|
||||
"lil-gui": "^0.21.0",
|
||||
"lucide-react": "^1.11.0",
|
||||
"r3f-perf": "^7.2.3",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
@@ -36,6 +37,9 @@
|
||||
"typescript": "~6.0.2",
|
||||
"typescript-eslint": "^8.58.0",
|
||||
"vite": "^8.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
@@ -2362,12 +2366,6 @@
|
||||
"integrity": "sha512-dMW4CWBTUK1AEEDeZc1g4xpPGIrSf9fJF960qbTZmN/QwZIWY5wgliS6JWl9/25fpTGJrMRtSjGtOmPnfjZB+A==",
|
||||
"license": "Standard 'no charge' license: https://gsap.com/standard-license."
|
||||
},
|
||||
"node_modules/gsap": {
|
||||
"version": "3.15.0",
|
||||
"resolved": "https://registry.npmjs.org/gsap/-/gsap-3.15.0.tgz",
|
||||
"integrity": "sha512-dMW4CWBTUK1AEEDeZc1g4xpPGIrSf9fJF960qbTZmN/QwZIWY5wgliS6JWl9/25fpTGJrMRtSjGtOmPnfjZB+A==",
|
||||
"license": "Standard 'no charge' license: https://gsap.com/standard-license."
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
@@ -2922,6 +2920,15 @@
|
||||
"yallist": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/lucide-react": {
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.11.0.tgz",
|
||||
"integrity": "sha512-UOhjdztXCgdBReRcIhsvz2siIBogfv/lhJEIViCpLt924dO+GDms9T7DNoucI23s6kEPpe988m5N0D2ajnzb2g==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/maath": {
|
||||
"version": "0.10.8",
|
||||
"resolved": "https://registry.npmjs.org/maath/-/maath-0.10.8.tgz",
|
||||
@@ -3159,15 +3166,6 @@
|
||||
"three": ">= 0.168.0 < 0.185.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postprocessing": {
|
||||
"version": "6.39.1",
|
||||
"resolved": "https://registry.npmjs.org/postprocessing/-/postprocessing-6.39.1.tgz",
|
||||
"integrity": "sha512-R2dG2zy+BAx3USl5EHw+PvnrlbT5PKnZVp3se0HCR0pWH8WQdh742yNG4YWOsq6c0bFpffk0Gd2RqPeoP/wKng==",
|
||||
"license": "Zlib",
|
||||
"peerDependencies": {
|
||||
"three": ">= 0.168.0 < 0.185.0"
|
||||
}
|
||||
},
|
||||
"node_modules/potpack": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz",
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"@react-three/rapier": "^2.2.0",
|
||||
"gsap": "^3.15.0",
|
||||
"lil-gui": "^0.21.0",
|
||||
"lucide-react": "^1.11.0",
|
||||
"r3f-perf": "^7.2.3",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
import {
|
||||
Box,
|
||||
Download,
|
||||
Expand,
|
||||
Keyboard,
|
||||
Lock,
|
||||
MousePointer2,
|
||||
Move3D,
|
||||
Redo2,
|
||||
RotateCw,
|
||||
Save,
|
||||
Undo2,
|
||||
} from "lucide-react";
|
||||
import type { TransformMode } from "@/types/editor";
|
||||
|
||||
interface EditorControlsProps {
|
||||
@@ -32,108 +45,192 @@ export function EditorControls({
|
||||
isPlayerMode,
|
||||
}: EditorControlsProps): React.JSX.Element {
|
||||
const cameraPosition = [0, 50, 100];
|
||||
const viewModeLabel = isPlayerMode ? "View locked" : "Lock view";
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="editor-camera-info">
|
||||
<div>Camera Position:</div>
|
||||
<div>X: {cameraPosition[0]!.toFixed(2)}</div>
|
||||
<div>Y: {cameraPosition[1]!.toFixed(2)}</div>
|
||||
<div>Z: {cameraPosition[2]!.toFixed(2)}</div>
|
||||
<span>Camera</span>
|
||||
<strong>
|
||||
X {cameraPosition[0]!.toFixed(0)} · Y {cameraPosition[1]!.toFixed(0)}{" "}
|
||||
· Z {cameraPosition[2]!.toFixed(0)}
|
||||
</strong>
|
||||
</div>
|
||||
|
||||
<div className="editor-controls-panel">
|
||||
<h3>Transform</h3>
|
||||
<aside className="editor-controls-panel" aria-label="Editor controls">
|
||||
<header className="editor-panel-header">
|
||||
<span className="editor-panel-kicker">Map Editor</span>
|
||||
<h2>Scene controls</h2>
|
||||
<p>Select an object, choose a transform mode, then drag the gizmo.</p>
|
||||
</header>
|
||||
|
||||
<div className="editor-transform-buttons">
|
||||
<button
|
||||
className={`editor-transform-button ${transformMode === "translate" ? "active" : ""}`}
|
||||
onClick={() => onTransformModeChange("translate")}
|
||||
>
|
||||
✋ Translate (T)
|
||||
</button>
|
||||
<button
|
||||
className={`editor-transform-button ${transformMode === "rotate" ? "active" : ""}`}
|
||||
onClick={() => onTransformModeChange("rotate")}
|
||||
>
|
||||
🔄 Rotate (R)
|
||||
</button>
|
||||
<button
|
||||
className={`editor-transform-button ${transformMode === "scale" ? "active" : ""}`}
|
||||
onClick={() => onTransformModeChange("scale")}
|
||||
>
|
||||
📐 Scale (S)
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="editor-history-buttons">
|
||||
<button
|
||||
className="editor-history-button"
|
||||
onClick={onUndo}
|
||||
disabled={undoCount === 0}
|
||||
style={{ color: undoCount > 0 ? "#00ff00" : "#555" }}
|
||||
>
|
||||
↩ Undo ({undoCount})
|
||||
</button>
|
||||
<button
|
||||
className="editor-history-button"
|
||||
onClick={onRedo}
|
||||
disabled={redoCount === 0}
|
||||
style={{ color: redoCount > 0 ? "#00ff00" : "#555" }}
|
||||
>
|
||||
↪ Redo ({redoCount})
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button className="editor-export-button" onClick={onExportJson}>
|
||||
💾 Export JSON
|
||||
</button>
|
||||
|
||||
{onSaveToServer && (
|
||||
<button className="editor-save-button" onClick={onSaveToServer}>
|
||||
💾 Save to Server
|
||||
</button>
|
||||
)}
|
||||
|
||||
<h3>View</h3>
|
||||
|
||||
{onPlayerMode && (
|
||||
<button
|
||||
className={`editor-player-button ${isPlayerMode ? "active" : ""}`}
|
||||
onClick={onPlayerMode}
|
||||
>
|
||||
🎮 Player Controller
|
||||
</button>
|
||||
)}
|
||||
|
||||
<h3>Selection</h3>
|
||||
{selectedNodeIndex !== null ? (
|
||||
<div className="editor-selected-info">
|
||||
<div className="editor-selected-name">
|
||||
Selected:{" "}
|
||||
<strong>
|
||||
{selectedNodeName || `Node ${selectedNodeIndex + 1}`}
|
||||
</strong>
|
||||
</div>
|
||||
<div className="editor-selected-index">
|
||||
Index: {selectedNodeIndex + 1} / {nodesCount}
|
||||
</div>
|
||||
<section
|
||||
className="editor-control-section"
|
||||
aria-labelledby="transform-heading"
|
||||
>
|
||||
<div className="editor-section-heading">
|
||||
<h3 id="transform-heading">Transform</h3>
|
||||
<span>T / R / S</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="editor-no-selection">No object selected</div>
|
||||
)}
|
||||
|
||||
<h3>Controls</h3>
|
||||
<div className="editor-controls-help">
|
||||
<p>Click - Select object</p>
|
||||
<p>T/R/S - Transform mode</p>
|
||||
<p>Ctrl+Z - Undo</p>
|
||||
<p>Ctrl+Y - Redo</p>
|
||||
<p>ESC - Deselect</p>
|
||||
<p>WASD/ZQSD - Move (Player mode)</p>
|
||||
<p>Space - Jump (Player mode)</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="editor-transform-buttons">
|
||||
<button
|
||||
className={`editor-transform-button ${transformMode === "translate" ? "active" : ""}`}
|
||||
onClick={() => onTransformModeChange("translate")}
|
||||
aria-pressed={transformMode === "translate"}
|
||||
>
|
||||
<Move3D size={16} aria-hidden="true" />
|
||||
<span>Translate</span>
|
||||
<kbd>T</kbd>
|
||||
</button>
|
||||
<button
|
||||
className={`editor-transform-button ${transformMode === "rotate" ? "active" : ""}`}
|
||||
onClick={() => onTransformModeChange("rotate")}
|
||||
aria-pressed={transformMode === "rotate"}
|
||||
>
|
||||
<RotateCw size={16} aria-hidden="true" />
|
||||
<span>Rotate</span>
|
||||
<kbd>R</kbd>
|
||||
</button>
|
||||
<button
|
||||
className={`editor-transform-button ${transformMode === "scale" ? "active" : ""}`}
|
||||
onClick={() => onTransformModeChange("scale")}
|
||||
aria-pressed={transformMode === "scale"}
|
||||
>
|
||||
<Expand size={16} aria-hidden="true" />
|
||||
<span>Scale</span>
|
||||
<kbd>S</kbd>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="editor-history-buttons">
|
||||
<button
|
||||
className="editor-history-button"
|
||||
onClick={onUndo}
|
||||
disabled={undoCount === 0}
|
||||
>
|
||||
<Undo2 size={15} aria-hidden="true" />
|
||||
Undo
|
||||
<span>{undoCount}</span>
|
||||
</button>
|
||||
<button
|
||||
className="editor-history-button"
|
||||
onClick={onRedo}
|
||||
disabled={redoCount === 0}
|
||||
>
|
||||
<Redo2 size={15} aria-hidden="true" />
|
||||
Redo
|
||||
<span>{redoCount}</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
className="editor-control-section"
|
||||
aria-labelledby="file-heading"
|
||||
>
|
||||
<div className="editor-section-heading">
|
||||
<h3 id="file-heading">File</h3>
|
||||
</div>
|
||||
|
||||
<button
|
||||
className="editor-action-button editor-action-button-primary"
|
||||
onClick={onExportJson}
|
||||
>
|
||||
<Download size={16} aria-hidden="true" />
|
||||
Export JSON
|
||||
</button>
|
||||
|
||||
{onSaveToServer && (
|
||||
<button className="editor-action-button" onClick={onSaveToServer}>
|
||||
<Save size={16} aria-hidden="true" />
|
||||
Save to server
|
||||
</button>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section
|
||||
className="editor-control-section"
|
||||
aria-labelledby="view-heading"
|
||||
>
|
||||
<div className="editor-section-heading">
|
||||
<h3 id="view-heading">View</h3>
|
||||
</div>
|
||||
|
||||
{onPlayerMode && (
|
||||
<button
|
||||
className={`editor-player-button ${isPlayerMode ? "active" : ""}`}
|
||||
onClick={onPlayerMode}
|
||||
aria-pressed={isPlayerMode}
|
||||
>
|
||||
<Lock size={16} aria-hidden="true" />
|
||||
{viewModeLabel}
|
||||
</button>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section
|
||||
className="editor-control-section"
|
||||
aria-labelledby="selection-heading"
|
||||
>
|
||||
<div className="editor-section-heading">
|
||||
<h3 id="selection-heading">Selection</h3>
|
||||
<span>{nodesCount} nodes</span>
|
||||
</div>
|
||||
|
||||
{selectedNodeIndex !== null ? (
|
||||
<div className="editor-selected-info">
|
||||
<Box size={17} aria-hidden="true" />
|
||||
<div>
|
||||
<strong>
|
||||
{selectedNodeName || `Node ${selectedNodeIndex + 1}`}
|
||||
</strong>
|
||||
<span>
|
||||
Index {selectedNodeIndex + 1} of {nodesCount}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="editor-no-selection">
|
||||
<MousePointer2 size={17} aria-hidden="true" />
|
||||
No object selected
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section
|
||||
className="editor-control-section"
|
||||
aria-labelledby="shortcuts-heading"
|
||||
>
|
||||
<div className="editor-section-heading">
|
||||
<h3 id="shortcuts-heading">Shortcuts</h3>
|
||||
<Keyboard size={15} aria-hidden="true" />
|
||||
</div>
|
||||
|
||||
<dl className="editor-shortcuts-list">
|
||||
<div>
|
||||
<dt>Click</dt>
|
||||
<dd>Select object</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>T / R / S</dt>
|
||||
<dd>Transform mode</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Ctrl Z / Y</dt>
|
||||
<dd>Undo / redo</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Esc</dt>
|
||||
<dd>Deselect</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>WASD</dt>
|
||||
<dd>Move when locked</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
</aside>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -70,10 +70,10 @@ export function EditorMap({
|
||||
args={[100, 100]}
|
||||
cellSize={1}
|
||||
cellThickness={0.5}
|
||||
cellColor="#444444"
|
||||
cellColor="#242424"
|
||||
sectionSize={5}
|
||||
sectionThickness={1}
|
||||
sectionColor="#666666"
|
||||
sectionColor="#3a3a3a"
|
||||
fadeDistance={50}
|
||||
fadeStrength={1}
|
||||
followCamera={false}
|
||||
@@ -199,10 +199,10 @@ function EditorModelNode({
|
||||
) {
|
||||
if (isSelected) {
|
||||
mesh.material = mesh.material.clone();
|
||||
(mesh.material as THREE.MeshStandardMaterial).color.set("#ff6600");
|
||||
(mesh.material as THREE.MeshStandardMaterial).color.set("#ffffff");
|
||||
} else if (isHovered) {
|
||||
mesh.material = mesh.material.clone();
|
||||
(mesh.material as THREE.MeshStandardMaterial).color.set("#ff9900");
|
||||
(mesh.material as THREE.MeshStandardMaterial).color.set("#b8b8b8");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -281,7 +281,7 @@ function EditorFallbackNode({
|
||||
}
|
||||
}, [node.position, node.rotation, node.scale]);
|
||||
|
||||
const color = isSelected ? "#ff6600" : isHovered ? "#ff9900" : "#cccccc";
|
||||
const color = isSelected ? "#ffffff" : isHovered ? "#b8b8b8" : "#6f6f6f";
|
||||
|
||||
return (
|
||||
<mesh
|
||||
|
||||
+337
-168
@@ -87,8 +87,16 @@ canvas {
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
||||
font-family: system-ui, sans-serif;
|
||||
background: #050505;
|
||||
color: #f8f8f8;
|
||||
font-family:
|
||||
Inter,
|
||||
ui-sans-serif,
|
||||
system-ui,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
"Segoe UI",
|
||||
sans-serif;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -99,77 +107,88 @@ canvas {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
color: white;
|
||||
color: #f8f8f8;
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.editor-loading h2 {
|
||||
font-size: 2rem;
|
||||
color: #ff6600;
|
||||
margin-bottom: 1rem;
|
||||
font-size: clamp(1.8rem, 4vw, 3rem);
|
||||
color: #ffffff;
|
||||
margin: 0 0 0.75rem;
|
||||
letter-spacing: -0.05em;
|
||||
}
|
||||
|
||||
.editor-loading p {
|
||||
font-size: 1rem;
|
||||
color: #aaa;
|
||||
color: #9b9b9b;
|
||||
}
|
||||
|
||||
.editor-error h2 {
|
||||
font-size: 1.8rem;
|
||||
color: #ff4444;
|
||||
margin-bottom: 1rem;
|
||||
font-size: clamp(1.8rem, 4vw, 3rem);
|
||||
color: #ffffff;
|
||||
margin: 0 0 0.75rem;
|
||||
letter-spacing: -0.05em;
|
||||
}
|
||||
|
||||
.editor-error p {
|
||||
font-size: 1.1rem;
|
||||
color: #ccc;
|
||||
margin-bottom: 2rem;
|
||||
color: #b7b7b7;
|
||||
margin: 0 0 2rem;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.editor-container code {
|
||||
background: rgba(255, 102, 0, 0.2);
|
||||
background: #171717;
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 4px;
|
||||
color: #ff8533;
|
||||
font-family: "Courier New", monospace;
|
||||
color: #ffffff;
|
||||
font-family: "SFMono-Regular", "Courier New", monospace;
|
||||
}
|
||||
|
||||
.editor-upload-section {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
border: 2px dashed rgba(255, 102, 0, 0.3);
|
||||
max-width: 500px;
|
||||
margin-top: 2rem;
|
||||
width: min(520px, calc(100vw - 2rem));
|
||||
background: #0d0d0d;
|
||||
border-radius: 24px;
|
||||
padding: 1.25rem;
|
||||
border: 1px solid #2a2a2a;
|
||||
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
.editor-upload-section h3 {
|
||||
color: #ff6600;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.4rem;
|
||||
color: #ffffff;
|
||||
margin: 0 0 1rem;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 650;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.editor-drop-zone {
|
||||
display: block;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
padding: 2rem 1rem;
|
||||
border: 2px dashed #ff6600;
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 102, 0, 0.1);
|
||||
color: #ff6600;
|
||||
font-weight: bold;
|
||||
min-height: 116px;
|
||||
padding: 1.25rem;
|
||||
border: 1px dashed #5b5b5b;
|
||||
border-radius: 18px;
|
||||
background: #111111;
|
||||
color: #f8f8f8;
|
||||
font-weight: 650;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
transition:
|
||||
background 160ms ease,
|
||||
border-color 160ms ease,
|
||||
transform 160ms ease;
|
||||
font-size: 0.95rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.editor-drop-zone:hover {
|
||||
background: rgba(255, 102, 0, 0.2);
|
||||
border-color: #ff8533;
|
||||
background: #181818;
|
||||
border-color: #ffffff;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.editor-folder-input {
|
||||
@@ -177,213 +196,349 @@ canvas {
|
||||
}
|
||||
|
||||
.editor-folder-structure {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 8px;
|
||||
background: #080808;
|
||||
border: 1px solid #202020;
|
||||
border-radius: 16px;
|
||||
padding: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.editor-folder-structure h4 {
|
||||
color: #aaa;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #ffffff;
|
||||
margin: 0 0 0.5rem;
|
||||
font-size: 0.78rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
.editor-folder-structure pre {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
padding: 1rem;
|
||||
border-radius: 6px;
|
||||
color: #ddd;
|
||||
font-family: "Courier New", monospace;
|
||||
font-size: 0.9rem;
|
||||
margin: 0;
|
||||
background: transparent;
|
||||
color: #a7a7a7;
|
||||
font-family: "SFMono-Regular", "Courier New", monospace;
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.55;
|
||||
overflow-x: auto;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.editor-camera-info {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: #00ff00;
|
||||
padding: 10px 15px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #00ff00;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
top: 16px;
|
||||
left: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
z-index: 2;
|
||||
background: rgba(5, 5, 5, 0.78);
|
||||
color: #f8f8f8;
|
||||
padding: 8px 10px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
box-shadow: 0 16px 50px rgba(0, 0, 0, 0.35);
|
||||
backdrop-filter: blur(18px);
|
||||
font-size: 11px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.editor-camera-info span {
|
||||
color: #9b9b9b;
|
||||
}
|
||||
|
||||
.editor-camera-info strong {
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.editor-controls-panel {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 250px;
|
||||
height: 100%;
|
||||
background: rgba(30, 30, 30, 0.95);
|
||||
padding: 20px;
|
||||
color: white;
|
||||
border-left: 2px solid #ff6600;
|
||||
right: 16px;
|
||||
top: 16px;
|
||||
bottom: 16px;
|
||||
width: min(340px, calc(100vw - 32px));
|
||||
background: rgba(8, 8, 8, 0.88);
|
||||
padding: 14px;
|
||||
color: #f8f8f8;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 28px;
|
||||
box-shadow: 0 24px 90px rgba(0, 0, 0, 0.45);
|
||||
overflow-y: auto;
|
||||
font-family: system-ui, sans-serif;
|
||||
backdrop-filter: blur(22px);
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #3a3a3a transparent;
|
||||
}
|
||||
|
||||
.editor-controls-panel h3 {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 15px;
|
||||
font-size: 18px;
|
||||
color: #ff6600;
|
||||
.editor-controls-panel::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.editor-controls-panel::-webkit-scrollbar-thumb {
|
||||
background: #3a3a3a;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.editor-panel-header {
|
||||
padding: 12px 12px 16px;
|
||||
}
|
||||
|
||||
.editor-panel-kicker {
|
||||
color: #8f8f8f;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.16em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.editor-panel-header h2 {
|
||||
margin: 0.35rem 0 0.45rem;
|
||||
color: #ffffff;
|
||||
font-size: 1.55rem;
|
||||
font-weight: 720;
|
||||
letter-spacing: -0.06em;
|
||||
}
|
||||
|
||||
.editor-panel-header p {
|
||||
margin: 0;
|
||||
color: #a3a3a3;
|
||||
font-size: 0.84rem;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.editor-control-section {
|
||||
padding: 14px 12px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.09);
|
||||
}
|
||||
|
||||
.editor-section-heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.editor-section-heading h3 {
|
||||
margin: 0;
|
||||
color: #ffffff;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.editor-section-heading span,
|
||||
.editor-section-heading svg {
|
||||
color: #777777;
|
||||
font-size: 0.74rem;
|
||||
}
|
||||
|
||||
.editor-transform-buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.editor-transform-button {
|
||||
padding: 12px;
|
||||
background: #333;
|
||||
color: white;
|
||||
border: 1px solid #555;
|
||||
border-radius: 6px;
|
||||
display: grid;
|
||||
grid-template-columns: 18px 1fr auto;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
padding: 10px 11px;
|
||||
background: #101010;
|
||||
color: #d9d9d9;
|
||||
border: 1px solid #242424;
|
||||
border-radius: 14px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
transition: all 0.2s;
|
||||
font-size: 0.88rem;
|
||||
font-weight: 620;
|
||||
text-align: left;
|
||||
transition:
|
||||
background 160ms ease,
|
||||
border-color 160ms ease,
|
||||
color 160ms ease,
|
||||
transform 160ms ease;
|
||||
}
|
||||
|
||||
.editor-transform-button.active {
|
||||
background: #ff6600;
|
||||
color: black;
|
||||
border-color: #ff6600;
|
||||
background: #ffffff;
|
||||
color: #050505;
|
||||
border-color: #ffffff;
|
||||
}
|
||||
|
||||
.editor-transform-button:hover {
|
||||
background: #444;
|
||||
background: #191919;
|
||||
border-color: #5c5c5c;
|
||||
color: #ffffff;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.editor-transform-button.active:hover {
|
||||
background: #ff8533;
|
||||
background: #ffffff;
|
||||
color: #050505;
|
||||
}
|
||||
|
||||
.editor-transform-button kbd {
|
||||
min-width: 22px;
|
||||
padding: 3px 6px;
|
||||
border-radius: 7px;
|
||||
background: rgba(0, 0, 0, 0.08);
|
||||
color: currentColor;
|
||||
font-family: inherit;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 720;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.editor-history-buttons {
|
||||
display: flex;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.editor-history-button {
|
||||
flex: 1;
|
||||
padding: 8px;
|
||||
background: #333;
|
||||
color: #aaa;
|
||||
border: 1px solid #555;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 7px;
|
||||
padding: 9px;
|
||||
background: #101010;
|
||||
color: #f2f2f2;
|
||||
border: 1px solid #242424;
|
||||
border-radius: 13px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.editor-history-button span {
|
||||
color: #8e8e8e;
|
||||
}
|
||||
|
||||
.editor-history-button:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.editor-export-button {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
padding: 12px;
|
||||
background: #ff6600;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.editor-export-button:hover {
|
||||
background: #ff8533;
|
||||
}
|
||||
|
||||
.editor-save-button {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
padding: 12px;
|
||||
background: #22c55e;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.editor-save-button:hover {
|
||||
background: #16a34a;
|
||||
opacity: 0.38;
|
||||
}
|
||||
|
||||
.editor-action-button,
|
||||
.editor-player-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 9px;
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background: #444;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
padding: 11px 12px;
|
||||
background: #101010;
|
||||
color: #f2f2f2;
|
||||
border: 1px solid #242424;
|
||||
border-radius: 14px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-size: 0.88rem;
|
||||
font-weight: 680;
|
||||
transition:
|
||||
background 160ms ease,
|
||||
border-color 160ms ease,
|
||||
color 160ms ease,
|
||||
transform 160ms ease;
|
||||
}
|
||||
|
||||
.editor-player-button.active {
|
||||
background: #ff6600;
|
||||
color: black;
|
||||
.editor-action-button + .editor-action-button {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.editor-action-button:hover,
|
||||
.editor-player-button:hover {
|
||||
background: #555;
|
||||
background: #191919;
|
||||
border-color: #5c5c5c;
|
||||
color: #ffffff;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.editor-action-button-primary,
|
||||
.editor-player-button.active {
|
||||
background: #ffffff;
|
||||
color: #050505;
|
||||
border-color: #ffffff;
|
||||
}
|
||||
|
||||
.editor-action-button-primary:hover,
|
||||
.editor-player-button.active:hover {
|
||||
background: #ff8533;
|
||||
background: #ffffff;
|
||||
color: #050505;
|
||||
}
|
||||
|
||||
.editor-selected-info {
|
||||
background: rgba(255, 102, 0, 0.1);
|
||||
border: 1px solid #ff6600;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
margin-bottom: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 11px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #ffffff;
|
||||
border-radius: 16px;
|
||||
padding: 12px;
|
||||
color: #050505;
|
||||
}
|
||||
|
||||
.editor-selected-name {
|
||||
font-size: 16px;
|
||||
margin-bottom: 5px;
|
||||
.editor-selected-info strong,
|
||||
.editor-selected-info span {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.editor-selected-index {
|
||||
font-size: 14px;
|
||||
color: #aaa;
|
||||
.editor-selected-info strong {
|
||||
font-size: 0.92rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.editor-selected-info span {
|
||||
color: #555555;
|
||||
font-size: 0.75rem;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.editor-no-selection {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px dashed #555;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
text-align: center;
|
||||
color: #888;
|
||||
font-style: italic;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
background: #101010;
|
||||
border: 1px dashed #363636;
|
||||
border-radius: 16px;
|
||||
padding: 12px;
|
||||
color: #8f8f8f;
|
||||
font-size: 0.86rem;
|
||||
}
|
||||
|
||||
.editor-controls-help {
|
||||
background: #222;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
border: 1px solid #444;
|
||||
.editor-shortcuts-list {
|
||||
display: grid;
|
||||
gap: 7px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.editor-controls-help p {
|
||||
margin: 4px 0;
|
||||
font-size: 12px;
|
||||
color: #aaa;
|
||||
.editor-shortcuts-list div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
padding: 7px 0;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.editor-shortcuts-list div:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.editor-shortcuts-list dt,
|
||||
.editor-shortcuts-list dd {
|
||||
margin: 0;
|
||||
font-size: 0.76rem;
|
||||
}
|
||||
|
||||
.editor-shortcuts-list dt {
|
||||
color: #ffffff;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.editor-shortcuts-list dd {
|
||||
color: #8d8d8d;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
@@ -398,4 +553,18 @@ canvas {
|
||||
.editor-drop-zone {
|
||||
padding: 1.5rem 1rem;
|
||||
}
|
||||
|
||||
.editor-camera-info {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.editor-controls-panel {
|
||||
top: auto;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
width: auto;
|
||||
max-height: 46vh;
|
||||
border-radius: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ export function EditorPage(): React.JSX.Element {
|
||||
camera={{ position: [0, 50, 100], fov: 50 }}
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
onCreated={({ gl }) => {
|
||||
gl.setClearColor("#1e293b");
|
||||
gl.setClearColor("#050505");
|
||||
}}
|
||||
>
|
||||
<EditorScene
|
||||
|
||||
Reference in New Issue
Block a user