feat(ui): redesign GeoIntel as Atlas Workbench
GeoIntel release gates / Compile, test, contracts and builds (push) Canceled after 0s
GeoIntel release gates / Python and npm vulnerability policy (push) Canceled after 0s
GeoIntel release gates / GIS image, SBOM and container scan (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-19 14:24:54 +02:00
parent ea27873dce
commit 87345f1a8c
19 changed files with 1817 additions and 631 deletions
+13 -5
View File
@@ -113,11 +113,19 @@ The workbench uses a task-based shell instead of a single long panel stack. `App
The premium V1 presentation layer lives in `src/styles/premium.css`. It groups navigation by Workspace, Analyze and Deliver, removes the permanent inspector column, gives desktop/ultrawide workspaces stable readable widths and switches narrow screens to full-width content with a horizontally scrollable navigation rail. API calls and workflow state remain owned by the existing hooks.
The final visual polish layer lives in `src/styles/visual-polish.css`. It keeps
the map dominant, places the results rail beside the map when the viewport is
wide enough, preserves a readable two-column map flow on smaller desktops and
stacks the context header and navigation explicitly on mobile. It contains no
workflow or API logic.
The Stitch-guided Atlas Workbench redesign lives in
`src/styles/atlas-workbench.css`, with the shared icon navigation in
`components/shell/WorkbenchNavigation.tsx`. The application uses one compact
navigation rail, one context bar and task-specific work surfaces. The primary
map keeps the theme rail, map and insight rail in one row on normal desktop
and gives additional ultrawide space to the map instead of stretching text
panels. Mobile keeps all workspaces in a horizontally scrollable rail and
bounds the theme list so the map remains reachable early in the flow.
The visual contract, Stitch project and screen references are documented in
`docs/UI_DESIGN_SYSTEM.md`. The redesign changes presentation and information
hierarchy only; API, persistence and GIS behavior remain owned by the existing
hooks and services.
## Current component boundaries
+10
View File
@@ -8,6 +8,7 @@
"name": "geointel-frontend",
"version": "1.0.0",
"dependencies": {
"lucide-react": "^1.25.0",
"maplibre-gl": "^4.7.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
@@ -2415,6 +2416,15 @@
"yallist": "^3.0.2"
}
},
"node_modules/lucide-react": {
"version": "1.25.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.25.0.tgz",
"integrity": "sha512-/mdJTRbiwcLOQ1NZZK1amZF9rIZyvO18D6r9TngE6TG1NmqHgFuT4eE7Xrkm9UsXMbBJD1NlfwHVltCDWHrOTw==",
"license": "ISC",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/lz-string": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
+1
View File
@@ -16,6 +16,7 @@
"node": "^20.19.0 || >=22.12.0"
},
"dependencies": {
"lucide-react": "^1.25.0",
"maplibre-gl": "^4.7.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
+48 -78
View File
@@ -1,7 +1,7 @@
import { useEffect, useMemo, useRef, useState } from 'react'
import './styles/app.css'
import './styles/premium.css'
import './styles/visual-polish.css'
import './styles/atlas-workbench.css'
import { ChangeDetectionPanel } from './components/analysis/ChangeDetectionPanel'
import { GeoAssistantPanel } from './components/assistant/GeoAssistantPanel'
import { DatasetPanel } from './components/datasets/DatasetPanel'
@@ -18,6 +18,11 @@ import { QualityResultsPanel } from './components/quality/QualityResultsPanel'
import type { DatasetCreateResponse } from './types'
import { ProviderPanel } from './components/providers/ProviderPanel'
import { SegmentationLab } from './components/segmentation/SegmentationLab'
import {
WorkbenchNavigation,
type WorkspaceNavigationGroup,
type WorkspaceNavigationItem,
} from './components/shell/WorkbenchNavigation'
import { useChangeDetectionWorkflow } from './hooks/useChangeDetectionWorkflow'
import { useDemoWorkflow } from './hooks/useDemoWorkflow'
import { useCoverageResolver } from './hooks/useCoverageResolver'
@@ -51,7 +56,7 @@ function isVectorDatasetType(datasetType: string): boolean {
return datasetType === 'vector' || datasetType === 'geojson'
}
const workspaceNavItems: Array<{ key: WorkspaceKey; label: string; description: string }> = [
const workspaceNavItems: WorkspaceNavigationItem[] = [
{ key: 'overview', label: 'Status', description: 'Beschikbaarheid en aandachtspunten' },
{ key: 'data', label: 'Bronnen', description: 'Gebieden en ingeladen gegevens' },
{ key: 'map', label: 'Kaart', description: 'Selecteren, uitlezen en vergelijken' },
@@ -62,7 +67,7 @@ const workspaceNavItems: Array<{ key: WorkspaceKey; label: string; description:
{ key: 'system', label: 'Beheer', description: 'Databronnen en systeemstatus' },
]
const workspaceNavGroups: Array<{ label: string; keys: WorkspaceKey[] }> = [
const workspaceNavGroups: WorkspaceNavigationGroup[] = [
{ label: 'Verkennen', keys: ['map', 'data'] },
{ label: 'Vragen', keys: ['assistant'] },
{ label: 'Analyseren', keys: ['analysis', 'ai'] },
@@ -779,71 +784,52 @@ function App(): JSX.Element {
>
Ga naar de werkruimte
</a>
<header className="workbench-topbar">
<div className="brand-block">
<span className="brand-mark" aria-hidden="true">GI</span>
<div>
<h1>GeoIntel</h1>
<p>GeoAI-werkruimte · gebiedsdata en analyse</p>
</div>
</div>
<div className="context-bar" aria-label="Actieve werkcontext">
<div>
<span>Werkruimte</span>
<strong title={projectContextLabel}>{projectContextLabel}</strong>
</div>
<div>
<span>Gebied</span>
<strong title={areaContextLabel}>{areaContextLabel}</strong>
</div>
<div>
<span>Bron</span>
<strong title={datasetContextLabel}>{datasetContextLabel}</strong>
</div>
<div>
<span>Kaartlaag</span>
<strong title={layerContextLabel}>{layerContextLabel}</strong>
</div>
</div>
</header>
<p className="sr-only" role="status" aria-live="polite">{workspaceStatusMessage}</p>
{errorMessage ? <p className="error" role="alert">{errorMessage}</p> : null}
<div className="workbench-layout">
<aside className="workbench-sidebar" aria-label="Navigatie van de werkruimte">
<nav aria-label="Hoofdonderdelen">
{workspaceNavGroups.map((group) => (
<div className="nav-group" key={group.label}>
<p className="nav-section-label">{group.label}</p>
{group.keys.map((key) => {
const item = workspaceNavItems.find((candidate) => candidate.key === key)
if (!item) {
return null
}
return (
<button
key={item.key}
type="button"
className={item.key === activeWorkspace ? 'nav-item nav-item-active' : 'nav-item'}
onClick={() => setActiveWorkspace(item.key)}
aria-current={item.key === activeWorkspace ? 'page' : undefined}
aria-label={`Open ${item.label}: ${item.description}`}
data-testid={`workspace-nav-${item.key}`}
>
<span>{item.label}</span>
<small>{item.description}</small>
</button>
)
})}
<WorkbenchNavigation
activeWorkspace={activeWorkspace}
groups={workspaceNavGroups}
items={workspaceNavItems}
onSelect={setActiveWorkspace}
/>
<div className="workbench-stage">
<header className="workbench-topbar">
<div className="mobile-brand">
<span className="brand-mark" aria-hidden="true">GI</span>
<strong>GeoIntel</strong>
</div>
<div className="context-bar" aria-label="Actieve werkcontext">
<div>
<span>Werkruimte</span>
<strong title={projectContextLabel}>{projectContextLabel}</strong>
</div>
))}
</nav>
</aside>
<div>
<span>Gebied</span>
<strong title={areaContextLabel}>{areaContextLabel}</strong>
</div>
<div>
<span>Bron</span>
<strong title={datasetContextLabel}>{datasetContextLabel}</strong>
</div>
<div>
<span>Kaartlaag</span>
<strong title={layerContextLabel}>{layerContextLabel}</strong>
</div>
</div>
<div className="context-health" aria-label="Systeemstatus">
<span aria-hidden="true" />
<strong>{workspaceDataLoading ? 'Laden' : errorMessage ? 'Aandacht' : 'Gereed'}</strong>
</div>
</header>
<div className="workbench-content">
<main
ref={workbenchMainRef}
className="workbench-main"
className={activeWorkspace === 'map' ? 'workbench-main workbench-main-map' : 'workbench-main'}
id="workspace-main"
tabIndex={-1}
aria-busy={workspaceDataLoading}
@@ -868,24 +854,6 @@ function App(): JSX.Element {
) : null}
</div>
</div> : null}
<div className="workspace-command-bar" aria-label="Snelkoppelingen">
<div className="workspace-nav-cluster">
{workspaceNavItems.slice(0, 5).map((item) => (
<button
key={item.key}
type="button"
className={item.key === activeWorkspace ? 'command-chip command-chip-active' : 'command-chip'}
onClick={() => setActiveWorkspace(item.key)}
aria-pressed={item.key === activeWorkspace}
aria-label={`Ga naar ${item.label}`}
>
{item.label}
</button>
))}
</div>
<span>{selectedProject ? `Actief bereik: ${projectContextLabel}` : 'De regionale gegevens worden ingeladen.'}</span>
</div>
{activeWorkspace === 'overview' ? (
<OverviewWorkspace
selectedProject={selectedProject}
@@ -1330,6 +1298,8 @@ function App(): JSX.Element {
/>
</aside>
) : null}
</div>
</div>
</div>
</div>
)
+5 -5
View File
@@ -1868,8 +1868,8 @@ export function MapWorkspace({
<header className="geo-explorer-header">
<div>
<p className="eyebrow">{activeScopeLabel} · geografische verkenner</p>
<h2>Wat bevindt zich in dit gebied?</h2>
<p>Kies een datathema, teken een rechthoek en lees de beschikbare gegevens meteen uit.</p>
<h2>Gebied analyseren</h2>
<p>Kies een thema, teken een rechthoek en lees de beschikbare gegevens.</p>
</div>
<div className="geo-analysis-mode" role="tablist" aria-label="Analyseperiode">
<button
@@ -1932,8 +1932,8 @@ export function MapWorkspace({
<div className="geo-panel-heading">
<span>1</span>
<div>
<h3>Kies een datathema</h3>
<p>Dit zijn databronnen, geen AI-modellen.</p>
<h3>Thema</h3>
<p>Kies welke gegevens u wilt meten.</p>
</div>
</div>
<div className="geo-loaded-scope" aria-label="Ingeladen regiobereik">
@@ -2307,7 +2307,7 @@ export function MapWorkspace({
<div className="geo-panel-heading">
<span>3</span>
<div>
<h3>Resultaten</h3>
<h3>Inzichten</h3>
<p>Alleen gemeten gegevens uit beschikbare bronnen.</p>
</div>
</div>
@@ -0,0 +1,90 @@
import {
Activity,
Bot,
Database,
Download,
Map,
ScanSearch,
Settings,
ShieldCheck,
type LucideIcon,
} from 'lucide-react'
import type { WorkspaceKey } from '../overview/OverviewWorkspace'
export interface WorkspaceNavigationItem {
key: WorkspaceKey
label: string
description: string
}
export interface WorkspaceNavigationGroup {
label: string
keys: WorkspaceKey[]
}
interface WorkbenchNavigationProps {
activeWorkspace: WorkspaceKey
groups: WorkspaceNavigationGroup[]
items: WorkspaceNavigationItem[]
onSelect: (workspace: WorkspaceKey) => void
}
const workspaceIcons: Record<WorkspaceKey, LucideIcon> = {
map: Map,
data: Database,
assistant: Bot,
analysis: ShieldCheck,
ai: ScanSearch,
exports: Download,
overview: Activity,
system: Settings,
}
export function WorkbenchNavigation({
activeWorkspace,
groups,
items,
onSelect,
}: WorkbenchNavigationProps): JSX.Element {
return (
<aside className="workbench-sidebar" aria-label="Navigatie van de werkruimte">
<div className="sidebar-brand" aria-label="GeoIntel">
<span className="brand-mark" aria-hidden="true">GI</span>
<span className="sidebar-brand-copy">
<strong>GeoIntel</strong>
<small>Atlas Workbench</small>
</span>
</div>
<nav aria-label="Hoofdonderdelen">
{groups.map((group) => (
<div className="nav-group" key={group.label}>
<p className="nav-section-label">{group.label}</p>
{group.keys.map((key) => {
const item = items.find((candidate) => candidate.key === key)
if (!item) {
return null
}
const Icon = workspaceIcons[item.key]
return (
<button
key={item.key}
type="button"
className={item.key === activeWorkspace ? 'nav-item nav-item-active' : 'nav-item'}
onClick={() => onSelect(item.key)}
aria-current={item.key === activeWorkspace ? 'page' : undefined}
aria-label={`Open ${item.label}: ${item.description}`}
title={item.description}
data-testid={`workspace-nav-${item.key}`}
>
<Icon className="nav-item-icon" aria-hidden="true" strokeWidth={1.8} />
<span>{item.label}</span>
</button>
)
})}
</div>
))}
</nav>
</aside>
)
}
File diff suppressed because it is too large Load Diff
-515
View File
@@ -1,515 +0,0 @@
/* GeoIntel visual polish: a restrained map-first presentation layer. */
:root {
--visual-canvas: #eef2f1;
--visual-surface: #ffffff;
--visual-surface-soft: #f7f9f8;
--visual-ink: #172a2d;
--visual-muted: #657477;
--visual-line: #d7e1df;
--visual-line-strong: #b8c8c4;
--visual-teal: #087d73;
--visual-teal-deep: #056057;
--visual-teal-soft: #e7f4f1;
--visual-amber: #a76616;
--visual-amber-soft: #fff7e9;
}
.workbench-shell {
background: var(--visual-canvas);
color: var(--visual-ink);
}
.app-shell > header.workbench-topbar {
min-height: 4.35rem;
grid-template-columns: minmax(14rem, 17rem) minmax(0, 1fr);
gap: 1.15rem;
padding-inline: clamp(1rem, 1.5vw, 1.75rem);
background: rgba(255, 255, 255, 0.985);
}
.brand-block {
gap: 0.8rem;
}
.brand-mark {
width: 2.35rem;
height: 2.35rem;
flex-basis: 2.35rem;
border: 1px solid #20464a;
border-radius: 8px;
background: #153b40;
box-shadow: 0 2px 5px rgba(21, 59, 64, 0.14);
}
.brand-block h1 {
color: var(--visual-ink);
font-size: 1.08rem;
letter-spacing: 0;
}
.app-shell > header.workbench-topbar .brand-block p {
color: #718083;
font-size: 0.71rem;
}
.context-bar {
border-color: var(--visual-line);
border-radius: 8px;
background: var(--visual-surface-soft);
}
.context-bar > div {
min-height: 2.8rem;
padding-inline: 0.8rem;
}
.context-bar span {
color: #708082;
font-size: 0.6rem;
letter-spacing: 0.03em;
}
.context-bar strong {
color: var(--visual-ink);
font-size: 0.79rem;
}
.workbench-layout {
grid-template-columns: 13.25rem minmax(0, 1fr);
height: calc(100dvh - 4.35rem);
}
.workbench-sidebar {
padding: 1rem 0.8rem;
border-right-color: var(--visual-line);
background: #f6f8f7;
}
.workbench-sidebar nav {
gap: 1.15rem;
}
.nav-group {
gap: 0.28rem;
}
.nav-section-label {
padding-inline: 0.62rem;
color: #7c898a;
font-size: 0.6rem;
letter-spacing: 0.07em;
}
.workbench-sidebar .nav-item {
min-height: 2.55rem;
border-radius: 7px;
padding: 0.48rem 0.7rem;
color: #3e5153;
}
.workbench-sidebar .nav-item span {
font-size: 0.82rem;
font-weight: 750;
}
.workbench-sidebar .nav-item-active {
border-color: #b8d9d2;
background: var(--visual-teal-soft);
color: var(--visual-teal-deep);
box-shadow: inset 3px 0 0 var(--visual-teal);
}
.workbench-main {
padding: 1.25rem clamp(1rem, 1.8vw, 2rem) 2rem;
}
.workbench-main > * {
width: min(100%, 118rem);
}
.workspace-heading {
margin-bottom: 1rem;
padding-bottom: 0.82rem;
border-bottom-color: var(--visual-line);
}
.workspace-heading h2 {
color: var(--visual-ink);
font-size: 1.48rem;
}
.workspace-heading-actions > p {
color: var(--visual-muted);
}
/* Normal desktop keeps the three map steps in one purposeful working row. */
@media (min-width: 1181px) {
.geo-explorer-layout {
grid-template-columns: minmax(15rem, 16.5rem) minmax(0, 1fr) minmax(17rem, 19rem);
gap: 0.85rem;
height: clamp(36rem, calc(100dvh - 9.8rem), 68rem);
min-height: 36rem;
}
.geo-theme-panel,
.geo-results-panel,
.geo-map-stage {
border-color: var(--visual-line);
border-radius: 8px;
box-shadow: 0 1px 2px rgba(23, 42, 45, 0.035);
}
.geo-results-panel {
grid-column: auto;
display: flex;
overflow-y: auto;
}
.geo-map-toolbar {
align-items: flex-start;
}
.geo-map-actions {
flex-wrap: wrap;
justify-content: flex-end;
}
}
@media (min-width: 1800px) {
.workbench-layout {
grid-template-columns: 14rem minmax(0, 1fr);
}
.workbench-main {
padding-inline: clamp(1.5rem, 2.2vw, 3rem);
}
.geo-explorer-layout {
grid-template-columns: minmax(17rem, 19rem) minmax(42rem, 1fr) minmax(21rem, 24rem);
}
}
.geo-explorer {
gap: 0.9rem;
}
.geo-explorer-header {
gap: 1.25rem;
border-bottom-color: var(--visual-line);
padding-bottom: 0.95rem;
}
.geo-explorer-header h2 {
color: var(--visual-ink);
font-size: clamp(1.45rem, 1.8vw, 1.9rem);
font-weight: 760;
}
.geo-explorer-header p:last-child {
color: var(--visual-muted);
font-size: 0.86rem;
}
.geo-analysis-mode {
border-color: var(--visual-line-strong);
background: #f3f7f6;
}
.geo-analysis-mode button {
color: #687879;
}
.geo-analysis-mode button.active {
color: var(--visual-teal-deep);
box-shadow: 0 1px 4px rgba(23, 42, 45, 0.11);
}
.geo-panel-heading > span {
background: var(--visual-ink);
}
.geo-panel-heading h3,
.geo-results-title-row h4 {
color: var(--visual-ink);
}
.geo-panel-heading p,
.geo-results-empty p {
color: var(--visual-muted);
}
.geo-theme-panel,
.geo-results-panel,
.geo-map-stage {
background: var(--visual-surface);
}
.geo-theme-panel,
.geo-results-panel {
padding: 0.85rem;
}
.geo-theme-list {
gap: 0.42rem;
scrollbar-gutter: stable;
}
.geo-theme-option {
min-height: 3.35rem;
border-color: #dce6e3;
border-radius: 7px;
padding: 0.52rem 0.56rem;
}
.geo-theme-option:not(:disabled):hover {
border-color: #98c5ba;
background: #f5faf8;
}
.geo-theme-option-active {
border-color: #6da99b;
background: var(--visual-teal-soft);
box-shadow: inset 3px 0 0 var(--visual-teal);
}
.geo-theme-option strong {
color: #233639;
font-size: 0.8rem;
}
.geo-theme-option small {
color: #6e7d7f;
font-size: 0.66rem;
}
.geo-theme-option i {
color: var(--visual-teal-deep);
font-size: 0.57rem;
letter-spacing: 0.02em;
}
.geo-loaded-scope {
border-color: #d6e5e1;
border-radius: 7px;
background: #f6faf9;
}
.geo-source-summary {
border-top-color: var(--visual-line);
}
.geo-source-summary strong {
color: #223639;
}
.geo-map-stage {
border-color: var(--visual-line-strong);
box-shadow: 0 3px 14px rgba(23, 42, 45, 0.08);
}
.geo-map-toolbar {
border-bottom-color: var(--visual-line);
background: #fbfcfc;
}
.geo-map-actions .primary-action,
.geo-result-next-actions .primary-action {
border-color: var(--visual-teal);
background: var(--visual-teal);
}
.geo-map-actions .primary-action:hover:not(:disabled),
.geo-result-next-actions .primary-action:hover:not(:disabled) {
border-color: var(--visual-teal-deep);
background: var(--visual-teal-deep);
}
.geo-map-canvas {
background: #dce9e6;
}
.geo-map-legend {
border-color: rgba(23, 42, 45, 0.18);
border-radius: 6px;
background: rgba(255, 255, 255, 0.96);
box-shadow: 0 3px 12px rgba(23, 42, 45, 0.12);
}
.geo-results-panel {
scrollbar-gutter: stable;
}
.geo-results-empty,
.geo-results-loading {
border-color: #cbdad6;
background: #fafcfb;
}
.geo-primary-metrics > div {
border-color: #dbe6e3;
border-radius: 6px;
background: #f7faf9;
}
.geo-primary-metrics > div:first-child {
border-top: 2px solid var(--visual-teal);
}
.geo-primary-metrics strong {
color: #183c3b;
}
.geo-theme-result-row {
border-color: #e0e9e7;
background: #fbfcfc;
}
.geo-theme-result-row:hover {
border-color: #b9d4cc;
background: #f4faf8;
}
.geo-result-next-actions {
border-color: #c4ddd7;
border-radius: 7px;
background: #f2faf7;
}
.geo-result-next-actions strong {
color: #174a42;
}
.geo-result-next-actions small {
color: #607773;
}
@media (max-width: 1180px) and (min-width: 921px) {
.geo-explorer-layout {
grid-template-columns: minmax(15rem, 17rem) minmax(0, 1fr);
grid-template-rows: auto auto;
height: auto;
min-height: 0;
}
.geo-results-panel {
grid-column: 1 / -1;
display: grid;
grid-template-columns: minmax(13rem, 0.7fr) minmax(0, 1.3fr);
align-items: start;
}
}
@media (max-width: 1359px) and (min-width: 921px) {
/* Keep the map readable until the viewport can support a result rail. */
.geo-explorer-layout {
grid-template-columns: minmax(15rem, 17rem) minmax(28rem, 1fr);
grid-template-rows: clamp(34rem, calc(100dvh - 10rem), 54rem) auto;
height: auto;
min-height: 0;
}
.geo-results-panel {
grid-column: 1 / -1;
display: grid;
grid-template-columns: minmax(13rem, 0.7fr) minmax(0, 1.3fr);
align-items: start;
}
}
@media (max-width: 1799px) and (min-width: 1360px) {
/* A two-column work row keeps the selection step wide at standard desktop sizes. */
.geo-explorer-layout {
grid-template-columns: minmax(16rem, 18rem) minmax(34rem, 1fr);
grid-template-rows: clamp(36rem, calc(100dvh - 10rem), 58rem) auto;
height: auto;
min-height: 0;
}
.geo-results-panel {
grid-column: 1 / -1;
display: grid;
grid-template-columns: minmax(14rem, 0.7fr) minmax(0, 1.3fr);
align-items: start;
overflow: visible;
}
}
@media (max-width: 920px) {
.app-shell > header.workbench-topbar {
position: relative;
display: grid;
grid-template-columns: 1fr;
gap: 0.65rem;
min-height: auto;
}
.app-shell > header.workbench-topbar {
padding-block: 0.7rem;
}
.workbench-sidebar {
background: #ffffff;
scrollbar-color: #a9bfba transparent;
scrollbar-width: thin;
}
.workbench-sidebar::-webkit-scrollbar {
height: 4px;
}
.workbench-sidebar::-webkit-scrollbar-thumb {
border-radius: 4px;
background: #a9bfba;
}
.workbench-sidebar::-webkit-scrollbar-track {
background: transparent;
}
.workbench-sidebar .nav-item {
border-color: transparent;
}
.workbench-sidebar .nav-item-active {
border-color: #b8d9d2;
box-shadow: inset 0 -3px 0 var(--visual-teal);
}
.geo-explorer-layout {
gap: 0.75rem;
}
}
@media (max-width: 620px) {
.workbench-main {
padding-inline: 0.75rem;
}
.geo-explorer-header {
gap: 0.8rem;
}
.geo-explorer-header h2 {
font-size: 1.4rem;
}
.geo-theme-panel,
.geo-results-panel {
padding: 0.75rem;
}
.geo-map-stage {
box-shadow: none;
}
.geo-map-canvas .map-container {
min-height: 25rem;
}
}
@media (prefers-reduced-motion: reduce) {
.workbench-shell button {
transition: none;
}
}