mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
merge main 07094d9 into feat/voice-outlines
This commit is contained in:
commit
c5aa0d28c3
593 files changed
+42386
-19629
No files matched your search
@@ -0,0 +1,25 @@
|
||||
/* ── Screen-space annotations (src/annotations/screenAnnotationRenderer.js) ── */
|
||||
.gev-screen-whiteboard { position: fixed; inset: 0; pointer-events: none; z-index: 90; }
|
||||
.gev-screen-whiteboard-svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
|
||||
/* No CSS opacity transition here on purpose: group opacity is driven per-frame in JS
|
||||
(the 260ms fade-in via computeAlpha, and the tracked-entity z-order ease). A CSS
|
||||
transition fought those per-frame writes and made the tracked-fade opacity oscillate
|
||||
(flicker). Fade-OUT keeps its own transition via .gev-anno.gev-out below. */
|
||||
.gev-anno-ring { filter: drop-shadow(0 0 6px currentColor); }
|
||||
.gev-anno-dot { filter: drop-shadow(0 0 5px rgba(255,255,255,0.6)); }
|
||||
.gev-anno-area { filter: drop-shadow(0 0 5px currentColor); }
|
||||
.gev-anno-arrow { filter: drop-shadow(0 0 4px currentColor); stroke-linecap: round; }
|
||||
.gev-anno-leader { stroke-dasharray: 2 3; }
|
||||
.gev-anno-card { fill: rgba(8,18,28,0.78); stroke: rgba(255,255,255,0.14); stroke-width: 1; }
|
||||
.gev-anno-text { fill: #eaf6ff; font: 600 13px "JetBrains Mono", ui-monospace, monospace; letter-spacing: 0.02em; }
|
||||
/* draw-on: outlined shapes reveal their stroke */
|
||||
.gev-draw { stroke-dasharray: 1400; stroke-dashoffset: 1400; }
|
||||
.gev-anno.gev-in .gev-draw { transition: stroke-dashoffset 900ms ease-out; stroke-dashoffset: 0; }
|
||||
/* opacity-only fade-in: a CSS transform here would override the SVG transform
|
||||
attribute used to POSITION the callout and snap it to 0,0. */
|
||||
.gev-anno-callout { opacity: 0; }
|
||||
.gev-anno.gev-in .gev-anno-callout { animation: gev-pop 320ms ease-out both; }
|
||||
.gev-anno.gev-out { opacity: 0 !important; transition: opacity 320ms ease; }
|
||||
.gev-pulse { animation: gev-ring-pulse 1.8s ease-in-out infinite; transform-box: fill-box; transform-origin: center; }
|
||||
@keyframes gev-ring-pulse { 0%,100% { opacity: 0.9; stroke-width: 2; } 50% { opacity: 0.35; stroke-width: 3.5; } }
|
||||
@keyframes gev-pop { from { opacity: 0; } to { opacity: 1; } }
|
||||
@@ -106,8 +106,6 @@
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
background: #03080d;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(107, 232, 255, 0.36) transparent;
|
||||
}
|
||||
|
||||
.bhote-embedded-callout.is-youtube {
|
||||
|
||||
@@ -35,8 +35,6 @@
|
||||
gap: 8px;
|
||||
max-height: calc(100vh - 24px);
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(0, 212, 255, 0.46) rgba(255, 255, 255, 0.06);
|
||||
backdrop-filter: blur(24px) saturate(1.4);
|
||||
-webkit-backdrop-filter: blur(24px) saturate(1.4);
|
||||
box-shadow:
|
||||
@@ -45,25 +43,6 @@
|
||||
0 1px 0 rgba(255, 255, 255, 0.06) inset;
|
||||
}
|
||||
|
||||
.cctv-panel-inner::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.cctv-panel-inner::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.cctv-panel-inner::-webkit-scrollbar-thumb {
|
||||
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;
|
||||
}
|
||||
|
||||
.cctv-controls {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
+43
-58
@@ -969,23 +969,50 @@ body.cockpit-mode #view-switcher button {
|
||||
.cockpit-context-window[data-state='lost'] {
|
||||
border-top-color: rgba(213, 168, 99, 0.72);
|
||||
}
|
||||
.cockpit-context-header {
|
||||
/* The Cockpit cards' headers (Contact, the briefing) share their frame: a
|
||||
title column over a hairline, a kicker in the accent, and a row of square
|
||||
actions. Each card sets only its spacing and sizes. */
|
||||
.cockpit-card-header {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 5px 10px;
|
||||
padding: 0 5px 7px;
|
||||
border-bottom: 1px solid
|
||||
color-mix(in srgb, var(--cockpit-accent) 22%, transparent);
|
||||
}
|
||||
.cockpit-context-header > div {
|
||||
.cockpit-card-header > div {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
}
|
||||
.cockpit-context-kicker {
|
||||
.cockpit-card-kicker {
|
||||
color: var(--cockpit-accent);
|
||||
font-size: 8px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.cockpit-card-actions {
|
||||
display: flex;
|
||||
}
|
||||
.cockpit-card-actions button {
|
||||
display: grid;
|
||||
height: 24px;
|
||||
place-items: center;
|
||||
padding: 0;
|
||||
border: 1px solid color-mix(in srgb, var(--cockpit-accent) 30%, transparent);
|
||||
border-radius: 5px;
|
||||
background: rgba(0, 7, 12, 0.72);
|
||||
color: color-mix(in srgb, var(--cockpit-accent) 82%, white);
|
||||
cursor: pointer;
|
||||
}
|
||||
.cockpit-card-actions button:hover,
|
||||
.cockpit-card-actions button:focus-visible {
|
||||
border-color: var(--cockpit-accent);
|
||||
color: #fff;
|
||||
outline: none;
|
||||
}
|
||||
.cockpit-context-header {
|
||||
gap: 5px 10px;
|
||||
padding: 0 5px 7px;
|
||||
}
|
||||
.cockpit-context-kicker {
|
||||
font-size: 8px;
|
||||
letter-spacing: 0.2em;
|
||||
}
|
||||
#cockpit-context-subject {
|
||||
@@ -1041,26 +1068,10 @@ body.cockpit-mode #view-switcher button {
|
||||
outline: none;
|
||||
}
|
||||
.cockpit-context-actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
.cockpit-context-actions button {
|
||||
display: grid;
|
||||
width: 28px;
|
||||
height: 24px;
|
||||
place-items: center;
|
||||
padding: 0;
|
||||
border: 1px solid color-mix(in srgb, var(--cockpit-accent) 30%, transparent);
|
||||
border-radius: 5px;
|
||||
background: rgba(0, 7, 12, 0.72);
|
||||
color: color-mix(in srgb, var(--cockpit-accent) 82%, white);
|
||||
cursor: pointer;
|
||||
}
|
||||
.cockpit-context-actions button:hover,
|
||||
.cockpit-context-actions button:focus-visible {
|
||||
border-color: var(--cockpit-accent);
|
||||
color: #fff;
|
||||
outline: none;
|
||||
}
|
||||
.cockpit-context-actions button:disabled {
|
||||
border-color: rgba(244, 251, 255, 0.1);
|
||||
@@ -1082,7 +1093,7 @@ body.cockpit-mode #view-switcher button {
|
||||
gap: 3px 8px;
|
||||
min-width: 0;
|
||||
padding: 6px 9px;
|
||||
border-left: 1px solid
|
||||
border-inline-start: 1px solid
|
||||
color-mix(in srgb, var(--cockpit-accent) 40%, transparent);
|
||||
background: rgba(0, 7, 12, 0.34);
|
||||
}
|
||||
@@ -1108,7 +1119,7 @@ body.cockpit-mode #view-switcher button {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.cockpit-context-cohorts > div.unknown {
|
||||
border-left-color: rgba(213, 168, 99, 0.58);
|
||||
border-inline-start-color: rgba(213, 168, 99, 0.58);
|
||||
}
|
||||
.cockpit-context-cohorts > div.unknown strong {
|
||||
color: #d5a863;
|
||||
@@ -1500,17 +1511,8 @@ body.cockpit-mode #view-switcher button {
|
||||
width: 100%;
|
||||
}
|
||||
.cockpit-signal-header {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 0 4px 7px 8px;
|
||||
border-bottom: 1px solid
|
||||
color-mix(in srgb, var(--cockpit-accent) 22%, transparent);
|
||||
}
|
||||
.cockpit-signal-header > div {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
}
|
||||
html:not([lang='en']) .cockpit-signal-header > div {
|
||||
min-width: 0;
|
||||
@@ -1526,9 +1528,7 @@ html:not([lang='en']) .cockpit-signal-kicker {
|
||||
letter-spacing: 0.1em;
|
||||
}
|
||||
.cockpit-signal-kicker {
|
||||
color: var(--cockpit-accent);
|
||||
font-size: 8px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.18em;
|
||||
}
|
||||
.cockpit-signal-kicker i {
|
||||
@@ -1542,7 +1542,6 @@ html:not([lang='en']) .cockpit-signal-kicker {
|
||||
animation: cockpit-live 1.6s ease-in-out infinite;
|
||||
}
|
||||
.cockpit-brief-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
}
|
||||
@@ -1552,22 +1551,7 @@ html:not([lang='en']) .cockpit-brief-actions > * {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.cockpit-brief-actions button {
|
||||
display: grid;
|
||||
width: 23px;
|
||||
height: 24px;
|
||||
place-items: center;
|
||||
padding: 0;
|
||||
border: 1px solid color-mix(in srgb, var(--cockpit-accent) 30%, transparent);
|
||||
border-radius: 5px;
|
||||
background: rgba(0, 7, 12, 0.72);
|
||||
color: color-mix(in srgb, var(--cockpit-accent) 82%, white);
|
||||
cursor: pointer;
|
||||
}
|
||||
.cockpit-brief-actions button:hover,
|
||||
.cockpit-brief-actions button:focus-visible {
|
||||
border-color: var(--cockpit-accent);
|
||||
color: #fff;
|
||||
outline: none;
|
||||
}
|
||||
.cockpit-brief-actions .material-symbols-outlined {
|
||||
font-size: 14px;
|
||||
@@ -1627,8 +1611,9 @@ html:not([lang='en']) #cockpit-brief-auto {
|
||||
display: grid;
|
||||
grid-template-columns: 54px minmax(0, 1fr);
|
||||
gap: 8px;
|
||||
padding: 7px 7px 7px 9px;
|
||||
border-left: 1px solid rgba(244, 251, 255, 0.2);
|
||||
padding-block: 7px;
|
||||
padding-inline: 9px 7px;
|
||||
border-inline-start: 1px solid rgba(244, 251, 255, 0.2);
|
||||
background: rgba(0, 7, 12, 0.3);
|
||||
}
|
||||
.cockpit-signal-list li.actionable {
|
||||
@@ -1638,7 +1623,7 @@ html:not([lang='en']) #cockpit-brief-auto {
|
||||
}
|
||||
.cockpit-signal-list li.actionable:hover,
|
||||
.cockpit-signal-list li.actionable:focus-within {
|
||||
border-left-color: var(--cockpit-accent);
|
||||
border-inline-start-color: var(--cockpit-accent);
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--cockpit-accent) 10%,
|
||||
@@ -1647,16 +1632,16 @@ html:not([lang='en']) #cockpit-brief-auto {
|
||||
}
|
||||
.cockpit-signal-list li.track,
|
||||
.cockpit-signal-list li.current {
|
||||
border-left-color: var(--cockpit-accent);
|
||||
border-inline-start-color: var(--cockpit-accent);
|
||||
}
|
||||
.cockpit-signal-list li.nearby {
|
||||
border-left-color: #fff;
|
||||
border-inline-start-color: #fff;
|
||||
}
|
||||
.cockpit-signal-list li.military {
|
||||
border-left-color: #d5a863;
|
||||
border-inline-start-color: #d5a863;
|
||||
}
|
||||
.cockpit-signal-list li.warning {
|
||||
border-left-color: #d5a863;
|
||||
border-inline-start-color: #d5a863;
|
||||
}
|
||||
.cockpit-signal-list time {
|
||||
color: rgba(244, 251, 255, 0.38);
|
||||
|
||||
+46
-16
@@ -41,8 +41,6 @@
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(165, 188, 199, 0.35) transparent;
|
||||
}
|
||||
|
||||
:root[data-ui-theme='cyber'] .cctv-panel-inner > .cyber-panel-body {
|
||||
@@ -68,8 +66,6 @@
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(165, 188, 199, 0.35) transparent;
|
||||
}
|
||||
|
||||
:root[data-ui-theme='cyber'] #pp-toggles > .pp-panel-body > * {
|
||||
@@ -82,6 +78,8 @@
|
||||
|
||||
:root[data-ui-theme='cyber'] {
|
||||
color-scheme: dark;
|
||||
--scroll-thumb: rgba(165, 188, 199, 0.35);
|
||||
--scroll-track: transparent;
|
||||
--cyber-red: #c45454;
|
||||
--cyber-red-bright: #e16666;
|
||||
--cyber-slate: #26343a;
|
||||
@@ -247,7 +245,7 @@
|
||||
.street-level-panel-inner,
|
||||
.global-context-panel-inner
|
||||
) {
|
||||
padding-right: 8px;
|
||||
padding-inline-end: 8px;
|
||||
}
|
||||
|
||||
/* Collapsed right-rail panels otherwise inherit different legacy gutters:
|
||||
@@ -796,6 +794,12 @@
|
||||
accent-color: var(--cyber-red);
|
||||
}
|
||||
|
||||
/* Street Level's range wears Cyber's slider accent like every other range. */
|
||||
:root[data-ui-theme='cyber'] .sl-range {
|
||||
--accent: inherit;
|
||||
--accent-glow: inherit;
|
||||
}
|
||||
|
||||
:root[data-ui-theme='cyber']
|
||||
:where(button, a[href], input, select, textarea):focus-visible {
|
||||
outline-color: var(--cyber-red-bright) !important;
|
||||
@@ -1650,12 +1654,27 @@
|
||||
}
|
||||
|
||||
:root[data-ui-theme='cyber'] #left-panel-stack {
|
||||
/* The stack holds still, so its rail needs no offset (leftPanelRail.js). */
|
||||
--panel-stack-top-pinned: 1;
|
||||
top: var(--cyber-left-rail-top) !important;
|
||||
bottom: calc(2vh + 13rem);
|
||||
left: 54px;
|
||||
inset-inline-start: calc(54px + var(--panel-rail-clearance));
|
||||
width: 300px;
|
||||
padding-left: 10px;
|
||||
border-left: 1px solid rgba(196, 84, 84, 0.72);
|
||||
padding-inline-start: 10px;
|
||||
border-inline-start: 1px solid transparent;
|
||||
}
|
||||
|
||||
/* An edge line marks each side's open panel, mirrored left and right. */
|
||||
:root[data-ui-theme='cyber']
|
||||
body:not(.cockpit-mode)
|
||||
#left-panel-stack:has(> [data-panel-id]:not(.collapsed, [hidden])) {
|
||||
border-inline-start-color: rgba(196, 84, 84, 0.72);
|
||||
}
|
||||
|
||||
:root[data-ui-theme='cyber']
|
||||
body:not(.cockpit-mode)
|
||||
#right-context-rail:has(> [data-panel-id]:not(.collapsed, [hidden])) {
|
||||
border-inline-end-color: rgba(196, 84, 84, 0.72);
|
||||
}
|
||||
|
||||
:root[data-ui-theme='cyber']
|
||||
@@ -1674,6 +1693,13 @@
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
/* The panel rail lists the collapsed panels. */
|
||||
:root[data-ui-theme='cyber']
|
||||
#left-panel-stack.layout-focus.layout-exclusive
|
||||
> [data-panel-id].collapsed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:root[data-ui-theme='cyber']
|
||||
#left-panel-stack
|
||||
> :is(#data-panel, #scene-panel) {
|
||||
@@ -1727,8 +1753,11 @@
|
||||
:root[data-ui-theme='cyber'] #right-context-rail {
|
||||
--right-display-collapsed-width: var(--cyber-panel-collapsed-width);
|
||||
--right-context-collapsed-width: var(--cyber-panel-collapsed-width);
|
||||
right: 62px;
|
||||
width: 312px;
|
||||
/* The edge line and its gap keep the panels where they were. */
|
||||
right: calc(51px + var(--panel-rail-clearance));
|
||||
width: 323px;
|
||||
padding-inline-end: 10px;
|
||||
border-inline-end: 1px solid transparent;
|
||||
}
|
||||
|
||||
/* The map rails now use one visible expanded width. Keeping the rail itself
|
||||
@@ -2499,15 +2528,13 @@
|
||||
);
|
||||
}
|
||||
|
||||
:root[data-ui-theme='cyber']
|
||||
body.cockpit-mode
|
||||
:is(.cockpit-context-header, .cockpit-signal-header) {
|
||||
:root[data-ui-theme='cyber'] body.cockpit-mode .cockpit-card-header {
|
||||
border-bottom-color: rgba(105, 203, 209, 0.25);
|
||||
}
|
||||
|
||||
:root[data-ui-theme='cyber']
|
||||
body.cockpit-mode
|
||||
:is(.cockpit-context-kicker, .cockpit-signal-kicker, .cockpit-route-kicker) {
|
||||
:is(.cockpit-card-kicker, .cockpit-route-kicker) {
|
||||
color: var(--cockpit-caution);
|
||||
}
|
||||
|
||||
@@ -2722,10 +2749,13 @@
|
||||
:root[data-ui-theme='cyber'] body.cockpit-mode #left-panel-stack {
|
||||
top: clamp(164px, 19vh, 204px) !important;
|
||||
bottom: auto;
|
||||
/* The cockpit keeps its physical sides in every language (rtl.css): the
|
||||
utility controls hold the right edge. */
|
||||
right: auto;
|
||||
left: 30px;
|
||||
width: var(--cyber-panel-width);
|
||||
padding-left: 0;
|
||||
border-left: 0;
|
||||
padding-inline-start: 0;
|
||||
border-inline-start: 0;
|
||||
}
|
||||
|
||||
:root[data-ui-theme='cyber'] body.cockpit-mode #left-panel-stack > #data-panel {
|
||||
|
||||
@@ -128,20 +128,9 @@ test('cockpit brief controls keep their English sizes', () => {
|
||||
|
||||
test('the count-line class is set only outside English', async () => {
|
||||
const { setLocale } = await import('../../i18n/index.js');
|
||||
const { LayerPanel } = await import('../layerPanel.js');
|
||||
const classes = () => {
|
||||
const toggled = new Map();
|
||||
const node = () => ({
|
||||
textContent: '',
|
||||
classList: { toggle: (name, on) => toggled.set(name, on) },
|
||||
appendChild() {},
|
||||
});
|
||||
LayerPanel.prototype._syncCount.call(LayerPanel.prototype, node(), node(), {
|
||||
countLabel: '693 nearby · 93 on screen',
|
||||
});
|
||||
return toggled.get('data-toggle-row--count-line');
|
||||
};
|
||||
globalThis.document ??= { createElement: () => ({}) };
|
||||
const { presentLayerCount } = await import('../layerPanel.js');
|
||||
const classes = () =>
|
||||
presentLayerCount({ countLabel: '693 nearby · 93 on screen' }).countLine;
|
||||
try {
|
||||
setLocale('en');
|
||||
assert.equal(classes(), false);
|
||||
|
||||
@@ -110,20 +110,11 @@
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
scroll-behavior: smooth;
|
||||
/* A visible, non-overlaying affordance that the list continues. */
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(54, 220, 255, 0.45) transparent;
|
||||
/* A visible, non-overlaying affordance that the list continues; the
|
||||
scrollbar itself is the kit's .ui-scroll. */
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
.first-run-choices::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
.first-run-choices::-webkit-scrollbar-thumb {
|
||||
background: rgba(54, 220, 255, 0.45);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.first-run-choices button {
|
||||
display: grid;
|
||||
grid-template-columns: 1.7rem 1fr 1.3rem;
|
||||
@@ -133,7 +124,7 @@
|
||||
min-height: 3.55rem;
|
||||
padding: 0.6rem 0.7rem;
|
||||
color: rgba(225, 244, 248, 0.92);
|
||||
text-align: left;
|
||||
text-align: start;
|
||||
background: rgba(29, 69, 81, 0.18);
|
||||
border: 1px solid rgba(100, 200, 220, 0.18);
|
||||
border-radius: 0.42rem;
|
||||
|
||||
@@ -10,6 +10,20 @@
|
||||
font-family: var(--font-sans);
|
||||
font-size: 12px;
|
||||
outline: none;
|
||||
/* Native number spinners and the open select list follow the dark skin. */
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
/* A select draws the field's own chevron, not the platform's arrow, so every
|
||||
dropdown in the app reads the same. The open list stays native. */
|
||||
select.gev-field {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
padding-inline-end: 22px;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='rgba(255,255,255,0.62)' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 8px center;
|
||||
background-size: 10px 6px;
|
||||
}
|
||||
|
||||
.gev-field:focus-visible {
|
||||
@@ -30,6 +44,11 @@
|
||||
padding: 2px 6px;
|
||||
}
|
||||
|
||||
select.gev-field--compact {
|
||||
padding-inline-end: 18px;
|
||||
background-position: right 6px center;
|
||||
}
|
||||
|
||||
/* Compact inline editors share density and hide native number spinners.
|
||||
Their containing feature sets width to fit the surrounding readout. */
|
||||
.gev-field--inline {
|
||||
|
||||
Vendored
+12
-6
@@ -9,6 +9,9 @@
|
||||
--glass-border: rgba(255, 255, 255, 0.08);
|
||||
--glass-border-hover: rgba(255, 255, 255, 0.15);
|
||||
--accent: #00d4ff;
|
||||
/* Every scrolling surface's scrollbar (the kit's .ui-scroll). */
|
||||
--scroll-thumb: rgba(0, 212, 255, 0.46);
|
||||
--scroll-track: rgba(255, 255, 255, 0.06);
|
||||
--accent-dim: rgba(0, 212, 255, 0.15);
|
||||
--accent-glow: rgba(0, 212, 255, 0.4);
|
||||
--text-primary: #e8eaed;
|
||||
@@ -27,8 +30,11 @@
|
||||
--dock-label-font-weight: 500;
|
||||
--dock-label-letter-spacing: 0.13rem;
|
||||
--transition-smooth: 300ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
--left-stack-x: 52px;
|
||||
--right-rail-x: 52px;
|
||||
/* The side stacks clear the 52px edge gutter that holds the vertical HUD
|
||||
readout, plus the floating panel rail (42px) and its 6px gap. */
|
||||
--panel-rail-clearance: 48px;
|
||||
--left-stack-x: calc(52px + var(--panel-rail-clearance));
|
||||
--right-rail-x: calc(52px + var(--panel-rail-clearance));
|
||||
--left-stack-top: 26vh;
|
||||
--left-stack-gap: 72px;
|
||||
--left-collapsed-width: 176px;
|
||||
@@ -497,7 +503,7 @@ body.cockpit-mode #cockpit-cloud-effects.active {
|
||||
#title-bar {
|
||||
position: fixed;
|
||||
top: 32px;
|
||||
left: 36px;
|
||||
inset-inline-start: 36px;
|
||||
z-index: 100;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
@@ -506,7 +512,7 @@ body.cockpit-mode #cockpit-cloud-effects.active {
|
||||
#title-bar .title-glow {
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
left: -20px;
|
||||
inset-inline-start: -20px;
|
||||
width: 200px;
|
||||
height: 80px;
|
||||
background: radial-gradient(ellipse, var(--accent-dim) 0%, transparent 70%);
|
||||
@@ -631,11 +637,11 @@ body.cockpit-mode #cockpit-cloud-effects.active {
|
||||
#style-indicator {
|
||||
position: fixed;
|
||||
top: 32px;
|
||||
right: 36px;
|
||||
inset-inline-end: 36px;
|
||||
z-index: 100;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
text-align: right;
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
#style-indicator .indicator-label {
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
/* Generic components in src/ui/kit. The surfaces use this application's
|
||||
glass, accent and type tokens. */
|
||||
|
||||
/* The thin themed scrollbar every scrolling surface wears (.ui-scroll). Without it a surface draws the platform's light scrollbar,
|
||||
because the page declares no dark color-scheme. */
|
||||
/* Cesium builds its credit list and the rail card builder its lines, so
|
||||
those two surfaces are named here instead of carrying the class. */
|
||||
.ui-scroll,
|
||||
.cesium-credit-lightbox > ul,
|
||||
.weather-card [data-line-id='status'] {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--scroll-thumb) var(--scroll-track);
|
||||
}
|
||||
.ui-scroll::-webkit-scrollbar,
|
||||
.cesium-credit-lightbox > ul::-webkit-scrollbar,
|
||||
.weather-card [data-line-id='status']::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
.ui-scroll::-webkit-scrollbar-thumb,
|
||||
.cesium-credit-lightbox > ul::-webkit-scrollbar-thumb,
|
||||
.weather-card [data-line-id='status']::-webkit-scrollbar-thumb {
|
||||
border-radius: 999px;
|
||||
background: var(--scroll-thumb);
|
||||
}
|
||||
.ui-scroll::-webkit-scrollbar-track,
|
||||
.cesium-credit-lightbox > ul::-webkit-scrollbar-track,
|
||||
.weather-card [data-line-id='status']::-webkit-scrollbar-track {
|
||||
background: var(--scroll-track);
|
||||
}
|
||||
|
||||
.ui-icon {
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
font-variation-settings:
|
||||
'FILL' 0,
|
||||
'wght' 400,
|
||||
'GRAD' 0,
|
||||
'opsz' 20;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.ui-tooltip {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
}
|
||||
.ui-tooltip__tip {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
z-index: 1;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 6px;
|
||||
background: var(--menu-bg);
|
||||
color: var(--dock-label-color);
|
||||
font: var(--dock-label-font-weight) var(--dock-label-font-size) / 1.4
|
||||
var(--font-sans);
|
||||
letter-spacing: var(--dock-label-letter-spacing);
|
||||
white-space: nowrap;
|
||||
pointer-events: none;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
/* Sides are for left-to-right; a right-to-left page mirrors them with the
|
||||
rails, so a tip still opens toward the middle of the screen. */
|
||||
.ui-tooltip__tip--right {
|
||||
inset-inline-start: calc(100% + 10px);
|
||||
}
|
||||
.ui-tooltip__tip--left {
|
||||
inset-inline-end: calc(100% + 10px);
|
||||
}
|
||||
|
||||
.ui-rail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
padding: 4px;
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: calc(var(--btn-radius) + 4px);
|
||||
background: var(--glass-bg);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
pointer-events: auto;
|
||||
}
|
||||
.ui-rail__sep {
|
||||
height: 1px;
|
||||
margin: 3px 6px;
|
||||
background: var(--glass-border);
|
||||
}
|
||||
.ui-rail__item {
|
||||
position: relative;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--btn-radius);
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition:
|
||||
color var(--transition-fast),
|
||||
background var(--transition-fast),
|
||||
border-color var(--transition-fast);
|
||||
}
|
||||
.ui-rail__item:hover:not(:disabled) {
|
||||
color: var(--text-primary);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
.ui-rail__item--on,
|
||||
.ui-rail__item--on:hover:not(:disabled) {
|
||||
border-color: var(--accent-dim);
|
||||
background: var(--accent-dim);
|
||||
color: var(--accent);
|
||||
}
|
||||
.ui-rail__item:disabled {
|
||||
opacity: 0.35;
|
||||
cursor: default;
|
||||
}
|
||||
.ui-rail__badge {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
inset-inline-end: 1px;
|
||||
min-width: 14px;
|
||||
padding: 0 3px;
|
||||
border-radius: 7px;
|
||||
background: var(--accent);
|
||||
color: var(--bg-dark);
|
||||
font: 600 9px/14px var(--font-mono);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Application view hosts (src/ui/views): the view's own elements lay out as
|
||||
children of the page, as the static markup they replace did. */
|
||||
.gev-view {
|
||||
display: contents;
|
||||
}
|
||||
+39
-30
@@ -3,7 +3,7 @@
|
||||
position: fixed;
|
||||
top: var(--left-stack-safe-top, var(--left-stack-top));
|
||||
bottom: var(--left-stack-safe-bottom, 4vh);
|
||||
left: var(--left-stack-x);
|
||||
inset-inline-start: var(--left-stack-x);
|
||||
z-index: 100;
|
||||
width: 360px;
|
||||
display: flex;
|
||||
@@ -84,29 +84,9 @@
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
padding-right: 4px;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(0, 212, 255, 0.5) rgba(255, 255, 255, 0.04);
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
.data-toggle-list::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.data-toggle-list::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.data-toggle-list::-webkit-scrollbar-thumb {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(0, 212, 255, 0.65),
|
||||
rgba(0, 212, 255, 0.2)
|
||||
);
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.data-layer-group-heading {
|
||||
margin: 8px 10px 2px;
|
||||
padding-top: 12px;
|
||||
@@ -197,11 +177,11 @@
|
||||
#right-context-rail #weather-panel.collapsed,
|
||||
#right-context-rail #recent-imagery-panel.collapsed {
|
||||
width: var(--right-display-collapsed-width);
|
||||
margin-left: auto;
|
||||
margin-inline-start: auto;
|
||||
}
|
||||
#right-context-rail > #pp-toggles.collapsed {
|
||||
width: var(--right-display-collapsed-width);
|
||||
margin-left: auto;
|
||||
margin-inline-start: auto;
|
||||
}
|
||||
#right-context-rail > #pp-toggles.collapsed .pp-header-row {
|
||||
width: 100%;
|
||||
@@ -240,7 +220,7 @@
|
||||
width: var(--right-context-collapsed-width);
|
||||
/* Keep the compact Context tab on the rail edge rather than leaving it
|
||||
stranded at the rail's expanded-width origin. */
|
||||
margin-left: auto;
|
||||
margin-inline-start: auto;
|
||||
}
|
||||
#right-context-rail
|
||||
#global-context-panel.collapsed
|
||||
@@ -260,6 +240,39 @@
|
||||
#right-context-rail.layout-exclusive > [data-panel-id].collapsed {
|
||||
display: none;
|
||||
}
|
||||
#left-panel-stack.layout-exclusive > [data-panel-id].collapsed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Panel rails (src/ui/panelNavigation.js). Each rail lives inside its stack,
|
||||
so it shows and hides with the stack, and floats in the screen-edge gutter.
|
||||
The stack's layout offsets it back to its resting top, so it holds still
|
||||
when an opened panel moves the stack. */
|
||||
.gev-panel-rail {
|
||||
position: absolute;
|
||||
top: var(--panel-rail-offset, 0px);
|
||||
z-index: 1;
|
||||
pointer-events: auto;
|
||||
transition: top var(--transition-fast);
|
||||
}
|
||||
.gev-panel-rail--left {
|
||||
inset-inline-end: calc(100% + 6px);
|
||||
}
|
||||
.gev-panel-rail--right {
|
||||
inset-inline-start: calc(100% + 6px);
|
||||
}
|
||||
body.mobile-shell #left-panel-stack > .gev-panel-rail,
|
||||
body.mobile-shell #right-context-rail > .gev-panel-rail,
|
||||
body.cockpit-mode .gev-panel-rail {
|
||||
display: none;
|
||||
}
|
||||
/* Clean view and recording hide the panels one by one, not their stacks. */
|
||||
body.ui-clean-view .gev-panel-rail,
|
||||
body.recording-mode .gev-panel-rail {
|
||||
opacity: 0 !important;
|
||||
visibility: hidden !important;
|
||||
pointer-events: none !important;
|
||||
}
|
||||
#right-context-rail.layout-focus > [data-panel-id].collapsed {
|
||||
display: block;
|
||||
flex: 0 0 auto;
|
||||
@@ -281,8 +294,6 @@
|
||||
#right-context-rail.layout-focus > #pp-toggles:not(.collapsed) {
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(0, 212, 255, 0.4) transparent;
|
||||
}
|
||||
#right-context-rail.layout-focus
|
||||
> #weather-panel:not(.collapsed)
|
||||
@@ -385,7 +396,7 @@
|
||||
gap: 10px;
|
||||
padding: 1px 3px 7px;
|
||||
border-bottom: 1px solid rgba(34, 230, 230, 0.14);
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
#space-mission-panel-host .space-mission-roster-header > div {
|
||||
display: grid;
|
||||
@@ -415,8 +426,6 @@
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
padding-right: 3px;
|
||||
scrollbar-color: rgba(34, 230, 230, 0.38) transparent;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
#space-mission-panel-host .space-mission-roster-item {
|
||||
position: relative;
|
||||
@@ -431,7 +440,7 @@
|
||||
border-radius: 4px;
|
||||
color: #d8ffff;
|
||||
background: rgba(42, 113, 171, 0.045);
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-family: var(--font-mono);
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* ============================================================
|
||||
Bottom-right corner row: the POWER UP chip (provider-settings.css)
|
||||
and, in the very corner, the interface language picker
|
||||
(src/ui/localePicker.js). Logical properties throughout, so a
|
||||
(src/ui/views/panels/LocalePicker.vue). Logical properties throughout, so a
|
||||
right-to-left page mirrors the row and the list.
|
||||
============================================================ */
|
||||
|
||||
|
||||
@@ -84,10 +84,10 @@ body.mobile-shell[data-mobile-sheet] #mobile-sheet-backdrop:not([hidden]) {
|
||||
background: rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
|
||||
/* ── Top bar: logo left, globe actions right ───────────────────────────── */
|
||||
/* ── Top bar: logo at the start, globe actions at the end ──────────────── */
|
||||
body.mobile-shell #title-bar {
|
||||
top: calc(var(--mobile-top-inset) + 10px);
|
||||
left: 14px;
|
||||
inset-inline-start: 14px;
|
||||
}
|
||||
|
||||
body.mobile-shell #title-bar .title-logo {
|
||||
@@ -101,7 +101,8 @@ body.mobile-shell #title-bar .subtitle {
|
||||
|
||||
body.mobile-shell #top-center-actions {
|
||||
top: calc(var(--mobile-top-inset) + 10px);
|
||||
right: 12px;
|
||||
inset-inline-start: auto;
|
||||
inset-inline-end: 12px;
|
||||
}
|
||||
|
||||
/* ── HUD: one compact readout under the top bar ────────────────────────── */
|
||||
@@ -408,7 +409,7 @@ body.mobile-shell:not(.cockpit-mode) #view-switcher {
|
||||
|
||||
body.mobile-shell #corner-actions {
|
||||
top: calc(var(--mobile-top-inset) + 60px);
|
||||
right: 12px;
|
||||
inset-inline-end: 12px;
|
||||
bottom: auto;
|
||||
}
|
||||
|
||||
|
||||
@@ -152,7 +152,7 @@
|
||||
overflow: auto;
|
||||
overscroll-behavior: contain;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--glass-border-hover) transparent;
|
||||
scrollbar-color: var(--scroll-thumb) var(--scroll-track);
|
||||
/* Keep an inset focus ring inside the intentional scrolling boundary. */
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
@@ -184,19 +184,9 @@
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(54, 220, 255, 0.45) transparent;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
.key-setup-rows::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
.key-setup-rows::-webkit-scrollbar-thumb {
|
||||
background: rgba(54, 220, 255, 0.45);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.key-setup-row {
|
||||
display: grid;
|
||||
gap: 0.35rem;
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
border-radius: 8px;
|
||||
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, 0.46) rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
#radio-panel .panel-header {
|
||||
margin-bottom: 0;
|
||||
@@ -51,7 +49,8 @@
|
||||
#radio-filter {
|
||||
/* Radio owns its compact density and arrow space; shared fields paint the
|
||||
palette and disabled state, including this intentional theme exception. */
|
||||
padding: 7px 24px 7px 8px;
|
||||
padding-block: 7px;
|
||||
padding-inline: 8px 24px;
|
||||
border-radius: 5px;
|
||||
font: 9px/1.2 var(--font-mono);
|
||||
--gev-field-border-color: rgba(65, 220, 231, 0.28);
|
||||
@@ -697,7 +696,7 @@ html:not([lang='en']) .data-toggle-right {
|
||||
}
|
||||
|
||||
/* Row text pieces that must not break inside: a hyphenated code (ADS-B) and
|
||||
the space before a " · " separator (see setRowText in layerPanel.js). */
|
||||
the space before a " · " separator (see rowTextRuns in layerRowText.js). */
|
||||
html:not([lang='en']) .data-keep {
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -1092,11 +1091,12 @@ html:not([lang='en']) .data-row-list-text {
|
||||
.sdr-radio-card select,
|
||||
.sdr-radio-card input[type='number'] {
|
||||
min-height: 28px;
|
||||
border: 1px solid rgba(255, 183, 82, 0.28);
|
||||
border-radius: 5px;
|
||||
color: #ffd79a;
|
||||
background: rgba(38, 25, 8, 0.52);
|
||||
font: 8px/1 var(--font-mono);
|
||||
/* The shared field paints the surface (and the select's chevron). */
|
||||
--gev-field-border-color: rgba(255, 183, 82, 0.28);
|
||||
--gev-field-color: #ffd79a;
|
||||
--gev-field-background: rgba(38, 25, 8, 0.52);
|
||||
}
|
||||
.sdr-radio-card button {
|
||||
padding: 6px 8px;
|
||||
@@ -1129,7 +1129,8 @@ html:not([lang='en']) .data-row-list-text {
|
||||
.sdr-gain-row select {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 5px 7px;
|
||||
padding-block: 5px;
|
||||
padding-inline: 7px 22px;
|
||||
}
|
||||
.sdr-adsb-stats {
|
||||
display: grid;
|
||||
|
||||
@@ -46,8 +46,6 @@
|
||||
max-height: calc(var(--right-stack-max-height, 50svh) - 64px);
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(0, 212, 255, 0.46) transparent;
|
||||
}
|
||||
#recent-imagery-panel.collapsed #recent-imagery-panel-body,
|
||||
#recent-imagery-panel.collapsed #recent-imagery-panel-count,
|
||||
@@ -168,7 +166,6 @@
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
scroll-snap-type: x proximity;
|
||||
scrollbar-width: thin;
|
||||
outline: none;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
#title-bar {
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
inset-inline-start: 20px;
|
||||
}
|
||||
|
||||
#top-center-actions {
|
||||
@@ -55,7 +55,7 @@
|
||||
#title-bar .frame-rate-readout {
|
||||
position: fixed;
|
||||
top: 52px;
|
||||
left: 20px;
|
||||
inset-inline-start: 20px;
|
||||
margin: 0;
|
||||
font-size: 10px;
|
||||
line-height: 12px;
|
||||
@@ -245,7 +245,7 @@
|
||||
/* 16px keeps the Radio broadcast waves (15px above the logo) on-screen. */
|
||||
#title-bar {
|
||||
top: 16px;
|
||||
left: 16px;
|
||||
inset-inline-start: 16px;
|
||||
}
|
||||
|
||||
#title-bar h1 > span:last-child,
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
/* Right-to-left languages (Arabic). The chrome mirrors through logical
|
||||
properties in the component sheets (inset-inline-*, margin-inline-*); this
|
||||
sheet holds only what logical properties cannot express. */
|
||||
|
||||
/* Arabic letters join: letter-spacing tears every word into separate glyphs.
|
||||
Latin marked translate="no" and the intel HUD's Latin telemetry keep their
|
||||
tracking; the HUD's translated lines are released below. Unlike cjk.css, a
|
||||
container that merely holds a translate="no" span is not spared: its own
|
||||
Arabic text would come apart. */
|
||||
html:lang(ar)
|
||||
body
|
||||
:not(#intel-hud, #intel-hud *, [translate='no'], [translate='no'] *) {
|
||||
letter-spacing: 0 !important;
|
||||
}
|
||||
|
||||
/* The intel HUD is an instrument overlay: corners, brackets, coordinates and
|
||||
sensor codes keep the English geometry. Its translated lines still read
|
||||
right to left inside that frame. */
|
||||
html[dir='rtl'] #intel-hud {
|
||||
direction: ltr;
|
||||
}
|
||||
html[dir='rtl'] #intel-hud :is(.hud-mode, .hud-summary-label, .hud-summary) {
|
||||
unicode-bidi: plaintext;
|
||||
text-align: left;
|
||||
letter-spacing: 0 !important;
|
||||
}
|
||||
|
||||
/* Font stacks: JetBrains Mono and Inter have no Arabic glyphs, and the
|
||||
generic monospace fallback is a fixed-pitch Arabic face that spaces every
|
||||
letter apart. Noto Sans Arabic (loaded with the other Google fonts; its
|
||||
files download only when the page shows Arabic) leads the mono stack, so
|
||||
the spaces between Arabic words come from it too and not from the
|
||||
fixed-pitch Latin face. The intel and cockpit HUDs keep JetBrains Mono for
|
||||
their Latin telemetry and column-aligned digits. */
|
||||
html:lang(ar) {
|
||||
--font-mono:
|
||||
'Noto Sans Arabic', 'JetBrains Mono', 'SF Mono', 'Fira Code', 'Geeza Pro',
|
||||
Tahoma, monospace;
|
||||
--font-sans:
|
||||
'Inter', 'Noto Sans Arabic', -apple-system, BlinkMacSystemFont, 'Geeza Pro',
|
||||
Tahoma, sans-serif;
|
||||
}
|
||||
/* The Global Context lines and their buttons set ui-monospace through the
|
||||
`font` shorthand; route them through the Arabic-aware stack too. */
|
||||
html:lang(ar)
|
||||
:is(
|
||||
#military-awareness-panel,
|
||||
.military-awareness-controls button,
|
||||
.military-awareness-standby button,
|
||||
.military-awareness-compass-label,
|
||||
.military-awareness-compass-heading,
|
||||
.military-awareness-direction-label,
|
||||
.tr3b-toggle
|
||||
) {
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
html:lang(ar) :is(#intel-hud, #cockpit-hud) {
|
||||
--font-mono:
|
||||
'JetBrains Mono', 'SF Mono', 'Fira Code', 'Noto Sans Arabic', 'Geeza Pro',
|
||||
Tahoma, monospace;
|
||||
}
|
||||
|
||||
/* The Display/Context rail moves to the left edge. It keeps a physical
|
||||
`right` in layers.css because the attribution-clearance model
|
||||
(creditAttribution.test.mjs) reads that rule; the swap only applies above
|
||||
the 720px band, where the rail is not edge-to-edge. */
|
||||
@media (min-width: 721px) {
|
||||
html[dir='rtl'] #right-context-rail {
|
||||
right: auto;
|
||||
left: var(--right-rail-x);
|
||||
}
|
||||
}
|
||||
@media (min-width: 961px) and (min-height: 650px) {
|
||||
html[dir='rtl'][data-ui-theme='cyber'] #right-context-rail {
|
||||
right: auto;
|
||||
left: calc(51px + var(--panel-rail-clearance));
|
||||
}
|
||||
}
|
||||
|
||||
/* The dock's wings swap sides (Location on the right, Visual presets on the
|
||||
left), so each slide-up tray is anchored from the wing's outer edge and
|
||||
centred on the whole dock from there, as it is in left-to-right. */
|
||||
html[dir='rtl'] #command-dock #location-bar .dock-popover-content {
|
||||
right: calc((var(--dock-unified-width) - var(--dock-location-open)) / 2);
|
||||
left: auto;
|
||||
transform-origin: bottom right;
|
||||
}
|
||||
html[dir='rtl'] #command-dock #control-panel .dock-popover-content {
|
||||
right: auto;
|
||||
left: calc((var(--dock-unified-width) - var(--dock-presets-open)) / 2);
|
||||
transform-origin: bottom left;
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
html[dir='rtl'] #command-dock #location-bar .dock-popover-content {
|
||||
right: calc(
|
||||
(var(--dock-unified-width) - var(--dock-popover-mobile-width)) / 2
|
||||
);
|
||||
}
|
||||
html[dir='rtl'] #command-dock #control-panel .dock-popover-content {
|
||||
left: calc(
|
||||
(var(--dock-unified-width) - var(--dock-popover-mobile-width)) / 2
|
||||
);
|
||||
}
|
||||
}
|
||||
/* The tray pin sits on the tray's top end corner: top-left here. */
|
||||
html[dir='rtl'] #command-dock :is(#location-bar, #control-panel) .dock-pin-btn {
|
||||
right: auto;
|
||||
left: -1rem;
|
||||
}
|
||||
|
||||
/* The right rail sits on the left: its expand/collapse arrows point the
|
||||
other way, and the start window's "go" arrows point along the reading
|
||||
direction. */
|
||||
html[dir='rtl'] #right-context-rail .panel-collapse-btn,
|
||||
html[dir='rtl'] .first-run-arrow {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
html[dir='rtl'] .first-run-choices button:hover .first-run-arrow,
|
||||
html[dir='rtl'] .first-run-choices button:focus-visible .first-run-arrow {
|
||||
transform: translateX(-2px) scaleX(-1);
|
||||
}
|
||||
|
||||
/* The cockpit is an aircraft panel, not page chrome: its windows keep their
|
||||
physical sides, so the data-layer stack goes back to the left edge instead
|
||||
of landing on the utility controls. Gauges, tapes and the roll counters stay
|
||||
left to right (speed left, altitude right, -10 not 10-); the cards keep
|
||||
reading right to left. */
|
||||
html[dir='rtl'] body.cockpit-mode #left-panel-stack {
|
||||
left: var(--left-stack-x);
|
||||
right: auto;
|
||||
}
|
||||
html[dir='rtl']
|
||||
#cockpit-hud
|
||||
:is(
|
||||
.cockpit-instrument-row,
|
||||
.cockpit-compass,
|
||||
.cockpit-pitch-rail,
|
||||
.cockpit-altitude-rim,
|
||||
.cockpit-roll-arc,
|
||||
.cockpit-vision-controls
|
||||
) {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
/* Feed text (news headlines, station names) arrives in its own language:
|
||||
each line takes its direction from its first letter, so an English headline
|
||||
reads and aligns left to right instead of moving its punctuation. */
|
||||
html[dir='rtl']
|
||||
:is(
|
||||
.cockpit-news-list li *,
|
||||
#radio-station-name,
|
||||
#radio-station-tags,
|
||||
.cockpit-radio-station
|
||||
) {
|
||||
unicode-bidi: plaintext;
|
||||
}
|
||||
|
||||
/* Clock readouts (~0:05 / 5:19) are numbers, not prose. */
|
||||
html[dir='rtl'] #bhote-koshi-event-panel [data-role='time'] {
|
||||
direction: ltr;
|
||||
unicode-bidi: isolate;
|
||||
}
|
||||
|
||||
/* Physical controls keep their left-to-right sense: FM seek down/up and the
|
||||
radio dial. The worded PREV / PLAY / NEXT row follows the reading
|
||||
direction like any other button row. */
|
||||
html[dir='rtl'] :is(.sdr-seek-row, .radio-tuner) {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
/* Canvas labels (installations, map cards, detections) are measured and drawn
|
||||
with left-to-right geometry: a card's text starts at its left padding. A
|
||||
canvas inherits the page direction, which turned that start into the right
|
||||
edge and pushed every title out of its card. */
|
||||
html[dir='rtl'] canvas {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
/* Scene callouts on the screen whiteboard: the card grows to the right of the
|
||||
text's x, so right-to-left text ends there instead of starting there. The
|
||||
injected stack (JetBrains Mono) has no Arabic glyphs; use the Arabic-aware
|
||||
one. */
|
||||
html[dir='rtl'] .gev-anno-text {
|
||||
text-anchor: end;
|
||||
font-family: var(--font-mono);
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
/* Position counters built from bare numbers (01 / 25, 1 / 3) read as a number
|
||||
pair, left to right, as an unspaced 1/3 does; otherwise the slash swaps
|
||||
them and the current item looks like the total. Translated counters say
|
||||
«من» instead. */
|
||||
html[dir='rtl']
|
||||
:is(
|
||||
#bhote-koshi-event-panel [data-role='beat-index'],
|
||||
#cockpit-brief-position,
|
||||
[data-mission-index]
|
||||
) {
|
||||
direction: ltr;
|
||||
unicode-bidi: isolate;
|
||||
}
|
||||
|
||||
/* The radio tuner header is a left-to-right instrument row; its channel
|
||||
readout («القناة 01 من 750») still reads in its own language. */
|
||||
html[dir='rtl'] #radio-tuner-value {
|
||||
unicode-bidi: plaintext;
|
||||
}
|
||||
|
||||
/* The select chevron sits at the end of the field, where the text stops
|
||||
(form-field.css pads that side through padding-inline-end). */
|
||||
html[dir='rtl'] select.gev-field {
|
||||
background-position: left 8px center;
|
||||
}
|
||||
html[dir='rtl'] select.gev-field--compact {
|
||||
background-position: left 6px center;
|
||||
}
|
||||
|
||||
/* A legend's range is printed under its colour ramp, which always runs low
|
||||
to high from left to right; the numbers keep that order. */
|
||||
html[dir='rtl'] .rail-card-scale {
|
||||
direction: ltr;
|
||||
}
|
||||
@@ -265,15 +265,6 @@ html:not([lang='en']) #scene-panel .scene-controls .scene-btn {
|
||||
counter-reset: scene-shot;
|
||||
}
|
||||
|
||||
.scene-shot-list::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.scene-shot-list::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.scene-shot-empty {
|
||||
border: 1px dashed rgba(255, 255, 255, 0.14);
|
||||
border-radius: 8px;
|
||||
|
||||
@@ -243,20 +243,20 @@ html:not([lang='en']) #global-loading-detail {
|
||||
|
||||
/* ── Split-flap chip text ─────────────────────────────────────────
|
||||
Per-character departure-board flip when a chip label changes.
|
||||
Driven entirely by src/splitFlap.js, which rebuilds the label as
|
||||
one span per character on the change and strips them again once
|
||||
the cascade lands.
|
||||
Planned by src/splitFlap.js and rendered by SplitFlapText.vue, which
|
||||
lays out one cell per character on the change and strips them again
|
||||
once the cascade lands.
|
||||
|
||||
The DOM text is the truth and its node never moves. A chip label is
|
||||
upgraded once into a permanent shell — a .gev-flap-text span holding
|
||||
one long-lived Text node, plus an aria-hidden .gev-flap-cells
|
||||
sibling — and from then on a label change is only `node.data = next`.
|
||||
a .gev-flap-text span holding one long-lived Text node, plus an
|
||||
aria-hidden .gev-flap-cells sibling, and a label change is only
|
||||
`node.data = next`.
|
||||
During a cascade the host takes .gev-flap-active, which clips the
|
||||
real text out of view and lets the cells stand in for it. The cells
|
||||
carry no text at all: both glyphs are generated content (::before =
|
||||
outgoing, ::after = incoming), and generated content never reaches
|
||||
textContent. That is what keeps QA pins and the traffic chip's own
|
||||
`textContent !==` guard honest, and it lets the cells be rebuilt
|
||||
textContent. That is what keeps QA pins honest, and it lets the
|
||||
cells be rebuilt
|
||||
freely while the aria-live region sees only one characterData
|
||||
change per label update.
|
||||
|
||||
|
||||
@@ -39,8 +39,6 @@
|
||||
0 8px 32px rgba(0, 0, 0, 0.5),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.03) inset,
|
||||
0 1px 0 rgba(255, 255, 255, 0.06) inset;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(0, 212, 255, 0.46) rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
/* Class-qualified, not id-qualified, so a theme's header rule still wins. */
|
||||
@@ -236,17 +234,10 @@ html:not([lang='en']) .sl-meta-row .sl-nearest-btn {
|
||||
gap: 4px 10px;
|
||||
}
|
||||
|
||||
/* The shared slider surface, in Street Level's green. */
|
||||
.sl-range {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
accent-color: var(--sl-green);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sl-range:focus-visible {
|
||||
outline: 1px solid var(--accent);
|
||||
outline-offset: 3px;
|
||||
--accent: var(--sl-green);
|
||||
--accent-glow: color-mix(in srgb, var(--sl-green) 55%, transparent);
|
||||
}
|
||||
|
||||
/* The value and its note share a line: ANY DATE (by upload date). */
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
0 0 1.2rem rgba(0, 212, 255, 0.08);
|
||||
backdrop-filter: blur(24px) saturate(1.3);
|
||||
-webkit-backdrop-filter: blur(24px) saturate(1.3);
|
||||
text-align: left;
|
||||
text-align: start;
|
||||
transform: translateX(-50%);
|
||||
transform-origin: bottom center;
|
||||
animation: gev-voice-card-in 180ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
@@ -95,7 +95,7 @@
|
||||
}
|
||||
|
||||
.gev-voice-card-dismiss {
|
||||
margin-left: auto;
|
||||
margin-inline-start: auto;
|
||||
width: 1.1rem;
|
||||
height: 1.1rem;
|
||||
padding: 0;
|
||||
@@ -288,7 +288,7 @@
|
||||
content: counter(gev-referent);
|
||||
display: inline-block;
|
||||
min-width: 0.9rem;
|
||||
margin-right: 0.3rem;
|
||||
margin-inline-end: 0.3rem;
|
||||
color: rgba(54, 220, 255, 0.72);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.52rem;
|
||||
@@ -415,7 +415,7 @@ body.recording-mode .gev-pointer-reticle {
|
||||
-webkit-backdrop-filter: blur(24px) saturate(1.3);
|
||||
color-scheme: dark;
|
||||
font: 0.75rem/1.45 var(--font-sans);
|
||||
text-align: left;
|
||||
text-align: start;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
:root[data-ui-theme='cyber'] #gev-voice-control .gev-voice-panel {
|
||||
@@ -443,7 +443,7 @@ body.recording-mode .gev-pointer-reticle {
|
||||
letter-spacing: 0.14em;
|
||||
}
|
||||
.gev-voice-panel-tabs {
|
||||
margin-left: auto;
|
||||
margin-inline-start: auto;
|
||||
}
|
||||
#gev-voice-control .gev-voice-panel-header .gev-voice-panel-tab {
|
||||
display: inline-flex;
|
||||
@@ -467,7 +467,8 @@ body.recording-mode .gev-pointer-reticle {
|
||||
min-height: 2rem;
|
||||
box-sizing: border-box;
|
||||
color: var(--text-primary);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
/* background-color, so a .gev-field select keeps its chevron. */
|
||||
background-color: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 0.3rem;
|
||||
font: inherit;
|
||||
@@ -504,7 +505,6 @@ body.recording-mode .gev-pointer-reticle {
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
overscroll-behavior: contain;
|
||||
scrollbar-width: thin;
|
||||
padding: 0.55rem 0.75rem 3px 3px;
|
||||
line-height: 1.45;
|
||||
overflow-wrap: anywhere;
|
||||
@@ -553,7 +553,8 @@ body.recording-mode .gev-pointer-reticle {
|
||||
#gev-voice-timeout-select {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
padding: 0 0.45rem;
|
||||
padding-block: 0;
|
||||
padding-inline: 0.45rem 22px;
|
||||
}
|
||||
#gev-voice-timeout-input {
|
||||
flex: 0 0 4.5rem;
|
||||
|
||||
@@ -180,12 +180,11 @@ html:not([lang='en'])
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
min-height: 30px;
|
||||
color: var(--text-primary);
|
||||
background: #09121b;
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 4px;
|
||||
font: inherit;
|
||||
font-size: 0.65rem;
|
||||
--gev-field-background: #09121b;
|
||||
--gev-field-border-color: var(--glass-border);
|
||||
}
|
||||
#gev-voice-control:hover .gev-voice-provider-field,
|
||||
#gev-voice-control:focus-within .gev-voice-provider-field {
|
||||
|
||||
@@ -42,8 +42,6 @@
|
||||
max-height: calc(var(--right-stack-max-height, 50svh) - 64px);
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(0, 212, 255, 0.46) transparent;
|
||||
}
|
||||
#weather-panel.collapsed #weather-panel-body,
|
||||
#weather-panel[hidden],
|
||||
|
||||
Reference in new issue
Block a user