feat: standardize Beam viewer environment settings

This commit is contained in:
CODEX 2026-06-05 17:04:11 +03:00
parent be2294d3fa
commit ef5b0a42a6
12 changed files with 556 additions and 194 deletions

View File

@ -6,8 +6,12 @@
--loading-top: #1c1c1c; --loading-top: #1c1c1c;
--loading-bottom: #1c1c1c; --loading-bottom: #1c1c1c;
--modal-bg: #1c1c1c; --modal-bg: #1c1c1c;
--modal-bg-alpha: 100%;
--modal-focus: #292929; --modal-focus: #292929;
--modal-border: #292929; --modal-focus-alpha: 35%;
--modal-border: #1c1c1c;
--modal-element: #262626;
--modal-element-outline: #262626;
--modal-radius: 14px; --modal-radius: 14px;
--panel: #1c1c1c; --panel: #1c1c1c;
--panel-2: #292929; --panel-2: #292929;
@ -21,27 +25,34 @@
--shadow: 0 18px 48px rgba(0, 0, 0, 0.35); --shadow: 0 18px 48px rgba(0, 0, 0, 0.35);
--panel-alpha: 1; --panel-alpha: 1;
--panel-radius: 14px; --panel-radius: 14px;
--toolbar-bg: #1c1c1c; --toolbar-bg: #292929;
--toolbar-bg-active: #ffea00; --toolbar-bg-active: #c4ff67;
--toolbar-border: #233044; --toolbar-border: #292929;
--template-fill: #1c1c1c; --toolbar-outline: #292929;
--template-outline: #233044; --template-fill: #292929;
--template-outline-hover: rgba(255, 234, 0, 0.7); --template-outline: #292929;
--template-outline-hover: #1c1c1c;
--project-btn-fill: #292929;
--project-btn-border: #292929;
--project-btn-hover: #1c1c1c;
--modal-btn-primary: #c4ff67;
--modal-btn-secondary: #292929;
--loader-color: #c4ff67;
--template-glow-strength: 80%; --template-glow-strength: 80%;
--highlight-color: #ffea00; --highlight-color: #c4ff67;
--cube-text: #242424; --cube-text: #242424;
--cube-edges: #e0e7ff; --cube-edges: #e0e7ff;
--cube-base: #ffea00; --cube-base: #ffffff;
--cube-hover: #ffffff; --cube-hover: #ffffff;
--cube-edge-color: #e0e7ff; --cube-edge-color: #e0e7ff;
--cube-edge-alpha: 0.6; --cube-edge-alpha: 0.6;
--plus-color: #ffea00; --plus-color: #c4ff67;
--measure-line: #ffea00; --measure-line: #c4ff67;
--measure-label: #ffea00; --measure-label: #c4ff67;
--measure-label-text: #292929; --measure-label-text: #292929;
--measure-dot: #ffea00; --measure-dot: #c4ff67;
--nodedc-glass-control-bg: var(--modal-focus); --nodedc-glass-control-bg: var(--modal-element);
--nodedc-glass-control-hover: color-mix(in srgb, var(--modal-focus) 86%, white 8%); --nodedc-glass-control-hover: color-mix(in srgb, var(--modal-element) 86%, white 8%);
--nodedc-glass-control-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.01); --nodedc-glass-control-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.01);
--tb-size: 25px; --tb-size: 25px;
--tb-gap: 10px; --tb-gap: 10px;
@ -341,6 +352,12 @@ header {
.side-panel { .side-panel {
position: fixed; position: fixed;
--env-control-h: 46px;
--env-button-radius: 17px;
--env-pill-radius: 999px;
--env-glass-control-bg: var(--nodedc-glass-control-bg);
--env-glass-control-hover: var(--nodedc-glass-control-hover);
--env-glass-control-shadow: var(--nodedc-glass-control-shadow);
top: 170px; top: 170px;
right: 12px; right: 12px;
bottom: 12px; bottom: 12px;
@ -350,11 +367,11 @@ header {
background: color-mix(in srgb, var(--modal-bg) var(--modal-bg-alpha, 100%), transparent); background: color-mix(in srgb, var(--modal-bg) var(--modal-bg-alpha, 100%), transparent);
border: 1px solid var(--modal-border); border: 1px solid var(--modal-border);
border-radius: var(--modal-radius, 16px); border-radius: var(--modal-radius, 16px);
padding: 14px 14px 10px; padding: 12px;
box-shadow: var(--shadow); box-shadow: var(--shadow);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 12px; gap: 10px;
z-index: 11; z-index: 11;
display: none; display: none;
} }
@ -364,52 +381,60 @@ header {
} }
.inspector-panel-header { .inspector-panel-header {
display: flex; position: sticky;
top: -12px;
z-index: 5;
flex: 0 0 auto;
display: grid;
grid-template-columns: minmax(0, 1fr) var(--env-control-h);
align-items: center; align-items: center;
justify-content: space-between; gap: 10px;
min-height: 28px; height: calc(var(--env-control-h) + 20px);
margin: 0 0 8px; min-height: var(--env-control-h);
margin: -12px -12px 0;
padding: 12px 12px 8px 16px;
background: color-mix(in srgb, var(--modal-bg) var(--modal-bg-alpha, 100%), transparent);
border-radius: var(--modal-radius, 16px) var(--modal-radius, 16px) 0 0;
color: var(--text);
font-weight: 700;
} }
.inspector-panel-header h3 { .inspector-panel-header h3 {
margin: 0; margin: 0;
min-width: 0;
font-size: 13px;
line-height: 1.2;
} }
.inspector-collapse-btn { .inspector-collapse-btn {
position: relative;
display: grid; display: grid;
place-items: center; place-items: center;
width: 28px; width: var(--env-control-h);
height: 28px; height: var(--env-control-h);
margin: -2px -2px 0 8px; margin: 0;
padding: 0; padding: 0;
border: 0; border: 0;
border-radius: 10px; border-radius: var(--env-button-radius);
background: transparent; background: var(--env-glass-control-bg);
color: var(--modal-btn-primary, var(--accent)); color: rgba(245, 245, 250, 0.78);
box-shadow: var(--env-glass-control-shadow);
cursor: pointer; cursor: pointer;
font: inherit;
font-size: 0;
font-weight: 900;
line-height: 1; line-height: 1;
outline: none; outline: none;
transition: background 140ms ease, transform 100ms ease; backdrop-filter: blur(24px) saturate(128%) brightness(1.05);
-webkit-backdrop-filter: blur(24px) saturate(128%) brightness(1.05);
transition: background 140ms ease, color 140ms ease, transform 100ms ease;
} }
.inspector-collapse-btn::before { .inspector-collapse-btn svg {
content: ""; display: block;
position: absolute; width: 16px;
left: 50%; height: 16px;
top: 38%;
width: 11px;
height: 2px;
border-radius: 999px;
background: currentColor;
transform: translate(-50%, -50%);
} }
.inspector-collapse-btn:hover { .inspector-collapse-btn:hover {
background: var(--nodedc-glass-control-hover); background: var(--env-glass-control-hover);
color: #fff;
} }
.inspector-collapse-btn:active { .inspector-collapse-btn:active {
@ -502,7 +527,7 @@ header {
padding: 10px 12px; padding: 10px 12px;
border-radius: var(--modal-radius, 10px); border-radius: var(--modal-radius, 10px);
border: 1px solid var(--modal-border); border: 1px solid var(--modal-border);
background: color-mix(in srgb, var(--modal-focus) var(--modal-focus-alpha, 100%), transparent); background: var(--modal-element);
color: var(--text); color: var(--text);
font-size: 14px; font-size: 14px;
outline: none; outline: none;
@ -625,7 +650,7 @@ header {
} }
.tree { .tree {
background: color-mix(in srgb, var(--modal-element) var(--modal-focus-alpha, 100%), transparent); background: var(--modal-element);
border-radius: 10px; border-radius: 10px;
border: 1px solid var(--modal-element-outline, var(--modal-element)); border: 1px solid var(--modal-element-outline, var(--modal-element));
height: 260px; height: 260px;
@ -722,7 +747,7 @@ header {
vertical-align: -1px; vertical-align: -1px;
border: 0; border: 0;
border-radius: 4px; border-radius: 4px;
background: color-mix(in srgb, var(--modal-element) var(--modal-focus-alpha, 100%), transparent); background: var(--modal-element);
box-shadow: inset 0 0 0 1px var(--modal-element-outline, var(--modal-element)); box-shadow: inset 0 0 0 1px var(--modal-element-outline, var(--modal-element));
cursor: pointer; cursor: pointer;
transition: transition:
@ -731,7 +756,7 @@ header {
} }
.tree li > input[type="checkbox"]:checked { .tree li > input[type="checkbox"]:checked {
background: var(--highlight-color, var(--accent)); background: var(--modal-btn-primary, var(--accent));
box-shadow: none; box-shadow: none;
} }
@ -740,7 +765,7 @@ header {
} }
.tree li > input[type="checkbox"]:checked:hover { .tree li > input[type="checkbox"]:checked:hover {
background: color-mix(in srgb, var(--highlight-color, var(--accent)) 86%, white 14%); background: color-mix(in srgb, var(--modal-btn-primary, var(--accent)) 86%, white 14%);
} }
.tree li > input[type="checkbox"]:focus, .tree li > input[type="checkbox"]:focus,
@ -914,7 +939,7 @@ header {
} }
.nodedc-dropdown-option.nodedc-select__option[data-selected="true"] { .nodedc-dropdown-option.nodedc-select__option[data-selected="true"] {
background: var(--modal-focus); background: var(--modal-element);
color: rgba(255, 255, 255, 0.96); color: rgba(255, 255, 255, 0.96);
} }
@ -935,12 +960,17 @@ header {
} }
.templates-panel { .templates-panel {
position: fixed; position: fixed;
--env-control-h: 46px;
--env-button-radius: 17px;
--env-glass-control-bg: var(--nodedc-glass-control-bg);
--env-glass-control-hover: var(--nodedc-glass-control-hover);
--env-glass-control-shadow: var(--nodedc-glass-control-shadow);
left: var(--templates-left, 72px); left: var(--templates-left, 72px);
bottom: var(--templates-bottom, 12px); bottom: var(--templates-bottom, 12px);
background: color-mix(in srgb, var(--modal-bg) var(--modal-bg-alpha, 100%), transparent); background: color-mix(in srgb, var(--modal-bg) var(--modal-bg-alpha, 100%), transparent);
border: 1px solid var(--modal-border); border: 1px solid var(--modal-border);
border-radius: var(--modal-radius, 14px); border-radius: var(--modal-radius, 14px);
padding: 10px; padding: 12px;
display: grid; display: grid;
gap: 10px; gap: 10px;
width: var(--templates-width, 280px); width: var(--templates-width, 280px);
@ -957,28 +987,71 @@ header {
} }
.templates-panel .templates-header { .templates-panel .templates-header {
display: flex; position: sticky;
top: -12px;
z-index: 5;
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
align-items: center; align-items: center;
justify-content: space-between; gap: 10px;
padding: 6px 6px 0 6px; height: calc(var(--env-control-h) + 20px);
min-height: var(--env-control-h);
margin: -12px -12px 0;
padding: 12px 12px 8px 16px;
border-radius: var(--modal-radius, 14px) var(--modal-radius, 14px) 0 0;
background: color-mix(in srgb, var(--modal-bg) var(--modal-bg-alpha, 100%), transparent);
color: var(--text); color: var(--text);
font-weight: 700; font-weight: 700;
} }
.templates-panel .templates-title { .templates-panel .templates-title {
font-size: 14px; font-size: 13px;
line-height: 1.2;
}
.templates-panel .templates-actions {
display: flex;
gap: 6px;
} }
.templates-panel .templates-close { .templates-panel .templates-close {
width: 26px;
height: 26px;
border-radius: 8px;
border: 1px solid var(--template-outline);
background: color-mix(in srgb, var(--template-fill) var(--modal-focus-alpha, 100%), transparent);
color: var(--text);
cursor: pointer;
display: grid; display: grid;
place-items: center; place-items: center;
width: var(--env-control-h);
height: var(--env-control-h);
padding: 0;
border: 0;
border-radius: var(--env-button-radius);
background: var(--env-glass-control-bg);
color: rgba(245, 245, 250, 0.78);
box-shadow: var(--env-glass-control-shadow);
cursor: pointer;
line-height: 1;
outline: none;
backdrop-filter: blur(24px) saturate(128%) brightness(1.05);
-webkit-backdrop-filter: blur(24px) saturate(128%) brightness(1.05);
transition: background 140ms ease, color 140ms ease, transform 100ms ease;
}
.templates-panel .templates-close svg {
display: block;
width: 16px;
height: 16px;
}
.templates-panel .templates-close:hover {
background: var(--env-glass-control-hover);
color: #fff;
}
.templates-panel .templates-close:active {
transform: translateY(1px);
}
.templates-panel .templates-close:focus,
.templates-panel .templates-close:focus-visible {
outline: none;
box-shadow: none;
} }
.templates-panel .template-list { .templates-panel .template-list {
@ -1155,7 +1228,7 @@ header {
padding: 12px; padding: 12px;
border-radius: var(--modal-radius, 10px); border-radius: var(--modal-radius, 10px);
border: 1px solid var(--modal-border); border: 1px solid var(--modal-border);
background: color-mix(in srgb, var(--modal-focus) var(--modal-focus-alpha, 1), transparent); background: var(--modal-element);
color: var(--text); color: var(--text);
font-size: 14px; font-size: 14px;
outline: none; outline: none;
@ -1220,14 +1293,21 @@ header {
} }
.settings-panel .settings-header { .settings-panel .settings-header {
position: sticky;
top: -12px;
z-index: 5;
display: grid; display: grid;
grid-template-columns: minmax(0, 1fr) var(--env-control-h); grid-template-columns: minmax(0, 1fr) var(--env-control-h);
align-items: center; align-items: center;
gap: 10px; gap: 10px;
color: var(--text); color: var(--text);
font-weight: 700; font-weight: 700;
height: calc(var(--env-control-h) + 20px);
min-height: var(--env-control-h); min-height: var(--env-control-h);
padding: 0 0 0 4px; margin: -12px -12px 0;
padding: 12px 12px 8px 16px;
background: color-mix(in srgb, var(--modal-bg) var(--modal-bg-alpha, 100%), transparent);
border-radius: var(--modal-radius, 14px) var(--modal-radius, 14px) 0 0;
} }
.settings-panel .settings-title { .settings-panel .settings-title {
@ -1290,6 +1370,63 @@ header {
margin-bottom: 2px; margin-bottom: 2px;
} }
.settings-panel .checkbox-row {
display: grid;
grid-template-columns: minmax(0, 1fr) 26px;
align-items: center;
gap: 10px;
min-height: var(--env-control-h);
padding: 5px 7px 5px 18px;
border-radius: var(--env-pill-radius);
background: var(--env-glass-control-bg);
box-shadow: var(--env-glass-control-shadow);
cursor: pointer;
backdrop-filter: blur(24px) saturate(128%) brightness(1.05);
-webkit-backdrop-filter: blur(24px) saturate(128%) brightness(1.05);
}
.settings-panel .checkbox-row__label {
min-width: 0;
overflow: hidden;
color: rgba(255, 255, 255, 0.9);
font-size: 12px;
font-weight: 760;
line-height: 1;
text-overflow: ellipsis;
white-space: nowrap;
}
.settings-panel .checkbox-row input[type="checkbox"] {
appearance: none;
-webkit-appearance: none;
width: 26px;
height: 26px;
margin: 0;
border: 0;
border-radius: 999px;
background: var(--modal-bg);
cursor: pointer;
transition: background 140ms ease, transform 100ms ease;
}
.settings-panel .checkbox-row input[type="checkbox"]:checked {
background: var(--modal-btn-primary, var(--accent));
}
.settings-panel .checkbox-row:hover {
background: var(--env-glass-control-hover);
}
.settings-panel .checkbox-row input[type="checkbox"]:active {
transform: scale(0.94);
}
.settings-panel .checkbox-row input[type="checkbox"]:focus,
.settings-panel .checkbox-row input[type="checkbox"]:focus-visible {
outline: none;
box-shadow: none;
}
.settings-panel .color-row { .settings-panel .color-row {
display: grid; display: grid;
grid-template-columns: minmax(110px, 1fr) minmax(150px, 168px); grid-template-columns: minmax(110px, 1fr) minmax(150px, 168px);
@ -1407,7 +1544,7 @@ header {
top: 50%; top: 50%;
z-index: 2; z-index: 2;
font-size: 12px; font-size: 12px;
font-weight: 760; font-weight: 500;
line-height: 1; line-height: 1;
transform: translateY(-50%); transform: translateY(-50%);
pointer-events: none; pointer-events: none;
@ -1659,13 +1796,47 @@ header {
.settings-panel input[type="number"] { .settings-panel input[type="number"] {
width: 100%; width: 100%;
max-width: 86px; max-width: 86px;
background: color-mix(in srgb, var(--modal-element) var(--modal-focus-alpha, 100%), transparent); background: var(--modal-element);
border: 1px solid var(--modal-element-outline, var(--modal-element)); border: 1px solid var(--modal-element-outline, var(--modal-element));
border-radius: 8px; border-radius: 8px;
color: var(--text); color: var(--text);
padding: 5px 6px; padding: 5px 6px;
} }
.settings-panel input.env-number-field[type="number"] {
width: 100%;
max-width: none;
height: var(--env-control-h);
border: 0;
border-radius: var(--env-pill-radius);
padding: 0 18px;
background: var(--env-glass-control-bg);
box-shadow: var(--env-glass-control-shadow);
color: rgba(255, 255, 255, 0.86);
font-size: 12px;
font-weight: 500;
line-height: 1;
text-align: center;
outline: none;
appearance: textfield;
-moz-appearance: textfield;
backdrop-filter: blur(24px) saturate(128%) brightness(1.05);
-webkit-backdrop-filter: blur(24px) saturate(128%) brightness(1.05);
}
.settings-panel input.env-number-field[type="number"]::-webkit-outer-spin-button,
.settings-panel input.env-number-field[type="number"]::-webkit-inner-spin-button {
margin: 0;
appearance: none;
-webkit-appearance: none;
}
.settings-panel input.env-number-field[type="number"]:focus,
.settings-panel input.env-number-field[type="number"]:focus-visible {
border: 0;
box-shadow: var(--env-glass-control-shadow);
}
.settings-panel .env-color-field .env-color-field__value input[type="text"] { .settings-panel .env-color-field .env-color-field__value input[type="text"] {
width: 100%; width: 100%;
max-width: none; max-width: none;
@ -1689,7 +1860,7 @@ header {
} }
.log { .log {
background: color-mix(in srgb, var(--modal-element) var(--modal-focus-alpha, 100%), transparent); background: var(--modal-element);
border: 1px solid var(--modal-element-outline, var(--modal-element)); border: 1px solid var(--modal-element-outline, var(--modal-element));
} }
@ -1810,7 +1981,7 @@ header {
.transform-grid input { .transform-grid input {
flex: 1; flex: 1;
background: color-mix(in srgb, var(--modal-element) var(--modal-focus-alpha, 100%), transparent); background: var(--modal-element);
border: 1px solid var(--modal-element-outline, var(--modal-element)); border: 1px solid var(--modal-element-outline, var(--modal-element));
border-radius: 8px; border-radius: 8px;
padding: 6px 8px; padding: 6px 8px;

View File

@ -3,8 +3,8 @@ const SDK_URL = "./lib/dc-camera-context.es.js";
import { createLogo } from "./src/ui/logo.js"; import { createLogo } from "./src/ui/logo.js";
import { toolbarConfig, applyToolbarVars } from "./src/ui/toolbar.js"; import { toolbarConfig, applyToolbarVars } from "./src/ui/toolbar.js";
import { createTemplatesMenu } from "./src/ui/templatesMenu.js"; import { createTemplatesMenu } from "./src/ui/templatesMenu.js?v=2";
import { createSettingsMenu } from "./src/ui/settingsMenu.js?v=2"; import { createSettingsMenu } from "./src/ui/settingsMenu.js?v=5";
import { createMeasurementModal } from "./src/ui/measurementModal.js"; import { createMeasurementModal } from "./src/ui/measurementModal.js";
import { createGlassSelect } from "./src/ui/glassSelect.js"; import { createGlassSelect } from "./src/ui/glassSelect.js";
import { createSliderControl } from "./src/ui/controls/slider.js"; import { createSliderControl } from "./src/ui/controls/slider.js";

View File

@ -52,7 +52,9 @@
set("--toolbar-border", s.toolbarBorder); set("--toolbar-border", s.toolbarBorder);
set("--toolbar-outline", s.toolbarOutline); set("--toolbar-outline", s.toolbarOutline);
set("--modal-bg", s.modalBg); set("--modal-bg", s.modalBg);
set("--modal-bg-alpha", s.modalBgAlpha !== undefined ? `${s.modalBgAlpha * 100}%` : undefined);
set("--modal-focus", s.modalFocus); set("--modal-focus", s.modalFocus);
set("--modal-focus-alpha", s.modalFocusAlpha !== undefined ? `${s.modalFocusAlpha * 100}%` : undefined);
set("--modal-border", s.modalBorder); set("--modal-border", s.modalBorder);
set("--modal-element", s.modalElement); set("--modal-element", s.modalElement);
set("--modal-element-outline", s.modalElementOutline); set("--modal-element-outline", s.modalElementOutline);
@ -87,7 +89,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@500;600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Manrope:wght@500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="./dcViewer.css?v=12"> <link rel="stylesheet" href="./dcViewer.css?v=16">
</head> </head>
<body> <body>
<header> <header>
@ -125,7 +127,6 @@
</section> </section>
<aside class="side-panel" id="viewerInspector"> <aside class="side-panel" id="viewerInspector">
<div class="panel-section">
<div class="inspector-panel-header"> <div class="inspector-panel-header">
<h3>Инспектор</h3> <h3>Инспектор</h3>
<button <button
@ -134,8 +135,13 @@
type="button" type="button"
title="Свернуть инспектор" title="Свернуть инспектор"
aria-label="Свернуть инспектор" aria-label="Свернуть инспектор"
>▁</button> >
<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true">
<path d="M4.25 4.25 11.75 11.75M11.75 4.25 4.25 11.75" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></path>
</svg>
</button>
</div> </div>
<div class="panel-section inspector-main-section">
<div id="treeContainer" class="tree"></div> <div id="treeContainer" class="tree"></div>
<div class="inspector-controls"> <div class="inspector-controls">
<div class="inspector-control-row inspector-control-row--half" id="displayModeControl"> <div class="inspector-control-row inspector-control-row--half" id="displayModeControl">
@ -251,6 +257,6 @@
<input id="fileInput" class="hidden" type="file" <input id="fileInput" class="hidden" type="file"
accept=".xkt,.glb,.gltf,.bim,.las,.laz,.obj,.stl"> accept=".xkt,.glb,.gltf,.bim,.las,.laz,.obj,.stl">
<script type="module" src="./dcViewer.js?v=17"></script> <script type="module" src="./dcViewer.js?v=21"></script>
</body> </body>
</html> </html>

View File

@ -2,7 +2,7 @@ import { createSliderControl } from "./controls/slider.js";
import { createColorControl } from "./controls/color.js"; import { createColorControl } from "./controls/color.js";
const defaultSettings = { const defaultSettings = {
themeVersion: "v4-beam-lime", themeVersion: "v5-drone-lime",
bgOuter: "#0f0f0f", bgOuter: "#0f0f0f",
canvasTop: "#1c1c1c", canvasTop: "#1c1c1c",
canvasBottom: "#1c1c1c", canvasBottom: "#1c1c1c",
@ -10,27 +10,27 @@ const defaultSettings = {
loadingBottom: "#1c1c1c", loadingBottom: "#1c1c1c",
accent: "#ffea00", accent: "#ffea00",
accent2: "#ffea00", accent2: "#ffea00",
templateFill: "#1c1c1c", templateFill: "#292929",
templateOutline: "#233044", templateOutline: "#292929",
templateOutlineHover: "rgba(255,234,0,0.7)", templateOutlineHover: "#1c1c1c",
projectBtnFill: "#1c1c1c", projectBtnFill: "#292929",
projectBtnBorder: "#233044", projectBtnBorder: "#292929",
projectBtnHover: "rgba(255,234,0,0.7)", projectBtnHover: "#1c1c1c",
modalBtnPrimary: "#c4ff67", modalBtnPrimary: "#c4ff67",
modalBtnSecondary: "#292929", modalBtnSecondary: "#292929",
loaderColor: "#c4ff67", loaderColor: "#c4ff67",
templateGlow: 80, templateGlow: 80,
toolbarBg: "#1c1c1c", toolbarBg: "#292929",
toolbarBgActive: "#c4ff67", toolbarBgActive: "#c4ff67",
toolbarBorder: "#233044", toolbarBorder: "#292929",
toolbarOutline: "#c4ff67", toolbarOutline: "#292929",
modalBg: "#1c1c1c", modalBg: "#1c1c1c",
modalBgAlpha: 1, modalBgAlpha: 1,
modalFocus: "#292929", modalFocus: "#292929",
modalFocusAlpha: 1, modalFocusAlpha: 0.35,
modalBorder: "#292929", modalBorder: "#1c1c1c",
modalElement: "#292929", modalElement: "#262626",
modalElementOutline: "#2b2b2b", modalElementOutline: "#262626",
modalRadius: "14px", modalRadius: "14px",
border: "#233044", border: "#233044",
text: "#e6edf3", text: "#e6edf3",
@ -255,6 +255,40 @@ export function createSettingsMenu({ panel, controls = {}, onChange = null, onSh
return slider; return slider;
}; };
const addNumber = ({
parent,
label,
value,
min = 0,
max,
step = 1,
onChange,
}) => {
const row = document.createElement("div");
row.className = "color-row";
const lbl = document.createElement("div");
lbl.textContent = label;
const input = document.createElement("input");
input.className = "env-number-field";
input.type = "number";
input.value = value;
input.min = min;
input.step = step;
if (max !== undefined) {
input.max = max;
}
input.addEventListener("input", () => {
const nextValue = Number(input.value);
if (Number.isFinite(nextValue)) {
onChange?.(nextValue);
}
});
row.appendChild(lbl);
row.appendChild(input);
parent.appendChild(row);
return input;
};
const font = section("Фон"); const font = section("Фон");
addColor(font, "Фон внешний", "bgOuter"); addColor(font, "Фон внешний", "bgOuter");
addColor(font, "Фон холста верх", "canvasTop"); addColor(font, "Фон холста верх", "canvasTop");
@ -327,16 +361,17 @@ export function createSettingsMenu({ panel, controls = {}, onChange = null, onSh
const viewport = section("Viewport"); const viewport = section("Viewport");
const addCheck = (label, checked, handler) => { const addCheck = (label, checked, handler) => {
const row = document.createElement("div"); const row = document.createElement("label");
row.className = "checkbox-row"; row.className = "checkbox-row";
const text = document.createElement("span");
text.className = "checkbox-row__label";
text.textContent = label;
const chk = document.createElement("input"); const chk = document.createElement("input");
chk.type = "checkbox"; chk.type = "checkbox";
chk.checked = checked; chk.checked = checked;
chk.addEventListener("change", () => handler(chk.checked)); chk.addEventListener("change", () => handler(chk.checked));
const lbl = document.createElement("label"); row.appendChild(text);
lbl.textContent = label;
row.appendChild(chk); row.appendChild(chk);
row.appendChild(lbl);
viewport.appendChild(row); viewport.appendChild(row);
}; };
if (controls.toggleEdges) addCheck("Рёбра", controls.toggleEdges.checked, (v) => { controls.toggleEdges.checked = v; controls.toggleEdges.dispatchEvent(new Event("change")); }); if (controls.toggleEdges) addCheck("Рёбра", controls.toggleEdges.checked, (v) => { controls.toggleEdges.checked = v; controls.toggleEdges.dispatchEvent(new Event("change")); });
@ -378,24 +413,20 @@ export function createSettingsMenu({ panel, controls = {}, onChange = null, onSh
}); });
addColor(modals, "Аутлайн", "modalBorder"); addColor(modals, "Аутлайн", "modalBorder");
addColor(modals, "Фон элемента", "modalElement"); addColor(modals, "Контролы", "modalElement");
addColor(modals, "Аутлайн элемента", "modalElementOutline"); addColor(modals, "Аутлайн элемента", "modalElementOutline");
const radiusRow = document.createElement("div"); addNumber({
radiusRow.className = "color-row"; parent: modals,
const radiusLbl = document.createElement("label"); label: "Радиус",
radiusLbl.textContent = "Радиус"; value: parseInt(getComputedStyle(document.documentElement).getPropertyValue("--modal-radius")) || 14,
const radiusInput = document.createElement("input"); min: 0,
radiusInput.type = "number"; max: 48,
radiusInput.value = parseInt(getComputedStyle(document.documentElement).getPropertyValue("--modal-radius")) || 14; step: 1,
radiusInput.addEventListener("change", () => { onChange: (nextValue) => {
const val = `${radiusInput.value}px`; const val = `${Math.max(0, Math.min(48, Math.round(nextValue)))}px`;
document.documentElement.style.setProperty("--modal-radius", val);
setVal("modalRadius", val); setVal("modalRadius", val);
},
}); });
radiusRow.appendChild(radiusLbl);
radiusRow.appendChild(radiusInput);
radiusRow.appendChild(document.createElement("div"));
modals.appendChild(radiusRow);
panel.appendChild(modals); panel.appendChild(modals);
const cube = section("Выделение объектов"); const cube = section("Выделение объектов");

View File

@ -62,16 +62,27 @@ export function createTemplatesMenu({ panel, fetchProjects, openProject, onShow,
header.className = "templates-header"; header.className = "templates-header";
header.innerHTML = `<span class="templates-title">Проекты и шаблоны</span>`; header.innerHTML = `<span class="templates-title">Проекты и шаблоны</span>`;
const actions = document.createElement("div"); const actions = document.createElement("div");
actions.style.display = "flex"; actions.className = "templates-actions";
actions.style.gap = "6px";
const refresh = document.createElement("button"); const refresh = document.createElement("button");
refresh.className = "templates-close"; refresh.className = "templates-close";
refresh.type = "button";
refresh.title = "Обновить список"; refresh.title = "Обновить список";
refresh.textContent = "↻"; refresh.setAttribute("aria-label", "Обновить список");
refresh.innerHTML = `
<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true">
<path d="M12.2 5.3A4.4 4.4 0 0 0 4.7 3.4L3.6 4.5M3.7 2.1v2.4h2.4M3.8 10.7a4.4 4.4 0 0 0 7.5 1.9l1.1-1.1M12.3 13.9v-2.4H9.9" fill="none" stroke="currentColor" stroke-width="1.35" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
`;
refresh.addEventListener("click", () => load()); refresh.addEventListener("click", () => load());
const closeBtn = document.createElement("button"); const closeBtn = document.createElement("button");
closeBtn.className = "templates-close"; closeBtn.className = "templates-close";
closeBtn.textContent = "×"; closeBtn.type = "button";
closeBtn.setAttribute("aria-label", "Закрыть проекты и шаблоны");
closeBtn.innerHTML = `
<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true">
<path d="M4.25 4.25 11.75 11.75M11.75 4.25 4.25 11.75" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></path>
</svg>
`;
closeBtn.addEventListener("click", hide); closeBtn.addEventListener("click", hide);
actions.appendChild(refresh); actions.appendChild(refresh);
actions.appendChild(closeBtn); actions.appendChild(closeBtn);

View File

@ -1,7 +1,7 @@
// Базовые цвета/настройки проекта. При очистке localStorage эти значения остаются. // Базовые цвета/настройки проекта. При очистке localStorage эти значения остаются.
// Чтобы изменить палитру по умолчанию — правь этот объект и закоммить изменения. // Чтобы изменить палитру по умолчанию — правь этот объект и закоммить изменения.
const PROJECT_THEME = { const PROJECT_THEME = {
themeVersion: "v4-beam-lime", themeVersion: "v5-drone-lime",
bgOuter: "#0f0f0f", bgOuter: "#0f0f0f",
canvasTop: "#1c1c1c", canvasTop: "#1c1c1c",
canvasBottom: "#1c1c1c", canvasBottom: "#1c1c1c",
@ -9,25 +9,25 @@ const PROJECT_THEME = {
loadingBottom: "#1c1c1c", loadingBottom: "#1c1c1c",
accent: "#ffea00", accent: "#ffea00",
accent2: "#ffea00", accent2: "#ffea00",
templateFill: "#1c1c1c", templateFill: "#292929",
templateOutline: "#233044", templateOutline: "#292929",
templateOutlineHover: "rgba(255,234,0,0.7)", templateOutlineHover: "#1c1c1c",
templateGlow: 80, templateGlow: 80,
toolbarBg: "#1c1c1c", toolbarBg: "#292929",
toolbarBgActive: "#ffea00", toolbarBgActive: "#c4ff67",
toolbarBorder: "#233044", toolbarBorder: "#292929",
toolbarOutline: "#ffea00", toolbarOutline: "#292929",
modalBg: "#1c1c1c", modalBg: "#1c1c1c",
modalBgAlpha: 1, modalBgAlpha: 1,
modalFocus: "#292929", modalFocus: "#292929",
modalFocusAlpha: 1, modalFocusAlpha: 0.35,
modalBorder: "#292929", modalBorder: "#1c1c1c",
modalElement: "#292929", modalElement: "#262626",
modalElementOutline: "#2b2b2b", modalElementOutline: "#262626",
modalRadius: "14px", modalRadius: "14px",
projectBtnFill: "#1c1c1c", projectBtnFill: "#292929",
projectBtnBorder: "#233044", projectBtnBorder: "#292929",
projectBtnHover: "rgba(255,234,0,0.7)", projectBtnHover: "#1c1c1c",
modalBtnPrimary: "#c4ff67", modalBtnPrimary: "#c4ff67",
modalBtnSecondary: "#292929", modalBtnSecondary: "#292929",
loaderColor: "#c4ff67", loaderColor: "#c4ff67",

View File

@ -1,4 +1,12 @@
[ [
{
"id": "proj_0a284bb5-1c82-48b1-a636-e204578a570a",
"name": "DRONE",
"type": "project",
"ownerId": null,
"createdAt": "2026-06-05T13:33:23.556Z",
"updatedAt": "2026-06-05T13:33:23.556Z"
},
{ {
"id": "proj_0c1b26f3-020c-4ee9-926c-fedf08003304", "id": "proj_0c1b26f3-020c-4ee9-926c-fedf08003304",
"name": "TrackEnsemble_6074_v11", "name": "TrackEnsemble_6074_v11",

View File

@ -0,0 +1,135 @@
{
"id": "proj_0a284bb5-1c82-48b1-a636-e204578a570a",
"name": "DRONE",
"type": "project",
"createdAt": "2026-06-05T13:33:23.556Z",
"updatedAt": "2026-06-05T13:33:23.556Z",
"ownerId": null,
"shares": [],
"models": [
{
"id": "model",
"type": "xkt",
"meta": {
"label": "Final Assembly.STEP",
"metadataPath": null,
"settingsSrc": "uploads/nodedc_c2f0a08f-3f98-446a-94e9-c7b91ee95611_32e82d59-f15f-49b1-ba36-de2f8cc9ebc2/Final_Assembly.STEP",
"sourceSrc": "uploads/nodedc_c2f0a08f-3f98-446a-94e9-c7b91ee95611_32e82d59-f15f-49b1-ba36-de2f8cc9ebc2/Final_Assembly.STEP",
"artifactSrc": "uploads/nodedc_c2f0a08f-3f98-446a-94e9-c7b91ee95611_32e82d59-f15f-49b1-ba36-de2f8cc9ebc2/Final_Assembly.xkt"
},
"visible": true,
"src": "http://localhost:8080/uploads/nodedc_c2f0a08f-3f98-446a-94e9-c7b91ee95611_32e82d59-f15f-49b1-ba36-de2f8cc9ebc2/Final_Assembly.xkt?v=2026-06-04T18%3A09%3A13.204700%2B00%3A00"
}
],
"viewerState": {
"camera": {
"eye": [
0.00010965801538165287,
0.010159958762113674,
1.2885417934859191
],
"look": [
0.00010965801538165287,
0.010159958762113674,
-0.4406459172923821
],
"up": [
0,
1,
0
],
"projection": "perspective"
},
"transparent": true,
"edges": true,
"addMode": true,
"displayMode": "white",
"lightingMode": "standard",
"navigationAxis": "auto",
"zoomSpeed": 52,
"selection": {
"ids": [],
"modelId": null
},
"models": [
{
"id": "model",
"visible": true
}
],
"transforms": {
"model": {
"__model__": {
"position": [
0,
0,
0
],
"rotation": [
0,
0,
0
],
"scale": [
1,
1,
1
]
}
}
}
},
"design": {
"themeVersion": "v5-drone-lime",
"bgOuter": "#0f0f0f",
"canvasTop": "#1c1c1c",
"canvasBottom": "#1c1c1c",
"loadingTop": "#1c1c1c",
"loadingBottom": "#1c1c1c",
"accent": "#ffea00",
"accent2": "#ffea00",
"templateFill": "#292929",
"templateOutline": "#292929",
"templateOutlineHover": "#1c1c1c",
"projectBtnFill": "#292929",
"projectBtnBorder": "#292929",
"projectBtnHover": "#1c1c1c",
"modalBtnPrimary": "#c4ff67",
"modalBtnSecondary": "#292929",
"loaderColor": "#c4ff67",
"templateGlow": 80,
"toolbarBg": "#292929",
"toolbarBgActive": "#c4ff67",
"toolbarBorder": "#292929",
"toolbarOutline": "#292929",
"modalBg": "#1c1c1c",
"modalBgAlpha": 1,
"modalFocus": "#292929",
"modalFocusAlpha": 0.35,
"modalBorder": "#1c1c1c",
"modalElement": "#262626",
"modalElementOutline": "#262626",
"modalRadius": "14px",
"border": "#233044",
"text": "#e6edf3",
"panel": "#1c1c1c",
"panel2": "#292929",
"highlight": "#c4ff67",
"cubeText": "#242424",
"cubeEdges": "#e0e7ff",
"plus": "#c4ff67",
"navCubeBase": "#ffffff",
"navCubeHover": "#ffffff",
"navCubeText": "#242424",
"navCubeEdge": "#e0e7ff",
"navCubeEdgeAlpha": 0.6,
"measureLine": "#c4ff67",
"measureLabel": "#c4ff67",
"measureLabelText": "#292929",
"measureDot": "#c4ff67"
},
"meta": {
"description": "",
"tags": []
}
}

View File

@ -78,7 +78,7 @@
} }
}, },
"design": { "design": {
"themeVersion": "v4-beam-lime", "themeVersion": "v5-drone-lime",
"bgOuter": "#0f0f0f", "bgOuter": "#0f0f0f",
"canvasTop": "#1c1c1c", "canvasTop": "#1c1c1c",
"canvasBottom": "#1c1c1c", "canvasBottom": "#1c1c1c",
@ -86,27 +86,27 @@
"loadingBottom": "#1c1c1c", "loadingBottom": "#1c1c1c",
"accent": "#ffea00", "accent": "#ffea00",
"accent2": "#ffea00", "accent2": "#ffea00",
"templateFill": "#1c1c1c", "templateFill": "#292929",
"templateOutline": "#233044", "templateOutline": "#292929",
"templateOutlineHover": "rgba(255,234,0,0.7)", "templateOutlineHover": "#1c1c1c",
"projectBtnFill": "#1c1c1c", "projectBtnFill": "#292929",
"projectBtnBorder": "#233044", "projectBtnBorder": "#292929",
"projectBtnHover": "rgba(255,234,0,0.7)", "projectBtnHover": "#1c1c1c",
"modalBtnPrimary": "#c4ff67", "modalBtnPrimary": "#c4ff67",
"modalBtnSecondary": "#292929", "modalBtnSecondary": "#292929",
"loaderColor": "#c4ff67", "loaderColor": "#c4ff67",
"templateGlow": 80, "templateGlow": 80,
"toolbarBg": "#1c1c1c", "toolbarBg": "#292929",
"toolbarBgActive": "#c4ff67", "toolbarBgActive": "#c4ff67",
"toolbarBorder": "#233044", "toolbarBorder": "#292929",
"toolbarOutline": "#c4ff67", "toolbarOutline": "#292929",
"modalBg": "#1c1c1c", "modalBg": "#1c1c1c",
"modalBgAlpha": 1, "modalBgAlpha": 1,
"modalFocus": "#292929", "modalFocus": "#292929",
"modalFocusAlpha": 1, "modalFocusAlpha": 0.35,
"modalBorder": "#292929", "modalBorder": "#1c1c1c",
"modalElement": "#292929", "modalElement": "#262626",
"modalElementOutline": "#2b2b2b", "modalElementOutline": "#262626",
"modalRadius": "14px", "modalRadius": "14px",
"border": "#233044", "border": "#233044",
"text": "#e6edf3", "text": "#e6edf3",

View File

@ -802,7 +802,7 @@
} }
}, },
"design": { "design": {
"themeVersion": "v4-beam-lime", "themeVersion": "v5-drone-lime",
"bgOuter": "#0f0f0f", "bgOuter": "#0f0f0f",
"canvasTop": "#1c1c1c", "canvasTop": "#1c1c1c",
"canvasBottom": "#1c1c1c", "canvasBottom": "#1c1c1c",
@ -810,27 +810,27 @@
"loadingBottom": "#1c1c1c", "loadingBottom": "#1c1c1c",
"accent": "#ffea00", "accent": "#ffea00",
"accent2": "#ffea00", "accent2": "#ffea00",
"templateFill": "#1c1c1c", "templateFill": "#292929",
"templateOutline": "#233044", "templateOutline": "#292929",
"templateOutlineHover": "rgba(255,234,0,0.7)", "templateOutlineHover": "#1c1c1c",
"projectBtnFill": "#1c1c1c", "projectBtnFill": "#292929",
"projectBtnBorder": "#233044", "projectBtnBorder": "#292929",
"projectBtnHover": "rgba(255,234,0,0.7)", "projectBtnHover": "#1c1c1c",
"modalBtnPrimary": "#c4ff67", "modalBtnPrimary": "#c4ff67",
"modalBtnSecondary": "#292929", "modalBtnSecondary": "#292929",
"loaderColor": "#c4ff67", "loaderColor": "#c4ff67",
"templateGlow": 80, "templateGlow": 80,
"toolbarBg": "#1c1c1c", "toolbarBg": "#292929",
"toolbarBgActive": "#c4ff67", "toolbarBgActive": "#c4ff67",
"toolbarBorder": "#233044", "toolbarBorder": "#292929",
"toolbarOutline": "#c4ff67", "toolbarOutline": "#292929",
"modalBg": "#1c1c1c", "modalBg": "#1c1c1c",
"modalBgAlpha": 1, "modalBgAlpha": 1,
"modalFocus": "#292929", "modalFocus": "#292929",
"modalFocusAlpha": 1, "modalFocusAlpha": 0.35,
"modalBorder": "#292929", "modalBorder": "#1c1c1c",
"modalElement": "#292929", "modalElement": "#262626",
"modalElementOutline": "#2b2b2b", "modalElementOutline": "#262626",
"modalRadius": "14px", "modalRadius": "14px",
"border": "#233044", "border": "#233044",
"text": "#e6edf3", "text": "#e6edf3",

View File

@ -73,7 +73,7 @@
} }
}, },
"design": { "design": {
"themeVersion": "v4-beam-lime", "themeVersion": "v5-drone-lime",
"bgOuter": "#0f0f0f", "bgOuter": "#0f0f0f",
"canvasTop": "#1c1c1c", "canvasTop": "#1c1c1c",
"canvasBottom": "#1c1c1c", "canvasBottom": "#1c1c1c",
@ -81,27 +81,27 @@
"loadingBottom": "#1c1c1c", "loadingBottom": "#1c1c1c",
"accent": "#ffea00", "accent": "#ffea00",
"accent2": "#ffea00", "accent2": "#ffea00",
"templateFill": "#1c1c1c", "templateFill": "#292929",
"templateOutline": "#233044", "templateOutline": "#292929",
"templateOutlineHover": "rgba(255,234,0,0.7)", "templateOutlineHover": "#1c1c1c",
"projectBtnFill": "#1c1c1c", "projectBtnFill": "#292929",
"projectBtnBorder": "#233044", "projectBtnBorder": "#292929",
"projectBtnHover": "rgba(255,234,0,0.7)", "projectBtnHover": "#1c1c1c",
"modalBtnPrimary": "#c4ff67", "modalBtnPrimary": "#c4ff67",
"modalBtnSecondary": "#292929", "modalBtnSecondary": "#292929",
"loaderColor": "#c4ff67", "loaderColor": "#c4ff67",
"templateGlow": 80, "templateGlow": 80,
"toolbarBg": "#1c1c1c", "toolbarBg": "#292929",
"toolbarBgActive": "#c4ff67", "toolbarBgActive": "#c4ff67",
"toolbarBorder": "#233044", "toolbarBorder": "#292929",
"toolbarOutline": "#c4ff67", "toolbarOutline": "#292929",
"modalBg": "#1c1c1c", "modalBg": "#1c1c1c",
"modalBgAlpha": 1, "modalBgAlpha": 1,
"modalFocus": "#292929", "modalFocus": "#292929",
"modalFocusAlpha": 1, "modalFocusAlpha": 0.35,
"modalBorder": "#292929", "modalBorder": "#1c1c1c",
"modalElement": "#292929", "modalElement": "#262626",
"modalElementOutline": "#2b2b2b", "modalElementOutline": "#262626",
"modalRadius": "14px", "modalRadius": "14px",
"border": "#233044", "border": "#233044",
"text": "#e6edf3", "text": "#e6edf3",

View File

@ -92,7 +92,7 @@
} }
}, },
"design": { "design": {
"themeVersion": "v4-beam-lime", "themeVersion": "v5-drone-lime",
"bgOuter": "#0f0f0f", "bgOuter": "#0f0f0f",
"canvasTop": "#1c1c1c", "canvasTop": "#1c1c1c",
"canvasBottom": "#1c1c1c", "canvasBottom": "#1c1c1c",
@ -100,27 +100,27 @@
"loadingBottom": "#1c1c1c", "loadingBottom": "#1c1c1c",
"accent": "#ffea00", "accent": "#ffea00",
"accent2": "#ffea00", "accent2": "#ffea00",
"templateFill": "#1c1c1c", "templateFill": "#292929",
"templateOutline": "#233044", "templateOutline": "#292929",
"templateOutlineHover": "rgba(255,234,0,0.7)", "templateOutlineHover": "#1c1c1c",
"projectBtnFill": "#1c1c1c", "projectBtnFill": "#292929",
"projectBtnBorder": "#233044", "projectBtnBorder": "#292929",
"projectBtnHover": "rgba(255,234,0,0.7)", "projectBtnHover": "#1c1c1c",
"modalBtnPrimary": "#c4ff67", "modalBtnPrimary": "#c4ff67",
"modalBtnSecondary": "#292929", "modalBtnSecondary": "#292929",
"loaderColor": "#c4ff67", "loaderColor": "#c4ff67",
"templateGlow": 80, "templateGlow": 80,
"toolbarBg": "#1c1c1c", "toolbarBg": "#292929",
"toolbarBgActive": "#c4ff67", "toolbarBgActive": "#c4ff67",
"toolbarBorder": "#233044", "toolbarBorder": "#292929",
"toolbarOutline": "#c4ff67", "toolbarOutline": "#292929",
"modalBg": "#1c1c1c", "modalBg": "#1c1c1c",
"modalBgAlpha": 1, "modalBgAlpha": 1,
"modalFocus": "#292929", "modalFocus": "#292929",
"modalFocusAlpha": 1, "modalFocusAlpha": 0.35,
"modalBorder": "#292929", "modalBorder": "#1c1c1c",
"modalElement": "#292929", "modalElement": "#262626",
"modalElementOutline": "#2b2b2b", "modalElementOutline": "#262626",
"modalRadius": "14px", "modalRadius": "14px",
"border": "#233044", "border": "#233044",
"text": "#e6edf3", "text": "#e6edf3",