mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 07:42:23 +00:00
Merge pull request #1739 from bilawalsidhu/manjunath/cockpit-panels
Cockpit side panels: the main page's rails and panels in first person
This commit is contained in:
10 files changed
+846
-32
No files matched your search
@@ -0,0 +1,4 @@
|
||||
- Give the desktop cockpit the main page's side rails: Data Layers alone on the
|
||||
left, and Display and Radio as a right rail that opens one panel at a time
|
||||
beside it, at the same height, with Contact and Live Signals restyled to
|
||||
match (manjunath22466, samehkhamis, #1739).
|
||||
+24
-3
@@ -661,9 +661,9 @@ cards keep Cyber's own red skin. The presets and chips call the
|
||||
same Display store actions as the dock's Visual Presets tray, which stays on
|
||||
desktop and phone as before, along with Location; voice `Visual Presets`
|
||||
still opens that tray. On a phone the More sheet shows that tray right above
|
||||
Visual, so Visual leaves out its own presets and chips there. In Cockpit the
|
||||
groups Cockpit's Display popover takes
|
||||
leave the panel as before and keep their own styling there.
|
||||
Visual, so Visual leaves out its own presets and chips there. In Cockpit,
|
||||
the groups that Cockpit's Display popover takes leave the panel as before and
|
||||
keep their own styling there.
|
||||
|
||||
Scope has an Opacity slider (`#scope-opacity-slider`, 50–100%, default 100%;
|
||||
`SCOPE_OPACITY_MIN_PCT` in `src/ui/visualSettings.js`, since a fainter mask
|
||||
@@ -723,6 +723,27 @@ and touch screens, while the rails are hidden (clean view, recording), and it
|
||||
follows the rails' sides in a right-to-left language. On a phone, Visual is in
|
||||
the More sheet with the dock's Visual Presets, as before.
|
||||
|
||||
## Cockpit side panels — November 14, 2026
|
||||
|
||||
On the desktop, the cockpit's side panels match the main page's
|
||||
(`src/ui/styles/cockpit-panels.css`). The left rail shows Data Layers only:
|
||||
Authoring, Utilities and Visual, with their panels, are hidden, since the
|
||||
cockpit's own Display panel stands in for Visual. Display and Radio
|
||||
(`CockpitUtilityControls.vue`) become a right rail of two icons with their
|
||||
names beside them, and each opens one panel at a time beside the rail, with a
|
||||
header and a "−" to close it. The rail's buttons are disclosures
|
||||
(`aria-expanded`, `aria-controls`) and go through the `toggleCockpitUtility`
|
||||
action (`src/ui/radioBindings.js`); closing a panel gives focus back to the
|
||||
rail. Both rails start at the same height: the left one in CSS, and Display
|
||||
and Radio through the cockpit layout pass, whose floor
|
||||
(`COCKPIT_UTILITY_MIN_TOP_PX` / `_RATIO` in `src/ui/cockpitPresentation.js`)
|
||||
matches it, so an open panel still stops short of the briefing. Contact and
|
||||
Live Signals are shorter, in the same glass, with the briefing's rows and
|
||||
weather tiles tightened; Regional News stays a briefing page. In a right-to-left language, the rail keeps its
|
||||
physical side as the cockpit's other windows do. The Cyber HUD and the phone
|
||||
keep their own cockpit layouts, with the Display and Radio launchers as
|
||||
before, and edge mode does not apply in the cockpit.
|
||||
|
||||
## God's Eye View in conversations — October 2, 2026
|
||||
|
||||
Tool answers that can be shown in God's Eye View include a view: camera, layers,
|
||||
|
||||
@@ -1461,5 +1461,6 @@
|
||||
"src/data/placementProvider.test.mjs",
|
||||
"src/layers/vessels/placement.test.mjs",
|
||||
"src/ui/styles/visual-panel.css",
|
||||
"src/ui/styles/scope-off-shadows.css"
|
||||
"src/ui/styles/scope-off-shadows.css",
|
||||
"src/ui/styles/cockpit-panels.css"
|
||||
]
|
||||
+39
-18
@@ -26,10 +26,7 @@ import {
|
||||
setSignalCollapsed,
|
||||
} from './ui/cockpitLayout.js';
|
||||
import { createBriefState } from './ui/cockpitBriefPages.js';
|
||||
import {
|
||||
publishBriefView,
|
||||
setBriefAutoRotate,
|
||||
} from './ui/cockpitBriefing.js';
|
||||
import { publishBriefView, setBriefAutoRotate } from './ui/cockpitBriefing.js';
|
||||
import { CockpitDisplayPortal } from './ui/cockpitDisplayPortal.js';
|
||||
import { _handleContextLayerChange } from './ui/contextLayerChanges.js';
|
||||
import { clearSelectedLayers } from './ui/contextActions.js';
|
||||
@@ -438,13 +435,10 @@ test('Cockpit owns a focused shared Display portal and compact Radio controls',
|
||||
/data-cockpit-launcher="radio"[\s\S]*?id="cockpit-radio-toggle-btn"/,
|
||||
);
|
||||
const cockpitRadio = html.match(
|
||||
/id="cockpit-radio-panel"[\s\S]*?<\/div>\s*<\/div>\s*<\/aside>/,
|
||||
/id="cockpit-radio-panel"[\s\S]*?<\/div>\s*<\/div>\s*<div class="cockpit-utility-rail/,
|
||||
);
|
||||
assert.ok(cockpitRadio, 'Cockpit compact Radio controls are missing');
|
||||
assert.doesNotMatch(
|
||||
cockpitRadio[0],
|
||||
/radio-filter|radio-tuner/,
|
||||
);
|
||||
assert.doesNotMatch(cockpitRadio[0], /radio-filter|radio-tuner/);
|
||||
assert.match(
|
||||
css,
|
||||
/\.cockpit-utility-controls[\s\S]*?top:\s*var\(\s*--cockpit-utility-top/,
|
||||
@@ -811,18 +805,27 @@ test('Reset releases Contact camera ownership through its selection-preserving r
|
||||
);
|
||||
assert.match(ui, /onReset: \(\) => this\.resetToGlobeView\(\)/);
|
||||
assert.match(ui, /this\._connectGlobeReset\?\.\(this\._locationControls\)/);
|
||||
assert.match(controls, /reset\(\) \{\s*if \(!this\.destroyed\) return this\.onReset\(\);/);
|
||||
assert.match(
|
||||
controls,
|
||||
/reset\(\) \{\s*if \(!this\.destroyed\) return this\.onReset\(\);/,
|
||||
);
|
||||
// Both reset buttons ask the one reset owner.
|
||||
const globeActions = fs.readFileSync(
|
||||
path.join(ROOT, 'src', 'ui', 'views', 'panels', 'GlobeActions.vue'),
|
||||
'utf8',
|
||||
);
|
||||
assert.match(globeActions, /id="reset-globe-view"[^>]*@click="act\('reset'\)"/);
|
||||
assert.match(
|
||||
globeActions,
|
||||
/id="reset-globe-view"[^>]*@click="act\('reset'\)"/,
|
||||
);
|
||||
const cockpitView = readRadioSource(
|
||||
new URL('./ui/views/CockpitView.vue', import.meta.url),
|
||||
'utf8',
|
||||
);
|
||||
assert.match(cockpitView, /id="cockpit-reset-globe"[^>]*@click="globe\.act\('reset'\)"/);
|
||||
assert.match(
|
||||
cockpitView,
|
||||
/id="cockpit-reset-globe"[^>]*@click="globe\.act\('reset'\)"/,
|
||||
);
|
||||
assert.match(
|
||||
cockpitEnter.toString(),
|
||||
/Object\.assign\(this\.presence, \{\s*active: true,/,
|
||||
@@ -1193,7 +1196,10 @@ test('cockpit summary presents the focused item as Contact', () => {
|
||||
const match = html.match(/<aside id="cockpit-context"[\s\S]*?<\/aside>/);
|
||||
assert.ok(match, 'cockpit Contact summary is missing');
|
||||
assert.match(match[0], /aria-label="Contact cockpit summary"/);
|
||||
assert.match(match[0], /class="cockpit-card-kicker cockpit-context-kicker">CONTACT</);
|
||||
assert.match(
|
||||
match[0],
|
||||
/class="cockpit-card-kicker cockpit-context-kicker">CONTACT</,
|
||||
);
|
||||
assert.match(match[0], /aria-label="Contact navigation"/);
|
||||
assert.match(
|
||||
match[0],
|
||||
@@ -1408,15 +1414,30 @@ test('Contact folded for one Cockpit left panel comes back only once neither is
|
||||
const previous = globalThis.document;
|
||||
const panels = {};
|
||||
globalThis.document = { getElementById: (id) => panels[id] ?? null };
|
||||
const panel = (...classes) => ({ classList: { contains: (name) => classes.includes(name) } });
|
||||
const panel = (...classes) => ({
|
||||
classList: { contains: (name) => classes.includes(name) },
|
||||
});
|
||||
try {
|
||||
const otherOpen = (id) => PanelChrome.prototype._otherCockpitLeftPanelOpen(id);
|
||||
const otherOpen = (id) =>
|
||||
PanelChrome.prototype._otherCockpitLeftPanelOpen(id);
|
||||
panels['data-panel'] = panel('collapsed');
|
||||
assert.equal(otherOpen('utilities-panel'), false, 'Data Layers closed: restore');
|
||||
assert.equal(
|
||||
otherOpen('utilities-panel'),
|
||||
false,
|
||||
'Data Layers closed: restore',
|
||||
);
|
||||
panels['data-panel'] = panel('collapsed', 'layout-auto-collapsed');
|
||||
assert.equal(otherOpen('utilities-panel'), true, 'Data Layers comes back: keep Contact folded');
|
||||
assert.equal(
|
||||
otherOpen('utilities-panel'),
|
||||
true,
|
||||
'Data Layers comes back: keep Contact folded',
|
||||
);
|
||||
panels['data-panel'] = panel();
|
||||
assert.equal(otherOpen('utilities-panel'), true, 'Data Layers open: keep it folded');
|
||||
assert.equal(
|
||||
otherOpen('utilities-panel'),
|
||||
true,
|
||||
'Data Layers open: keep it folded',
|
||||
);
|
||||
} finally {
|
||||
globalThis.document = previous;
|
||||
}
|
||||
|
||||
@@ -27,9 +27,11 @@ export const COCKPIT_UTILITY_REC_GAP_PX = 12;
|
||||
|
||||
export const COCKPIT_UTILITY_SIGNAL_GAP_PX = 8;
|
||||
|
||||
export const COCKPIT_UTILITY_MIN_TOP_PX = 96;
|
||||
// Display and Radio start where the left rail does (max(206px, 22vh) in
|
||||
// cockpit-panels.css), so the two rails' tops line up.
|
||||
export const COCKPIT_UTILITY_MIN_TOP_PX = 206;
|
||||
|
||||
export const COCKPIT_UTILITY_MIN_TOP_RATIO = 0.12;
|
||||
export const COCKPIT_UTILITY_MIN_TOP_RATIO = 0.22;
|
||||
|
||||
export const COCKPIT_UTILITY_LAUNCHER_MIN_HEIGHT_PX = 50;
|
||||
|
||||
|
||||
@@ -450,3 +450,42 @@ test('the real layout pass drives the mounted controls: compact hides the collap
|
||||
assert.equal(cockpit.utilityLayout.top, '120.00px');
|
||||
assert.equal(strip.classList.contains('layout-primary-only'), false);
|
||||
});
|
||||
|
||||
test('the rail opens one panel at a time and reads as a disclosure', async (t) => {
|
||||
const { $, controls } = fixture(t);
|
||||
const item = (kind) =>
|
||||
document.querySelector(`[data-cockpit-rail-item="${kind}"]`);
|
||||
item('display').click();
|
||||
await nextTick();
|
||||
assert.equal($('cockpit-display-panel').hidden, false);
|
||||
assert.equal(item('display').getAttribute('aria-expanded'), 'true');
|
||||
assert.equal(
|
||||
item('display').getAttribute('aria-controls'),
|
||||
'cockpit-display-panel',
|
||||
);
|
||||
item('radio').click();
|
||||
await nextTick();
|
||||
assert.equal($('cockpit-display-panel').hidden, true);
|
||||
assert.equal($('cockpit-radio-panel').hidden, false);
|
||||
assert.equal(item('display').getAttribute('aria-expanded'), 'false');
|
||||
assert.equal(item('radio').getAttribute('aria-expanded'), 'true');
|
||||
item('radio').click();
|
||||
await nextTick();
|
||||
assert.equal(controls._cockpitDisclosureOpen('radio'), false);
|
||||
assert.equal(controls._cockpitDisclosureOpen('display'), false);
|
||||
});
|
||||
|
||||
test('collapsing an open panel hands focus to the rail when its toggle is hidden', async (t) => {
|
||||
const { $, controls } = fixture(t);
|
||||
const railItem = document.querySelector('[data-cockpit-rail-item="radio"]');
|
||||
// happy-dom lays nothing out: show the rail and hide the launcher's toggle
|
||||
// as the desktop look does.
|
||||
railItem.getClientRects = () => [{ width: 32, height: 32 }];
|
||||
$('cockpit-radio-toggle-btn').getClientRects = () => [];
|
||||
controls._setCockpitDisclosure('radio', true);
|
||||
await nextTick();
|
||||
document.querySelector('.cockpit-utility-collapse').click();
|
||||
await nextTick();
|
||||
assert.equal($('cockpit-radio-panel').hidden, true);
|
||||
assert.equal(document.activeElement, railItem);
|
||||
});
|
||||
+22
-4
@@ -27,10 +27,18 @@ export function bindRadioControls() {
|
||||
this.store?.publishUtility({ ...this._cockpitDisclosure });
|
||||
if (displayOpen) this.actions.revealStyleParameters();
|
||||
if (!expanded && returnFocus) {
|
||||
(kind === 'display'
|
||||
? this._cockpitDisplayToggleBtn
|
||||
: this._cockpitRadioToggleBtn
|
||||
)?.focus({ preventScroll: true });
|
||||
// The desktop cockpit hides the launchers' toggles behind its rail, so
|
||||
// focus goes to whichever of the two is shown.
|
||||
const toggle =
|
||||
kind === 'display'
|
||||
? this._cockpitDisplayToggleBtn
|
||||
: this._cockpitRadioToggleBtn;
|
||||
const railItem = toggle?.ownerDocument?.querySelector?.(
|
||||
`[data-cockpit-rail-item="${kind}"]`,
|
||||
);
|
||||
(toggle?.getClientRects?.().length ? toggle : railItem || toggle)?.focus({
|
||||
preventScroll: true,
|
||||
});
|
||||
}
|
||||
if (
|
||||
!displayOpen &&
|
||||
@@ -444,6 +452,16 @@ export function bindRadioControls() {
|
||||
),
|
||||
toggleCockpitRadio: () =>
|
||||
setCockpitDisclosure('radio', !this._cockpitDisclosureOpen('radio')),
|
||||
/**
|
||||
* Open or close the cockpit's Display or Radio panel; opening one closes
|
||||
* the other.
|
||||
* @param {'display'|'radio'} kind
|
||||
* @param {{ returnFocus?: boolean }} [options]
|
||||
*/
|
||||
toggleCockpitUtility: (kind, { returnFocus = false } = {}) => {
|
||||
const open = !this._cockpitDisclosureOpen(kind);
|
||||
setCockpitDisclosure(kind, open, { returnFocus: !open && returnFocus });
|
||||
},
|
||||
/**
|
||||
* @param {string} filter
|
||||
* @returns {string} The station tag the filter shows afterwards.
|
||||
|
||||
@@ -0,0 +1,693 @@
|
||||
/* ── Cockpit side windows in the app's panel look ───────────────────────
|
||||
Contact (left), the briefing carousel (right) and the Display / Radio
|
||||
controls get the same smoked glass, 16px corners and card rows as the
|
||||
main panels. They keep cockpit.css's slight 3D slant toward the centre,
|
||||
and stand the same height. The text glow goes; the state colours on the
|
||||
top edge (uncertain, lost) stay. */
|
||||
|
||||
.cockpit-context-window,
|
||||
.cockpit-signal-window {
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-top-color: rgba(255, 255, 255, 0.14);
|
||||
border-radius: 16px;
|
||||
background: rgba(12, 12, 20, 0.72);
|
||||
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);
|
||||
text-shadow: none;
|
||||
scrollbar-color: rgba(255, 255, 255, 0.18) transparent;
|
||||
}
|
||||
|
||||
/* Open, both windows share one compact size; their lists scroll. */
|
||||
.cockpit-context-window:not([data-collapsed='true']),
|
||||
.cockpit-signal-window:not([data-collapsed='true']) {
|
||||
width: min(280px, 23vw);
|
||||
height: min(27vh, 260px);
|
||||
}
|
||||
|
||||
/* Contact's header: the buttons share the kicker's row, so the subject runs
|
||||
the full width on at most two lines instead of wrapping beside them. */
|
||||
.cockpit-context-window:not([data-collapsed='true'])
|
||||
.cockpit-context-header
|
||||
> div {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.cockpit-context-window:not([data-collapsed='true']) .cockpit-context-kicker {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
.cockpit-context-window:not([data-collapsed='true']) .cockpit-context-actions {
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
.cockpit-context-window:not([data-collapsed='true']) #cockpit-context-subject {
|
||||
display: -webkit-box;
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 2;
|
||||
overflow: hidden;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
}
|
||||
|
||||
/* Their foot sits just above the bottom HUD readouts (MGRS / GSD), which
|
||||
stand at clamp(84px, 11vh, 118px) and are about 38px tall. */
|
||||
.cockpit-context-window,
|
||||
.cockpit-signal-window {
|
||||
bottom: calc(clamp(84px, 11vh, 118px) + 54px);
|
||||
}
|
||||
|
||||
.cockpit-context-window[data-state='uncertain'] {
|
||||
border-top-color: rgba(116, 219, 255, 0.72);
|
||||
}
|
||||
|
||||
.cockpit-context-window[data-state='lost'] {
|
||||
border-top-color: rgba(213, 168, 99, 0.72);
|
||||
}
|
||||
|
||||
.cockpit-context-window[data-collapsed='true'],
|
||||
.cockpit-signal-window[data-collapsed='true'] {
|
||||
border-radius: 12px;
|
||||
background: rgba(12, 12, 20, 0.66);
|
||||
}
|
||||
|
||||
/* Headers: a quiet hairline like the panel headers. */
|
||||
.cockpit-card-header {
|
||||
border-bottom-color: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.cockpit-card-actions button {
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 6px;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
color: rgba(224, 236, 242, 0.82);
|
||||
}
|
||||
|
||||
.cockpit-card-actions button:hover,
|
||||
.cockpit-card-actions button:focus-visible {
|
||||
border-color: rgba(0, 212, 255, 0.45);
|
||||
background: rgba(0, 212, 255, 0.1);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Rows and tiles inside: the card style used in Context and Visual. */
|
||||
.cockpit-context-cohorts > div,
|
||||
.cockpit-signal-list li,
|
||||
.cockpit-news-list li,
|
||||
.cockpit-local-grid > div {
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
border-radius: 10px;
|
||||
background: rgba(255, 255, 255, 0.035);
|
||||
}
|
||||
|
||||
/* The weather tiles stand apart as cards, with no lit grid behind them,
|
||||
and are short enough to fit the shorter window. */
|
||||
.cockpit-local-grid {
|
||||
gap: 5px;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.cockpit-local-grid > div {
|
||||
min-height: 0;
|
||||
gap: 2px;
|
||||
padding: 6px 9px;
|
||||
}
|
||||
|
||||
.cockpit-route-card {
|
||||
border-bottom-color: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
/* Display / Radio: the closed launchers and the open panels match too. */
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
.cockpit-utility-control.is-expanded {
|
||||
border-color: rgba(255, 255, 255, 0.08);
|
||||
border-radius: 16px;
|
||||
background: rgba(12, 12, 20, 0.72);
|
||||
box-shadow:
|
||||
0 8px 32px rgba(0, 0, 0, 0.42),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
/* ── Cockpit uses the main page's rails ───────────────────────────────
|
||||
The left rail shows Data Layers only (Authoring has no place in first
|
||||
person, Utilities has nothing to do there, and the cockpit's own Display
|
||||
panel stands in for Visual), one panel opens beside it, and the collapsed
|
||||
header strips the cockpit used instead are hidden. */
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
#left-panel-stack
|
||||
> .gev-panel-rail--left {
|
||||
display: block;
|
||||
}
|
||||
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
#left-panel-stack
|
||||
> .gev-panel-rail--left
|
||||
:is(
|
||||
[data-rail-item='scene-panel'],
|
||||
[data-rail-item='utilities-panel'],
|
||||
[data-rail-item='pp-toggles']
|
||||
),
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
#left-panel-stack
|
||||
> .gev-panel-rail--left
|
||||
.ui-tooltip:has(
|
||||
> :is(
|
||||
[data-rail-item='scene-panel'],
|
||||
[data-rail-item='utilities-panel'],
|
||||
[data-rail-item='pp-toggles']
|
||||
)
|
||||
) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* With some icons hidden, the runs between them break up: with a panel
|
||||
open, each icon left is its own island. */
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
#left-panel-stack
|
||||
> .gev-panel-rail--left
|
||||
.ui-rail--collapsible:not(.ui-rail--expanded):has(.ui-rail__item--on)
|
||||
> .ui-tooltip::before {
|
||||
inset: -4px -5px;
|
||||
border-width: 1px;
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
/* Authoring, Utilities or Visual may still be open underneath; its hidden icon must
|
||||
not split the rail into islands. */
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
#left-panel-stack
|
||||
> .gev-panel-rail--left
|
||||
.ui-rail:has(
|
||||
:is(
|
||||
[data-rail-item='scene-panel'],
|
||||
[data-rail-item='utilities-panel'],
|
||||
[data-rail-item='pp-toggles']
|
||||
)[aria-pressed='true']
|
||||
)
|
||||
> .ui-tooltip::before,
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
#left-panel-stack
|
||||
> .gev-panel-rail--left
|
||||
.ui-rail:has(
|
||||
:is(
|
||||
[data-rail-item='scene-panel'],
|
||||
[data-rail-item='utilities-panel'],
|
||||
[data-rail-item='pp-toggles']
|
||||
)[aria-pressed='true']
|
||||
)::before {
|
||||
/* Nor may the sliding highlight stay where the hidden icon was. */
|
||||
display: none;
|
||||
}
|
||||
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
#left-panel-stack
|
||||
> .gev-panel-rail--left
|
||||
.ui-rail:has(
|
||||
:is(
|
||||
[data-rail-item='scene-panel'],
|
||||
[data-rail-item='utilities-panel'],
|
||||
[data-rail-item='pp-toggles']
|
||||
)[aria-pressed='true']
|
||||
) {
|
||||
background: rgba(16, 20, 28, 0.82) !important;
|
||||
border-color: rgba(255, 255, 255, 0.14) !important;
|
||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.45) !important;
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
}
|
||||
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
#left-panel-stack
|
||||
> [data-panel-id].collapsed,
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
#left-panel-stack
|
||||
> :is(
|
||||
[data-panel-id='scene-panel'],
|
||||
[data-panel-id='utilities-panel'],
|
||||
[data-panel-id='pp-toggles']
|
||||
) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Both rails start at one height, just under the top HUD text, so their top
|
||||
edges line up; the cockpit layout pass places Display and Radio at the
|
||||
same height (COCKPIT_UTILITY_MIN_TOP_PX / _RATIO in cockpitPresentation.js). */
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
#left-panel-stack {
|
||||
top: max(206px, 22vh) !important;
|
||||
}
|
||||
|
||||
/* Bottom HUD readouts: scale from their foot, and drop the three-line right
|
||||
block (GSD / ALT / AIS) by one scaled line so both start level, as on the
|
||||
main page. */
|
||||
body.cockpit-mode #intel-hud .hud-bottom-left {
|
||||
transform-origin: left bottom;
|
||||
}
|
||||
|
||||
body.cockpit-mode #intel-hud .hud-bottom-right {
|
||||
bottom: calc(clamp(84px, 11vh, 118px) - 20px);
|
||||
transform-origin: right bottom;
|
||||
}
|
||||
|
||||
/* ── Live signals rows ──────────────────────────────────────────────────
|
||||
Callsign on the left, time small at the top right, the detail under it.
|
||||
The status colour is a slim inset bar (cyan for the aircraft you ride,
|
||||
white for nearby, amber for military / warnings), not a bent border. */
|
||||
.cockpit-signal-list {
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.cockpit-signal-list li {
|
||||
position: relative;
|
||||
display: block;
|
||||
padding: 5px 10px 5px 12px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
box-shadow: inset 2px 0 0 rgba(244, 251, 255, 0.22);
|
||||
}
|
||||
|
||||
.cockpit-signal-list li:is(.track, .current) {
|
||||
border-color: rgba(0, 212, 255, 0.22);
|
||||
background: rgba(0, 212, 255, 0.06);
|
||||
box-shadow: inset 2px 0 0 var(--cockpit-accent, #00d4ff);
|
||||
}
|
||||
|
||||
.cockpit-signal-list li:is(.nearby) {
|
||||
border-color: rgba(255, 255, 255, 0.06);
|
||||
box-shadow: inset 2px 0 0 rgba(244, 251, 255, 0.55);
|
||||
}
|
||||
|
||||
.cockpit-signal-list li:is(.military, .warning) {
|
||||
border-color: rgba(255, 255, 255, 0.06);
|
||||
box-shadow: inset 2px 0 0 #d5a863;
|
||||
}
|
||||
|
||||
.cockpit-signal-list li.actionable:is(:hover, :focus-within) {
|
||||
border-color: rgba(0, 212, 255, 0.3);
|
||||
background: rgba(0, 212, 255, 0.08);
|
||||
}
|
||||
|
||||
.cockpit-signal-list time {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
inset-inline-end: 28px;
|
||||
color: rgba(224, 236, 242, 0.4);
|
||||
font: 7px/1 var(--font-mono);
|
||||
letter-spacing: 0.06em;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.cockpit-signal-list li > div {
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
.cockpit-signal-target {
|
||||
grid-template-columns: minmax(0, 1fr) 14px;
|
||||
gap: 8px;
|
||||
font-size: 10.5px;
|
||||
letter-spacing: 0.1em;
|
||||
}
|
||||
|
||||
.cockpit-signal-target-rule {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.cockpit-signal-target-chevron {
|
||||
justify-self: end;
|
||||
color: rgba(224, 236, 242, 0.5) !important;
|
||||
}
|
||||
|
||||
.cockpit-signal-list
|
||||
li.actionable:is(:hover, :focus-within)
|
||||
.cockpit-signal-target-chevron {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.cockpit-signal-list span:not([class]) {
|
||||
color: rgba(224, 236, 242, 0.62);
|
||||
font-size: 8px;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
/* Keep a long callsign clear of the time at the row's end. */
|
||||
.cockpit-signal-list .cockpit-signal-target-label {
|
||||
margin-inline-end: 50px;
|
||||
}
|
||||
|
||||
/* ── Briefing content fits the narrower window ────────────────────────── */
|
||||
.cockpit-signal-window {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.cockpit-signal-window > *,
|
||||
.cockpit-brief-stage,
|
||||
.cockpit-brief-stage > section,
|
||||
.cockpit-signal-list,
|
||||
.cockpit-signal-list li {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Route: a quiet heading, endpoints that truncate. */
|
||||
.cockpit-route-card {
|
||||
padding-inline: 4px;
|
||||
}
|
||||
|
||||
.cockpit-route-heading {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
#cockpit-route-status {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cockpit-route-airport strong {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Header: the title on one line, the controls on the row under it. */
|
||||
.cockpit-signal-window:not([data-collapsed='true']) .cockpit-signal-header {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: 6px;
|
||||
padding-inline: 4px;
|
||||
}
|
||||
|
||||
.cockpit-signal-window:not([data-collapsed='true']) .cockpit-brief-heading {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
gap: 2px 8px;
|
||||
}
|
||||
|
||||
.cockpit-signal-window:not([data-collapsed='true']) .cockpit-signal-kicker {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cockpit-signal-window:not([data-collapsed='true']) .cockpit-brief-actions {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.cockpit-signal-window:not([data-collapsed='true']) #cockpit-signal-toggle {
|
||||
margin-inline-start: auto;
|
||||
}
|
||||
|
||||
/* ── Open Display / Radio panels: the main panels' header and size ───── */
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
.cockpit-utility-control.is-expanded {
|
||||
min-width: 295px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
.cockpit-utility-control.is-expanded
|
||||
.cockpit-utility-launcher {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 4px 4px 10px 8px;
|
||||
color: rgba(224, 236, 242, 0.78);
|
||||
font: 9px/1 var(--font-mono);
|
||||
letter-spacing: 0.22em;
|
||||
}
|
||||
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
.cockpit-utility-control.is-expanded
|
||||
.cockpit-utility-divider {
|
||||
flex: 0 1 22px;
|
||||
height: 1px;
|
||||
margin-right: auto;
|
||||
background: rgba(255, 255, 255, 0.18);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* The launcher's own toggle steps aside: the rail and "−" open and close
|
||||
the panel instead. */
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
.cockpit-utility-control.is-expanded
|
||||
.cockpit-utility-glyph {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
.cockpit-utility-collapse {
|
||||
display: grid;
|
||||
flex: none;
|
||||
place-items: center;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
padding: 0;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
border-radius: 6px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
color: rgba(206, 232, 239, 0.75);
|
||||
font: 13px/1 var(--font-mono);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
.cockpit-utility-collapse:hover,
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
.cockpit-utility-collapse:focus-visible {
|
||||
border-color: rgba(0, 212, 255, 0.45);
|
||||
color: #fff;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* ── Right rail as on the left: an icon column that stays, labels beside it
|
||||
while nothing is open, and the open panel to its left. The launchers'
|
||||
own strips give way to it. ─────────────────────────────────────────── */
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
.cockpit-utility-controls {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
align-items: start;
|
||||
justify-content: end;
|
||||
column-gap: 6px;
|
||||
}
|
||||
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
.cockpit-utility-control:not(.is-expanded) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
.cockpit-utility-control.is-expanded {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
/* Cyber and the phone keep the launchers' own toggles, so the rail and "−"
|
||||
are for the desktop look only. */
|
||||
.cockpit-utility-rail,
|
||||
.cockpit-utility-collapse {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
.cockpit-utility-rail {
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: stretch;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
:is(.cockpit-utility-rail__labels, .cockpit-utility-rail__icons) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 4px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||
border-radius: 14px;
|
||||
background: rgba(16, 20, 28, 0.82);
|
||||
box-shadow:
|
||||
0 6px 20px rgba(0, 0, 0, 0.45),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.05);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
}
|
||||
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
.cockpit-utility-rail__labels {
|
||||
box-sizing: border-box;
|
||||
width: 170px;
|
||||
padding-inline: 14px;
|
||||
}
|
||||
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
.cockpit-utility-rail__labels
|
||||
> span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 32px;
|
||||
color: rgba(225, 236, 242, 0.82);
|
||||
font:
|
||||
500 9.28px/1 Inter,
|
||||
-apple-system,
|
||||
system-ui,
|
||||
sans-serif;
|
||||
letter-spacing: 2.08px;
|
||||
}
|
||||
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
.cockpit-utility-rail.is-open
|
||||
.cockpit-utility-rail__labels {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
.cockpit-utility-rail__item {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 10px;
|
||||
background: transparent;
|
||||
color: var(--text-secondary, rgba(206, 232, 239, 0.75));
|
||||
cursor: pointer;
|
||||
transition:
|
||||
color var(--transition-fast, 120ms),
|
||||
background var(--transition-fast, 120ms),
|
||||
border-color var(--transition-fast, 120ms);
|
||||
}
|
||||
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
.cockpit-utility-rail__item
|
||||
.material-symbols-outlined {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
.cockpit-utility-rail__item:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: var(--text-primary, #fff);
|
||||
}
|
||||
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
.cockpit-utility-rail__item.is-on,
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
.cockpit-utility-rail__item.is-on:hover {
|
||||
border-color: rgba(0, 212, 255, 0.15);
|
||||
background: rgba(0, 212, 255, 0.15);
|
||||
color: #00d4ff;
|
||||
}
|
||||
|
||||
/* With a panel open, each icon is its own island, as on the main rails. */
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
.cockpit-utility-rail.is-open
|
||||
.cockpit-utility-rail__icons {
|
||||
border-color: transparent;
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
}
|
||||
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
.cockpit-utility-rail.is-open
|
||||
.cockpit-utility-rail__item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
.cockpit-utility-rail.is-open
|
||||
.cockpit-utility-rail__item::before {
|
||||
position: absolute;
|
||||
inset: -5px;
|
||||
z-index: -1;
|
||||
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||
border-radius: 14px;
|
||||
background: rgba(16, 20, 28, 0.82);
|
||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.45);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
content: '';
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Hovering an icon lights its label too, as on the left. */
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
.cockpit-utility-rail:has(.cockpit-utility-rail__item:nth-child(1):hover)
|
||||
.cockpit-utility-rail__labels
|
||||
> span:nth-child(1),
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
.cockpit-utility-rail:has(.cockpit-utility-rail__item:nth-child(2):hover)
|
||||
.cockpit-utility-rail__labels
|
||||
> span:nth-child(2) {
|
||||
color: var(--text-primary, #fff);
|
||||
}
|
||||
|
||||
/* Mirror the left rail's 52px inset, clear of the HUD's vertical readouts. */
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
.cockpit-utility-controls {
|
||||
right: 52px;
|
||||
}
|
||||
|
||||
/* Like the cockpit's other windows (rtl.css), the rail keeps its physical
|
||||
side in a right-to-left language: icons at the screen edge, the open panel
|
||||
inside them. Only the text reads right to left. */
|
||||
html[dir='rtl']:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
.cockpit-utility-controls {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
html[dir='rtl']:not([data-ui-theme='cyber'])
|
||||
body.cockpit-mode:not(.mobile-shell)
|
||||
:is(.cockpit-utility-control.is-expanded, .cockpit-utility-rail__labels) {
|
||||
direction: rtl;
|
||||
}
|
||||
@@ -9,10 +9,12 @@ import { useCockpitStore } from '../../stores/cockpit.js';
|
||||
import { useDisplayStore } from '../../stores/display.js';
|
||||
import UiButton from '../../kit/UiButton.vue';
|
||||
|
||||
// The Cockpit's Display and Radio launchers and their popovers. RadioControls
|
||||
// presents which popover is open and the compact Radio controls; until it
|
||||
// does, both are closed and the controls show their initial state. The
|
||||
// controls call RadioControls through the store. The Display popover's slots
|
||||
// The Cockpit's Display and Radio launchers and their popovers, and the
|
||||
// desktop look's rail, whose icons stay at the edge and open one panel at a
|
||||
// time as the main page's rails do. RadioControls presents which popover is
|
||||
// open and the compact Radio controls; until it does, both are closed and the
|
||||
// controls show their initial state. The controls call RadioControls through
|
||||
// the store. The Display popover's slots
|
||||
// are static: the Display panel teleports its real controls into them while
|
||||
// the Cockpit display portal publishes them.
|
||||
const store = useRadioStore();
|
||||
@@ -52,6 +54,7 @@ const radioLabel = computed(() =>
|
||||
<div class="cockpit-utility-launcher" data-cockpit-launcher="display">
|
||||
<span>{{ t('DISPLAY') }}</span>
|
||||
<span class="cockpit-utility-divider" aria-hidden="true"></span>
|
||||
<button v-if="open.display" type="button" class="cockpit-utility-collapse" :aria-label="t('Collapse panel')" :title="t('Collapse panel')" @click="act('toggleCockpitUtility', 'display', { returnFocus: true })">−</button>
|
||||
<button id="cockpit-display-toggle-btn" class="cockpit-utility-glyph" type="button" :aria-expanded="String(open.display)" aria-controls="cockpit-display-panel" :aria-label="displayLabel" :title="displayLabel">{{ open.display ? '▶' : '◀' }}</button>
|
||||
</div>
|
||||
<div id="cockpit-display-panel" class="cockpit-utility-popover ui-scroll" v-class-toggle="{ 'uses-shared-display-controls': !!cockpitPortal }" role="group" :aria-label="t('Cockpit display options')" :hidden="!open.display">
|
||||
@@ -65,6 +68,7 @@ const radioLabel = computed(() =>
|
||||
<div class="cockpit-utility-launcher" data-cockpit-launcher="radio">
|
||||
<span>{{ t('RADIO') }}</span>
|
||||
<span class="cockpit-utility-divider" aria-hidden="true"></span>
|
||||
<button v-if="open.radio" type="button" class="cockpit-utility-collapse" :aria-label="t('Collapse panel')" :title="t('Collapse panel')" @click="act('toggleCockpitUtility', 'radio', { returnFocus: true })">−</button>
|
||||
<button id="cockpit-radio-toggle-btn" @click="act('toggleCockpitRadio')" class="cockpit-utility-glyph" type="button" :aria-expanded="String(open.radio)" aria-controls="cockpit-radio-panel" :aria-label="radioLabel" :title="radioLabel">{{ open.radio ? '▶' : '◀' }}</button>
|
||||
</div>
|
||||
<div id="cockpit-radio-panel" class="cockpit-utility-popover cockpit-radio-compact" role="group" :aria-label="t('Cockpit compact Radio controls')" :hidden="!open.radio">
|
||||
@@ -82,5 +86,15 @@ const radioLabel = computed(() =>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cockpit-utility-rail" :class="{ 'is-open': open.display || open.radio }">
|
||||
<span class="cockpit-utility-rail__labels" aria-hidden="true">
|
||||
<span>{{ t('DISPLAY') }}</span>
|
||||
<span>{{ t('RADIO') }}</span>
|
||||
</span>
|
||||
<span class="cockpit-utility-rail__icons">
|
||||
<button type="button" class="cockpit-utility-rail__item" data-cockpit-rail-item="display" :class="{ 'is-on': open.display }" :aria-expanded="String(open.display)" aria-controls="cockpit-display-panel" :aria-label="displayLabel" :title="displayLabel" @click="act('toggleCockpitUtility', 'display')"><span class="material-symbols-outlined" aria-hidden="true">tune</span></button>
|
||||
<button type="button" class="cockpit-utility-rail__item" data-cockpit-rail-item="radio" :class="{ 'is-on': open.radio }" :aria-expanded="String(open.radio)" aria-controls="cockpit-radio-panel" :aria-label="radioLabel" :title="radioLabel" @click="act('toggleCockpitUtility', 'radio')"><span class="material-symbols-outlined" aria-hidden="true">radio</span></button>
|
||||
</span>
|
||||
</div>
|
||||
</aside>
|
||||
</template>
|
||||
@@ -50,6 +50,7 @@
|
||||
@import './src/ui/styles/left-tools.css';
|
||||
@import './src/ui/styles/panel-edge.css';
|
||||
@import './src/ui/styles/visual-panel.css';
|
||||
@import './src/ui/styles/cockpit-panels.css';
|
||||
@import './src/ui/styles/ai-chat.css';
|
||||
@import './src/ui/styles/scope-off-shadows.css';
|
||||
@import './src/ui/styles/cyber.css';
|
||||
Reference in new issue
Block a user