mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
Merge origin/i18n-spanish: whole-interface translation meets KartaView
This branch moved twice at once: upstream merged KartaView (#1121, a third keyless street-level provider sharing a new lineCoverage.js), and the remote i18n-spanish gained the whole-interface translation work (1993 catalog entries, translateKnown, data-i18n-runtime, the unmarked- string detector in npm run i18n) built on the Street View merge. Integration keeps the remote's mechanisms and re-adds KartaView on top: - presentMeta: remote's translateHint for coverage hints, plus the KartaView count line as its own literal tn pair (Russian plurals need the noun inside the template; keyed off coverageLabel.noun with the sequences pair as the default). - uiState: upstream's highlightsSequence legend gate (KartaView's lines do not highlight) with t('Selected'). - layer-panels.html: remote's data-i18n/data-i18n-runtime hooks plus the sl-since-note wiring KartaView's upload-date SINCE note needs. - kartaview viewer: the photo-navigation aria-label and img alt go through t(), as the remote's unmarked-string detector requires. - catalogs: remote's 1987-entry base (its refined keys supersede this branch's earlier Street View wording) plus the KartaView entries: coverage hints, the upload-date note, the streets count line (with Russian one/few/many/other forms) and the viewer strings. npm run i18n: complete, no unmarked strings. npm test: 6520/6522 (the one failure is the pre-existing environmental OPENAI_API_KEY leak in devFreshDotenv). Build passes.
This commit is contained in:
commit
32b11e27de
176 files changed
+9713
-2210
No files matched your search
@@ -12,7 +12,9 @@
|
||||
background: var(--glass-bg);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 18px 52px rgba(0, 0, 0, 0.48), inset 0 0 28px rgba(14, 78, 92, 0.12);
|
||||
box-shadow:
|
||||
0 18px 52px rgba(0, 0, 0, 0.48),
|
||||
inset 0 0 28px rgba(14, 78, 92, 0.12);
|
||||
backdrop-filter: blur(24px) saturate(1.4);
|
||||
font-family: var(--font-mono);
|
||||
letter-spacing: 0;
|
||||
@@ -153,33 +155,69 @@
|
||||
}
|
||||
|
||||
@keyframes bhote-embedded-anchor-enter {
|
||||
from { opacity: 0; transform: scale(0.2); }
|
||||
to { opacity: 1; transform: scale(1); }
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.2);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bhote-embedded-leader-enter {
|
||||
from { opacity: 0; stroke-dashoffset: 1; }
|
||||
to { opacity: 1; stroke-dashoffset: 0; }
|
||||
from {
|
||||
opacity: 0;
|
||||
stroke-dashoffset: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bhote-embedded-body-enter {
|
||||
from { opacity: 0; transform: translateY(8px) scale(0.98); }
|
||||
to { opacity: 1; transform: translateY(0) scale(1); }
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(8px) scale(0.98);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bhote-embedded-body-exit {
|
||||
from { opacity: 1; transform: translateY(0) scale(1); }
|
||||
to { opacity: 0; transform: translateY(6px) scale(0.985); }
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translateY(6px) scale(0.985);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bhote-embedded-leader-exit {
|
||||
from { opacity: 1; stroke-dashoffset: 0; }
|
||||
to { opacity: 0; stroke-dashoffset: 1; }
|
||||
from {
|
||||
opacity: 1;
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
stroke-dashoffset: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bhote-embedded-anchor-exit {
|
||||
from { opacity: 1; transform: scale(1); }
|
||||
to { opacity: 0; transform: scale(0.2); }
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: scale(0.2);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
@@ -243,16 +281,22 @@
|
||||
border-top: 3px solid #6be8ff;
|
||||
}
|
||||
|
||||
/* The title takes the full card width and up to two lines, so a translated
|
||||
title keeps its place name; the link sits beside the status below it. */
|
||||
.bhote-embedded-callout footer strong {
|
||||
grid-column: 1 / -1;
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
font-size: 11px;
|
||||
line-height: 1.25;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow-wrap: anywhere;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.bhote-embedded-callout footer small {
|
||||
grid-column: 1;
|
||||
align-self: center;
|
||||
color: rgba(151, 211, 224, 0.7);
|
||||
font-size: 8px;
|
||||
line-height: 1.35;
|
||||
@@ -260,8 +304,9 @@
|
||||
|
||||
.bhote-embedded-callout footer a {
|
||||
grid-column: 2;
|
||||
grid-row: 1 / span 2;
|
||||
grid-row: 2;
|
||||
align-self: center;
|
||||
white-space: nowrap;
|
||||
color: #6be8ff;
|
||||
font-size: 8px;
|
||||
text-decoration: none;
|
||||
@@ -354,6 +399,12 @@
|
||||
color: rgba(222, 240, 245, 0.88);
|
||||
}
|
||||
|
||||
/* A longer translated heading wraps; the readout beside it stays one piece. */
|
||||
html:not([lang='en']) .bhote-event-section-head span:last-child {
|
||||
flex: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.bhote-event-range {
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
@@ -372,6 +423,11 @@
|
||||
color: rgba(174, 208, 216, 0.62);
|
||||
}
|
||||
|
||||
/* Keep "16 OCT 2021" together so the year never wraps alone. */
|
||||
.bhote-event-date {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.bhote-event-story-nav {
|
||||
display: grid;
|
||||
grid-template-columns: 34px minmax(0, 1fr) 34px;
|
||||
@@ -388,7 +444,11 @@
|
||||
border-radius: 4px;
|
||||
color: var(--accent);
|
||||
background: rgba(7, 25, 35, 0.9);
|
||||
font: 22px/1 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;
|
||||
font:
|
||||
22px/1 'JetBrains Mono',
|
||||
'SFMono-Regular',
|
||||
Consolas,
|
||||
monospace;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -398,7 +458,7 @@
|
||||
align-content: center;
|
||||
column-gap: 9px;
|
||||
min-width: 0;
|
||||
padding: 7px 10px;
|
||||
padding: 5px 10px;
|
||||
border: 1px solid rgba(88, 196, 218, 0.18);
|
||||
background: rgba(6, 19, 27, 0.72);
|
||||
}
|
||||
@@ -410,22 +470,28 @@
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
/* Up to two lines: a translated beat title keeps its subject. Two title lines
|
||||
and one meta line still fit the 54px arrow buttons, so the controls stay put. */
|
||||
.bhote-event-story-current strong {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
color: var(--text-primary);
|
||||
font-size: 11px;
|
||||
line-height: 1.25;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow-wrap: break-word;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
/* Phase / time / sources run long in translation: allow a second line. */
|
||||
.bhote-event-story-current small {
|
||||
display: -webkit-box;
|
||||
grid-column: 1 / -1;
|
||||
margin-top: 4px;
|
||||
margin-top: 3px;
|
||||
overflow: hidden;
|
||||
color: rgba(116, 216, 235, 0.7);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.bhote-event-actions {
|
||||
@@ -448,14 +514,15 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* One line in English; a longer translation wraps to a second line. */
|
||||
.bhote-event-actions button {
|
||||
min-width: 0;
|
||||
min-height: 30px;
|
||||
padding: 0 5px;
|
||||
padding: 3px 5px;
|
||||
overflow: hidden;
|
||||
font-size: 8px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
line-height: 1.2;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.bhote-event-actions button:disabled,
|
||||
@@ -469,7 +536,7 @@
|
||||
border-color: rgba(88, 196, 218, 0.36);
|
||||
}
|
||||
|
||||
.bhote-event-actions button[data-action="cinematic"][aria-pressed="true"] {
|
||||
.bhote-event-actions button[data-action='cinematic'][aria-pressed='true'] {
|
||||
color: #071017;
|
||||
border-color: var(--accent);
|
||||
background: var(--accent);
|
||||
@@ -593,7 +660,6 @@
|
||||
max-height: 48vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
* {
|
||||
@@ -602,7 +668,8 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body {
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -78,6 +78,14 @@ html[lang='es'] .cctv-controls {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* A translated label longer than its button wraps inside the button instead
|
||||
of running past the panel edge. English labels fit on one line, so the
|
||||
English layout is unchanged. */
|
||||
.cctv-controls .scene-btn {
|
||||
white-space: normal;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.cctv-calibration-block {
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 9px;
|
||||
@@ -165,6 +173,9 @@ html[lang='es'] .cctv-controls {
|
||||
padding: 6px 8px;
|
||||
font-family: var(--font-sans);
|
||||
font-size: 12px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@@ -373,3 +384,33 @@ html[lang='es'] .cctv-controls {
|
||||
color: #a8f7ff;
|
||||
text-shadow: 0 0 10px rgba(0, 212, 255, 0.18);
|
||||
}
|
||||
|
||||
/* Longer translated labels and units: tighter calibration chips keep the
|
||||
seven values to two rows so SAVE / RESET stay in view, and the camera
|
||||
select uses the compact mono face. English keeps its original look. */
|
||||
html:not([lang='en']) .cctv-cal-readout {
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
html:not([lang='en']) .cctv-cal-value {
|
||||
flex: 1 1 auto;
|
||||
white-space: nowrap;
|
||||
letter-spacing: 0.3px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
html:not([lang='en']) #cctv-camera-select {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
/* The camera name gets its own row; PREV / NEXT share the row below it. */
|
||||
html:not([lang='en']) .cctv-controls:has(#cctv-camera-select) {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
html:not([lang='en']) #cctv-camera-select {
|
||||
flex: 1 1 100%;
|
||||
order: -1;
|
||||
}
|
||||
@@ -569,6 +569,9 @@ body.cockpit-mode:has(#left-panel-stack > .panel-collapsible:not(.collapsed))
|
||||
text-align: center;
|
||||
vertical-align: -0.08em;
|
||||
}
|
||||
.cockpit-roll-token.is-separator {
|
||||
min-width: 0.3em;
|
||||
}
|
||||
.cockpit-roll-token.is-rolling {
|
||||
color: color-mix(in srgb, currentColor 92%, var(--cockpit-accent));
|
||||
text-shadow: 0 0 10px
|
||||
@@ -1499,6 +1502,12 @@ body.cockpit-mode #view-switcher button {
|
||||
.cockpit-signal-header > div {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
.cockpit-signal-kicker {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.cockpit-signal-header strong {
|
||||
color: #fff;
|
||||
@@ -1526,6 +1535,11 @@ body.cockpit-mode #view-switcher button {
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
}
|
||||
/* A longer translated state label widens the cycle button; the other
|
||||
controls keep their size instead of being squeezed. */
|
||||
.cockpit-brief-actions > * {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.cockpit-brief-actions button {
|
||||
display: grid;
|
||||
width: 23px;
|
||||
@@ -1548,10 +1562,13 @@ body.cockpit-mode #view-switcher button {
|
||||
font-size: 14px;
|
||||
}
|
||||
#cockpit-brief-auto {
|
||||
width: 50px;
|
||||
width: auto;
|
||||
min-width: 50px;
|
||||
padding: 0 4px;
|
||||
color: rgba(244, 251, 255, 0.42);
|
||||
font: 700 6px/1 var(--font-mono);
|
||||
letter-spacing: 0.05em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
#cockpit-brief-auto[aria-pressed='true'] {
|
||||
border-color: var(--cockpit-accent);
|
||||
@@ -1857,14 +1874,20 @@ body.cockpit-mode #view-switcher button {
|
||||
letter-spacing: 0.1em;
|
||||
}
|
||||
.cockpit-signal-footer > span {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.cockpit-brief-tabs {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
gap: 3px;
|
||||
}
|
||||
.cockpit-brief-tabs button {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.cockpit-brief-tabs button {
|
||||
padding: 2px 4px;
|
||||
border: 0;
|
||||
|
||||
@@ -696,3 +696,54 @@
|
||||
rgba(232, 234, 237, 0.24)
|
||||
);
|
||||
}
|
||||
|
||||
/* Language switch under the Visual Presets title: small mono chips, the
|
||||
active language in accent like the other dock toggles. */
|
||||
#command-dock #control-panel .dock-locale-switch {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 0.2rem;
|
||||
margin-top: 0.22rem;
|
||||
}
|
||||
|
||||
#command-dock .dock-locale-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
min-width: 1.35rem;
|
||||
height: 0.95rem;
|
||||
margin: 0;
|
||||
padding: 0 0.22rem;
|
||||
color: var(--text-secondary);
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 0.18rem;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.46rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.06rem;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
color var(--transition-fast),
|
||||
border-color var(--transition-fast),
|
||||
background var(--transition-fast);
|
||||
}
|
||||
|
||||
#command-dock .dock-locale-btn:hover {
|
||||
color: var(--text-primary);
|
||||
border-color: var(--glass-border-hover);
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
#command-dock .dock-locale-btn:focus-visible {
|
||||
outline: 1px solid var(--text-primary);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
#command-dock .dock-locale-btn.active {
|
||||
color: var(--accent);
|
||||
border-color: rgba(0, 212, 255, 0.55);
|
||||
background: var(--accent-dim);
|
||||
}
|
||||
@@ -392,6 +392,12 @@
|
||||
transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
}
|
||||
|
||||
/* The side wings sit at z-index 100 (controls.css, location.css), so their
|
||||
open trays would cover the alert; lift the voice column while it shows. */
|
||||
#command-dock > #gev-voice-control[data-status='error']:not(.error-dismissed) {
|
||||
z-index: 101;
|
||||
}
|
||||
|
||||
#command-dock
|
||||
#gev-voice-control[data-status='error']:not(.error-dismissed)
|
||||
.gev-voice-error-tray {
|
||||
|
||||
@@ -221,6 +221,18 @@
|
||||
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 {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
letter-spacing: 1.5px;
|
||||
}
|
||||
|
||||
.panel-divider {
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
@@ -228,6 +240,8 @@
|
||||
}
|
||||
|
||||
.panel-collapse-btn {
|
||||
/* A long translated title must not squeeze the toggle into a sliver. */
|
||||
flex-shrink: 0;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 6px;
|
||||
@@ -770,6 +784,15 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* One label column across the Display rows: --pp-label-chars is the longest
|
||||
label of the active language (set by applicationShell), and the labels are
|
||||
monospace, so every slider track starts at the same x whatever the
|
||||
language. 0.125em is the 1px letter-spacing at 8px. */
|
||||
.pp-slider-row > .pp-slider-mini-label {
|
||||
flex: 0 0 auto;
|
||||
min-width: calc(var(--pp-label-chars, 0) * (1ch + 0.125em));
|
||||
}
|
||||
|
||||
.detection-slider-row {
|
||||
min-width: 170px;
|
||||
padding: 5px 8px 6px;
|
||||
@@ -966,6 +989,11 @@
|
||||
#draw-mode-row {
|
||||
min-width: 184px;
|
||||
}
|
||||
/* Equal thirds with room around the label, so a long shape name never runs
|
||||
edge to edge in its segment. */
|
||||
#draw-mode-row .pp-mode-btn {
|
||||
padding: 0 4px;
|
||||
}
|
||||
#draw-label-row {
|
||||
flex-wrap: wrap;
|
||||
row-gap: 6px;
|
||||
@@ -987,8 +1015,11 @@
|
||||
.pp-text-input:focus {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
/* Sized to its longest colour name so a translated one is never clipped;
|
||||
74px keeps the English width. */
|
||||
.draw-color-select {
|
||||
flex: 0 0 74px;
|
||||
flex: 0 0 auto;
|
||||
min-width: 74px;
|
||||
}
|
||||
.draw-clear-btn {
|
||||
flex: 0 0 auto;
|
||||
@@ -1234,15 +1265,20 @@ body.ui-clean-view #scene-runtime.active {
|
||||
);
|
||||
}
|
||||
|
||||
.param-slider-row {
|
||||
display: flex;
|
||||
/* One label column for every parameter, as wide as the longest label in the
|
||||
active language, so all slider tracks start and end at the same x. */
|
||||
#param-sliders {
|
||||
display: grid;
|
||||
grid-template-columns: max-content minmax(0, 1fr) max-content;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.param-slider-row:last-child {
|
||||
margin-bottom: 0;
|
||||
.param-slider-row {
|
||||
display: grid;
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: subgrid;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.param-label {
|
||||
|
||||
@@ -1371,10 +1371,12 @@
|
||||
padding: 2px 4px;
|
||||
}
|
||||
|
||||
/* ALPR keeps its nearby count in the legend while a wider status chip is shown. */
|
||||
/* ALPR keeps its nearby count in the legend while a wider status chip
|
||||
(including ENABLING / DISABLING) is shown. */
|
||||
.data-toggle-row[data-layer-id='alpr-cameras']
|
||||
.data-toggle-right:has(
|
||||
.data-toggle-btn:is(
|
||||
.transitioning,
|
||||
.feed-loading,
|
||||
.feed-degraded,
|
||||
.feed-stale,
|
||||
|
||||
+88
-5
@@ -250,8 +250,12 @@
|
||||
gap: 5px;
|
||||
}
|
||||
.radio-transport button {
|
||||
min-width: 0;
|
||||
min-height: 30px;
|
||||
padding: 6px 3px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
border: 1px solid rgba(65, 220, 231, 0.28);
|
||||
border-radius: 5px;
|
||||
color: #8feaf2;
|
||||
@@ -610,10 +614,14 @@
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* The name takes the room the status column leaves; a long word wraps inside
|
||||
it instead of pushing the toggle out of line. */
|
||||
.data-toggle-left {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.data-icon {
|
||||
@@ -621,16 +629,37 @@
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* A fixed icon box, so every name (and the meta line under it, 26px in)
|
||||
starts at the same x whatever the glyph's width. */
|
||||
.data-toggle-left .data-icon {
|
||||
flex: none;
|
||||
width: 18px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.data-name {
|
||||
min-width: 0;
|
||||
font-family: var(--font-sans);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
letter-spacing: 0.3px;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
/* Translated layer names are longer: break between words, and hyphenate a
|
||||
word that still does not fit instead of splitting it anywhere. */
|
||||
html:not([lang='en']) .data-name {
|
||||
overflow-wrap: break-word;
|
||||
hyphens: auto;
|
||||
/* No two-letter pieces such as "es-paciales": a word hyphenates only when
|
||||
at least four letters stay on each side. */
|
||||
hyphenate-limit-chars: 8 4 4;
|
||||
}
|
||||
|
||||
.data-toggle-right {
|
||||
display: flex;
|
||||
flex: 0 1 auto;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
@@ -640,8 +669,56 @@
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.5px;
|
||||
color: var(--text-secondary);
|
||||
min-width: 30px;
|
||||
text-align: right;
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
/* Row text pieces that must not break inside: a hyphenated code (ADS-B) and
|
||||
the space before a " · " separator (see setRowText in layerPanel.js). */
|
||||
.data-keep {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* A count phrase ("24 nearby · 2 on screen") takes its own line under the
|
||||
name instead of squeezing it beside the toggle. */
|
||||
.data-toggle-row--count-line .data-toggle-top {
|
||||
flex-wrap: wrap;
|
||||
row-gap: 2px;
|
||||
}
|
||||
|
||||
.data-toggle-row--count-line .data-toggle-right {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
/* A zero basis, so a long name wraps beside the toggle instead of pushing the
|
||||
toggle onto the count's line. */
|
||||
.data-toggle-row--count-line .data-toggle-left {
|
||||
flex-basis: 0;
|
||||
}
|
||||
|
||||
.data-toggle-row--count-line .data-count {
|
||||
order: 1;
|
||||
flex: 1 0 100%;
|
||||
padding-left: 26px;
|
||||
font-size: 10px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* The "—" placeholder gives way to a wide status chip, so the name keeps the
|
||||
room it needs. */
|
||||
.data-toggle-right:has(
|
||||
.data-toggle-btn:is(
|
||||
.transitioning,
|
||||
.feed-loading,
|
||||
.feed-degraded,
|
||||
.feed-stale,
|
||||
.feed-partial,
|
||||
.feed-fallback,
|
||||
.feed-unavailable
|
||||
)
|
||||
)
|
||||
.data-count-empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.data-toggle-btn {
|
||||
@@ -662,8 +739,10 @@
|
||||
min-width var(--transition-fast),
|
||||
letter-spacing var(--transition-fast);
|
||||
outline: none;
|
||||
flex: none;
|
||||
min-width: 38px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* An inset ring stays visible inside the scrolling layer list. */
|
||||
@@ -672,13 +751,15 @@
|
||||
outline-offset: -3px;
|
||||
}
|
||||
|
||||
/* Status chips up to eight characters share one width; a longer word grows
|
||||
its own chip rather than widening every chip and squeezing every name. */
|
||||
.data-toggle-btn.feed-loading,
|
||||
.data-toggle-btn.feed-degraded,
|
||||
.data-toggle-btn.feed-stale,
|
||||
.data-toggle-btn.feed-partial,
|
||||
.data-toggle-btn.feed-fallback,
|
||||
.data-toggle-btn.feed-unavailable {
|
||||
min-width: 82px;
|
||||
min-width: 72px;
|
||||
letter-spacing: 0.8px;
|
||||
}
|
||||
|
||||
@@ -883,14 +964,16 @@
|
||||
.data-row-list-lead {
|
||||
min-width: 34px;
|
||||
flex: none;
|
||||
white-space: nowrap;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
/* The whole instruction wraps under itself rather than losing its tail:
|
||||
"Turn right onto …" says nothing, and Russian or Spanish run longer. */
|
||||
.data-row-list-text {
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
/* A list line that states a limit rather than offering an action. */
|
||||
|
||||
@@ -293,17 +293,34 @@
|
||||
height: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
/* Outside A / B there is one chip (SHOW): it takes the whole row, so a long
|
||||
translation is not cut to a stub. */
|
||||
.recent-imagery-readout:not([data-mode='ab'])
|
||||
.ri-card-chips
|
||||
.ri-chip[data-slot='a'] {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
.recent-imagery-readout:not([data-mode='ab'])
|
||||
.ri-card-chips
|
||||
.ri-chip[data-slot='b'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 4. The selection bar: mode, then two rows that are always there. */
|
||||
/* 4. The selection bar: mode, then two rows that are always there. The slot
|
||||
rows share the bar's columns (subgrid), so the tag column fits the longest
|
||||
tag in either row and both rows stay aligned. */
|
||||
.recent-imagery-readout .ri-selection {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(44px, max-content) minmax(0, 1fr) 22px;
|
||||
grid-template-rows: 20px 20px 20px;
|
||||
column-gap: 6px;
|
||||
row-gap: 4px;
|
||||
padding: 5px;
|
||||
border: 1px solid var(--weather-card-border);
|
||||
border-radius: 6px;
|
||||
}
|
||||
.recent-imagery-readout .ri-mode {
|
||||
grid-column: 1 / -1;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 4px;
|
||||
@@ -312,14 +329,16 @@
|
||||
height: 20px;
|
||||
}
|
||||
.recent-imagery-readout .ri-slot {
|
||||
grid-column: 1 / -1;
|
||||
display: grid;
|
||||
grid-template-columns: 44px minmax(0, 1fr) 22px;
|
||||
grid-template-columns: subgrid;
|
||||
align-items: center;
|
||||
column-gap: 6px;
|
||||
height: 20px;
|
||||
}
|
||||
.recent-imagery-readout .ri-slot-tag {
|
||||
height: 16px;
|
||||
padding: 0 3px;
|
||||
line-height: 16px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
border-radius: 3px;
|
||||
@@ -347,6 +366,15 @@
|
||||
color: var(--text-secondary);
|
||||
font-style: italic;
|
||||
}
|
||||
/* A translated slot line (date · source · resolution · state) is longer:
|
||||
let it take a second line rather than cut off the state at the end. */
|
||||
html:not([lang='en']) .recent-imagery-readout .ri-slot {
|
||||
height: auto;
|
||||
min-height: 20px;
|
||||
}
|
||||
html:not([lang='en']) .recent-imagery-readout .ri-slot-value {
|
||||
white-space: normal;
|
||||
}
|
||||
.recent-imagery-readout .ri-unpin {
|
||||
width: 22px;
|
||||
padding: 0;
|
||||
@@ -355,19 +383,25 @@
|
||||
}
|
||||
|
||||
/* 5. Opacity (both images), SWAP and export, one row. SWAP keeps its slot
|
||||
while no swipe is live. */
|
||||
while no swipe is live. A label longer than its 76px column widens it to
|
||||
the label's longest word, and the value wraps under the label. */
|
||||
.recent-imagery-readout .ri-controls {
|
||||
display: grid;
|
||||
grid-template-columns: 76px minmax(0, 1fr) 40px 128px;
|
||||
grid-template-columns:
|
||||
minmax(76px, min-content) minmax(0, 1fr) minmax(40px, max-content)
|
||||
128px;
|
||||
align-items: center;
|
||||
column-gap: 6px;
|
||||
height: 22px;
|
||||
}
|
||||
.recent-imagery-readout .ri-opacity-label {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
gap: 4px;
|
||||
align-content: center;
|
||||
column-gap: 4px;
|
||||
font-size: 8px;
|
||||
line-height: 10px;
|
||||
letter-spacing: 1px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -385,7 +419,7 @@
|
||||
opacity: 0.35;
|
||||
}
|
||||
.recent-imagery-readout .ri-swap {
|
||||
width: 40px;
|
||||
min-width: 40px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
.recent-imagery-readout .ri-exports .rail-card-actions {
|
||||
|
||||
@@ -160,9 +160,20 @@
|
||||
padding: 6px 8px;
|
||||
font-family: var(--font-sans);
|
||||
font-size: 12px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* A translated action longer than its button wraps inside the button instead
|
||||
of running past it. English labels fit on one line, so the English layout
|
||||
is unchanged. */
|
||||
#scene-panel .scene-controls .scene-btn {
|
||||
white-space: normal;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.scene-btn {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||
@@ -454,3 +465,15 @@
|
||||
.director-sharing-dialog::backdrop {
|
||||
background: rgba(0, 8, 14, 0.65);
|
||||
}
|
||||
|
||||
html:not([lang='en']) #scene-select {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
/* A translated scene name needs the width: the select takes its own row and
|
||||
NEW / DELETE share the next one. */
|
||||
html:not([lang='en']) #scene-select {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
@@ -213,6 +213,20 @@ button.sl-status:focus-visible {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Longer translations: rather than squeeze the hint into a narrow column
|
||||
beside the button, put the button on its own line when both do not fit. */
|
||||
html:not([lang='en']) .sl-meta-row {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
html:not([lang='en']) .sl-meta-row .sl-meta {
|
||||
flex: 1 1 14em;
|
||||
}
|
||||
|
||||
html:not([lang='en']) .sl-meta-row .sl-nearest-btn {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
/* ── Errors ──────────────────────────────────────────────────────────────── */
|
||||
.sl-error {
|
||||
font-family: var(--font-sans);
|
||||
|
||||
@@ -82,3 +82,63 @@
|
||||
.gev-voice-cost-value[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* The dock heading row is only as wide as the voice column, so the cost
|
||||
cluster keeps its size and the state label gives way: a long translated
|
||||
state (ПОДКЛЮЧЕНИЕ, CONECTANDO) ends in an ellipsis instead of pushing the
|
||||
cost past the card edge. The kicker already collapses to zero width here;
|
||||
taking it out of the row returns its gap to the state label. The row also
|
||||
reaches left over the gutter beside the mic ring: at heading height the
|
||||
ring has already curved away. English fits as it is, so this applies to
|
||||
the other languages only. */
|
||||
html:not([lang='en']) #command-dock .gev-voice-heading {
|
||||
gap: 0.2rem;
|
||||
margin-left: -0.6rem;
|
||||
}
|
||||
|
||||
html:not([lang='en']) #command-dock .gev-voice-heading > .gev-voice-kicker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
html:not([lang='en']) #command-dock .gev-voice-heading > #gev-voice-status {
|
||||
flex: 0 1 auto;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
letter-spacing: 0;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
html:not([lang='en']) #command-dock .gev-voice-cost {
|
||||
gap: 0.16rem;
|
||||
padding-left: 0.1rem;
|
||||
}
|
||||
|
||||
html:not([lang='en']) #command-dock .gev-voice-tier-btn {
|
||||
min-width: 0;
|
||||
padding: 0 0.2rem;
|
||||
}
|
||||
|
||||
html:not([lang='en']) #command-dock .gev-voice-cost-value {
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
/* Live hints ("Ask or command", "Requesting microphone") run longer in other
|
||||
languages; give them a second line from the spare space under the readout
|
||||
rather than cutting them mid-word. */
|
||||
html:not([lang='en'])
|
||||
#command-dock
|
||||
#gev-voice-control:is(
|
||||
[data-status='connecting'],
|
||||
[data-status='listening'],
|
||||
[data-status='executing']
|
||||
)
|
||||
#gev-voice-detail {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
line-height: 1.15;
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
@@ -147,9 +147,10 @@
|
||||
.rail-card-line {
|
||||
white-space: pre-line;
|
||||
}
|
||||
/* One line stays reserved so the card does not jump when a status appears;
|
||||
a long (or translated) status wraps instead of being cut. */
|
||||
.weather-card [data-line-id='status'] {
|
||||
height: 1.45em;
|
||||
overflow-y: auto;
|
||||
min-height: 1.45em;
|
||||
color: var(--accent);
|
||||
}
|
||||
.rail-card-ramp {
|
||||
@@ -258,6 +259,7 @@ body.recording-mode .gev-wind-inspection-marker {
|
||||
top: 8px;
|
||||
left: 0;
|
||||
transform: translateX(-50%);
|
||||
white-space: nowrap;
|
||||
font: 9px/1.45 var(--font-mono);
|
||||
color: var(--text-dim);
|
||||
}
|
||||
@@ -311,9 +313,18 @@ body.recording-mode .gev-wind-inspection-marker {
|
||||
.rail-card-compact.status {
|
||||
color: var(--accent);
|
||||
}
|
||||
/* One label column per card, as wide as its longest label: chips line up
|
||||
across rows, and a long translated label never runs under its chips. */
|
||||
.rail-card-settings {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(56px, max-content) minmax(0, 1fr);
|
||||
column-gap: 6px;
|
||||
}
|
||||
.rail-card-setting {
|
||||
display: grid;
|
||||
grid-template-columns: 56px minmax(0, 1fr);
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: minmax(56px, max-content) minmax(0, 1fr);
|
||||
grid-template-columns: subgrid;
|
||||
align-items: baseline;
|
||||
gap: 6px;
|
||||
margin-top: 6px;
|
||||
|
||||
Reference in new issue
Block a user