From 561bc176f966844ba0fbe2818a6ee21ed75ed2c2 Mon Sep 17 00:00:00 2001 From: you Date: Thu, 19 Mar 2026 21:49:40 +0000 Subject: [PATCH] fix: map markers use distinct shapes (diamond/circle/square/triangle) + high-contrast colors for accessibility --- public/map.js | 54 +++++++++++++++++++++++++++++++++++------------- public/style.css | 2 ++ 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/public/map.js b/public/map.js index 152bde83..a16a3b00 100644 --- a/public/map.js +++ b/public/map.js @@ -17,16 +17,44 @@ // Safe escape — falls back to identity if app.js hasn't loaded yet const safeEsc = (typeof esc === 'function') ? esc : function (s) { return s; }; - // Role → marker style (WCAG AA compliant: all ≥4.5:1 on both light/dark backgrounds) + // Distinct shapes + high-contrast WCAG AA colors for each role const ROLE_STYLE = { - repeater: { color: '#1d4ed8', fill: true, radius: 8, weight: 2 }, - companion: { color: '#0369a1', fill: false, radius: 7, weight: 2 }, - room: { color: '#6d28d9', fill: true, radius: 7, weight: 2 }, - sensor: { color: '#92400e', fill: true, radius: 4, weight: 1 }, + repeater: { color: '#dc2626', shape: 'diamond', radius: 10, weight: 2 }, // red diamond + companion: { color: '#2563eb', shape: 'circle', radius: 8, weight: 2 }, // blue circle + room: { color: '#16a34a', shape: 'square', radius: 9, weight: 2 }, // green square + sensor: { color: '#d97706', shape: 'triangle', radius: 8, weight: 2 }, // amber triangle }; const ROLE_LABELS = { repeater: 'Repeaters', companion: 'Companions', room: 'Room Servers', sensor: 'Sensors' }; - const ROLE_COLORS = { repeater: '#1d4ed8', companion: '#0369a1', room: '#6d28d9', sensor: '#92400e' }; + const ROLE_COLORS = { repeater: '#dc2626', companion: '#2563eb', room: '#16a34a', sensor: '#d97706' }; + + function makeMarkerIcon(role) { + const s = ROLE_STYLE[role] || ROLE_STYLE.companion; + const size = s.radius * 2 + 4; + const c = size / 2; + let path; + switch (s.shape) { + case 'diamond': + path = ``; + break; + case 'square': + path = ``; + break; + case 'triangle': + path = ``; + break; + default: // circle + path = ``; + } + const svg = `${path}`; + return L.divIcon({ + html: svg, + className: 'meshcore-marker', + iconSize: [size, size], + iconAnchor: [c, c], + popupAnchor: [0, -c], + }); + } function init(container) { container.innerHTML = ` @@ -243,7 +271,9 @@ const cbId = 'mcRole_' + role; const lbl = document.createElement('label'); lbl.setAttribute('for', cbId); - lbl.innerHTML = ` ${ROLE_LABELS[role]} (${count})`; + const shapeMap = { repeater: '◆', companion: '●', room: '■', sensor: '▲' }; + const shape = shapeMap[role] || '●'; + lbl.innerHTML = ` ${ROLE_LABELS[role]} (${count})`; lbl.querySelector('input').addEventListener('change', e => { filters[e.target.dataset.role] = e.target.checked; renderMarkers(); @@ -308,13 +338,9 @@ }); for (const node of filtered) { - const style = ROLE_STYLE[node.role] || ROLE_STYLE.companion; - const marker = L.circleMarker([node.lat, node.lon], { - radius: style.radius, - color: style.color, - fillColor: style.color, - fillOpacity: style.fill ? 0.8 : 0, - weight: style.weight, + const icon = makeMarkerIcon(node.role || 'companion'); + const marker = L.marker([node.lat, node.lon], { + icon, alt: `${node.name || 'Unknown'} (${node.role || 'node'})`, }); diff --git a/public/style.css b/public/style.css index a0983082..4868a639 100644 --- a/public/style.css +++ b/public/style.css @@ -1346,3 +1346,5 @@ tr[data-hops]:hover { background: rgba(59,130,246,0.1); } cursor: pointer; font-size: 16px; } + +.meshcore-marker { background: none !important; border: none !important; }