style: format the UI shell and component styles

This commit is contained in:
Sameh Khamis committed 2026-09-13 04:26:11 -07:00
1 parent f5ef9091f0
commit 03ad8142e6
32 files changed
+3887 -1307

No files matched your search

+34 -1
View File
@@ -213,5 +213,38 @@
"src/ui/sceneControls.js", "src/ui/sceneControls.js",
"src/ui/scenePresentation.js", "src/ui/scenePresentation.js",
"src/ui/sceneControls.test.mjs", "src/ui/sceneControls.test.mjs",
"scripts/qa-scene-controls.mjs" "scripts/qa-scene-controls.mjs",
"src/ui.js",
"src/standalone/ui.js",
"src/ui/applicationShell.js",
"src/ui/panelLayoutController.js",
"src/ui/panelPositionControls.js",
"src/ui/recordingControls.js",
"src/ui/shellElements.js",
"src/ui/shellFeedback.js",
"src/ui/uiLifetime.js",
"src/ui/styles.js",
"src/ui/shellLifecycle.test.mjs",
"src/testSupport/readStylesheet.mjs",
"src/splitFlap.js",
"style.css",
"src/ui/styles/overlays.css",
"src/ui/styles/command-dock-trays.css",
"src/ui/styles/location.css",
"src/ui/styles/recording.css",
"src/ui/styles/status.css",
"src/ui/styles/layers.css",
"src/ui/styles/controls.css",
"src/ui/styles/cctv.css",
"src/ui/styles/foundation.css",
"src/ui/styles/first-run.css",
"src/ui/styles/scenes.css",
"src/ui/styles/command-dock.css",
"src/ui/styles/command-dock-sliding.css",
"src/ui/styles/provider-settings.css",
"src/ui/styles/radio.css",
"src/ui/styles/responsive.css",
"src/ui/styles/voice-cost.css",
"src/ui/styles/cockpit.css",
"src/ui/styles/command-dock-compact.css"
] ]
+3 -9
View File
@@ -425,9 +425,7 @@
"external": [] "external": []
}, },
"ui-shell": { "ui-shell": {
"exports": [ "exports": ["./ui/shell"],
"./ui/shell"
],
"modules": [ "modules": [
"src/bloom.js", "src/bloom.js",
"src/cctvFocusPolicy.js", "src/cctvFocusPolicy.js",
@@ -522,14 +520,10 @@
"src/worldFocus.js", "src/worldFocus.js",
"src/ui/uiLifetime.js" "src/ui/uiLifetime.js"
], ],
"external": [ "external": ["cesium"]
"cesium"
]
}, },
"ui-styles": { "ui-styles": {
"exports": [ "exports": ["./ui/styles"],
"./ui/styles"
],
"modules": [ "modules": [
"src/ui/styles.js", "src/ui/styles.js",
"style.css", "style.css",
+5 -1
View File
@@ -210,7 +210,11 @@ try {
); );
// Escape can also collapse the Scene accordion. Reopen it through its // Escape can also collapse the Scene accordion. Reopen it through its
// installed disclosure before testing the next visible user action. // installed disclosure before testing the next visible user action.
if (await page.$eval('#scene-panel', (panel) => panel.classList.contains('collapsed'))) if (
await page.$eval('#scene-panel', (panel) =>
panel.classList.contains('collapsed'),
)
)
await page.click('[data-collapse-target="scene-panel"]'); await page.click('[data-collapse-target="scene-panel"]');
await page.waitForSelector('#scene-download-btn', { visible: true }); await page.waitForSelector('#scene-download-btn', { visible: true });
await page.click('#scene-download-btn'); await page.click('#scene-download-btn');
+31 -16
View File
@@ -107,7 +107,8 @@ function positiveNumber(value, fallback) {
} }
function nowMs() { function nowMs() {
return typeof performance !== 'undefined' && typeof performance.now === 'function' return typeof performance !== 'undefined' &&
typeof performance.now === 'function'
? performance.now() ? performance.now()
: Date.now(); : Date.now();
} }
@@ -235,7 +236,9 @@ export function visibleGlyphs(plan, elapsedMs, options = {}) {
const turnRatio = Number.isFinite(Number(options.turnRatio)) const turnRatio = Number.isFinite(Number(options.turnRatio))
? Number(options.turnRatio) ? Number(options.turnRatio)
: FLAP_TURN_RATIO; : FLAP_TURN_RATIO;
const elapsed = Number.isFinite(Number(elapsedMs)) ? Math.max(0, Number(elapsedMs)) : 0; const elapsed = Number.isFinite(Number(elapsedMs))
? Math.max(0, Number(elapsedMs))
: 0;
const turn = charMs * turnRatio; const turn = charMs * turnRatio;
let out = ''; let out = '';
for (const cell of plan?.cells || []) { for (const cell of plan?.cells || []) {
@@ -246,9 +249,11 @@ export function visibleGlyphs(plan, elapsedMs, options = {}) {
/** Whether the viewer asked for reduced motion. */ /** Whether the viewer asked for reduced motion. */
function prefersReducedMotion() { function prefersReducedMotion() {
return typeof window !== 'undefined' return (
&& typeof window.matchMedia === 'function' typeof window !== 'undefined' &&
&& window.matchMedia('(prefers-reduced-motion: reduce)').matches; typeof window.matchMedia === 'function' &&
window.matchMedia('(prefers-reduced-motion: reduce)').matches
);
} }
/** /**
@@ -274,7 +279,11 @@ function isVisible(element) {
if (element.getClientRects().length === 0) return false; if (element.getClientRects().length === 0) return false;
const view = element.ownerDocument?.defaultView; const view = element.ownerDocument?.defaultView;
if (typeof view?.getComputedStyle !== 'function') return true; if (typeof view?.getComputedStyle !== 'function') return true;
for (let node = element; node && node.nodeType === 1; node = node.parentElement) { for (
let node = element;
node && node.nodeType === 1;
node = node.parentElement
) {
const style = view.getComputedStyle(node); const style = view.getComputedStyle(node);
if (style.visibility === 'hidden' || style.display === 'none') return false; if (style.visibility === 'hidden' || style.display === 'none') return false;
if (Number(style.opacity) === 0) return false; if (Number(style.opacity) === 0) return false;
@@ -301,10 +310,12 @@ function ensureHost(element) {
// nothing changed. // nothing changed.
const text = element.firstElementChild; const text = element.firstElementChild;
const cells = text?.nextElementSibling; const cells = text?.nextElementSibling;
if (text?.classList?.contains(TEXT_CLASS) if (
&& text.firstChild?.nodeType === 3 text?.classList?.contains(TEXT_CLASS) &&
&& cells?.classList?.contains(CELLS_CLASS) text.firstChild?.nodeType === 3 &&
&& !cells.nextElementSibling) { cells?.classList?.contains(CELLS_CLASS) &&
!cells.nextElementSibling
) {
return { text, cells }; return { text, cells };
} }
@@ -363,7 +374,8 @@ function easeWidth(element, fromWidth, toWidth, durationMs) {
element.style.width = `${toWidth}px`; element.style.width = `${toWidth}px`;
const finish = (event) => { const finish = (event) => {
if (event && (event.target !== element || event.propertyName !== 'width')) return; if (event && (event.target !== element || event.propertyName !== 'width'))
return;
cancelWidthEase(element); cancelWidthEase(element);
clearSizing(element); clearSizing(element);
}; };
@@ -449,16 +461,19 @@ export function setSplitFlapText(element, text, options = {}) {
// previous label's glyphs, and those are what must flap away. // previous label's glyphs, and those are what must flap away.
const state = flapStates.get(element); const state = flapStates.get(element);
const displayed = state const displayed = state
? visibleGlyphs(state.plan, nowMs() - state.startedAt, { charMs: state.charMs }) ? visibleGlyphs(state.plan, nowMs() - state.startedAt, {
charMs: state.charMs,
})
: settled; : settled;
clearFlapTimer(element); clearFlapTimer(element);
const beforeWidth = measureWidth(element); const beforeWidth = measureWidth(element);
const animate = SPLIT_FLAP_ENABLED const animate =
&& options.immediate !== true SPLIT_FLAP_ENABLED &&
&& !prefersReducedMotion() options.immediate !== true &&
&& isVisible(element); !prefersReducedMotion() &&
isVisible(element);
const plan = animate ? planSplitFlap(displayed, next, options) : null; const plan = animate ? planSplitFlap(displayed, next, options) : null;
+27 -7
View File
@@ -1,7 +1,14 @@
/** Compose UI controls with the application's existing engines and layer instances. */ /** Compose UI controls with the application's existing engines and layer instances. */
import { StyleManager as ApplicationShell } from '../ui/applicationShell.js'; import { StyleManager as ApplicationShell } from '../ui/applicationShell.js';
import { LocationSearch } from '../ui/location.js'; import { LocationSearch } from '../ui/location.js';
import { CITY_POIS, GLOBE_VIEW, flyToGlobeView, flyToPresetLocation, flyToPOI, searchAndFlyTo } from '../locations.js'; import {
CITY_POIS,
GLOBE_VIEW,
flyToGlobeView,
flyToPresetLocation,
flyToPOI,
searchAndFlyTo,
} from '../locations.js';
import { interruptCameraMotion } from '../cameraVerbs.js'; import { interruptCameraMotion } from '../cameraVerbs.js';
import { IntelHUD } from '../hud.js'; import { IntelHUD } from '../hud.js';
import { ShareLinkManager } from '../sharelink.js'; import { ShareLinkManager } from '../sharelink.js';
@@ -12,8 +19,14 @@ import {
isCelestialRingStyleSupported, isCelestialRingStyleSupported,
setKeyholeFadeTuning, setKeyholeFadeTuning,
} from '../celestialRing.js'; } from '../celestialRing.js';
import { destroyTrackedReadout, initTrackedReadout } from '../data/trackedReadout.js'; import {
import { destroyWorldOverlay, initWorldOverlay } from '../overlays/worldOverlay.js'; destroyTrackedReadout,
initTrackedReadout,
} from '../data/trackedReadout.js';
import {
destroyWorldOverlay,
initWorldOverlay,
} from '../overlays/worldOverlay.js';
import { import {
destroyDetection, destroyDetection,
initDetection, initDetection,
@@ -47,7 +60,11 @@ import {
warmGroundFloor, warmGroundFloor,
} from '../data/groundFloor.js'; } from '../data/groundFloor.js';
import { sampleMeshFloorCells } from '../data/meshFloorSampler.js'; import { sampleMeshFloorCells } from '../data/meshFloorSampler.js';
import { holdContinuousRender, releaseContinuousRender, governorRequestRender } from '../renderGovernor.js'; import {
holdContinuousRender,
releaseContinuousRender,
governorRequestRender,
} from '../renderGovernor.js';
import { import {
setScopeMaskEnabled, setScopeMaskEnabled,
isScopeMaskEnabled, isScopeMaskEnabled,
@@ -65,7 +82,9 @@ import {
export class StyleManager extends ApplicationShell { export class StyleManager extends ApplicationShell {
constructor(viewer, options = {}) { constructor(viewer, options = {}) {
super(viewer, { ...options, services: { super(viewer, {
...options,
services: {
CITY_POIS, CITY_POIS,
GLOBE_VIEW, GLOBE_VIEW,
flyToGlobeView, flyToGlobeView,
@@ -127,7 +146,8 @@ export class StyleManager extends ApplicationShell {
fetchRegionalBrief, fetchRegionalBrief,
regionalDistanceM, regionalDistanceM,
weatherCodeLabel, weatherCodeLabel,
LocationSearch LocationSearch,
} }); },
});
} }
} }
+8 -3
View File
@@ -3,9 +3,14 @@ import { readFileSync } from 'node:fs';
import path from 'node:path'; import path from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
export function readStylesheet(file, ancestors = new Set()) { export function readStylesheet(file, ancestors = new Set()) {
const resolved = path.resolve(file instanceof URL ? fileURLToPath(file) : file); const resolved = path.resolve(
file instanceof URL ? fileURLToPath(file) : file,
);
if (ancestors.has(resolved)) throw new Error('Circular stylesheet import'); if (ancestors.has(resolved)) throw new Error('Circular stylesheet import');
const next = new Set(ancestors).add(resolved); const next = new Set(ancestors).add(resolved);
return readFileSync(resolved, 'utf8').replace(/@import ['"]([^'"]+)['"];\s*/g, return readFileSync(resolved, 'utf8').replace(
(_, imported) => readStylesheet(path.resolve(path.dirname(resolved), imported), next)); /@import ['"]([^'"]+)['"];\s*/g,
(_, imported) =>
readStylesheet(path.resolve(path.dirname(resolved), imported), next),
);
} }
+1410 -462
View File
File diff suppressed because it is too large. Load diff
+122 -34
View File
@@ -50,7 +50,12 @@ const RIGHT_STACK_OBSTACLE_SELECTOR = [
'#gev-voice-control', '#gev-voice-control',
].join(', '); ].join(', ');
export class PanelLayoutController { export class PanelLayoutController {
constructor({ readHud, scheduleCockpitLayout, syncPanelCollapseButton, readDisplayScrollTop }) { constructor({
readHud,
scheduleCockpitLayout,
syncPanelCollapseButton,
readDisplayScrollTop,
}) {
this.readHud = readHud; this.readHud = readHud;
this.scheduleCockpitLayout = scheduleCockpitLayout; this.scheduleCockpitLayout = scheduleCockpitLayout;
this._syncPanelCollapseButton = syncPanelCollapseButton; this._syncPanelCollapseButton = syncPanelCollapseButton;
@@ -103,7 +108,9 @@ export class PanelLayoutController {
if (!dock) return; if (!dock) return;
this._commandDockTrayObserver?.disconnect?.(); this._commandDockTrayObserver?.disconnect?.();
if (typeof ResizeObserver === 'function') { if (typeof ResizeObserver === 'function') {
this._commandDockTrayObserver = new ResizeObserver(() => this._updateCommandDockTrayStack()); this._commandDockTrayObserver = new ResizeObserver(() =>
this._updateCommandDockTrayStack(),
);
dock.querySelectorAll('.dock-popover-content').forEach((tray) => { dock.querySelectorAll('.dock-popover-content').forEach((tray) => {
this._commandDockTrayObserver.observe(tray); this._commandDockTrayObserver.observe(tray);
}); });
@@ -115,24 +122,48 @@ export class PanelLayoutController {
if (this.destroyed) return; if (this.destroyed) return;
const dock = document.getElementById('command-dock'); const dock = document.getElementById('command-dock');
if (!dock) return; if (!dock) return;
const locationPanel = dock.querySelector('#location-bar.dock-pinned:not(.collapsed)'); const locationPanel = dock.querySelector(
const presetsPanel = dock.querySelector('#control-panel.dock-pinned:not(.collapsed)'); '#location-bar.dock-pinned:not(.collapsed)',
const locationHeight = locationPanel?.querySelector('.dock-popover-content')?.getBoundingClientRect().height || 0; );
const presetsHeight = presetsPanel?.querySelector('.dock-popover-content')?.getBoundingClientRect().height || 0; const presetsPanel = dock.querySelector(
'#control-panel.dock-pinned:not(.collapsed)',
);
const locationHeight =
locationPanel
?.querySelector('.dock-popover-content')
?.getBoundingClientRect().height || 0;
const presetsHeight =
presetsPanel
?.querySelector('.dock-popover-content')
?.getBoundingClientRect().height || 0;
const pinnedCount = Number(locationHeight > 0) + Number(presetsHeight > 0); const pinnedCount = Number(locationHeight > 0) + Number(presetsHeight > 0);
const locationHeightPx = Math.ceil(locationHeight); const locationHeightPx = Math.ceil(locationHeight);
const presetsHeightPx = Math.ceil(presetsHeight); const presetsHeightPx = Math.ceil(presetsHeight);
const topPinnedPanel = dock.querySelector('.dock-pinned-top.dock-pinned:not(.collapsed)'); const topPinnedPanel = dock.querySelector(
const lowerPinnedPanel = topPinnedPanel?.id === 'location-bar' ? presetsPanel : locationPanel; '.dock-pinned-top.dock-pinned:not(.collapsed)',
const lowerPinnedHeight = lowerPinnedPanel );
const lowerPinnedPanel =
topPinnedPanel?.id === 'location-bar' ? presetsPanel : locationPanel;
const lowerPinnedHeight =
lowerPinnedPanel
?.querySelector('.dock-popover-content') ?.querySelector('.dock-popover-content')
?.getBoundingClientRect().height || 0; ?.getBoundingClientRect().height || 0;
const stackHeight = pinnedCount > 1 const stackHeight =
pinnedCount > 1
? `calc(${locationHeightPx}px + ${presetsHeightPx}px + 1.2rem)` ? `calc(${locationHeightPx}px + ${presetsHeightPx}px + 1.2rem)`
: `${locationHeightPx + presetsHeightPx}px`; : `${locationHeightPx + presetsHeightPx}px`;
dock.style.setProperty('--dock-location-pinned-height', `${locationHeightPx}px`); dock.style.setProperty(
dock.style.setProperty('--dock-presets-pinned-height', `${presetsHeightPx}px`); '--dock-location-pinned-height',
dock.style.setProperty('--dock-lower-pinned-height', `${Math.ceil(lowerPinnedHeight)}px`); `${locationHeightPx}px`,
);
dock.style.setProperty(
'--dock-presets-pinned-height',
`${presetsHeightPx}px`,
);
dock.style.setProperty(
'--dock-lower-pinned-height',
`${Math.ceil(lowerPinnedHeight)}px`,
);
dock.style.setProperty('--dock-pinned-stack-height', stackHeight); dock.style.setProperty('--dock-pinned-stack-height', stackHeight);
dock.classList.toggle('dock-has-pinned-tray', pinnedCount > 0); dock.classList.toggle('dock-has-pinned-tray', pinnedCount > 0);
dock.classList.toggle('dock-has-two-pinned-trays', pinnedCount > 1); dock.classList.toggle('dock-has-two-pinned-trays', pinnedCount > 1);
@@ -177,10 +208,16 @@ export class PanelLayoutController {
this._scheduleRightPanelLayout(); this._scheduleRightPanelLayout();
}); });
this._rightStackResizeObserver.observe(stack); this._rightStackResizeObserver.observe(stack);
for (const panel of [this._ppToggles, this._cctvPanel, globalContextPanel]) { for (const panel of [
this._ppToggles,
this._cctvPanel,
globalContextPanel,
]) {
if (panel) this._rightStackResizeObserver.observe(panel); if (panel) this._rightStackResizeObserver.observe(panel);
} }
document.querySelectorAll(RIGHT_STACK_OBSTACLE_SELECTOR).forEach((element) => { document
.querySelectorAll(RIGHT_STACK_OBSTACLE_SELECTOR)
.forEach((element) => {
this._rightStackResizeObserver.observe(element); this._rightStackResizeObserver.observe(element);
}); });
} }
@@ -209,11 +246,17 @@ export class PanelLayoutController {
const transitionHud = document.getElementById('intel-hud'); const transitionHud = document.getElementById('intel-hud');
if (transitionHud) { if (transitionHud) {
this._rightStackHudTransitionHandler = (event) => { this._rightStackHudTransitionHandler = (event) => {
if (event.propertyName === 'opacity' || event.propertyName === 'visibility') { if (
event.propertyName === 'opacity' ||
event.propertyName === 'visibility'
) {
this._scheduleRightPanelLayout({ reconsiderAutoCollapse: true }); this._scheduleRightPanelLayout({ reconsiderAutoCollapse: true });
} }
}; };
transitionHud.addEventListener('transitionend', this._rightStackHudTransitionHandler); transitionHud.addEventListener(
'transitionend',
this._rightStackHudTransitionHandler,
);
} }
this._scheduleRightPanelLayout(); this._scheduleRightPanelLayout();
@@ -228,7 +271,9 @@ export class PanelLayoutController {
this._rightStackLayoutFrame = null; this._rightStackLayoutFrame = null;
if (this._rightStackReconsiderAutoCollapse) { if (this._rightStackReconsiderAutoCollapse) {
this._rightStackReconsiderAutoCollapse = false; this._rightStackReconsiderAutoCollapse = false;
for (const panel of this._rightPanelStack.querySelectorAll('.layout-auto-collapsed')) { for (const panel of this._rightPanelStack.querySelectorAll(
'.layout-auto-collapsed',
)) {
panel.classList.remove('collapsed', 'layout-auto-collapsed'); panel.classList.remove('collapsed', 'layout-auto-collapsed');
this._syncPanelCollapseButton(panel); this._syncPanelCollapseButton(panel);
} }
@@ -265,10 +310,14 @@ export class PanelLayoutController {
this._leftStackResizeObserver.observe(stack); this._leftStackResizeObserver.observe(stack);
stack.querySelectorAll(':scope > [data-panel-id]').forEach((panel) => { stack.querySelectorAll(':scope > [data-panel-id]').forEach((panel) => {
this._leftStackResizeObserver.observe(panel); this._leftStackResizeObserver.observe(panel);
const inner = [...panel.children].find((child) => !child.classList.contains('panel-glow')); const inner = [...panel.children].find(
(child) => !child.classList.contains('panel-glow'),
);
if (inner) this._leftStackResizeObserver.observe(inner); if (inner) this._leftStackResizeObserver.observe(inner);
}); });
document.querySelectorAll(LEFT_STACK_OBSTACLE_SELECTOR).forEach((element) => { document
.querySelectorAll(LEFT_STACK_OBSTACLE_SELECTOR)
.forEach((element) => {
this._leftStackResizeObserver.observe(element); this._leftStackResizeObserver.observe(element);
}); });
} }
@@ -303,7 +352,10 @@ export class PanelLayoutController {
const transitionHud = document.getElementById('intel-hud'); const transitionHud = document.getElementById('intel-hud');
if (transitionHud) { if (transitionHud) {
this._leftStackHudTransitionHandler = (event) => { this._leftStackHudTransitionHandler = (event) => {
if (event.propertyName === 'opacity' || event.propertyName === 'visibility') { if (
event.propertyName === 'opacity' ||
event.propertyName === 'visibility'
) {
this._scheduleLeftPanelLayout({ reconsiderAutoCollapse: true }); this._scheduleLeftPanelLayout({ reconsiderAutoCollapse: true });
// The Cockpit strip hangs off the HUD's REC readout, so it has to // The Cockpit strip hangs off the HUD's REC readout, so it has to
// remeasure on the same event: the readout keeps its rect through // remeasure on the same event: the readout keeps its rect through
@@ -311,7 +363,10 @@ export class PanelLayoutController {
this.scheduleCockpitLayout(); this.scheduleCockpitLayout();
} }
}; };
transitionHud.addEventListener('transitionend', this._leftStackHudTransitionHandler); transitionHud.addEventListener(
'transitionend',
this._leftStackHudTransitionHandler,
);
} }
this._leftStackCockpitModeHandler = () => { this._leftStackCockpitModeHandler = () => {
@@ -321,12 +376,22 @@ export class PanelLayoutController {
// the accordion remains in the same obstacle-safe lane instead of // the accordion remains in the same obstacle-safe lane instead of
// jumping to a cockpit-specific top anchor. // jumping to a cockpit-specific top anchor.
this._scheduleLeftPanelLayout(); this._scheduleLeftPanelLayout();
if (this._cockpitLayoutFrame !== null) cancelAnimationFrame(this._cockpitLayoutFrame); if (this._cockpitLayoutFrame !== null)
this._cockpitLayoutFrame = requestAnimationFrame(() => { this._cockpitLayoutFrame = null; this._scheduleLeftPanelLayout(); }); cancelAnimationFrame(this._cockpitLayoutFrame);
this._cockpitLayoutFrame = requestAnimationFrame(() => {
this._cockpitLayoutFrame = null;
this._scheduleLeftPanelLayout();
});
clearTimeout(this._cockpitLayoutTimer); clearTimeout(this._cockpitLayoutTimer);
this._cockpitLayoutTimer = setTimeout(() => { this._cockpitLayoutTimer = null; this._scheduleLeftPanelLayout(); }, 300); this._cockpitLayoutTimer = setTimeout(() => {
this._cockpitLayoutTimer = null;
this._scheduleLeftPanelLayout();
}, 300);
}; };
window.addEventListener('gev:cockpit-mode-changed', this._leftStackCockpitModeHandler); window.addEventListener(
'gev:cockpit-mode-changed',
this._leftStackCockpitModeHandler,
);
this._scheduleLeftPanelLayout(); this._scheduleLeftPanelLayout();
} }
@@ -340,7 +405,9 @@ export class PanelLayoutController {
this._leftStackLayoutFrame = null; this._leftStackLayoutFrame = null;
if (this._leftStackReconsiderAutoCollapse) { if (this._leftStackReconsiderAutoCollapse) {
this._leftStackReconsiderAutoCollapse = false; this._leftStackReconsiderAutoCollapse = false;
for (const panel of this._leftPanelStack.querySelectorAll('.layout-auto-collapsed')) { for (const panel of this._leftPanelStack.querySelectorAll(
'.layout-auto-collapsed',
)) {
panel.classList.remove('collapsed', 'layout-auto-collapsed'); panel.classList.remove('collapsed', 'layout-auto-collapsed');
this._syncPanelCollapseButton(panel); this._syncPanelCollapseButton(panel);
} }
@@ -366,21 +433,42 @@ export class PanelLayoutController {
destroy() { destroy() {
if (this.destroyed) return; if (this.destroyed) return;
this.destroyed = true; this.destroyed = true;
for (const field of ['_leftStackLayoutFrame', '_rightStackLayoutFrame', '_cockpitLayoutFrame']) { for (const field of [
'_leftStackLayoutFrame',
'_rightStackLayoutFrame',
'_cockpitLayoutFrame',
]) {
if (this[field] !== null) cancelAnimationFrame(this[field]); if (this[field] !== null) cancelAnimationFrame(this[field]);
this[field] = null; this[field] = null;
} }
clearTimeout(this._adaptivePanelSettleTimer); clearTimeout(this._adaptivePanelSettleTimer);
clearTimeout(this._cockpitLayoutTimer); clearTimeout(this._cockpitLayoutTimer);
this._adaptivePanelSettleTimer = this._cockpitLayoutTimer = null; this._adaptivePanelSettleTimer = this._cockpitLayoutTimer = null;
for (const field of ['_commandDockTrayObserver', '_leftStackResizeObserver', '_leftStackMutationObserver', '_rightStackResizeObserver', '_rightStackMutationObserver']) { for (const field of [
this[field]?.disconnect(); this[field] = null; '_commandDockTrayObserver',
} '_leftStackResizeObserver',
for (const field of ['_leftStackHudTransitionHandler', '_rightStackHudTransitionHandler']) { '_leftStackMutationObserver',
if (this[field]) document.getElementById('intel-hud')?.removeEventListener('transitionend', this[field]); '_rightStackResizeObserver',
'_rightStackMutationObserver',
]) {
this[field]?.disconnect();
this[field] = null; this[field] = null;
} }
if (this._leftStackCockpitModeHandler) window.removeEventListener('gev:cockpit-mode-changed', this._leftStackCockpitModeHandler); for (const field of [
'_leftStackHudTransitionHandler',
'_rightStackHudTransitionHandler',
]) {
if (this[field])
document
.getElementById('intel-hud')
?.removeEventListener('transitionend', this[field]);
this[field] = null;
}
if (this._leftStackCockpitModeHandler)
window.removeEventListener(
'gev:cockpit-mode-changed',
this._leftStackCockpitModeHandler,
);
this._leftStackCockpitModeHandler = null; this._leftStackCockpitModeHandler = null;
} }
} }
+40 -12
View File
@@ -4,7 +4,12 @@ const PANEL_POSITION_STORAGE_VERSION = 'v8';
const PANEL_Z_BASE = 100; const PANEL_Z_BASE = 100;
const PANEL_Z_MAX = 139; const PANEL_Z_MAX = 139;
export class PanelPositionControls { export class PanelPositionControls {
constructor({ syncPanelCollapseButton, layoutRightPanels, syncCctvPanelViewport, showToast }) { constructor({
syncPanelCollapseButton,
layoutRightPanels,
syncCctvPanelViewport,
showToast,
}) {
this._syncPanelCollapseButton = syncPanelCollapseButton; this._syncPanelCollapseButton = syncPanelCollapseButton;
this._layoutRightPanels = layoutRightPanels; this._layoutRightPanels = layoutRightPanels;
this._syncCctvPanelViewport = syncCctvPanelViewport; this._syncCctvPanelViewport = syncCctvPanelViewport;
@@ -17,7 +22,9 @@ export class PanelPositionControls {
this.destroyed = false; this.destroyed = false;
} }
listen(target, type, callback) { listen(target, type, callback) {
const listener = (event) => { if (!this.destroyed) callback(event); }; const listener = (event) => {
if (!this.destroyed) callback(event);
};
target.addEventListener(type, listener); target.addEventListener(type, listener);
this.removers.push(() => target.removeEventListener(type, listener)); this.removers.push(() => target.removeEventListener(type, listener));
} }
@@ -35,10 +42,13 @@ export class PanelPositionControls {
const marker = `godsEyeView.${PANEL_POSITION_STORAGE_VERSION}.layoutResetNotified`; const marker = `godsEyeView.${PANEL_POSITION_STORAGE_VERSION}.layoutResetNotified`;
if (localStorage.getItem(marker)) return; if (localStorage.getItem(marker)) return;
localStorage.setItem(marker, '1'); localStorage.setItem(marker, '1');
const hadOldPositions = Object.keys(localStorage) const hadOldPositions = Object.keys(localStorage).some((key) =>
.some((key) => key.startsWith('godsEyeView.v6.panelPos.')); key.startsWith('godsEyeView.v6.panelPos.'),
);
if (hadOldPositions) { if (hadOldPositions) {
this._showToast('Panel layout updated — positions reset to new defaults'); this._showToast(
'Panel layout updated — positions reset to new defaults',
);
} }
} catch { } catch {
// storage unavailable // storage unavailable
@@ -63,7 +73,9 @@ export class PanelPositionControls {
// full row set a frame or two later, so the restore-time clamp used a stale (shorter) height and // full row set a frame or two later, so the restore-time clamp used a stale (shorter) height and
// the panel could still hang off the bottom (audit U2). Re-clamp on every size change. // the panel could still hang off the bottom (audit U2). Re-clamp on every size change.
if (this._ppToggles && typeof ResizeObserver !== 'undefined') { if (this._ppToggles && typeof ResizeObserver !== 'undefined') {
this._draggableResizeObserver = new ResizeObserver(() => this._reclampDraggablePanels()); this._draggableResizeObserver = new ResizeObserver(() =>
this._reclampDraggablePanels(),
);
this._draggableResizeObserver.observe(this._ppToggles); this._draggableResizeObserver.observe(this._ppToggles);
} }
} }
@@ -105,7 +117,10 @@ export class PanelPositionControls {
_savePanelCollapsedState(panelId, collapsed) { _savePanelCollapsedState(panelId, collapsed) {
try { try {
localStorage.setItem(this._panelCollapseStorageKey(panelId), collapsed ? '1' : '0'); localStorage.setItem(
this._panelCollapseStorageKey(panelId),
collapsed ? '1' : '0',
);
} catch { } catch {
// storage unavailable // storage unavailable
} }
@@ -124,10 +139,15 @@ export class PanelPositionControls {
const raw = localStorage.getItem(this._panelStorageKey(panelId)); const raw = localStorage.getItem(this._panelStorageKey(panelId));
if (!raw) return; if (!raw) return;
const pos = JSON.parse(raw); const pos = JSON.parse(raw);
if (!pos || typeof pos.left !== 'number' || typeof pos.top !== 'number') return; if (!pos || typeof pos.left !== 'number' || typeof pos.top !== 'number')
return;
// Clamp to the viewport: a position saved at one window size would otherwise land off-screen at // Clamp to the viewport: a position saved at one window size would otherwise land off-screen at
// another (audit U2 — observed a panel at x:-192). The drag handler clamps; restore must too. // another (audit U2 — observed a panel at x:-192). The drag handler clamps; restore must too.
const { left, top } = this._clampToViewport(Math.round(pos.left), Math.round(pos.top), panelEl); const { left, top } = this._clampToViewport(
Math.round(pos.left),
Math.round(pos.top),
panelEl,
);
panelEl.style.left = `${left}px`; panelEl.style.left = `${left}px`;
panelEl.style.top = `${top}px`; panelEl.style.top = `${top}px`;
panelEl.style.right = 'auto'; panelEl.style.right = 'auto';
@@ -153,10 +173,13 @@ export class PanelPositionControls {
_savePanelPosition(panelId, panelEl) { _savePanelPosition(panelId, panelEl) {
const rect = panelEl.getBoundingClientRect(); const rect = panelEl.getBoundingClientRect();
try { try {
localStorage.setItem(this._panelStorageKey(panelId), JSON.stringify({ localStorage.setItem(
this._panelStorageKey(panelId),
JSON.stringify({
left: Math.round(rect.left), left: Math.round(rect.left),
top: Math.round(rect.top), top: Math.round(rect.top),
})); }),
);
} catch { } catch {
// storage unavailable // storage unavailable
} }
@@ -187,7 +210,12 @@ export class PanelPositionControls {
this.listen(handleEl, 'pointerdown', (event) => { this.listen(handleEl, 'pointerdown', (event) => {
if (event.button !== 0) return; if (event.button !== 0) return;
if (event.target.closest('.panel-collapse-btn')) return; if (event.target.closest('.panel-collapse-btn')) return;
if (event.target.closest('input, select, option, button:not(.panel-collapse-btn)')) return; if (
event.target.closest(
'input, select, option, button:not(.panel-collapse-btn)',
)
)
return;
this._cancelDrag?.(); this._cancelDrag?.();
event.preventDefault(); event.preventDefault();
+31 -8
View File
@@ -4,7 +4,11 @@ export class RecordingControls {
this._syncShareState = syncShareState; this._syncShareState = syncShareState;
this.destroyed = false; this.destroyed = false;
this._recordingMode = false; this._recordingMode = false;
this._recordingConfig = { hidePanels: true, hudMode: 'minimal', safeFrame: '16:9' }; this._recordingConfig = {
hidePanels: true,
hudMode: 'minimal',
safeFrame: '16:9',
};
this._preRecordingHudState = null; this._preRecordingHudState = null;
this._safeFrameOverlay = document.getElementById('safe-frame-overlay'); this._safeFrameOverlay = document.getElementById('safe-frame-overlay');
this._safeFrameBox = document.getElementById('safe-frame-box'); this._safeFrameBox = document.getElementById('safe-frame-box');
@@ -15,21 +19,34 @@ export class RecordingControls {
_initRecordingOverlay() { _initRecordingOverlay() {
if (this.destroyed) return; if (this.destroyed) return;
if (!this._safeFrameOverlay || !this._safeFrameBox) return; if (!this._safeFrameOverlay || !this._safeFrameBox) return;
this._safeFrameOverlay.classList.remove('active', 'ratio-9-16', 'ratio-16-9'); this._safeFrameOverlay.classList.remove(
'active',
'ratio-9-16',
'ratio-16-9',
);
} }
setRecordingMode(enabled, options = {}) { setRecordingMode(enabled, options = {}) {
if (this.destroyed) return; if (this.destroyed) return;
const { hidePanels = true, hudMode = 'minimal', safeFrame = '16:9' } = options; const {
hidePanels = true,
hudMode = 'minimal',
safeFrame = '16:9',
} = options;
this._recordingMode = !!enabled; this._recordingMode = !!enabled;
this._recordingConfig = { hidePanels, hudMode, safeFrame }; this._recordingConfig = { hidePanels, hudMode, safeFrame };
document.body.classList.toggle('recording-mode', this._recordingMode && hidePanels); document.body.classList.toggle(
'recording-mode',
this._recordingMode && hidePanels,
);
if (this._safeFrameOverlay) { if (this._safeFrameOverlay) {
this._safeFrameOverlay.classList.remove('ratio-9-16', 'ratio-16-9'); this._safeFrameOverlay.classList.remove('ratio-9-16', 'ratio-16-9');
this._safeFrameOverlay.classList.toggle('active', this._recordingMode); this._safeFrameOverlay.classList.toggle('active', this._recordingMode);
this._safeFrameOverlay.classList.add(safeFrame === '9:16' ? 'ratio-9-16' : 'ratio-16-9'); this._safeFrameOverlay.classList.add(
safeFrame === '9:16' ? 'ratio-9-16' : 'ratio-16-9',
);
} }
if (this._recordingMode) { if (this._recordingMode) {
@@ -46,7 +63,8 @@ export class RecordingControls {
} else if (hudMode === 'full' || hudMode === 'minimal') { } else if (hudMode === 'full' || hudMode === 'minimal') {
this.hud.setMode('on'); this.hud.setMode('on');
this.hud.setVariant(hudMode === 'minimal' ? 'minimal' : 'tactical'); this.hud.setVariant(hudMode === 'minimal' ? 'minimal' : 'tactical');
if (this._hudLayoutSelect) this._hudLayoutSelect.value = this.hud.getVariant(); if (this._hudLayoutSelect)
this._hudLayoutSelect.value = this.hud.getVariant();
} else { } else {
this.hud.setMode('auto'); this.hud.setMode('auto');
} }
@@ -55,11 +73,16 @@ export class RecordingControls {
this._preRecordingHudState = null; this._preRecordingHudState = null;
if (saved) { if (saved) {
this.hud.setVariant(saved.variant); this.hud.setVariant(saved.variant);
if (this._hudLayoutSelect) this._hudLayoutSelect.value = this.hud.getVariant(); if (this._hudLayoutSelect)
this._hudLayoutSelect.value = this.hud.getVariant();
} }
this.hud.setMode(saved ? saved.mode : 'auto'); this.hud.setMode(saved ? saved.mode : 'auto');
if (this._safeFrameOverlay) { if (this._safeFrameOverlay) {
this._safeFrameOverlay.classList.remove('active', 'ratio-9-16', 'ratio-16-9'); this._safeFrameOverlay.classList.remove(
'active',
'ratio-9-16',
'ratio-16-9',
);
} }
} }
this._hudBtn.classList.toggle('active', this.hud.visible); this._hudBtn.classList.toggle('active', this.hud.visible);
+54 -18
View File
@@ -18,14 +18,20 @@ export function readShellElements(document = globalThis.document) {
_hudLayoutRow: document.getElementById('hud-layout-row'), _hudLayoutRow: document.getElementById('hud-layout-row'),
_hudLayoutSelect: document.getElementById('hud-layout-select'), _hudLayoutSelect: document.getElementById('hud-layout-select'),
_detectionSliderRow: document.getElementById('detection-slider-row'), _detectionSliderRow: document.getElementById('detection-slider-row'),
_detectionDensitySlider: document.getElementById('detection-density-slider'), _detectionDensitySlider: document.getElementById(
'detection-density-slider',
),
_detectionDensityValue: document.getElementById('detection-density-value'), _detectionDensityValue: document.getElementById('detection-density-value'),
_detectionAllocationRow: document.getElementById('detection-allocation-row'), _detectionAllocationRow: document.getElementById(
'detection-allocation-row',
),
_detectionFadeRow: document.getElementById('detection-fade-row'), _detectionFadeRow: document.getElementById('detection-fade-row'),
_detectionFadeSlider: document.getElementById('detection-fade-slider'), _detectionFadeSlider: document.getElementById('detection-fade-slider'),
_detectionFadeValue: document.getElementById('detection-fade-value'), _detectionFadeValue: document.getElementById('detection-fade-value'),
_detectionOpacityRow: document.getElementById('detection-opacity-row'), _detectionOpacityRow: document.getElementById('detection-opacity-row'),
_detectionOpacitySlider: document.getElementById('detection-opacity-slider'), _detectionOpacitySlider: document.getElementById(
'detection-opacity-slider',
),
_detectionOpacityValue: document.getElementById('detection-opacity-value'), _detectionOpacityValue: document.getElementById('detection-opacity-value'),
_celestialBtn: document.getElementById('celestial-toggle'), _celestialBtn: document.getElementById('celestial-toggle'),
_scopeBtn: document.getElementById('scope-toggle'), _scopeBtn: document.getElementById('scope-toggle'),
@@ -42,17 +48,39 @@ export function readShellElements(document = globalThis.document) {
_contextRadioDock: document.getElementById('context-radio-dock'), _contextRadioDock: document.getElementById('context-radio-dock'),
_contextRadioToggleBtn: document.getElementById('context-radio-toggle-btn'), _contextRadioToggleBtn: document.getElementById('context-radio-toggle-btn'),
_contextRadioMini: document.getElementById('context-radio-mini'), _contextRadioMini: document.getElementById('context-radio-mini'),
_contextRadioMiniEnableBtn: document.getElementById('context-radio-mini-enable-btn'), _contextRadioMiniEnableBtn: document.getElementById(
_contextRadioDetailsBtn: document.getElementById('context-radio-details-btn'), 'context-radio-mini-enable-btn',
_contextRadioMiniCloseBtn: document.getElementById('context-radio-mini-close-btn'), ),
_contextRadioMiniStation: document.getElementById('context-radio-mini-station'), _contextRadioDetailsBtn: document.getElementById(
_contextRadioMiniPrevBtn: document.getElementById('context-radio-mini-prev-btn'), 'context-radio-details-btn',
_contextRadioMiniPlayBtn: document.getElementById('context-radio-mini-play-btn'), ),
_contextRadioMiniNextBtn: document.getElementById('context-radio-mini-next-btn'), _contextRadioMiniCloseBtn: document.getElementById(
_contextRadioMiniVolume: document.getElementById('context-radio-mini-volume'), 'context-radio-mini-close-btn',
_contextRadioMiniVolumeValue: document.getElementById('context-radio-mini-volume-value'), ),
_cockpitUtilityControls: document.getElementById('cockpit-utility-controls'), _contextRadioMiniStation: document.getElementById(
_cockpitDisplayToggleBtn: document.getElementById('cockpit-display-toggle-btn'), 'context-radio-mini-station',
),
_contextRadioMiniPrevBtn: document.getElementById(
'context-radio-mini-prev-btn',
),
_contextRadioMiniPlayBtn: document.getElementById(
'context-radio-mini-play-btn',
),
_contextRadioMiniNextBtn: document.getElementById(
'context-radio-mini-next-btn',
),
_contextRadioMiniVolume: document.getElementById(
'context-radio-mini-volume',
),
_contextRadioMiniVolumeValue: document.getElementById(
'context-radio-mini-volume-value',
),
_cockpitUtilityControls: document.getElementById(
'cockpit-utility-controls',
),
_cockpitDisplayToggleBtn: document.getElementById(
'cockpit-display-toggle-btn',
),
_cockpitDisplayPanel: document.getElementById('cockpit-display-panel'), _cockpitDisplayPanel: document.getElementById('cockpit-display-panel'),
_cockpitRadioToggleBtn: document.getElementById('cockpit-radio-toggle-btn'), _cockpitRadioToggleBtn: document.getElementById('cockpit-radio-toggle-btn'),
_cockpitRadioPanel: document.getElementById('cockpit-radio-panel'), _cockpitRadioPanel: document.getElementById('cockpit-radio-panel'),
@@ -62,7 +90,9 @@ export function readShellElements(document = globalThis.document) {
_cockpitRadioPlayBtn: document.getElementById('cockpit-radio-play-btn'), _cockpitRadioPlayBtn: document.getElementById('cockpit-radio-play-btn'),
_cockpitRadioNextBtn: document.getElementById('cockpit-radio-next-btn'), _cockpitRadioNextBtn: document.getElementById('cockpit-radio-next-btn'),
_cockpitRadioVolume: document.getElementById('cockpit-radio-volume'), _cockpitRadioVolume: document.getElementById('cockpit-radio-volume'),
_cockpitRadioVolumeValue: document.getElementById('cockpit-radio-volume-value'), _cockpitRadioVolumeValue: document.getElementById(
'cockpit-radio-volume-value',
),
_radioLayerState: document.getElementById('radio-layer-state'), _radioLayerState: document.getElementById('radio-layer-state'),
_radioEnableBtn: document.getElementById('radio-enable-btn'), _radioEnableBtn: document.getElementById('radio-enable-btn'),
_radioFilter: document.getElementById('radio-filter'), _radioFilter: document.getElementById('radio-filter'),
@@ -83,12 +113,18 @@ export function readShellElements(document = globalThis.document) {
_radioVolumeValue: document.getElementById('radio-volume-value'), _radioVolumeValue: document.getElementById('radio-volume-value'),
_radioPlaybackState: document.getElementById('radio-playback-state'), _radioPlaybackState: document.getElementById('radio-playback-state'),
_radioStationHomepage: document.getElementById('radio-station-homepage'), _radioStationHomepage: document.getElementById('radio-station-homepage'),
_globalContextFlightsBtn: document.getElementById('global-context-flights-btn'), _globalContextFlightsBtn: document.getElementById(
_globalContextMissionsBtn: document.getElementById('global-context-missions-btn'), 'global-context-flights-btn',
),
_globalContextMissionsBtn: document.getElementById(
'global-context-missions-btn',
),
_contextModeStandby: document.getElementById('context-mode-standby'), _contextModeStandby: document.getElementById('context-mode-standby'),
_contextFlightsView: document.getElementById('context-flights-view'), _contextFlightsView: document.getElementById('context-flights-view'),
_contextMissionsView: document.getElementById('context-missions-view'), _contextMissionsView: document.getElementById('context-missions-view'),
_installationsSearchBtn: document.getElementById('installations-search-btn'), _installationsSearchBtn: document.getElementById(
'installations-search-btn',
),
_leftPanelStack: document.getElementById('left-panel-stack'), _leftPanelStack: document.getElementById('left-panel-stack'),
_cctvEnableBtn: document.getElementById('cctv-enable-btn'), _cctvEnableBtn: document.getElementById('cctv-enable-btn'),
_cctvNearestBtn: document.getElementById('cctv-nearest-btn'), _cctvNearestBtn: document.getElementById('cctv-nearest-btn'),
+55 -15
View File
@@ -1,5 +1,13 @@
/** Own loading notices, traffic status, toast timing and visibility cleanup. */ /** Own loading notices, traffic status, toast timing and visibility cleanup. */
import { aggregateLayerLoading, createGlobalStatusNotice, createLoadingFeedbackState, createTrafficSyncFeedbackState, presentGlobalLoadingStatus, reduceLoadingFeedback, reduceTrafficSyncFeedback } from '../loadingFeedback.js'; import {
aggregateLayerLoading,
createGlobalStatusNotice,
createLoadingFeedbackState,
createTrafficSyncFeedbackState,
presentGlobalLoadingStatus,
reduceLoadingFeedback,
reduceTrafficSyncFeedback,
} from '../loadingFeedback.js';
import { setSplitFlapText, disposeSplitFlap } from '../splitFlap.js'; import { setSplitFlapText, disposeSplitFlap } from '../splitFlap.js';
export class ShellFeedback { export class ShellFeedback {
constructor({ readLayers }) { constructor({ readLayers }) {
@@ -9,15 +17,24 @@ export class ShellFeedback {
this._trafficSyncFeedbackState = createTrafficSyncFeedbackState(); this._trafficSyncFeedbackState = createTrafficSyncFeedbackState();
this._loadingFeedbackEvent = null; this._loadingFeedbackEvent = null;
this._globalStatusNotice = null; this._globalStatusNotice = null;
this._loadingFeedbackTicker = this._trafficChipTicker = this._toastTimer = null; this._loadingFeedbackTicker =
this._trafficChipTicker =
this._toastTimer =
null;
this._loadingVisibilityHandler = null; this._loadingVisibilityHandler = null;
this._lastLoadingFeedbackUpdateAt = 0; this._lastLoadingFeedbackUpdateAt = 0;
this._globalLoadingStatus = document.getElementById('global-loading-status'); this._globalLoadingStatus = document.getElementById(
'global-loading-status',
);
this._globalLoadingLabel = document.getElementById('global-loading-label'); this._globalLoadingLabel = document.getElementById('global-loading-label');
this._globalLoadingDetail = document.getElementById('global-loading-detail'); this._globalLoadingDetail = document.getElementById(
'global-loading-detail',
);
this._trafficSyncChip = document.getElementById('traffic-sync-chip'); this._trafficSyncChip = document.getElementById('traffic-sync-chip');
this._trafficSyncLabel = document.getElementById('traffic-sync-label'); this._trafficSyncLabel = document.getElementById('traffic-sync-label');
this._trafficSyncProgress = document.getElementById('traffic-sync-progress'); this._trafficSyncProgress = document.getElementById(
'traffic-sync-progress',
);
this._toast = document.getElementById('toast'); this._toast = document.getElementById('toast');
} }
observeVisibility() { observeVisibility() {
@@ -26,23 +43,38 @@ export class ShellFeedback {
if (!document.hidden) this._updateGlobalLoadingFeedback(); if (!document.hidden) this._updateGlobalLoadingFeedback();
else this._stopLoadingFeedbackTicker(); else this._stopLoadingFeedbackTicker();
}; };
document.addEventListener('visibilitychange', this._loadingVisibilityHandler); document.addEventListener(
'visibilitychange',
this._loadingVisibilityHandler,
);
} }
_updateTrafficSyncChip(forceShow = false, now = performance.now()) { _updateTrafficSyncChip(forceShow = false, now = performance.now()) {
if (this.destroyed) return; if (this.destroyed) return;
if (!this._trafficSyncChip || !this._trafficSyncLabel || !this._trafficSyncProgress) return; if (
!this._trafficSyncChip ||
!this._trafficSyncLabel ||
!this._trafficSyncProgress
)
return;
const layers = this.readLayers(); const layers = this.readLayers();
const traffic = Array.isArray(layers) ? layers.find((layer) => layer.id === 'traffic') : null; const traffic = Array.isArray(layers)
? layers.find((layer) => layer.id === 'traffic')
: null;
this._trafficSyncFeedbackState = reduceTrafficSyncFeedback( this._trafficSyncFeedbackState = reduceTrafficSyncFeedback(
this._trafficSyncFeedbackState, this._trafficSyncFeedbackState,
{ enabled: traffic?.enabled === true, stats: traffic?.stats || {}, forceShow }, {
enabled: traffic?.enabled === true,
stats: traffic?.stats || {},
forceShow,
},
now, now,
); );
const presentation = this._trafficSyncFeedbackState; const presentation = this._trafficSyncFeedbackState;
// setSplitFlapText carries the same unchanged-text guard internally, and // setSplitFlapText carries the same unchanged-text guard internally, and
// the flap keeps textContent equal to the settled label throughout, so // the flap keeps textContent equal to the settled label throughout, so
// this stays a no-op on the repeat ticks exactly as it did before. // this stays a no-op on the repeat ticks exactly as it did before.
if (presentation.label) setSplitFlapText(this._trafficSyncLabel, presentation.label); if (presentation.label)
setSplitFlapText(this._trafficSyncLabel, presentation.label);
// Written on every change INCLUDING the empty settled value — the reducer // Written on every change INCLUDING the empty settled value — the reducer
// clears the progress number once the sync lands, and a truthiness guard // clears the progress number once the sync lands, and a truthiness guard
// here would strand the last "..." beside the settled label. // here would strand the last "..." beside the settled label.
@@ -69,9 +101,11 @@ export class ShellFeedback {
summary, summary,
now, now,
); );
if (this._globalStatusNotice?.persistent !== true if (
&& Number.isFinite(this._globalStatusNotice?.hideAt) this._globalStatusNotice?.persistent !== true &&
&& now >= this._globalStatusNotice.hideAt) { Number.isFinite(this._globalStatusNotice?.hideAt) &&
now >= this._globalStatusNotice.hideAt
) {
this._globalStatusNotice = null; this._globalStatusNotice = null;
} }
// Loading phases and universal notices both have time-driven transitions. // Loading phases and universal notices both have time-driven transitions.
@@ -129,7 +163,9 @@ export class ShellFeedback {
const now = performance.now(); const now = performance.now();
this._lastLoadingFeedbackUpdateAt = now; this._lastLoadingFeedbackUpdateAt = now;
this._updateGlobalLoadingFeedback(now); this._updateGlobalLoadingFeedback(now);
const noticeNeedsTicker = Number.isFinite(this._globalStatusNotice?.hideAt); const noticeNeedsTicker = Number.isFinite(
this._globalStatusNotice?.hideAt,
);
if (this._loadingFeedbackState?.phase === 'idle' && !noticeNeedsTicker) { if (this._loadingFeedbackState?.phase === 'idle' && !noticeNeedsTicker) {
this._stopLoadingFeedbackTicker(); this._stopLoadingFeedbackTicker();
} }
@@ -158,7 +194,11 @@ export class ShellFeedback {
clearInterval(this._trafficChipTicker); clearInterval(this._trafficChipTicker);
clearTimeout(this._toastTimer); clearTimeout(this._toastTimer);
this._trafficChipTicker = this._toastTimer = null; this._trafficChipTicker = this._toastTimer = null;
if (this._loadingVisibilityHandler) document.removeEventListener('visibilitychange', this._loadingVisibilityHandler); if (this._loadingVisibilityHandler)
document.removeEventListener(
'visibilitychange',
this._loadingVisibilityHandler,
);
this._loadingVisibilityHandler = null; this._loadingVisibilityHandler = null;
disposeSplitFlap(this._globalLoadingLabel); disposeSplitFlap(this._globalLoadingLabel);
disposeSplitFlap(this._trafficSyncLabel); disposeSplitFlap(this._trafficSyncLabel);
+165 -40
View File
@@ -7,133 +7,258 @@ import { ShellFeedback } from './shellFeedback.js';
import { RecordingControls } from './recordingControls.js'; import { RecordingControls } from './recordingControls.js';
function fixture() { function fixture() {
const saved = Object.fromEntries(['document', 'window', 'localStorage', 'requestAnimationFrame', 'cancelAnimationFrame', 'setTimeout', 'clearTimeout', 'setInterval', 'clearInterval'].map((key) => [key, globalThis[key]])); const saved = Object.fromEntries(
const frames = new Map(), timers = new Map(); [
'document',
'window',
'localStorage',
'requestAnimationFrame',
'cancelAnimationFrame',
'setTimeout',
'clearTimeout',
'setInterval',
'clearInterval',
].map((key) => [key, globalThis[key]]),
);
const frames = new Map(),
timers = new Map();
let next = 0; let next = 0;
const element = () => { const element = () => {
const classes = new Set(); const classes = new Set();
return Object.assign(new EventTarget(), { return Object.assign(new EventTarget(), {
style: {}, dataset: {}, hidden: false, textContent: '', style: {},
dataset: {},
hidden: false,
textContent: '',
classList: { classList: {
add: (...names) => names.forEach((name) => classes.add(name)), add: (...names) => names.forEach((name) => classes.add(name)),
remove: (...names) => names.forEach((name) => classes.delete(name)), remove: (...names) => names.forEach((name) => classes.delete(name)),
contains: (name) => classes.has(name), contains: (name) => classes.has(name),
toggle: (name, active) => active ? classes.add(name) : classes.delete(name), toggle: (name, active) =>
active ? classes.add(name) : classes.delete(name),
}, },
querySelector: () => null, querySelectorAll: () => [], closest: () => null, querySelector: () => null,
getBoundingClientRect: () => ({ left: 20, top: 30, width: 100, height: 80 }), querySelectorAll: () => [],
closest: () => null,
getBoundingClientRect: () => ({
left: 20,
top: 30,
width: 100,
height: 80,
}),
}); });
}; };
const nodes = new Map(['left-panel-stack', 'right-context-rail', 'pp-toggles', 'toast', 'global-loading-status', 'global-loading-detail', 'safe-frame-overlay', 'safe-frame-box', 'hud-toggle', 'hud-layout-select'].map((id) => [id, element()])); const nodes = new Map(
globalThis.document = Object.assign(new EventTarget(), { hidden: false, getElementById: (id) => nodes.get(id) || null, querySelectorAll: () => [], body: element() }); [
globalThis.window = Object.assign(new EventTarget(), { innerWidth: 600, innerHeight: 400 }); 'left-panel-stack',
'right-context-rail',
'pp-toggles',
'toast',
'global-loading-status',
'global-loading-detail',
'safe-frame-overlay',
'safe-frame-box',
'hud-toggle',
'hud-layout-select',
].map((id) => [id, element()]),
);
globalThis.document = Object.assign(new EventTarget(), {
hidden: false,
getElementById: (id) => nodes.get(id) || null,
querySelectorAll: () => [],
body: element(),
});
globalThis.window = Object.assign(new EventTarget(), {
innerWidth: 600,
innerHeight: 400,
});
const writes = []; const writes = [];
globalThis.localStorage = { setItem: (...args) => writes.push(args), getItem: () => null }; globalThis.localStorage = {
globalThis.requestAnimationFrame = (callback) => { frames.set(++next, callback); return next; }; setItem: (...args) => writes.push(args),
getItem: () => null,
};
globalThis.requestAnimationFrame = (callback) => {
frames.set(++next, callback);
return next;
};
globalThis.cancelAnimationFrame = (id) => frames.delete(id); globalThis.cancelAnimationFrame = (id) => frames.delete(id);
globalThis.setTimeout = globalThis.setInterval = (callback) => { timers.set(++next, callback); return next; }; globalThis.setTimeout = globalThis.setInterval = (callback) => {
globalThis.clearTimeout = globalThis.clearInterval = (id) => timers.delete(id); timers.set(++next, callback);
return { frames, timers, nodes, element, writes, restore() { Object.assign(globalThis, saved); } }; return next;
};
globalThis.clearTimeout = globalThis.clearInterval = (id) =>
timers.delete(id);
return {
frames,
timers,
nodes,
element,
writes,
restore() {
Object.assign(globalThis, saved);
},
};
} }
test('UI lifetime revokes listeners and nested presentation work synchronously', () => { test('UI lifetime revokes listeners and nested presentation work synchronously', () => {
const f = fixture(); const f = fixture();
try { try {
const owner = new UiLifetime(), target = new EventTarget(); const owner = new UiLifetime(),
target = new EventTarget();
let calls = 0; let calls = 0;
owner.listen(target, 'change', () => calls++); owner.listen(target, 'change', () => calls++);
owner.frame(() => { calls++; owner.frame(() => calls++); }); owner.frame(() => {
calls++;
owner.frame(() => calls++);
});
owner.timeout(() => calls++, 100); owner.timeout(() => calls++, 100);
const retainedFrame = [...f.frames.values()][0]; const retainedFrame = [...f.frames.values()][0];
owner.destroy(); owner.destroy(); owner.destroy();
owner.destroy();
target.dispatchEvent(new Event('change')); target.dispatchEvent(new Event('change'));
retainedFrame(); retainedFrame();
owner.frame(() => calls++); owner.timeout(() => calls++, 1); owner.frame(() => calls++);
owner.timeout(() => calls++, 1);
assert.equal(calls, 0); assert.equal(calls, 0);
assert.equal(f.frames.size + f.timers.size + owner.removers.size, 0); assert.equal(f.frames.size + f.timers.size + owner.removers.size, 0);
} finally { f.restore(); } } finally {
f.restore();
}
}); });
test('one-time UI listeners and cancelled notice timers release their registrations', () => { test('one-time UI listeners and cancelled notice timers release their registrations', () => {
const f = fixture(); const f = fixture();
try { try {
const owner = new UiLifetime(), target = new EventTarget(); const owner = new UiLifetime(),
target = new EventTarget();
let calls = 0; let calls = 0;
owner.listen(target, 'change', () => calls++, { once: true }); owner.listen(target, 'change', () => calls++, { once: true });
target.dispatchEvent(new Event('change')); target.dispatchEvent(new Event('change')); target.dispatchEvent(new Event('change'));
target.dispatchEvent(new Event('change'));
owner.cancelTimeout(owner.timeout(() => calls++, 100)); owner.cancelTimeout(owner.timeout(() => calls++, 100));
assert.equal(calls, 1); assert.equal(calls, 1);
assert.equal(owner.removers.size + owner.timers.size + f.timers.size, 0); assert.equal(owner.removers.size + owner.timers.size + f.timers.size, 0);
owner.destroy(); owner.destroy();
} finally { f.restore(); } } finally {
f.restore();
}
}); });
test('panel layout coalesces each rail and cannot rearm after destruction', () => { test('panel layout coalesces each rail and cannot rearm after destruction', () => {
const f = fixture(); const f = fixture();
try { try {
let layouts = 0; let layouts = 0;
const owner = new PanelLayoutController({ readHud: () => ({}), scheduleCockpitLayout() {}, syncPanelCollapseButton() {}, readDisplayScrollTop: () => 0 }); const owner = new PanelLayoutController({
readHud: () => ({}),
scheduleCockpitLayout() {},
syncPanelCollapseButton() {},
readDisplayScrollTop: () => 0,
});
owner._syncLeftPanelAdaptiveLayout = () => layouts++; owner._syncLeftPanelAdaptiveLayout = () => layouts++;
owner._scheduleLeftPanelLayout(); owner._scheduleLeftPanelLayout(); owner._scheduleLeftPanelLayout();
owner._scheduleLeftPanelLayout();
assert.equal(f.frames.size, 1); assert.equal(f.frames.size, 1);
owner._scheduleAdaptivePanelLayout({ settle: true }); owner._scheduleAdaptivePanelLayout({ settle: true });
assert.equal(f.frames.size, 2); assert.equal(f.frames.size, 2);
assert.equal(f.timers.size, 1); assert.equal(f.timers.size, 1);
const late = [...f.frames.values(), ...f.timers.values()]; const late = [...f.frames.values(), ...f.timers.values()];
owner.destroy(); owner.destroy(); owner.destroy();
owner.destroy();
late.forEach((callback) => callback()); late.forEach((callback) => callback());
owner._scheduleAdaptivePanelLayout({ settle: true }); owner._scheduleAdaptivePanelLayout({ settle: true });
assert.equal(layouts, 0); assert.equal(layouts, 0);
assert.equal(f.frames.size + f.timers.size, 0); assert.equal(f.frames.size + f.timers.size, 0);
} finally { f.restore(); } } finally {
f.restore();
}
}); });
test('panel disposal cancels a live drag without saving or accepting later pointer movement', () => { test('panel disposal cancels a live drag without saving or accepting later pointer movement', () => {
const f = fixture(); const f = fixture();
try { try {
const owner = new PanelPositionControls({ syncPanelCollapseButton() {}, layoutRightPanels() {}, syncCctvPanelViewport() {}, showToast() {} }); const owner = new PanelPositionControls({
const panel = f.element(), handle = f.element(); syncPanelCollapseButton() {},
layoutRightPanels() {},
syncCctvPanelViewport() {},
showToast() {},
});
const panel = f.element(),
handle = f.element();
owner._makePanelDraggable('sample', panel, handle); owner._makePanelDraggable('sample', panel, handle);
handle.dispatchEvent(Object.assign(new Event('pointerdown', { cancelable: true }), { button: 0, clientX: 30, clientY: 40 })); handle.dispatchEvent(
Object.assign(new Event('pointerdown', { cancelable: true }), {
button: 0,
clientX: 30,
clientY: 40,
}),
);
assert.equal(panel.classList.contains('panel-dragging'), true); assert.equal(panel.classList.contains('panel-dragging'), true);
owner.destroy(); owner.destroy();
const before = { ...panel.style }; const before = { ...panel.style };
window.dispatchEvent(Object.assign(new Event('pointermove'), { clientX: 500, clientY: 300 })); window.dispatchEvent(
Object.assign(new Event('pointermove'), { clientX: 500, clientY: 300 }),
);
window.dispatchEvent(new Event('pointerup')); window.dispatchEvent(new Event('pointerup'));
assert.deepEqual(panel.style, before); assert.deepEqual(panel.style, before);
assert.equal(panel.classList.contains('panel-dragging'), false); assert.equal(panel.classList.contains('panel-dragging'), false);
assert.equal(owner.removers.length, 0); assert.equal(owner.removers.length, 0);
assert.deepEqual(f.writes, []); assert.deepEqual(f.writes, []);
} finally { f.restore(); } } finally {
f.restore();
}
}); });
test('feedback disposal clears toast and polling work and rejects retained notices', () => { test('feedback disposal clears toast and polling work and rejects retained notices', () => {
const f = fixture(); const f = fixture();
try { try {
const owner = new ShellFeedback({ readLayers: () => [] }); const owner = new ShellFeedback({ readLayers: () => [] });
owner.observeVisibility(); owner._startTrafficChipTicker(); owner._showToast('Saved'); owner.observeVisibility();
owner._startTrafficChipTicker();
owner._showToast('Saved');
assert.equal(f.timers.size, 2); assert.equal(f.timers.size, 2);
owner.destroy(); owner.destroy();
owner._showToast('Late'); owner._showGlobalStatusNotice('Late'); owner._startTrafficChipTicker(); owner._showToast('Late');
owner._showGlobalStatusNotice('Late');
owner._startTrafficChipTicker();
document.dispatchEvent(new Event('visibilitychange')); document.dispatchEvent(new Event('visibilitychange'));
assert.equal(f.timers.size, 0); assert.equal(f.timers.size, 0);
assert.equal(f.nodes.get('toast').textContent, 'Saved'); assert.equal(f.nodes.get('toast').textContent, 'Saved');
assert.equal(f.nodes.get('toast').classList.contains('visible'), false); assert.equal(f.nodes.get('toast').classList.contains('visible'), false);
assert.equal(f.nodes.get('global-loading-status').hidden, true); assert.equal(f.nodes.get('global-loading-status').hidden, true);
} finally { f.restore(); } } finally {
f.restore();
}
}); });
test('recording restores the original HUD after repeated entry and revokes retained controls', () => { test('recording restores the original HUD after repeated entry and revokes retained controls', () => {
const f = fixture(); const f = fixture();
try { try {
let mode = 'auto', variant = 'tactical'; let mode = 'auto',
variant = 'tactical';
const owner = new RecordingControls({ syncShareState() {} }); const owner = new RecordingControls({ syncShareState() {} });
owner.hud = { getMode: () => mode, getVariant: () => variant, setMode: (value) => { mode = value; }, setVariant: (value) => { variant = value; }, visible: true }; owner.hud = {
getMode: () => mode,
getVariant: () => variant,
setMode: (value) => {
mode = value;
},
setVariant: (value) => {
variant = value;
},
visible: true,
};
owner.setRecordingMode(true, { hudMode: 'minimal', safeFrame: '9:16' }); owner.setRecordingMode(true, { hudMode: 'minimal', safeFrame: '9:16' });
owner.setRecordingMode(true, { hudMode: 'off' }); owner.setRecordingMode(true, { hudMode: 'off' });
owner.destroy(); owner.setRecordingMode(true); owner.destroy();
assert.equal(mode, 'auto'); assert.equal(variant, 'tactical'); owner.setRecordingMode(true);
assert.equal(mode, 'auto');
assert.equal(variant, 'tactical');
assert.equal(document.body.classList.contains('recording-mode'), false); assert.equal(document.body.classList.contains('recording-mode'), false);
assert.equal(f.nodes.get('safe-frame-overlay').classList.contains('active'), false); assert.equal(
} finally { f.restore(); } f.nodes.get('safe-frame-overlay').classList.contains('active'),
false,
);
} finally {
f.restore();
}
}); });
+18 -5
View File
@@ -14,7 +14,11 @@
#cctv-panel .panel-glow { #cctv-panel .panel-glow {
position: absolute; position: absolute;
inset: -18px; inset: -18px;
background: radial-gradient(ellipse at center, rgba(0, 212, 255, 0.16) 0%, transparent 65%); background: radial-gradient(
ellipse at center,
rgba(0, 212, 255, 0.16) 0%,
transparent 65%
);
filter: blur(16px); filter: blur(16px);
opacity: 0.25; opacity: 0.25;
pointer-events: none; pointer-events: none;
@@ -51,7 +55,11 @@
} }
.cctv-panel-inner::-webkit-scrollbar-thumb { .cctv-panel-inner::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, rgba(0, 212, 255, 0.6), rgba(0, 212, 255, 0.28)); background: linear-gradient(
180deg,
rgba(0, 212, 255, 0.6),
rgba(0, 212, 255, 0.28)
);
border: 1px solid rgba(0, 212, 255, 0.4); border: 1px solid rgba(0, 212, 255, 0.4);
border-radius: 999px; border-radius: 999px;
} }
@@ -99,7 +107,9 @@
border-radius: 7px; border-radius: 7px;
padding: 4px 7px; padding: 4px 7px;
cursor: text; cursor: text;
transition: border-color 0.15s ease, background 0.15s ease; transition:
border-color 0.15s ease,
background 0.15s ease;
} }
.cctv-cal-value:hover:not(:disabled) { .cctv-cal-value:hover:not(:disabled) {
@@ -212,7 +222,11 @@
overflow: hidden; overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.12); border: 1px solid rgba(255, 255, 255, 0.12);
background: background:
radial-gradient(circle at 20% 20%, rgba(0, 244, 255, 0.09), transparent 44%), radial-gradient(
circle at 20% 20%,
rgba(0, 244, 255, 0.09),
transparent 44%
),
linear-gradient(180deg, rgba(8, 14, 19, 0.88), rgba(4, 7, 11, 0.96)); linear-gradient(180deg, rgba(8, 14, 19, 0.88), rgba(4, 7, 11, 0.96));
min-height: 150px; min-height: 150px;
/* .cctv-panel-inner is a scrolling column flex container, so the default /* .cctv-panel-inner is a scrolling column flex container, so the default
@@ -335,4 +349,3 @@
color: #a8f7ff; color: #a8f7ff;
text-shadow: 0 0 10px rgba(0, 212, 255, 0.18); text-shadow: 0 0 10px rgba(0, 212, 255, 0.18);
} }
+582 -215
View File
File diff suppressed because it is too large. Load diff
+42 -13
View File
@@ -21,11 +21,15 @@
} }
#command-dock:has(#location-bar:not(.collapsed)) { #command-dock:has(#location-bar:not(.collapsed)) {
transform: translateX(calc(-50% + (var(--dock-location-open) - var(--dock-side-compact)) / 2)); transform: translateX(
calc(-50% + (var(--dock-location-open) - var(--dock-side-compact)) / 2)
);
} }
#command-dock:has(#control-panel:not(.collapsed)) { #command-dock:has(#control-panel:not(.collapsed)) {
transform: translateX(calc(-50% - (var(--dock-presets-open) - var(--dock-side-compact)) / 2)); transform: translateX(
calc(-50% - (var(--dock-presets-open) - var(--dock-side-compact)) / 2)
);
} }
#command-dock > #location-bar, #command-dock > #location-bar,
@@ -300,13 +304,27 @@
transform-origin: bottom; transform-origin: bottom;
} }
#command-dock .gev-voice-visualizer span:nth-child(3n) { height: 1.35rem; } #command-dock .gev-voice-visualizer span:nth-child(3n) {
#command-dock .gev-voice-visualizer span:nth-child(4n) { height: 0.95rem; } height: 1.35rem;
#command-dock .gev-voice-visualizer span:nth-child(5n) { height: 1.7rem; } }
#command-dock .gev-voice-visualizer span:nth-child(7n) { height: 0.75rem; } #command-dock .gev-voice-visualizer span:nth-child(4n) {
height: 0.95rem;
}
#command-dock .gev-voice-visualizer span:nth-child(5n) {
height: 1.7rem;
}
#command-dock .gev-voice-visualizer span:nth-child(7n) {
height: 0.75rem;
}
#command-dock #gev-voice-control[data-status='listening'] .gev-voice-visualizer span, #command-dock
#command-dock #gev-voice-control[data-status='executing'] .gev-voice-visualizer span { #gev-voice-control[data-status='listening']
.gev-voice-visualizer
span,
#command-dock
#gev-voice-control[data-status='executing']
.gev-voice-visualizer
span {
animation: gev-voice-bar 680ms ease-in-out infinite alternate; animation: gev-voice-bar 680ms ease-in-out infinite alternate;
animation-delay: calc(var(--bar) * -45ms); animation-delay: calc(var(--bar) * -45ms);
} }
@@ -336,13 +354,25 @@
} }
@keyframes command-dock-reveal { @keyframes command-dock-reveal {
from { opacity: 0; transform: translateY(0.2rem); } from {
to { opacity: 1; transform: translateY(0); } opacity: 0;
transform: translateY(0.2rem);
}
to {
opacity: 1;
transform: translateY(0);
}
} }
@keyframes gev-voice-bar { @keyframes gev-voice-bar {
from { transform: scaleY(0.35); opacity: 0.5; } from {
to { transform: scaleY(1); opacity: 1; } transform: scaleY(0.35);
opacity: 0.5;
}
to {
transform: scaleY(1);
opacity: 1;
}
} }
/* Compact dock clearance keeps the reference-like low profile. */ /* Compact dock clearance keeps the reference-like low profile. */
@@ -401,4 +431,3 @@ body:not(.ui-clean-view):not(.recording-mode) #cesium-credits {
flex-basis: 8.5rem; flex-basis: 8.5rem;
} }
} }
+97 -30
View File
@@ -170,7 +170,13 @@
} }
#command-dock #location-bar .dock-popover-content { #command-dock #location-bar .dock-popover-content {
left: calc((var(--dock-side-compact) + var(--dock-side-compact) + var(--dock-voice-width) - var(--dock-popover-mobile-width)) / 2); left: calc(
(
var(--dock-side-compact) + var(--dock-side-compact) +
var(--dock-voice-width) - var(--dock-popover-mobile-width)
) /
2
);
} }
#command-dock #control-panel .dock-popover-content { #command-dock #control-panel .dock-popover-content {
@@ -182,7 +188,13 @@
left edge — pinned by the 480 px bounds assert in left edge — pinned by the 480 px bounds assert in
`scripts/qa-map-source-tray.mjs`. */ `scripts/qa-map-source-tray.mjs`. */
width: var(--dock-popover-mobile-width); width: var(--dock-popover-mobile-width);
right: calc((var(--dock-side-compact) + var(--dock-side-compact) + var(--dock-voice-width) - var(--dock-popover-mobile-width)) / 2); right: calc(
(
var(--dock-side-compact) + var(--dock-side-compact) +
var(--dock-voice-width) - var(--dock-popover-mobile-width)
) /
2
);
} }
/* ── Required-attribution clearance ───────────────────────────────── /* ── Required-attribution clearance ─────────────────────────────────
@@ -213,8 +225,12 @@
bottom: calc(100% + 3rem); bottom: calc(100% + 3rem);
} }
#command-dock.dock-has-pinned-tray #location-bar:not(.collapsed):not(.dock-pinned) .dock-popover-content, #command-dock.dock-has-pinned-tray
#command-dock.dock-has-pinned-tray #control-panel:not(.collapsed):not(.dock-pinned) .dock-popover-content { #location-bar:not(.collapsed):not(.dock-pinned)
.dock-popover-content,
#command-dock.dock-has-pinned-tray
#control-panel:not(.collapsed):not(.dock-pinned)
.dock-popover-content {
bottom: calc(100% + 3rem + var(--dock-pinned-stack-height)); bottom: calc(100% + 3rem + var(--dock-pinned-stack-height));
} }
@@ -224,8 +240,12 @@
top tray drops var(--dock-lower-pinned-height), landing on top of its top tray drops var(--dock-lower-pinned-height), landing on top of its
pinned sibling. Naming the panel makes it (2,5,0), ahead of both the pinned sibling. Naming the panel makes it (2,5,0), ahead of both the
ordinary (2,1,0) and single-pinned (2,4,0) rules. */ ordinary (2,1,0) and single-pinned (2,4,0) rules. */
#command-dock.dock-has-two-pinned-trays #location-bar.dock-pinned-top.dock-pinned:not(.collapsed) .dock-popover-content, #command-dock.dock-has-two-pinned-trays
#command-dock.dock-has-two-pinned-trays #control-panel.dock-pinned-top.dock-pinned:not(.collapsed) .dock-popover-content { #location-bar.dock-pinned-top.dock-pinned:not(.collapsed)
.dock-popover-content,
#command-dock.dock-has-two-pinned-trays
#control-panel.dock-pinned-top.dock-pinned:not(.collapsed)
.dock-popover-content {
bottom: calc(100% + 3rem + var(--dock-lower-pinned-height)); bottom: calc(100% + 3rem + var(--dock-lower-pinned-height));
} }
} }
@@ -242,14 +262,22 @@
bottom: calc(100% + 2vh + 2.5rem); bottom: calc(100% + 2vh + 2.5rem);
} }
#command-dock.dock-has-pinned-tray #location-bar:not(.collapsed):not(.dock-pinned) .dock-popover-content, #command-dock.dock-has-pinned-tray
#command-dock.dock-has-pinned-tray #control-panel:not(.collapsed):not(.dock-pinned) .dock-popover-content { #location-bar:not(.collapsed):not(.dock-pinned)
.dock-popover-content,
#command-dock.dock-has-pinned-tray
#control-panel:not(.collapsed):not(.dock-pinned)
.dock-popover-content {
bottom: calc(100% + 2vh + 2.5rem + var(--dock-pinned-stack-height)); bottom: calc(100% + 2vh + 2.5rem + var(--dock-pinned-stack-height));
} }
/* Panel IDs for the same specificity reason as the max-width:900px block. */ /* Panel IDs for the same specificity reason as the max-width:900px block. */
#command-dock.dock-has-two-pinned-trays #location-bar.dock-pinned-top.dock-pinned:not(.collapsed) .dock-popover-content, #command-dock.dock-has-two-pinned-trays
#command-dock.dock-has-two-pinned-trays #control-panel.dock-pinned-top.dock-pinned:not(.collapsed) .dock-popover-content { #location-bar.dock-pinned-top.dock-pinned:not(.collapsed)
.dock-popover-content,
#command-dock.dock-has-two-pinned-trays
#control-panel.dock-pinned-top.dock-pinned:not(.collapsed)
.dock-popover-content {
bottom: calc(100% + 2vh + 2.5rem + var(--dock-lower-pinned-height)); bottom: calc(100% + 2vh + 2.5rem + var(--dock-lower-pinned-height));
} }
} }
@@ -327,24 +355,39 @@
height: var(--audio-level, 5px); height: var(--audio-level, 5px);
opacity: var(--audio-opacity, 0.56); opacity: var(--audio-opacity, 0.56);
animation: none; animation: none;
transition: height 70ms linear, opacity 90ms linear; transition:
height 70ms linear,
opacity 90ms linear;
} }
#command-dock #gev-voice-control[data-status='listening'] .gev-voice-visualizer span, #command-dock
#command-dock #gev-voice-control[data-status='executing'] .gev-voice-visualizer span { #gev-voice-control[data-status='listening']
.gev-voice-visualizer
span,
#command-dock
#gev-voice-control[data-status='executing']
.gev-voice-visualizer
span {
animation: none; animation: none;
} }
#command-dock #gev-voice-control[data-speaker='user'] :is(#gev-voice-status, #gev-voice-detail) { #command-dock
#gev-voice-control[data-speaker='user']
:is(#gev-voice-status, #gev-voice-detail) {
color: #ffbf69; color: #ffbf69;
} }
#command-dock #gev-voice-control[data-speaker='user'] .gev-voice-visualizer span { #command-dock
#gev-voice-control[data-speaker='user']
.gev-voice-visualizer
span {
background: linear-gradient(to top, #d98a35, #ffcf8a); background: linear-gradient(to top, #d98a35, #ffcf8a);
box-shadow: 0 0 0.35rem rgba(255, 191, 105, 0.32); box-shadow: 0 0 0.35rem rgba(255, 191, 105, 0.32);
} }
#command-dock #gev-voice-control[data-speaker='ai'] :is(#gev-voice-status, #gev-voice-detail) { #command-dock
#gev-voice-control[data-speaker='ai']
:is(#gev-voice-status, #gev-voice-detail) {
color: var(--accent); color: var(--accent);
} }
@@ -364,11 +407,15 @@
@media (max-width: 900px) { @media (max-width: 900px) {
#command-dock #location-bar .dock-popover-content { #command-dock #location-bar .dock-popover-content {
left: calc((var(--dock-unified-width) - var(--dock-popover-mobile-width)) / 2); left: calc(
(var(--dock-unified-width) - var(--dock-popover-mobile-width)) / 2
);
} }
#command-dock #control-panel .dock-popover-content { #command-dock #control-panel .dock-popover-content {
right: calc((var(--dock-unified-width) - var(--dock-popover-mobile-width)) / 2); right: calc(
(var(--dock-unified-width) - var(--dock-popover-mobile-width)) / 2
);
} }
} }
@@ -417,32 +464,44 @@
box-shadow: box-shadow:
0 0 0 0.18rem rgba(232, 234, 237, 0.04), 0 0 0 0.18rem rgba(232, 234, 237, 0.04),
0 0 0.75rem rgba(232, 234, 237, 0.08); 0 0 0.75rem rgba(232, 234, 237, 0.08);
transition: background var(--transition-fast), border-color var(--transition-fast), box-shadow var(--transition-fast); transition:
background var(--transition-fast),
border-color var(--transition-fast),
box-shadow var(--transition-fast);
} }
#command-dock .gev-mic-orbit img { #command-dock .gev-mic-orbit img {
filter: grayscale(1) saturate(0) brightness(1.65); filter: grayscale(1) saturate(0) brightness(1.65);
opacity: 0.68; opacity: 0.68;
transition: filter var(--transition-fast), opacity var(--transition-fast); transition:
filter var(--transition-fast),
opacity var(--transition-fast);
} }
#command-dock #gev-voice-button:hover .gev-mic-orbit, #command-dock #gev-voice-button:hover .gev-mic-orbit,
#command-dock #gev-voice-control:is( #command-dock
#gev-voice-control:is(
[data-status='connecting'], [data-status='connecting'],
[data-status='listening']:not([data-microphone='muted']), [data-status='listening']:not([data-microphone='muted']),
[data-status='executing'] [data-status='executing']
) .gev-mic-orbit { )
.gev-mic-orbit {
background: rgba(0, 212, 255, 0.06); background: rgba(0, 212, 255, 0.06);
border-color: rgba(0, 212, 255, 0.48); border-color: rgba(0, 212, 255, 0.48);
box-shadow: 0 0 0 0.18rem rgba(0, 212, 255, 0.06), 0 0 0.9rem rgba(0, 212, 255, 0.12); box-shadow:
0 0 0 0.18rem rgba(0, 212, 255, 0.06),
0 0 0.9rem rgba(0, 212, 255, 0.12);
} }
#command-dock #gev-voice-button:hover .gev-mic-orbit img, #command-dock #gev-voice-button:hover .gev-mic-orbit img,
#command-dock #gev-voice-control:is( #command-dock
#gev-voice-control:is(
[data-status='connecting'], [data-status='connecting'],
[data-status='listening']:not([data-microphone='muted']), [data-status='listening']:not([data-microphone='muted']),
[data-status='executing'] [data-status='executing']
) .gev-mic-orbit img { )
.gev-mic-orbit
img {
filter: none; filter: none;
opacity: 1; opacity: 1;
} }
@@ -470,7 +529,6 @@
background: var(--glass-bg); background: var(--glass-bg);
border-color: var(--glass-border-hover); border-color: var(--glass-border-hover);
color: var(--accent); color: var(--accent);
} }
#command-dock > :is(#location-bar, #control-panel).dock-pinned { #command-dock > :is(#location-bar, #control-panel).dock-pinned {
@@ -590,7 +648,9 @@
} }
#gev-voice-control:not([data-status='error']):hover .gev-voice-help-tray, #gev-voice-control:not([data-status='error']):hover .gev-voice-help-tray,
#gev-voice-control:not([data-status='error']) #gev-voice-button:focus-visible ~ .gev-voice-help-tray { #gev-voice-control:not([data-status='error'])
#gev-voice-button:focus-visible
~ .gev-voice-help-tray {
visibility: visible; visibility: visible;
opacity: 1; opacity: 1;
transform: translate(-50%, 0) scale(1); transform: translate(-50%, 0) scale(1);
@@ -622,10 +682,17 @@
} }
#command-dock #location-bar .location-toolbar::after { #command-dock #location-bar .location-toolbar::after {
background: linear-gradient(90deg, rgba(232, 234, 237, 0.24), rgba(232, 234, 237, 0)); background: linear-gradient(
90deg,
rgba(232, 234, 237, 0.24),
rgba(232, 234, 237, 0)
);
} }
#command-dock #control-panel .panel-header::after { #command-dock #control-panel .panel-header::after {
background: linear-gradient(90deg, rgba(232, 234, 237, 0), rgba(232, 234, 237, 0.24)); background: linear-gradient(
90deg,
rgba(232, 234, 237, 0),
rgba(232, 234, 237, 0.24)
);
} }
+46 -18
View File
@@ -5,7 +5,9 @@
--dock-presets-pinned-height: 0px; --dock-presets-pinned-height: 0px;
--dock-pinned-stack-height: 0px; --dock-pinned-stack-height: 0px;
--dock-lower-pinned-height: 0px; --dock-lower-pinned-height: 0px;
--dock-unified-width: calc(var(--dock-side-compact) * 2 + var(--dock-voice-width)); --dock-unified-width: calc(
var(--dock-side-compact) * 2 + var(--dock-voice-width)
);
--dock-location-open: max(34rem, calc(var(--dock-unified-width) + 0.75rem)); --dock-location-open: max(34rem, calc(var(--dock-unified-width) + 0.75rem));
--dock-presets-open: max(34rem, calc(var(--dock-unified-width) + 0.75rem)); --dock-presets-open: max(34rem, calc(var(--dock-unified-width) + 0.75rem));
} }
@@ -33,7 +35,9 @@
-webkit-mask-position: center; -webkit-mask-position: center;
-webkit-mask-repeat: no-repeat; -webkit-mask-repeat: no-repeat;
-webkit-mask-size: contain; -webkit-mask-size: contain;
transition: background-color 160ms ease, filter 160ms ease; transition:
background-color 160ms ease,
filter 160ms ease;
} }
#command-dock .dock-label-icon-location { #command-dock .dock-label-icon-location {
@@ -53,12 +57,10 @@
} }
#command-dock :is(#location-bar, #control-panel):hover .location-toolbar-label, #command-dock :is(#location-bar, #control-panel):hover .location-toolbar-label,
#command-dock :is(#location-bar, #control-panel):hover .panel-title{ #command-dock :is(#location-bar, #control-panel):hover .panel-title {
color: var(--accent); color: var(--accent);
} }
#command-dock #control-panel .panel-header { #command-dock #control-panel .panel-header {
justify-content: center; justify-content: center;
} }
@@ -173,7 +175,9 @@
width: 0.9rem; width: 0.9rem;
height: 0.9rem; height: 0.9rem;
opacity: 0.58; opacity: 0.58;
transition: opacity 160ms ease, transform 160ms ease; transition:
opacity 160ms ease,
transform 160ms ease;
} }
#command-dock #location-bar .dock-pin-btn { #command-dock #location-bar .dock-pin-btn {
@@ -188,12 +192,18 @@
transform: rotate(-12deg) scale(1.08); transform: rotate(-12deg) scale(1.08);
} }
#command-dock.dock-has-pinned-tray #location-bar:not(.collapsed):not(.dock-pinned) .dock-popover-content, #command-dock.dock-has-pinned-tray
#command-dock.dock-has-pinned-tray #control-panel:not(.collapsed):not(.dock-pinned) .dock-popover-content { #location-bar:not(.collapsed):not(.dock-pinned)
.dock-popover-content,
#command-dock.dock-has-pinned-tray
#control-panel:not(.collapsed):not(.dock-pinned)
.dock-popover-content {
bottom: calc(100% + 1.2rem + var(--dock-pinned-stack-height)); bottom: calc(100% + 1.2rem + var(--dock-pinned-stack-height));
} }
#command-dock.dock-has-two-pinned-trays .dock-pinned-top.dock-pinned:not(.collapsed) .dock-popover-content { #command-dock.dock-has-two-pinned-trays
.dock-pinned-top.dock-pinned:not(.collapsed)
.dock-popover-content {
bottom: calc(100% + 1.2rem + var(--dock-lower-pinned-height)); bottom: calc(100% + 1.2rem + var(--dock-lower-pinned-height));
} }
@@ -264,7 +274,9 @@
background: rgba(0, 212, 255, 0.06); background: rgba(0, 212, 255, 0.06);
border: 1px solid rgba(0, 212, 255, 0.48); border: 1px solid rgba(0, 212, 255, 0.48);
border-radius: 50%; border-radius: 50%;
box-shadow: 0 0 0 0.18rem rgba(0, 212, 255, 0.06), 0 0 0.9rem rgba(0, 212, 255, 0.12); box-shadow:
0 0 0 0.18rem rgba(0, 212, 255, 0.06),
0 0 0.9rem rgba(0, 212, 255, 0.12);
} }
#command-dock .gev-mic-label { #command-dock .gev-mic-label {
@@ -288,9 +300,13 @@
transition: opacity 180ms ease; transition: opacity 180ms ease;
} }
#command-dock #gev-voice-control[data-status='connecting'] .gev-voice-visualizer, #command-dock
#gev-voice-control[data-status='connecting']
.gev-voice-visualizer,
#command-dock #gev-voice-control[data-status='listening'] .gev-voice-visualizer, #command-dock #gev-voice-control[data-status='listening'] .gev-voice-visualizer,
#command-dock #gev-voice-control[data-status='executing'] .gev-voice-visualizer { #command-dock
#gev-voice-control[data-status='executing']
.gev-voice-visualizer {
visibility: visible; visibility: visible;
opacity: 1; opacity: 1;
transform: none; transform: none;
@@ -362,16 +378,23 @@
background: rgba(34, 10, 16, 0.96); background: rgba(34, 10, 16, 0.96);
border: 1px solid rgba(255, 92, 110, 0.5); border: 1px solid rgba(255, 92, 110, 0.5);
border-radius: 0.42rem; border-radius: 0.42rem;
box-shadow: 0 0.9rem 2.2rem rgba(0, 0, 0, 0.62), 0 0 1.4rem rgba(255, 92, 110, 0.08); box-shadow:
0 0.9rem 2.2rem rgba(0, 0, 0, 0.62),
0 0 1.4rem rgba(255, 92, 110, 0.08);
backdrop-filter: blur(28px) saturate(1.25); backdrop-filter: blur(28px) saturate(1.25);
-webkit-backdrop-filter: blur(28px) saturate(1.25); -webkit-backdrop-filter: blur(28px) saturate(1.25);
pointer-events: none; pointer-events: none;
transform: translate(-50%, 0.55rem) scale(0.985); transform: translate(-50%, 0.55rem) scale(0.985);
transform-origin: bottom center; transform-origin: bottom center;
transition: opacity 180ms ease, visibility 180ms ease, transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1); transition:
opacity 180ms ease,
visibility 180ms ease,
transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
} }
#command-dock #gev-voice-control[data-status='error']:not(.error-dismissed) .gev-voice-error-tray { #command-dock
#gev-voice-control[data-status='error']:not(.error-dismissed)
.gev-voice-error-tray {
visibility: visible; visibility: visible;
opacity: 1; opacity: 1;
pointer-events: auto; pointer-events: auto;
@@ -421,7 +444,10 @@
/* The compact dock leaves both lower corners free for required attribution and telemetry. */ /* The compact dock leaves both lower corners free for required attribution and telemetry. */
body:not(.ui-clean-view):not(.recording-mode) #cesium-credits, body:not(.ui-clean-view):not(.recording-mode) #cesium-credits,
body:not(.ui-clean-view):not(.recording-mode):has(#intel-hud[data-variant='minimal'].active) #cesium-credits { body:not(.ui-clean-view):not(.recording-mode):has(
#intel-hud[data-variant='minimal'].active
)
#cesium-credits {
right: auto; right: auto;
bottom: 0.75rem; bottom: 0.75rem;
left: 1.5rem; left: 1.5rem;
@@ -452,7 +478,10 @@ body:not(.ui-clean-view):not(.recording-mode):has(#intel-hud[data-variant='minim
@media (max-width: 900px) { @media (max-width: 900px) {
body:not(.ui-clean-view):not(.recording-mode) #cesium-credits, body:not(.ui-clean-view):not(.recording-mode) #cesium-credits,
body:not(.ui-clean-view):not(.recording-mode):has(#intel-hud[data-variant='minimal'].active) #cesium-credits { body:not(.ui-clean-view):not(.recording-mode):has(
#intel-hud[data-variant='minimal'].active
)
#cesium-credits {
bottom: calc(2vh + 5rem); bottom: calc(2vh + 5rem);
left: 0.75rem; left: 0.75rem;
max-width: calc(100vw - 1.5rem); max-width: calc(100vw - 1.5rem);
@@ -480,4 +509,3 @@ body:not(.ui-clean-view):not(.recording-mode):has(#intel-hud[data-variant='minim
bottom: calc(50vh + 8px); bottom: calc(50vh + 8px);
} }
} }
+11 -3
View File
@@ -7,7 +7,10 @@
z-index: 145; z-index: 145;
display: grid; display: grid;
grid-template-areas: 'locations voice presets'; grid-template-areas: 'locations voice presets';
grid-template-columns: minmax(300px, 1fr) minmax(230px, 270px) minmax(390px, 1.15fr); grid-template-columns: minmax(300px, 1fr) minmax(230px, 270px) minmax(
390px,
1.15fr
);
align-items: stretch; align-items: stretch;
width: min(94vw, 1240px); width: min(94vw, 1240px);
padding: 6px; padding: 6px;
@@ -22,7 +25,9 @@
backdrop-filter: blur(28px) saturate(1.35); backdrop-filter: blur(28px) saturate(1.35);
-webkit-backdrop-filter: blur(28px) saturate(1.35); -webkit-backdrop-filter: blur(28px) saturate(1.35);
transform: translateX(-50%); transform: translateX(-50%);
transition: opacity var(--transition-smooth), visibility var(--transition-smooth); transition:
opacity var(--transition-smooth),
visibility var(--transition-smooth);
} }
#command-dock > #location-bar, #command-dock > #location-bar,
@@ -214,7 +219,10 @@ body:not(.ui-clean-view):not(.recording-mode) #cesium-credits {
bottom: calc(2vh + 7rem); bottom: calc(2vh + 7rem);
} }
body:not(.ui-clean-view):not(.recording-mode):has(#intel-hud[data-variant='minimal'].active) #cesium-credits { body:not(.ui-clean-view):not(.recording-mode):has(
#intel-hud[data-variant='minimal'].active
)
#cesium-credits {
right: 36px; right: 36px;
left: auto; left: auto;
width: min(340px, calc(100vw - 72px)) !important; width: min(340px, calc(100vw - 72px)) !important;
+76 -22
View File
@@ -12,7 +12,11 @@
#control-panel .panel-glow { #control-panel .panel-glow {
position: absolute; position: absolute;
inset: -30px; inset: -30px;
background: radial-gradient(ellipse at center bottom, var(--accent-dim) 0%, transparent 60%); background: radial-gradient(
ellipse at center bottom,
var(--accent-dim) 0%,
transparent 60%
);
filter: blur(30px); filter: blur(30px);
opacity: 0.4; opacity: 0.4;
pointer-events: none; pointer-events: none;
@@ -185,7 +189,11 @@
content: ''; content: '';
position: absolute; position: absolute;
inset: 0; inset: 0;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.04) 0%, transparent 100%); background: linear-gradient(
180deg,
rgba(255, 255, 255, 0.04) 0%,
transparent 100%
);
opacity: 0; opacity: 0;
transition: opacity var(--transition-fast); transition: opacity var(--transition-fast);
} }
@@ -264,7 +272,9 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
transition: opacity 0.8s ease, visibility 0.8s ease; transition:
opacity 0.8s ease,
visibility 0.8s ease;
} }
#loading-screen.hidden { #loading-screen.hidden {
@@ -287,8 +297,15 @@
} }
@keyframes logo-pulse { @keyframes logo-pulse {
0%, 100% { opacity: 0.72; transform: scale(0.98); } 0%,
50% { opacity: 1; transform: scale(1.02); } 100% {
opacity: 0.72;
transform: scale(0.98);
}
50% {
opacity: 1;
transform: scale(1.02);
}
} }
.loader-content h2 { .loader-content h2 {
@@ -309,8 +326,13 @@
} }
@keyframes pulse { @keyframes pulse {
0%, 100% { opacity: 0.4; } 0%,
50% { opacity: 1; } 100% {
opacity: 0.4;
}
50% {
opacity: 1;
}
} }
/* ── Post-Processing Toggles ─────────────────── */ /* ── Post-Processing Toggles ─────────────────── */
@@ -332,7 +354,9 @@
border: 1px solid var(--glass-border); border: 1px solid var(--glass-border);
border-radius: var(--panel-radius); border-radius: var(--panel-radius);
background: var(--glass-bg); background: var(--glass-bg);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.42), inset 0 1px 0 rgba(255, 255, 255, 0.04); box-shadow:
0 8px 32px rgba(0, 0, 0, 0.42),
inset 0 1px 0 rgba(255, 255, 255, 0.04);
backdrop-filter: blur(24px) saturate(1.4); backdrop-filter: blur(24px) saturate(1.4);
-webkit-backdrop-filter: blur(24px) saturate(1.4); -webkit-backdrop-filter: blur(24px) saturate(1.4);
} }
@@ -389,7 +413,9 @@
border: 1px solid var(--glass-border); border: 1px solid var(--glass-border);
border-radius: var(--panel-radius); border-radius: var(--panel-radius);
background: var(--glass-bg); background: var(--glass-bg);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.42), inset 0 1px 0 rgba(255, 255, 255, 0.04); box-shadow:
0 8px 32px rgba(0, 0, 0, 0.42),
inset 0 1px 0 rgba(255, 255, 255, 0.04);
backdrop-filter: blur(24px) saturate(1.4); backdrop-filter: blur(24px) saturate(1.4);
-webkit-backdrop-filter: blur(24px) saturate(1.4); -webkit-backdrop-filter: blur(24px) saturate(1.4);
cursor: default; cursor: default;
@@ -461,7 +487,11 @@
font-size: 14px; font-size: 14px;
font-weight: 400; font-weight: 400;
font-style: normal; font-style: normal;
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 20; font-variation-settings:
'FILL' 0,
'wght' 400,
'GRAD' 0,
'opsz' 20;
} }
.pp-label { .pp-label {
@@ -600,7 +630,7 @@
outline-offset: 2px; outline-offset: 2px;
} }
.gev-quantitative-slider:disabled { .gev-quantitative-slider:disabled {
opacity: .42; opacity: 0.42;
cursor: not-allowed; cursor: not-allowed;
} }
.gev-slider-value { .gev-slider-value {
@@ -608,7 +638,7 @@
color: var(--accent); color: var(--accent);
font-family: var(--font-mono); font-family: var(--font-mono);
font-size: 9px; font-size: 9px;
letter-spacing: .5px; letter-spacing: 0.5px;
text-align: right; text-align: right;
flex-shrink: 0; flex-shrink: 0;
} }
@@ -793,7 +823,9 @@
} }
/* Proximity / All segmented control under the 3D toggle */ /* Proximity / All segmented control under the 3D toggle */
#models3d-mode-row { min-width: 184px; } #models3d-mode-row {
min-width: 184px;
}
.pp-mode-seg { .pp-mode-seg {
display: flex; display: flex;
flex: 1; flex: 1;
@@ -812,11 +844,21 @@
letter-spacing: 0.6px; letter-spacing: 0.6px;
text-transform: uppercase; text-transform: uppercase;
cursor: pointer; cursor: pointer;
transition: background-color var(--transition-fast), color var(--transition-fast); transition:
background-color var(--transition-fast),
color var(--transition-fast);
}
.pp-mode-btn + .pp-mode-btn {
border-left: 1px solid rgba(255, 255, 255, 0.14);
}
.pp-mode-btn:hover {
background: rgba(255, 255, 255, 0.08);
color: var(--text-primary);
}
.pp-mode-btn.active {
background: var(--accent);
color: #000;
} }
.pp-mode-btn + .pp-mode-btn { border-left: 1px solid rgba(255, 255, 255, 0.14); }
.pp-mode-btn:hover { background: rgba(255, 255, 255, 0.08); color: var(--text-primary); }
.pp-mode-btn.active { background: var(--accent); color: #000; }
/* These controls also move into Cockpit Display; keep focus on the control /* These controls also move into Cockpit Display; keep focus on the control
itself and inside clipped button groups, independent of selection color. */ itself and inside clipped button groups, independent of selection color. */
@@ -913,8 +955,13 @@ body.ui-clean-view #scene-runtime.active {
} }
@keyframes detection-pulse { @keyframes detection-pulse {
0%, 100% { box-shadow: 0 0 12px rgba(0, 255, 80, 0.15); } 0%,
50% { box-shadow: 0 0 22px rgba(0, 255, 80, 0.35); } 100% {
box-shadow: 0 0 12px rgba(0, 255, 80, 0.15);
}
50% {
box-shadow: 0 0 22px rgba(0, 255, 80, 0.35);
}
} }
/* ── Parameter Slider Panel ──────────────────── */ /* ── Parameter Slider Panel ──────────────────── */
@@ -942,7 +989,10 @@ body.ui-clean-view #scene-runtime.active {
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;
transform: translateY(6px); transform: translateY(6px);
transition: opacity var(--transition-smooth), visibility var(--transition-smooth), transform var(--transition-smooth); transition:
opacity var(--transition-smooth),
visibility var(--transition-smooth),
transform var(--transition-smooth);
} }
#param-slider-panel::-webkit-scrollbar { #param-slider-panel::-webkit-scrollbar {
@@ -988,7 +1038,12 @@ body.ui-clean-view #scene-runtime.active {
} }
#param-slider-panel:not(.collapsed) .param-panel-divider { #param-slider-panel:not(.collapsed) .param-panel-divider {
background: linear-gradient(90deg, rgb(0 212 255 / 28%), rgba(0, 212, 255, 0.18) 58%, transparent); background: linear-gradient(
90deg,
rgb(0 212 255 / 28%),
rgba(0, 212, 255, 0.18) 58%,
transparent
);
} }
.param-slider-row { .param-slider-row {
@@ -1058,4 +1113,3 @@ body.ui-clean-view #scene-runtime.active {
text-align: right; text-align: right;
flex-shrink: 0; flex-shrink: 0;
} }
+96 -28
View File
@@ -31,7 +31,9 @@
-webkit-backdrop-filter: blur(28px) saturate(1.25); -webkit-backdrop-filter: blur(28px) saturate(1.25);
opacity: 0; opacity: 0;
transform: translate(-50%, calc(-50% + 0.8rem)) scale(0.985); transform: translate(-50%, calc(-50% + 0.8rem)) scale(0.985);
transition: opacity 220ms ease, transform 280ms cubic-bezier(0.2, 0.8, 0.2, 1); transition:
opacity 220ms ease,
transform 280ms cubic-bezier(0.2, 0.8, 0.2, 1);
/* Inert until revealed AND again while fading out, so a click during the /* Inert until revealed AND again while fading out, so a click during the
dismiss animation cannot launch a second mission behind the fade. */ dismiss animation cannot launch a second mission behind the fade. */
pointer-events: none; pointer-events: none;
@@ -55,7 +57,12 @@
position: absolute; position: absolute;
inset: 0 0 auto; inset: 0 0 auto;
height: 2px; height: 2px;
background: linear-gradient(90deg, transparent, rgba(54, 220, 255, 0.8), transparent); background: linear-gradient(
90deg,
transparent,
rgba(54, 220, 255, 0.8),
transparent
);
box-shadow: 0 0 1rem rgba(54, 220, 255, 0.45); box-shadow: 0 0 1rem rgba(54, 220, 255, 0.45);
pointer-events: none; pointer-events: none;
} }
@@ -73,7 +80,9 @@
text-transform: uppercase; text-transform: uppercase;
} }
.first-run-kicker { color: rgba(54, 220, 255, 0.86); } .first-run-kicker {
color: rgba(54, 220, 255, 0.86);
}
#first-run-launcher h2 { #first-run-launcher h2 {
margin-top: 0.6rem; margin-top: 0.6rem;
@@ -107,7 +116,9 @@
scrollbar-gutter: stable; scrollbar-gutter: stable;
} }
.first-run-choices::-webkit-scrollbar { width: 6px; } .first-run-choices::-webkit-scrollbar {
width: 6px;
}
.first-run-choices::-webkit-scrollbar-thumb { .first-run-choices::-webkit-scrollbar-thumb {
background: rgba(54, 220, 255, 0.45); background: rgba(54, 220, 255, 0.45);
border-radius: 3px; border-radius: 3px;
@@ -127,7 +138,10 @@
border: 1px solid rgba(100, 200, 220, 0.18); border: 1px solid rgba(100, 200, 220, 0.18);
border-radius: 0.42rem; border-radius: 0.42rem;
cursor: pointer; cursor: pointer;
transition: background var(--transition-fast), border-color var(--transition-fast), transform var(--transition-fast); transition:
background var(--transition-fast),
border-color var(--transition-fast),
transform var(--transition-fast);
} }
.first-run-choices button:hover, .first-run-choices button:hover,
@@ -173,7 +187,9 @@
.first-run-arrow { .first-run-arrow {
color: rgba(54, 220, 255, 0.4); color: rgba(54, 220, 255, 0.4);
font-size: 1rem; font-size: 1rem;
transition: transform var(--transition-fast), color var(--transition-fast); transition:
transform var(--transition-fast),
color var(--transition-fast);
} }
.first-run-choices button:hover .first-run-arrow, .first-run-choices button:hover .first-run-arrow,
@@ -212,7 +228,9 @@
cursor: pointer; cursor: pointer;
} }
.first-run-suppress:hover { color: rgba(225, 244, 248, 0.92); } .first-run-suppress:hover {
color: rgba(225, 244, 248, 0.92);
}
/* The live region carries mission progress and failures, so it owns a full row /* The live region carries mission progress and failures, so it owns a full row
and reserves its height — otherwise the card resizes as it speaks. */ and reserves its height — otherwise the card resizes as it speaks. */
@@ -249,15 +267,31 @@ body.scene-playback-mode #first-run-launcher {
transform: translate(-50%, 0.8rem) scale(0.985); transform: translate(-50%, 0.8rem) scale(0.985);
} }
#first-run-launcher.visible { transform: translate(-50%, 0) scale(1); } #first-run-launcher.visible {
transform: translate(-50%, 0) scale(1);
}
/* Five tiles have to clear the dock on a 375 x 812 phone, so the rows tighten /* Five tiles have to clear the dock on a 375 x 812 phone, so the rows tighten
and the redundant "ESC to dismiss" hint (no ESC key on a phone) drops. */ and the redundant "ESC to dismiss" hint (no ESC key on a phone) drops. */
#first-run-launcher h2 { margin-top: 0.45rem; } #first-run-launcher h2 {
#first-run-description { margin-top: 0.25rem; font-size: 0.72rem; } margin-top: 0.45rem;
.first-run-choices { margin-top: 0.75rem; } }
.first-run-choices button { min-height: 3rem; padding: 0.45rem 0.6rem; } #first-run-description {
.first-run-choices small { font-size: 0.62rem; } margin-top: 0.25rem;
.first-run-footer > span:last-child { display: none; } font-size: 0.72rem;
}
.first-run-choices {
margin-top: 0.75rem;
}
.first-run-choices button {
min-height: 3rem;
padding: 0.45rem 0.6rem;
}
.first-run-choices small {
font-size: 0.62rem;
}
.first-run-footer > span:last-child {
display: none;
}
} }
/* Short viewports — landscape phones, small laptop windows. The chrome tightens /* Short viewports — landscape phones, small laptop windows. The chrome tightens
@@ -266,15 +300,38 @@ body.scene-playback-mode #first-run-launcher {
fits. (It was dropped here while there were five; the tile that went is fits. (It was dropped here while there were five; the tile that went is
exactly why it can come back.) */ exactly why it can come back.) */
@media (max-height: 620px) { @media (max-height: 620px) {
#first-run-launcher { padding: 0.75rem 0.85rem; } #first-run-launcher {
#first-run-launcher h2 { margin-top: 0.3rem; font-size: 1.1rem; } padding: 0.75rem 0.85rem;
#first-run-description { margin-top: 0.2rem; font-size: 0.7rem; line-height: 1.4; } }
.first-run-choices { margin-top: 0.55rem; } #first-run-launcher h2 {
.first-run-choices button { min-height: 2.7rem; padding: 0.36rem 0.6rem; } margin-top: 0.3rem;
.first-run-choices strong { font-size: 0.68rem; } font-size: 1.1rem;
.first-run-choices small { font-size: 0.6rem; } }
.first-run-footer { margin-top: 0.5rem; } #first-run-description {
.first-run-note { margin-top: 0.3rem; min-height: 0; } margin-top: 0.2rem;
font-size: 0.7rem;
line-height: 1.4;
}
.first-run-choices {
margin-top: 0.55rem;
}
.first-run-choices button {
min-height: 2.7rem;
padding: 0.36rem 0.6rem;
}
.first-run-choices strong {
font-size: 0.68rem;
}
.first-run-choices small {
font-size: 0.6rem;
}
.first-run-footer {
margin-top: 0.5rem;
}
.first-run-note {
margin-top: 0.3rem;
min-height: 0;
}
} }
/* A soft edge on the list, so a tile cut by the fold reads as "more below" /* A soft edge on the list, so a tile cut by the fold reads as "more below"
@@ -283,8 +340,16 @@ body.scene-playback-mode #first-run-launcher {
promise content that is not there, which is the opposite of the affordance promise content that is not there, which is the opposite of the affordance
it is meant to be. Decorative only: the list scrolls under it. */ it is meant to be. Decorative only: the list scrolls under it. */
.first-run-choices[data-scrollable='true'] { .first-run-choices[data-scrollable='true'] {
-webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 1.4rem), transparent 100%); -webkit-mask-image: linear-gradient(
mask-image: linear-gradient(to bottom, #000 calc(100% - 1.4rem), transparent 100%); to bottom,
#000 calc(100% - 1.4rem),
transparent 100%
);
mask-image: linear-gradient(
to bottom,
#000 calc(100% - 1.4rem),
transparent 100%
);
} }
/* Scrolled to the end, there is nothing left to promise, so the fade lifts. /* Scrolled to the end, there is nothing left to promise, so the fade lifts.
@@ -308,8 +373,11 @@ body.scene-playback-mode #first-run-launcher {
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
#first-run-launcher, #first-run-launcher,
.first-run-choices button, .first-run-choices button,
.first-run-arrow { transition: none; } .first-run-arrow {
transition: none;
}
/* Scrolling a tile into view must not animate either. */ /* Scrolling a tile into view must not animate either. */
.first-run-choices { scroll-behavior: auto; } .first-run-choices {
scroll-behavior: auto;
}
} }
+25 -12
View File
@@ -69,7 +69,8 @@
outline-offset: 2px; outline-offset: 2px;
} }
html, body { html,
body {
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
@@ -185,7 +186,9 @@ body.cockpit-mode #cockpit-cloud-effects.active {
pointer-events: none; pointer-events: none;
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;
transition: opacity 300ms ease, visibility 300ms ease; transition:
opacity 300ms ease,
visibility 300ms ease;
} }
.celestial-ring-overlay.visible { .celestial-ring-overlay.visible {
@@ -222,19 +225,27 @@ body.cockpit-mode #cockpit-cloud-effects.active {
font-weight: 400; font-weight: 400;
font-style: normal; font-style: normal;
line-height: 1; line-height: 1;
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 20; font-variation-settings:
'FILL' 0,
'wght' 400,
'GRAD' 0,
'opsz' 20;
transition: opacity 120ms linear; transition: opacity 120ms linear;
user-select: none; user-select: none;
} }
.celestial-sun { .celestial-sun {
color: rgba(231, 194, 72, 0.92); color: rgba(231, 194, 72, 0.92);
text-shadow: 0 0 7px rgba(231, 194, 72, 0.72), 0 0 18px rgba(231, 194, 72, 0.3); text-shadow:
0 0 7px rgba(231, 194, 72, 0.72),
0 0 18px rgba(231, 194, 72, 0.3);
} }
.celestial-moon { .celestial-moon {
color: rgba(55, 211, 239, 0.84); color: rgba(55, 211, 239, 0.84);
text-shadow: 0 0 7px rgba(55, 211, 239, 0.65), 0 0 17px rgba(55, 211, 239, 0.25); text-shadow:
0 0 7px rgba(55, 211, 239, 0.65),
0 0 17px rgba(55, 211, 239, 0.25);
} }
#celestial-toggle:disabled { #celestial-toggle:disabled {
@@ -499,7 +510,7 @@ body.cockpit-mode #cockpit-cloud-effects.active {
#title-bar .title-logo::before, #title-bar .title-logo::before,
#title-bar .title-logo::after { #title-bar .title-logo::after {
--radio-broadcast-opacity: .28; --radio-broadcast-opacity: 0.28;
content: ''; content: '';
position: absolute; position: absolute;
z-index: 1; z-index: 1;
@@ -517,7 +528,7 @@ body.cockpit-mode #cockpit-cloud-effects.active {
} }
#title-bar .title-logo::after { #title-bar .title-logo::after {
--radio-broadcast-opacity: .17; --radio-broadcast-opacity: 0.17;
top: -15px; top: -15px;
width: 27px; width: 27px;
height: 13px; height: 13px;
@@ -529,12 +540,15 @@ body.cockpit-mode #cockpit-cloud-effects.active {
animation: title-radio-broadcast 1.8s ease-in-out infinite; animation: title-radio-broadcast 1.8s ease-in-out infinite;
} }
#title-bar.radio-broadcasting .title-logo::after { animation-delay: .28s; } #title-bar.radio-broadcasting .title-logo::after {
animation-delay: 0.28s;
}
@keyframes title-radio-broadcast { @keyframes title-radio-broadcast {
0%, 100% { 0%,
opacity: calc(var(--radio-broadcast-opacity) * .55); 100% {
transform: translateX(-50%) scale(.94); opacity: calc(var(--radio-broadcast-opacity) * 0.55);
transform: translateX(-50%) scale(0.94);
} }
50% { 50% {
opacity: var(--radio-broadcast-opacity); opacity: var(--radio-broadcast-opacity);
@@ -608,4 +622,3 @@ body.cockpit-mode #cockpit-cloud-effects.active {
text-shadow: 0 0 20px var(--accent-glow); text-shadow: 0 0 20px var(--accent-glow);
transition: var(--transition-smooth); transition: var(--transition-smooth);
} }
+390 -182
View File
File diff suppressed because it is too large. Load diff
+13 -5
View File
@@ -12,7 +12,11 @@
#location-bar .panel-glow { #location-bar .panel-glow {
position: absolute; position: absolute;
inset: -20px; inset: -20px;
background: radial-gradient(ellipse at center, var(--accent-dim) 0%, transparent 60%); background: radial-gradient(
ellipse at center,
var(--accent-dim) 0%,
transparent 60%
);
filter: blur(20px); filter: blur(20px);
opacity: 0.25; opacity: 0.25;
pointer-events: none; pointer-events: none;
@@ -137,7 +141,8 @@
max-height: 0; max-height: 0;
overflow: hidden; overflow: hidden;
opacity: 0; opacity: 0;
transition: max-height 250ms cubic-bezier(0.4, 0, 0.2, 1), transition:
max-height 250ms cubic-bezier(0.4, 0, 0.2, 1),
opacity 200ms ease, opacity 200ms ease,
padding 250ms cubic-bezier(0.4, 0, 0.2, 1); padding 250ms cubic-bezier(0.4, 0, 0.2, 1);
padding: 0 4px; padding: 0 4px;
@@ -155,7 +160,9 @@
border-top: 1px solid var(--glass-border); border-top: 1px solid var(--glass-border);
margin: 0 4px; margin: 0 4px;
opacity: 0; opacity: 0;
transition: opacity 200ms ease, margin 250ms ease; transition:
opacity 200ms ease,
margin 250ms ease;
} }
.location-bar-divider.visible { .location-bar-divider.visible {
@@ -272,7 +279,9 @@
font-family: var(--font-sans); font-family: var(--font-sans);
font-size: 13px; font-size: 13px;
letter-spacing: 0.3px; letter-spacing: 0.3px;
transition: width var(--transition-smooth), padding var(--transition-smooth); transition:
width var(--transition-smooth),
padding var(--transition-smooth);
caret-color: var(--accent); caret-color: var(--accent);
} }
@@ -289,4 +298,3 @@
#location-search.searching { #location-search.searching {
opacity: 0.5; opacity: 0.5;
} }
+15 -6
View File
@@ -40,7 +40,8 @@
#safe-frame-box::before { #safe-frame-box::before {
background: background:
linear-gradient(to right, linear-gradient(
to right,
transparent 33.333%, transparent 33.333%,
rgba(255, 255, 255, 0.2) 33.333%, rgba(255, 255, 255, 0.2) 33.333%,
rgba(255, 255, 255, 0.2) 33.7%, rgba(255, 255, 255, 0.2) 33.7%,
@@ -50,7 +51,8 @@
rgba(255, 255, 255, 0.2) 66.7%, rgba(255, 255, 255, 0.2) 66.7%,
transparent 66.7% transparent 66.7%
), ),
linear-gradient(to bottom, linear-gradient(
to bottom,
transparent 33.333%, transparent 33.333%,
rgba(255, 255, 255, 0.2) 33.333%, rgba(255, 255, 255, 0.2) 33.333%,
rgba(255, 255, 255, 0.2) 33.7%, rgba(255, 255, 255, 0.2) 33.7%,
@@ -77,7 +79,9 @@
user-select: none; user-select: none;
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;
transition: opacity var(--transition-smooth), visibility var(--transition-smooth); transition:
opacity var(--transition-smooth),
visibility var(--transition-smooth);
} }
#orbit-indicator.active { #orbit-indicator.active {
@@ -99,7 +103,9 @@
pointer-events: none; pointer-events: none;
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;
transition: opacity 0.4s ease, visibility 0.4s ease; transition:
opacity 0.4s ease,
visibility 0.4s ease;
--hud-color: rgba(0, 255, 255, 0.6); --hud-color: rgba(0, 255, 255, 0.6);
--hud-glow: rgba(0, 255, 255, 0.4); --hud-glow: rgba(0, 255, 255, 0.4);
--hud-border: rgba(0, 255, 255, 0.15); --hud-border: rgba(0, 255, 255, 0.15);
@@ -322,7 +328,11 @@
#intel-hud[data-variant='operator'] .hud-bottom-bar { #intel-hud[data-variant='operator'] .hud-bottom-bar {
border-color: rgba(255, 255, 255, 0.2); border-color: rgba(255, 255, 255, 0.2);
background: linear-gradient(90deg, rgba(0, 0, 0, 0.52), rgba(8, 22, 18, 0.52)); background: linear-gradient(
90deg,
rgba(0, 0, 0, 0.52),
rgba(8, 22, 18, 0.52)
);
} }
#intel-hud[data-variant='operator'] .hud-corner { #intel-hud[data-variant='operator'] .hud-corner {
@@ -361,4 +371,3 @@
#intel-hud[data-variant='minimal'] .hud-summary-wrap { #intel-hud[data-variant='minimal'] .hud-summary-wrap {
max-width: 280px; max-width: 280px;
} }
+56 -16
View File
@@ -17,7 +17,11 @@
font-family: var(--font-mono); font-family: var(--font-mono);
font-size: 0.6rem; font-size: 0.6rem;
letter-spacing: 0.09rem; letter-spacing: 0.09rem;
background: linear-gradient(145deg, rgba(10, 27, 37, 0.92), rgba(7, 13, 22, 0.9)); background: linear-gradient(
145deg,
rgba(10, 27, 37, 0.92),
rgba(7, 13, 22, 0.9)
);
border: 1px solid rgba(54, 220, 255, 0.38); border: 1px solid rgba(54, 220, 255, 0.38);
border-radius: 0.42rem; border-radius: 0.42rem;
box-shadow: box-shadow:
@@ -26,12 +30,19 @@
backdrop-filter: blur(18px) saturate(1.2); backdrop-filter: blur(18px) saturate(1.2);
-webkit-backdrop-filter: blur(18px) saturate(1.2); -webkit-backdrop-filter: blur(18px) saturate(1.2);
cursor: pointer; cursor: pointer;
transition: border-color var(--transition-fast), background var(--transition-fast), transform var(--transition-fast); transition:
border-color var(--transition-fast),
background var(--transition-fast),
transform var(--transition-fast);
} }
#key-setup-chip:hover, #key-setup-chip:hover,
#key-setup-chip:focus-visible { #key-setup-chip:focus-visible {
background: linear-gradient(145deg, rgba(14, 38, 51, 0.95), rgba(9, 18, 28, 0.93)); background: linear-gradient(
145deg,
rgba(14, 38, 51, 0.95),
rgba(9, 18, 28, 0.93)
);
border-color: rgba(54, 220, 255, 0.7); border-color: rgba(54, 220, 255, 0.7);
outline: none; outline: none;
transform: translateY(-1px); transform: translateY(-1px);
@@ -42,7 +53,9 @@
font-size: 0.95rem; font-size: 0.95rem;
} }
#key-setup-chip[hidden] { display: none; } #key-setup-chip[hidden] {
display: none;
}
#key-setup { #key-setup {
position: fixed; position: fixed;
@@ -70,12 +83,16 @@
-webkit-backdrop-filter: blur(28px) saturate(1.25); -webkit-backdrop-filter: blur(28px) saturate(1.25);
opacity: 0; opacity: 0;
transform: translate(-50%, calc(-50% + 0.8rem)) scale(0.985); transform: translate(-50%, calc(-50% + 0.8rem)) scale(0.985);
transition: opacity 220ms ease, transform 280ms cubic-bezier(0.2, 0.8, 0.2, 1); transition:
opacity 220ms ease,
transform 280ms cubic-bezier(0.2, 0.8, 0.2, 1);
pointer-events: none; pointer-events: none;
} }
/* Same cascade note as the launcher: an author display beats [hidden]. */ /* Same cascade note as the launcher: an author display beats [hidden]. */
#key-setup[hidden] { display: none; } #key-setup[hidden] {
display: none;
}
#key-setup.visible { #key-setup.visible {
opacity: 1; opacity: 1;
@@ -87,7 +104,12 @@
position: absolute; position: absolute;
inset: 0 0 auto; inset: 0 0 auto;
height: 2px; height: 2px;
background: linear-gradient(90deg, transparent, rgba(54, 220, 255, 0.8), transparent); background: linear-gradient(
90deg,
transparent,
rgba(54, 220, 255, 0.8),
transparent
);
box-shadow: 0 0 1rem rgba(54, 220, 255, 0.45); box-shadow: 0 0 1rem rgba(54, 220, 255, 0.45);
pointer-events: none; pointer-events: none;
} }
@@ -105,7 +127,9 @@
text-transform: uppercase; text-transform: uppercase;
} }
.key-setup-kicker { color: rgba(54, 220, 255, 0.86); } .key-setup-kicker {
color: rgba(54, 220, 255, 0.86);
}
.key-setup-close { .key-setup-close {
display: grid; display: grid;
@@ -116,7 +140,9 @@
border: 1px solid transparent; border: 1px solid transparent;
border-radius: 0.3rem; border-radius: 0.3rem;
cursor: pointer; cursor: pointer;
transition: color var(--transition-fast), border-color var(--transition-fast); transition:
color var(--transition-fast),
border-color var(--transition-fast);
} }
.key-setup-close:hover, .key-setup-close:hover,
@@ -126,7 +152,9 @@
outline: none; outline: none;
} }
.key-setup-close .material-symbols-outlined { font-size: 1rem; } .key-setup-close .material-symbols-outlined {
font-size: 1rem;
}
#key-setup h2 { #key-setup h2 {
margin-top: 0.6rem; margin-top: 0.6rem;
@@ -163,7 +191,9 @@
scrollbar-gutter: stable; scrollbar-gutter: stable;
} }
.key-setup-rows::-webkit-scrollbar { width: 6px; } .key-setup-rows::-webkit-scrollbar {
width: 6px;
}
.key-setup-rows::-webkit-scrollbar-thumb { .key-setup-rows::-webkit-scrollbar-thumb {
background: rgba(54, 220, 255, 0.45); background: rgba(54, 220, 255, 0.45);
border-radius: 3px; border-radius: 3px;
@@ -209,7 +239,9 @@
box-shadow: 0 0 0.5rem rgba(84, 227, 156, 0.66); box-shadow: 0 0 0.5rem rgba(84, 227, 156, 0.66);
} }
.key-setup-tier { font-size: 0.55rem; } .key-setup-tier {
font-size: 0.55rem;
}
.key-setup-exposed { .key-setup-exposed {
padding: 0.05rem 0.3rem; padding: 0.05rem 0.3rem;
@@ -264,9 +296,13 @@
outline: none; outline: none;
} }
.key-setup-fields input::placeholder { color: rgba(148, 178, 188, 0.42); } .key-setup-fields input::placeholder {
color: rgba(148, 178, 188, 0.42);
}
.key-setup-footer { margin-top: 0.85rem; } .key-setup-footer {
margin-top: 0.85rem;
}
.key-setup-apply { .key-setup-apply {
padding: 0.5rem 0.95rem; padding: 0.5rem 0.95rem;
@@ -318,7 +354,9 @@ body.scene-playback-mode #key-setup {
@media (max-width: 620px) { @media (max-width: 620px) {
/* Clear the dock. Desktop-first app; this is a courtesy, not a layout. */ /* Clear the dock. Desktop-first app; this is a courtesy, not a layout. */
#key-setup-chip { bottom: 5.8rem; } #key-setup-chip {
bottom: 5.8rem;
}
} }
/* Provider Settings additions: externally-managed badge + per-row remove. */ /* Provider Settings additions: externally-managed badge + per-row remove. */
@@ -344,7 +382,9 @@ body.scene-playback-mode #key-setup {
border: 1px solid rgba(255, 170, 150, 0.3); border: 1px solid rgba(255, 170, 150, 0.3);
border-radius: 0.32rem; border-radius: 0.32rem;
cursor: pointer; cursor: pointer;
transition: color var(--transition-fast), border-color var(--transition-fast); transition:
color var(--transition-fast),
border-color var(--transition-fast);
} }
.key-setup-remove:hover, .key-setup-remove:hover,
+314 -88
View File
@@ -5,7 +5,7 @@
min-height: 0; min-height: 0;
margin-top: 10px; margin-top: 10px;
padding-top: 10px; padding-top: 10px;
border-top: 1px solid rgba(65, 220, 231, .16); border-top: 1px solid rgba(65, 220, 231, 0.16);
} }
.radio-panel-inner { .radio-panel-inner {
position: relative; position: relative;
@@ -14,20 +14,24 @@
flex-direction: column; flex-direction: column;
gap: 9px; gap: 9px;
padding: 10px; padding: 10px;
border: 1px solid rgba(65, 220, 231, .18); border: 1px solid rgba(65, 220, 231, 0.18);
border-radius: 8px; border-radius: 8px;
background: rgba(2, 15, 20, .42); background: rgba(2, 15, 20, 0.42);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .035); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.035);
scrollbar-width: thin; scrollbar-width: thin;
scrollbar-color: rgba(0, 212, 255, .46) rgba(255, 255, 255, .06); scrollbar-color: rgba(0, 212, 255, 0.46) rgba(255, 255, 255, 0.06);
}
#radio-panel .panel-header {
margin-bottom: 0;
} }
#radio-panel .panel-header { margin-bottom: 0; }
.radio-layer-state { .radio-layer-state {
color: var(--text-dim); color: var(--text-dim);
font: 8px/1 var(--font-mono); font: 8px/1 var(--font-mono);
letter-spacing: .12em; letter-spacing: 0.12em;
}
.radio-layer-state.active {
color: #65f7ff;
} }
.radio-layer-state.active { color: #65f7ff; }
.radio-directory-row { .radio-directory-row {
display: grid; display: grid;
grid-template-columns: auto minmax(0, 1fr); grid-template-columns: auto minmax(0, 1fr);
@@ -38,37 +42,42 @@
grid-column: 2; grid-column: 2;
color: var(--text-dim); color: var(--text-dim);
font: 7px/1 var(--font-mono); font: 7px/1 var(--font-mono);
letter-spacing: .12em; letter-spacing: 0.12em;
}
#radio-enable-btn {
grid-row: 1 / span 2;
min-height: 36px;
} }
#radio-enable-btn { grid-row: 1 / span 2; min-height: 36px; }
#radio-filter { #radio-filter {
min-width: 0; min-width: 0;
padding: 7px 24px 7px 8px; padding: 7px 24px 7px 8px;
border: 1px solid rgba(65, 220, 231, .28); border: 1px solid rgba(65, 220, 231, 0.28);
border-radius: 5px; border-radius: 5px;
color: #c5faff; color: #c5faff;
background: rgba(5, 24, 31, .88); background: rgba(5, 24, 31, 0.88);
font: 9px/1.2 var(--font-mono); font: 9px/1.2 var(--font-mono);
} }
.radio-station-card { .radio-station-card {
display: grid; display: grid;
gap: 4px; gap: 4px;
padding: 10px; padding: 10px;
border: 1px solid rgba(65, 220, 231, .18); border: 1px solid rgba(65, 220, 231, 0.18);
border-radius: 7px; border-radius: 7px;
background: rgba(2, 15, 20, .48); background: rgba(2, 15, 20, 0.48);
overflow-wrap: anywhere; overflow-wrap: anywhere;
} }
.radio-station-card strong { .radio-station-card strong {
color: #e8feff; color: #e8feff;
font: 11px/1.25 var(--font-mono); font: 11px/1.25 var(--font-mono);
letter-spacing: .05em; letter-spacing: 0.05em;
} }
.radio-station-card span { .radio-station-card span {
color: var(--text-dim); color: var(--text-dim);
font: 8px/1.35 var(--font-mono); font: 8px/1.35 var(--font-mono);
} }
#radio-station-tags { color: #72dce6; } #radio-station-tags {
color: #72dce6;
}
.radio-tuner { .radio-tuner {
display: grid; display: grid;
width: 100%; width: 100%;
@@ -77,12 +86,18 @@
gap: 5px; gap: 5px;
padding: 8px; padding: 8px;
overflow: hidden; overflow: hidden;
border: 1px solid rgba(65, 220, 231, .22); border: 1px solid rgba(65, 220, 231, 0.22);
border-radius: 7px; border-radius: 7px;
background: linear-gradient(180deg, rgba(1, 9, 13, .92), rgba(4, 24, 30, .72)); background: linear-gradient(
box-shadow: inset 0 0 18px rgba(0, 212, 255, .045); 180deg,
rgba(1, 9, 13, 0.92),
rgba(4, 24, 30, 0.72)
);
box-shadow: inset 0 0 18px rgba(0, 212, 255, 0.045);
}
.radio-tuner[hidden] {
display: none;
} }
.radio-tuner[hidden] { display: none; }
.radio-tuner-header, .radio-tuner-header,
.radio-tuner-readout { .radio-tuner-readout {
display: flex; display: flex;
@@ -90,7 +105,7 @@
gap: 8px; gap: 8px;
color: #6daeb5; color: #6daeb5;
font: 7px/1.2 var(--font-mono); font: 7px/1.2 var(--font-mono);
letter-spacing: .1em; letter-spacing: 0.1em;
} }
#radio-tuner-value { #radio-tuner-value {
min-width: 0; min-width: 0;
@@ -108,10 +123,22 @@
height: 51px; height: 51px;
overflow: hidden; overflow: hidden;
contain: paint; contain: paint;
border-block: 1px solid rgba(72, 225, 237, .3); border-block: 1px solid rgba(72, 225, 237, 0.3);
background: background:
linear-gradient(180deg, transparent 23px, rgba(65, 220, 231, .8) 24px, transparent 25px, transparent 28px, rgba(65, 220, 231, .6) 29px, transparent 30px), linear-gradient(
radial-gradient(ellipse at center, rgba(39, 177, 191, .12), transparent 70%); 180deg,
transparent 23px,
rgba(65, 220, 231, 0.8) 24px,
transparent 25px,
transparent 28px,
rgba(65, 220, 231, 0.6) 29px,
transparent 30px
),
radial-gradient(
ellipse at center,
rgba(39, 177, 191, 0.12),
transparent 70%
);
} }
.radio-tuner-scale { .radio-tuner-scale {
position: absolute; position: absolute;
@@ -119,7 +146,7 @@
overflow: hidden; overflow: hidden;
color: #58cbd6; color: #58cbd6;
font: 9px/1 var(--font-mono); font: 9px/1 var(--font-mono);
text-shadow: 0 0 7px rgba(65, 220, 231, .5); text-shadow: 0 0 7px rgba(65, 220, 231, 0.5);
pointer-events: none; pointer-events: none;
} }
.radio-tuner-tick { .radio-tuner-tick {
@@ -127,7 +154,7 @@
top: 6px; top: 6px;
bottom: 0; bottom: 0;
min-width: 1px; min-width: 1px;
border-left: 1px solid rgba(85, 224, 235, .38); border-left: 1px solid rgba(85, 224, 235, 0.38);
color: #58cbd6; color: #58cbd6;
padding-left: 3px; padding-left: 3px;
white-space: nowrap; white-space: nowrap;
@@ -135,7 +162,7 @@
transition: left 80ms linear; transition: left 80ms linear;
} }
.radio-tuner-tick.is-current { .radio-tuner-tick.is-current {
border-left-color: rgba(168, 247, 255, .9); border-left-color: rgba(168, 247, 255, 0.9);
color: #a8f7ff; color: #a8f7ff;
} }
.radio-tuner-needle { .radio-tuner-needle {
@@ -147,13 +174,17 @@
width: 3px; width: 3px;
border-radius: 1px; border-radius: 1px;
background: #ff6f4f; background: #ff6f4f;
box-shadow: 0 0 4px #ff6f4f, 0 0 10px rgba(255, 77, 50, .65); box-shadow:
0 0 4px #ff6f4f,
0 0 10px rgba(255, 77, 50, 0.65);
pointer-events: none; pointer-events: none;
transform: translateX(-50%); transform: translateX(-50%);
transition: left .18s ease-out; transition: left 0.18s ease-out;
} }
.radio-tuner.is-dragging .radio-tuner-needle, .radio-tuner.is-dragging .radio-tuner-needle,
.radio-tuner.is-dragging .radio-tuner-tick { transition: none; } .radio-tuner.is-dragging .radio-tuner-tick {
transition: none;
}
#radio-tuner-slider { #radio-tuner-slider {
position: absolute; position: absolute;
z-index: 2; z-index: 2;
@@ -184,7 +215,10 @@
background: transparent; background: transparent;
box-shadow: none; box-shadow: none;
} }
#radio-tuner-slider::-moz-range-track { height: 100%; background: transparent; } #radio-tuner-slider::-moz-range-track {
height: 100%;
background: transparent;
}
#radio-tuner-slider::-moz-range-thumb { #radio-tuner-slider::-moz-range-thumb {
width: 14px; width: 14px;
height: 47px; height: 47px;
@@ -194,9 +228,11 @@
box-shadow: none; box-shadow: none;
} }
.radio-tuner.is-static .radio-tuner-dial { .radio-tuner.is-static .radio-tuner-dial {
box-shadow: inset 0 0 14px rgba(255, 111, 79, .1); box-shadow: inset 0 0 14px rgba(255, 111, 79, 0.1);
}
.radio-tuner.is-static #radio-tuner-station {
color: #ff9b7e;
} }
.radio-tuner.is-static #radio-tuner-station { color: #ff9b7e; }
.radio-tuner-readout span:first-child { .radio-tuner-readout span:first-child {
min-width: 0; min-width: 0;
color: #7fe6ef; color: #7fe6ef;
@@ -216,12 +252,12 @@
.radio-transport button { .radio-transport button {
min-height: 30px; min-height: 30px;
padding: 6px 3px; padding: 6px 3px;
border: 1px solid rgba(65, 220, 231, .28); border: 1px solid rgba(65, 220, 231, 0.28);
border-radius: 5px; border-radius: 5px;
color: #8feaf2; color: #8feaf2;
background: rgba(14, 69, 78, .14); background: rgba(14, 69, 78, 0.14);
font: 8px/1 var(--font-mono); font: 8px/1 var(--font-mono);
letter-spacing: .06em; letter-spacing: 0.06em;
cursor: pointer; cursor: pointer;
} }
.radio-transport button:hover:not(:disabled), .radio-transport button:hover:not(:disabled),
@@ -231,12 +267,15 @@
outline: none; outline: none;
} }
.radio-transport button:disabled, .radio-transport button:disabled,
#radio-filter:disabled { opacity: .42; cursor: not-allowed; } #radio-filter:disabled {
opacity: 0.42;
cursor: not-allowed;
}
#radio-play-btn.active { #radio-play-btn.active {
color: #fff; color: #fff;
border-color: rgba(101, 247, 255, .8); border-color: rgba(101, 247, 255, 0.8);
background: rgba(65, 220, 231, .18); background: rgba(65, 220, 231, 0.18);
box-shadow: 0 0 12px rgba(65, 220, 231, .12); box-shadow: 0 0 12px rgba(65, 220, 231, 0.12);
} }
.radio-volume-row { .radio-volume-row {
display: grid; display: grid;
@@ -245,15 +284,19 @@
align-items: center; align-items: center;
color: var(--text-dim); color: var(--text-dim);
font: 7px/1 var(--font-mono); font: 7px/1 var(--font-mono);
letter-spacing: .1em; letter-spacing: 0.1em;
}
.radio-volume-row input {
width: 100%;
} }
.radio-volume-row input { width: 100%; }
.radio-playback-state { .radio-playback-state {
min-height: 12px; min-height: 12px;
color: #79d7df; color: #79d7df;
font: 8px/1.35 var(--font-mono); font: 8px/1.35 var(--font-mono);
} }
.radio-playback-state.error { color: #ff9d8f; } .radio-playback-state.error {
color: #ff9d8f;
}
.radio-links { .radio-links {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@@ -263,13 +306,16 @@
.radio-links a { .radio-links a {
color: #64cbd5; color: #64cbd5;
font: 7px/1.3 var(--font-mono); font: 7px/1.3 var(--font-mono);
letter-spacing: .06em; letter-spacing: 0.06em;
text-decoration: none; text-decoration: none;
} }
.radio-links a:hover, .radio-links a:focus-visible { color: #fff; } .radio-links a:hover,
.radio-links a:focus-visible {
color: #fff;
}
.radio-privacy { .radio-privacy {
margin: 0; margin: 0;
color: rgba(190, 230, 230, .48); color: rgba(190, 230, 230, 0.48);
font: 7px/1.4 var(--font-mono); font: 7px/1.4 var(--font-mono);
} }
#military-awareness-panel { #military-awareness-panel {
@@ -281,36 +327,129 @@
color: #dceeff; color: #dceeff;
background: transparent; background: transparent;
border: 0; border: 0;
font: 11px/1.35 ui-monospace, SFMono-Regular, Menlo, monospace; font:
11px/1.35 ui-monospace,
SFMono-Regular,
Menlo,
monospace;
} }
#military-awareness-panel[hidden] { display: none; } #military-awareness-panel[hidden] {
.military-awareness-subject { margin: 0 0 7px; color: #fff; } display: none;
.military-awareness-controls { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 5px; margin: 0 0 8px; } }
.military-awareness-controls button { border: 1px solid rgba(98,181,255,0.34); border-radius: 4px; padding: 4px 3px; color: #a9d9ff; background: rgba(42,113,171,0.12); font: 9px/1.2 ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: 0.07em; cursor: pointer; } .military-awareness-subject {
.military-awareness-controls button:hover, .military-awareness-controls button:focus-visible { color: #fff; border-color: rgba(127,200,255,0.85); background: rgba(66,148,211,0.24); outline: none; } margin: 0 0 7px;
.military-awareness-controls button:disabled { color: #627181; border-color: rgba(98,181,255,0.12); background: transparent; cursor: default; } color: #fff;
.military-awareness-row { border-top: 1px solid rgba(255,255,255,0.1); padding: 7px 0; } }
.military-awareness-row > div { display: flex; justify-content: space-between; } .military-awareness-controls {
.military-awareness-row b { color: #7fc8ff; } display: grid;
.military-awareness-row.unknown b { color: #8fcfff; } grid-template-columns: 1fr 1fr 1fr;
.military-awareness-row small, .military-awareness-note { color: #a9b7c7; } gap: 5px;
.military-awareness-row ul { margin: 4px 0 0; padding: 0; list-style: none; } margin: 0 0 8px;
.military-awareness-row li { display: block; } }
.military-awareness-target { display: flex; justify-content: space-between; gap: 10px; width: 100%; border: 0; padding: 1px 0; color: #dceeff; background: transparent; font: inherit; text-align: left; cursor: pointer; } .military-awareness-controls button {
.military-awareness-target.unavailable { color: var(--text-dim); cursor: default; } border: 1px solid rgba(98, 181, 255, 0.34);
.military-awareness-target:hover, .military-awareness-target:focus-visible { color: #fff; outline: none; text-shadow: 0 0 8px rgba(114, 202, 255, 0.85); } border-radius: 4px;
.military-awareness-target:focus-visible { box-shadow: inset 0 -1px 0 #7fc8ff; } padding: 4px 3px;
.military-awareness-target span { color: #7fc8ff; white-space: nowrap; } color: #a9d9ff;
.military-awareness-note { margin: 8px 0 0; font-size: 10px; } background: rgba(42, 113, 171, 0.12);
font:
9px/1.2 ui-monospace,
SFMono-Regular,
Menlo,
monospace;
letter-spacing: 0.07em;
cursor: pointer;
}
.military-awareness-controls button:hover,
.military-awareness-controls button:focus-visible {
color: #fff;
border-color: rgba(127, 200, 255, 0.85);
background: rgba(66, 148, 211, 0.24);
outline: none;
}
.military-awareness-controls button:disabled {
color: #627181;
border-color: rgba(98, 181, 255, 0.12);
background: transparent;
cursor: default;
}
.military-awareness-row {
border-top: 1px solid rgba(255, 255, 255, 0.1);
padding: 7px 0;
}
.military-awareness-row > div {
display: flex;
justify-content: space-between;
}
.military-awareness-row b {
color: #7fc8ff;
}
.military-awareness-row.unknown b {
color: #8fcfff;
}
.military-awareness-row small,
.military-awareness-note {
color: #a9b7c7;
}
.military-awareness-row ul {
margin: 4px 0 0;
padding: 0;
list-style: none;
}
.military-awareness-row li {
display: block;
}
.military-awareness-target {
display: flex;
justify-content: space-between;
gap: 10px;
width: 100%;
border: 0;
padding: 1px 0;
color: #dceeff;
background: transparent;
font: inherit;
text-align: left;
cursor: pointer;
}
.military-awareness-target.unavailable {
color: var(--text-dim);
cursor: default;
}
.military-awareness-target:hover,
.military-awareness-target:focus-visible {
color: #fff;
outline: none;
text-shadow: 0 0 8px rgba(114, 202, 255, 0.85);
}
.military-awareness-target:focus-visible {
box-shadow: inset 0 -1px 0 #7fc8ff;
}
.military-awareness-target span {
color: #7fc8ff;
white-space: nowrap;
}
.military-awareness-note {
margin: 8px 0 0;
font-size: 10px;
}
.military-awareness-standby { .military-awareness-standby {
display: grid; display: grid;
gap: 7px; gap: 7px;
padding: 12px 4px 4px; padding: 12px 4px 4px;
border-top: 1px solid rgba(98, 181, 255, 0.16); border-top: 1px solid rgba(98, 181, 255, 0.16);
} }
.military-awareness-standby strong { color: #79c2ff; font-size: 10px; letter-spacing: 0.1em; } .military-awareness-standby strong {
.military-awareness-standby span { color: #8192a6; font-size: 9px; letter-spacing: 0.05em; } color: #79c2ff;
font-size: 10px;
letter-spacing: 0.1em;
}
.military-awareness-standby span {
color: #8192a6;
font-size: 9px;
letter-spacing: 0.05em;
}
.military-awareness-standby button { .military-awareness-standby button {
justify-self: start; justify-self: start;
padding: 4px 12px; padding: 4px 12px;
@@ -318,27 +457,51 @@
border-radius: 4px; border-radius: 4px;
color: #a9d9ff; color: #a9d9ff;
background: rgba(42, 113, 171, 0.12); background: rgba(42, 113, 171, 0.12);
font: 9px/1.2 ui-monospace, SFMono-Regular, Menlo, monospace; font:
9px/1.2 ui-monospace,
SFMono-Regular,
Menlo,
monospace;
letter-spacing: 0.08em; letter-spacing: 0.08em;
cursor: pointer; cursor: pointer;
} }
#military-awareness-direction-overlay { position: absolute; inset: 0; z-index: 4; overflow: hidden; pointer-events: none; } #military-awareness-direction-overlay {
#military-awareness-direction-overlay[hidden] { display: none; } position: absolute;
inset: 0;
z-index: 4;
overflow: hidden;
pointer-events: none;
}
#military-awareness-direction-overlay[hidden] {
display: none;
}
.military-awareness-compass-ring { .military-awareness-compass-ring {
--compass-rotation: 0deg; --compass-rotation: 0deg;
position: absolute; position: absolute;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid rgba(111,196,255,0.2); border: 1px solid rgba(111, 196, 255, 0.2);
outline: 1px dashed rgba(111,196,255,0.28); outline: 1px dashed rgba(111, 196, 255, 0.28);
outline-offset: -5px; outline-offset: -5px;
border-radius: 50%; border-radius: 50%;
box-shadow: box-shadow:
inset 0 0 0 5px rgba(34,128,196,0.018), inset 0 0 0 5px rgba(34, 128, 196, 0.018),
inset 0 0 24px rgba(34,128,196,0.035), inset 0 0 24px rgba(34, 128, 196, 0.035),
0 0 14px rgba(34,128,196,0.05); 0 0 14px rgba(34, 128, 196, 0.05);
}
.military-awareness-compass-ring::before {
content: '';
position: absolute;
left: 50%;
top: -7px;
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-bottom: 8px solid rgba(127, 200, 255, 0.88);
transform: translateX(-50%);
filter: drop-shadow(0 0 5px rgba(87, 184, 255, 0.7));
} }
.military-awareness-compass-ring::before { content: ''; position: absolute; left: 50%; top: -7px; width: 0; height: 0; border-left: 5px solid transparent; border-right: 5px solid transparent; border-bottom: 8px solid rgba(127,200,255,0.88); transform: translateX(-50%); filter: drop-shadow(0 0 5px rgba(87,184,255,0.7)); }
.military-awareness-compass-ring::after { .military-awareness-compass-ring::after {
content: ''; content: '';
position: absolute; position: absolute;
@@ -349,22 +512,86 @@
rgba(125, 205, 255, 0.38) 0deg 0.7deg, rgba(125, 205, 255, 0.38) 0deg 0.7deg,
transparent 0.7deg 15deg transparent 0.7deg 15deg
); );
-webkit-mask: radial-gradient(circle, transparent calc(100% - 8px), #000 calc(100% - 8px) calc(100% - 4px), transparent calc(100% - 4px)); -webkit-mask: radial-gradient(
mask: radial-gradient(circle, transparent calc(100% - 8px), #000 calc(100% - 8px) calc(100% - 4px), transparent calc(100% - 4px)); circle,
transparent calc(100% - 8px),
#000 calc(100% - 8px) calc(100% - 4px),
transparent calc(100% - 4px)
);
mask: radial-gradient(
circle,
transparent calc(100% - 8px),
#000 calc(100% - 8px) calc(100% - 4px),
transparent calc(100% - 4px)
);
transform: rotate(var(--compass-rotation)); transform: rotate(var(--compass-rotation));
transition: transform 80ms linear; transition: transform 80ms linear;
} }
.military-awareness-compass-label, .military-awareness-compass-heading { position: absolute; transform: translate(-50%, -50%); color: rgba(156,213,255,0.7); font: 9px/1 ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: 0.08em; text-shadow: 0 0 7px rgba(53,161,235,0.55); } .military-awareness-compass-label,
.military-awareness-compass-label.cardinal-n { color: rgba(255,188,87,0.92); } .military-awareness-compass-heading {
.military-awareness-compass-heading { color: rgba(190,225,249,0.76); font-size: 8px; } position: absolute;
.military-awareness-direction-marker { position: absolute; width: 1px; height: 1px; transform: translate(-50%, -50%); transition: opacity 100ms linear; } transform: translate(-50%, -50%);
.military-awareness-direction-marker[hidden] { display: none; } color: rgba(156, 213, 255, 0.7);
.military-awareness-direction-arrow { position: absolute; left: 50%; top: 50%; color: #74dbff; font: 23px/1 sans-serif; transform-origin: 50% 50%; text-shadow: 0 0 5px rgba(87,223,255,0.9), 0 0 13px rgba(87,223,255,0.35); } font:
.military-awareness-direction-label { position: absolute; left: 50%; top: 50%; padding: 3px 6px; border: 1px solid rgba(116,219,255,0.24); border-radius: 3px; color: #bceeff; background: rgba(5,12,20,0.68); font: 8px/1.3 ui-monospace, SFMono-Regular, Menlo, monospace; text-align: center; white-space: pre; text-shadow: 0 0 5px rgba(116,219,255,0.38); } 9px/1 ui-monospace,
SFMono-Regular,
Menlo,
monospace;
letter-spacing: 0.08em;
text-shadow: 0 0 7px rgba(53, 161, 235, 0.55);
}
.military-awareness-compass-label.cardinal-n {
color: rgba(255, 188, 87, 0.92);
}
.military-awareness-compass-heading {
color: rgba(190, 225, 249, 0.76);
font-size: 8px;
}
.military-awareness-direction-marker {
position: absolute;
width: 1px;
height: 1px;
transform: translate(-50%, -50%);
transition: opacity 100ms linear;
}
.military-awareness-direction-marker[hidden] {
display: none;
}
.military-awareness-direction-arrow {
position: absolute;
left: 50%;
top: 50%;
color: #74dbff;
font: 23px/1 sans-serif;
transform-origin: 50% 50%;
text-shadow:
0 0 5px rgba(87, 223, 255, 0.9),
0 0 13px rgba(87, 223, 255, 0.35);
}
.military-awareness-direction-label {
position: absolute;
left: 50%;
top: 50%;
padding: 3px 6px;
border: 1px solid rgba(116, 219, 255, 0.24);
border-radius: 3px;
color: #bceeff;
background: rgba(5, 12, 20, 0.68);
font:
8px/1.3 ui-monospace,
SFMono-Regular,
Menlo,
monospace;
text-align: center;
white-space: pre;
text-shadow: 0 0 5px rgba(116, 219, 255, 0.38);
}
.data-toggle-row.gev-voice-focus { .data-toggle-row.gev-voice-focus {
background: rgba(92, 255, 198, 0.1); background: rgba(92, 255, 198, 0.1);
box-shadow: inset 0 0 0 1px rgba(92, 255, 198, 0.72), 0 0 18px rgba(92, 255, 198, 0.18); box-shadow:
inset 0 0 0 1px rgba(92, 255, 198, 0.72),
0 0 18px rgba(92, 255, 198, 0.18);
} }
.data-toggle-top { .data-toggle-top {
@@ -573,4 +800,3 @@
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.18); box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.18);
flex: none; flex: none;
} }
-1
View File
@@ -30,4 +30,3 @@ body.recording-mode #scene-runtime.active {
body.scene-playback-mode :is(#clear-selected-layers, #reset-globe-view) { body.scene-playback-mode :is(#clear-selected-layers, #reset-globe-view) {
display: none !important; display: none !important;
} }
-1
View File
@@ -213,4 +213,3 @@
text-align: center; text-align: center;
} }
} }
+39 -11
View File
@@ -35,7 +35,9 @@
order: 2; order: 2;
} }
#left-panel-stack > #scene-panel { order: 4; } #left-panel-stack > #scene-panel {
order: 4;
}
/* /*
* Runtime focus mode: the layout engine applies this only when the expanded * Runtime focus mode: the layout engine applies this only when the expanded
@@ -59,7 +61,8 @@
* When an expanded panel and its collapsed siblings fit together, preserve * When an expanded panel and its collapsed siblings fit together, preserve
* the canonical control order and center their combined stack at 50vh. * the canonical control order and center their combined stack at 50vh.
*/ */
#left-panel-stack.layout-tail > :is(#data-panel, #cctv-panel, #scene-panel):not(.collapsed) { #left-panel-stack.layout-tail
> :is(#data-panel, #cctv-panel, #scene-panel):not(.collapsed) {
box-sizing: border-box; box-sizing: border-box;
flex: 0 0 var(--left-panel-allocated-height); flex: 0 0 var(--left-panel-allocated-height);
height: var(--left-panel-allocated-height); height: var(--left-panel-allocated-height);
@@ -91,19 +94,33 @@
} }
#left-panel-stack > [data-panel-id]:not(.collapsed) .panel-divider { #left-panel-stack > [data-panel-id]:not(.collapsed) .panel-divider {
background: linear-gradient(90deg, rgb(0 212 255 / 28%), rgba(0, 212, 255, 0.18) 58%, transparent); background: linear-gradient(
90deg,
rgb(0 212 255 / 28%),
rgba(0, 212, 255, 0.18) 58%,
transparent
);
box-shadow: 0 0 7px rgba(0, 212, 255, 0.22); box-shadow: 0 0 7px rgba(0, 212, 255, 0.22);
} }
#right-context-rail [data-panel-id]:not(.collapsed) .panel-divider { #right-context-rail [data-panel-id]:not(.collapsed) .panel-divider {
background: linear-gradient(90deg, rgb(0 212 255 / 28%), rgba(0, 212, 255, 0.18) 58%, transparent); background: linear-gradient(
90deg,
rgb(0 212 255 / 28%),
rgba(0, 212, 255, 0.18) 58%,
transparent
);
box-shadow: 0 0 7px rgba(0, 212, 255, 0.22); box-shadow: 0 0 7px rgba(0, 212, 255, 0.22);
} }
#scene-panel .panel-glow { #scene-panel .panel-glow {
position: absolute; position: absolute;
inset: -18px; inset: -18px;
background: radial-gradient(ellipse at center, rgba(0, 212, 255, 0.16) 0%, transparent 65%); background: radial-gradient(
ellipse at center,
rgba(0, 212, 255, 0.16) 0%,
transparent 65%
);
filter: blur(16px); filter: blur(16px);
opacity: 0.25; opacity: 0.25;
pointer-events: none; pointer-events: none;
@@ -209,7 +226,9 @@
border-radius: 9px; border-radius: 9px;
padding: 7px 8px; padding: 7px 8px;
background: rgba(255, 255, 255, 0.03); background: rgba(255, 255, 255, 0.03);
transition: border-color var(--transition-fast), background var(--transition-fast); transition:
border-color var(--transition-fast),
background var(--transition-fast);
} }
.scene-shot-row.active { .scene-shot-row.active {
@@ -300,7 +319,11 @@
#scene-progress-fill { #scene-progress-fill {
width: 0%; width: 0%;
height: 100%; height: 100%;
background: linear-gradient(90deg, rgba(0, 212, 255, 0.2), rgba(0, 212, 255, 0.75)); background: linear-gradient(
90deg,
rgba(0, 212, 255, 0.2),
rgba(0, 212, 255, 0.75)
);
color: #d5f7ff; color: #d5f7ff;
font-family: var(--font-mono); font-family: var(--font-mono);
font-size: 8px; font-size: 8px;
@@ -329,7 +352,9 @@
z-index: 105; z-index: 105;
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;
transition: opacity 180ms ease, visibility 180ms ease; transition:
opacity 180ms ease,
visibility 180ms ease;
font-family: var(--font-mono); font-family: var(--font-mono);
font-size: 11px; font-size: 11px;
letter-spacing: 2px; letter-spacing: 2px;
@@ -354,7 +379,9 @@
#global-context-panel.collapsed .global-context-actions, #global-context-panel.collapsed .global-context-actions,
#global-context-panel.collapsed .global-context-modes, #global-context-panel.collapsed .global-context-modes,
#global-context-panel.collapsed .context-mode-view, #global-context-panel.collapsed .context-mode-view,
#global-context-panel.collapsed > .global-context-panel-inner > .context-mode-standby, #global-context-panel.collapsed
> .global-context-panel-inner
> .context-mode-standby,
#radio-panel.collapsed .radio-panel-inner > :not(.panel-header), #radio-panel.collapsed .radio-panel-inner > :not(.panel-header),
#cctv-panel.collapsed .cctv-controls, #cctv-panel.collapsed .cctv-controls,
#cctv-panel.collapsed .cctv-calibration-block, #cctv-panel.collapsed .cctv-calibration-block,
@@ -380,7 +407,9 @@
width: var(--left-collapsed-width); width: var(--left-collapsed-width);
} }
#global-context-panel #radio-panel.collapsed { width: 100%; } #global-context-panel #radio-panel.collapsed {
width: 100%;
}
#data-panel.collapsed .panel-title, #data-panel.collapsed .panel-title,
#cctv-panel.collapsed .panel-title, #cctv-panel.collapsed .panel-title,
@@ -409,4 +438,3 @@
#param-slider-panel.collapsed .param-panel-header { #param-slider-panel.collapsed .param-panel-header {
margin-bottom: 2px; margin-bottom: 2px;
} }
+79 -23
View File
@@ -74,10 +74,14 @@
letter-spacing: 1.35px; letter-spacing: 1.35px;
text-transform: uppercase; text-transform: uppercase;
transform: translateX(-50%); transform: translateX(-50%);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.34), 0 0 14px rgba(0, 212, 255, 0.08); box-shadow:
0 8px 24px rgba(0, 0, 0, 0.34),
0 0 14px rgba(0, 212, 255, 0.08);
pointer-events: none; pointer-events: none;
} }
#global-loading-status[hidden] { display: none; } #global-loading-status[hidden] {
display: none;
}
#global-loading-label { #global-loading-label {
color: rgba(184, 244, 255, 0.92); color: rgba(184, 244, 255, 0.92);
} }
@@ -92,25 +96,49 @@
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
#global-loading-status:is([data-state='complete'], [data-state='cancelled'], [data-state='error']) { #global-loading-status:is(
[data-state='complete'],
[data-state='cancelled'],
[data-state='error']
) {
justify-content: center; justify-content: center;
text-align: center; text-align: center;
} }
#global-loading-status:is([data-state='complete'], [data-state='cancelled'], [data-state='error']) #global-loading-detail { #global-loading-status:is(
[data-state='complete'],
[data-state='cancelled'],
[data-state='error']
)
#global-loading-detail {
display: none; display: none;
} }
#global-loading-status[data-state='error'] { border-color: rgba(255, 98, 82, 0.55); color: #ff9b8e; } #global-loading-status[data-state='error'] {
border-color: rgba(255, 98, 82, 0.55);
color: #ff9b8e;
}
#global-loading-status[data-state='error'] #global-loading-label, #global-loading-status[data-state='error'] #global-loading-label,
#global-loading-status[data-state='error'] #global-loading-detail { color: #ff9b8e; } #global-loading-status[data-state='error'] #global-loading-detail {
#global-loading-status[data-state='cancelled'] { color: #ffd488; } color: #ff9b8e;
#global-loading-status[data-state='retry'] { border-color: rgba(255, 212, 136, 0.55); } }
#global-loading-status[data-state='cancelled'] {
color: #ffd488;
}
#global-loading-status[data-state='retry'] {
border-color: rgba(255, 212, 136, 0.55);
}
#global-loading-status[data-state='retry'] #global-loading-label, #global-loading-status[data-state='retry'] #global-loading-label,
#global-loading-status[data-state='retry'] #global-loading-detail { color: #ffd488; } #global-loading-status[data-state='retry'] #global-loading-detail {
color: #ffd488;
}
#global-loading-status[data-state='cancelled'] #global-loading-label, #global-loading-status[data-state='cancelled'] #global-loading-label,
#global-loading-status[data-state='cancelled'] #global-loading-detail { color: #ffd488; } #global-loading-status[data-state='cancelled'] #global-loading-detail {
color: #ffd488;
}
#global-loading-status[data-state='acquiring'] { #global-loading-status[data-state='acquiring'] {
border-color: rgba(0, 212, 255, 0.42); border-color: rgba(0, 212, 255, 0.42);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.34), 0 0 18px rgba(0, 212, 255, 0.12); box-shadow:
0 8px 24px rgba(0, 0, 0, 0.34),
0 0 18px rgba(0, 212, 255, 0.12);
} }
.share-icon { .share-icon {
@@ -141,8 +169,12 @@
text-transform: uppercase; text-transform: uppercase;
opacity: 0; opacity: 0;
pointer-events: none; pointer-events: none;
transition: opacity 0.22s ease, transform 0.22s ease; transition:
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.34), 0 0 14px rgba(0, 212, 255, 0.08); opacity 0.22s ease,
transform 0.22s ease;
box-shadow:
0 8px 24px rgba(0, 0, 0, 0.34),
0 0 14px rgba(0, 212, 255, 0.08);
} }
/* Stacked below the traffic chip so both can show at once */ /* Stacked below the traffic chip so both can show at once */
@@ -270,21 +302,43 @@
them. */ them. */
.gev-flap-sizing { .gev-flap-sizing {
display: inline-block; display: inline-block;
transition: width var(--gev-flap-total, 500ms) cubic-bezier(0.2, 0.75, 0.25, 1); transition: width var(--gev-flap-total, 500ms)
cubic-bezier(0.2, 0.75, 0.25, 1);
} }
/* The old leaf hinges down and away on the cell's midline. */ /* The old leaf hinges down and away on the cell's midline. */
@keyframes gev-flap-out { @keyframes gev-flap-out {
0% { transform: rotateX(0deg); opacity: 1; } 0% {
48% { transform: rotateX(-88deg); opacity: 0.5; } transform: rotateX(0deg);
100% { transform: rotateX(-90deg); opacity: 0; } opacity: 1;
}
48% {
transform: rotateX(-88deg);
opacity: 0.5;
}
100% {
transform: rotateX(-90deg);
opacity: 0;
}
} }
/* The new leaf waits for the old one to clear, then drops in and settles /* The new leaf waits for the old one to clear, then drops in and settles
with a single small overshoot — the mechanical clack. */ with a single small overshoot — the mechanical clack. */
@keyframes gev-flap-in { @keyframes gev-flap-in {
0% { transform: rotateX(90deg); opacity: 0; } 0% {
46% { transform: rotateX(90deg); opacity: 0; } transform: rotateX(90deg);
78% { transform: rotateX(-7deg); opacity: 1; } opacity: 0;
100% { transform: rotateX(0deg); opacity: 1; } }
46% {
transform: rotateX(90deg);
opacity: 0;
}
78% {
transform: rotateX(-7deg);
opacity: 1;
}
100% {
transform: rotateX(0deg);
opacity: 1;
}
} }
/* Matches the .cockpit-roll-track treatment: collapse the duration rather /* Matches the .cockpit-roll-track treatment: collapse the duration rather
than dropping the animation, so the final frame still lands the glyph. than dropping the animation, so the final frame still lands the glyph.
@@ -322,7 +376,10 @@
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;
pointer-events: none; pointer-events: none;
transition: opacity 0.3s ease, visibility 0.3s ease, transform 0.3s ease; transition:
opacity 0.3s ease,
visibility 0.3s ease,
transform 0.3s ease;
box-shadow: 0 0 20px rgba(0, 212, 255, 0.15); box-shadow: 0 0 20px rgba(0, 212, 255, 0.15);
} }
@@ -331,4 +388,3 @@
visibility: visible; visibility: visible;
transform: translateX(-50%) translateY(0); transform: translateX(-50%) translateY(0);
} }
+3 -2
View File
@@ -28,7 +28,9 @@
letter-spacing: 0.045rem; letter-spacing: 0.045rem;
line-height: 1; line-height: 1;
cursor: pointer; cursor: pointer;
transition: color var(--transition-fast), border-color var(--transition-fast), transition:
color var(--transition-fast),
border-color var(--transition-fast),
background var(--transition-fast); background var(--transition-fast);
} }
@@ -75,4 +77,3 @@
#command-dock.tray-open .gev-voice-cost { #command-dock.tray-open .gev-voice-cost {
display: none; display: none;
} }