Restyle the cockpit side panels

The cockpit's Display and Radio panels get the compact dark styling of the
rest of the shell, a header with the edge switch and collapse, and a rail.

Co-authored-by: Sameh Khamis <[email protected]>
This commit is contained in:
Manjunath PaparajuandSameh Khamis authored and Sameh Khamis committed 2026-11-14 15:51:13 -08:00
1 parent cd785b1cec
commit 733b227c01
5 files changed
+699 -10

No files matched your search

+1 -1
View File
@@ -1275,7 +1275,7 @@ test('cockpit briefing cycle control keeps its state as the accessible name', ()
assert.match(match[0], />CYCLE OFF<\/button>/);
assert.match(
match[0],
/title="Cycle briefing pages automatically every 9 seconds \(Signals → News → Local\)\./,
/title="Cycle briefing pages automatically every 9 seconds \(Signals → Local\)\./,
);
// The presented label is the state; the help is only the tooltip.
+1 -7
View File
@@ -31,12 +31,6 @@ export const COCKPIT_BRIEF_PAGES = [
subtitleKey: msg('OBSERVED / MAPPED PINGS'),
sourceKey: msg('SOURCE-BACKED EVENTS · NO SYNTHETIC NEWS'),
},
{
id: 'news',
kickerKey: msg('REGIONAL NEWS'),
subtitleKey: msg('LATEST LOCATION-MATCHED REPORTING'),
// The news footer comes from the news state (`news.footer`), not here.
},
{
id: 'local',
kickerKey: msg('LOCAL INFO'),
@@ -52,7 +46,7 @@ export const CYCLE_HELP_KEYS = Object.freeze({
'Stop automatic page cycling. Previous, Next, and the SIG/NEWS/LOCAL tabs stay available.',
),
off: msg(
'Cycle briefing pages automatically every 9 seconds (Signals → News → Local). Pauses while you hover or focus the panel. Live signal data refreshes continuously either way.',
'Cycle briefing pages automatically every 9 seconds (Signals → Local). Pauses while you hover or focus the panel. Live signal data refreshes continuously either way.',
),
});
+668
View File
@@ -0,0 +1,668 @@
/* ── 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(34vh, 330px);
}
/* 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);
}
.cockpit-route-card {
border-bottom-color: rgba(255, 255, 255, 0.08);
}
/* Display / Radio: the closed launchers and the open panels match too. */
body.cockpit-mode .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, Utilities; Authoring has no place in
first person, and the cockpit has its own Display control on the right in
place of Visual), one panel opens beside it, and the collapsed header
strips the cockpit used instead are hidden. */
body.cockpit-mode #left-panel-stack > .gev-panel-rail--left {
display: block;
}
body.cockpit-mode #left-panel-stack > .gev-panel-rail--left :is([data-rail-item='scene-panel'], [data-rail-item='pp-toggles']) {
display: none;
}
body.cockpit-mode #left-panel-stack > [data-panel-id].collapsed {
display: none;
}
/* Display and Radio as the main page's right rail: a column of labels and a
column of icon buttons, each reading as one glass strip. */
body.cockpit-mode .cockpit-utility-controls {
gap: 0;
}
body.cockpit-mode .cockpit-utility-control:not(.is-expanded) .cockpit-utility-launcher {
display: flex;
justify-content: flex-end;
align-items: stretch;
gap: 6px;
width: auto;
min-height: 0;
padding: 0;
border: 0;
border-radius: 0;
background: transparent;
box-shadow: none;
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
body.cockpit-mode .cockpit-utility-control:not(.is-expanded) .cockpit-utility-launcher > span:first-child,
body.cockpit-mode .cockpit-utility-control:not(.is-expanded) .cockpit-utility-glyph {
display: flex;
align-items: center;
box-sizing: border-box;
height: 40px;
border: 1px solid rgba(255, 255, 255, 0.08);
border-block-width: 0;
background: rgba(12, 12, 20, 0.72);
backdrop-filter: blur(24px) saturate(1.4);
-webkit-backdrop-filter: blur(24px) saturate(1.4);
}
body.cockpit-mode .cockpit-utility-control:not(.is-expanded) .cockpit-utility-launcher > span:first-child {
width: 120px;
padding: 0 14px;
color: rgba(224, 236, 242, 0.72);
font: 9px/1 var(--font-mono);
letter-spacing: 0.16em;
}
body.cockpit-mode .cockpit-utility-control:not(.is-expanded) .cockpit-utility-divider {
display: none;
}
body.cockpit-mode .cockpit-utility-control:not(.is-expanded) .cockpit-utility-glyph {
justify-content: center;
width: 42px;
padding: 0;
border-radius: 0;
color: rgba(224, 236, 242, 0.82);
font-size: 0;
cursor: pointer;
}
body.cockpit-mode .cockpit-utility-glyph::before {
font-family: 'Material Symbols Outlined';
font-size: 18px;
font-weight: 400;
line-height: 1;
letter-spacing: normal;
-webkit-font-feature-settings: 'liga';
font-feature-settings: 'liga';
}
body.cockpit-mode .cockpit-utility-control.is-expanded .cockpit-utility-glyph {
font-size: 0;
}
body.cockpit-mode #cockpit-display-toggle-btn::before {
content: 'tune';
}
body.cockpit-mode #cockpit-radio-toggle-btn::before {
content: 'radio';
}
body.cockpit-mode .cockpit-utility-control:not(.is-expanded) .cockpit-utility-glyph:hover {
color: #fff;
background: rgba(0, 212, 255, 0.12);
}
/* Round the ends of each strip and draw its top / bottom edge. */
body.cockpit-mode .cockpit-utility-control:first-child:not(.is-expanded) :is(.cockpit-utility-launcher > span:first-child, .cockpit-utility-glyph) {
padding-top: 4px;
border-top-width: 1px;
border-radius: 12px 12px 0 0;
}
body.cockpit-mode .cockpit-utility-control:last-child:not(.is-expanded) :is(.cockpit-utility-launcher > span:first-child, .cockpit-utility-glyph) {
padding-bottom: 4px;
border-bottom-width: 1px;
border-radius: 0 0 12px 12px;
}
/* A strip left alone beside an open panel is rounded all round. */
body.cockpit-mode .cockpit-utility-control.is-expanded + .cockpit-utility-control:not(.is-expanded) :is(.cockpit-utility-launcher > span:first-child, .cockpit-utility-glyph),
body.cockpit-mode .cockpit-utility-control:not(.is-expanded):has(+ .cockpit-utility-control.is-expanded) :is(.cockpit-utility-launcher > span:first-child, .cockpit-utility-glyph) {
padding-block: 4px;
border-block-width: 1px;
border-radius: 12px;
}
body.cockpit-mode .cockpit-utility-control.is-expanded + .cockpit-utility-control:not(.is-expanded) {
margin-top: 7px;
}
/* Map credits: one quiet centred line at the very bottom, under Reset /
Exit Cockpit, clear of the HUD readouts in the corners. */
body.cockpit-mode #cesium-credits {
top: auto !important;
right: auto !important;
bottom: 8px !important;
left: 50% !important;
max-width: calc(100vw - 2rem) !important;
padding: 0.2rem 0.75rem !important;
border-radius: 999px !important;
transform: translateX(-50%);
white-space: nowrap;
}
body.cockpit-mode #cesium-credits :is(.cesium-widget-credits, .cesium-credit-textContainer) {
display: inline-flex !important;
flex-wrap: nowrap !important;
align-items: center;
gap: 0.4rem;
white-space: nowrap;
}
body.cockpit-mode #cesium-credits img {
height: 13px;
}
/* Both rails start at one height, just under the top HUD text, so their top
edges line up. */
body.cockpit-mode #left-panel-stack,
body.cockpit-mode #cockpit-utility-controls {
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: 5px;
}
.cockpit-signal-list li {
position: relative;
display: block;
padding: 8px 12px 8px 14px;
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: 9px;
right: 30px;
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: 3px;
}
.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 top right. */
.cockpit-signal-list .cockpit-signal-target-label {
margin-right: 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;
}
/* The Display / Radio strips carry the left rail's exact frame: border,
glass, corners and drop shadow. */
body.cockpit-mode .cockpit-utility-control:not(.is-expanded) .cockpit-utility-launcher > span:first-child,
body.cockpit-mode .cockpit-utility-control:not(.is-expanded) .cockpit-utility-glyph {
border-color: rgba(255, 255, 255, 0.14);
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);
}
body.cockpit-mode .cockpit-utility-control:first-child:not(.is-expanded) :is(.cockpit-utility-launcher > span:first-child, .cockpit-utility-glyph) {
border-radius: 14px 14px 0 0;
box-shadow:
0 6px 20px rgba(0, 0, 0, 0.45),
inset 0 1px 0 rgba(255, 255, 255, 0.05);
}
body.cockpit-mode .cockpit-utility-control:last-child:not(.is-expanded) :is(.cockpit-utility-launcher > span:first-child, .cockpit-utility-glyph) {
border-radius: 0 0 14px 14px;
}
body.cockpit-mode .cockpit-utility-control.is-expanded + .cockpit-utility-control:not(.is-expanded) :is(.cockpit-utility-launcher > span:first-child, .cockpit-utility-glyph),
body.cockpit-mode .cockpit-utility-control:not(.is-expanded):has(+ .cockpit-utility-control.is-expanded) :is(.cockpit-utility-launcher > span:first-child, .cockpit-utility-glyph) {
border-radius: 14px;
}
/* ── Open Display / Radio panels: the main panels' header and size ───── */
body.cockpit-mode .cockpit-utility-control.is-expanded {
min-width: 295px;
box-sizing: border-box;
}
body.cockpit-mode .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;
}
body.cockpit-mode .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; "−" collapses instead. */
body.cockpit-mode .cockpit-utility-control.is-expanded .cockpit-utility-glyph {
display: none;
}
body.cockpit-mode .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;
}
body.cockpit-mode .cockpit-utility-collapse:hover,
body.cockpit-mode .cockpit-utility-collapse:focus-visible {
border-color: rgba(0, 212, 255, 0.45);
color: #fff;
outline: none;
}
/* Align-to-edge keeps the rail's inset (mirroring the left rail); docked, the
open panel takes the rail's place and the icons go above it (below). */
/* Same measurements as the left rail: a 170px label strip, 4px gap, 42px
icon strip, 38px rows. */
body.cockpit-mode .cockpit-utility-control:not(.is-expanded) .cockpit-utility-launcher {
gap: 4px;
}
body.cockpit-mode .cockpit-utility-control:not(.is-expanded) .cockpit-utility-launcher > span:first-child {
width: 170px;
}
body.cockpit-mode .cockpit-utility-control:not(.is-expanded) .cockpit-utility-launcher > span:first-child,
body.cockpit-mode .cockpit-utility-control:not(.is-expanded) .cockpit-utility-glyph {
height: 38px;
}
/* Hover as on the left rail: a 32px rounded square lights behind the icon
and the icon and its label brighten; the strip itself stays put. */
body.cockpit-mode .cockpit-utility-control:not(.is-expanded) .cockpit-utility-glyph::before {
display: grid;
place-items: center;
width: 32px;
height: 32px;
border: 1px solid transparent;
border-radius: var(--btn-radius, 8px);
box-sizing: border-box;
transition:
color var(--transition-fast, 120ms),
background var(--transition-fast, 120ms);
}
body.cockpit-mode .cockpit-utility-control:not(.is-expanded) .cockpit-utility-glyph:hover {
background: rgba(16, 20, 28, 0.82);
color: var(--text-primary, #fff);
}
body.cockpit-mode .cockpit-utility-control:not(.is-expanded) .cockpit-utility-glyph:hover::before {
background: rgba(255, 255, 255, 0.05);
}
body.cockpit-mode .cockpit-utility-control:not(.is-expanded) .cockpit-utility-launcher:has(.cockpit-utility-glyph:hover) > span:first-child {
color: var(--text-primary, #fff);
}
/* ── 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. ─────────────────────────────────────────── */
body.cockpit-mode .cockpit-utility-controls {
display: grid;
grid-template-columns: auto auto;
align-items: start;
justify-content: end;
column-gap: 6px;
}
body.cockpit-mode .cockpit-utility-control:not(.is-expanded) {
display: none;
}
body.cockpit-mode .cockpit-utility-control.is-expanded {
grid-column: 1;
grid-row: 1;
margin-top: 0 !important;
}
.cockpit-utility-rail {
display: none;
}
body.cockpit-mode .cockpit-utility-rail {
grid-column: 2;
grid-row: 1;
display: flex;
gap: 4px;
align-items: stretch;
pointer-events: auto;
}
body.cockpit-mode :is(.cockpit-utility-rail__labels, .cockpit-utility-rail__icons) {
display: flex;
flex-direction: column;
gap: 2px;
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);
}
body.cockpit-mode .cockpit-utility-rail__labels {
box-sizing: border-box;
width: 170px;
padding-inline: 14px;
}
body.cockpit-mode .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;
}
body.cockpit-mode .cockpit-utility-rail.is-open .cockpit-utility-rail__labels {
display: none;
}
body.cockpit-mode .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);
}
body.cockpit-mode .cockpit-utility-rail__item .material-symbols-outlined {
font-size: 18px;
}
body.cockpit-mode .cockpit-utility-rail__item:hover {
background: rgba(255, 255, 255, 0.05);
color: var(--text-primary, #fff);
}
body.cockpit-mode .cockpit-utility-rail__item.is-on,
body.cockpit-mode .cockpit-utility-rail__item.is-on:hover {
border-color: rgba(0, 212, 255, 0.15);
background: rgba(0, 212, 255, 0.15);
color: #00d4ff;
}
/* Hovering an icon lights its label too, as on the left. */
body.cockpit-mode .cockpit-utility-rail:has(.cockpit-utility-rail__item:nth-child(1):hover) .cockpit-utility-rail__labels > span:nth-child(1),
body.cockpit-mode .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);
}
/* Docked (align-to-edge) with a panel open: as on the main page, the panel
takes the edge and the rail's icons sit in a row above it. */
body.cockpit-mode.panels-edge-right .cockpit-utility-controls:has(.cockpit-utility-control.is-expanded) {
grid-template-columns: auto;
justify-items: end;
row-gap: 8px;
}
body.cockpit-mode.panels-edge-right .cockpit-utility-controls:has(.cockpit-utility-control.is-expanded) .cockpit-utility-rail {
grid-column: 1;
grid-row: 1;
}
body.cockpit-mode.panels-edge-right .cockpit-utility-controls:has(.cockpit-utility-control.is-expanded) .cockpit-utility-control.is-expanded {
grid-column: 1;
grid-row: 2;
}
body.cockpit-mode.panels-edge-right .cockpit-utility-controls:has(.cockpit-utility-control.is-expanded) .cockpit-utility-rail__icons {
flex-direction: row;
}
/* Mirror the left rail's 52px inset, clear of the HUD's vertical readouts. */
body.cockpit-mode .cockpit-utility-controls {
right: 52px;
}
+1 -2
View File
@@ -84,8 +84,7 @@ function leaveSignals() {
<span id="cockpit-brief-source">{{ b.source }}</span>
<nav class="cockpit-brief-tabs" :aria-label="t('Cockpit briefing pages')">
<button type="button" data-cockpit-brief-index="0" :aria-label="t('Show Live Signals')" :aria-current="String(b.pageIndex === 0)">{{ t('SIG') }}</button>
<button type="button" data-cockpit-brief-index="1" :aria-label="t('Show Regional News')" :aria-current="brief ? String(b.pageIndex === 1) : undefined">{{ t('NEWS') }}</button>
<button type="button" data-cockpit-brief-index="2" :aria-label="t('Show Local Info')" :aria-current="brief ? String(b.pageIndex === 2) : undefined">{{ t('LOCAL') }}</button>
<button type="button" data-cockpit-brief-index="1" :aria-label="t('Show Local Info')" :aria-current="brief ? String(b.pageIndex === 1) : undefined">{{ t('LOCAL') }}</button>
</nav>
</footer>
</aside>
@@ -8,6 +8,20 @@ import { useRadioStore } from '../../stores/radio.js';
import { useCockpitStore } from '../../stores/cockpit.js';
import { useDisplayStore } from '../../stores/display.js';
import UiButton from '../../kit/UiButton.vue';
import UiPanelEdge from '../../kit/UiPanelEdge.vue';
// The open panel's header carries the same edge switch and collapse button as
// the main panels; collapsing goes through each launcher's own toggle.
function collapse(which) {
if (which === 'radio') act('toggleCockpitRadio');
else document.getElementById('cockpit-display-toggle-btn')?.click();
}
// The rail opens one panel at a time, as the main page's rails do.
function railToggle(which) {
const other = which === 'radio' ? 'display' : 'radio';
if (open.value[other]) collapse(other);
collapse(which);
}
// The Cockpit's Display and Radio launchers and their popovers. RadioControls
// presents which popover is open and the compact Radio controls; until it
@@ -52,6 +66,8 @@ 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>
<UiPanelEdge v-if="open.display" />
<button v-if="open.display" type="button" class="cockpit-utility-collapse" :aria-label="t('Collapse panel')" :title="t('Collapse panel')" @click="collapse('display')">−</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 +81,8 @@ 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>
<UiPanelEdge v-if="open.radio" />
<button v-if="open.radio" type="button" class="cockpit-utility-collapse" :aria-label="t('Collapse panel')" :title="t('Collapse panel')" @click="collapse('radio')">−</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 +100,15 @@ const radioLabel = computed(() =>
</label>
</div>
</div>
<nav class="cockpit-utility-rail" :class="{ 'is-open': open.display || open.radio }" :aria-label="t('Cockpit display and Radio controls')">
<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" :class="{ 'is-on': open.display }" :aria-pressed="String(open.display)" :aria-label="displayLabel" :title="displayLabel" @click="railToggle('display')"><span class="material-symbols-outlined" aria-hidden="true">tune</span></button>
<button type="button" class="cockpit-utility-rail__item" :class="{ 'is-on': open.radio }" :aria-pressed="String(open.radio)" :aria-label="radioLabel" :title="radioLabel" @click="railToggle('radio')"><span class="material-symbols-outlined" aria-hidden="true">radio</span></button>
</span>
</nav>
</aside>
</template>