diff --git a/CHANGELOG.md b/CHANGELOG.md index a486845..f368e0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Added - **Custom 404/500 error pages** — styled error pages extending `base.html` with i18n support (EN/DE). The 404 page is context-aware: when the URL matches `/markets/{country}/{city}`, it shows a city-specific message with a link back to the country overview instead of a generic "page not found". -- **Map: city article indicators** — country overview map bubbles now differentiate cities with/without published articles. Cities without articles appear in muted gray and are not clickable, preventing dead-end navigations. The `/api/markets//cities.json` endpoint includes a `has_article` boolean per city. +- **Map: city article indicators** — country overview map bubbles now differentiate cities with/without published articles. All cities retain score-based colors (green/amber/red); non-article cities are visually receded with lower opacity, dashed borders, desaturated color, and default cursor (no click). Tooltips show scores for all cities — article cities get "Click to explore →", non-article cities get "Coming soon". The `/api/markets//cities.json` endpoint includes a `has_article` boolean per city. ### Fixed - **Admin template preview maps** — Leaflet maps rendered blank because `article-maps.js` called `L.divIcon()` at the IIFE top level before Leaflet was dynamically loaded, crashing the script. Moved `VENUE_ICON` creation into the `script.onload` callback so it runs after Leaflet is available. Previous commit's `.card` `overflow: visible` fix remains (clips tile layers otherwise). diff --git a/web/src/padelnomics/static/css/input.css b/web/src/padelnomics/static/css/input.css index 93e334b..f32fa83 100644 --- a/web/src/padelnomics/static/css/input.css +++ b/web/src/padelnomics/static/css/input.css @@ -892,6 +892,18 @@ transform: scale(1.1); } +/* Non-article city markers: faded + dashed border, no click affordance */ +.pn-marker--muted { + opacity: 0.45; + border: 2px dashed rgba(255,255,255,0.6); + cursor: default; + filter: saturate(0.7); +} +.pn-marker--muted:hover { + transform: none; + box-shadow: 0 2px 8px rgba(0,0,0,0.28); +} + /* Small fixed venue dot */ .pn-venue { width: 10px; diff --git a/web/src/padelnomics/static/js/article-maps.js b/web/src/padelnomics/static/js/article-maps.js index 57fa3b6..2767504 100644 --- a/web/src/padelnomics/static/js/article-maps.js +++ b/web/src/padelnomics/static/js/article-maps.js @@ -19,11 +19,12 @@ return '#DC2626'; } - function makeIcon(size, color) { + function makeIcon(size, color, muted) { var s = Math.round(size); + var cls = 'pn-marker' + (muted ? ' pn-marker--muted' : ''); return L.divIcon({ className: '', - html: '
', + html: '
', iconSize: [s, s], iconAnchor: [s / 2, s / 2], }); @@ -44,17 +45,20 @@ if (!c.lat || !c.lon) return; var size = 10 + 36 * Math.sqrt((c.padel_venue_count || 1) / maxV); var hasArticle = c.has_article !== false; - var color = hasArticle ? scoreColor(c.market_score) : '#9CA3AF'; + var color = scoreColor(c.market_score); var pop = c.population >= 1000000 ? (c.population / 1000000).toFixed(1) + 'M' : (c.population >= 1000 ? Math.round(c.population / 1000) + 'K' : (c.population || '')); var tip = '' + c.city_name + '
' + (c.padel_venue_count || 0) + ' venues' - + (pop ? ' · ' + pop : ''); + + (pop ? ' · ' + pop : '') + + '
Score ' + Math.round(c.market_score) + '/100'; if (hasArticle) { - tip += '
Score ' + Math.round(c.market_score) + '/100'; + tip += '
Click to explore →'; + } else { + tip += '
Coming soon'; } - var marker = L.marker([c.lat, c.lon], { icon: makeIcon(size, color) }) + var marker = L.marker([c.lat, c.lon], { icon: makeIcon(size, color, !hasArticle) }) .bindTooltip(tip, { className: 'map-tooltip', direction: 'top', offset: [0, -Math.round(size / 2)] }) .addTo(map); if (hasArticle) {