From 6b3aae2c995eb7ae03c6d7d2ea102b799cd111da Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Wed, 30 Sep 2026 08:30:57 +0000 Subject: [PATCH 01/12] Phones: smaller all-regions pins with the same tap targets, and a poster size a phone can draw The all-regions map drew its pins at their zoom-2 size below zoom 2, which is where a phone's globe sits, so they read far too large. They now shrink below zoom 2 (unchanged from zoom 2 up, so desktop keeps them), and an invisible hit twin with the old radii takes the click and the cursor, so a tap selects exactly what it did before. The poster dialog's default is 24x36 in at 300 dpi, a 78-megapixel render: minutes on a desktop, and beyond a phone's canvas limit, so "Export poster" appeared to do nothing on a phone. On a phone it now opens on Letter at 150 dpi, and sizes or resolutions over the phone canvas limit are disabled with the reason shown, in every locale. Desktop is unchanged. --- library/components/maps/RegionCountryMap.vue | 30 +- library/components/poster/PosterDialog.vue | 26 +- library/i18n/locales/ar/poster.json | 3 +- library/i18n/locales/de/poster.json | 3 +- library/i18n/locales/en/poster.json | 3 +- library/i18n/locales/es/poster.json | 3 +- library/i18n/locales/fr/poster.json | 3 +- library/i18n/locales/hi/poster.json | 3 +- library/i18n/locales/it/poster.json | 3 +- library/i18n/locales/pt/poster.json | 3 +- library/i18n/locales/ro/poster.json | 3 +- library/i18n/locales/ru/poster.json | 3 +- library/i18n/locales/zh/poster.json | 3 +- public/dx-maps/country-map/country-map.js | 34 +- .../country-map/country-map/index.html | 2 +- .../country-posters/countries-map/index.html | 2 +- .../country-poster-wall/index.html | 2 +- .../country-posters-mfe/index.html | 2 +- .../country-posters/country-posters.js | 310 +++++++++--------- public/dx-maps/doxa-maps-spa/manifest.json | 30 +- public/dx-maps/manifest.json | 30 +- .../region-dashboards/index.html | 2 +- .../regions-dashboard/regions-dashboard.js | 112 +++---- .../regions-dashboard/regions-list/index.html | 2 +- .../regions-dashboard/regions-mfe/index.html | 2 +- 25 files changed, 331 insertions(+), 288 deletions(-) diff --git a/library/components/maps/RegionCountryMap.vue b/library/components/maps/RegionCountryMap.vue index ba0c8459e..2f28850e2 100644 --- a/library/components/maps/RegionCountryMap.vue +++ b/library/components/maps/RegionCountryMap.vue @@ -258,7 +258,13 @@ function pinExpressions() { // visible loss of contrast and buys nothing when nothing is being de-emphasised yet. // The HOVER state is deliberately untouched — dimming the rest to 0.25 is the design saying // "these pins are one group", which is a taste call rather than a legibility defect. + // ★ SMALLER BELOW ZOOM 2 (2026-09-30: on a phone the globe sits below zoom 2 and the dots, clamped at their + // zoom-2 size, read as far too large). From zoom 2 up nothing changes, so the desktop chooser keeps its dots. radius: lit + ? ['case', isLit, ['interpolate', ['linear'], ['zoom'], 0, 2.5, 1, 3, 2, 4.5, 6, 8], ['interpolate', ['linear'], ['zoom'], 0, 1.6, 1, 2, 2, 3, 6, 5.5]] + : ['interpolate', ['linear'], ['zoom'], 0, 2, 1, 2.5, 2, 4, 6, 7.5], + // THE TAP TARGET KEEPS ITS SIZE: the invisible rcm-pins-hit twin (addLayers) keeps the ramps the dots had. + hitRadius: lit ? ['case', isLit, ['interpolate', ['linear'], ['zoom'], 2, 4.5, 6, 8], ['interpolate', ['linear'], ['zoom'], 2, 3, 6, 5.5]] : ['interpolate', ['linear'], ['zoom'], 2, 4, 6, 7.5], } @@ -274,13 +280,14 @@ function pinExpressions() { opacity: ['case', ['get', 'inCountry'], 1, 0.75], strokeOpacity: 1, // theirs, unchanged (CountryMap.vue) — only the chooser's dim is ours radius: ['interpolate', ['linear'], ['zoom'], 2, 3, 6, 6.5], + hitRadius: ['interpolate', ['linear'], ['zoom'], 2, 3, 6, 6.5], } } function addLayers() { const m = map.value if (!m) return - const { color: pinColor, sortKey: pinSortKey, opacity: pinOpacity, radius: pinRadius, strokeOpacity: pinStrokeOpacity } = pinExpressions() + const { color: pinColor, sortKey: pinSortKey, opacity: pinOpacity, radius: pinRadius, strokeOpacity: pinStrokeOpacity, hitRadius: pinHitRadius } = pinExpressions() if (!m.getSource(SRC_COUNTRIES)) { // ── COUNTRY POLYGONS, PER PROVIDER ──────────────────────────────────────────────────────── @@ -358,9 +365,18 @@ function addLayers() { 'circle-stroke-opacity': pinStrokeOpacity, }, }) - m.on('click', 'rcm-pins', onPinClick) - m.on('mouseenter', 'rcm-pins', () => { m.getCanvas().style.cursor = 'pointer' }) - m.on('mouseleave', 'rcm-pins', () => { m.getCanvas().style.cursor = '' }) + // ★ AN INVISIBLE HIT TWIN (the pattern UPG-100's pins use): the engines hit-test a circle at its radius + + // stroke, so shrinking the drawn dot would shrink the tap target with it. The twin keeps the old ramps, the + // same sort key (so a tap picks the same pin as before), and both opacities 0, which skips the draw but + // keeps the features queryable. The click and the cursor bind to it. + m.addLayer({ + id: 'rcm-pins-hit', type: 'circle', source: SRC_PINS, + layout: { 'circle-sort-key': pinSortKey }, + paint: { 'circle-radius': pinHitRadius, 'circle-stroke-width': 1, 'circle-opacity': 0, 'circle-stroke-opacity': 0 }, + }) + m.on('click', 'rcm-pins-hit', onPinClick) + m.on('mouseenter', 'rcm-pins-hit', () => { m.getCanvas().style.cursor = 'pointer' }) + m.on('mouseleave', 'rcm-pins-hit', () => { m.getCanvas().style.cursor = '' }) } } @@ -768,12 +784,16 @@ function applySubject() { if (src) src.setData(geojson.value) // Re-apply the MODE, not just the data — see pinExpressions(). if (m.getLayer('rcm-pins')) { - const { color, sortKey, opacity, radius, strokeOpacity } = pinExpressions() + const { color, sortKey, opacity, radius, strokeOpacity, hitRadius } = pinExpressions() m.setPaintProperty('rcm-pins', 'circle-color', color) m.setPaintProperty('rcm-pins', 'circle-opacity', opacity) m.setPaintProperty('rcm-pins', 'circle-stroke-opacity', strokeOpacity) m.setPaintProperty('rcm-pins', 'circle-radius', radius) m.setLayoutProperty('rcm-pins', 'circle-sort-key', sortKey) + if (m.getLayer('rcm-pins-hit')) { + m.setPaintProperty('rcm-pins-hit', 'circle-radius', hitRadius) + m.setLayoutProperty('rcm-pins-hit', 'circle-sort-key', sortKey) + } } for (const id of ['rcm-focus-fill', 'rcm-focus-outline']) { if (m.getLayer(id)) m.setFilter(id, countryFilter()) diff --git a/library/components/poster/PosterDialog.vue b/library/components/poster/PosterDialog.vue index 730ad0d49..a34ae2bac 100644 --- a/library/components/poster/PosterDialog.vue +++ b/library/components/poster/PosterDialog.vue @@ -32,9 +32,10 @@ +

{{ T('poster.phoneLimit', "On a phone, the largest sizes are off: a phone can't draw a poster that big. Use a computer for them.") }}