feat(i18n): presentation extraction — t() call sites, data-i18n templates, locale selector

Route every application-owned presentation string through the i18n
core: t() call sites across the app/layers/ui/data/voice surfaces,
data-i18n* attributes in the src/ui/templates/*.html markup (applied at
boot by applyDocumentTranslations from src/standalone/application.js),
and the dock locale selector (runtime-rendered from the offered pair —
a single EN button while only English ships).

Rebased onto current main, where the extraction follows the module
splits: the shell wiring now lands in the owner modules
(panelChrome, visualSettings, locationNavigation, shareRestoration,
shellFeedback, applicationShell) and the voice status corpus in the
realtime* modules; scene status lives in director.js +
playbackAdapter.js.

Modules shared with the node runtime (loadingFeedback,
installationFeedback, regionalModel, keySetupCore, vessels ingestion)
stay environment-neutral English and localize at their browser render
sites (chip maps in shellFeedback/installations controls, the weather
table in cockpitBriefing, keySetupRequirementEnvVars compositions), so
the package-boundary graph stays clean; the browser boundary groups
now own the i18n modules they reach (scripts/package-boundaries.json).
This commit is contained in:
Meir Michanie committed 2026-10-01 12:41:20 +02:00
1 parent bde1e430ef
commit 284c4d4601
98 files changed
+3474 -1625

No files matched your search

+78 -76
View File
@@ -1,5 +1,5 @@
<!-- Aircraft first-person view. It can be entered from the visible view switcher. -->
<section id="cockpit-hud" aria-label="Aircraft cockpit view" hidden>
<section id="cockpit-hud" aria-label="Aircraft cockpit view" hidden data-i18n-aria-label="cockpit.hud.sectionLabel">
<!-- One optical coordinate system: every targeting mark shares this center.
Decorative only; telemetry and interactive panels remain separate. -->
<svg class="cockpit-frame-lines" viewBox="0 0 1200 720" preserveAspectRatio="none" aria-hidden="true" focusable="false">
@@ -61,19 +61,19 @@
<span>+20</span><span>+10</span><span class="active">00</span><span>−10</span><span>−20</span>
</div>
<div class="cockpit-horizon-guide">
<span></span><small>LEVEL</small><span></span>
<span data-i18n="cockpit.hud.level"></span><small>LEVEL</small><span></span>
</div>
<div id="cockpit-route-direction" class="cockpit-route-direction" hidden aria-label="Estimated destination direction">
<div id="cockpit-route-direction" class="cockpit-route-direction" hidden aria-label="Estimated destination direction" data-i18n-aria-label="cockpit.hud.routeDirectionAriaLabel">
<svg class="cockpit-route-chevron" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path d="M12 2 4.58 19.93c-.34.83.51 1.63 1.31 1.27L12 18.47l6.11 2.73c.8.36 1.65-.44 1.31-1.27L12 2Z" />
</svg>
<small id="cockpit-route-direction-label">DEST ---°</small>
<small id="cockpit-route-direction-label" data-i18n="cockpit.hud.routeDirectionIdle">DEST ---°</small>
</div>
<div class="cockpit-visor-status cockpit-visor-status-left">OPTICAL PLANE · 01</div>
<div class="cockpit-visor-status cockpit-visor-status-right">VISOR LOCK · ACTIVE</div>
<div class="cockpit-visor-status cockpit-visor-status-left" data-i18n="cockpit.hud.visorPlane">OPTICAL PLANE · 01</div>
<div class="cockpit-visor-status cockpit-visor-status-right" data-i18n="cockpit.hud.visorLock">VISOR LOCK · ACTIVE</div>
</div>
<div id="cockpit-speed-rim" class="cockpit-altitude-rim cockpit-speed-rim">
<span class="cockpit-altitude-rim-label">GROUND SPEED · KTS</span>
<span class="cockpit-altitude-rim-label" data-i18n="cockpit.readout.rimGroundSpeed">GROUND SPEED · KTS</span>
<div class="cockpit-altitude-rim-rail"></div>
<div class="cockpit-altitude-rim-scale">
<span data-speed-rim-tick><b></b><i></i></span>
@@ -92,7 +92,7 @@
</div>
</div>
<div id="cockpit-altitude-rim" class="cockpit-altitude-rim">
<span class="cockpit-altitude-rim-label">ALTITUDE · FT</span>
<span class="cockpit-altitude-rim-label" data-i18n="cockpit.readout.rimAltitude">ALTITUDE · FT</span>
<div class="cockpit-altitude-rim-rail"></div>
<div class="cockpit-altitude-rim-scale">
<span data-altitude-rim-tick><b></b><i></i></span>
@@ -112,36 +112,36 @@
</div>
<div class="cockpit-topline">
<div>
<span class="cockpit-kicker">FIRST PERSON</span>
<span class="cockpit-kicker" data-i18n="cockpit.hud.firstPerson">FIRST PERSON</span>
<strong id="cockpit-callsign">AIRCRAFT</strong>
<span id="cockpit-aircraft-meta" class="cockpit-aircraft-meta">LIVE TRACK · COURSE ALIGNED</span>
<span id="cockpit-aircraft-meta" class="cockpit-aircraft-meta" data-i18n="cockpit.hud.aircraftMetaInitial">LIVE TRACK · COURSE ALIGNED</span>
</div>
<div class="cockpit-vision-controls" role="group" aria-label="Cockpit vision style">
<button id="cockpit-vision-previous" class="cockpit-vision-nav" type="button" aria-label="Previous cockpit vision style" title="Previous vision style">
<div class="cockpit-vision-controls" role="group" aria-label="Cockpit vision style" data-i18n-aria-label="cockpit.hud.visionGroupAriaLabel">
<button id="cockpit-vision-previous" class="cockpit-vision-nav" type="button" aria-label="Previous cockpit vision style" title="Previous vision style" data-i18n-aria-label="cockpit.vision.previousAriaLabel" data-i18n-title="cockpit.vision.previousTitle">
<span class="material-symbols-outlined" aria-hidden="true">chevron_left</span>
<small>PREV</small>
<small data-i18n="cockpit.vision.previousLabel">PREV</small>
</button>
<button id="cockpit-vision-current" class="cockpit-vision-current" type="button" data-cockpit-vision="optical" aria-label="Current cockpit vision style: NORMAL. Activate for next style." title="Current style: NORMAL — click for next">
<small>CURRENT</small>
<button id="cockpit-vision-current" class="cockpit-vision-current" type="button" data-cockpit-vision="optical" aria-label="Current cockpit vision style: NORMAL. Activate for next style." title="Current style: NORMAL — click for next" data-i18n-aria-label="cockpit.vision.currentAriaLabel" data-i18n-title="cockpit.vision.currentTitle">
<small data-i18n="cockpit.vision.current">CURRENT</small>
<strong id="cockpit-vision-current-label" aria-live="polite">NORMAL</strong>
</button>
<button id="cockpit-vision-next" class="cockpit-vision-nav" type="button" aria-label="Next cockpit vision style" title="Next vision style">
<small>NEXT</small>
<button id="cockpit-vision-next" class="cockpit-vision-nav" type="button" aria-label="Next cockpit vision style" title="Next vision style" data-i18n-aria-label="cockpit.vision.nextAriaLabel" data-i18n-title="cockpit.vision.nextTitle">
<small data-i18n="cockpit.vision.nextLabel">NEXT</small>
<span class="material-symbols-outlined" aria-hidden="true">chevron_right</span>
</button>
</div>
</div>
<div class="cockpit-reticle material-symbols-outlined" aria-hidden="true">my_location</div>
<div class="cockpit-compass" aria-label="Current aircraft heading">
<div class="cockpit-compass" aria-label="Current aircraft heading" data-i18n-aria-label="cockpit.hud.compassAriaLabel">
<div class="cockpit-instrument-row">
<div class="cockpit-readout cockpit-speed">
<span class="cockpit-readout-label">GROUND SPEED</span>
<span class="cockpit-readout-label" data-i18n="cockpit.readout.groundSpeed">GROUND SPEED</span>
<strong id="cockpit-speed-value">---</strong>
<span class="cockpit-readout-unit">KTS</span>
</div>
<div class="cockpit-heading-value"><span id="cockpit-heading-value">---</span><small>°</small></div>
<div class="cockpit-readout cockpit-altitude">
<span class="cockpit-readout-label">ALTITUDE</span>
<span class="cockpit-readout-label" data-i18n="cockpit.readout.altitude">ALTITUDE</span>
<strong id="cockpit-altitude-value">-----</strong>
<span class="cockpit-readout-unit">FT</span>
</div>
@@ -153,26 +153,26 @@
<span id="cockpit-clock">--:--:--Z</span>
<span id="cockpit-position">--° · --°</span>
</div>
<aside id="cockpit-context" class="cockpit-context-window" aria-label="Contact cockpit summary" hidden>
<aside id="cockpit-context" class="cockpit-context-window" aria-label="Contact cockpit summary" hidden data-i18n-aria-label="cockpit.context.panelAriaLabel">
<header class="cockpit-context-header">
<div>
<span class="cockpit-context-kicker">CONTACT</span>
<strong id="cockpit-context-subject">CONTACTS · 250 KM</strong>
<strong id="cockpit-context-subject" data-i18n="cockpit.context.subjectWindow">CONTACTS · 250 KM</strong>
</div>
<nav class="cockpit-context-actions" aria-label="Contact navigation">
<button id="cockpit-context-previous" type="button" aria-label="Previous — prior visited contact in the 250 km window" title="Previous — prior visited contact in the 250 km window">
<nav class="cockpit-context-actions" aria-label="Contact navigation" data-i18n-aria-label="cockpit.context.navAriaLabel">
<button id="cockpit-context-previous" type="button" aria-label="Previous — prior visited contact in the 250 km window" title="Previous — prior visited contact in the 250 km window" data-i18n-aria-label="cockpit.context.previousAriaLabel" data-i18n-title="cockpit.context.previousTitle">
<span class="material-symbols-outlined" aria-hidden="true">skip_previous</span>
</button>
<button id="cockpit-context-next" type="button" aria-label="Next — nearest unvisited contact in the 250 km window" title="Next — nearest unvisited contact in the 250 km window">
<button id="cockpit-context-next" type="button" aria-label="Next — nearest unvisited contact in the 250 km window" title="Next — nearest unvisited contact in the 250 km window" data-i18n-aria-label="cockpit.context.nextAriaLabel" data-i18n-title="cockpit.context.nextTitle">
<span class="material-symbols-outlined" aria-hidden="true">skip_next</span>
</button>
<button id="cockpit-context-toggle" type="button" aria-label="Collapse Contact panel" aria-expanded="true" title="Collapse contact panel">
<button id="cockpit-context-toggle" type="button" aria-label="Collapse Contact panel" aria-expanded="true" title="Collapse contact panel" data-i18n-aria-label="cockpit.context.toggleAriaLabel" data-i18n-title="cockpit.context.toggleTitle">
<span class="material-symbols-outlined" aria-hidden="true">chevron_left</span>
</button>
</nav>
<span class="cockpit-context-qualifier">CONTEXT ONLY</span>
<span class="cockpit-context-qualifier" data-i18n="cockpit.context.qualifier">CONTEXT ONLY</span>
</header>
<div class="cockpit-context-cohorts" role="list" aria-label="Nearby cohort counts">
<div class="cockpit-context-cohorts" role="list" aria-label="Nearby cohort counts" data-i18n-aria-label="cockpit.context.cohortsAriaLabel">
<div role="listitem" data-context-cohort="flights"><span>FLT</span><strong>—</strong><small>OpenSky</small></div>
<div role="listitem" data-context-cohort="military"><span>MIL</span><strong>—</strong><small>adsb.lol</small></div>
<div role="listitem" data-context-cohort="ais-live-vessels"><span>AIS</span><strong>—</strong><small>AISStream</small></div>
@@ -181,14 +181,14 @@
<div class="cockpit-context-nearest">
<span id="cockpit-context-direction" class="material-symbols-outlined" aria-hidden="true">navigation</span>
<div>
<small>NEAREST OBSERVED / MAPPED</small>
<strong id="cockpit-context-nearest-label">NO AVAILABLE EXAMPLE</strong>
<small data-i18n="cockpit.context.nearestLabel">NEAREST OBSERVED / MAPPED</small>
<strong id="cockpit-context-nearest-label" data-i18n="cockpit.context.nearestEmpty">NO AVAILABLE EXAMPLE</strong>
</div>
<span id="cockpit-context-bearing">—</span>
<span id="cockpit-context-distance">—</span>
</div>
<footer class="cockpit-context-footer">
<span id="cockpit-context-uncertainty">AVAILABLE INPUTS ONLY · NOT AN ALL-CLEAR</span>
<span id="cockpit-context-uncertainty" data-i18n="cockpit.context.uncertaintyNote">AVAILABLE INPUTS ONLY · NOT AN ALL-CLEAR</span>
<button
id="cockpit-weather-toggle"
class="cockpit-context-weather"
@@ -196,6 +196,7 @@
aria-label="Enable cockpit weather effects"
aria-pressed="false"
title="Enable cockpit weather effects"
data-i18n-aria-label="cockpit.context.weatherEnableAriaLabel" data-i18n-title="cockpit.context.weatherEnableTitle"
>
<span>WX</span>
<strong id="cockpit-weather-state">OFF</strong>
@@ -203,35 +204,35 @@
<time id="cockpit-context-updated">--:--:--Z</time>
</footer>
</aside>
<aside id="cockpit-signal-stream" class="cockpit-signal-window" aria-label="Cockpit briefing carousel" hidden>
<section id="cockpit-route" class="cockpit-route-card" aria-label="Estimated flight plan" hidden>
<aside id="cockpit-signal-stream" class="cockpit-signal-window" aria-label="Cockpit briefing carousel" hidden data-i18n-aria-label="cockpit.brief.carouselAriaLabel">
<section id="cockpit-route" class="cockpit-route-card" aria-label="Estimated flight plan" hidden data-i18n-aria-label="cockpit.route.cardAriaLabel">
<div class="cockpit-route-heading">
<span class="cockpit-route-kicker">ESTIMATED FLIGHT PLAN</span>
<small id="cockpit-route-status">ROUTE DATA UNAVAILABLE</small>
<span class="cockpit-route-kicker" data-i18n="cockpit.route.kicker">ESTIMATED FLIGHT PLAN</span>
<small id="cockpit-route-status" data-i18n="cockpit.route.statusUnavailable">ROUTE DATA UNAVAILABLE</small>
</div>
<div class="cockpit-route-endpoints">
<div class="cockpit-route-airport">
<span>FROM</span>
<strong id="cockpit-route-from">UNKNOWN</strong>
<span data-i18n="cockpit.route.fromLabel">FROM</span>
<strong id="cockpit-route-from" data-i18n="cockpit.route.unknownEndpoint">UNKNOWN</strong>
</div>
<span class="cockpit-route-arrow material-symbols-outlined" aria-hidden="true">east</span>
<div class="cockpit-route-airport">
<span>TO</span>
<strong id="cockpit-route-to">UNKNOWN</strong>
<span data-i18n="cockpit.route.toLabel">TO</span>
<strong id="cockpit-route-to" data-i18n="cockpit.route.unknownEndpoint">UNKNOWN</strong>
</div>
</div>
</section>
<header class="cockpit-signal-header">
<div class="cockpit-brief-heading">
<span id="cockpit-brief-kicker" class="cockpit-signal-kicker"><i aria-hidden="true"></i> LIVE SIGNALS</span>
<strong id="cockpit-brief-subtitle">OBSERVED / MAPPED PINGS</strong>
<strong id="cockpit-brief-subtitle" data-i18n="cockpit.brief.subtitle">OBSERVED / MAPPED PINGS</strong>
</div>
<nav class="cockpit-brief-actions" aria-label="Cockpit briefing controls">
<button id="cockpit-brief-previous" type="button" aria-label="Previous briefing page" title="Previous briefing page">
<nav class="cockpit-brief-actions" aria-label="Cockpit briefing controls" data-i18n-aria-label="cockpit.brief.actionsAriaLabel">
<button id="cockpit-brief-previous" type="button" aria-label="Previous briefing page" title="Previous briefing page" data-i18n-aria-label="cockpit.brief.previousAriaLabel" data-i18n-title="cockpit.brief.previousTitle">
<span class="material-symbols-outlined" aria-hidden="true">chevron_left</span>
</button>
<span id="cockpit-brief-position" aria-live="polite">1 / 3</span>
<button id="cockpit-brief-next" type="button" aria-label="Next briefing page" title="Next briefing page">
<button id="cockpit-brief-next" type="button" aria-label="Next briefing page" title="Next briefing page" data-i18n-aria-label="cockpit.brief.nextAriaLabel" data-i18n-title="cockpit.brief.nextTitle">
<span class="material-symbols-outlined" aria-hidden="true">chevron_right</span>
</button>
<button
@@ -240,51 +241,52 @@
aria-label="CYCLE OFF"
aria-pressed="false"
title="Cycle briefing pages automatically every 9 seconds (Signals → News → Local). Pauses while you hover or focus the panel. Live signal data refreshes continuously either way."
data-i18n-title="cockpit.brief.autoTitle"
>CYCLE OFF</button>
<button id="cockpit-signal-toggle" type="button" aria-label="Collapse cockpit briefing panel" aria-expanded="true" title="Collapse briefing panel">
<button id="cockpit-signal-toggle" type="button" aria-label="Collapse cockpit briefing panel" aria-expanded="true" title="Collapse briefing panel" data-i18n-aria-label="cockpit.brief.collapseAriaLabel" data-i18n-title="cockpit.brief.collapseTitle">
<span class="material-symbols-outlined" aria-hidden="true">right_panel_close</span>
</button>
</nav>
</header>
<div class="cockpit-brief-stage">
<section data-cockpit-brief-page="signals" aria-label="Live signals">
<section data-cockpit-brief-page="signals" aria-label="Live signals" data-i18n-aria-label="cockpit.brief.signalsAriaLabel">
<ol id="cockpit-signal-list" class="cockpit-signal-list" aria-live="polite"></ol>
</section>
<section data-cockpit-brief-page="news" aria-label="Latest regional news" hidden>
<div id="cockpit-news-status" class="cockpit-brief-status">ACQUIRING REGIONAL NEWS</div>
<section data-cockpit-brief-page="news" aria-label="Latest regional news" hidden data-i18n-aria-label="cockpit.brief.newsAriaLabel">
<div id="cockpit-news-status" class="cockpit-brief-status" data-i18n="cockpit.brief.newsAcquiring">ACQUIRING REGIONAL NEWS</div>
<ol id="cockpit-news-list" class="cockpit-news-list" aria-live="polite"></ol>
</section>
<section data-cockpit-brief-page="local" aria-label="Location-based information" hidden>
<section data-cockpit-brief-page="local" aria-label="Location-based information" hidden data-i18n-aria-label="cockpit.brief.localAriaLabel">
<div class="cockpit-local-place">
<strong id="cockpit-local-place">RESOLVING REGION</strong>
<strong id="cockpit-local-place" data-i18n="cockpit.brief.localResolving">RESOLVING REGION</strong>
<small id="cockpit-local-coordinates">--</small>
</div>
<div class="cockpit-local-grid">
<div><span>TEMP</span><strong id="cockpit-local-temperature">—</strong><small>OPEN-METEO</small></div>
<div><span>WIND</span><strong id="cockpit-local-wind">—</strong><small id="cockpit-local-wind-direction">—</small></div>
<div><span>SKY</span><strong id="cockpit-local-condition">UNKNOWN</strong><small id="cockpit-local-cloud">—</small></div>
<div><span>PRECIP</span><strong id="cockpit-local-precipitation">—</strong><small>MM</small></div>
<div data-i18n="cockpit.brief.labelTemp"><span>TEMP</span><strong id="cockpit-local-temperature">—</strong><small>OPEN-METEO</small></div>
<div data-i18n="cockpit.brief.labelWind"><span>WIND</span><strong id="cockpit-local-wind">—</strong><small id="cockpit-local-wind-direction">—</small></div>
<div data-i18n="cockpit.brief.labelSky"><span>SKY</span><strong id="cockpit-local-condition">UNKNOWN</strong><small id="cockpit-local-cloud">—</small></div>
<div data-i18n="cockpit.brief.labelPrecip"><span>PRECIP</span><strong id="cockpit-local-precipitation">—</strong><small>MM</small></div>
</div>
<a class="cockpit-weather-credit" href="https://open-meteo.com/" target="_blank" rel="noopener">Weather data by Open-Meteo.com</a>
</section>
</div>
<footer class="cockpit-signal-footer">
<span id="cockpit-brief-source">SOURCE-BACKED EVENTS · NO SYNTHETIC NEWS</span>
<span id="cockpit-brief-source" data-i18n="cockpit.brief.sourceNote">SOURCE-BACKED EVENTS · NO SYNTHETIC NEWS</span>
<nav class="cockpit-brief-tabs" aria-label="Cockpit briefing pages">
<button type="button" data-cockpit-brief-index="0" aria-label="Show Live Signals" aria-current="true">SIG</button>
<button type="button" data-cockpit-brief-index="1" aria-label="Show Regional News">NEWS</button>
<button type="button" data-cockpit-brief-index="2" aria-label="Show Local Info">LOCAL</button>
<button type="button" data-cockpit-brief-index="0" aria-label="Show Live Signals" aria-current="true" data-i18n-aria-label="cockpit.brief.tabSignalsAriaLabel">SIG</button>
<button type="button" data-cockpit-brief-index="1" aria-label="Show Regional News" data-i18n-aria-label="cockpit.brief.tabNewsAriaLabel">NEWS</button>
<button type="button" data-cockpit-brief-index="2" aria-label="Show Local Info" data-i18n-aria-label="cockpit.brief.tabLocalAriaLabel">LOCAL</button>
</nav>
</footer>
</aside>
<aside id="cockpit-utility-controls" class="cockpit-utility-controls" aria-label="Cockpit display and Radio controls">
<aside id="cockpit-utility-controls" class="cockpit-utility-controls" aria-label="Cockpit display and Radio controls" data-i18n-aria-label="cockpit.utility.controlsAriaLabel">
<div class="cockpit-utility-control">
<div class="cockpit-utility-launcher" data-cockpit-launcher="display">
<span>DISPLAY</span>
<span data-i18n="cockpit.utility.displayLabel">DISPLAY</span>
<span class="cockpit-utility-divider" aria-hidden="true"></span>
<button id="cockpit-display-toggle-btn" class="cockpit-utility-glyph" type="button" aria-expanded="false" aria-controls="cockpit-display-panel" aria-label="Expand Cockpit display options" title="Expand Cockpit display options">◀</button>
<button id="cockpit-display-toggle-btn" class="cockpit-utility-glyph" type="button" aria-expanded="false" aria-controls="cockpit-display-panel" aria-label="Expand Cockpit display options" title="Expand Cockpit display options" data-i18n-aria-label="cockpit.utility.displayToggleAriaLabel" data-i18n-title="cockpit.utility.displayToggleTitle">◀</button>
</div>
<div id="cockpit-display-panel" class="cockpit-utility-popover" role="group" aria-label="Cockpit display options" hidden>
<div id="cockpit-display-panel" class="cockpit-utility-popover" role="group" aria-label="Cockpit display options" hidden data-i18n-aria-label="cockpit.utility.displayPanelAriaLabel">
<div class="cockpit-display-slot" data-cockpit-display-slot="hud"></div>
<div class="cockpit-display-slot" data-cockpit-display-slot="detection"></div>
<div class="cockpit-display-slot" data-cockpit-display-slot="parameters"></div>
@@ -293,21 +295,21 @@
</div>
<div class="cockpit-utility-control">
<div class="cockpit-utility-launcher" data-cockpit-launcher="radio">
<span>RADIO</span>
<span data-i18n="cockpit.utility.radioLabel">RADIO</span>
<span class="cockpit-utility-divider" aria-hidden="true"></span>
<button id="cockpit-radio-toggle-btn" class="cockpit-utility-glyph" type="button" aria-expanded="false" aria-controls="cockpit-radio-panel" aria-label="Expand Cockpit Radio controls" title="Expand Cockpit Radio controls">◀</button>
<button id="cockpit-radio-toggle-btn" class="cockpit-utility-glyph" type="button" aria-expanded="false" aria-controls="cockpit-radio-panel" aria-label="Expand Cockpit Radio controls" title="Expand Cockpit Radio controls" data-i18n-aria-label="cockpit.utility.radioToggleAriaLabel" data-i18n-title="cockpit.utility.radioToggleTitle">◀</button>
</div>
<div id="cockpit-radio-panel" class="cockpit-utility-popover cockpit-radio-compact" role="group" aria-label="Cockpit compact Radio controls" hidden>
<strong id="cockpit-radio-station" class="cockpit-radio-station">READY</strong>
<button id="cockpit-radio-enable-btn" class="panel-layer-toggle" type="button" aria-pressed="false">ENABLE</button>
<div id="cockpit-radio-panel" class="cockpit-utility-popover cockpit-radio-compact" role="group" aria-label="Cockpit compact Radio controls" hidden data-i18n-aria-label="cockpit.utility.radioPanelAriaLabel">
<strong id="cockpit-radio-station" class="cockpit-radio-station" data-i18n="cockpit.radio.station.ready">READY</strong>
<button id="cockpit-radio-enable-btn" class="panel-layer-toggle" type="button" aria-pressed="false" data-i18n="cockpit.radio.enable">ENABLE</button>
<div class="cockpit-radio-transport">
<button id="cockpit-radio-prev-btn" type="button" aria-label="Previous filtered radio station" disabled>◀◀</button>
<button id="cockpit-radio-play-btn" type="button" aria-label="Play selected radio station" disabled>▶</button>
<button id="cockpit-radio-next-btn" type="button" aria-label="Next filtered radio station" disabled>▶▶</button>
<button id="cockpit-radio-prev-btn" type="button" aria-label="Previous filtered radio station" disabled data-i18n-aria-label="cockpit.radio.prevAriaLabel">◀◀</button>
<button id="cockpit-radio-play-btn" type="button" aria-label="Play selected radio station" disabled data-i18n-aria-label="cockpit.radio.playAriaLabel">▶</button>
<button id="cockpit-radio-next-btn" type="button" aria-label="Next filtered radio station" disabled data-i18n-aria-label="cockpit.radio.nextAriaLabel">▶▶</button>
</div>
<label class="cockpit-radio-volume" for="cockpit-radio-volume">
<span>VOLUME</span>
<input id="cockpit-radio-volume" class="gev-quantitative-slider" type="range" min="0" max="100" value="80" aria-label="Cockpit Radio volume" disabled />
<span data-i18n="cockpit.radio.volumeLabel">VOLUME</span>
<input id="cockpit-radio-volume" class="gev-quantitative-slider" type="range" min="0" max="100" value="80" aria-label="Cockpit Radio volume" disabled data-i18n-aria-label="cockpit.radio.volumeAriaLabel" />
<output id="cockpit-radio-volume-value" class="gev-slider-value" for="cockpit-radio-volume">80%</output>
</label>
</div>
@@ -316,13 +318,13 @@
<div class="cockpit-help">ESC EXIT <span>·</span> C TOGGLE</div>
</section>
<nav id="view-switcher" aria-label="View switcher">
<button id="cockpit-reset-globe" type="button" aria-label="Reset cockpit to full globe view" title="Exit cockpit and return to full globe view" hidden>
<nav id="view-switcher" aria-label="View switcher" data-i18n-aria-label="cockpit.exit.navAriaLabel">
<button id="cockpit-reset-globe" type="button" aria-label="Reset cockpit to full globe view" title="Exit cockpit and return to full globe view" hidden data-i18n-aria-label="cockpit.exit.resetAriaLabel" data-i18n-title="cockpit.exit.resetTitle">
<span class="material-symbols-outlined" aria-hidden="true">public</span>
<span>RESET</span>
<span data-i18n="cockpit.exit.resetLabel">RESET</span>
</button>
<button id="map-view-switch" type="button" aria-label="Exit cockpit view" aria-keyshortcuts="Escape C" title="Exit cockpit view" hidden>
<button id="map-view-switch" type="button" aria-label="Exit cockpit view" aria-keyshortcuts="Escape C" title="Exit cockpit view" hidden data-i18n-aria-label="cockpit.exit.ariaLabel" data-i18n-title="cockpit.exit.title">
<span class="material-symbols-outlined" aria-hidden="true">close_fullscreen</span>
<span>EXIT COCKPIT</span>
<span data-i18n="cockpit.exit.label">EXIT COCKPIT</span>
</button>
</nav>
+32 -25
View File
@@ -1,51 +1,58 @@
<!-- Unified bottom command dock: locations, voice (injected at runtime), visual presets -->
<div id="command-dock" aria-label="Navigation, voice, and visual preset controls">
<div id="command-dock" aria-label="Navigation, voice, and visual preset controls" data-i18n-aria-label="shell.dock.ariaLabel">
<!-- Control panel -->
<div id="control-panel" class="panel-collapsible collapsed">
<div class="panel-glow"></div>
<div class="panel-inner">
<button id="control-panel-toggle" class="panel-header dock-tray-toggle" type="button"
data-dock-toggle-target="control-panel" aria-expanded="false"
aria-controls="control-panel-popover" aria-label="Expand Visual Presets">
aria-controls="control-panel-popover" aria-label="Expand Visual Presets" data-i18n-aria-label="cockpit.presets.toggleAriaLabel">
<span class="panel-title"><span class="dock-label-icon dock-label-icon-presets" aria-hidden="true"></span>VISUAL PRESETS</span>
<span class="panel-divider"></span>
</button>
<!-- Language selector: only the group container is static (its
data-i18n-aria-label is applied from the catalog at boot). The
buttons are rendered at runtime by ui.js _initLocaleSelector(): one
per locale in the configured GEV_DEFAULT_LOCALE / GEV_SECONDARY_LOCALE
pair plus the always-shipped English fallback, deduped, labels EN/ES/FR
with aria-labels from shell.locale.<code>.ariaLabel. -->
<div class="dock-locale-switch" role="group" data-i18n-aria-label="shell.locale.groupAriaLabel"></div>
<div id="control-panel-popover" class="dock-popover-content">
<button class="dock-pin-btn" data-pin-target="control-panel" type="button" aria-label="Pin visual presets" aria-pressed="false" title="Keep visual presets open"><img src="/pin.svg" alt="" /></button>
<button class="dock-pin-btn" data-pin-target="control-panel" type="button" aria-label="Pin visual presets" aria-pressed="false" title="Keep visual presets open" data-i18n-aria-label="cockpit.presets.pinAriaLabel" data-i18n-title="cockpit.presets.pinTitle"><img src="/pin.svg" alt="" /></button>
<div id="style-buttons" class="button-grid">
<button class="style-btn active" data-style="normal" title="Show the globe without a visual filter.">
<button class="style-btn active" data-style="normal" title="Show the globe without a visual filter." data-i18n-title="cockpit.presets.styleNormalTitle">
<span class="btn-icon">◯</span>
<span class="btn-label">Normal</span>
<span class="btn-label" data-i18n="cockpit.presets.styleNormalLabel">Normal</span>
<span class="btn-key">1</span>
</button>
<button class="style-btn" data-style="retro" title="Emulate a green phosphor CRT with scanlines and screen curvature.">
<button class="style-btn" data-style="retro" title="Emulate a green phosphor CRT with scanlines and screen curvature." data-i18n-title="cockpit.presets.styleCrtTitle">
<span class="btn-icon">▦</span>
<span class="btn-label">CRT</span>
<span class="btn-label" data-i18n="cockpit.presets.styleCrtLabel">CRT</span>
<span class="btn-key">2</span>
</button>
<button class="style-btn" data-style="surveillance" title="Simulate night-vision goggles with green intensification and a tube vignette.">
<button class="style-btn" data-style="surveillance" title="Simulate night-vision goggles with green intensification and a tube vignette." data-i18n-title="cockpit.presets.styleNvgTitle">
<span class="btn-icon">🌙</span>
<span class="btn-label">NVG</span>
<span class="btn-label" data-i18n="cockpit.presets.styleNvgLabel">NVG</span>
<span class="btn-key">3</span>
</button>
<button class="style-btn" data-style="thermal" title="Simulate FLIR-style thermal contrast. Turn up Ironbow for color.">
<button class="style-btn" data-style="thermal" title="Simulate FLIR-style thermal contrast. Turn up Ironbow for color." data-i18n-title="cockpit.presets.styleFlirTitle">
<span class="btn-icon">🌡️</span>
<span class="btn-label">FLIR</span>
<span class="btn-label" data-i18n="cockpit.presets.styleFlirLabel">FLIR</span>
<span class="btn-key">4</span>
</button>
<button class="style-btn" data-style="anime" title="Apply bright cel-shaded color and illustrated outlines.">
<button class="style-btn" data-style="anime" title="Apply bright cel-shaded color and illustrated outlines." data-i18n-title="cockpit.presets.styleAnimeTitle">
<span class="btn-icon">✦</span>
<span class="btn-label">Anime</span>
<span class="btn-label" data-i18n="cockpit.presets.styleAnimeLabel">Anime</span>
<span class="btn-key">5</span>
</button>
<button class="style-btn" data-style="noir" title="Apply high-contrast monochrome film-noir grading.">
<button class="style-btn" data-style="noir" title="Apply high-contrast monochrome film-noir grading." data-i18n-title="cockpit.presets.styleNoirTitle">
<span class="btn-icon">◐</span>
<span class="btn-label">Noir</span>
<span class="btn-label" data-i18n="cockpit.presets.styleNoirLabel">Noir</span>
<span class="btn-key">6</span>
</button>
<button class="style-btn" data-style="snow" title="Add a cold, snowy whiteout treatment to the scene.">
<button class="style-btn" data-style="snow" title="Add a cold, snowy whiteout treatment to the scene." data-i18n-title="cockpit.presets.styleSnowTitle">
<span class="btn-icon">❄</span>
<span class="btn-label">Snow</span>
<span class="btn-label" data-i18n="cockpit.presets.styleSnowLabel">Snow</span>
<span class="btn-key">7</span>
</button>
</div>
@@ -58,7 +65,7 @@
</section>
</div>
<div id="style-mini-status" class="panel-mini-status" aria-live="polite">
<span class="panel-mini-label">Style</span>
<span class="panel-mini-label" data-i18n="cockpit.presets.miniStyleLabel">Style</span>
<span id="style-mini-value" class="panel-mini-value">NORMAL</span>
</div>
</div>
@@ -69,26 +76,26 @@
<div class="panel-glow"></div>
<div class="location-inner">
<div class="location-toolbar">
<button id="location-bar-toggle" class="dock-tray-toggle" type="button" data-dock-toggle-target="location-bar" aria-controls="location-bar-popover" aria-expanded="false" aria-label="Expand LOCATION" title="Expand LOCATION">
<button id="location-bar-toggle" class="dock-tray-toggle" type="button" data-dock-toggle-target="location-bar" aria-controls="location-bar-popover" aria-expanded="false" aria-label="Expand LOCATION" title="Expand LOCATION" data-i18n-aria-label="cockpit.location.toggleAria" data-i18n-title="cockpit.location.toggleAria">
<span class="location-toolbar-label"><span class="dock-label-icon dock-label-icon-location" aria-hidden="true"></span>LOCATION</span>
</button>
<button class="panel-collapse-btn" data-collapse-target="location-bar" title="Collapse panel">+</button>
<button class="panel-collapse-btn" data-collapse-target="location-bar" title="Collapse panel" data-i18n-title="cockpit.location.collapseTitle">+</button>
</div>
<div id="location-mini-status" class="location-mini-status" aria-live="polite">
<div id="location-mini-city" class="location-mini-city">📍 Location: --</div>
<div id="location-mini-poi" class="location-mini-poi">Landmark: --</div>
<div id="location-mini-city" class="location-mini-city" data-i18n="cockpit.location.miniCityInitial">📍 Location: --</div>
<div id="location-mini-poi" class="location-mini-poi" data-i18n="cockpit.location.miniPoiInitial">Landmark: --</div>
</div>
<div id="location-bar-popover" class="dock-popover-content">
<button class="dock-pin-btn" data-pin-target="location-bar" type="button" aria-label="Pin location tray" aria-pressed="false" title="Keep location tray open"><img src="/pin.svg" alt="" /></button>
<button class="dock-pin-btn" data-pin-target="location-bar" type="button" aria-label="Pin location tray" aria-pressed="false" title="Keep location tray open" data-i18n-aria-label="cockpit.location.pinAriaLabel" data-i18n-title="cockpit.location.pinTitle"><img src="/pin.svg" alt="" /></button>
<div id="poi-row" class="poi-row-container"></div>
<div class="location-bar-divider" id="location-bar-divider"></div>
<div class="location-city-row">
<div id="location-pills" class="location-pills"></div>
<div class="location-search-wrap">
<button id="search-toggle" class="search-toggle-btn" title="Search any location">
<button id="search-toggle" class="search-toggle-btn" title="Search any location" data-i18n-title="cockpit.location.searchToggleTitle">
<span>&#x1F50E;</span>
</button>
<input type="text" id="location-search" placeholder="Search any location..." autocomplete="off" spellcheck="false" aria-label="Search location by name or coordinates" />
<input type="text" id="location-search" placeholder="Search any location..." autocomplete="off" spellcheck="false" aria-label="Search location by name or coordinates" data-i18n-aria-label="cockpit.location.searchAria" data-i18n-placeholder="cockpit.location.searchPlaceholder" />
</div>
</div>
</div>
+52 -51
View File
@@ -28,112 +28,112 @@
<div class="panel-glow"></div>
<div class="global-context-panel-inner" data-rail-scroller>
<div class="panel-header">
<span class="panel-title">CONTEXT</span>
<span class="panel-title" data-i18n="cockpit.context.panelTitle">CONTEXT</span>
<span class="panel-divider"></span>
<div id="context-radio-dock" class="context-radio-dock">
<button id="context-radio-toggle-btn" class="context-radio-toggle" type="button" aria-expanded="false" aria-controls="context-radio-mini" aria-label="Open compact Radio controls" title="Open compact Radio controls">
<button id="context-radio-toggle-btn" class="context-radio-toggle" type="button" aria-expanded="false" aria-controls="context-radio-mini" aria-label="Open compact Radio controls" title="Open compact Radio controls" data-i18n-aria-label="cockpit.context.radioToggleAriaLabel" data-i18n-title="cockpit.context.radioToggleTitle">
<span class="material-symbols-outlined" aria-hidden="true">radio</span>
</button>
<div id="context-radio-mini" class="context-radio-mini" role="group" aria-label="Compact Radio controls" hidden>
<div id="context-radio-mini" class="context-radio-mini" role="group" aria-label="Compact Radio controls" hidden data-i18n-aria-label="cockpit.context.radioMiniGroupAriaLabel">
<div class="context-radio-mini-heading">
<span>RADIO</span>
<strong id="context-radio-mini-station">RADIO READY</strong>
<span data-i18n="cockpit.context.radioMiniTitle">RADIO</span>
<strong id="context-radio-mini-station" data-i18n="cockpit.context.radioMiniReady">RADIO READY</strong>
<span class="context-radio-mini-actions">
<button id="context-radio-details-btn" type="button" aria-label="Open detailed Radio controls" aria-expanded="false" aria-controls="radio-panel" title="Open detailed Radio controls">
<button id="context-radio-details-btn" type="button" aria-label="Open detailed Radio controls" aria-expanded="false" aria-controls="radio-panel" title="Open detailed Radio controls" data-i18n-aria-label="cockpit.context.radioDetailsAriaLabel" data-i18n-title="cockpit.context.radioDetailsTitle">
<span class="material-symbols-outlined" aria-hidden="true">open_in_full</span>
</button>
<button id="context-radio-mini-close-btn" type="button" aria-label="Close compact Radio controls" title="Close compact Radio controls">
<button id="context-radio-mini-close-btn" type="button" aria-label="Close compact Radio controls" title="Close compact Radio controls" data-i18n-aria-label="cockpit.context.radioCloseAriaLabel" data-i18n-title="cockpit.context.radioCloseTitle">
<span class="material-symbols-outlined" aria-hidden="true">close</span>
</button>
</span>
</div>
<button id="context-radio-mini-enable-btn" class="panel-layer-toggle" type="button" aria-pressed="false">ENABLE</button>
<button id="context-radio-mini-enable-btn" class="panel-layer-toggle" type="button" aria-pressed="false" data-i18n="cockpit.context.radioMiniEnable">ENABLE</button>
<div class="context-radio-mini-transport">
<button id="context-radio-mini-prev-btn" type="button" aria-label="Previous filtered radio station" title="Previous station" disabled>◀◀</button>
<button id="context-radio-mini-play-btn" type="button" aria-label="Play selected radio station" title="Play" disabled>▶</button>
<button id="context-radio-mini-next-btn" type="button" aria-label="Next filtered radio station" title="Next station" disabled>▶▶</button>
<button id="context-radio-mini-prev-btn" type="button" aria-label="Previous filtered radio station" title="Previous station" disabled data-i18n-aria-label="cockpit.context.radioMiniPrevAriaLabel" data-i18n-title="cockpit.context.radioMiniPrevTitle">◀◀</button>
<button id="context-radio-mini-play-btn" type="button" aria-label="Play selected radio station" title="Play" disabled data-i18n-aria-label="cockpit.context.radioMiniPlayAriaLabel" data-i18n-title="cockpit.context.radioMiniPlayTitle">▶</button>
<button id="context-radio-mini-next-btn" type="button" aria-label="Next filtered radio station" title="Next station" disabled data-i18n-aria-label="cockpit.context.radioMiniNextAriaLabel" data-i18n-title="cockpit.context.radioMiniNextTitle">▶▶</button>
</div>
<label class="context-radio-mini-volume" for="context-radio-mini-volume">
<span>VOLUME</span>
<input id="context-radio-mini-volume" class="gev-quantitative-slider" type="range" min="0" max="100" value="80" aria-label="Compact Radio volume" disabled />
<span data-i18n="cockpit.context.radioMiniVolumeLabel">VOLUME</span>
<input id="context-radio-mini-volume" class="gev-quantitative-slider" type="range" min="0" max="100" value="80" aria-label="Compact Radio volume" disabled data-i18n-aria-label="cockpit.context.radioMiniVolumeAriaLabel" />
<output id="context-radio-mini-volume-value" class="gev-slider-value" for="context-radio-mini-volume">80%</output>
</label>
</div>
</div>
<button class="panel-collapse-btn" data-collapse-target="global-context-panel" title="Expand panel">▶</button>
<button class="panel-collapse-btn" data-collapse-target="global-context-panel" title="Expand panel" data-i18n-title="cockpit.context.collapseTitle">▶</button>
</div>
<div class="cyber-panel-body" data-rail-scroller>
<div class="global-context-modes" role="tablist" aria-label="Context mode">
<button id="global-context-flights-btn" class="context-mode-button" type="button" role="tab" tabindex="0" aria-selected="false" aria-controls="context-flights-view" aria-label="CONTACTS" title="Cycles the nearest contacts of whatever type you select — planes, vessels, installations. Satellites track independently.">
<div class="global-context-modes" role="tablist" aria-label="Context mode" data-i18n-aria-label="cockpit.context.modesAriaLabel">
<button id="global-context-flights-btn" class="context-mode-button" type="button" role="tab" tabindex="0" aria-selected="false" aria-controls="context-flights-view" aria-label="CONTACTS" title="Cycles the nearest contacts of whatever type you select — planes, vessels, installations. Satellites track independently." data-i18n-aria-label="cockpit.context.contactsTabAriaLabel" data-i18n-title="cockpit.context.contactsTabTitle">
<span class="material-symbols-outlined" aria-hidden="true">radar</span>
<span>CONTACTS</span>
<span data-i18n="cockpit.context.contactsTabLabel">CONTACTS</span>
</button>
<button id="global-context-missions-btn" class="context-mode-button" type="button" role="tab" tabindex="0" aria-selected="false" aria-controls="context-missions-view">
<span class="material-symbols-outlined" aria-hidden="true">rocket_launch</span>
<span>SPACE MISSIONS</span>
<span data-i18n="cockpit.context.missionsTabLabel">SPACE MISSIONS</span>
</button>
</div>
<div id="context-mode-standby" class="context-mode-standby">
<strong>SELECT CONTEXT</strong>
<strong data-i18n="cockpit.context.standbyTitle">SELECT CONTEXT</strong>
<span>CONTACTS — nearest planes · vessels · sites<br>SPACE MISSIONS — launches &amp; orbital assets</span>
</div>
<section id="context-flights-view" class="context-mode-view" role="tabpanel" aria-labelledby="global-context-flights-btn" hidden>
<div class="global-context-actions" aria-label="Contact Context actions">
<div class="global-context-actions" aria-label="Contact Context actions" data-i18n-aria-label="cockpit.context.actionsAriaLabel">
<button id="cockpit-entry" type="button" hidden>
<span class="material-symbols-outlined" aria-hidden="true">flight</span>
<span>COCKPIT</span>
<span data-i18n="cockpit.context.cockpitEntryLabel">COCKPIT</span>
</button>
<button id="installations-search-btn" class="panel-layer-toggle" type="button">SEARCH NEARBY SITES</button>
<button id="tr3b-toggle" class="tr3b-toggle" type="button" aria-pressed="false" hidden aria-label="Reclassify tracked contact as TR-3B" title="Reclassify as TR-3B">🛸</button>
<button id="installations-search-btn" class="panel-layer-toggle" type="button" data-i18n="cockpit.context.searchNearbyLabel">SEARCH NEARBY SITES</button>
<button id="tr3b-toggle" class="tr3b-toggle" type="button" aria-pressed="false" hidden aria-label="Reclassify tracked contact as TR-3B" title="Reclassify as TR-3B" data-i18n-aria-label="cockpit.context.tr3bAriaLabel" data-i18n-title="cockpit.context.tr3bTitle">🛸</button>
</div>
<aside id="military-awareness-panel">
<div class="military-awareness-standby">
<strong>CONTACTS CONTEXT OFF</strong>
<span>SELECT CONTACTS TO LOAD OBSERVED / MAPPED PROXIMITY</span>
<strong data-i18n="cockpit.context.awarenessOffTitle">CONTACTS CONTEXT OFF</strong>
<span data-i18n="cockpit.context.awarenessOffHint">SELECT CONTACTS TO LOAD OBSERVED / MAPPED PROXIMITY</span>
</div>
</aside>
</section>
<section id="context-missions-view" class="context-mode-view" role="tabpanel" aria-labelledby="global-context-missions-btn" hidden>
<div id="space-mission-panel-host">
<div id="space-mission-roster" class="space-mission-roster" aria-label="Available Space Missions">
<div id="space-mission-roster" class="space-mission-roster" aria-label="Available Space Missions" data-i18n-aria-label="cockpit.context.rosterAriaLabel">
<div class="space-mission-roster-header">
<div>
<strong>AVAILABLE MISSIONS</strong>
<span>SELECT A MISSION TO INSPECT</span>
<strong data-i18n="cockpit.context.rosterTitle">AVAILABLE MISSIONS</strong>
<span data-i18n="cockpit.context.rosterHint">SELECT A MISSION TO INSPECT</span>
</div>
<output data-mission-roster-count>—</output>
</div>
<div class="space-mission-roster-list" data-mission-roster-list>
<div class="space-mission-roster-empty">LOADING 30-DAY MISSION INDEX</div>
<div class="space-mission-roster-empty" data-i18n="cockpit.context.rosterLoading">LOADING 30-DAY MISSION INDEX</div>
</div>
<p class="space-mission-roster-focus-continuation" tabindex="-1" data-mission-roster-focus-continuation>TAB PREVIEWS · ENTER / SPACE SELECTS</p>
<p class="space-mission-roster-focus-continuation" tabindex="-1" data-mission-roster-focus-continuation data-i18n="layers.missions.roster.keyboardHint">TAB PREVIEWS · ENTER / SPACE SELECTS</p>
</div>
</div>
</section>
<section id="radio-panel" class="panel-collapsible collapsed" data-panel-id="radio-panel" aria-label="Internet radio and local SDR">
<section id="radio-panel" class="panel-collapsible collapsed" data-panel-id="radio-panel" aria-label="Internet radio and local SDR" data-i18n-aria-label="layers.radio.panelAriaLabel">
<div class="radio-panel-inner">
<div class="panel-header">
<span class="panel-title">RADIO</span>
<span class="panel-title" data-i18n="layers.radio.panelTitle">RADIO</span>
<span id="radio-layer-state" class="radio-layer-state">OFF</span>
<span class="panel-divider"></span>
<button class="panel-collapse-btn" data-collapse-target="radio-panel" title="Expand Radio" aria-label="Expand Radio section">+</button>
<button class="panel-collapse-btn" data-collapse-target="radio-panel" title="Expand Radio" aria-label="Expand Radio section" data-i18n-title="layers.radio.expandTitle" data-i18n-aria-label="layers.radio.expandAriaLabel">+</button>
</div>
<div class="radio-directory-row">
<button id="radio-enable-btn" class="panel-layer-toggle" type="button" aria-pressed="false">ENABLE</button>
<label class="radio-filter-label" for="radio-filter">STATION TAG</label>
<select id="radio-filter" aria-label="Filter stations by station tag" disabled>
<option value="all">All</option>
<button id="radio-enable-btn" class="panel-layer-toggle" type="button" aria-pressed="false" data-i18n="layers.radio.enable">ENABLE</button>
<label class="radio-filter-label" for="radio-filter" data-i18n="layers.radio.stationTagLabel">STATION TAG</label>
<select id="radio-filter" aria-label="Filter stations by station tag" disabled data-i18n-aria-label="layers.radio.filterAriaLabel">
<option value="all" data-i18n="layers.radio.filterAll">All</option>
</select>
</div>
<div id="radio-station-card" class="radio-station-card" aria-live="polite">
<strong id="radio-station-name">NO STATION SELECTED</strong>
<span id="radio-station-meta">Enable Radio, then choose a globe marker or use next.</span>
<strong id="radio-station-name" data-i18n="layers.radio.noStation">NO STATION SELECTED</strong>
<span id="radio-station-meta" data-i18n="layers.radio.stationHint">Enable Radio, then choose a globe marker or use next.</span>
<span id="radio-station-tags"></span>
</div>
<div id="radio-tuner" class="radio-tuner" hidden>
<div class="radio-tuner-header">
<span id="radio-tuner-band-label">DIRECTORY BAND</span>
<output id="radio-tuner-value" for="radio-tuner-slider">DRAG TO TUNE</output>
<output id="radio-tuner-value" for="radio-tuner-slider" data-i18n="layers.radio.dragToTune">DRAG TO TUNE</output>
</div>
<div class="radio-tuner-dial">
<div class="radio-tuner-scale" aria-hidden="true"></div>
@@ -142,26 +142,27 @@
aria-label="Tune available internet radio stations" aria-valuetext="No station available" disabled />
</div>
<div class="radio-tuner-readout">
<span id="radio-tuner-station">ALL · DRAG THE NEEDLE</span>
<span>SNAPS TO AVAILABLE STATIONS</span>
<span id="radio-tuner-station" data-i18n="layers.radio.tunerIdle">ALL · DRAG THE NEEDLE</span>
<span data-i18n="layers.radio.snapsNote">SNAPS TO AVAILABLE STATIONS</span>
</div>
</div>
<div class="radio-transport" role="group" aria-label="Radio playback">
<button id="radio-prev-btn" type="button" aria-label="Previous filtered station" disabled>PREV</button>
<button id="radio-play-btn" type="button" aria-label="Play selected station" disabled>PLAY</button>
<button id="radio-next-btn" type="button" aria-label="Next filtered station" disabled>NEXT</button>
<button id="radio-stop-btn" type="button" aria-label="Stop radio playback" disabled>STOP</button>
<div class="radio-transport" role="group" aria-label="Radio playback" data-i18n-aria-label="layers.radio.transportAriaLabel">
<button id="radio-prev-btn" type="button" aria-label="Previous filtered station" disabled data-i18n-aria-label="layers.radio.prevAriaLabel" data-i18n="layers.radio.prev">PREV</button>
<button id="radio-play-btn" type="button" aria-label="Play selected station" disabled data-i18n-aria-label="layers.radio.playAriaLabel" data-i18n="layers.radio.play">PLAY</button>
<button id="radio-next-btn" type="button" aria-label="Next filtered station" disabled data-i18n-aria-label="layers.radio.nextAriaLabel" data-i18n="layers.radio.next">NEXT</button>
<button id="radio-stop-btn" type="button" aria-label="Stop radio playback" disabled data-i18n-aria-label="layers.radio.stopAriaLabel" data-i18n="layers.radio.stop">STOP</button>
</div>
<label class="radio-volume-row" for="radio-volume">
<span>VOLUME</span>
<input id="radio-volume" class="gev-quantitative-slider" type="range" min="0" max="100" value="80" aria-label="Radio volume" />
<span data-i18n="layers.radio.volumeLabel">VOLUME</span>
<input id="radio-volume" class="gev-quantitative-slider" type="range" min="0" max="100" value="80" aria-label="Radio volume" data-i18n-aria-label="layers.radio.volumeAriaLabel" />
<output id="radio-volume-value" class="gev-slider-value" for="radio-volume">80%</output>
</label>
<div id="radio-playback-state" class="radio-playback-state" role="status" aria-live="polite">Radio off</div>
<div id="radio-playback-state" class="radio-playback-state" role="status" aria-live="polite" data-i18n="layers.radio.playbackOff">Radio off</div>
<div class="radio-links">
<a id="radio-station-homepage" href="#" target="_blank" rel="noopener noreferrer" hidden>STATION SITE</a>
<a id="radio-station-homepage" href="#" target="_blank" rel="noopener noreferrer" hidden data-i18n="layers.radio.stationSite">STATION SITE</a>
<a href="https://www.radio-browser.info/" target="_blank" rel="noopener noreferrer">DIRECTORY: RADIO BROWSER</a>
</div>
<p class="radio-privacy" data-i18n="layers.radio.privacyNote">Audio connects directly to the broadcaster after you press play. Your IP is visible to that broadcaster.</p>
<p class="radio-privacy">Audio connects directly to the broadcaster after you press play. Your IP is visible to that broadcaster.</p>
<div id="sdr-radio-card" class="sdr-radio-card" role="group" aria-label="Local RTL-SDR receiver">
<div class="sdr-radio-heading">
+41 -41
View File
@@ -3,20 +3,20 @@
<div class="compact pp-header-row">
<span class="pp-header-label">DISPLAY</span>
<span class="panel-divider" aria-hidden="true"></span>
<button class="panel-collapse-btn pp-collapse-btn" data-collapse-target="pp-toggles" title="Collapse panel">&#x25C0;</button>
<button class="panel-collapse-btn pp-collapse-btn" data-collapse-target="pp-toggles" title="Collapse panel" data-i18n-title="cockpit.display.collapseTitle">&#x25C0;</button>
</div>
<div class="pp-panel-body" data-rail-scroller>
<div class="pp-toggle-group">
<button class="pp-toggle-btn" id="hud-toggle" title="Intelligence HUD (H)">
<button class="pp-toggle-btn" id="hud-toggle" title="Intelligence HUD (H)" data-i18n-title="cockpit.display.hudToggleTitle">
<span class="pp-icon">&#x25CE;</span>
<span class="pp-label">HUD</span>
</button>
<div class="pp-slider-row" id="hud-layout-row">
<span class="pp-slider-mini-label">Layout</span>
<select id="hud-layout-select" class="pp-select" aria-label="HUD layout">
<option value="tactical">Tactical</option>
<option value="operator">Operator</option>
<option value="minimal">Minimal</option>
<span class="pp-slider-mini-label" data-i18n="cockpit.display.hudLayoutLabel">Layout</span>
<select id="hud-layout-select" class="pp-select" aria-label="HUD layout" data-i18n-aria-label="cockpit.display.hudLayoutAriaLabel">
<option value="tactical" data-i18n="cockpit.display.hudLayoutTactical">Tactical</option>
<option value="operator" data-i18n="cockpit.display.hudLayoutOperator">Operator</option>
<option value="minimal" data-i18n="cockpit.display.hudLayoutMinimal">Minimal</option>
<option value="cyber">Cyber</option>
</select>
</div>
@@ -52,39 +52,39 @@
</div>
</div>
<div class="pp-toggle-group">
<button class="pp-toggle-btn" id="detection-toggle" title="Detection Overlay (D)" aria-label="Detection overlay" aria-pressed="false">
<button class="pp-toggle-btn" id="detection-toggle" title="Detection Overlay (D)" aria-label="Detection overlay" aria-pressed="false" data-i18n-title="cockpit.display.detectionToggleTitle" data-i18n-aria-label="cockpit.display.detectionAriaLabel">
<span class="pp-icon">&#x25A3;</span>
<span class="pp-label">DETECT</span>
<span class="pp-label" data-i18n="cockpit.display.detectionLabel">DETECT</span>
</button>
<div class="pp-slider-row detection-slider-row" id="detection-slider-row">
<label class="pp-slider-mini-label" for="detection-density-slider">Density</label>
<input type="range" class="pp-slider" id="detection-density-slider" min="0" max="100" step="25" value="50" aria-label="Detection label density" />
<label class="pp-slider-mini-label" for="detection-density-slider" data-i18n="cockpit.display.densityLabel">Density</label>
<input type="range" class="pp-slider" id="detection-density-slider" min="0" max="100" step="25" value="50" aria-label="Detection label density" data-i18n-aria-label="cockpit.display.densityAriaLabel" />
<span class="pp-slider-value" id="detection-density-value">50%</span>
</div>
<div class="pp-slider-row detection-allocation-row" id="detection-allocation-row">
<span class="pp-slider-mini-label">Allocation</span>
<div class="pp-mode-seg detection-allocation-seg" role="radiogroup" aria-label="Detection label allocation">
<button class="pp-mode-btn active" id="detection-allocation-elastic" data-allocation="ELASTIC" role="radio" aria-checked="true">Elastic</button>
<button class="pp-mode-btn" id="detection-allocation-weighted" data-allocation="WEIGHTED" role="radio" aria-checked="false">Weighted</button>
<span class="pp-slider-mini-label" data-i18n="cockpit.display.allocationLabel">Allocation</span>
<div class="pp-mode-seg detection-allocation-seg" role="radiogroup" aria-label="Detection label allocation" data-i18n-aria-label="cockpit.display.allocationAriaLabel">
<button class="pp-mode-btn active" id="detection-allocation-elastic" data-allocation="ELASTIC" role="radio" aria-checked="true" data-i18n="cockpit.display.allocationElastic">Elastic</button>
<button class="pp-mode-btn" id="detection-allocation-weighted" data-allocation="WEIGHTED" role="radio" aria-checked="false" data-i18n="cockpit.display.allocationWeighted">Weighted</button>
</div>
</div>
<div class="pp-slider-row detection-fade-row" id="detection-fade-row">
<label class="pp-slider-mini-label" for="detection-fade-slider">Fade</label>
<input type="range" class="pp-slider" id="detection-fade-slider" min="0" max="40" step="1" value="7" aria-label="Detection fade distance" title="World-overlay fade distance outside the keyhole as a percentage of its radius" />
<label class="pp-slider-mini-label" for="detection-fade-slider" data-i18n="cockpit.display.fadeLabel">Fade</label>
<input type="range" class="pp-slider" id="detection-fade-slider" min="0" max="40" step="1" value="7" aria-label="Detection fade distance" title="World-overlay fade distance outside the keyhole as a percentage of its radius" data-i18n-aria-label="cockpit.display.fadeAriaLabel" data-i18n-title="cockpit.display.fadeTitle" />
<span class="pp-slider-value" id="detection-fade-value">7%</span>
</div>
<div class="pp-slider-row detection-opacity-row" id="detection-opacity-row">
<label class="pp-slider-mini-label" for="detection-opacity-slider">Outside</label>
<input type="range" class="pp-slider" id="detection-opacity-slider" min="0" max="100" step="1" value="1" aria-label="Detection opacity outside the keyhole" title="World-overlay label and card opacity beyond the fade distance" />
<label class="pp-slider-mini-label" for="detection-opacity-slider" data-i18n="cockpit.display.outsideLabel">Outside</label>
<input type="range" class="pp-slider" id="detection-opacity-slider" min="0" max="100" step="1" value="1" aria-label="Detection opacity outside the keyhole" title="World-overlay label and card opacity beyond the fade distance" data-i18n-aria-label="cockpit.display.outsideAriaLabel" data-i18n-title="cockpit.display.outsideTitle" />
<span class="pp-slider-value" id="detection-opacity-value">1%</span>
</div>
</div>
<!-- The one shared preset-parameter surface is inserted here at runtime. -->
<div id="param-slider-panel">
<div class="param-panel-header">
<span class="param-panel-title">PARAMETERS</span>
<span class="param-panel-title" data-i18n="cockpit.display.parametersTitle">PARAMETERS</span>
<span class="param-panel-divider"></span>
<button class="panel-collapse-btn" id="param-panel-collapse-btn" data-collapse-target="param-slider-panel" title="Collapse panel">−</button>
<button class="panel-collapse-btn" id="param-panel-collapse-btn" data-collapse-target="param-slider-panel" title="Collapse panel" data-i18n-title="cockpit.display.parametersCollapseTitle">−</button>
</div>
<div id="param-sliders"></div>
</div>
@@ -93,31 +93,31 @@
button ships lit and the mode row ships open — the same markup-carries-the-default
convention as #scope-toggle. ui.js re-syncs both from durable layer state, which
is what flips them back for a share link or a stored session carrying 3D off. -->
<button class="pp-toggle-btn active" id="models3d-toggle" aria-pressed="true" title="3D aircraft — flat icons zoomed out, 3D models up close">
<button class="pp-toggle-btn active" id="models3d-toggle" aria-pressed="true" title="3D aircraft — flat icons zoomed out, 3D models up close" data-i18n-title="cockpit.display.modelsToggleTitle">
<span class="pp-icon">&#x2708;</span>
<span class="pp-label">3D</span>
</button>
<div class="pp-slider-row visible" id="models3d-mode-row">
<span class="pp-slider-mini-label">Models</span>
<div class="pp-mode-seg" role="radiogroup" aria-label="3D model coverage">
<button class="pp-mode-btn active" id="models3d-mode-proximity" data-mode="proximity" role="radio" aria-checked="true">Proximity</button>
<button class="pp-mode-btn" id="models3d-mode-all" data-mode="all" role="radio" aria-checked="false">All</button>
<span class="pp-slider-mini-label" data-i18n="cockpit.display.modelsLabel">Models</span>
<div class="pp-mode-seg" role="radiogroup" aria-label="3D model coverage" data-i18n-aria-label="cockpit.display.modelsCoverageAriaLabel">
<button class="pp-mode-btn active" id="models3d-mode-proximity" data-mode="proximity" role="radio" aria-checked="true" data-i18n="cockpit.display.modelsModeProximity">Proximity</button>
<button class="pp-mode-btn" id="models3d-mode-all" data-mode="all" role="radio" aria-checked="false" data-i18n="cockpit.display.modelsModeAll">All</button>
</div>
</div>
</div>
<div class="pp-toggle-group">
<button class="pp-toggle-btn active" id="scope-toggle" title="Scope — the circular viewport mask" aria-pressed="true">
<button class="pp-toggle-btn active" id="scope-toggle" title="Scope — the circular viewport mask" aria-pressed="true" data-i18n-title="cockpit.display.scopeToggleTitle">
<span class="pp-icon material-symbols-outlined" aria-hidden="true">adjust</span>
<span class="pp-label">Scope</span>
<span class="pp-label" data-i18n="cockpit.display.scopeLabel">Scope</span>
</button>
<div class="pp-slider-row visible" id="scope-slider-row">
<span class="pp-slider-mini-label">Feather</span>
<span class="pp-slider-mini-label" data-i18n="cockpit.display.featherLabel">Feather</span>
<!-- Feather starts at an 11% soft scope-mask edge (owner final lock
2026-08-24, superseding the 08-22 hard-crop and 08-23 8% rulings);
mirrors SCOPE_FEATHER_RATIO_DEFAULT in src/scopeMask.js. Feather
softens the black mask edge; label fading is the separate Fade
slider. -->
<input type="range" class="pp-slider" id="scope-feather-slider" min="0" max="100" value="11" aria-label="Scope edge feather" title="Scope edge feather as a percentage of the keyhole radius" />
<input type="range" class="pp-slider" id="scope-feather-slider" min="0" max="100" value="11" aria-label="Scope edge feather" title="Scope edge feather as a percentage of the keyhole radius" data-i18n-aria-label="cockpit.display.featherAria" data-i18n-title="cockpit.display.featherTitle" />
<span class="pp-slider-value" id="scope-feather-value">11%</span>
</div>
</div>
@@ -148,35 +148,35 @@
</div>
</div>
<div class="pp-toggle-group">
<button class="pp-toggle-btn" id="celestial-toggle" title="Celestial ring — reveal the full globe" aria-pressed="false">
<button class="pp-toggle-btn" id="celestial-toggle" title="Celestial ring — reveal the full globe" aria-pressed="false" data-i18n-title="cockpit.display.celestialToggleTitle">
<span class="pp-icon material-symbols-outlined" aria-hidden="true">flare</span>
<span class="pp-label">Celestial</span>
<span class="pp-label" data-i18n="cockpit.display.celestialLabel">Celestial</span>
</button>
</div>
<button class="pp-toggle-btn" id="clean-view-toggle" title="Hide UI chrome">
<button class="pp-toggle-btn" id="clean-view-toggle" title="Hide UI chrome" data-i18n-title="cockpit.display.cleanViewToggleTitle">
<span class="pp-icon">&#x25A1;</span>
<span class="pp-label">Clean UI</span>
<span class="pp-label" data-i18n="cockpit.display.cleanViewLabel">Clean UI</span>
</button>
<div class="pp-toggle-group">
<button class="pp-toggle-btn" id="bloom-toggle" title="Bloom / Glow">
<button class="pp-toggle-btn" id="bloom-toggle" title="Bloom / Glow" data-i18n-title="cockpit.display.bloomToggleTitle">
<span class="pp-icon">&#x2728;</span>
<span class="pp-label">Bloom</span>
<span class="pp-label" data-i18n="cockpit.display.bloomLabel">Bloom</span>
</button>
<div class="pp-slider-row" id="bloom-slider-row">
<input type="range" class="pp-slider" id="bloom-intensity-slider" min="0" max="200" value="0" aria-label="Bloom intensity" />
<input type="range" class="pp-slider" id="bloom-intensity-slider" min="0" max="200" value="0" aria-label="Bloom intensity" data-i18n-aria-label="cockpit.display.bloomAria" />
<span class="pp-slider-value" id="bloom-intensity-value">0%</span>
</div>
</div>
<div class="pp-toggle-group">
<button class="pp-toggle-btn" id="sharpen-toggle" title="Sharpening">
<button class="pp-toggle-btn" id="sharpen-toggle" title="Sharpening" data-i18n-title="cockpit.display.sharpenToggleTitle">
<span class="pp-icon">&#x1F50D;</span>
<span class="pp-label">Sharpen</span>
<span class="pp-label" data-i18n="cockpit.display.sharpenLabel">Sharpen</span>
</button>
<div class="pp-slider-row" id="sharpen-slider-row">
<input type="range" class="pp-slider" id="sharpen-intensity-slider" min="0" max="100" value="49" aria-label="Sharpen intensity" />
<input type="range" class="pp-slider" id="sharpen-intensity-slider" min="0" max="100" value="49" aria-label="Sharpen intensity" data-i18n-aria-label="cockpit.display.sharpenAria" />
<span class="pp-slider-value" id="sharpen-intensity-value">49%</span>
</div>
</div>
</div>
</div>
<button id="clean-view-exit" title="Return UI controls">EXIT CLEAN VIEW</button>
<button id="clean-view-exit" title="Return UI controls" data-i18n-title="cockpit.display.cleanViewExitTitle" data-i18n="cockpit.display.cleanViewExitLabel">EXIT CLEAN VIEW</button>
+1 -1
View File
@@ -6,6 +6,6 @@
<div class="loader-content">
<span class="loader-logo brand-logo" data-logo-gaze data-logo-src="/logo.svg" aria-hidden="true"><img src="/logo.svg" alt="" /></span>
<h2>GOD'S EYE <span class="title-accent">VIEW</span></h2>
<p class="loader-status">Initializing photorealistic world...</p>
<p class="loader-status" data-i18n="shell.loading.initialStatus">Initializing photorealistic world...</p>
</div>
</div>
+43 -43
View File
@@ -4,9 +4,9 @@
<div class="panel-glow"></div>
<div class="data-panel-inner">
<div class="panel-header">
<span class="panel-title">DATA LAYERS</span>
<span class="panel-title" data-i18n="shell.panels.dataLayers">DATA LAYERS</span>
<span class="panel-divider"></span>
<button class="panel-collapse-btn" data-collapse-target="data-panel" title="Collapse panel">+</button>
<button class="panel-collapse-btn" data-collapse-target="data-panel" title="Collapse panel" data-i18n-title="shell.panels.collapseTitle">+</button>
</div>
<div id="data-toggles" class="data-toggle-list"></div>
</div>
@@ -17,56 +17,56 @@
<div class="panel-glow"></div>
<div class="cctv-panel-inner" data-rail-scroller>
<div class="panel-header">
<span class="panel-title">CCTV</span>
<span class="panel-title" data-i18n="layers.cctv.panelTitle">CCTV</span>
<span class="panel-divider"></span>
<button class="panel-collapse-btn" data-collapse-target="cctv-panel" title="Collapse panel">+</button>
<button class="panel-collapse-btn" data-collapse-target="cctv-panel" title="Collapse panel" data-i18n-title="layers.cctv.collapseTitle">+</button>
</div>
<div class="cyber-panel-body" data-rail-scroller>
<div id="cctv-frame-wrap" class="cctv-frame-wrap">
<img id="cctv-frame" alt="CCTV feed frame" />
<canvas id="cctv-video" class="cctv-frame" hidden></canvas>
</div>
<div id="cctv-source-badge" role="status" aria-live="polite">SOURCE · UNKNOWN</div>
<div id="cctv-meta">Enable CCTV to load camera intersections</div>
<div id="cctv-source-badge" role="status" aria-live="polite" data-i18n="layers.cctv.sourceUnknown">SOURCE · UNKNOWN</div>
<div id="cctv-meta" data-i18n="layers.cctv.metaIdle">Enable CCTV to load camera intersections</div>
<div class="cctv-controls">
<button id="cctv-enable-btn" class="scene-btn">CCTV OFF</button>
<button id="cctv-nearest-btn" class="scene-btn">NEAREST</button>
<button id="cctv-enable-btn" class="scene-btn" data-i18n="layers.cctv.toggleOff">CCTV OFF</button>
<button id="cctv-nearest-btn" class="scene-btn" data-i18n="layers.cctv.nearest">NEAREST</button>
</div>
<div class="cctv-controls">
<button id="cctv-prev-btn" class="scene-btn">PREV</button>
<select id="cctv-camera-select" aria-label="CCTV camera"></select>
<button id="cctv-next-btn" class="scene-btn">NEXT</button>
<button id="cctv-prev-btn" class="scene-btn" data-i18n="layers.cctv.prev">PREV</button>
<select id="cctv-camera-select" aria-label="CCTV camera" data-i18n-aria-label="layers.cctv.cameraAriaLabel"></select>
<button id="cctv-next-btn" class="scene-btn" data-i18n="layers.cctv.next">NEXT</button>
</div>
<div class="cctv-controls">
<button id="cctv-focus-btn" class="scene-btn">FOCUS</button>
<button id="cctv-coverage-btn" class="scene-btn">COVERAGE OFF</button>
<button id="cctv-auto-hop-btn" class="scene-btn">AUTO HOP OFF</button>
<button id="cctv-focus-btn" class="scene-btn" data-i18n="layers.cctv.focus">FOCUS</button>
<button id="cctv-coverage-btn" class="scene-btn" data-i18n="layers.cctv.coverageOff">COVERAGE OFF</button>
<button id="cctv-auto-hop-btn" class="scene-btn" data-i18n="layers.cctv.autoHopOff">AUTO HOP OFF</button>
</div>
<div class="cctv-controls">
<button id="cctv-projection-btn" class="scene-btn">PROJECTION ON</button>
<button id="cctv-projection-btn" class="scene-btn" data-i18n="layers.cctv.projectionOn">PROJECTION ON</button>
<span id="cctv-quality-chip">CAL · --</span>
</div>
<div class="cctv-calibration-block">
<div class="cctv-calibration-header">
<div class="cctv-summary-label">CALIBRATION</div>
<button id="cctv-adjust-btn" class="scene-btn" title="Drag the camera in the world: rings rotate, arrows move, handles set range/FOV">ADJUST</button>
<div class="cctv-summary-label" data-i18n="layers.cctv.calibrationLabel">CALIBRATION</div>
<button id="cctv-adjust-btn" class="scene-btn" title="Drag the camera in the world: rings rotate, arrows move, handles set range/FOV" data-i18n-title="layers.cctv.adjustTitle" data-i18n="layers.cctv.adjustLabel">ADJUST</button>
</div>
<div id="cctv-cal-readout" class="cctv-cal-readout" aria-label="Camera pose — click a value to type">
<button class="cctv-cal-value" data-cal-field="heading" title="Heading (compass °) — click to type">HDG --</button>
<button class="cctv-cal-value" data-cal-field="pitch" title="Pitch (° up/down) — click to type">PITCH --</button>
<button class="cctv-cal-value" data-cal-field="fov" title="Horizontal FOV (°) — click to type">FOV --</button>
<button class="cctv-cal-value" data-cal-field="range" title="Range / monitor-plane distance (m) — click to type">RANGE --</button>
<button class="cctv-cal-value" data-cal-field="height" title="Mount height above ground (m) — click to type">HGT --</button>
<button class="cctv-cal-value" data-cal-field="north" title="North offset from catalog position (m) — click to type">ΔN --</button>
<button class="cctv-cal-value" data-cal-field="east" title="East offset from catalog position (m) — click to type">ΔE --</button>
<div id="cctv-cal-readout" class="cctv-cal-readout" aria-label="Camera pose — click a value to type" data-i18n-aria-label="layers.cctv.calPoseAriaLabel">
<button class="cctv-cal-value" data-cal-field="heading" title="Heading (compass °) — click to type" data-i18n-title="layers.cctv.calHeadingTitle">HDG --</button>
<button class="cctv-cal-value" data-cal-field="pitch" title="Pitch (° up/down) — click to type" data-i18n-title="layers.cctv.calPitchTitle">PITCH --</button>
<button class="cctv-cal-value" data-cal-field="fov" title="Horizontal FOV (°) — click to type" data-i18n-title="layers.cctv.calFovTitle">FOV --</button>
<button class="cctv-cal-value" data-cal-field="range" title="Range / monitor-plane distance (m) — click to type" data-i18n-title="layers.cctv.calRangeTitle">RANGE --</button>
<button class="cctv-cal-value" data-cal-field="height" title="Mount height above ground (m) — click to type" data-i18n-title="layers.cctv.calHeightTitle">HGT --</button>
<button class="cctv-cal-value" data-cal-field="north" title="North offset from catalog position (m) — click to type" data-i18n-title="layers.cctv.calNorthTitle">ΔN --</button>
<button class="cctv-cal-value" data-cal-field="east" title="East offset from catalog position (m) — click to type" data-i18n-title="layers.cctv.calEastTitle">ΔE --</button>
</div>
<div class="cctv-controls cctv-calibration-actions">
<button id="cctv-calib-save-btn" class="scene-btn">SAVE CAL</button>
<button id="cctv-calib-reset-btn" class="scene-btn">RESET CAL</button>
<button id="cctv-calib-save-btn" class="scene-btn" data-i18n="layers.cctv.saveCal">SAVE CAL</button>
<button id="cctv-calib-reset-btn" class="scene-btn" data-i18n="layers.cctv.resetCal">RESET CAL</button>
</div>
</div>
<div class="cctv-summary-label">SCENE SUMMARY</div>
<div id="cctv-summary">Enable CCTV to start camera-linked intelligence summaries.</div>
<div class="cctv-summary-label" data-i18n="layers.cctv.summaryLabel">SCENE SUMMARY</div>
<div id="cctv-summary" data-i18n="layers.cctv.summaryIdle">Enable CCTV to start camera-linked intelligence summaries.</div>
</div>
</div>
</div>
@@ -76,35 +76,35 @@
<div class="panel-glow"></div>
<div class="scene-panel-inner">
<div class="panel-header">
<span class="panel-title">SCENES</span>
<span class="panel-title" data-i18n="setup.scenes.panelTitle">SCENES</span>
<span class="panel-divider"></span>
<button class="panel-collapse-btn" data-collapse-target="scene-panel" title="Collapse panel">+</button>
<button class="panel-collapse-btn" data-collapse-target="scene-panel" title="Collapse panel" data-i18n-title="setup.scenes.collapseTitle">+</button>
</div>
<div class="scene-controls">
<select id="scene-select" aria-label="Scene recipe"></select>
<button id="scene-new-btn" class="scene-btn">NEW</button>
<button id="scene-delete-btn" class="scene-btn scene-btn-danger">DEL</button>
<select id="scene-select" aria-label="Scene recipe" data-i18n-aria-label="setup.scenes.recipeAriaLabel"></select>
<button id="scene-new-btn" class="scene-btn" data-i18n="setup.scenes.new">NEW</button>
<button id="scene-delete-btn" class="scene-btn scene-btn-danger" data-i18n="setup.scenes.delete">DEL</button>
</div>
<div class="scene-controls">
<button id="scene-capture-btn" class="scene-btn">CAPTURE SHOT</button>
<button id="scene-update-shot-btn" class="scene-btn">UPDATE SHOT</button>
<button id="scene-capture-btn" class="scene-btn" data-i18n="setup.scenes.capture">CAPTURE SHOT</button>
<button id="scene-update-shot-btn" class="scene-btn" data-i18n="setup.scenes.updateShot">UPDATE SHOT</button>
</div>
<div id="scene-shot-list" class="scene-shot-list"></div>
<div class="scene-controls">
<button id="scene-start-btn" class="scene-btn">START</button>
<button id="scene-stop-btn" class="scene-btn scene-btn-danger">STOP</button>
<button id="scene-next-btn" class="scene-btn">NEXT</button>
<button id="scene-start-btn" class="scene-btn" data-i18n="setup.scenes.start">START</button>
<button id="scene-stop-btn" class="scene-btn scene-btn-danger" data-i18n="setup.scenes.stop">STOP</button>
<button id="scene-next-btn" class="scene-btn" data-i18n="setup.scenes.next">NEXT</button>
</div>
<div class="scene-controls">
<button id="scene-export-btn" class="scene-btn">EXPORT PRESETS</button>
<button id="scene-import-btn" class="scene-btn">IMPORT</button>
<button id="scene-download-btn" class="scene-btn">RUN LOG</button>
<button id="scene-export-btn" class="scene-btn" data-i18n="setup.scenes.exportPresets">EXPORT PRESETS</button>
<button id="scene-import-btn" class="scene-btn" data-i18n="setup.scenes.import">IMPORT</button>
<button id="scene-download-btn" class="scene-btn" data-i18n="setup.scenes.runLog">RUN LOG</button>
<input type="file" id="scene-import-file" accept="application/json,.json" hidden />
</div>
<div class="scene-progress">
<div id="scene-progress-fill">0%</div>
</div>
<div id="scene-status">Ready</div>
<div id="scene-status" data-i18n="setup.scenes.statusReady">Ready</div>
</div>
</div>
+8 -8
View File
@@ -4,22 +4,22 @@
that payload, so the key registry lives in src/keySetupCore.mjs alone. -->
<button id="key-setup-chip" type="button" hidden aria-haspopup="dialog" aria-controls="key-setup">
<span class="material-symbols-outlined" aria-hidden="true">bolt</span>
<span data-key-setup-chip-label>POWER UP</span>
<span data-key-setup-chip-label data-i18n="setup.keySetup.chip">POWER UP</span>
</button>
<aside id="key-setup" role="dialog" aria-labelledby="key-setup-title" aria-describedby="key-setup-description" hidden>
<div class="key-setup-scanline" aria-hidden="true"></div>
<header class="key-setup-header">
<span class="key-setup-kicker">GROUND STATION · PROVIDER SETTINGS</span>
<button type="button" class="key-setup-close" data-key-setup-close aria-label="Close key setup">
<span class="key-setup-kicker" data-i18n="setup.keySetup.kicker">GROUND STATION · PROVIDER SETTINGS</span>
<button type="button" class="key-setup-close" data-key-setup-close aria-label="Close key setup" data-i18n-aria-label="setup.keySetup.closeAriaLabel">
<span class="material-symbols-outlined" aria-hidden="true">close</span>
</button>
</header>
<h2 id="key-setup-title">Power up the globe</h2>
<p id="key-setup-description">The globe already flies keyless. Every key below switches on another real feed — paste one and it's saved into this app's local configuration, then the server restarts itself. Server-side keys stay on this machine; Google Maps and Cesium ion run in the browser and must be provider-restricted. Keys you configured elsewhere are shown but never touched.</p>
<h2 id="key-setup-title" data-i18n="setup.keySetup.title">Power up the globe</h2>
<p id="key-setup-description" data-i18n="setup.keySetup.description">The globe already flies keyless. Every key below switches on another real feed — paste one and it's saved into this app's local configuration, then the server restarts itself. Server-side keys stay on this machine; Google Maps and Cesium ion run in the browser and must be provider-restricted. Keys you configured elsewhere are shown but never touched.</p>
<div class="key-setup-rows" data-key-setup-rows></div>
<div class="key-setup-footer">
<button type="button" class="key-setup-apply" data-key-setup-apply>SAVE KEYS</button>
<span class="key-setup-hint">ESC to close</span>
<button type="button" class="key-setup-apply" data-key-setup-apply data-i18n="setup.keySetup.apply">SAVE KEYS</button>
<span class="key-setup-hint" data-i18n="setup.keySetup.hint">ESC to close</span>
</div>
<p class="key-setup-note" data-key-setup-status role="status" aria-live="polite">The Google Maps key buys the photorealistic planet — everything else stacks on top.</p>
<p class="key-setup-note" data-key-setup-status role="status" aria-live="polite" data-i18n="setup.keySetup.note">The Google Maps key buys the photorealistic planet — everything else stacks on top.</p>
</aside>
+10 -10
View File
@@ -2,7 +2,7 @@
<div id="world-overlay-root" aria-hidden="true">
<canvas id="world-overlay-canvas"></canvas>
</div>
<div id="world-overlay-actions" class="world-overlay-accessibility" role="region" aria-label="Visible map targets">
<div id="world-overlay-actions" class="world-overlay-accessibility" role="region" aria-label="Visible map targets" data-i18n-aria-label="layers.overlay.regionAriaLabel">
<div id="world-overlay-action-list"></div>
<div id="world-overlay-status" role="status" aria-live="polite" aria-atomic="true"></div>
</div>
@@ -11,21 +11,21 @@
<div id="title-bar">
<div class="title-glow"></div>
<h1><span class="title-logo brand-logo" data-logo-gaze data-logo-src="/logo.svg" aria-hidden="true"><img src="/logo.svg" alt="" /></span> <span>GOD'S EYE <span class="title-accent">VIEW</span></span></h1>
<p class="subtitle">NO PLACE LEFT BEHIND</p>
<p class="subtitle" data-i18n="shell.title.subtitle">NO PLACE LEFT BEHIND</p>
</div>
<!-- Style indicator -->
<div id="style-indicator">
<span class="indicator-label">ACTIVE STYLE</span>
<span class="indicator-label" data-i18n="cockpit.presets.activeStyleLabel">ACTIVE STYLE</span>
<span class="indicator-value" id="active-style-name">NORMAL</span>
</div>
<!-- Top-center camera and share actions -->
<nav id="top-center-actions" aria-label="Globe actions">
<button id="clear-selected-layers" type="button" aria-label="Clear selected data layers" title="Turn off all selected data layers">
<nav id="top-center-actions" aria-label="Globe actions" data-i18n-aria-label="shell.actions.navAriaLabel">
<button id="clear-selected-layers" type="button" aria-label="Clear selected data layers" title="Turn off all selected data layers" data-i18n-aria-label="shell.actions.clearLayers.ariaLabel" data-i18n-title="shell.actions.clearLayers.title">
<span class="material-symbols-outlined" aria-hidden="true">layers_clear</span>
</button>
<button id="share-btn" type="button" aria-label="Copy share link" title="Copy share link">
<button id="share-btn" type="button" aria-label="Copy share link" title="Copy share link" data-i18n-aria-label="shell.actions.share.ariaLabel" data-i18n-title="shell.actions.share.title">
<span class="share-icon" aria-hidden="true">&#x1F517;</span>
</button>
<button id="tilt-map-view" type="button" aria-label="Tilt map to oblique view" aria-pressed="false" title="Toggle straight-down and tilted map views">
@@ -35,20 +35,20 @@
<span class="camera-compass-n" aria-hidden="true">N</span>
<span class="camera-compass-needle material-symbols-outlined" aria-hidden="true">navigation</span>
</button>
<button id="reset-globe-view" type="button" aria-label="Reset to full globe view" title="Reset camera and return to full globe view">
<button id="reset-globe-view" type="button" aria-label="Reset to full globe view" title="Reset camera and return to full globe view" data-i18n-aria-label="shell.actions.resetView.ariaLabel" data-i18n-title="shell.actions.resetView.title">
<span class="material-symbols-outlined" aria-hidden="true">public</span>
</button>
</nav>
<div id="global-loading-status" role="status" aria-live="polite" aria-atomic="true" hidden>
<span id="global-loading-label">LOADING LIVE DATA</span>
<span id="global-loading-label" data-i18n="shell.status.loadingLiveData">LOADING LIVE DATA</span>
<span id="global-loading-detail"></span>
</div>
<div id="traffic-sync-chip" aria-live="polite">
<span id="traffic-sync-label">syncing road network</span>
<span id="traffic-sync-label" data-i18n="shell.status.trafficSyncing">syncing road network</span>
<span id="traffic-sync-progress">0%</span>
</div>
<div id="cctv-sync-chip" aria-live="polite">
<span id="cctv-sync-label">loading frames</span>
<span id="cctv-sync-label" data-i18n="shell.status.framesLoading">loading frames</span>
<span id="cctv-sync-progress">0/0</span>
</div>
+8 -8
View File
@@ -5,9 +5,9 @@
<aside id="first-run-launcher" role="dialog" aria-labelledby="first-run-title" aria-describedby="first-run-description" hidden>
<div class="first-run-scanline" aria-hidden="true"></div>
<header class="first-run-header">
<span class="first-run-kicker">MISSION CONTROL · FIRST LAUNCH</span>
<span class="first-run-kicker" data-i18n="setup.firstRun.kicker">MISSION CONTROL · FIRST LAUNCH</span>
</header>
<h2 id="first-run-title">Choose your first view</h2>
<h2 id="first-run-title" data-i18n="setup.firstRun.title">Choose your first view</h2>
<!-- The card's one persuasive line, owner-authored and VERBATIM (2026-08-23)
— including the unspaced em dash. The arc it has to carry: this looks
like something you should not have access to → the sources are all
@@ -16,12 +16,12 @@
<div class="first-run-choices">
<button type="button" data-first-run-choice="contacts">
<span class="material-symbols-outlined" aria-hidden="true">radar</span>
<span><strong>LIVE CONTACTS</strong><small>Aircraft, vessels and nearby intelligence</small></span>
<span><strong data-i18n="setup.firstRun.choice.contacts">LIVE CONTACTS</strong><small data-i18n="setup.firstRun.choice.contactsSub">Aircraft, vessels and nearby intelligence</small></span>
<span class="material-symbols-outlined first-run-arrow" aria-hidden="true">arrow_forward</span>
</button>
<button type="button" data-first-run-choice="space-missions">
<span class="material-symbols-outlined" aria-hidden="true">rocket_launch</span>
<span><strong>SPACE MISSIONS</strong><small>Launches, spacecraft and orbital context</small></span>
<span><strong data-i18n="setup.firstRun.choice.spaceMissions">SPACE MISSIONS</strong><small data-i18n="setup.firstRun.choice.spaceMissionsSub">Launches, spacecraft and orbital context</small></span>
<span class="material-symbols-outlined first-run-arrow" aria-hidden="true">arrow_forward</span>
</button>
<button type="button" data-first-run-choice="environmental">
@@ -35,18 +35,18 @@
</button>
<button type="button" class="first-run-explore" data-first-run-choice="explore">
<span class="material-symbols-outlined" aria-hidden="true">public</span>
<span><strong>EXPLORE MANUALLY</strong><small>Begin with a clean globe</small></span>
<span data-i18n="setup.firstRun.choice.explore" data-i18n="setup.firstRun.choice.exploreSub"><strong>EXPLORE MANUALLY</strong><small>Begin with a clean globe</small></span>
<span class="material-symbols-outlined first-run-arrow" aria-hidden="true">arrow_forward</span>
</button>
</div>
<div class="first-run-footer">
<label class="first-run-suppress">
<input type="checkbox" data-first-run-suppress />
<span>Don't show this again</span>
<span data-i18n="setup.firstRun.suppress">Don't show this again</span>
</label>
<span>ESC to dismiss</span>
<span data-i18n="setup.firstRun.dismissHint">ESC to dismiss</span>
</div>
<!-- Kept out of the footer row: it swaps to mission progress and errors, and
a live region that also holds layout makes the row jump as it speaks. -->
<p class="first-run-note" data-first-run-status role="status" aria-live="polite">Tip: the GEV MIC button in the dock lets you talk to the map.</p>
<p class="first-run-note" data-first-run-status role="status" aria-live="polite" data-i18n="setup.firstRun.note">Tip: the GEV MIC button in the dock lets you talk to the map.</p>
</aside>