mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
Merge main into the Utilities panel, rebuilt on the Vue panel rail
main moved the panels to Vue views and a one-open-panel rail, and removed
the two templates this branch edited. This merge carries the branch onto
that architecture; it adds no behaviour of its own beyond the port.
- The Utilities panel is a RailPanel in LayerPanelsView with its own rail
item (handyman). LayerPanel renders the Directions rows into its list with
LayerRows, and falls back to Data Layers without it.
- Draw leaves DisplayPanel for DrawControls, DrawMarkList and DrawSwatches,
which render what the draw tool publishes to the display store and call
its actions, as GeoImport does. drawMarks.js words the rows; the DOM-built
drawMarkList.js is gone. A row's fly-to is an immediate navigation like
Import's ('drawing' gets its Cockpit refusal), the name field keeps a
draft through v-field-value, and the colours are one radio group with a
single tab stop.
- UiSegmented draws an option's icon, named by its label, for the shape
buttons.
- The catalogs take main's languages with this branch's strings, Arabic
included, and the tests follow the views to their new homes.
This commit is contained in:
commit
4ebb287d36
699 files changed
+46673
-20476
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);
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.42rem;
|
||||
color: rgba(206, 232, 239, 0.72);
|
||||
font-size: 0.58rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.13rem;
|
||||
color: var(--dock-label-color);
|
||||
font-size: var(--dock-label-font-size);
|
||||
font-weight: var(--dock-label-font-weight);
|
||||
letter-spacing: var(--dock-label-letter-spacing);
|
||||
}
|
||||
|
||||
#command-dock .dock-label-icon {
|
||||
|
||||
+179
-30
@@ -81,16 +81,104 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* A longer translated title in a collapsed rail launcher tightens its
|
||||
tracking rather than pushing the disclosure button out of the card. */
|
||||
html:not([lang='en'])
|
||||
#right-context-rail
|
||||
.panel-collapsible.collapsed
|
||||
.panel-title {
|
||||
/* Side panel titles read like the command dock's labels: one type for every
|
||||
panel, collapsed or open, and bright enough to read over the globe. The
|
||||
dock's type is in rem, but a collapsed launcher is a fixed 176px, so the
|
||||
rail caps it at its 16px-root size: a larger browser font must not cut
|
||||
Recent Imagery short. */
|
||||
:is(#left-panel-stack, #right-context-rail)
|
||||
:is(.panel-header > .panel-title, .pp-header-label) {
|
||||
font-family: var(--font-mono);
|
||||
font-size: min(var(--dock-label-font-size), 9.28px);
|
||||
font-weight: var(--dock-label-font-weight);
|
||||
letter-spacing: min(var(--dock-label-letter-spacing), 2.08px);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* A hovered panel sets --rail-label-hover on itself (see below); its own
|
||||
header picks it up. Radio sits inside Context and keeps its own colour. */
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
:is(#left-panel-stack, #right-context-rail)
|
||||
:is(
|
||||
.panel-header > .panel-title,
|
||||
.pp-header-label,
|
||||
:is(.panel-header, .pp-header-row) > .panel-collapse-btn
|
||||
) {
|
||||
color: var(--rail-label-hover, var(--dock-label-color));
|
||||
}
|
||||
|
||||
#radio-panel {
|
||||
--rail-label-hover: initial;
|
||||
}
|
||||
|
||||
/* A title never pushes its toggle out of the card: one that does not fit
|
||||
ends in an ellipsis. Cyber draws a marker left of the title, which a clip
|
||||
would cut, and keeps its own header layout. */
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
:is(#left-panel-stack, #right-context-rail)
|
||||
:is(.panel-header > .panel-title, .pp-header-label) {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
letter-spacing: 1.5px;
|
||||
}
|
||||
|
||||
/* A collapsed launcher is 176px wide and its divider has no room to draw,
|
||||
so the header drops it and packs closer. English titles such as Recent
|
||||
Imagery then fit whole. Only the rail's own launchers: Radio, nested in
|
||||
Context, keeps its divider. */
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
:is(#left-panel-stack, #right-context-rail)
|
||||
> .panel-collapsible.collapsed
|
||||
> [class$='-panel-inner']
|
||||
> .panel-header,
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
:is(#left-panel-stack, #right-context-rail)
|
||||
> #pp-toggles.collapsed
|
||||
> .pp-header-row {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
:is(#left-panel-stack, #right-context-rail)
|
||||
> .panel-collapsible.collapsed
|
||||
> [class$='-panel-inner']
|
||||
> .panel-header
|
||||
> .panel-divider,
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
:is(#left-panel-stack, #right-context-rail)
|
||||
> #pp-toggles.collapsed
|
||||
> .pp-header-row
|
||||
> .panel-divider {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
:is(#left-panel-stack, #right-context-rail)
|
||||
> .panel-collapsible.collapsed
|
||||
> [class$='-panel-inner']
|
||||
> .panel-header
|
||||
> .panel-collapse-btn,
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
:is(#left-panel-stack, #right-context-rail)
|
||||
> #pp-toggles.collapsed
|
||||
> .pp-header-row
|
||||
> .panel-collapse-btn {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* Translated titles run longer (Aktuelle Bilddaten), so a collapsed rail
|
||||
launcher sets them in compact type rather than pushing the disclosure
|
||||
button out of the card or cutting the word short. Chinese and Japanese
|
||||
titles fit at full size and stay at or above their 9px floor (cjk.css);
|
||||
Radio, nested in Context, is not a launcher. */
|
||||
html:not([lang='en']):not(:lang(zh)):not(:lang(ja))
|
||||
:is(#left-panel-stack, #right-context-rail)
|
||||
> .panel-collapsible.collapsed
|
||||
> [class$='-panel-inner']
|
||||
> .panel-header
|
||||
> .panel-title {
|
||||
font-size: 8px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.panel-divider {
|
||||
@@ -109,6 +197,8 @@ html:not([lang='en'])
|
||||
font-family: var(--font-mono);
|
||||
font-size: 13px;
|
||||
line-height: 1;
|
||||
/* A long title or a header action must not squeeze the toggle. */
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background-color var(--transition-fast),
|
||||
@@ -116,17 +206,52 @@ html:not([lang='en'])
|
||||
color var(--transition-fast);
|
||||
}
|
||||
|
||||
/* A long translated title must not squeeze the toggle into a sliver. */
|
||||
html:not([lang='en']) .panel-collapse-btn {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.panel-collapse-btn:hover {
|
||||
border-color: var(--glass-border-hover);
|
||||
color: var(--text-primary);
|
||||
background: rgba(255, 255, 255, 0.07);
|
||||
}
|
||||
|
||||
/* A side panel answers the pointer as the command dock's trays do: a
|
||||
brighter edge and an accent title and toggle. Touch screens have no hover,
|
||||
so a tap must not leave a panel lit. */
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
:is(#left-panel-stack, #right-context-rail)
|
||||
:where(
|
||||
.data-panel-inner,
|
||||
.utilities-panel-inner,
|
||||
.scene-panel-inner,
|
||||
.cctv-panel-inner,
|
||||
.weather-panel-inner,
|
||||
.recent-imagery-panel-inner,
|
||||
.street-level-panel-inner,
|
||||
.global-context-panel-inner,
|
||||
#pp-toggles:not(.collapsed),
|
||||
#pp-toggles.collapsed > .pp-header-row
|
||||
) {
|
||||
transition: border-color var(--transition-fast);
|
||||
}
|
||||
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
:is(#left-panel-stack, #right-context-rail)
|
||||
:where(
|
||||
.data-panel-inner,
|
||||
.utilities-panel-inner,
|
||||
.scene-panel-inner,
|
||||
.cctv-panel-inner,
|
||||
.weather-panel-inner,
|
||||
.recent-imagery-panel-inner,
|
||||
.street-level-panel-inner,
|
||||
.global-context-panel-inner,
|
||||
#pp-toggles:not(.collapsed),
|
||||
#pp-toggles.collapsed > .pp-header-row
|
||||
):hover {
|
||||
--rail-label-hover: var(--accent);
|
||||
border-color: var(--glass-border-hover);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Button Grid ──────────────────────────────── */
|
||||
.button-grid {
|
||||
display: flex;
|
||||
@@ -431,12 +556,21 @@ html:not([lang='en']) .panel-collapse-btn {
|
||||
}
|
||||
|
||||
#pp-toggles .pp-header-label {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 8px;
|
||||
letter-spacing: 1.4px;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
/* Display's title and toggle sit where the other panels' do: 14px of panel
|
||||
padding plus a 4px header inset. The cyber theme sets its own insets. */
|
||||
:root:not([data-ui-theme='cyber']) #pp-toggles.collapsed > .pp-header-row {
|
||||
padding: 8px 18px;
|
||||
}
|
||||
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
#pp-toggles:not(.collapsed)
|
||||
> .pp-header-row {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
#pp-toggles > .pp-header-row .pp-collapse-btn {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
@@ -445,6 +579,16 @@ html:not([lang='en']) .panel-collapse-btn {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* In the default theme Display's toggle matches the other panels' 22px. */
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
#pp-toggles
|
||||
> .pp-header-row
|
||||
.pp-collapse-btn {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
#pp-toggles.collapsed {
|
||||
gap: 0;
|
||||
}
|
||||
@@ -783,21 +927,6 @@ html:not([lang='en']) .pp-slider-row > .pp-slider-mini-label {
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
}
|
||||
#utilities-panel .draw-color[data-color='primary'] {
|
||||
background: #8be9ff;
|
||||
}
|
||||
#utilities-panel .draw-color[data-color='amber'] {
|
||||
background: #ffb547;
|
||||
}
|
||||
#utilities-panel .draw-color[data-color='cyan'] {
|
||||
background: #39d0ff;
|
||||
}
|
||||
#utilities-panel .draw-color[data-color='green'] {
|
||||
background: #5dff9f;
|
||||
}
|
||||
#utilities-panel .draw-color[data-color='red'] {
|
||||
background: #ff6b6b;
|
||||
}
|
||||
/* The swatch keeps its own colour in every theme; only a ring marks it. */
|
||||
#utilities-panel .draw-color:is(.active, :hover, :focus-visible) {
|
||||
border-color: rgba(0, 0, 0, 0.45);
|
||||
@@ -859,6 +988,8 @@ html:not([lang='en']) .pp-slider-row > .pp-slider-mini-label {
|
||||
opacity: 0.45;
|
||||
}
|
||||
.draw-mark-colors {
|
||||
/* Right after its swatch for keyboard and screen readers, shown last. */
|
||||
order: 1;
|
||||
flex-basis: 100%;
|
||||
padding: 2px 0 4px 16px;
|
||||
}
|
||||
@@ -1207,3 +1338,21 @@ html:not([lang='en']) .param-slider-row {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Touch: Draw's swatches keep their small dots but answer a 24px target. */
|
||||
@media (pointer: coarse) {
|
||||
#utilities-panel .draw-color,
|
||||
#utilities-panel .draw-mark-swatch {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#utilities-panel .draw-color::before,
|
||||
#utilities-panel .draw-mark-swatch::before {
|
||||
position: absolute;
|
||||
inset: 50% auto auto 50%;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
transform: translate(-50%, -50%);
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
+52
-17
@@ -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;
|
||||
@@ -203,6 +201,7 @@
|
||||
.recent-imagery-panel-inner,
|
||||
.street-level-panel-inner,
|
||||
.scene-panel-inner,
|
||||
.utilities-panel-inner,
|
||||
.global-context-panel-inner,
|
||||
.radio-panel-inner,
|
||||
#pp-toggles:not(.collapsed),
|
||||
@@ -224,6 +223,7 @@
|
||||
.recent-imagery-panel-inner,
|
||||
.street-level-panel-inner,
|
||||
.scene-panel-inner,
|
||||
.utilities-panel-inner,
|
||||
.global-context-panel-inner,
|
||||
.radio-panel-inner
|
||||
)
|
||||
@@ -247,7 +247,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:
|
||||
@@ -306,6 +306,7 @@
|
||||
.recent-imagery-panel-inner,
|
||||
.street-level-panel-inner,
|
||||
.scene-panel-inner,
|
||||
.utilities-panel-inner,
|
||||
.global-context-panel-inner,
|
||||
.radio-panel-inner
|
||||
)
|
||||
@@ -322,6 +323,7 @@
|
||||
.recent-imagery-panel-inner,
|
||||
.street-level-panel-inner,
|
||||
.scene-panel-inner,
|
||||
.utilities-panel-inner,
|
||||
.global-context-panel-inner,
|
||||
.radio-panel-inner
|
||||
)
|
||||
@@ -346,6 +348,7 @@
|
||||
.recent-imagery-panel-inner,
|
||||
.street-level-panel-inner,
|
||||
.scene-panel-inner,
|
||||
.utilities-panel-inner,
|
||||
.global-context-panel-inner,
|
||||
.radio-panel-inner
|
||||
)
|
||||
@@ -804,6 +807,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;
|
||||
@@ -1658,12 +1667,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']
|
||||
@@ -1682,6 +1706,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, #utilities-panel, #scene-panel) {
|
||||
@@ -1698,7 +1729,7 @@
|
||||
:root[data-ui-theme='cyber']
|
||||
#left-panel-stack
|
||||
> :is(#data-panel, #utilities-panel, #scene-panel).collapsed
|
||||
:is(.data-panel-inner, .scene-panel-inner) {
|
||||
:is(.data-panel-inner, .utilities-panel-inner, .scene-panel-inner) {
|
||||
box-sizing: border-box;
|
||||
height: 50px;
|
||||
min-height: 50px;
|
||||
@@ -1735,8 +1766,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
|
||||
@@ -2507,15 +2541,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);
|
||||
}
|
||||
|
||||
@@ -2730,10 +2762,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 {
|
||||
|
||||
@@ -126,20 +126,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
+17
-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;
|
||||
@@ -21,9 +24,17 @@
|
||||
--panel-radius: 16px;
|
||||
--btn-radius: 10px;
|
||||
--transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
/* Label type shared by the command dock and the side panel titles. */
|
||||
--dock-label-color: rgba(206, 232, 239, 0.72);
|
||||
--dock-label-font-size: 0.58rem;
|
||||
--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;
|
||||
@@ -492,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;
|
||||
@@ -501,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%);
|
||||
@@ -626,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,181 @@
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* A collapsible rail shows each item as an icon and its label while no panel
|
||||
is open, and folds the labels away once one is. The collapsed item keeps
|
||||
the plain rail's 32px square. */
|
||||
.ui-rail--collapsible {
|
||||
transition: border-radius var(--transition-fast);
|
||||
}
|
||||
.ui-rail--collapsible .ui-rail__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
min-width: 32px;
|
||||
padding: 0 6px;
|
||||
transition:
|
||||
color var(--transition-fast),
|
||||
background var(--transition-fast),
|
||||
border-color var(--transition-fast),
|
||||
border-radius var(--transition-fast);
|
||||
}
|
||||
.ui-rail__label {
|
||||
max-width: 0;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
font: var(--dock-label-font-weight) var(--dock-label-font-size) / 1
|
||||
var(--font-sans);
|
||||
letter-spacing: var(--dock-label-letter-spacing);
|
||||
transition:
|
||||
max-width var(--transition-fast),
|
||||
margin var(--transition-fast),
|
||||
opacity var(--transition-fast);
|
||||
}
|
||||
.ui-rail--expanded {
|
||||
border-radius: 21px;
|
||||
}
|
||||
.ui-rail--expanded .ui-rail__item {
|
||||
border-radius: 16px;
|
||||
}
|
||||
.ui-rail--expanded .ui-rail__label {
|
||||
max-width: 220px;
|
||||
margin-inline: 8px 8px;
|
||||
opacity: 1;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
+56
-53
@@ -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;
|
||||
@@ -40,7 +40,8 @@
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
#data-panel .panel-glow {
|
||||
#data-panel .panel-glow,
|
||||
#utilities-panel .panel-glow {
|
||||
position: absolute;
|
||||
inset: -20px;
|
||||
background: radial-gradient(
|
||||
@@ -53,7 +54,8 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.data-panel-inner {
|
||||
.data-panel-inner,
|
||||
.utilities-panel-inner {
|
||||
position: relative;
|
||||
background: var(--glass-bg);
|
||||
border: 1px solid var(--glass-border);
|
||||
@@ -84,29 +86,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 +179,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 +222,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
|
||||
@@ -249,7 +231,7 @@
|
||||
box-sizing: border-box;
|
||||
justify-content: center;
|
||||
overflow: visible;
|
||||
padding: 8px 10px 8px 14px;
|
||||
padding: 8px 14px;
|
||||
}
|
||||
#right-context-rail #global-context-panel.collapsed .panel-header {
|
||||
margin: 0;
|
||||
@@ -260,6 +242,42 @@
|
||||
#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);
|
||||
}
|
||||
/* Each rail keeps its outer edge at the screen-side clearance, so a rail that
|
||||
widens (labelled pills while its side has no open panel) grows toward the
|
||||
middle of the screen. Collapsed, it sits 6px outside its stack. */
|
||||
.gev-panel-rail--left {
|
||||
inset-inline-start: calc(-1 * var(--panel-rail-clearance));
|
||||
}
|
||||
.gev-panel-rail--right {
|
||||
inset-inline-end: calc(-1 * var(--panel-rail-clearance));
|
||||
}
|
||||
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 +299,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 +401,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 +431,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 +445,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:
|
||||
@@ -989,13 +1003,6 @@
|
||||
#global-context-panel .panel-glow {
|
||||
display: none;
|
||||
}
|
||||
/* Expanded, its section titles (Context, Radio) stay legible over bright
|
||||
imagery. Cyber keeps its own red titles; collapsed launchers match siblings. */
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
#global-context-panel:not(.collapsed)
|
||||
.panel-title {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.global-context-panel-inner {
|
||||
position: relative;
|
||||
display: flex;
|
||||
@@ -1011,6 +1018,15 @@
|
||||
backdrop-filter: blur(24px) saturate(1.4);
|
||||
-webkit-backdrop-filter: blur(24px) saturate(1.4);
|
||||
}
|
||||
|
||||
/* Data Layers and Context share the 14px side padding of the other default
|
||||
theme panels, so every title and toggle sits 19px from its edge. Cyber
|
||||
keeps its own gutters. */
|
||||
:root:not([data-ui-theme='cyber'])
|
||||
:is(.data-panel-inner, .global-context-panel-inner) {
|
||||
padding-right: 14px;
|
||||
padding-left: 14px;
|
||||
}
|
||||
.global-context-actions {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
@@ -1254,19 +1270,6 @@ body.recording-mode #right-context-rail > [data-panel-id] {
|
||||
width: var(--panel-expanded-width);
|
||||
}
|
||||
|
||||
#utilities-panel .panel-glow {
|
||||
position: absolute;
|
||||
inset: -20px;
|
||||
background: radial-gradient(
|
||||
ellipse at center,
|
||||
var(--accent-dim) 0%,
|
||||
transparent 60%
|
||||
);
|
||||
filter: blur(20px);
|
||||
opacity: 0.25;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* The layer list takes its natural height; the panel scrolls as a whole. */
|
||||
.utilities-list {
|
||||
flex: 0 0 auto;
|
||||
|
||||
@@ -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 ────────────────────────── */
|
||||
@@ -403,7 +404,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,
|
||||
@@ -56,10 +54,6 @@
|
||||
#recent-imagery-split-line[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
#recent-imagery-panel.collapsed .panel-title {
|
||||
font-size: 8px;
|
||||
letter-spacing: 1.8px;
|
||||
}
|
||||
#recent-imagery-panel.collapsed .panel-header {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
@@ -172,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;
|
||||
@@ -247,7 +247,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;
|
||||
}
|
||||
+122
-42
@@ -1,6 +1,8 @@
|
||||
/* ── Scene Director Panel ───────────────────── */
|
||||
#scene-panel {
|
||||
--panel-expanded-width: 360px;
|
||||
/* Secondary text in Authoring: dimmer than labels, but legible on the globe. */
|
||||
--scene-quiet: rgba(232, 234, 237, 0.62);
|
||||
position: fixed;
|
||||
top: calc(var(--left-stack-top) + (var(--left-stack-gap) * 2));
|
||||
left: var(--left-stack-x);
|
||||
@@ -160,15 +162,15 @@
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* The scene's name takes its own row; NEW SCENE / DELETE share the next. */
|
||||
#scene-select {
|
||||
flex: 1;
|
||||
flex: 1 1 100%;
|
||||
min-width: 0;
|
||||
min-height: 32px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* A translated scene name needs the width: the select takes its own row,
|
||||
NEW / DEL share the next one. */
|
||||
html:not([lang='en']) #scene-select {
|
||||
flex: 1 1 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@@ -178,8 +180,7 @@ html:not([lang='en']) #scene-select {
|
||||
}
|
||||
|
||||
/* A translated action longer than its button wraps inside the button instead
|
||||
of running past it. English keeps its one-line buttons ("EXPORT PRESETS"
|
||||
included). */
|
||||
of running past it. English keeps its one-line buttons. */
|
||||
html:not([lang='en']) #scene-panel .scene-controls .scene-btn {
|
||||
white-space: normal;
|
||||
overflow-wrap: break-word;
|
||||
@@ -190,6 +191,70 @@ html:not([lang='en']) #scene-panel .scene-controls .scene-btn {
|
||||
min-width: 72px;
|
||||
}
|
||||
|
||||
#scene-panel .scene-btn {
|
||||
font-size: 10px;
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
/* Authoring's steps: SCENE, SHOTS, PREVIEW, SAVE & SHARE. */
|
||||
.scene-step-heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin: 10px 0 6px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 10px;
|
||||
letter-spacing: 1.6px;
|
||||
color: var(--scene-quiet);
|
||||
}
|
||||
|
||||
.scene-step-heading::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.panel-header + .scene-step-heading {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* One primary action per step: CAPTURE SHOT and PLAY. The panel id keeps
|
||||
this above Cyber's shared button surface. */
|
||||
#scene-panel .scene-btn-primary:not(:disabled) {
|
||||
color: var(--accent);
|
||||
border-color: color-mix(in srgb, var(--accent) 55%, transparent);
|
||||
background: color-mix(in srgb, var(--accent) 10%, transparent);
|
||||
}
|
||||
|
||||
/* The file row is housekeeping; it stays quieter than the steps above. */
|
||||
#scene-panel .scene-file-controls .scene-btn {
|
||||
font-size: 9px;
|
||||
/* Room for one long translated word (IMPORTIEREN, ІМПОРТУВАТИ) per line. */
|
||||
padding: 8px 4px;
|
||||
letter-spacing: 0.6px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* Keep the shared hover on the restyled actions. */
|
||||
#scene-panel .scene-btn-primary:hover:not(:disabled),
|
||||
#scene-panel .scene-file-controls .scene-btn:hover:not(:disabled) {
|
||||
color: var(--accent);
|
||||
border-color: var(--accent);
|
||||
background: color-mix(in srgb, var(--accent) 16%, transparent);
|
||||
}
|
||||
|
||||
.scene-shot-hint {
|
||||
margin: 0 0 8px;
|
||||
font-size: 11px;
|
||||
line-height: 1.4;
|
||||
color: var(--scene-quiet);
|
||||
}
|
||||
|
||||
#scene-panel:not(.has-shots) .scene-shot-hint {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#scene-start-btn,
|
||||
#scene-stop-btn,
|
||||
#scene-next-btn {
|
||||
@@ -200,39 +265,34 @@ html:not([lang='en']) #scene-panel .scene-controls .scene-btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
max-height: 160px;
|
||||
min-height: 96px;
|
||||
max-height: 260px;
|
||||
/* Two shot rows stay visible; the panel scrolls when space is short. */
|
||||
min-height: 128px;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 4px 2px 6px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.scene-shot-list::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.scene-shot-list::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
border-radius: 999px;
|
||||
margin-bottom: 4px;
|
||||
counter-reset: scene-shot;
|
||||
}
|
||||
|
||||
.scene-shot-empty {
|
||||
border: 1px dashed rgba(255, 255, 255, 0.14);
|
||||
border-radius: 8px;
|
||||
padding: 10px 8px;
|
||||
padding: 16px 12px;
|
||||
text-align: center;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 9px;
|
||||
letter-spacing: 1px;
|
||||
color: var(--text-dim);
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.3px;
|
||||
line-height: 1.5;
|
||||
color: var(--scene-quiet);
|
||||
}
|
||||
|
||||
.scene-shot-row {
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
border-radius: 9px;
|
||||
padding: 7px 8px;
|
||||
padding: 8px 10px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
counter-increment: scene-shot;
|
||||
transition:
|
||||
border-color var(--transition-fast),
|
||||
background var(--transition-fast);
|
||||
@@ -253,12 +313,38 @@ html:not([lang='en']) #scene-panel .scene-controls .scene-btn {
|
||||
.scene-shot-label {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 10px;
|
||||
letter-spacing: 1px;
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.4px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* While renaming, the label holds only the editor at full width. */
|
||||
.scene-shot-label:has(.scene-shot-rename) {
|
||||
overflow: visible;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.scene-shot-label:has(.scene-shot-rename)::before {
|
||||
content: none;
|
||||
}
|
||||
|
||||
/* Shots are numbered 01, 02… in playback order. */
|
||||
.scene-shot-label::before {
|
||||
content: counter(scene-shot, decimal-leading-zero);
|
||||
margin-right: 8px;
|
||||
font-size: 10px;
|
||||
color: var(--scene-quiet);
|
||||
}
|
||||
|
||||
.scene-shot-row.active .scene-shot-label::before {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.scene-shot-rename {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
@@ -270,11 +356,11 @@ html:not([lang='en']) #scene-panel .scene-controls .scene-btn {
|
||||
}
|
||||
|
||||
.scene-shot-meta {
|
||||
margin-top: 4px;
|
||||
margin-top: 5px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 8px;
|
||||
letter-spacing: 0.8px;
|
||||
color: var(--text-dim);
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.4px;
|
||||
color: var(--scene-quiet);
|
||||
}
|
||||
|
||||
.scene-shot-actions {
|
||||
@@ -285,7 +371,7 @@ html:not([lang='en']) #scene-panel .scene-controls .scene-btn {
|
||||
.scene-progress {
|
||||
margin-top: 8px;
|
||||
width: 100%;
|
||||
height: 18px;
|
||||
height: 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
@@ -315,9 +401,10 @@ html:not([lang='en']) #scene-panel .scene-controls .scene-btn {
|
||||
#scene-status {
|
||||
margin-top: 8px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 10px;
|
||||
letter-spacing: 1px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.4px;
|
||||
line-height: 1.4;
|
||||
color: var(--scene-quiet);
|
||||
}
|
||||
|
||||
#scene-runtime {
|
||||
@@ -367,6 +454,8 @@ html:not([lang='en']) #scene-panel .scene-controls .scene-btn {
|
||||
#cctv-panel.collapsed .cctv-summary-label,
|
||||
#cctv-panel.collapsed #cctv-summary,
|
||||
#scene-panel.collapsed .scene-controls,
|
||||
#scene-panel.collapsed .scene-step-heading,
|
||||
#scene-panel.collapsed .scene-shot-hint,
|
||||
#scene-panel.collapsed .scene-shot-list,
|
||||
#scene-panel.collapsed .scene-progress,
|
||||
#scene-panel.collapsed #scene-status,
|
||||
@@ -391,15 +480,6 @@ html:not([lang='en']) #scene-panel .scene-controls .scene-btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#data-panel.collapsed .panel-title,
|
||||
#cctv-panel.collapsed .panel-title,
|
||||
#radio-panel.collapsed .panel-title,
|
||||
#utilities-panel.collapsed .panel-title,
|
||||
#scene-panel.collapsed .panel-title {
|
||||
font-size: 8px;
|
||||
letter-spacing: 1.8px;
|
||||
}
|
||||
|
||||
#control-panel.collapsed .panel-inner,
|
||||
#data-panel.collapsed .data-panel-inner,
|
||||
#cctv-panel.collapsed .cctv-panel-inner,
|
||||
|
||||
@@ -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,18 +42,12 @@
|
||||
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],
|
||||
.weather-readout[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
#weather-panel.collapsed .panel-title {
|
||||
font-size: 8px;
|
||||
letter-spacing: 1.8px;
|
||||
}
|
||||
#weather-panel.collapsed .panel-header {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user