Files
gods-eye-view/src/ui/styles/foundation.css
T
Said Turksever 44f9138d1e Keep panel titles inside their cards and scope the hover
Collapsed Recent Imagery (English) and Data Layers (Portuguese) pushed
their toggles out of the card. Collapsed rail launchers now drop the
divider and pack closer, translated collapsed titles use compact type,
and any title that still does not fit ends in an ellipsis.

The hover now matches the dock trays' hover rather than their pinned
look, only on pointers that can hover, and recolours only the hovered
panel's own header, so Radio and body labels keep their colours.

The cyber theme keeps main's gutters and Display toggle size, the dock
and rails share --dock-label-* tokens, and the dimmed open Context title
rule is gone.
2026-10-30 13:05:33 +00:00

661 lines
17 KiB
CSS

/* ═══════════════════════════════════════════════
GOD'S EYE VIEW — Dark Sci-Fi UI
Apple meets Blade Runner
═══════════════════════════════════════════════ */
:root {
--bg-dark: #0a0a0f;
--glass-bg: rgba(12, 12, 20, 0.72);
--glass-border: rgba(255, 255, 255, 0.08);
--glass-border-hover: rgba(255, 255, 255, 0.15);
--accent: #00d4ff;
--accent-dim: rgba(0, 212, 255, 0.15);
--accent-glow: rgba(0, 212, 255, 0.4);
--text-primary: #e8eaed;
--text-secondary: rgba(232, 234, 237, 0.5);
--text-dim: rgba(232, 234, 237, 0.3);
/* Opaque surface for browser-painted menus, which cannot use --glass-bg. */
--menu-bg: #12121c;
--font-mono: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace;
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
--panel-radius: 16px;
--btn-radius: 10px;
--transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
/* Label type shared by the command dock and the side panel titles. */
--dock-label-color: rgba(206, 232, 239, 0.72);
--dock-label-font-size: 0.58rem;
--dock-label-font-weight: 500;
--dock-label-letter-spacing: 0.13rem;
--transition-smooth: 300ms cubic-bezier(0.4, 0, 0.2, 1);
--left-stack-x: 52px;
--right-rail-x: 52px;
--left-stack-top: 26vh;
--left-stack-gap: 72px;
--left-collapsed-width: 176px;
--display-panel-expanded-width: 272px;
--cockpit-radio-collapsed-width: 148px;
--cockpit-radio-expanded-width: 232px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* Keyboard focus is an app-wide state and must survive selected/active button
skins. Individual controls can choose an inset/outset offset, but no local
`outline: none` rule may erase the ring. */
:where(
button,
a[href],
input,
select,
textarea,
summary,
[tabindex],
[role='button'],
[role='checkbox'],
[role='combobox'],
[role='link'],
[role='listbox'],
[role='menuitem'],
[role='menuitemcheckbox'],
[role='menuitemradio'],
[role='option'],
[role='radio'],
[role='searchbox'],
[role='slider'],
[role='spinbutton'],
[role='switch'],
[role='tab'],
[role='textbox'],
[role='treeitem']
):focus-visible {
outline: 2px solid var(--text-primary) !important;
outline-offset: 2px;
}
/* Native `<select>` popups are painted by the browser, not by this sheet: the
closed control inherits its panel skin, but the option list falls back to the
platform's light palette unless the control declares a scheme. Declare it on
the controls rather than on :root so the app keeps its own hand-tuned
scrollbar and input skins, and set the option colors explicitly because the
popup surface is not themed on every platform. */
select {
color-scheme: dark;
}
select option,
select optgroup {
background-color: var(--menu-bg);
color: var(--text-primary);
}
select option:disabled {
color: var(--text-dim);
}
html,
body {
width: 100%;
height: 100%;
overflow: hidden;
background: var(--bg-dark);
font-family: var(--font-sans);
color: var(--text-primary);
-webkit-font-smoothing: antialiased;
}
/* ── Cesium Container ─────────────────────────── */
#cesiumContainer {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}
/* Unified world-overlay host: detection uses one host-owned blend surface
beneath the shared text/card canvas; both share one frame path. The root
holds ONLY the card canvas — it is a stacking context (z-index below), and
a canvas that blends against the scene cannot live inside one. */
#world-overlay-root {
position: absolute;
inset: 0;
z-index: 6;
overflow: hidden;
pointer-events: none;
}
#world-overlay-detection-surface,
#world-overlay-canvas {
position: absolute;
inset: 0;
display: block;
width: 100%;
height: 100%;
pointer-events: none;
}
/* Scope mask — the explicit circular viewport treatment (see
src/scopeMask.js for the emergent-artifact history). z2 keeps it above
the WebGL scene but under the z5 detection surface, preserving the old
compositing order where detection's blend rode the masked scene. */
#scope-mask {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
/* z1 like the celestial ring; the ring is appended LATER so DOM order
keeps it ABOVE the mask — matching the old stacking, where the DOM
ring rode above the in-scene scope artifact. Detection (z5) and the
overlay root (z6) stay above both. */
z-index: 1;
pointer-events: none;
}
/* The detection surface is host-owned but parented into #cesiumContainer
(position:absolute, z-index:auto — NOT a stacking context) so its
`mix-blend-mode: screen` composites against the WebGL scene. z5 keeps it
under the z6 root that carries the shared card canvas; do not move this
surface inside #world-overlay-root, which would isolate the blend. */
#world-overlay-detection-surface {
z-index: 5;
}
/* Canvas cards retain their spatial renderer; this parallel focusable list
exposes only the currently painted actionable targets to assistive tech. */
.world-overlay-accessibility {
position: fixed;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* ── Source-backed cockpit weather ─────────────
Low-resolution GPU cloud pass composited only inside the cockpit keyhole. */
#cockpit-cloud-effects {
position: fixed;
inset: 0;
z-index: 7;
display: none;
width: 100vw;
height: 100vh;
opacity: 0;
pointer-events: none;
clip-path: circle(min(40vw, 52vh) at 50% 50%);
filter: blur(0.7px) saturate(0.72) contrast(1.08);
transform: translateZ(0);
transition: opacity 900ms ease;
}
body.cockpit-mode #cockpit-cloud-effects.active {
display: block;
opacity: 0.86;
}
/* ── Full-globe celestial keyhole ring ─────── */
.celestial-ring-overlay {
position: absolute;
inset: 0;
/* Above the z1 scope mask regardless of DOM order (the ring is built by
StyleManager before the scope installs) — the ring always rode above
the scope treatment. Detection (z5) and the overlay root (z6) stay on
top of both. */
z-index: 2;
overflow: hidden;
pointer-events: none;
opacity: 0;
visibility: hidden;
transition:
opacity 300ms ease,
visibility 300ms ease;
}
.celestial-ring-overlay.visible {
opacity: 1;
visibility: visible;
}
.celestial-ring-overlay.disabled {
display: none;
}
.celestial-ring-canvas {
position: absolute;
inset: 0;
display: block;
transform-origin: 50% 50%;
will-change: transform, opacity;
}
.celestial-ring-outline {
position: absolute;
box-sizing: border-box;
border: 0.8px solid rgba(151, 218, 232, 0.095);
border-radius: 50%;
will-change: width, height;
}
.celestial-marker {
position: absolute;
z-index: 2;
transform: translate(-50%, -50%);
font-family: 'Material Symbols Outlined';
font-size: 20px;
font-weight: 400;
font-style: normal;
line-height: 1;
font-variation-settings:
'FILL' 0,
'wght' 400,
'GRAD' 0,
'opsz' 20;
transition: opacity 120ms linear;
user-select: none;
}
.celestial-sun {
color: rgba(231, 194, 72, 0.92);
text-shadow:
0 0 7px rgba(231, 194, 72, 0.72),
0 0 18px rgba(231, 194, 72, 0.3);
}
.celestial-moon {
color: rgba(55, 211, 239, 0.84);
text-shadow:
0 0 7px rgba(55, 211, 239, 0.65),
0 0 17px rgba(55, 211, 239, 0.25);
}
#celestial-toggle:disabled {
cursor: not-allowed;
opacity: 0.42;
}
/* Hide Cesium default UI elements */
.cesium-viewer-toolbar,
.cesium-viewer-animationContainer,
.cesium-viewer-timelineContainer,
.cesium-viewer-bottom,
.cesium-viewer-fullscreenContainer,
.cesium-viewer-vrContainer,
.cesium-viewer-geocoderContainer,
.cesium-viewer-selectionIndicatorContainer,
.cesium-viewer-infoBoxContainer {
display: none !important;
}
/* Data-source attribution (Google Maps / 3D Tiles, etc.).
Required by the Google Maps Platform Terms of Service — keep it visible.
Styled to be unobtrusive but legible against the dark UI. */
#cesium-credits {
position: fixed;
/* Bottom-LEFT corner, mirrored from the GEV mic pill (right:36/bottom:36) so the two bottom
corners are symmetric; sits below the coordinate HUD (.hud-bottom-left, bottom:180). Was
dead-center at the bottom, tucked under the LOCATIONS/STYLE panels. Must stay visible (Google
/Cesium attribution ToS). */
bottom: 36px;
left: 36px;
z-index: 90;
max-width: 90vw;
/* Keep the Google logo + "Data attribution" link on ONE line — they were wrapping to a second
row (looked like it was falling over). nowrap + a roomy max-width holds them inline. */
white-space: nowrap;
pointer-events: auto;
font-family: var(--font-mono);
font-size: 10px;
color: rgba(232, 234, 237, 0.55);
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.9);
}
#cesium-credits .cesium-credit-logoContainer,
#cesium-credits .cesium-credit-textContainer,
#cesium-credits .cesium-credit-expand-link {
display: inline-block;
vertical-align: middle;
}
#cesium-credits a,
#cesium-credits .cesium-credit-expand-link {
color: rgba(232, 234, 237, 0.7);
}
/* Cesium's stock attribution lightbox grows with every registered data source.
Keep the title and close affordance fixed while the complete, unabridged
credit list wraps and scrolls inside a compact panel. */
.cesium-credit-lightbox-overlay {
z-index: 200 !important;
}
.cesium-credit-lightbox {
display: flex;
flex-direction: column;
width: 470px;
max-width: calc(100vw - 2rem);
max-height: min(70dvh, 36rem);
overflow: hidden;
}
.cesium-credit-lightbox > ul {
flex: 1;
min-height: 0;
margin: 0;
padding: 8px 20px 14px 40px;
overflow-x: hidden;
overflow-y: auto;
overscroll-behavior: contain;
scrollbar-gutter: stable;
font-size: 12px !important;
line-height: 1.35;
white-space: normal;
}
.cesium-credit-lightbox > ul > li {
padding-bottom: 5px;
overflow-wrap: anywhere;
}
@media (max-width: 575px) {
.cesium-credit-lightbox {
width: 100% !important;
max-width: none !important;
height: 100% !important;
max-height: 100dvh !important;
margin: 0 !important;
}
}
/* Clean-view / recording: the Google + Cesium attribution MUST stay visible.
Google Maps Platform ToS and Cesium's terms require the credit whenever the
content is displayed — and these are exactly the modes used to record demos
and launch videos, so hiding #cesium-credits here (as it was before) shipped
non-compliant footage. The full credit line — Cesium/Ion logo, "Google Maps",
and the "Data attribution" link that opens the per-layer license popover —
stays on screen; it's already styled small and unobtrusive above. The GEV
chrome (panels/HUD) still fades via its own rules; only the required notice
is exempt. (Note: Cesium re-applies inline styles on the credit elements each
frame, so the on-screen credit content is owned by Cesium's update loop, not
CSS overrides — we deliberately don't fight it here.) */
#gev-voice-control {
position: fixed;
right: 36px;
bottom: 36px;
/* Z ladder: panels ~100, voice pill 150, toast 200, clean-view-exit 300 */
z-index: 150;
display: flex;
align-items: center;
gap: 10px;
min-width: 214px;
padding: 9px 14px;
color: var(--text-primary);
background: var(--glass-bg);
border: 1px solid var(--glass-border);
border-radius: 999px;
box-shadow:
0 8px 32px rgba(0, 0, 0, 0.5),
0 0 0 1px rgba(255, 255, 255, 0.03) inset,
0 1px 0 rgba(255, 255, 255, 0.06) inset,
0 0 20px rgba(0, 212, 255, 0.08);
backdrop-filter: blur(24px) saturate(1.4);
-webkit-backdrop-filter: blur(24px) saturate(1.4);
font-family: var(--font-mono);
pointer-events: auto;
}
#gev-voice-button {
width: 72px;
height: 34px;
flex: 0 0 auto;
color: var(--accent);
background: rgba(0, 212, 255, 0.06);
border: 1px solid rgba(0, 212, 255, 0.28);
border-radius: var(--btn-radius);
font-family: var(--font-mono);
font-size: 10px;
letter-spacing: 0;
cursor: pointer;
}
#gev-voice-button:hover {
color: #ffffff;
border-color: var(--accent);
box-shadow: 0 0 12px rgba(0, 212, 255, 0.22);
}
.gev-voice-readout {
min-width: 0;
line-height: 1.2;
text-align: right;
}
#gev-voice-status {
font-size: 10px;
color: var(--accent);
}
#gev-voice-detail {
max-width: 132px;
margin-top: 2px;
overflow: hidden;
color: var(--text-secondary);
font-size: 9px;
text-overflow: ellipsis;
white-space: nowrap;
}
#gev-voice-control[data-status='listening'] {
border-color: rgba(92, 255, 198, 0.45);
}
#gev-voice-control[data-status='listening'] #gev-voice-status {
color: #5cffc6;
}
#gev-voice-control[data-status='executing'] {
border-color: rgba(255, 220, 120, 0.55);
}
#gev-voice-control[data-status='executing'] #gev-voice-status {
color: #ffdc78;
}
#gev-voice-control[data-status='error'] {
align-items: flex-start;
width: min(440px, calc(100vw - 72px));
border-color: rgba(255, 92, 110, 0.6);
/* Multi-line error block reads as a panel, not a capsule */
border-radius: var(--panel-radius);
}
#gev-voice-control[data-status='error'] #gev-voice-status {
color: #ff5c6e;
}
#gev-voice-control[data-status='error'] .gev-voice-readout {
flex: 1;
text-align: left;
}
#gev-voice-control[data-status='error'] #gev-voice-detail {
max-width: none;
overflow: visible;
color: #ffb4bd;
line-height: 1.45;
text-overflow: clip;
white-space: pre-wrap;
overflow-wrap: anywhere;
user-select: text;
}
/* ── Title Bar ────────────────────────────────── */
#title-bar {
position: fixed;
top: 32px;
left: 36px;
z-index: 100;
pointer-events: none;
user-select: none;
}
#title-bar .title-glow {
position: absolute;
top: -20px;
left: -20px;
width: 200px;
height: 80px;
background: radial-gradient(ellipse, var(--accent-dim) 0%, transparent 70%);
filter: blur(20px);
opacity: 0.6;
}
#title-bar h1 {
display: flex;
align-items: center;
gap: 12px;
font-family: var(--font-mono);
font-size: 26px;
font-weight: 600;
letter-spacing: 8px;
color: var(--text-primary);
text-shadow: 0 0 30px var(--accent-glow);
position: relative;
}
#title-bar .title-logo {
position: relative;
display: block;
width: 52px;
height: auto;
filter: drop-shadow(0 0 12px rgba(0, 246, 255, 0.5));
}
#title-bar .title-logo::before,
#title-bar .title-logo::after {
--radio-broadcast-opacity: 0.28;
content: '';
position: absolute;
z-index: 1;
left: 50%;
top: -9px;
width: 13px;
height: 7px;
border: 1px solid var(--accent);
border-bottom: 0;
border-radius: 999px 999px 0 0;
opacity: 0;
pointer-events: none;
transform: translateX(-50%);
transform-origin: 50% 100%;
}
#title-bar .title-logo::after {
--radio-broadcast-opacity: 0.17;
top: -15px;
width: 27px;
height: 13px;
}
#title-bar.radio-broadcasting .title-logo::before,
#title-bar.radio-broadcasting .title-logo::after {
opacity: var(--radio-broadcast-opacity);
animation: title-radio-broadcast 1.8s ease-in-out infinite;
}
#title-bar.radio-broadcasting .title-logo::after {
animation-delay: 0.28s;
}
@keyframes title-radio-broadcast {
0%,
100% {
opacity: calc(var(--radio-broadcast-opacity) * 0.55);
transform: translateX(-50%) scale(0.94);
}
50% {
opacity: var(--radio-broadcast-opacity);
transform: translateX(-50%) scale(1);
}
}
@media (prefers-reduced-motion: reduce) {
#title-bar.radio-broadcasting .title-logo::before,
#title-bar.radio-broadcasting .title-logo::after {
animation: none;
}
}
.brand-logo {
aspect-ratio: 775 / 520;
overflow: visible;
pointer-events: none;
}
.brand-logo > img,
.brand-logo > svg {
display: block;
width: 100%;
height: 100%;
}
#title-bar .title-accent,
.loader-content .title-accent {
color: var(--accent);
font-weight: 300;
}
#title-bar .subtitle {
font-family: var(--font-mono);
font-size: 11px;
letter-spacing: 5px;
color: var(--text-dim);
margin-top: 8px;
text-transform: uppercase;
}
#title-bar .frame-rate-readout {
margin-top: 6px;
font-family: var(--font-mono);
font-size: 11px;
font-variant-numeric: tabular-nums;
letter-spacing: 1px;
color: var(--accent);
}
/* ── Style Indicator ──────────────────────────── */
#style-indicator {
position: fixed;
top: 32px;
right: 36px;
z-index: 100;
pointer-events: none;
user-select: none;
text-align: right;
}
#style-indicator .indicator-label {
display: block;
font-family: var(--font-mono);
font-size: 10px;
letter-spacing: 3px;
color: var(--text-dim);
text-transform: uppercase;
}
#style-indicator .indicator-value {
display: block;
font-family: var(--font-mono);
font-size: 16px;
font-weight: 500;
letter-spacing: 5px;
color: var(--accent);
margin-top: 4px;
text-shadow: 0 0 20px var(--accent-glow);
transition: var(--transition-smooth);
}