mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 02:14:01 +00:00
style: format the UI shell and component styles
This commit is contained in:
1 parent
f5ef9091f0
commit
03ad8142e6
32 files changed
+4126
-1546
No files matched your search
@@ -213,5 +213,38 @@
|
||||
"src/ui/sceneControls.js",
|
||||
"src/ui/scenePresentation.js",
|
||||
"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"
|
||||
]
|
||||
@@ -425,9 +425,7 @@
|
||||
"external": []
|
||||
},
|
||||
"ui-shell": {
|
||||
"exports": [
|
||||
"./ui/shell"
|
||||
],
|
||||
"exports": ["./ui/shell"],
|
||||
"modules": [
|
||||
"src/bloom.js",
|
||||
"src/cctvFocusPolicy.js",
|
||||
@@ -522,14 +520,10 @@
|
||||
"src/worldFocus.js",
|
||||
"src/ui/uiLifetime.js"
|
||||
],
|
||||
"external": [
|
||||
"cesium"
|
||||
]
|
||||
"external": ["cesium"]
|
||||
},
|
||||
"ui-styles": {
|
||||
"exports": [
|
||||
"./ui/styles"
|
||||
],
|
||||
"exports": ["./ui/styles"],
|
||||
"modules": [
|
||||
"src/ui/styles.js",
|
||||
"style.css",
|
||||
|
||||
@@ -210,7 +210,11 @@ try {
|
||||
);
|
||||
// Escape can also collapse the Scene accordion. Reopen it through its
|
||||
// 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.waitForSelector('#scene-download-btn', { visible: true });
|
||||
await page.click('#scene-download-btn');
|
||||
|
||||
+31
-16
@@ -107,7 +107,8 @@ function positiveNumber(value, fallback) {
|
||||
}
|
||||
|
||||
function nowMs() {
|
||||
return typeof performance !== 'undefined' && typeof performance.now === 'function'
|
||||
return typeof performance !== 'undefined' &&
|
||||
typeof performance.now === 'function'
|
||||
? performance.now()
|
||||
: Date.now();
|
||||
}
|
||||
@@ -235,7 +236,9 @@ export function visibleGlyphs(plan, elapsedMs, options = {}) {
|
||||
const turnRatio = Number.isFinite(Number(options.turnRatio))
|
||||
? Number(options.turnRatio)
|
||||
: 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;
|
||||
let out = '';
|
||||
for (const cell of plan?.cells || []) {
|
||||
@@ -246,9 +249,11 @@ export function visibleGlyphs(plan, elapsedMs, options = {}) {
|
||||
|
||||
/** Whether the viewer asked for reduced motion. */
|
||||
function prefersReducedMotion() {
|
||||
return typeof window !== 'undefined'
|
||||
&& typeof window.matchMedia === 'function'
|
||||
&& window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
return (
|
||||
typeof window !== 'undefined' &&
|
||||
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;
|
||||
const view = element.ownerDocument?.defaultView;
|
||||
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);
|
||||
if (style.visibility === 'hidden' || style.display === 'none') return false;
|
||||
if (Number(style.opacity) === 0) return false;
|
||||
@@ -301,10 +310,12 @@ function ensureHost(element) {
|
||||
// nothing changed.
|
||||
const text = element.firstElementChild;
|
||||
const cells = text?.nextElementSibling;
|
||||
if (text?.classList?.contains(TEXT_CLASS)
|
||||
&& text.firstChild?.nodeType === 3
|
||||
&& cells?.classList?.contains(CELLS_CLASS)
|
||||
&& !cells.nextElementSibling) {
|
||||
if (
|
||||
text?.classList?.contains(TEXT_CLASS) &&
|
||||
text.firstChild?.nodeType === 3 &&
|
||||
cells?.classList?.contains(CELLS_CLASS) &&
|
||||
!cells.nextElementSibling
|
||||
) {
|
||||
return { text, cells };
|
||||
}
|
||||
|
||||
@@ -363,7 +374,8 @@ function easeWidth(element, fromWidth, toWidth, durationMs) {
|
||||
element.style.width = `${toWidth}px`;
|
||||
|
||||
const finish = (event) => {
|
||||
if (event && (event.target !== element || event.propertyName !== 'width')) return;
|
||||
if (event && (event.target !== element || event.propertyName !== 'width'))
|
||||
return;
|
||||
cancelWidthEase(element);
|
||||
clearSizing(element);
|
||||
};
|
||||
@@ -449,16 +461,19 @@ export function setSplitFlapText(element, text, options = {}) {
|
||||
// previous label's glyphs, and those are what must flap away.
|
||||
const state = flapStates.get(element);
|
||||
const displayed = state
|
||||
? visibleGlyphs(state.plan, nowMs() - state.startedAt, { charMs: state.charMs })
|
||||
? visibleGlyphs(state.plan, nowMs() - state.startedAt, {
|
||||
charMs: state.charMs,
|
||||
})
|
||||
: settled;
|
||||
clearFlapTimer(element);
|
||||
|
||||
const beforeWidth = measureWidth(element);
|
||||
|
||||
const animate = SPLIT_FLAP_ENABLED
|
||||
&& options.immediate !== true
|
||||
&& !prefersReducedMotion()
|
||||
&& isVisible(element);
|
||||
const animate =
|
||||
SPLIT_FLAP_ENABLED &&
|
||||
options.immediate !== true &&
|
||||
!prefersReducedMotion() &&
|
||||
isVisible(element);
|
||||
|
||||
const plan = animate ? planSplitFlap(displayed, next, options) : null;
|
||||
|
||||
|
||||
+88
-68
@@ -1,7 +1,14 @@
|
||||
/** Compose UI controls with the application's existing engines and layer instances. */
|
||||
import { StyleManager as ApplicationShell } from '../ui/applicationShell.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 { IntelHUD } from '../hud.js';
|
||||
import { ShareLinkManager } from '../sharelink.js';
|
||||
@@ -12,8 +19,14 @@ import {
|
||||
isCelestialRingStyleSupported,
|
||||
setKeyholeFadeTuning,
|
||||
} from '../celestialRing.js';
|
||||
import { destroyTrackedReadout, initTrackedReadout } from '../data/trackedReadout.js';
|
||||
import { destroyWorldOverlay, initWorldOverlay } from '../overlays/worldOverlay.js';
|
||||
import {
|
||||
destroyTrackedReadout,
|
||||
initTrackedReadout,
|
||||
} from '../data/trackedReadout.js';
|
||||
import {
|
||||
destroyWorldOverlay,
|
||||
initWorldOverlay,
|
||||
} from '../overlays/worldOverlay.js';
|
||||
import {
|
||||
destroyDetection,
|
||||
initDetection,
|
||||
@@ -47,7 +60,11 @@ import {
|
||||
warmGroundFloor,
|
||||
} from '../data/groundFloor.js';
|
||||
import { sampleMeshFloorCells } from '../data/meshFloorSampler.js';
|
||||
import { holdContinuousRender, releaseContinuousRender, governorRequestRender } from '../renderGovernor.js';
|
||||
import {
|
||||
holdContinuousRender,
|
||||
releaseContinuousRender,
|
||||
governorRequestRender,
|
||||
} from '../renderGovernor.js';
|
||||
import {
|
||||
setScopeMaskEnabled,
|
||||
isScopeMaskEnabled,
|
||||
@@ -65,69 +82,72 @@ import {
|
||||
|
||||
export class StyleManager extends ApplicationShell {
|
||||
constructor(viewer, options = {}) {
|
||||
super(viewer, { ...options, services: {
|
||||
CITY_POIS,
|
||||
GLOBE_VIEW,
|
||||
flyToGlobeView,
|
||||
flyToPresetLocation,
|
||||
flyToPOI,
|
||||
searchAndFlyTo,
|
||||
interruptCameraMotion,
|
||||
IntelHUD,
|
||||
ShareLinkManager,
|
||||
OrbitController,
|
||||
CelestialRing,
|
||||
getKeyholeFadeTuning,
|
||||
isCelestialRingStyleSupported,
|
||||
setKeyholeFadeTuning,
|
||||
destroyTrackedReadout,
|
||||
initTrackedReadout,
|
||||
destroyWorldOverlay,
|
||||
initWorldOverlay,
|
||||
destroyDetection,
|
||||
initDetection,
|
||||
cycleDetectionMode,
|
||||
readDetectionDiagnostics,
|
||||
getDetectionTuning,
|
||||
getDetectionMode,
|
||||
setDetectionModeByLabel,
|
||||
suspendDetection,
|
||||
resumeDetection,
|
||||
setDetectionStyle,
|
||||
setDetectionTuning,
|
||||
trafficLayer,
|
||||
flightsLayer,
|
||||
militaryFlightsLayer,
|
||||
isTr3b,
|
||||
toggleTr3b,
|
||||
satellitesLayer,
|
||||
cctvLayer,
|
||||
radioLayer,
|
||||
bikeshareLayer,
|
||||
aisLiveVesselsLayer,
|
||||
militaryAwarenessLayer,
|
||||
militaryInstallationsLayer,
|
||||
rocketLaunchesLayer,
|
||||
cachedGroundFloor,
|
||||
cachedMeshFloor,
|
||||
GROUND_FLOOR_LIFT_M,
|
||||
meshFloorPreferred,
|
||||
warmGroundFloor,
|
||||
sampleMeshFloorCells,
|
||||
holdContinuousRender,
|
||||
releaseContinuousRender,
|
||||
governorRequestRender,
|
||||
setScopeMaskEnabled,
|
||||
isScopeMaskEnabled,
|
||||
setScopeMaskFeather,
|
||||
getScopeMaskFeather,
|
||||
setScopeTerminusOverride,
|
||||
getScopeTerminusOverride,
|
||||
clampScopeTerminusPct,
|
||||
fetchRegionalBrief,
|
||||
regionalDistanceM,
|
||||
weatherCodeLabel,
|
||||
LocationSearch
|
||||
} });
|
||||
super(viewer, {
|
||||
...options,
|
||||
services: {
|
||||
CITY_POIS,
|
||||
GLOBE_VIEW,
|
||||
flyToGlobeView,
|
||||
flyToPresetLocation,
|
||||
flyToPOI,
|
||||
searchAndFlyTo,
|
||||
interruptCameraMotion,
|
||||
IntelHUD,
|
||||
ShareLinkManager,
|
||||
OrbitController,
|
||||
CelestialRing,
|
||||
getKeyholeFadeTuning,
|
||||
isCelestialRingStyleSupported,
|
||||
setKeyholeFadeTuning,
|
||||
destroyTrackedReadout,
|
||||
initTrackedReadout,
|
||||
destroyWorldOverlay,
|
||||
initWorldOverlay,
|
||||
destroyDetection,
|
||||
initDetection,
|
||||
cycleDetectionMode,
|
||||
readDetectionDiagnostics,
|
||||
getDetectionTuning,
|
||||
getDetectionMode,
|
||||
setDetectionModeByLabel,
|
||||
suspendDetection,
|
||||
resumeDetection,
|
||||
setDetectionStyle,
|
||||
setDetectionTuning,
|
||||
trafficLayer,
|
||||
flightsLayer,
|
||||
militaryFlightsLayer,
|
||||
isTr3b,
|
||||
toggleTr3b,
|
||||
satellitesLayer,
|
||||
cctvLayer,
|
||||
radioLayer,
|
||||
bikeshareLayer,
|
||||
aisLiveVesselsLayer,
|
||||
militaryAwarenessLayer,
|
||||
militaryInstallationsLayer,
|
||||
rocketLaunchesLayer,
|
||||
cachedGroundFloor,
|
||||
cachedMeshFloor,
|
||||
GROUND_FLOOR_LIFT_M,
|
||||
meshFloorPreferred,
|
||||
warmGroundFloor,
|
||||
sampleMeshFloorCells,
|
||||
holdContinuousRender,
|
||||
releaseContinuousRender,
|
||||
governorRequestRender,
|
||||
setScopeMaskEnabled,
|
||||
isScopeMaskEnabled,
|
||||
setScopeMaskFeather,
|
||||
getScopeMaskFeather,
|
||||
setScopeTerminusOverride,
|
||||
getScopeTerminusOverride,
|
||||
clampScopeTerminusPct,
|
||||
fetchRegionalBrief,
|
||||
regionalDistanceM,
|
||||
weatherCodeLabel,
|
||||
LocationSearch,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,14 @@ import { readFileSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
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');
|
||||
const next = new Set(ancestors).add(resolved);
|
||||
return readFileSync(resolved, 'utf8').replace(/@import ['"]([^'"]+)['"];\s*/g,
|
||||
(_, imported) => readStylesheet(path.resolve(path.dirname(resolved), imported), next));
|
||||
return readFileSync(resolved, 'utf8').replace(
|
||||
/@import ['"]([^'"]+)['"];\s*/g,
|
||||
(_, imported) =>
|
||||
readStylesheet(path.resolve(path.dirname(resolved), imported), next),
|
||||
);
|
||||
}
|
||||
+1563
-615
File diff suppressed because it is too large.
Load diff
+130
-42
@@ -50,7 +50,12 @@ const RIGHT_STACK_OBSTACLE_SELECTOR = [
|
||||
'#gev-voice-control',
|
||||
].join(', ');
|
||||
export class PanelLayoutController {
|
||||
constructor({ readHud, scheduleCockpitLayout, syncPanelCollapseButton, readDisplayScrollTop }) {
|
||||
constructor({
|
||||
readHud,
|
||||
scheduleCockpitLayout,
|
||||
syncPanelCollapseButton,
|
||||
readDisplayScrollTop,
|
||||
}) {
|
||||
this.readHud = readHud;
|
||||
this.scheduleCockpitLayout = scheduleCockpitLayout;
|
||||
this._syncPanelCollapseButton = syncPanelCollapseButton;
|
||||
@@ -103,7 +108,9 @@ export class PanelLayoutController {
|
||||
if (!dock) return;
|
||||
this._commandDockTrayObserver?.disconnect?.();
|
||||
if (typeof ResizeObserver === 'function') {
|
||||
this._commandDockTrayObserver = new ResizeObserver(() => this._updateCommandDockTrayStack());
|
||||
this._commandDockTrayObserver = new ResizeObserver(() =>
|
||||
this._updateCommandDockTrayStack(),
|
||||
);
|
||||
dock.querySelectorAll('.dock-popover-content').forEach((tray) => {
|
||||
this._commandDockTrayObserver.observe(tray);
|
||||
});
|
||||
@@ -115,24 +122,48 @@ export class PanelLayoutController {
|
||||
if (this.destroyed) return;
|
||||
const dock = document.getElementById('command-dock');
|
||||
if (!dock) return;
|
||||
const locationPanel = dock.querySelector('#location-bar.dock-pinned:not(.collapsed)');
|
||||
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 locationPanel = dock.querySelector(
|
||||
'#location-bar.dock-pinned:not(.collapsed)',
|
||||
);
|
||||
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 locationHeightPx = Math.ceil(locationHeight);
|
||||
const presetsHeightPx = Math.ceil(presetsHeight);
|
||||
const topPinnedPanel = dock.querySelector('.dock-pinned-top.dock-pinned:not(.collapsed)');
|
||||
const lowerPinnedPanel = topPinnedPanel?.id === 'location-bar' ? presetsPanel : locationPanel;
|
||||
const lowerPinnedHeight = lowerPinnedPanel
|
||||
?.querySelector('.dock-popover-content')
|
||||
?.getBoundingClientRect().height || 0;
|
||||
const stackHeight = pinnedCount > 1
|
||||
? `calc(${locationHeightPx}px + ${presetsHeightPx}px + 1.2rem)`
|
||||
: `${locationHeightPx + presetsHeightPx}px`;
|
||||
dock.style.setProperty('--dock-location-pinned-height', `${locationHeightPx}px`);
|
||||
dock.style.setProperty('--dock-presets-pinned-height', `${presetsHeightPx}px`);
|
||||
dock.style.setProperty('--dock-lower-pinned-height', `${Math.ceil(lowerPinnedHeight)}px`);
|
||||
const topPinnedPanel = dock.querySelector(
|
||||
'.dock-pinned-top.dock-pinned:not(.collapsed)',
|
||||
);
|
||||
const lowerPinnedPanel =
|
||||
topPinnedPanel?.id === 'location-bar' ? presetsPanel : locationPanel;
|
||||
const lowerPinnedHeight =
|
||||
lowerPinnedPanel
|
||||
?.querySelector('.dock-popover-content')
|
||||
?.getBoundingClientRect().height || 0;
|
||||
const stackHeight =
|
||||
pinnedCount > 1
|
||||
? `calc(${locationHeightPx}px + ${presetsHeightPx}px + 1.2rem)`
|
||||
: `${locationHeightPx + presetsHeightPx}px`;
|
||||
dock.style.setProperty(
|
||||
'--dock-location-pinned-height',
|
||||
`${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.classList.toggle('dock-has-pinned-tray', pinnedCount > 0);
|
||||
dock.classList.toggle('dock-has-two-pinned-trays', pinnedCount > 1);
|
||||
@@ -177,12 +208,18 @@ export class PanelLayoutController {
|
||||
this._scheduleRightPanelLayout();
|
||||
});
|
||||
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);
|
||||
}
|
||||
document.querySelectorAll(RIGHT_STACK_OBSTACLE_SELECTOR).forEach((element) => {
|
||||
this._rightStackResizeObserver.observe(element);
|
||||
});
|
||||
document
|
||||
.querySelectorAll(RIGHT_STACK_OBSTACLE_SELECTOR)
|
||||
.forEach((element) => {
|
||||
this._rightStackResizeObserver.observe(element);
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof MutationObserver !== 'undefined') {
|
||||
@@ -209,11 +246,17 @@ export class PanelLayoutController {
|
||||
const transitionHud = document.getElementById('intel-hud');
|
||||
if (transitionHud) {
|
||||
this._rightStackHudTransitionHandler = (event) => {
|
||||
if (event.propertyName === 'opacity' || event.propertyName === 'visibility') {
|
||||
if (
|
||||
event.propertyName === 'opacity' ||
|
||||
event.propertyName === 'visibility'
|
||||
) {
|
||||
this._scheduleRightPanelLayout({ reconsiderAutoCollapse: true });
|
||||
}
|
||||
};
|
||||
transitionHud.addEventListener('transitionend', this._rightStackHudTransitionHandler);
|
||||
transitionHud.addEventListener(
|
||||
'transitionend',
|
||||
this._rightStackHudTransitionHandler,
|
||||
);
|
||||
}
|
||||
|
||||
this._scheduleRightPanelLayout();
|
||||
@@ -228,7 +271,9 @@ export class PanelLayoutController {
|
||||
this._rightStackLayoutFrame = null;
|
||||
if (this._rightStackReconsiderAutoCollapse) {
|
||||
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');
|
||||
this._syncPanelCollapseButton(panel);
|
||||
}
|
||||
@@ -265,12 +310,16 @@ export class PanelLayoutController {
|
||||
this._leftStackResizeObserver.observe(stack);
|
||||
stack.querySelectorAll(':scope > [data-panel-id]').forEach((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);
|
||||
});
|
||||
document.querySelectorAll(LEFT_STACK_OBSTACLE_SELECTOR).forEach((element) => {
|
||||
this._leftStackResizeObserver.observe(element);
|
||||
});
|
||||
document
|
||||
.querySelectorAll(LEFT_STACK_OBSTACLE_SELECTOR)
|
||||
.forEach((element) => {
|
||||
this._leftStackResizeObserver.observe(element);
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof MutationObserver !== 'undefined') {
|
||||
@@ -303,7 +352,10 @@ export class PanelLayoutController {
|
||||
const transitionHud = document.getElementById('intel-hud');
|
||||
if (transitionHud) {
|
||||
this._leftStackHudTransitionHandler = (event) => {
|
||||
if (event.propertyName === 'opacity' || event.propertyName === 'visibility') {
|
||||
if (
|
||||
event.propertyName === 'opacity' ||
|
||||
event.propertyName === 'visibility'
|
||||
) {
|
||||
this._scheduleLeftPanelLayout({ reconsiderAutoCollapse: true });
|
||||
// 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
|
||||
@@ -311,7 +363,10 @@ export class PanelLayoutController {
|
||||
this.scheduleCockpitLayout();
|
||||
}
|
||||
};
|
||||
transitionHud.addEventListener('transitionend', this._leftStackHudTransitionHandler);
|
||||
transitionHud.addEventListener(
|
||||
'transitionend',
|
||||
this._leftStackHudTransitionHandler,
|
||||
);
|
||||
}
|
||||
|
||||
this._leftStackCockpitModeHandler = () => {
|
||||
@@ -321,12 +376,22 @@ export class PanelLayoutController {
|
||||
// the accordion remains in the same obstacle-safe lane instead of
|
||||
// jumping to a cockpit-specific top anchor.
|
||||
this._scheduleLeftPanelLayout();
|
||||
if (this._cockpitLayoutFrame !== null) cancelAnimationFrame(this._cockpitLayoutFrame);
|
||||
this._cockpitLayoutFrame = requestAnimationFrame(() => { this._cockpitLayoutFrame = null; this._scheduleLeftPanelLayout(); });
|
||||
if (this._cockpitLayoutFrame !== null)
|
||||
cancelAnimationFrame(this._cockpitLayoutFrame);
|
||||
this._cockpitLayoutFrame = requestAnimationFrame(() => {
|
||||
this._cockpitLayoutFrame = null;
|
||||
this._scheduleLeftPanelLayout();
|
||||
});
|
||||
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();
|
||||
}
|
||||
@@ -340,7 +405,9 @@ export class PanelLayoutController {
|
||||
this._leftStackLayoutFrame = null;
|
||||
if (this._leftStackReconsiderAutoCollapse) {
|
||||
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');
|
||||
this._syncPanelCollapseButton(panel);
|
||||
}
|
||||
@@ -366,21 +433,42 @@ export class PanelLayoutController {
|
||||
destroy() {
|
||||
if (this.destroyed) return;
|
||||
this.destroyed = true;
|
||||
for (const field of ['_leftStackLayoutFrame', '_rightStackLayoutFrame', '_cockpitLayoutFrame']) {
|
||||
for (const field of [
|
||||
'_leftStackLayoutFrame',
|
||||
'_rightStackLayoutFrame',
|
||||
'_cockpitLayoutFrame',
|
||||
]) {
|
||||
if (this[field] !== null) cancelAnimationFrame(this[field]);
|
||||
this[field] = null;
|
||||
}
|
||||
clearTimeout(this._adaptivePanelSettleTimer);
|
||||
clearTimeout(this._cockpitLayoutTimer);
|
||||
this._adaptivePanelSettleTimer = this._cockpitLayoutTimer = null;
|
||||
for (const field of ['_commandDockTrayObserver', '_leftStackResizeObserver', '_leftStackMutationObserver', '_rightStackResizeObserver', '_rightStackMutationObserver']) {
|
||||
this[field]?.disconnect(); this[field] = null;
|
||||
}
|
||||
for (const field of ['_leftStackHudTransitionHandler', '_rightStackHudTransitionHandler']) {
|
||||
if (this[field]) document.getElementById('intel-hud')?.removeEventListener('transitionend', this[field]);
|
||||
for (const field of [
|
||||
'_commandDockTrayObserver',
|
||||
'_leftStackResizeObserver',
|
||||
'_leftStackMutationObserver',
|
||||
'_rightStackResizeObserver',
|
||||
'_rightStackMutationObserver',
|
||||
]) {
|
||||
this[field]?.disconnect();
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,12 @@ const PANEL_POSITION_STORAGE_VERSION = 'v8';
|
||||
const PANEL_Z_BASE = 100;
|
||||
const PANEL_Z_MAX = 139;
|
||||
export class PanelPositionControls {
|
||||
constructor({ syncPanelCollapseButton, layoutRightPanels, syncCctvPanelViewport, showToast }) {
|
||||
constructor({
|
||||
syncPanelCollapseButton,
|
||||
layoutRightPanels,
|
||||
syncCctvPanelViewport,
|
||||
showToast,
|
||||
}) {
|
||||
this._syncPanelCollapseButton = syncPanelCollapseButton;
|
||||
this._layoutRightPanels = layoutRightPanels;
|
||||
this._syncCctvPanelViewport = syncCctvPanelViewport;
|
||||
@@ -17,7 +22,9 @@ export class PanelPositionControls {
|
||||
this.destroyed = false;
|
||||
}
|
||||
listen(target, type, callback) {
|
||||
const listener = (event) => { if (!this.destroyed) callback(event); };
|
||||
const listener = (event) => {
|
||||
if (!this.destroyed) callback(event);
|
||||
};
|
||||
target.addEventListener(type, listener);
|
||||
this.removers.push(() => target.removeEventListener(type, listener));
|
||||
}
|
||||
@@ -35,10 +42,13 @@ export class PanelPositionControls {
|
||||
const marker = `godsEyeView.${PANEL_POSITION_STORAGE_VERSION}.layoutResetNotified`;
|
||||
if (localStorage.getItem(marker)) return;
|
||||
localStorage.setItem(marker, '1');
|
||||
const hadOldPositions = Object.keys(localStorage)
|
||||
.some((key) => key.startsWith('godsEyeView.v6.panelPos.'));
|
||||
const hadOldPositions = Object.keys(localStorage).some((key) =>
|
||||
key.startsWith('godsEyeView.v6.panelPos.'),
|
||||
);
|
||||
if (hadOldPositions) {
|
||||
this._showToast('Panel layout updated — positions reset to new defaults');
|
||||
this._showToast(
|
||||
'Panel layout updated — positions reset to new defaults',
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
// 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
|
||||
// the panel could still hang off the bottom (audit U2). Re-clamp on every size change.
|
||||
if (this._ppToggles && typeof ResizeObserver !== 'undefined') {
|
||||
this._draggableResizeObserver = new ResizeObserver(() => this._reclampDraggablePanels());
|
||||
this._draggableResizeObserver = new ResizeObserver(() =>
|
||||
this._reclampDraggablePanels(),
|
||||
);
|
||||
this._draggableResizeObserver.observe(this._ppToggles);
|
||||
}
|
||||
}
|
||||
@@ -105,7 +117,10 @@ export class PanelPositionControls {
|
||||
|
||||
_savePanelCollapsedState(panelId, collapsed) {
|
||||
try {
|
||||
localStorage.setItem(this._panelCollapseStorageKey(panelId), collapsed ? '1' : '0');
|
||||
localStorage.setItem(
|
||||
this._panelCollapseStorageKey(panelId),
|
||||
collapsed ? '1' : '0',
|
||||
);
|
||||
} catch {
|
||||
// storage unavailable
|
||||
}
|
||||
@@ -124,10 +139,15 @@ export class PanelPositionControls {
|
||||
const raw = localStorage.getItem(this._panelStorageKey(panelId));
|
||||
if (!raw) return;
|
||||
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
|
||||
// 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.top = `${top}px`;
|
||||
panelEl.style.right = 'auto';
|
||||
@@ -153,10 +173,13 @@ export class PanelPositionControls {
|
||||
_savePanelPosition(panelId, panelEl) {
|
||||
const rect = panelEl.getBoundingClientRect();
|
||||
try {
|
||||
localStorage.setItem(this._panelStorageKey(panelId), JSON.stringify({
|
||||
left: Math.round(rect.left),
|
||||
top: Math.round(rect.top),
|
||||
}));
|
||||
localStorage.setItem(
|
||||
this._panelStorageKey(panelId),
|
||||
JSON.stringify({
|
||||
left: Math.round(rect.left),
|
||||
top: Math.round(rect.top),
|
||||
}),
|
||||
);
|
||||
} catch {
|
||||
// storage unavailable
|
||||
}
|
||||
@@ -187,7 +210,12 @@ export class PanelPositionControls {
|
||||
this.listen(handleEl, 'pointerdown', (event) => {
|
||||
if (event.button !== 0) 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?.();
|
||||
event.preventDefault();
|
||||
|
||||
@@ -4,7 +4,11 @@ export class RecordingControls {
|
||||
this._syncShareState = syncShareState;
|
||||
this.destroyed = 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._safeFrameOverlay = document.getElementById('safe-frame-overlay');
|
||||
this._safeFrameBox = document.getElementById('safe-frame-box');
|
||||
@@ -15,21 +19,34 @@ export class RecordingControls {
|
||||
_initRecordingOverlay() {
|
||||
if (this.destroyed) 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 = {}) {
|
||||
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._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) {
|
||||
this._safeFrameOverlay.classList.remove('ratio-9-16', 'ratio-16-9');
|
||||
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) {
|
||||
@@ -46,7 +63,8 @@ export class RecordingControls {
|
||||
} else if (hudMode === 'full' || hudMode === 'minimal') {
|
||||
this.hud.setMode('on');
|
||||
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 {
|
||||
this.hud.setMode('auto');
|
||||
}
|
||||
@@ -55,11 +73,16 @@ export class RecordingControls {
|
||||
this._preRecordingHudState = null;
|
||||
if (saved) {
|
||||
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');
|
||||
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);
|
||||
|
||||
+54
-18
@@ -18,14 +18,20 @@ export function readShellElements(document = globalThis.document) {
|
||||
_hudLayoutRow: document.getElementById('hud-layout-row'),
|
||||
_hudLayoutSelect: document.getElementById('hud-layout-select'),
|
||||
_detectionSliderRow: document.getElementById('detection-slider-row'),
|
||||
_detectionDensitySlider: document.getElementById('detection-density-slider'),
|
||||
_detectionDensitySlider: document.getElementById(
|
||||
'detection-density-slider',
|
||||
),
|
||||
_detectionDensityValue: document.getElementById('detection-density-value'),
|
||||
_detectionAllocationRow: document.getElementById('detection-allocation-row'),
|
||||
_detectionAllocationRow: document.getElementById(
|
||||
'detection-allocation-row',
|
||||
),
|
||||
_detectionFadeRow: document.getElementById('detection-fade-row'),
|
||||
_detectionFadeSlider: document.getElementById('detection-fade-slider'),
|
||||
_detectionFadeValue: document.getElementById('detection-fade-value'),
|
||||
_detectionOpacityRow: document.getElementById('detection-opacity-row'),
|
||||
_detectionOpacitySlider: document.getElementById('detection-opacity-slider'),
|
||||
_detectionOpacitySlider: document.getElementById(
|
||||
'detection-opacity-slider',
|
||||
),
|
||||
_detectionOpacityValue: document.getElementById('detection-opacity-value'),
|
||||
_celestialBtn: document.getElementById('celestial-toggle'),
|
||||
_scopeBtn: document.getElementById('scope-toggle'),
|
||||
@@ -42,17 +48,39 @@ export function readShellElements(document = globalThis.document) {
|
||||
_contextRadioDock: document.getElementById('context-radio-dock'),
|
||||
_contextRadioToggleBtn: document.getElementById('context-radio-toggle-btn'),
|
||||
_contextRadioMini: document.getElementById('context-radio-mini'),
|
||||
_contextRadioMiniEnableBtn: document.getElementById('context-radio-mini-enable-btn'),
|
||||
_contextRadioDetailsBtn: document.getElementById('context-radio-details-btn'),
|
||||
_contextRadioMiniCloseBtn: document.getElementById('context-radio-mini-close-btn'),
|
||||
_contextRadioMiniStation: document.getElementById('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'),
|
||||
_contextRadioMiniEnableBtn: document.getElementById(
|
||||
'context-radio-mini-enable-btn',
|
||||
),
|
||||
_contextRadioDetailsBtn: document.getElementById(
|
||||
'context-radio-details-btn',
|
||||
),
|
||||
_contextRadioMiniCloseBtn: document.getElementById(
|
||||
'context-radio-mini-close-btn',
|
||||
),
|
||||
_contextRadioMiniStation: document.getElementById(
|
||||
'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'),
|
||||
_cockpitRadioToggleBtn: document.getElementById('cockpit-radio-toggle-btn'),
|
||||
_cockpitRadioPanel: document.getElementById('cockpit-radio-panel'),
|
||||
@@ -62,7 +90,9 @@ export function readShellElements(document = globalThis.document) {
|
||||
_cockpitRadioPlayBtn: document.getElementById('cockpit-radio-play-btn'),
|
||||
_cockpitRadioNextBtn: document.getElementById('cockpit-radio-next-btn'),
|
||||
_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'),
|
||||
_radioEnableBtn: document.getElementById('radio-enable-btn'),
|
||||
_radioFilter: document.getElementById('radio-filter'),
|
||||
@@ -83,12 +113,18 @@ export function readShellElements(document = globalThis.document) {
|
||||
_radioVolumeValue: document.getElementById('radio-volume-value'),
|
||||
_radioPlaybackState: document.getElementById('radio-playback-state'),
|
||||
_radioStationHomepage: document.getElementById('radio-station-homepage'),
|
||||
_globalContextFlightsBtn: document.getElementById('global-context-flights-btn'),
|
||||
_globalContextMissionsBtn: document.getElementById('global-context-missions-btn'),
|
||||
_globalContextFlightsBtn: document.getElementById(
|
||||
'global-context-flights-btn',
|
||||
),
|
||||
_globalContextMissionsBtn: document.getElementById(
|
||||
'global-context-missions-btn',
|
||||
),
|
||||
_contextModeStandby: document.getElementById('context-mode-standby'),
|
||||
_contextFlightsView: document.getElementById('context-flights-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'),
|
||||
_cctvEnableBtn: document.getElementById('cctv-enable-btn'),
|
||||
_cctvNearestBtn: document.getElementById('cctv-nearest-btn'),
|
||||
|
||||
+55
-15
@@ -1,5 +1,13 @@
|
||||
/** 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';
|
||||
export class ShellFeedback {
|
||||
constructor({ readLayers }) {
|
||||
@@ -9,15 +17,24 @@ export class ShellFeedback {
|
||||
this._trafficSyncFeedbackState = createTrafficSyncFeedbackState();
|
||||
this._loadingFeedbackEvent = null;
|
||||
this._globalStatusNotice = null;
|
||||
this._loadingFeedbackTicker = this._trafficChipTicker = this._toastTimer = null;
|
||||
this._loadingFeedbackTicker =
|
||||
this._trafficChipTicker =
|
||||
this._toastTimer =
|
||||
null;
|
||||
this._loadingVisibilityHandler = null;
|
||||
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._globalLoadingDetail = document.getElementById('global-loading-detail');
|
||||
this._globalLoadingDetail = document.getElementById(
|
||||
'global-loading-detail',
|
||||
);
|
||||
this._trafficSyncChip = document.getElementById('traffic-sync-chip');
|
||||
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');
|
||||
}
|
||||
observeVisibility() {
|
||||
@@ -26,23 +43,38 @@ export class ShellFeedback {
|
||||
if (!document.hidden) this._updateGlobalLoadingFeedback();
|
||||
else this._stopLoadingFeedbackTicker();
|
||||
};
|
||||
document.addEventListener('visibilitychange', this._loadingVisibilityHandler);
|
||||
document.addEventListener(
|
||||
'visibilitychange',
|
||||
this._loadingVisibilityHandler,
|
||||
);
|
||||
}
|
||||
_updateTrafficSyncChip(forceShow = false, now = performance.now()) {
|
||||
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 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,
|
||||
{ enabled: traffic?.enabled === true, stats: traffic?.stats || {}, forceShow },
|
||||
{
|
||||
enabled: traffic?.enabled === true,
|
||||
stats: traffic?.stats || {},
|
||||
forceShow,
|
||||
},
|
||||
now,
|
||||
);
|
||||
const presentation = this._trafficSyncFeedbackState;
|
||||
// setSplitFlapText carries the same unchanged-text guard internally, and
|
||||
// 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.
|
||||
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
|
||||
// clears the progress number once the sync lands, and a truthiness guard
|
||||
// here would strand the last "..." beside the settled label.
|
||||
@@ -69,9 +101,11 @@ export class ShellFeedback {
|
||||
summary,
|
||||
now,
|
||||
);
|
||||
if (this._globalStatusNotice?.persistent !== true
|
||||
&& Number.isFinite(this._globalStatusNotice?.hideAt)
|
||||
&& now >= this._globalStatusNotice.hideAt) {
|
||||
if (
|
||||
this._globalStatusNotice?.persistent !== true &&
|
||||
Number.isFinite(this._globalStatusNotice?.hideAt) &&
|
||||
now >= this._globalStatusNotice.hideAt
|
||||
) {
|
||||
this._globalStatusNotice = null;
|
||||
}
|
||||
// Loading phases and universal notices both have time-driven transitions.
|
||||
@@ -129,7 +163,9 @@ export class ShellFeedback {
|
||||
const now = performance.now();
|
||||
this._lastLoadingFeedbackUpdateAt = now;
|
||||
this._updateGlobalLoadingFeedback(now);
|
||||
const noticeNeedsTicker = Number.isFinite(this._globalStatusNotice?.hideAt);
|
||||
const noticeNeedsTicker = Number.isFinite(
|
||||
this._globalStatusNotice?.hideAt,
|
||||
);
|
||||
if (this._loadingFeedbackState?.phase === 'idle' && !noticeNeedsTicker) {
|
||||
this._stopLoadingFeedbackTicker();
|
||||
}
|
||||
@@ -158,7 +194,11 @@ export class ShellFeedback {
|
||||
clearInterval(this._trafficChipTicker);
|
||||
clearTimeout(this._toastTimer);
|
||||
this._trafficChipTicker = this._toastTimer = null;
|
||||
if (this._loadingVisibilityHandler) document.removeEventListener('visibilitychange', this._loadingVisibilityHandler);
|
||||
if (this._loadingVisibilityHandler)
|
||||
document.removeEventListener(
|
||||
'visibilitychange',
|
||||
this._loadingVisibilityHandler,
|
||||
);
|
||||
this._loadingVisibilityHandler = null;
|
||||
disposeSplitFlap(this._globalLoadingLabel);
|
||||
disposeSplitFlap(this._trafficSyncLabel);
|
||||
|
||||
+165
-40
@@ -7,133 +7,258 @@ import { ShellFeedback } from './shellFeedback.js';
|
||||
import { RecordingControls } from './recordingControls.js';
|
||||
|
||||
function fixture() {
|
||||
const saved = Object.fromEntries(['document', 'window', 'localStorage', 'requestAnimationFrame', 'cancelAnimationFrame', 'setTimeout', 'clearTimeout', 'setInterval', 'clearInterval'].map((key) => [key, globalThis[key]]));
|
||||
const frames = new Map(), timers = new Map();
|
||||
const saved = Object.fromEntries(
|
||||
[
|
||||
'document',
|
||||
'window',
|
||||
'localStorage',
|
||||
'requestAnimationFrame',
|
||||
'cancelAnimationFrame',
|
||||
'setTimeout',
|
||||
'clearTimeout',
|
||||
'setInterval',
|
||||
'clearInterval',
|
||||
].map((key) => [key, globalThis[key]]),
|
||||
);
|
||||
const frames = new Map(),
|
||||
timers = new Map();
|
||||
let next = 0;
|
||||
const element = () => {
|
||||
const classes = new Set();
|
||||
return Object.assign(new EventTarget(), {
|
||||
style: {}, dataset: {}, hidden: false, textContent: '',
|
||||
style: {},
|
||||
dataset: {},
|
||||
hidden: false,
|
||||
textContent: '',
|
||||
classList: {
|
||||
add: (...names) => names.forEach((name) => classes.add(name)),
|
||||
remove: (...names) => names.forEach((name) => classes.delete(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,
|
||||
getBoundingClientRect: () => ({ left: 20, top: 30, width: 100, height: 80 }),
|
||||
querySelector: () => null,
|
||||
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()]));
|
||||
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 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()]),
|
||||
);
|
||||
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 = [];
|
||||
globalThis.localStorage = { setItem: (...args) => writes.push(args), getItem: () => null };
|
||||
globalThis.requestAnimationFrame = (callback) => { frames.set(++next, callback); return next; };
|
||||
globalThis.localStorage = {
|
||||
setItem: (...args) => writes.push(args),
|
||||
getItem: () => null,
|
||||
};
|
||||
globalThis.requestAnimationFrame = (callback) => {
|
||||
frames.set(++next, callback);
|
||||
return next;
|
||||
};
|
||||
globalThis.cancelAnimationFrame = (id) => frames.delete(id);
|
||||
globalThis.setTimeout = globalThis.setInterval = (callback) => { timers.set(++next, callback); return next; };
|
||||
globalThis.clearTimeout = globalThis.clearInterval = (id) => timers.delete(id);
|
||||
return { frames, timers, nodes, element, writes, restore() { Object.assign(globalThis, saved); } };
|
||||
globalThis.setTimeout = globalThis.setInterval = (callback) => {
|
||||
timers.set(++next, callback);
|
||||
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', () => {
|
||||
const f = fixture();
|
||||
try {
|
||||
const owner = new UiLifetime(), target = new EventTarget();
|
||||
const owner = new UiLifetime(),
|
||||
target = new EventTarget();
|
||||
let calls = 0;
|
||||
owner.listen(target, 'change', () => calls++);
|
||||
owner.frame(() => { calls++; owner.frame(() => calls++); });
|
||||
owner.frame(() => {
|
||||
calls++;
|
||||
owner.frame(() => calls++);
|
||||
});
|
||||
owner.timeout(() => calls++, 100);
|
||||
const retainedFrame = [...f.frames.values()][0];
|
||||
owner.destroy(); owner.destroy();
|
||||
owner.destroy();
|
||||
owner.destroy();
|
||||
target.dispatchEvent(new Event('change'));
|
||||
retainedFrame();
|
||||
owner.frame(() => calls++); owner.timeout(() => calls++, 1);
|
||||
owner.frame(() => calls++);
|
||||
owner.timeout(() => calls++, 1);
|
||||
assert.equal(calls, 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', () => {
|
||||
const f = fixture();
|
||||
try {
|
||||
const owner = new UiLifetime(), target = new EventTarget();
|
||||
const owner = new UiLifetime(),
|
||||
target = new EventTarget();
|
||||
let calls = 0;
|
||||
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));
|
||||
assert.equal(calls, 1);
|
||||
assert.equal(owner.removers.size + owner.timers.size + f.timers.size, 0);
|
||||
owner.destroy();
|
||||
} finally { f.restore(); }
|
||||
} finally {
|
||||
f.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('panel layout coalesces each rail and cannot rearm after destruction', () => {
|
||||
const f = fixture();
|
||||
try {
|
||||
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._scheduleLeftPanelLayout(); owner._scheduleLeftPanelLayout();
|
||||
owner._scheduleLeftPanelLayout();
|
||||
owner._scheduleLeftPanelLayout();
|
||||
assert.equal(f.frames.size, 1);
|
||||
owner._scheduleAdaptivePanelLayout({ settle: true });
|
||||
assert.equal(f.frames.size, 2);
|
||||
assert.equal(f.timers.size, 1);
|
||||
const late = [...f.frames.values(), ...f.timers.values()];
|
||||
owner.destroy(); owner.destroy();
|
||||
owner.destroy();
|
||||
owner.destroy();
|
||||
late.forEach((callback) => callback());
|
||||
owner._scheduleAdaptivePanelLayout({ settle: true });
|
||||
assert.equal(layouts, 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', () => {
|
||||
const f = fixture();
|
||||
try {
|
||||
const owner = new PanelPositionControls({ syncPanelCollapseButton() {}, layoutRightPanels() {}, syncCctvPanelViewport() {}, showToast() {} });
|
||||
const panel = f.element(), handle = f.element();
|
||||
const owner = new PanelPositionControls({
|
||||
syncPanelCollapseButton() {},
|
||||
layoutRightPanels() {},
|
||||
syncCctvPanelViewport() {},
|
||||
showToast() {},
|
||||
});
|
||||
const panel = f.element(),
|
||||
handle = f.element();
|
||||
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);
|
||||
owner.destroy();
|
||||
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'));
|
||||
assert.deepEqual(panel.style, before);
|
||||
assert.equal(panel.classList.contains('panel-dragging'), false);
|
||||
assert.equal(owner.removers.length, 0);
|
||||
assert.deepEqual(f.writes, []);
|
||||
} finally { f.restore(); }
|
||||
} finally {
|
||||
f.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('feedback disposal clears toast and polling work and rejects retained notices', () => {
|
||||
const f = fixture();
|
||||
try {
|
||||
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);
|
||||
owner.destroy();
|
||||
owner._showToast('Late'); owner._showGlobalStatusNotice('Late'); owner._startTrafficChipTicker();
|
||||
owner._showToast('Late');
|
||||
owner._showGlobalStatusNotice('Late');
|
||||
owner._startTrafficChipTicker();
|
||||
document.dispatchEvent(new Event('visibilitychange'));
|
||||
assert.equal(f.timers.size, 0);
|
||||
assert.equal(f.nodes.get('toast').textContent, 'Saved');
|
||||
assert.equal(f.nodes.get('toast').classList.contains('visible'), false);
|
||||
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', () => {
|
||||
const f = fixture();
|
||||
try {
|
||||
let mode = 'auto', variant = 'tactical';
|
||||
let mode = 'auto',
|
||||
variant = 'tactical';
|
||||
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: 'off' });
|
||||
owner.destroy(); owner.setRecordingMode(true);
|
||||
assert.equal(mode, 'auto'); assert.equal(variant, 'tactical');
|
||||
owner.destroy();
|
||||
owner.setRecordingMode(true);
|
||||
assert.equal(mode, 'auto');
|
||||
assert.equal(variant, 'tactical');
|
||||
assert.equal(document.body.classList.contains('recording-mode'), false);
|
||||
assert.equal(f.nodes.get('safe-frame-overlay').classList.contains('active'), false);
|
||||
} finally { f.restore(); }
|
||||
assert.equal(
|
||||
f.nodes.get('safe-frame-overlay').classList.contains('active'),
|
||||
false,
|
||||
);
|
||||
} finally {
|
||||
f.restore();
|
||||
}
|
||||
});
|
||||
+18
-5
@@ -14,7 +14,11 @@
|
||||
#cctv-panel .panel-glow {
|
||||
position: absolute;
|
||||
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);
|
||||
opacity: 0.25;
|
||||
pointer-events: none;
|
||||
@@ -51,7 +55,11 @@
|
||||
}
|
||||
|
||||
.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-radius: 999px;
|
||||
}
|
||||
@@ -99,7 +107,9 @@
|
||||
border-radius: 7px;
|
||||
padding: 4px 7px;
|
||||
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) {
|
||||
@@ -212,7 +222,11 @@
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
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));
|
||||
min-height: 150px;
|
||||
/* .cctv-panel-inner is a scrolling column flex container, so the default
|
||||
@@ -335,4 +349,3 @@
|
||||
color: #a8f7ff;
|
||||
text-shadow: 0 0 10px rgba(0, 212, 255, 0.18);
|
||||
}
|
||||
|
||||
+589
-222
File diff suppressed because it is too large.
Load diff
@@ -21,11 +21,15 @@
|
||||
}
|
||||
|
||||
#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)) {
|
||||
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,
|
||||
@@ -300,13 +304,27 @@
|
||||
transform-origin: bottom;
|
||||
}
|
||||
|
||||
#command-dock .gev-voice-visualizer span:nth-child(3n) { height: 1.35rem; }
|
||||
#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-visualizer span:nth-child(3n) {
|
||||
height: 1.35rem;
|
||||
}
|
||||
#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 #gev-voice-control[data-status='executing'] .gev-voice-visualizer span {
|
||||
#command-dock
|
||||
#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-delay: calc(var(--bar) * -45ms);
|
||||
}
|
||||
@@ -336,13 +354,25 @@
|
||||
}
|
||||
|
||||
@keyframes command-dock-reveal {
|
||||
from { opacity: 0; transform: translateY(0.2rem); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(0.2rem);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes gev-voice-bar {
|
||||
from { transform: scaleY(0.35); opacity: 0.5; }
|
||||
to { transform: scaleY(1); opacity: 1; }
|
||||
from {
|
||||
transform: scaleY(0.35);
|
||||
opacity: 0.5;
|
||||
}
|
||||
to {
|
||||
transform: scaleY(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +170,13 @@
|
||||
}
|
||||
|
||||
#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 {
|
||||
@@ -182,7 +188,13 @@
|
||||
left edge — pinned by the 480 px bounds assert in
|
||||
`scripts/qa-map-source-tray.mjs`. */
|
||||
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 ─────────────────────────────────
|
||||
@@ -213,8 +225,12 @@
|
||||
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 #control-panel:not(.collapsed):not(.dock-pinned) .dock-popover-content {
|
||||
#command-dock.dock-has-pinned-tray
|
||||
#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));
|
||||
}
|
||||
|
||||
@@ -224,8 +240,12 @@
|
||||
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
|
||||
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 #control-panel.dock-pinned-top.dock-pinned:not(.collapsed) .dock-popover-content {
|
||||
#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
|
||||
#control-panel.dock-pinned-top.dock-pinned:not(.collapsed)
|
||||
.dock-popover-content {
|
||||
bottom: calc(100% + 3rem + var(--dock-lower-pinned-height));
|
||||
}
|
||||
}
|
||||
@@ -242,14 +262,22 @@
|
||||
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 #control-panel:not(.collapsed):not(.dock-pinned) .dock-popover-content {
|
||||
#command-dock.dock-has-pinned-tray
|
||||
#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));
|
||||
}
|
||||
|
||||
/* 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 #control-panel.dock-pinned-top.dock-pinned:not(.collapsed) .dock-popover-content {
|
||||
#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
|
||||
#control-panel.dock-pinned-top.dock-pinned:not(.collapsed)
|
||||
.dock-popover-content {
|
||||
bottom: calc(100% + 2vh + 2.5rem + var(--dock-lower-pinned-height));
|
||||
}
|
||||
}
|
||||
@@ -327,24 +355,39 @@
|
||||
height: var(--audio-level, 5px);
|
||||
opacity: var(--audio-opacity, 0.56);
|
||||
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 #gev-voice-control[data-status='executing'] .gev-voice-visualizer span {
|
||||
#command-dock
|
||||
#gev-voice-control[data-status='listening']
|
||||
.gev-voice-visualizer
|
||||
span,
|
||||
#command-dock
|
||||
#gev-voice-control[data-status='executing']
|
||||
.gev-voice-visualizer
|
||||
span {
|
||||
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;
|
||||
}
|
||||
|
||||
#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);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -364,11 +407,15 @@
|
||||
|
||||
@media (max-width: 900px) {
|
||||
#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 {
|
||||
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:
|
||||
0 0 0 0.18rem rgba(232, 234, 237, 0.04),
|
||||
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 {
|
||||
filter: grayscale(1) saturate(0) brightness(1.65);
|
||||
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-control:is(
|
||||
[data-status='connecting'],
|
||||
[data-status='listening']:not([data-microphone='muted']),
|
||||
[data-status='executing']
|
||||
) .gev-mic-orbit {
|
||||
#command-dock
|
||||
#gev-voice-control:is(
|
||||
[data-status='connecting'],
|
||||
[data-status='listening']:not([data-microphone='muted']),
|
||||
[data-status='executing']
|
||||
)
|
||||
.gev-mic-orbit {
|
||||
background: rgba(0, 212, 255, 0.06);
|
||||
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-control:is(
|
||||
[data-status='connecting'],
|
||||
[data-status='listening']:not([data-microphone='muted']),
|
||||
[data-status='executing']
|
||||
) .gev-mic-orbit img {
|
||||
#command-dock
|
||||
#gev-voice-control:is(
|
||||
[data-status='connecting'],
|
||||
[data-status='listening']:not([data-microphone='muted']),
|
||||
[data-status='executing']
|
||||
)
|
||||
.gev-mic-orbit
|
||||
img {
|
||||
filter: none;
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -470,7 +529,6 @@
|
||||
background: var(--glass-bg);
|
||||
border-color: var(--glass-border-hover);
|
||||
color: var(--accent);
|
||||
|
||||
}
|
||||
|
||||
#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']) #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;
|
||||
opacity: 1;
|
||||
transform: translate(-50%, 0) scale(1);
|
||||
@@ -622,10 +682,17 @@
|
||||
}
|
||||
|
||||
#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 {
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
--dock-presets-pinned-height: 0px;
|
||||
--dock-pinned-stack-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-presets-open: max(34rem, calc(var(--dock-unified-width) + 0.75rem));
|
||||
}
|
||||
@@ -33,7 +35,9 @@
|
||||
-webkit-mask-position: center;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
-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 {
|
||||
@@ -53,12 +57,10 @@
|
||||
}
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
#command-dock #control-panel .panel-header {
|
||||
justify-content: center;
|
||||
}
|
||||
@@ -173,7 +175,9 @@
|
||||
width: 0.9rem;
|
||||
height: 0.9rem;
|
||||
opacity: 0.58;
|
||||
transition: opacity 160ms ease, transform 160ms ease;
|
||||
transition:
|
||||
opacity 160ms ease,
|
||||
transform 160ms ease;
|
||||
}
|
||||
|
||||
#command-dock #location-bar .dock-pin-btn {
|
||||
@@ -188,12 +192,18 @@
|
||||
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 #control-panel:not(.collapsed):not(.dock-pinned) .dock-popover-content {
|
||||
#command-dock.dock-has-pinned-tray
|
||||
#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));
|
||||
}
|
||||
|
||||
#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));
|
||||
}
|
||||
|
||||
@@ -264,7 +274,9 @@
|
||||
background: rgba(0, 212, 255, 0.06);
|
||||
border: 1px solid rgba(0, 212, 255, 0.48);
|
||||
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 {
|
||||
@@ -288,9 +300,13 @@
|
||||
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='executing'] .gev-voice-visualizer {
|
||||
#command-dock
|
||||
#gev-voice-control[data-status='executing']
|
||||
.gev-voice-visualizer {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
@@ -362,16 +378,23 @@
|
||||
background: rgba(34, 10, 16, 0.96);
|
||||
border: 1px solid rgba(255, 92, 110, 0.5);
|
||||
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);
|
||||
-webkit-backdrop-filter: blur(28px) saturate(1.25);
|
||||
pointer-events: none;
|
||||
transform: translate(-50%, 0.55rem) scale(0.985);
|
||||
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;
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
@@ -421,7 +444,10 @@
|
||||
|
||||
/* 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):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;
|
||||
bottom: 0.75rem;
|
||||
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) {
|
||||
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);
|
||||
left: 0.75rem;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
z-index: 145;
|
||||
display: grid;
|
||||
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;
|
||||
width: min(94vw, 1240px);
|
||||
padding: 6px;
|
||||
@@ -22,7 +25,9 @@
|
||||
backdrop-filter: blur(28px) saturate(1.35);
|
||||
-webkit-backdrop-filter: blur(28px) saturate(1.35);
|
||||
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,
|
||||
@@ -214,7 +219,10 @@ body:not(.ui-clean-view):not(.recording-mode) #cesium-credits {
|
||||
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;
|
||||
left: auto;
|
||||
width: min(340px, calc(100vw - 72px)) !important;
|
||||
|
||||
+76
-22
@@ -12,7 +12,11 @@
|
||||
#control-panel .panel-glow {
|
||||
position: absolute;
|
||||
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);
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
@@ -185,7 +189,11 @@
|
||||
content: '';
|
||||
position: absolute;
|
||||
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;
|
||||
transition: opacity var(--transition-fast);
|
||||
}
|
||||
@@ -264,7 +272,9 @@
|
||||
display: flex;
|
||||
align-items: 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 {
|
||||
@@ -287,8 +297,15 @@
|
||||
}
|
||||
|
||||
@keyframes logo-pulse {
|
||||
0%, 100% { opacity: 0.72; transform: scale(0.98); }
|
||||
50% { opacity: 1; transform: scale(1.02); }
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.72;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: scale(1.02);
|
||||
}
|
||||
}
|
||||
|
||||
.loader-content h2 {
|
||||
@@ -309,8 +326,13 @@
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 0.4; }
|
||||
50% { opacity: 1; }
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.4;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Post-Processing Toggles ─────────────────── */
|
||||
@@ -332,7 +354,9 @@
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: var(--panel-radius);
|
||||
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);
|
||||
-webkit-backdrop-filter: blur(24px) saturate(1.4);
|
||||
}
|
||||
@@ -389,7 +413,9 @@
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: var(--panel-radius);
|
||||
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);
|
||||
-webkit-backdrop-filter: blur(24px) saturate(1.4);
|
||||
cursor: default;
|
||||
@@ -461,7 +487,11 @@
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
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 {
|
||||
@@ -600,7 +630,7 @@
|
||||
outline-offset: 2px;
|
||||
}
|
||||
.gev-quantitative-slider:disabled {
|
||||
opacity: .42;
|
||||
opacity: 0.42;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.gev-slider-value {
|
||||
@@ -608,7 +638,7 @@
|
||||
color: var(--accent);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 9px;
|
||||
letter-spacing: .5px;
|
||||
letter-spacing: 0.5px;
|
||||
text-align: right;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@@ -793,7 +823,9 @@
|
||||
}
|
||||
|
||||
/* Proximity / All segmented control under the 3D toggle */
|
||||
#models3d-mode-row { min-width: 184px; }
|
||||
#models3d-mode-row {
|
||||
min-width: 184px;
|
||||
}
|
||||
.pp-mode-seg {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
@@ -812,11 +844,21 @@
|
||||
letter-spacing: 0.6px;
|
||||
text-transform: uppercase;
|
||||
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
|
||||
itself and inside clipped button groups, independent of selection color. */
|
||||
@@ -913,8 +955,13 @@ body.ui-clean-view #scene-runtime.active {
|
||||
}
|
||||
|
||||
@keyframes detection-pulse {
|
||||
0%, 100% { box-shadow: 0 0 12px rgba(0, 255, 80, 0.15); }
|
||||
50% { box-shadow: 0 0 22px rgba(0, 255, 80, 0.35); }
|
||||
0%,
|
||||
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 ──────────────────── */
|
||||
@@ -942,7 +989,10 @@ body.ui-clean-view #scene-runtime.active {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
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 {
|
||||
@@ -988,7 +1038,12 @@ body.ui-clean-view #scene-runtime.active {
|
||||
}
|
||||
|
||||
#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 {
|
||||
@@ -1058,4 +1113,3 @@ body.ui-clean-view #scene-runtime.active {
|
||||
text-align: right;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
+96
-28
@@ -31,7 +31,9 @@
|
||||
-webkit-backdrop-filter: blur(28px) saturate(1.25);
|
||||
opacity: 0;
|
||||
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
|
||||
dismiss animation cannot launch a second mission behind the fade. */
|
||||
pointer-events: none;
|
||||
@@ -55,7 +57,12 @@
|
||||
position: absolute;
|
||||
inset: 0 0 auto;
|
||||
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);
|
||||
pointer-events: none;
|
||||
}
|
||||
@@ -73,7 +80,9 @@
|
||||
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 {
|
||||
margin-top: 0.6rem;
|
||||
@@ -107,7 +116,9 @@
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
.first-run-choices::-webkit-scrollbar { width: 6px; }
|
||||
.first-run-choices::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
.first-run-choices::-webkit-scrollbar-thumb {
|
||||
background: rgba(54, 220, 255, 0.45);
|
||||
border-radius: 3px;
|
||||
@@ -127,7 +138,10 @@
|
||||
border: 1px solid rgba(100, 200, 220, 0.18);
|
||||
border-radius: 0.42rem;
|
||||
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,
|
||||
@@ -173,7 +187,9 @@
|
||||
.first-run-arrow {
|
||||
color: rgba(54, 220, 255, 0.4);
|
||||
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,
|
||||
@@ -212,7 +228,9 @@
|
||||
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
|
||||
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);
|
||||
}
|
||||
|
||||
#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
|
||||
and the redundant "ESC to dismiss" hint (no ESC key on a phone) drops. */
|
||||
#first-run-launcher h2 { margin-top: 0.45rem; }
|
||||
#first-run-description { margin-top: 0.25rem; 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; }
|
||||
#first-run-launcher h2 {
|
||||
margin-top: 0.45rem;
|
||||
}
|
||||
#first-run-description {
|
||||
margin-top: 0.25rem;
|
||||
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
|
||||
@@ -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
|
||||
exactly why it can come back.) */
|
||||
@media (max-height: 620px) {
|
||||
#first-run-launcher { padding: 0.75rem 0.85rem; }
|
||||
#first-run-launcher h2 { margin-top: 0.3rem; font-size: 1.1rem; }
|
||||
#first-run-description { 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; }
|
||||
#first-run-launcher {
|
||||
padding: 0.75rem 0.85rem;
|
||||
}
|
||||
#first-run-launcher h2 {
|
||||
margin-top: 0.3rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
#first-run-description {
|
||||
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"
|
||||
@@ -283,8 +340,16 @@ body.scene-playback-mode #first-run-launcher {
|
||||
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. */
|
||||
.first-run-choices[data-scrollable='true'] {
|
||||
-webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 1.4rem), transparent 100%);
|
||||
mask-image: linear-gradient(to bottom, #000 calc(100% - 1.4rem), transparent 100%);
|
||||
-webkit-mask-image: linear-gradient(
|
||||
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.
|
||||
@@ -308,8 +373,11 @@ body.scene-playback-mode #first-run-launcher {
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
#first-run-launcher,
|
||||
.first-run-choices button,
|
||||
.first-run-arrow { transition: none; }
|
||||
.first-run-arrow {
|
||||
transition: none;
|
||||
}
|
||||
/* Scrolling a tile into view must not animate either. */
|
||||
.first-run-choices { scroll-behavior: auto; }
|
||||
.first-run-choices {
|
||||
scroll-behavior: auto;
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+25
-12
@@ -69,7 +69,8 @@
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
html, body {
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
@@ -185,7 +186,9 @@ body.cockpit-mode #cockpit-cloud-effects.active {
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 300ms ease, visibility 300ms ease;
|
||||
transition:
|
||||
opacity 300ms ease,
|
||||
visibility 300ms ease;
|
||||
}
|
||||
|
||||
.celestial-ring-overlay.visible {
|
||||
@@ -222,19 +225,27 @@ body.cockpit-mode #cockpit-cloud-effects.active {
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
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;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.celestial-sun {
|
||||
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 {
|
||||
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 {
|
||||
@@ -499,7 +510,7 @@ body.cockpit-mode #cockpit-cloud-effects.active {
|
||||
|
||||
#title-bar .title-logo::before,
|
||||
#title-bar .title-logo::after {
|
||||
--radio-broadcast-opacity: .28;
|
||||
--radio-broadcast-opacity: 0.28;
|
||||
content: '';
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
@@ -517,7 +528,7 @@ body.cockpit-mode #cockpit-cloud-effects.active {
|
||||
}
|
||||
|
||||
#title-bar .title-logo::after {
|
||||
--radio-broadcast-opacity: .17;
|
||||
--radio-broadcast-opacity: 0.17;
|
||||
top: -15px;
|
||||
width: 27px;
|
||||
height: 13px;
|
||||
@@ -529,12 +540,15 @@ body.cockpit-mode #cockpit-cloud-effects.active {
|
||||
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 {
|
||||
0%, 100% {
|
||||
opacity: calc(var(--radio-broadcast-opacity) * .55);
|
||||
transform: translateX(-50%) scale(.94);
|
||||
0%,
|
||||
100% {
|
||||
opacity: calc(var(--radio-broadcast-opacity) * 0.55);
|
||||
transform: translateX(-50%) scale(0.94);
|
||||
}
|
||||
50% {
|
||||
opacity: var(--radio-broadcast-opacity);
|
||||
@@ -608,4 +622,3 @@ body.cockpit-mode #cockpit-cloud-effects.active {
|
||||
text-shadow: 0 0 20px var(--accent-glow);
|
||||
transition: var(--transition-smooth);
|
||||
}
|
||||
|
||||
+390
-182
File diff suppressed because it is too large.
Load diff
@@ -12,7 +12,11 @@
|
||||
#location-bar .panel-glow {
|
||||
position: absolute;
|
||||
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);
|
||||
opacity: 0.25;
|
||||
pointer-events: none;
|
||||
@@ -137,9 +141,10 @@
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
transition: max-height 250ms cubic-bezier(0.4, 0, 0.2, 1),
|
||||
opacity 200ms ease,
|
||||
padding 250ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition:
|
||||
max-height 250ms cubic-bezier(0.4, 0, 0.2, 1),
|
||||
opacity 200ms ease,
|
||||
padding 250ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
@@ -155,7 +160,9 @@
|
||||
border-top: 1px solid var(--glass-border);
|
||||
margin: 0 4px;
|
||||
opacity: 0;
|
||||
transition: opacity 200ms ease, margin 250ms ease;
|
||||
transition:
|
||||
opacity 200ms ease,
|
||||
margin 250ms ease;
|
||||
}
|
||||
|
||||
.location-bar-divider.visible {
|
||||
@@ -272,7 +279,9 @@
|
||||
font-family: var(--font-sans);
|
||||
font-size: 13px;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -289,4 +298,3 @@
|
||||
#location-search.searching {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,8 @@
|
||||
|
||||
#safe-frame-box::before {
|
||||
background:
|
||||
linear-gradient(to right,
|
||||
linear-gradient(
|
||||
to right,
|
||||
transparent 33.333%,
|
||||
rgba(255, 255, 255, 0.2) 33.333%,
|
||||
rgba(255, 255, 255, 0.2) 33.7%,
|
||||
@@ -50,7 +51,8 @@
|
||||
rgba(255, 255, 255, 0.2) 66.7%,
|
||||
transparent 66.7%
|
||||
),
|
||||
linear-gradient(to bottom,
|
||||
linear-gradient(
|
||||
to bottom,
|
||||
transparent 33.333%,
|
||||
rgba(255, 255, 255, 0.2) 33.333%,
|
||||
rgba(255, 255, 255, 0.2) 33.7%,
|
||||
@@ -77,7 +79,9 @@
|
||||
user-select: none;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity var(--transition-smooth), visibility var(--transition-smooth);
|
||||
transition:
|
||||
opacity var(--transition-smooth),
|
||||
visibility var(--transition-smooth);
|
||||
}
|
||||
|
||||
#orbit-indicator.active {
|
||||
@@ -99,7 +103,9 @@
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
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-glow: rgba(0, 255, 255, 0.4);
|
||||
--hud-border: rgba(0, 255, 255, 0.15);
|
||||
@@ -322,7 +328,11 @@
|
||||
|
||||
#intel-hud[data-variant='operator'] .hud-bottom-bar {
|
||||
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 {
|
||||
@@ -361,4 +371,3 @@
|
||||
#intel-hud[data-variant='minimal'] .hud-summary-wrap {
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,11 @@
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.6rem;
|
||||
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-radius: 0.42rem;
|
||||
box-shadow:
|
||||
@@ -26,12 +30,19 @@
|
||||
backdrop-filter: blur(18px) saturate(1.2);
|
||||
-webkit-backdrop-filter: blur(18px) saturate(1.2);
|
||||
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: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);
|
||||
outline: none;
|
||||
transform: translateY(-1px);
|
||||
@@ -42,7 +53,9 @@
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
#key-setup-chip[hidden] { display: none; }
|
||||
#key-setup-chip[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#key-setup {
|
||||
position: fixed;
|
||||
@@ -70,12 +83,16 @@
|
||||
-webkit-backdrop-filter: blur(28px) saturate(1.25);
|
||||
opacity: 0;
|
||||
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;
|
||||
}
|
||||
|
||||
/* Same cascade note as the launcher: an author display beats [hidden]. */
|
||||
#key-setup[hidden] { display: none; }
|
||||
#key-setup[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#key-setup.visible {
|
||||
opacity: 1;
|
||||
@@ -87,7 +104,12 @@
|
||||
position: absolute;
|
||||
inset: 0 0 auto;
|
||||
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);
|
||||
pointer-events: none;
|
||||
}
|
||||
@@ -105,7 +127,9 @@
|
||||
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 {
|
||||
display: grid;
|
||||
@@ -116,7 +140,9 @@
|
||||
border: 1px solid transparent;
|
||||
border-radius: 0.3rem;
|
||||
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,
|
||||
@@ -126,7 +152,9 @@
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.key-setup-close .material-symbols-outlined { font-size: 1rem; }
|
||||
.key-setup-close .material-symbols-outlined {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
#key-setup h2 {
|
||||
margin-top: 0.6rem;
|
||||
@@ -163,7 +191,9 @@
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
.key-setup-rows::-webkit-scrollbar { width: 6px; }
|
||||
.key-setup-rows::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
.key-setup-rows::-webkit-scrollbar-thumb {
|
||||
background: rgba(54, 220, 255, 0.45);
|
||||
border-radius: 3px;
|
||||
@@ -209,7 +239,9 @@
|
||||
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 {
|
||||
padding: 0.05rem 0.3rem;
|
||||
@@ -264,9 +296,13 @@
|
||||
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 {
|
||||
padding: 0.5rem 0.95rem;
|
||||
@@ -318,7 +354,9 @@ body.scene-playback-mode #key-setup {
|
||||
|
||||
@media (max-width: 620px) {
|
||||
/* 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. */
|
||||
@@ -344,7 +382,9 @@ body.scene-playback-mode #key-setup {
|
||||
border: 1px solid rgba(255, 170, 150, 0.3);
|
||||
border-radius: 0.32rem;
|
||||
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,
|
||||
|
||||
+314
-88
@@ -5,7 +5,7 @@
|
||||
min-height: 0;
|
||||
margin-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 {
|
||||
position: relative;
|
||||
@@ -14,20 +14,24 @@
|
||||
flex-direction: column;
|
||||
gap: 9px;
|
||||
padding: 10px;
|
||||
border: 1px solid rgba(65, 220, 231, .18);
|
||||
border: 1px solid rgba(65, 220, 231, 0.18);
|
||||
border-radius: 8px;
|
||||
background: rgba(2, 15, 20, .42);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .035);
|
||||
background: rgba(2, 15, 20, 0.42);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.035);
|
||||
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 {
|
||||
color: var(--text-dim);
|
||||
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 {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
@@ -38,37 +42,42 @@
|
||||
grid-column: 2;
|
||||
color: var(--text-dim);
|
||||
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 {
|
||||
min-width: 0;
|
||||
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;
|
||||
color: #c5faff;
|
||||
background: rgba(5, 24, 31, .88);
|
||||
background: rgba(5, 24, 31, 0.88);
|
||||
font: 9px/1.2 var(--font-mono);
|
||||
}
|
||||
.radio-station-card {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
padding: 10px;
|
||||
border: 1px solid rgba(65, 220, 231, .18);
|
||||
border: 1px solid rgba(65, 220, 231, 0.18);
|
||||
border-radius: 7px;
|
||||
background: rgba(2, 15, 20, .48);
|
||||
background: rgba(2, 15, 20, 0.48);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.radio-station-card strong {
|
||||
color: #e8feff;
|
||||
font: 11px/1.25 var(--font-mono);
|
||||
letter-spacing: .05em;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
.radio-station-card span {
|
||||
color: var(--text-dim);
|
||||
font: 8px/1.35 var(--font-mono);
|
||||
}
|
||||
#radio-station-tags { color: #72dce6; }
|
||||
#radio-station-tags {
|
||||
color: #72dce6;
|
||||
}
|
||||
.radio-tuner {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
@@ -77,12 +86,18 @@
|
||||
gap: 5px;
|
||||
padding: 8px;
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(65, 220, 231, .22);
|
||||
border: 1px solid rgba(65, 220, 231, 0.22);
|
||||
border-radius: 7px;
|
||||
background: linear-gradient(180deg, rgba(1, 9, 13, .92), rgba(4, 24, 30, .72));
|
||||
box-shadow: inset 0 0 18px rgba(0, 212, 255, .045);
|
||||
background: linear-gradient(
|
||||
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-readout {
|
||||
display: flex;
|
||||
@@ -90,7 +105,7 @@
|
||||
gap: 8px;
|
||||
color: #6daeb5;
|
||||
font: 7px/1.2 var(--font-mono);
|
||||
letter-spacing: .1em;
|
||||
letter-spacing: 0.1em;
|
||||
}
|
||||
#radio-tuner-value {
|
||||
min-width: 0;
|
||||
@@ -108,10 +123,22 @@
|
||||
height: 51px;
|
||||
overflow: hidden;
|
||||
contain: paint;
|
||||
border-block: 1px solid rgba(72, 225, 237, .3);
|
||||
border-block: 1px solid rgba(72, 225, 237, 0.3);
|
||||
background:
|
||||
linear-gradient(180deg, transparent 23px, rgba(65, 220, 231, .8) 24px, transparent 25px, transparent 28px, rgba(65, 220, 231, .6) 29px, transparent 30px),
|
||||
radial-gradient(ellipse at center, rgba(39, 177, 191, .12), transparent 70%);
|
||||
linear-gradient(
|
||||
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 {
|
||||
position: absolute;
|
||||
@@ -119,7 +146,7 @@
|
||||
overflow: hidden;
|
||||
color: #58cbd6;
|
||||
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;
|
||||
}
|
||||
.radio-tuner-tick {
|
||||
@@ -127,7 +154,7 @@
|
||||
top: 6px;
|
||||
bottom: 0;
|
||||
min-width: 1px;
|
||||
border-left: 1px solid rgba(85, 224, 235, .38);
|
||||
border-left: 1px solid rgba(85, 224, 235, 0.38);
|
||||
color: #58cbd6;
|
||||
padding-left: 3px;
|
||||
white-space: nowrap;
|
||||
@@ -135,7 +162,7 @@
|
||||
transition: left 80ms linear;
|
||||
}
|
||||
.radio-tuner-tick.is-current {
|
||||
border-left-color: rgba(168, 247, 255, .9);
|
||||
border-left-color: rgba(168, 247, 255, 0.9);
|
||||
color: #a8f7ff;
|
||||
}
|
||||
.radio-tuner-needle {
|
||||
@@ -147,13 +174,17 @@
|
||||
width: 3px;
|
||||
border-radius: 1px;
|
||||
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;
|
||||
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-tick { transition: none; }
|
||||
.radio-tuner.is-dragging .radio-tuner-tick {
|
||||
transition: none;
|
||||
}
|
||||
#radio-tuner-slider {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
@@ -184,7 +215,10 @@
|
||||
background: transparent;
|
||||
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 {
|
||||
width: 14px;
|
||||
height: 47px;
|
||||
@@ -194,9 +228,11 @@
|
||||
box-shadow: none;
|
||||
}
|
||||
.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 {
|
||||
min-width: 0;
|
||||
color: #7fe6ef;
|
||||
@@ -216,12 +252,12 @@
|
||||
.radio-transport button {
|
||||
min-height: 30px;
|
||||
padding: 6px 3px;
|
||||
border: 1px solid rgba(65, 220, 231, .28);
|
||||
border: 1px solid rgba(65, 220, 231, 0.28);
|
||||
border-radius: 5px;
|
||||
color: #8feaf2;
|
||||
background: rgba(14, 69, 78, .14);
|
||||
background: rgba(14, 69, 78, 0.14);
|
||||
font: 8px/1 var(--font-mono);
|
||||
letter-spacing: .06em;
|
||||
letter-spacing: 0.06em;
|
||||
cursor: pointer;
|
||||
}
|
||||
.radio-transport button:hover:not(:disabled),
|
||||
@@ -231,12 +267,15 @@
|
||||
outline: none;
|
||||
}
|
||||
.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 {
|
||||
color: #fff;
|
||||
border-color: rgba(101, 247, 255, .8);
|
||||
background: rgba(65, 220, 231, .18);
|
||||
box-shadow: 0 0 12px rgba(65, 220, 231, .12);
|
||||
border-color: rgba(101, 247, 255, 0.8);
|
||||
background: rgba(65, 220, 231, 0.18);
|
||||
box-shadow: 0 0 12px rgba(65, 220, 231, 0.12);
|
||||
}
|
||||
.radio-volume-row {
|
||||
display: grid;
|
||||
@@ -245,15 +284,19 @@
|
||||
align-items: center;
|
||||
color: var(--text-dim);
|
||||
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 {
|
||||
min-height: 12px;
|
||||
color: #79d7df;
|
||||
font: 8px/1.35 var(--font-mono);
|
||||
}
|
||||
.radio-playback-state.error { color: #ff9d8f; }
|
||||
.radio-playback-state.error {
|
||||
color: #ff9d8f;
|
||||
}
|
||||
.radio-links {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -263,13 +306,16 @@
|
||||
.radio-links a {
|
||||
color: #64cbd5;
|
||||
font: 7px/1.3 var(--font-mono);
|
||||
letter-spacing: .06em;
|
||||
letter-spacing: 0.06em;
|
||||
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 {
|
||||
margin: 0;
|
||||
color: rgba(190, 230, 230, .48);
|
||||
color: rgba(190, 230, 230, 0.48);
|
||||
font: 7px/1.4 var(--font-mono);
|
||||
}
|
||||
#military-awareness-panel {
|
||||
@@ -281,36 +327,129 @@
|
||||
color: #dceeff;
|
||||
background: transparent;
|
||||
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-subject { margin: 0 0 7px; color: #fff; }
|
||||
.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-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-panel[hidden] {
|
||||
display: none;
|
||||
}
|
||||
.military-awareness-subject {
|
||||
margin: 0 0 7px;
|
||||
color: #fff;
|
||||
}
|
||||
.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-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 {
|
||||
display: grid;
|
||||
gap: 7px;
|
||||
padding: 12px 4px 4px;
|
||||
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 span { color: #8192a6; font-size: 9px; letter-spacing: 0.05em; }
|
||||
.military-awareness-standby strong {
|
||||
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 {
|
||||
justify-self: start;
|
||||
padding: 4px 12px;
|
||||
@@ -318,27 +457,51 @@
|
||||
border-radius: 4px;
|
||||
color: #a9d9ff;
|
||||
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;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#military-awareness-direction-overlay { position: absolute; inset: 0; z-index: 4; overflow: hidden; pointer-events: none; }
|
||||
#military-awareness-direction-overlay[hidden] { display: none; }
|
||||
#military-awareness-direction-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 4;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
#military-awareness-direction-overlay[hidden] {
|
||||
display: none;
|
||||
}
|
||||
.military-awareness-compass-ring {
|
||||
--compass-rotation: 0deg;
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid rgba(111,196,255,0.2);
|
||||
outline: 1px dashed rgba(111,196,255,0.28);
|
||||
border: 1px solid rgba(111, 196, 255, 0.2);
|
||||
outline: 1px dashed rgba(111, 196, 255, 0.28);
|
||||
outline-offset: -5px;
|
||||
border-radius: 50%;
|
||||
box-shadow:
|
||||
inset 0 0 0 5px rgba(34,128,196,0.018),
|
||||
inset 0 0 24px rgba(34,128,196,0.035),
|
||||
0 0 14px rgba(34,128,196,0.05);
|
||||
inset 0 0 0 5px rgba(34, 128, 196, 0.018),
|
||||
inset 0 0 24px rgba(34, 128, 196, 0.035),
|
||||
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 {
|
||||
content: '';
|
||||
position: absolute;
|
||||
@@ -349,22 +512,86 @@
|
||||
rgba(125, 205, 255, 0.38) 0deg 0.7deg,
|
||||
transparent 0.7deg 15deg
|
||||
);
|
||||
-webkit-mask: radial-gradient(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));
|
||||
-webkit-mask: radial-gradient(
|
||||
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));
|
||||
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.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); }
|
||||
.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.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 {
|
||||
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 {
|
||||
@@ -573,4 +800,3 @@
|
||||
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.18);
|
||||
flex: none;
|
||||
}
|
||||
|
||||
@@ -30,4 +30,3 @@ body.recording-mode #scene-runtime.active {
|
||||
body.scene-playback-mode :is(#clear-selected-layers, #reset-globe-view) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@@ -213,4 +213,3 @@
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
+39
-11
@@ -35,7 +35,9 @@
|
||||
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
|
||||
@@ -59,7 +61,8 @@
|
||||
* When an expanded panel and its collapsed siblings fit together, preserve
|
||||
* 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;
|
||||
flex: 0 0 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 {
|
||||
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);
|
||||
}
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
#scene-panel .panel-glow {
|
||||
position: absolute;
|
||||
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);
|
||||
opacity: 0.25;
|
||||
pointer-events: none;
|
||||
@@ -209,7 +226,9 @@
|
||||
border-radius: 9px;
|
||||
padding: 7px 8px;
|
||||
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 {
|
||||
@@ -300,7 +319,11 @@
|
||||
#scene-progress-fill {
|
||||
width: 0%;
|
||||
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;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 8px;
|
||||
@@ -329,7 +352,9 @@
|
||||
z-index: 105;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 180ms ease, visibility 180ms ease;
|
||||
transition:
|
||||
opacity 180ms ease,
|
||||
visibility 180ms ease;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 11px;
|
||||
letter-spacing: 2px;
|
||||
@@ -354,7 +379,9 @@
|
||||
#global-context-panel.collapsed .global-context-actions,
|
||||
#global-context-panel.collapsed .global-context-modes,
|
||||
#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),
|
||||
#cctv-panel.collapsed .cctv-controls,
|
||||
#cctv-panel.collapsed .cctv-calibration-block,
|
||||
@@ -380,7 +407,9 @@
|
||||
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,
|
||||
#cctv-panel.collapsed .panel-title,
|
||||
@@ -409,4 +438,3 @@
|
||||
#param-slider-panel.collapsed .param-panel-header {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
+79
-23
@@ -74,10 +74,14 @@
|
||||
letter-spacing: 1.35px;
|
||||
text-transform: uppercase;
|
||||
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;
|
||||
}
|
||||
#global-loading-status[hidden] { display: none; }
|
||||
#global-loading-status[hidden] {
|
||||
display: none;
|
||||
}
|
||||
#global-loading-label {
|
||||
color: rgba(184, 244, 255, 0.92);
|
||||
}
|
||||
@@ -92,25 +96,49 @@
|
||||
text-overflow: ellipsis;
|
||||
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;
|
||||
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;
|
||||
}
|
||||
#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-detail { color: #ff9b8e; }
|
||||
#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='error'] #global-loading-detail {
|
||||
color: #ff9b8e;
|
||||
}
|
||||
#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-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-detail { color: #ffd488; }
|
||||
#global-loading-status[data-state='cancelled'] #global-loading-detail {
|
||||
color: #ffd488;
|
||||
}
|
||||
#global-loading-status[data-state='acquiring'] {
|
||||
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 {
|
||||
@@ -141,8 +169,12 @@
|
||||
text-transform: uppercase;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: 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);
|
||||
transition:
|
||||
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 */
|
||||
@@ -270,21 +302,43 @@
|
||||
them. */
|
||||
.gev-flap-sizing {
|
||||
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. */
|
||||
@keyframes gev-flap-out {
|
||||
0% { transform: rotateX(0deg); opacity: 1; }
|
||||
48% { transform: rotateX(-88deg); opacity: 0.5; }
|
||||
100% { transform: rotateX(-90deg); opacity: 0; }
|
||||
0% {
|
||||
transform: rotateX(0deg);
|
||||
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
|
||||
with a single small overshoot — the mechanical clack. */
|
||||
@keyframes gev-flap-in {
|
||||
0% { transform: rotateX(90deg); opacity: 0; }
|
||||
46% { transform: rotateX(90deg); opacity: 0; }
|
||||
78% { transform: rotateX(-7deg); opacity: 1; }
|
||||
100% { transform: rotateX(0deg); opacity: 1; }
|
||||
0% {
|
||||
transform: rotateX(90deg);
|
||||
opacity: 0;
|
||||
}
|
||||
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
|
||||
than dropping the animation, so the final frame still lands the glyph.
|
||||
@@ -322,7 +376,10 @@
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -331,4 +388,3 @@
|
||||
visibility: visible;
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,9 @@
|
||||
letter-spacing: 0.045rem;
|
||||
line-height: 1;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -75,4 +77,3 @@
|
||||
#command-dock.tray-open .gev-voice-cost {
|
||||
display: none;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user