From bc9ea586143db7354c1d4bcaa0e02807ca7a0495 Mon Sep 17 00:00:00 2001 From: Roman Priesol Date: Wed, 9 Sep 2026 09:02:29 +0200 Subject: [PATCH] Start the NPS Unigrid visual redesign Black topbar banner with live map-center coordinates, a black header (with NPS pictogram) on waypoint popups, offline basemap wood/park colors unified with the online Liberty green, and the sidebar grouped into the planned Explorer / Offline hub / Navigation menu sections. --- public/css/style.css | 58 +++++++++++++++++++++++++++---- public/js/gpx.js | 7 ++-- public/js/map.js | 83 ++++++++++++++++++++++++++++---------------- views/index.erb | 1 + 4 files changed, 112 insertions(+), 37 deletions(-) diff --git a/public/css/style.css b/public/css/style.css index 2cf5b12..7dbfed2 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -14,16 +14,24 @@ html, body { flex: 0 0 auto; display: flex; align-items: center; - height: 48px; + height: 44px; padding: 0 16px; - background: #fff; - border-bottom: 1px solid #ddd; + background: #1a1a1a; } .topbar__brand { - font-size: 15px; + font-size: 14px; font-weight: 700; - color: #333; + letter-spacing: 0.12em; + text-transform: uppercase; + color: #fff; +} + +.topbar__coords { + margin: 0 auto; + font-size: 12px; + font-variant-numeric: tabular-nums; + color: #bbb; } .topbar__auth { @@ -32,7 +40,11 @@ html, body { align-items: center; gap: 8px; font-size: 13px; - color: #555; + color: #ccc; +} + +.topbar__auth a { + color: #fff; } .app-body { @@ -61,6 +73,17 @@ html, body { position: relative; } +.sidebar-group-label { + width: 28px; + height: 1px; + margin: 4px 0; + background: #ddd; +} + +.sidebar-group-label:first-child { + display: none; +} + .sidebar-icon { width: 36px; height: 36px; @@ -649,6 +672,29 @@ html, body { user-select: none; } +.gpx-popup__header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + margin: -15px -10px 8px; + padding: 10px 12px; + background: #1a1a1a; + color: #fff; +} + +.gpx-popup__header-icon { + flex-shrink: 0; + width: 18px; + height: 18px; + color: #fff; +} + +.gpx-popup__header-icon svg { + width: 100%; + height: 100%; +} + .gpx-popup__time { color: #888; font-size: 12px; diff --git a/public/js/gpx.js b/public/js/gpx.js index d24ebf9..4359eed 100644 --- a/public/js/gpx.js +++ b/public/js/gpx.js @@ -916,9 +916,12 @@ function gpxFileUrl(path) { // properties come straight from an uploaded GPX file — untrusted input — // so every value is escaped before landing in the popup's HTML. -function popupHtml({ name, description, time, category, photos }) { +function popupHtml({ name, description, time, category, photos, icon }) { const parts = []; - if (name) parts.push(`${escapeHtml(name)}`); + if (name) { + const glyph = icon && POI_ICONS[icon] ? `${POI_ICONS[icon]}` : ""; + parts.push(`
${escapeHtml(name)}${glyph}
`); + } if (category) parts.push(`

${escapeHtml(category)}

`); if (photos?.length > 0) parts.push(formatPhotos(photos)); if (description) parts.push(formatDescription(description)); diff --git a/public/js/map.js b/public/js/map.js index ea34d07..37b7f95 100644 --- a/public/js/map.js +++ b/public/js/map.js @@ -22,22 +22,23 @@ const ZOOM = 13; const ONLINE_STYLE_URL = "https://tiles.openfreemap.org/styles/liberty"; // Recolors OpenFreeMap Liberty to the same NPS-inspired palette as -// OFFLINE_THEME below — warm cream ground, brown/khaki land cover, green -// reserved for parks, muted roads/boundaries — mapped onto Liberty's -// OpenMapTiles layer ids instead of protomaps' theme roles (the two -// schemas don't share layer names, so this is a separate hand-built -// table, not a shared object). Only the layers that dominate the map's -// overall color read are touched; labels, halos, rail, and minor +// OFFLINE_THEME below — warm cream ground, muted roads/boundaries — mapped +// onto Liberty's OpenMapTiles layer ids instead of protomaps' theme roles +// (the two schemas don't share layer names, so this is a separate +// hand-built table, not a shared object). Only the layers that dominate the +// map's overall color read are touched; labels, halos, rail, and minor // overlays (cemetery/hospital/school/pitch/track shading, building // outlines) are left at Liberty's own defaults — same "not a full // recolor" scope as OFFLINE_THEME. Road casings have no protomaps-role // equivalent to reuse (offline roads don't get a separate outline), so // #c9a66b there is reasoned to fit the family rather than sourced from // anywhere real. +// +// wood/grass are deliberately left unpatched — Liberty's own green reads +// better than this app's earlier brown/khaki recolor, and OFFLINE_THEME's +// wood_a/scrub_a below were changed to match Liberty's green instead. const ONLINE_STYLE_PATCH = { background: { "background-color": "#f9f6ef" }, - landcover_wood: { "fill-color": "#c9bd9c" }, - landcover_grass: { "fill-color": "#d9cfb0" }, landcover_sand: { "fill-color": "#f0e4c4" }, water: { "fill-color": "#a3cedf" }, park: { "fill-color": "#b3e378" }, @@ -155,19 +156,25 @@ const OFFLINE_THEME = { background: "#f9f6ef", // real: Park Tiles' own background, hsl(40, 37%, 97%) earth: "#f9f6ef", water: "#a3cedf", - // Brown/khaki, not green: in Park Tiles' own Imagery style, green is - // specifically the protected-area/park color (below), not a general land - // cover fill — actual forest there is just the aerial photo underneath, - // which this offline style has no equivalent of. Using that same vivid - // green for ordinary wood/scrub as well (an earlier version of this - // theme did) made everything read as "green," not "brown with green - // parks" — reversed here to match the real distinction. - wood_a: "#c9bd9c", - wood_b: "#bcae8a", - scrub_a: "#d9cfb0", - scrub_b: "#cfc29d", - park_a: "#c8e2a0", - park_b: "#b3e378", // real: Park Tiles' protected-area/park-point green + // Matches OpenFreeMap Liberty's own wood/grass green (its landcover_wood + // hsla(98,61%,72%,0.7) and landcover_grass rgba(176,213,154,1) at 0.3 + // opacity, blended over the cream background) — the online map's actual + // look whenever ONLINE_STYLE_PATCH's brown/khaki recolor fails to apply + // (see loadOnlineStyle's fetch fallback) turned out to be the look this + // app's users actually want, so offline was changed to match it instead + // of chasing the patch's own reliability. + wood_a: "#c3e8ab", + wood_b: "#a8d888", + scrub_a: "#dcecc9", + scrub_b: "#cfe3b8", + // Close to wood_b, not the more saturated Park Tiles green: protomaps' + // theme routes "forest"/"nature_reserve"/"protected_area" (e.g. the Malé + // Karpaty CHKO boundary) through this same park_a/park_b pair as actual + // named parks — Liberty's online style doesn't make that reserve-boundary + // distinction at all, so a visibly different green here reads as an + // offline-only blotch rather than a deliberate distinction. + park_a: "#c3e8ab", + park_b: "#a8d888", sand: "#f0e4c4", beach: "#f0e4c4", buildings: "#ded7cc", @@ -388,25 +395,43 @@ map.once("style.load", () => { map.addControl(new OverviewMapControl({}), "top-right"); }); +const coordsLabel = document.querySelector(".topbar__coords"); +const updateCoords = () => { + const { lng, lat } = map.getCenter(); + coordsLabel.textContent = `${lat.toFixed(5)}, ${lng.toFixed(5)}`; +}; +updateCoords(); +map.on("move", updateCoords); + const sidebar = document.getElementById("sidebar"); +// Grouped into the TODO's planned 4-module menu (Explorer / Offline hub / +// Navigation / Camper & HW) ahead of that split actually happening — +// Camper & HW has no sidebar item yet (leveling/solar/telemetry are all +// still unbuilt), so it's left out rather than shown empty. const layerToggle = new LayerToggleControl(applyStyle); +const profilePanel = new TrackProfilePanel(map); +profilePanel.mount(); + +sidebar.insertAdjacentHTML("beforeend", ``); +new GpxPanelControl(map, profilePanel).mount(sidebar); + +sidebar.insertAdjacentHTML("beforeend", ``); layerToggle.mount(sidebar); layerToggle.setActive("online"); +new RadarControl(map).mount(sidebar); + +sidebar.insertAdjacentHTML("beforeend", ``); +gnssControl.mount(sidebar); +new LiveTrackPanelControl(map).mount(sidebar); + // The sidebar already has a "Base map" icon (opens the same panel) — // this is a second entry point for it, grouped with Home/zoom on the map // itself rather than off in the page's own side rail, matching NPS's -// layout. Added after mount() since it reuses that panel/toggle state. +// layout. layerToggle only needs to exist (not be mounted yet) for this. const mapLayersButton = new MapLayersButton(layerToggle); map.addControl(mapLayersButton, "top-left"); -const profilePanel = new TrackProfilePanel(map); -profilePanel.mount(); -new GpxPanelControl(map, profilePanel).mount(sidebar); -new RadarControl(map).mount(sidebar); -gnssControl.mount(sidebar); -new LiveTrackPanelControl(map).mount(sidebar); - function applyStyle(name) { layerToggle.setActive(name); map.setStyle(name === "offline" ? offlineStyle() : ONLINE_STYLE); diff --git a/views/index.erb b/views/index.erb index 43cfb35..3771293 100644 --- a/views/index.erb +++ b/views/index.erb @@ -1,6 +1,7 @@
MapTool +
<% if logged_in %> <%= user %>