Gather Directions, Draw and Import in a Utilities panel

UTILITIES becomes its own panel in the left column, after Data Layers and
CCTV and above Scenes. The layer panel renders the Directions row into the
panel's list instead of Data Layers, and Draw and Import move there from
DISPLAY with their ids unchanged. Radio, the other row under the old
Utilities heading, stays in Data Layers under "Audio".

The panel collapses and remembers its state like the other left panels; share
links do not carry it. It hides in clean view, recording and Cockpit, takes
the Cyber left-rail treatment, and sits collapsed in the phone's Layers sheet.
Voice focuses the Directions row in its new list. The Directions, Draw,
Import and form-field gates open UTILITIES to reach their controls.
This commit is contained in:
Said Turksever committed 2026-10-29 11:12:46 +00:00
1 parent aaa3ff46cf
commit 08dd88b2ce
26 files changed
+319 -87

No files matched your search

+16 -5
View File
@@ -1,4 +1,9 @@
import { getScopeMaskFeather, isScopeMaskEnabled, setScopeMaskEnabled, setScopeMaskFeather } from '../scopeMask.js';
import {
getScopeMaskFeather,
isScopeMaskEnabled,
setScopeMaskEnabled,
setScopeMaskFeather,
} from '../scopeMask.js';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import test from 'node:test';
@@ -274,8 +279,14 @@ test('Display and voice HUD routes preserve explicit Cyber entry and exit author
test('scope choice and expanded feather survive every HUD layout and both restore paths', async (t) => {
const { owner } = styleOwner(t, 'tactical');
const previous = { enabled: isScopeMaskEnabled(), feather: getScopeMaskFeather() };
t.after(() => { setScopeMaskEnabled(previous.enabled); setScopeMaskFeather(previous.feather); });
const previous = {
enabled: isScopeMaskEnabled(),
feather: getScopeMaskFeather(),
};
t.after(() => {
setScopeMaskEnabled(previous.enabled);
setScopeMaskFeather(previous.feather);
});
Object.assign(owner.services, { setScopeMaskEnabled, setScopeMaskFeather });
owner._scopeBtn = { checked: true };
owner._scopeFeatherSlider = { value: '11', disabled: false };
@@ -534,11 +545,11 @@ test('Cyber side panels share one width and one framed surface material', () =>
);
assert.match(
cyberStyles,
/#left-panel-stack\s*> :is\(#data-panel, #scene-panel\)\.collapsed \{\s*width: var\(--cyber-panel-collapsed-width\);/,
/#left-panel-stack\s*> :is\(#data-panel, #utilities-panel, #scene-panel\)\.collapsed \{\s*width: var\(--cyber-panel-collapsed-width\);/,
);
assert.match(
cyberStyles,
/:is\(#data-panel, #scene-panel\)\.collapsed\s+:is\(\.data-panel-inner, \.scene-panel-inner\) \{[\s\S]*?height: 50px;[\s\S]*?min-height: 50px;/,
/:is\(#data-panel, #utilities-panel, #scene-panel\)\.collapsed\s+:is\(\.data-panel-inner, \.scene-panel-inner\) \{[\s\S]*?height: 50px;[\s\S]*?min-height: 50px;/,
);
assert.match(
cyberStyles,