feat(ui): redesign GeoIntel as Atlas Workbench
This commit is contained in:
@@ -9,6 +9,14 @@
|
||||
|
||||
## 1.0.0 - Final Belgium and Belgian North Sea release (2026-07-19)
|
||||
|
||||
- Rebuilt the complete frontend presentation as the Stitch-guided GeoIntel
|
||||
Atlas Workbench. The new shell uses one compact icon navigation rail, one
|
||||
context bar and task-specific work surfaces across Map, Sources, AI
|
||||
Questions, Quality, Image Analysis, Downloads, Status and Administration.
|
||||
- Restored the normal desktop map to one stable theme/map/insight row, made
|
||||
the map absorb ultrawide space, bounded the mobile theme list and removed
|
||||
the duplicate workspace command bar. API, database, GIS and AI behavior are
|
||||
unchanged.
|
||||
- Promoted the nationally federated, map-first GeoIntel workbench from the
|
||||
release candidate to the final `1.0.0` version.
|
||||
- Added a restrained final visual polish pass for the map-first workbench:
|
||||
|
||||
@@ -14,7 +14,14 @@ def test_frontend_declares_mol_as_primary_operating_focus() -> None:
|
||||
map_source = (ROOT / "frontend" / "src" / "components" / "GeoMap.tsx").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8")
|
||||
navigation = (
|
||||
ROOT
|
||||
/ "frontend"
|
||||
/ "src"
|
||||
/ "components"
|
||||
/ "shell"
|
||||
/ "WorkbenchNavigation.tsx"
|
||||
).read_text(encoding="utf-8")
|
||||
|
||||
assert "PRIMARY_FOCUS_LABEL = 'Mol'" in focus
|
||||
assert "PRIMARY_FOCUS_REGION = 'Mol, Kempen'" in focus
|
||||
@@ -29,7 +36,8 @@ def test_frontend_declares_mol_as_primary_operating_focus() -> None:
|
||||
assert "PRIMARY_FOCUS_AREA_GEOJSON" in project_hook
|
||||
assert "4.35,51.28" not in project_hook
|
||||
assert "center: PRIMARY_FOCUS_CENTER" in map_source
|
||||
assert "GeoAI-werkruimte · gebiedsdata en analyse" in app
|
||||
assert "GeoIntel" in navigation
|
||||
assert "Atlas Workbench" in navigation
|
||||
|
||||
|
||||
def test_operator_workflows_put_mol_first_and_name_future_projects() -> None:
|
||||
|
||||
@@ -15,8 +15,9 @@ def test_map_first_explorer_is_the_default_product_flow() -> None:
|
||||
workspace = read("frontend/src/components/map/MapWorkspace.tsx")
|
||||
|
||||
assert "useState<WorkspaceKey>('map')" in app
|
||||
assert "Wat bevindt zich in dit gebied?" in workspace
|
||||
assert "Kies een datathema" in workspace
|
||||
assert "Gebied analyseren" in workspace
|
||||
assert "<h3>Thema</h3>" in workspace
|
||||
assert "<h3>Inzichten</h3>" in workspace
|
||||
assert "Teken rechthoek" in workspace
|
||||
assert "Volledig werkgebied" in workspace
|
||||
assert "Alle beschikbare thema" in workspace
|
||||
|
||||
@@ -24,7 +24,7 @@ def test_map_workspace_lives_in_task_based_workbench_shell() -> None:
|
||||
|
||||
assert "activeWorkspace === 'map'" in app
|
||||
assert app.index("activeWorkspace === 'map'") < app.index("<MapWorkspace")
|
||||
assert 'className="workbench-main"' in app
|
||||
assert "'workbench-main workbench-main-map' : 'workbench-main'" in app
|
||||
assert 'className="workbench-inspector"' in app
|
||||
assert ".workbench-layout" in styles
|
||||
assert ".workbench-sidebar" in styles
|
||||
|
||||
@@ -8,13 +8,22 @@ ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
def test_app_uses_task_based_workbench_shell() -> None:
|
||||
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8")
|
||||
navigation = (
|
||||
ROOT
|
||||
/ "frontend"
|
||||
/ "src"
|
||||
/ "components"
|
||||
/ "shell"
|
||||
/ "WorkbenchNavigation.tsx"
|
||||
).read_text(encoding="utf-8")
|
||||
|
||||
assert "type WorkspaceKey" in app
|
||||
assert "workspaceNavItems" in app
|
||||
assert 'data-testid={`workspace-nav-${item.key}`}' in app
|
||||
assert "<WorkbenchNavigation" in app
|
||||
assert 'data-testid={`workspace-nav-${item.key}`}' in navigation
|
||||
assert 'className="workbench-layout"' in app
|
||||
assert 'className="workbench-sidebar"' in app
|
||||
assert 'className="workbench-main"' in app
|
||||
assert 'className="workbench-sidebar"' in navigation
|
||||
assert "'workbench-main workbench-main-map' : 'workbench-main'" in app
|
||||
assert 'className="workbench-inspector"' in app
|
||||
assert "activeWorkspace === 'data'" in app
|
||||
assert "activeWorkspace === 'map'" in app
|
||||
@@ -26,6 +35,9 @@ def test_app_uses_task_based_workbench_shell() -> None:
|
||||
|
||||
def test_workbench_shell_css_provides_navigation_main_and_inspector_regions() -> None:
|
||||
css = (ROOT / "frontend" / "src" / "styles" / "app.css").read_text(encoding="utf-8")
|
||||
atlas = (ROOT / "frontend" / "src" / "styles" / "atlas-workbench.css").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
|
||||
assert ".workbench-topbar" in css
|
||||
assert ".context-bar" in css
|
||||
@@ -36,3 +48,6 @@ def test_workbench_shell_css_provides_navigation_main_and_inspector_regions() ->
|
||||
assert ".workbench-inspector" in css
|
||||
assert ".workspace-grid-data" in css
|
||||
assert ".workspace-grid-ai" in css
|
||||
assert ".workbench-stage" in atlas
|
||||
assert ".context-health" in atlas
|
||||
assert ".nav-item-icon" in atlas
|
||||
|
||||
@@ -6,17 +6,30 @@ from pathlib import Path
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def test_frontend_shell_has_visual_polish_contracts() -> None:
|
||||
def test_frontend_shell_has_atlas_workbench_contracts() -> None:
|
||||
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8")
|
||||
css = (ROOT / "frontend" / "src" / "styles" / "app.css").read_text(encoding="utf-8")
|
||||
css = (ROOT / "frontend" / "src" / "styles" / "atlas-workbench.css").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
navigation = (
|
||||
ROOT
|
||||
/ "frontend"
|
||||
/ "src"
|
||||
/ "components"
|
||||
/ "shell"
|
||||
/ "WorkbenchNavigation.tsx"
|
||||
).read_text(encoding="utf-8")
|
||||
|
||||
assert "workspace-command-bar" in app
|
||||
assert "workspace-nav-cluster" in app
|
||||
assert "--surface-raised" in css
|
||||
assert ".workspace-command-bar" in css
|
||||
assert "workspace-command-bar" not in app
|
||||
assert "workspace-nav-cluster" not in app
|
||||
assert "WorkbenchNavigation" in app
|
||||
assert "lucide-react" in navigation
|
||||
assert "--atlas-nav-width" in css
|
||||
assert ".workbench-stage" in css
|
||||
assert ".geo-explorer-layout" in css
|
||||
assert "grid-template-columns: 15.75rem minmax(28rem, 1fr) 20rem;" in css
|
||||
assert ".workspace-panel" in css
|
||||
assert ".data-density-grid" in css
|
||||
assert ".result-summary-card" in css
|
||||
assert ".geo-assistant-panel" in css
|
||||
|
||||
|
||||
def test_primary_panels_use_empty_state_components() -> None:
|
||||
|
||||
@@ -20,9 +20,18 @@ def test_global_focus_visible_contracts_are_defined() -> None:
|
||||
|
||||
def test_primary_navigation_has_keyboard_labels() -> None:
|
||||
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8")
|
||||
navigation = (
|
||||
ROOT
|
||||
/ "frontend"
|
||||
/ "src"
|
||||
/ "components"
|
||||
/ "shell"
|
||||
/ "WorkbenchNavigation.tsx"
|
||||
).read_text(encoding="utf-8")
|
||||
|
||||
assert 'aria-label={`Open ${item.label}: ${item.description}`}' in app
|
||||
assert 'aria-label={`Ga naar ${item.label}`}' in app
|
||||
assert 'aria-label={`Open ${item.label}: ${item.description}`}' in navigation
|
||||
assert "title={item.description}" in navigation
|
||||
assert "aria-current={item.key === activeWorkspace ? 'page' : undefined}" in navigation
|
||||
assert "label: 'Bronnen'" in app
|
||||
assert "label: 'Kaart'" in app
|
||||
assert "label: 'Kwaliteit'" in app
|
||||
|
||||
@@ -8,32 +8,45 @@ ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
def test_workbench_shell_has_skip_link_and_main_focus_target() -> None:
|
||||
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8")
|
||||
navigation = (
|
||||
ROOT
|
||||
/ "frontend"
|
||||
/ "src"
|
||||
/ "components"
|
||||
/ "shell"
|
||||
/ "WorkbenchNavigation.tsx"
|
||||
).read_text(encoding="utf-8")
|
||||
|
||||
assert 'className="skip-link"' in app
|
||||
assert 'href="#workspace-main"' in app
|
||||
assert 'nav aria-label="Hoofdonderdelen"' in app
|
||||
assert '<nav aria-label="Hoofdonderdelen">' in navigation
|
||||
assert 'id="workspace-main"' in app
|
||||
assert "tabIndex={-1}" in app
|
||||
|
||||
|
||||
def test_mobile_shell_density_contracts_are_defined() -> None:
|
||||
css = (ROOT / "frontend" / "src" / "styles" / "app.css").read_text(encoding="utf-8")
|
||||
atlas = (ROOT / "frontend" / "src" / "styles" / "atlas-workbench.css").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
|
||||
assert ".skip-link" in css
|
||||
assert ".skip-link:focus-visible" in css
|
||||
assert "scroll-margin-top: 8.5rem;" in css
|
||||
assert "grid-template-columns: repeat(4, minmax(6.75rem, 1fr));" in css
|
||||
assert "overflow-x: auto;" in css
|
||||
assert "scroll-snap-type: x proximity;" in css
|
||||
assert "white-space: nowrap;" in css
|
||||
assert "@media (max-width: 920px)" in atlas
|
||||
assert "overflow-x: auto;" in atlas
|
||||
assert "flex: 0 0 4.4rem;" in atlas
|
||||
assert "white-space: nowrap;" in atlas
|
||||
assert ".context-bar,\n .quick-action-grid,\n .workspace-nav-cluster" not in css
|
||||
|
||||
|
||||
def test_small_screen_navigation_keeps_labels_readable() -> None:
|
||||
css = (ROOT / "frontend" / "src" / "styles" / "app.css").read_text(encoding="utf-8")
|
||||
css = (ROOT / "frontend" / "src" / "styles" / "atlas-workbench.css").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
|
||||
assert "@media (max-width: 620px)" in css
|
||||
assert "@media (max-width: 560px)" in css
|
||||
assert ".workbench-sidebar .nav-item" in css
|
||||
assert "min-width: 9.5rem;" in css
|
||||
assert ".nav-item small" in css
|
||||
assert "line-height: 1.18;" in css
|
||||
assert "min-width: 4.4rem;" in css
|
||||
assert ".nav-item-icon" in css
|
||||
assert "text-overflow: ellipsis;" in css
|
||||
|
||||
@@ -10819,3 +10819,45 @@ Decision:
|
||||
|
||||
- RC-11 is complete subject only to binding the accepted final commit and
|
||||
immutable image in the signed/checksummed package. No RC-12 phase is used.
|
||||
|
||||
## 2026-07-19 - Stitch-guided complete Atlas Workbench redesign
|
||||
|
||||
Implemented:
|
||||
|
||||
- Created the private Stitch project `GeoIntel Complete Workbench Redesign`
|
||||
and the `GeoIntel Atlas Workbench` design system.
|
||||
- Generated production references for the Map Explorer, Sources catalog and
|
||||
local AI Questions workspaces.
|
||||
- Rebuilt the application shell so the brand lives in one compact navigation
|
||||
rail and the active work context lives in one 56-pixel top bar.
|
||||
- Added Lucide icons for the eight workspace destinations and removed the
|
||||
duplicate command navigation.
|
||||
- Restyled Map, Sources, AI Questions, Quality, Image Analysis, Downloads,
|
||||
Status and Administration under one restrained token system.
|
||||
- Restored the desktop map to a theme/map/insight row, allowed the map to
|
||||
absorb ultrawide width and bounded the mobile theme list.
|
||||
- Replaced the old `visual-polish.css` layer with the documented
|
||||
`atlas-workbench.css` implementation.
|
||||
|
||||
Validated during implementation:
|
||||
|
||||
- the complete readiness gate passed: 1,052 backend tests, 22 frontend tests,
|
||||
frontend TypeScript typecheck, the production build and Alembic head
|
||||
`202607160001`;
|
||||
- the browser UX audit passed at 390x844, 1366x768 and 2560x1080, including
|
||||
accessible control names, loading states, keyboard tabs, the advanced
|
||||
coverage flow, zero horizontal overflow, zero console errors and zero failed
|
||||
API requests;
|
||||
- all seven Belgium/North Sea release journeys passed against the live backend
|
||||
through the redesigned frontend;
|
||||
- browser review passed at 1280x720, 3440x1200 and 390x844 with no horizontal
|
||||
document overflow or clipped button/select text;
|
||||
- Map, Sources, AI Questions, Quality, Image Analysis, Downloads, Status and
|
||||
Administration were each reviewed against live backend data.
|
||||
|
||||
Behavior preservation:
|
||||
|
||||
- No API contract, migration, database model, provider behavior, GIS
|
||||
calculation, model runtime or persistence flow changed.
|
||||
- Existing map selection, source loading, local Ollama, QA, analysis and
|
||||
download actions remain wired through their original hooks and services.
|
||||
|
||||
@@ -99,6 +99,9 @@ geen open productroadmap meer.
|
||||
- [x] Maak de vijf bestaande officiële beleidsrasters veilig op aanvraag
|
||||
beschikbaar voor elke begrensde selectie in Vlaanderen, met Dataset-cache,
|
||||
semantische metrics en zonder 1.425 vooraf geladen rasters.
|
||||
- [x] Vervang de gegroeide dashboardpresentatie door de Stitch-gestuurde Atlas
|
||||
Workbench met een compacte navigatierail, vaste contextbalk, taakgerichte
|
||||
schermen en gevalideerde desktop-, ultrawide- en mobiele layouts.
|
||||
|
||||
Bewuste, niet-blokkerende grenzen:
|
||||
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
# GeoIntel Atlas Workbench
|
||||
|
||||
## Purpose
|
||||
|
||||
The Atlas Workbench is the visual and interaction system for GeoIntel. It
|
||||
keeps the product map-first, task-oriented and understandable without hiding
|
||||
source authority, observation time or GIS limitations.
|
||||
|
||||
The redesign was developed in Stitch under:
|
||||
|
||||
- project: `GeoIntel Complete Workbench Redesign`
|
||||
- project id: `18332842433441398219`
|
||||
- design system: `GeoIntel Atlas Workbench`
|
||||
- design-system asset: `4667932515738184526`
|
||||
- Map Explorer screen: `c24113d1f1474c91b332b2365be24993`
|
||||
- Sources screen: `239796f2d66a44e7983e5fc588305ff8`
|
||||
- AI Questions screen: `246aa57bbe974861885325ff9d10c6bd`
|
||||
|
||||
The React implementation remains the source of truth for behavior. Stitch is
|
||||
the design reference for hierarchy, spacing and component treatment.
|
||||
|
||||
## Product hierarchy
|
||||
|
||||
1. `Kaart` is the primary workspace.
|
||||
2. `Bronnen` explains available and missing official data.
|
||||
3. `AI-vragen` answers only from persisted area evidence.
|
||||
4. `Kwaliteit` reviews stored checks and provenance.
|
||||
5. `Beeldanalyse` contains detection and segmentation workflows.
|
||||
6. `Downloads` stores and previews traceable outputs.
|
||||
7. `Status` summarizes readiness and source freshness.
|
||||
8. `Beheer` exposes provider and technical administration.
|
||||
|
||||
The navigation rail is the only persistent workspace navigation. Duplicate
|
||||
command bars or competing shortcut rows must not be reintroduced.
|
||||
|
||||
## Layout rules
|
||||
|
||||
- Desktop uses an 88-pixel navigation rail and a 56-pixel context bar.
|
||||
- The normal map layout is `theme drawer / map / insight inspector`.
|
||||
- Theme and insight rails stay stable on ultrawide displays; the map receives
|
||||
the additional width.
|
||||
- Non-map workspaces use one task header and one primary work surface.
|
||||
- Panels scroll internally when their function requires stable surrounding
|
||||
context.
|
||||
- Mobile navigation is horizontally scrollable. The theme list is bounded so
|
||||
the map remains reachable early in the page flow.
|
||||
- Typography never scales with viewport width.
|
||||
|
||||
## Visual tokens
|
||||
|
||||
| Role | Value |
|
||||
| --- | --- |
|
||||
| Canvas | `#f3f6f5` |
|
||||
| Surface | `#ffffff` |
|
||||
| Ink | `#152522` |
|
||||
| Muted text | `#687773` |
|
||||
| Border | `#dce4e1` |
|
||||
| Primary teal | `#0b6b62` |
|
||||
| Context blue | `#315d73` |
|
||||
| Attention amber | `#a55c23` |
|
||||
| Error red | `#a33c39` |
|
||||
| Radius | `4px` |
|
||||
|
||||
The UI does not use gradients, decorative blobs, oversized hero sections,
|
||||
nested cards or pill-heavy status presentation.
|
||||
|
||||
## Interaction rules
|
||||
|
||||
- One primary action per task surface.
|
||||
- Use icons plus short labels for navigation and familiar commands.
|
||||
- `Laatste toestand / Evolutie` is a segmented control.
|
||||
- Rectangle drawing is the primary spatial-selection action.
|
||||
- Technical model, provider and provenance detail stays available through
|
||||
explicit disclosures.
|
||||
- Empty states state the next useful action in one short sentence.
|
||||
- Unavailable data or model states remain honest and are never replaced with
|
||||
fabricated output.
|
||||
|
||||
## Implementation boundaries
|
||||
|
||||
- Shared shell and navigation:
|
||||
`frontend/src/components/shell/WorkbenchNavigation.tsx`
|
||||
- Shared visual implementation:
|
||||
`frontend/src/styles/atlas-workbench.css`
|
||||
- Map workflow:
|
||||
`frontend/src/components/map/MapWorkspace.tsx`
|
||||
- API, persistence and GIS behavior remain outside the design system.
|
||||
|
||||
Any future visual change must retain the browser acceptance widths of
|
||||
390 pixels, 1280 pixels and 3440 pixels and must pass frontend typecheck,
|
||||
unit tests, production build and the UX browser audit.
|
||||
+13
-5
@@ -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
|
||||
|
||||
|
||||
Generated
+10
@@ -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",
|
||||
|
||||
@@ -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
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user