Refine production workspace layouts

This commit is contained in:
b0txec
2026-08-19 10:38:25 +03:00
parent 9147342686
commit fd1c0148b6
5 changed files with 95 additions and 37 deletions
+7 -28
View File
@@ -4,8 +4,9 @@
| Environment | Purpose | Address | | Environment | Purpose | Address |
|---|---|---| |---|---|---|
| Windows | Source editing, local Docker build, and initial testing | `http://localhost:9190` | | Windows | Source editing and Git workflow only | Not applicable |
| Rocky Linux | Production-like isolated staging | `http://192.168.1.101:9190` | | Rocky Linux | Docker development and production-like staging | `http://192.168.1.101:9190` |
| Future VPS | Planned deployment environment | Not provisioned yet |
| Workplace production | Out of scope until reviewed and approved | Not documented here | | Workplace production | Out of scope until reviewed and approved | Not documented here |
Rocky checkout: Rocky checkout:
@@ -37,30 +38,9 @@ codex/staging-baseline
## Windows development ## Windows development
Build the frontend using the project Node container: Use Windows to edit and review source files and to create Git commits. Docker builds,
the running application, synthetic-data operations, and browser testing belong on
```powershell Rocky. Docker Desktop is not part of the normal project workflow.
docker compose run --rm node
```
Start or update the complete local stack when backend or Compose changes require it:
```powershell
docker compose up --build -d
```
Seed deterministic observations:
```powershell
docker compose --profile tools run --rm seed
```
Inspect status and logs:
```powershell
docker compose ps
docker compose logs --tail 100 scala
```
## Commit and transfer workflow ## Commit and transfer workflow
@@ -126,9 +106,8 @@ Prefer a normal Git revert rather than manually copying old files:
1. Identify the faulty commit with `git log --oneline`. 1. Identify the faulty commit with `git log --oneline`.
2. On the development checkout, run `git revert <commit>`. 2. On the development checkout, run `git revert <commit>`.
3. Build and test the reverted state locally. 3. Push the revert and build and test it on Rocky.
4. Push the revert commit. 4. Push the revert commit.
5. Pull it on Rocky and run the appropriate frontend-only or full-stack deployment command. 5. Pull it on Rocky and run the appropriate frontend-only or full-stack deployment command.
Database rollback is a separate operation and must not be inferred from a source rollback. Database backup and restore procedures are not yet defined. Database rollback is a separate operation and must not be inferred from a source rollback. Database backup and restore procedures are not yet defined.
+45
View File
@@ -14,4 +14,49 @@
} }
.overlayFields{display:grid;grid-template-columns:minmax(260px,2fr) minmax(160px,1fr);gap:12px;padding:16px 16px 0}.overlayFields label,.overlayFields span{display:block}.overlayFields span{margin-bottom:6px;font-size:12px;font-weight:700}.overlayFields input{width:100%} .overlayFields{display:grid;grid-template-columns:minmax(260px,2fr) minmax(160px,1fr);gap:12px;padding:16px 16px 0}.overlayFields label,.overlayFields span{display:block}.overlayFields span{margin-bottom:6px;font-size:12px;font-weight:700}.overlayFields input{width:100%}
.mapControlSections{display:grid;gap:18px;padding:16px}.valueControls{padding-bottom:16px;border-bottom:1px solid var(--border)} .mapControlSections{display:grid;gap:18px;padding:16px}.valueControls{padding-bottom:16px;border-bottom:1px solid var(--border)}
/* Workspace layout: controls explain the content without enclosing every region. */
.aggregator>.container{
grid-template-columns:230px minmax(0,1fr);
grid-template-areas:"cities query" "cities results";
column-gap:28px;
row-gap:18px;
}
.aggregator .workspaceRail{
grid-area:cities;
padding:4px 24px 10px 0;
border-right:1px solid rgba(90,126,166,.2);
}
.aggregator .workspaceRail h2{margin:0 0 18px;font-size:16px}
.aggregator .workspaceToolbar{
grid-area:query;
display:grid;
grid-template-columns:minmax(330px,1.15fr) minmax(480px,1.85fr);
gap:14px 28px;
align-items:end;
padding:2px 0 20px;
border-bottom:1px solid rgba(90,126,166,.22);
}
.aggregator .workspaceToolbar>h2{grid-column:1/-1;margin:0;font-size:18px}
.aggregator .queryOptions{
display:grid;
grid-template-columns:minmax(130px,1fr) minmax(125px,1.05fr) minmax(115px,.8fr);
gap:20px;
align-items:end;
}
.aggregator .queryPanel h4{margin:0 0 9px}
.aggregator .queryPanel p{margin:8px 0 0}
.aggregator .queryOptions ul{margin:0;padding:0}
.aggregator .resultsPanel{grid-area:results}
@media(max-width:1200px){
.aggregator>.container{grid-template-columns:210px minmax(0,1fr);grid-template-areas:"cities query" "cities results"}
.aggregator .workspaceToolbar{grid-template-columns:1fr}
.aggregator .workspaceToolbar>h2{grid-column:auto}
}
@media(max-width:800px){
.aggregator>.container{grid-template-columns:1fr;grid-template-areas:"cities" "query" "results"}
.aggregator .workspaceRail{padding-right:0;border-right:0;border-bottom:1px solid rgba(90,126,166,.2)}
.aggregator .queryOptions{grid-template-columns:1fr}
}
.pageWorkspace{max-width:1800px;margin:auto;padding:34px 28px 70px}.pageHeading{display:flex;align-items:end;justify-content:space-between;gap:24px;margin-bottom:24px}.pageHeading h1{margin:5px 0 6px;font-size:34px}.pageHeading p{margin:0;color:var(--text-muted)}.selectionCount{padding:10px 14px;border:1px solid var(--border);border-radius:10px;background:var(--surface);font-weight:700}.aggregator>.container{display:grid;grid-template-columns:240px 300px minmax(0,1fr);gap:18px;align-items:start}.panel{padding:20px;border:1px solid var(--border);border-radius:16px;background:var(--surface);box-shadow:var(--shadow-sm)}.panel h2{margin:0 0 18px;font-size:16px}.cityPanel{max-height:calc(100vh - 160px);overflow:auto}.cityPanel ul{margin:12px 0;padding:0;columns:1}.cityPanel li{padding:3px 0}.queryPanel h4{margin:22px 0 9px}.queryPanel p{margin:10px 0}.resultsPanel{min-width:0}.emptyState{display:flex;min-height:260px;align-items:center;justify-content:center;flex-direction:column;gap:8px;margin-top:18px;padding:30px;border:1px dashed #bac6d4;border-radius:16px;background:rgba(255,255,255,.55);color:var(--text-muted);text-align:center}.emptyState strong{color:var(--text);font-size:18px}.mapWorkspace{margin-top:14px}.mapToolbar{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}.mapToolbar strong,.mapToolbar span{display:block}.mapToolbar span{margin-top:3px;color:var(--text-muted);font-size:12px}.broadcastControls{margin-bottom:14px;border:1px solid var(--border);border-radius:12px;background:var(--surface)}.broadcastControls summary{padding:14px 16px;cursor:pointer}.broadcastControls summary span,.broadcastControls summary strong,.broadcastControls summary small{display:block}.broadcastControls summary small,.controlHelp,.fieldHint{color:var(--text-muted);font-size:12px}.broadcastControls[open] summary{border-bottom:1px solid var(--border)}.broadcastControls .grid-1-2{padding:16px}.controlHelp{margin:16px 16px 0}.canvasFrame{overflow:hidden;border:1px solid var(--border);border-radius:14px;background:#d8dee6;box-shadow:var(--shadow-sm)}.canvasFrame canvas{display:block}.weatherIconButton{border-radius:7px}.fieldHint{margin:4px 0 10px}@media(max-width:1200px){.aggregator>.container{grid-template-columns:220px 1fr}.resultsPanel{grid-column:1/-1}.cityPanel{max-height:none}}@media(max-width:700px){.pageWorkspace{padding:24px 14px}.pageHeading{align-items:start;flex-direction:column}.aggregator>.container{grid-template-columns:1fr}.resultsPanel{grid-column:auto}} .pageWorkspace{max-width:1800px;margin:auto;padding:34px 28px 70px}.pageHeading{display:flex;align-items:end;justify-content:space-between;gap:24px;margin-bottom:24px}.pageHeading h1{margin:5px 0 6px;font-size:34px}.pageHeading p{margin:0;color:var(--text-muted)}.selectionCount{padding:10px 14px;border:1px solid var(--border);border-radius:10px;background:var(--surface);font-weight:700}.aggregator>.container{display:grid;grid-template-columns:240px 300px minmax(0,1fr);gap:18px;align-items:start}.panel{padding:20px;border:1px solid var(--border);border-radius:16px;background:var(--surface);box-shadow:var(--shadow-sm)}.panel h2{margin:0 0 18px;font-size:16px}.cityPanel{max-height:calc(100vh - 160px);overflow:auto}.cityPanel ul{margin:12px 0;padding:0;columns:1}.cityPanel li{padding:3px 0}.queryPanel h4{margin:22px 0 9px}.queryPanel p{margin:10px 0}.resultsPanel{min-width:0}.emptyState{display:flex;min-height:260px;align-items:center;justify-content:center;flex-direction:column;gap:8px;margin-top:18px;padding:30px;border:1px dashed #bac6d4;border-radius:16px;background:rgba(255,255,255,.55);color:var(--text-muted);text-align:center}.emptyState strong{color:var(--text);font-size:18px}.mapWorkspace{margin-top:14px}.mapToolbar{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}.mapToolbar strong,.mapToolbar span{display:block}.mapToolbar span{margin-top:3px;color:var(--text-muted);font-size:12px}.broadcastControls{margin-bottom:14px;border:1px solid var(--border);border-radius:12px;background:var(--surface)}.broadcastControls summary{padding:14px 16px;cursor:pointer}.broadcastControls summary span,.broadcastControls summary strong,.broadcastControls summary small{display:block}.broadcastControls summary small,.controlHelp,.fieldHint{color:var(--text-muted);font-size:12px}.broadcastControls[open] summary{border-bottom:1px solid var(--border)}.broadcastControls .grid-1-2{padding:16px}.controlHelp{margin:16px 16px 0}.canvasFrame{overflow:hidden;border:1px solid var(--border);border-radius:14px;background:#d8dee6;box-shadow:var(--shadow-sm)}.canvasFrame canvas{display:block}.weatherIconButton{border-radius:7px}.fieldHint{margin:4px 0 10px}@media(max-width:1200px){.aggregator>.container{grid-template-columns:220px 1fr}.resultsPanel{grid-column:1/-1}.cityPanel{max-height:none}}@media(max-width:700px){.pageWorkspace{padding:24px 14px}.pageHeading{align-items:start;flex-direction:column}.aggregator>.container{grid-template-columns:1fr}.resultsPanel{grid-column:auto}}
+5 -5
View File
@@ -24,13 +24,13 @@ export function Cities() {
<div class="aggregator pageWorkspace"> <div class="aggregator pageWorkspace">
<div class="pageHeading"><div><span class="eyebrow">Historical observations</span><h1>City analysis</h1><p>Choose stations and a calculation, then compare the resulting values.</p></div><div class="selectionCount">{getCities().size} cities selected</div></div> <div class="pageHeading"><div><span class="eyebrow">Historical observations</span><h1>City analysis</h1><p>Choose stations and a calculation, then compare the resulting values.</p></div><div class="selectionCount">{getCities().size} cities selected</div></div>
<div class="container"> <div class="container">
<aside class="column panel cityPanel"> <aside class="column cityPanel workspaceRail">
<h2>1. Choose cities</h2> <h2>1. Choose cities</h2>
<SelectCity getCities={getCities} setCities={setCities} /> <SelectCity getCities={getCities} setCities={setCities} />
</aside> </aside>
<aside class="column panel queryPanel"> <aside class="column queryPanel workspaceToolbar">
<h2>2. Configure query</h2> <h2>2. Configure query</h2>
<div> <div class="queryTimeRange">
<SelectTimeRange <SelectTimeRange
getStart={getStart} getStart={getStart}
setStart={setStart} setStart={setStart}
@@ -38,8 +38,8 @@ export function Cities() {
setEnd={setEnd} setEnd={setEnd}
/> />
</div> </div>
<div> <div class="queryOptions">
<div> <div class="queryField">
<h4>Select field</h4> <h4>Select field</h4>
<SelectField getField={getField} setField={setField} /> <SelectField getField={getField} setField={setField} />
</div> </div>
+6 -4
View File
@@ -36,12 +36,14 @@ export function Faktiska() {
return <div class="mapGraphicsPage pageWorkspace"> return <div class="mapGraphicsPage pageWorkspace">
<div class="pageHeading"><div><span class="eyebrow">Daily newsroom production</span><h1>Faktiskā</h1><p>Create the actual-weather map with temperatures and weather symbols.</p></div><div class="selectionCount">{cities().size} cities selected</div></div> <div class="pageHeading"><div><span class="eyebrow">Daily newsroom production</span><h1>Faktiskā</h1><p>Create the actual-weather map with temperatures and weather symbols.</p></div><div class="selectionCount">{cities().size} cities selected</div></div>
<div class="graphicsSetup"> <div class="graphicsSetup">
<aside class="panel cityPanel"><h2>1. Choose cities</h2><SelectCity getCities={cities} setCities={setCities}/></aside> <aside class="cityPanel workspaceRail"><h2>1. Choose cities</h2><SelectCity getCities={cities} setCities={setCities}/></aside>
<section class="graphicsMain"> <section class="graphicsMain">
<div class="panel productionOptions"><h2>2. Choose output</h2> <div class="productionToolbar">
<div class="choiceGroup"><span>Output</span><div><button classList={{active: resolution() === "1920x1080"}} onClick={() => setResolution("1920x1080")}>1920 × 1080</button><button classList={{active: resolution() === "3840x1440"}} onClick={() => setResolution("3840x1440")}>3840 × 1440</button><label class="windChoice"><input type="checkbox" checked={wind()} onChange={e => setWind(e.target.checked)}/> Wind layout</label></div></div> <div class="productionOptions"><h2>2. Choose output</h2>
<div class="choiceGroup"><span>Output</span><div><button classList={{active: resolution() === "1920x1080"}} onClick={() => setResolution("1920x1080")}>1920 × 1080</button><button classList={{active: resolution() === "3840x1440"}} onClick={() => setResolution("3840x1440")}>3840 × 1440</button><label class="windChoice"><input type="checkbox" checked={wind()} onChange={e => setWind(e.target.checked)}/> Wind layout</label></div></div>
</div>
<div class="dataOptions"><h2>3. Retrieve temperatures</h2><div class="dataOptionsGrid"><SelectTimeRange getStart={start} setStart={setStart} getEnd={end} setEnd={setEnd}/><label><span>Temperature field</span><select value={field()} onChange={e => setField(e.target.value)}><option value="tempMax">Maximum temperature</option><option value="tempMin">Minimum temperature</option><option value="tempAvg">Average temperature</option></select></label><label><span>Calculation</span><select value={key()} onChange={e => setKey(e.target.value)}><option value="max">Maximum</option><option value="min">Minimum</option><option value="avg">Average</option></select></label><button class="primary loadMapButton" disabled={!cities().size} onClick={() => setTrigger(Date.now())}>Load map data</button></div></div>
</div> </div>
<div class="panel dataOptions"><h2>3. Retrieve temperatures</h2><div class="dataOptionsGrid"><SelectTimeRange getStart={start} setStart={setStart} getEnd={end} setEnd={setEnd}/><label><span>Temperature field</span><select value={field()} onChange={e => setField(e.target.value)}><option value="tempMax">Maximum temperature</option><option value="tempMin">Minimum temperature</option><option value="tempAvg">Average temperature</option></select></label><label><span>Calculation</span><select value={key()} onChange={e => setKey(e.target.value)}><option value="max">Maximum</option><option value="min">Minimum</option><option value="avg">Average</option></select></label></div><button class="primary" disabled={!cities().size} onClick={() => setTrigger(Date.now())}>Load map data</button></div>
<Show when={result.loading}><LoadingSpinner text="Loading map data"/></Show> <Show when={result.loading}><LoadingSpinner text="Loading map data"/></Show>
<Show when={result.error}><div class="emptyState"><strong>Could not load map data</strong><span>{result.error.message}</span></div></Show> <Show when={result.error}><div class="emptyState"><strong>Could not load map data</strong><span>{result.error.message}</span></div></Show>
<Show when={result() && isQueryResult(result())}><section class="mapProduction"><h2>4. Add conditions and export</h2><MapView type={mapType()} data={cityData} mode="faktiska"/></section></Show> <Show when={result() && isQueryResult(result())}><section class="mapProduction"><h2>4. Add conditions and export</h2><MapView type={mapType()} data={cityData} mode="faktiska"/></section></Show>
@@ -1,2 +1,34 @@
.graphicsSetup{display:grid;grid-template-columns:240px minmax(0,1fr);gap:18px;align-items:start}.graphicsMain{display:grid;gap:18px}.productionOptions,.dataOptions{padding:20px}.choiceGroup{display:grid;grid-template-columns:130px 1fr;gap:16px;align-items:start;margin-top:16px}.choiceGroup>span,.dataOptions label>span{display:block;font-size:12px;font-weight:700}.choiceGroup>div{display:flex;flex-wrap:wrap;gap:9px}.choiceGroup button{min-width:150px;text-align:left}.choiceGroup button small{display:block;margin-top:3px;color:var(--text-muted);font-family:Inter,sans-serif}.choiceGroup button.active{border-color:var(--accent);background:var(--accent-pale);color:var(--accent-strong)}.windChoice{display:flex;align-items:center;gap:7px;padding:9px 12px}.dataOptionsGrid{display:grid;grid-template-columns:minmax(250px,1.4fr) 1fr 1fr;gap:18px;align-items:end;margin-bottom:16px}.dataOptions select{width:100%;margin-top:6px}.mapProduction>h2{margin:0 0 10px}.symbolEditor{min-width:0}.symbolEditorHeading{display:flex;align-items:start;justify-content:space-between;gap:12px}.symbolPalette{display:flex;flex-wrap:wrap;gap:5px;margin:10px 0 16px}.citySymbols{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:8px}.citySymbolRow{display:grid;grid-template-columns:1fr 52px 54px;gap:6px;align-items:center;padding:7px;border:1px solid var(--border);border-radius:9px}.assignedSymbol{font-family:'Daira by LTV grafika';font-size:28px;padding:2px}.clearSymbol{padding:5px;font-size:11px}@media(max-width:900px){.graphicsSetup{grid-template-columns:1fr}.dataOptionsGrid{grid-template-columns:1fr}.choiceGroup{grid-template-columns:1fr}} .graphicsSetup{display:grid;grid-template-columns:240px minmax(0,1fr);gap:18px;align-items:start}.graphicsMain{display:grid;gap:18px}.productionOptions,.dataOptions{padding:20px}.choiceGroup{display:grid;grid-template-columns:130px 1fr;gap:16px;align-items:start;margin-top:16px}.choiceGroup>span,.dataOptions label>span{display:block;font-size:12px;font-weight:700}.choiceGroup>div{display:flex;flex-wrap:wrap;gap:9px}.choiceGroup button{min-width:150px;text-align:left}.choiceGroup button small{display:block;margin-top:3px;color:var(--text-muted);font-family:Inter,sans-serif}.choiceGroup button.active{border-color:var(--accent);background:var(--accent-pale);color:var(--accent-strong)}.windChoice{display:flex;align-items:center;gap:7px;padding:9px 12px}.dataOptionsGrid{display:grid;grid-template-columns:minmax(250px,1.4fr) 1fr 1fr;gap:18px;align-items:end;margin-bottom:16px}.dataOptions select{width:100%;margin-top:6px}.mapProduction>h2{margin:0 0 10px}.symbolEditor{min-width:0}.symbolEditorHeading{display:flex;align-items:start;justify-content:space-between;gap:12px}.symbolPalette{display:flex;flex-wrap:wrap;gap:5px;margin:10px 0 16px}.citySymbols{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:8px}.citySymbolRow{display:grid;grid-template-columns:1fr 52px 54px;gap:6px;align-items:center;padding:7px;border:1px solid var(--border);border-radius:9px}.assignedSymbol{font-family:'Daira by LTV grafika';font-size:28px;padding:2px}.clearSymbol{padding:5px;font-size:11px}@media(max-width:900px){.graphicsSetup{grid-template-columns:1fr}.dataOptionsGrid{grid-template-columns:1fr}.choiceGroup{grid-template-columns:1fr}}
.symbolEditorHeading{gap:16px}.bulkSymbol{display:flex;align-items:center;gap:8px}.selectedPreview,.currentSymbol{display:flex;align-items:center;justify-content:center;font-family:'Daira by LTV grafika';font-size:30px}.selectedPreview{width:42px;height:42px;border-radius:9px;background:#17212d;color:#fff}.symbolPalette{display:grid;grid-template-columns:repeat(auto-fill,46px);gap:8px;margin:14px 0 22px;padding:14px;border:1px solid var(--border);border-radius:14px;background:var(--surface-soft)}.assignmentHeading{display:flex;align-items:baseline;gap:10px;margin-bottom:10px}.assignmentHeading span{color:var(--text-muted);font-size:12px}.citySymbols{grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:10px}.citySymbolRow{grid-template-columns:minmax(90px,1fr) 48px 100px 36px;gap:8px;min-height:62px;padding:8px 10px;border-radius:12px;background:#fff}.cityName{overflow:hidden;text-overflow:ellipsis;font-weight:700}.currentSymbol{width:44px;height:44px;border:1px solid #d7dfe8;border-radius:9px;background:#f6f8fa}.currentSymbol.empty{font-family:Inter,sans-serif;font-size:18px;color:#98a2b3}.assignAction{padding:7px 9px;font-size:12px}.clearSymbol{width:36px;padding:0;font-size:20px;color:var(--text-muted)}@media(max-width:900px){.symbolEditorHeading{flex-direction:column}.citySymbols{grid-template-columns:1fr}} .symbolEditorHeading{gap:16px}.bulkSymbol{display:flex;align-items:center;gap:8px}.selectedPreview,.currentSymbol{display:flex;align-items:center;justify-content:center;font-family:'Daira by LTV grafika';font-size:30px}.selectedPreview{width:42px;height:42px;border-radius:9px;background:#17212d;color:#fff}.symbolPalette{display:grid;grid-template-columns:repeat(auto-fill,46px);gap:8px;margin:14px 0 22px;padding:14px;border:1px solid var(--border);border-radius:14px;background:var(--surface-soft)}.assignmentHeading{display:flex;align-items:baseline;gap:10px;margin-bottom:10px}.assignmentHeading span{color:var(--text-muted);font-size:12px}.citySymbols{grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:10px}.citySymbolRow{grid-template-columns:minmax(90px,1fr) 48px 100px 36px;gap:8px;min-height:62px;padding:8px 10px;border-radius:12px;background:#fff}.cityName{overflow:hidden;text-overflow:ellipsis;font-weight:700}.currentSymbol{width:44px;height:44px;border:1px solid #d7dfe8;border-radius:9px;background:#f6f8fa}.currentSymbol.empty{font-family:Inter,sans-serif;font-size:18px;color:#98a2b3}.assignAction{padding:7px 9px;font-size:12px}.clearSymbol{width:36px;padding:0;font-size:20px;color:var(--text-muted)}@media(max-width:900px){.symbolEditorHeading{flex-direction:column}.citySymbols{grid-template-columns:1fr}}
/* Production layout: a compact control band above one uninterrupted workspace. */
.graphicsSetup{grid-template-columns:230px minmax(0,1fr);column-gap:28px}
.mapGraphicsPage .workspaceRail{
padding:4px 24px 10px 0;
border-right:1px solid rgba(90,126,166,.2);
}
.mapGraphicsPage .workspaceRail h2{margin:0 0 18px;font-size:16px}
.graphicsMain{gap:16px}
.productionToolbar{
padding:2px 0 20px;
border-bottom:1px solid rgba(90,126,166,.22);
}
.productionOptions,.dataOptions{padding:0}
.productionOptions h2,.dataOptions h2{margin:0;font-size:18px}
.productionOptions{display:grid;grid-template-columns:210px minmax(0,1fr);gap:24px;align-items:center}
.productionOptions .choiceGroup{margin:0}
.dataOptions{display:grid;grid-template-columns:210px minmax(0,1fr);gap:24px;align-items:center;margin-top:18px;padding-top:18px;border-top:1px solid rgba(90,126,166,.14)}
.dataOptionsGrid{grid-template-columns:minmax(300px,1.35fr) minmax(180px,1fr) minmax(160px,.9fr) auto;gap:16px;margin:0;align-items:end}
.loadMapButton{white-space:nowrap;align-self:end}
.mapProduction>h2{font-size:22px}
@media(max-width:1200px){
.productionOptions,.dataOptions{grid-template-columns:1fr;gap:12px}
.dataOptionsGrid{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media(max-width:900px){
.graphicsSetup{grid-template-columns:1fr}
.mapGraphicsPage .workspaceRail{padding-right:0;border-right:0;border-bottom:1px solid rgba(90,126,166,.2)}
.dataOptionsGrid{grid-template-columns:1fr}
.loadMapButton{justify-self:start}
}