fix(map): readable incident rail — dark basemap, opaque cards, AA colours (#136)
- "Dark" base layer was bright OSM in prod; invert the raster via .drb-tiles-dark so dark-theme overlays sit on a dark ground. - Rail and mobile incident cards are opaque, not glass over the map. - Dark --ink-muted #77839A -> #8C98AE, --sev-major #D03B3B -> #F05252 (both failed AA as small text on surface/raised). - Card borders used `color + "55"` on CSS var() colours, which is invalid CSS — no border rendered. Now color-mix. - 10px rail labels -> 11px. Frontend unverified: no Node on the authoring box (#207). Closes #136 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
49166789f4
commit
8099871c68
@@ -43,11 +43,11 @@
|
||||
--line-strong: rgba(255,255,255,.17);
|
||||
--ink: #E8EBF0;
|
||||
--ink-2: #A5B0C2;
|
||||
--ink-muted: #77839A;
|
||||
--ink-muted: #8C98AE; /* was #77839A: 4.2:1 on raised, failed AA at the 10-12px it's used at (#136) */
|
||||
|
||||
--accent: #3987E5;
|
||||
--sev-moderate: #C98500;
|
||||
--sev-major: #D03B3B;
|
||||
--sev-major: #F05252; /* was #D03B3B: 3.7:1 as text on surface (#136) */
|
||||
|
||||
--map-bg: #0E131B;
|
||||
--map-block: #161C26;
|
||||
@@ -191,6 +191,14 @@ html:not(.dark) .border-indigo-800 { border-color: #a5b4fc !important; }
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* "Dark" base layer (#136): the prod tile URL is bright OSM, so dark-theme
|
||||
* overlay cards sat on a bright, busy map. Inverting the raster keeps the
|
||||
* keyless OSM source (CARTO's dark_all CDN proved flaky, #117) while giving
|
||||
* the overlays the dark ground their palette was chosen for. */
|
||||
.leaflet-tile-pane .drb-tiles-dark {
|
||||
filter: invert(1) hue-rotate(180deg) brightness(0.9) contrast(0.9) saturate(0.6);
|
||||
}
|
||||
|
||||
/* Pinned 511 camera tiles (MapView DotCameraLayer): strip Leaflet's tooltip
|
||||
* padding and let the tile's own theme colours show, so the tile is no bigger
|
||||
* than the video it holds. */
|
||||
|
||||
@@ -1802,6 +1802,7 @@ function MapView({
|
||||
<TileLayer
|
||||
url={MAP_TILE_URL}
|
||||
attribution={MAP_TILE_ATTRIBUTION}
|
||||
className={MAP_TILE_URL.includes("dark") ? undefined : "drb-tiles-dark"}
|
||||
/>
|
||||
</LayersControl.BaseLayer>
|
||||
<LayersControl.BaseLayer checked={startBase === "light"} name={BASE_NAMES.light}>
|
||||
@@ -2083,7 +2084,7 @@ function MapView({
|
||||
{/* Gate A / A2 (server-26#46) — the rail's titles, locations and
|
||||
unit counts are pipeline output. Pinned above the scroll area
|
||||
so it cannot be scrolled off the screen it qualifies. */}
|
||||
<div className="bg-surface/90 backdrop-blur-sm border border-line rounded-lg px-2 py-1.5 shrink-0 pointer-events-auto">
|
||||
<div className="bg-surface border border-line-strong rounded-lg px-2 py-1.5 shrink-0 pointer-events-auto shadow">
|
||||
{filteredFrom != null && (
|
||||
<p className="text-[11px] text-ink-2 mb-1"><b className="text-ink font-semibold tabular-nums">{incidents.length} of {filteredFrom}</b> · filtered</p>
|
||||
)}
|
||||
@@ -2104,12 +2105,13 @@ function MapView({
|
||||
const latest = (callsByIncident.get(inc.incident_id) ?? [])
|
||||
.filter(hasAudio)
|
||||
.reduce<CallRecord | null>((best, c) => (!best || c.started_at > best.started_at ? c : best), null);
|
||||
const baseClass = `w-full text-left bg-surface/90 backdrop-blur-sm border rounded-lg px-3 py-2 text-xs hover:brightness-110 transition-all ${selected ? "ring-2 ring-accent" : ""}`;
|
||||
// Opaque, not glass (#136): any see-through over a busy basemap put text on map colour.
|
||||
const baseClass = `w-full text-left bg-surface border shadow rounded-lg px-3 py-2 text-xs hover:brightness-110 transition-all ${selected ? "ring-2 ring-accent" : ""}`;
|
||||
const cardBody = (
|
||||
<>
|
||||
<div className="flex items-center gap-1.5 mb-0.5">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" dangerouslySetInnerHTML={{ __html: TYPE_GLYPH_PATHS[inc.type === "accident" ? "collision" : (inc.type && TYPE_GLYPH_PATHS[inc.type] ? inc.type : "other")] }} />
|
||||
<span className="uppercase tracking-wide font-semibold text-[10px]" style={{ color }}>
|
||||
<span className="uppercase tracking-wide font-semibold text-[11px]" style={{ color }}>
|
||||
{isKnownSeverity(inc.severity) ? SEVERITY_LABEL[inc.severity] : "Unknown"}
|
||||
</span>
|
||||
{live && <span className="ml-auto bg-accent text-white rounded px-1 text-[9px] font-semibold tracking-wide">ON AIR</span>}
|
||||
@@ -2123,9 +2125,9 @@ function MapView({
|
||||
{onSelectIncident && units.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1 mt-1">
|
||||
{units.slice(0, 4).map((u) => (
|
||||
<span key={u} className="font-mono text-[10px] px-1 rounded bg-raised text-ink-2">{u}</span>
|
||||
<span key={u} className="font-mono text-[11px] px-1 rounded bg-raised text-ink-2">{u}</span>
|
||||
))}
|
||||
{units.length > 4 && <span className="font-mono text-[10px] px-1 rounded bg-raised text-ink-muted">+{units.length - 4}</span>}
|
||||
{units.length > 4 && <span className="font-mono text-[11px] px-1 rounded bg-raised text-ink-2">+{units.length - 4}</span>}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center justify-between mt-0.5">
|
||||
@@ -2137,7 +2139,7 @@ function MapView({
|
||||
)}
|
||||
</div>
|
||||
{!inc.location_coords && !onSelectIncident && (
|
||||
<p className="text-[10px] text-accent mt-1">View details →</p>
|
||||
<p className="text-[11px] text-accent mt-1">View details →</p>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
@@ -2153,7 +2155,7 @@ function MapView({
|
||||
onClick={() => handleIncidentSelect(inc)}
|
||||
aria-pressed={selected}
|
||||
className={`${baseClass} ${latest ? "pr-10" : ""}`}
|
||||
style={{ borderColor: color + "55" }}
|
||||
style={{ borderColor: `color-mix(in srgb, ${color} 60%, transparent)` }}
|
||||
>
|
||||
{cardBody}
|
||||
</button>
|
||||
@@ -2166,7 +2168,7 @@ function MapView({
|
||||
<a
|
||||
href={`/incidents/${inc.incident_id}`}
|
||||
className={`block ${baseClass} ${latest ? "pr-10" : ""}`}
|
||||
style={{ borderColor: color + "55" }}
|
||||
style={{ borderColor: `color-mix(in srgb, ${color} 60%, transparent)` }}
|
||||
>
|
||||
{cardBody}
|
||||
</a>
|
||||
@@ -2181,13 +2183,13 @@ function MapView({
|
||||
<div className="absolute bottom-0 left-0 right-0 z-[1001] md:hidden">
|
||||
<button
|
||||
onClick={() => setDrawerOpen((v: boolean) => !v)}
|
||||
className="w-full bg-surface/95 border-t border-line px-4 py-2 text-xs text-ink-2 flex items-center justify-between"
|
||||
className="w-full bg-surface border-t border-line-strong px-4 py-2 text-xs text-ink-2 flex items-center justify-between"
|
||||
>
|
||||
<span>Incidents ({filteredFrom != null ? `${incidents.length} of ${filteredFrom} · filtered` : incidents.length})</span>
|
||||
<span>{drawerOpen ? "▼" : "▲"}</span>
|
||||
</button>
|
||||
{drawerOpen && (
|
||||
<div className="bg-surface/95 border-t border-line max-h-52 overflow-y-auto px-3 py-2 space-y-1.5">
|
||||
<div className="bg-surface border-t border-line max-h-52 overflow-y-auto px-3 py-2 space-y-1.5">
|
||||
{/* Gate A / A2 (server-26#46) */}
|
||||
<MachineOutputNotice variant="inline" className="text-[10px] items-start" />
|
||||
{railIncidents.map((inc) => {
|
||||
@@ -2210,7 +2212,7 @@ function MapView({
|
||||
handleIncidentSelect(inc);
|
||||
}}
|
||||
className="w-full text-left border rounded px-2 py-1.5 text-xs"
|
||||
style={{ borderColor: color + "55" }}
|
||||
style={{ borderColor: `color-mix(in srgb, ${color} 60%, transparent)` }}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
@@ -2221,7 +2223,7 @@ function MapView({
|
||||
key={inc.incident_id}
|
||||
href={`/incidents/${inc.incident_id}`}
|
||||
className="block w-full text-left border rounded px-2 py-1.5 text-xs"
|
||||
style={{ borderColor: color + "55" }}
|
||||
style={{ borderColor: `color-mix(in srgb, ${color} 60%, transparent)` }}
|
||||
>
|
||||
{label}
|
||||
</a>
|
||||
|
||||
Reference in New Issue
Block a user