feat(mapa): data freshness marker + mobile responsive panels

- Mark consumo fields with asterisk in popup (INE 59532 refreshes with next Censo de Viviendas)
- Add footnote in map header explaining the pending update
- Collapse variables panel on mobile behind a toggle button
- Move legend below the map on mobile (out of overlay)
This commit is contained in:
2026-07-17 07:57:51 +02:00
parent 9bbcfc1e57
commit 60e28b5296
3 changed files with 53 additions and 12 deletions
+49 -12
View File
@@ -27,6 +27,7 @@ const popupTitleTmpl = t(lang, "map.popup_title");
{t(lang, "map.title")}
</h1>
<p class="text-xs sm:text-sm text-stone-600 leading-snug">{t(lang, "map.lead")}</p>
<p class="text-[10px] text-stone-500 mt-1 leading-snug italic">{t(lang, "map.consumo_footnote")}</p>
</div>
</section>
@@ -46,8 +47,19 @@ const popupTitleTmpl = t(lang, "map.popup_title");
{t(lang, "map.loading")}
</div>
<button
id="toggle-variables-panel"
type="button"
class="absolute top-3 left-3 z-30 sm:hidden bg-white/95 backdrop-blur border border-stone-200 rounded-lg shadow-sm px-3 py-2 text-xs font-medium text-stone-700 active:bg-stone-100 transition-colors"
aria-controls="variables-panel"
aria-expanded="false"
>
<span id="toggle-variables-panel-label">⚙ {t(lang, "map.layers_button")}</span>
</button>
<div
class="absolute top-3 left-3 z-20 bg-white/95 backdrop-blur border border-stone-200 rounded-lg shadow-sm p-3 w-64 max-w-[calc(100vw-1.5rem)] max-h-[calc(100%-1.5rem)] overflow-y-auto"
id="variables-panel"
class="absolute top-3 left-3 z-20 bg-white/95 backdrop-blur border border-stone-200 rounded-lg shadow-sm p-3 w-64 max-w-[calc(100vw-1.5rem)] max-h-[calc(100%-1.5rem)] overflow-y-auto hidden sm:block"
>
<div class="mb-2">
<p class="text-xs font-medium text-stone-700 mb-2">
@@ -100,15 +112,25 @@ const popupTitleTmpl = t(lang, "map.popup_title");
</div>
<div
class="absolute bottom-3 right-3 z-20 bg-white/95 backdrop-blur border border-stone-200 rounded-lg shadow-sm p-3 max-w-xs text-xs max-h-[calc(100%-1.5rem)] overflow-y-auto"
class="hidden sm:block absolute bottom-3 right-3 z-20 bg-white/95 backdrop-blur border border-stone-200 rounded-lg shadow-sm p-3 max-w-xs text-xs max-h-[calc(100%-1.5rem)] overflow-y-auto"
>
<p class="font-medium text-stone-800 mb-2">
{t(lang, "map.legend_title")}
</p>
<div id="legend-content"></div>
<div class="legend-content"></div>
</div>
</div>
<div
id="legend-flow"
class="sm:hidden mx-3 mt-3 mb-4 bg-white border border-stone-200 rounded-lg shadow-sm p-3 text-xs"
>
<p class="font-medium text-stone-800 mb-2">
{t(lang, "map.legend_title")}
</p>
<div class="legend-content"></div>
</div>
<script>
import maplibregl, { Popup } from "maplibre-gl";
import { Protocol } from "pmtiles";
@@ -465,8 +487,9 @@ const popupTitleTmpl = t(lang, "map.popup_title");
}
function updateLegend(cfg: typeof LAYERS[string]) {
const el = document.getElementById("legend-content");
if (!el) return;
const els = document.querySelectorAll<HTMLElement>(".legend-content");
if (!els.length) return;
const writeAll = (html: string) => els.forEach((e) => (e.innerHTML = html));
// Caso especial: la capa composta 'pressio_turistica' usa dos fill-colors
// distintos geográficamente (Airbnb en Mallorca/Menorca, HUT en Eivissa).
@@ -478,14 +501,14 @@ const popupTitleTmpl = t(lang, "map.popup_title");
const hutBar = LAYERS.hut_plazas.colors
.map((c) => `<span class="flex-1" style="background:${c}"></span>`)
.join("");
el.innerHTML = `
writeAll(`
<p class="font-semibold text-stone-700 mb-1">${escape(cfg.label)}</p>
<p class="text-stone-500 mb-2 leading-tight">${escape(cfg.desc)}</p>
<p class="text-xs font-medium text-stone-700 mb-1">Airbnb — Mallorca / Menorca</p>
<div class="flex items-center gap-0.5 h-2 rounded overflow-hidden mb-2">${airbnbBar}</div>
<p class="text-xs font-medium text-stone-700 mb-1">HUT — Eivissa</p>
<div class="flex items-center gap-0.5 h-2 rounded overflow-hidden mb-2">${hutBar}</div>
`;
`);
return;
}
@@ -499,7 +522,7 @@ const popupTitleTmpl = t(lang, "map.popup_title");
: `<span></span>`,
)
.join("");
el.innerHTML = `
writeAll(`
<p class="font-semibold text-stone-700 mb-1">${escape(cfg.label)}</p>
<p class="text-stone-500 mb-1 leading-tight">${escape(cfg.desc)}</p>
<div class="flex items-center gap-0.5 h-2 rounded overflow-hidden mb-0.5">${swatches}</div>
@@ -509,7 +532,7 @@ const popupTitleTmpl = t(lang, "map.popup_title");
<span class="text-[10px] text-stone-500">${escape(T.no_data)}</span>
</div>
<p class="text-[10px] text-stone-500 italic">${T.legend_applies_to}: ${escape(cfg.appliesTo)}</p>
`;
`);
}
map.on("load", () => {
@@ -564,6 +587,20 @@ const popupTitleTmpl = t(lang, "map.popup_title");
map.on("mouseleave", "airbnb-clusters", () => { map.getCanvas().style.cursor = ""; });
});
const toggleBtn = document.getElementById("toggle-variables-panel");
const variablesPanel = document.getElementById("variables-panel");
const toggleLabel = document.getElementById("toggle-variables-panel-label");
const openButtonText = "⚙ " + (lang === "es" ? "Capas" : "Capes");
const closeButtonText = "✕ " + (lang === "es" ? "Cerrar" : "Tanca");
if (toggleBtn && variablesPanel && toggleLabel) {
toggleBtn.addEventListener("click", () => {
const isOpen = !variablesPanel.classList.contains("hidden");
variablesPanel.classList.toggle("hidden");
toggleBtn.setAttribute("aria-expanded", String(!isOpen));
toggleLabel.textContent = !isOpen ? closeButtonText : openButtonText;
});
}
function openPopup(e: maplibregl.MapLayerMouseEvent, kind: "airbnb" | "hut" | "all") {
const f = e.features?.[0];
if (!f) return;
@@ -595,17 +632,17 @@ const popupTitleTmpl = t(lang, "map.popup_title");
hint: { ca: "estimació = 16,2% × habitatges (taxa CCAA)", es: "estimación = 16,2% × viviendas (tasa CCAA)" },
},
consumo_p10_kwh: {
label: { ca: "Consum p10", es: "Consumo p10" },
label: { ca: "Consum p10*", es: "Consumo p10*" },
unit: "kWh/any",
hint: { ca: "el 10% de llars d'aquesta secció consumeix menys", es: "el 10% de hogares de esta sección consume menos" },
},
consumo_p50_kwh: {
label: { ca: "Mediana consum", es: "Mediana consumo" },
label: { ca: "Mediana consum*", es: "Mediana consumo*" },
unit: "kWh/any",
hint: { ca: "mediana del consum anual de la secció", es: "mediana del consumo anual de la sección" },
},
ratio_p10_p50_consumo: {
label: { ca: "Ratio p10/p50", es: "Ratio p10/p50" },
label: { ca: "Ratio p10/p50*", es: "Ratio p10/p50*" },
hint: { ca: "<0,3 = quasi-vacant · >0,6 = ús estable", es: "<0,3 = casi vacía · >0,6 = uso estable" },
},
airbnb_listings: {
+2
View File
@@ -77,6 +77,8 @@
"variable_pressio_turistica": "Pressió turística",
"variable_pressio_turistica_desc": "Cada illa té la seva font oficial. Mallorca i Menorca → Airbnb (Inside Airbnb, juny 2026). Eivissa → HUT legal (Consell d'Eivissa, juliol 2026). Formentera → IBESTAT 2019 (única font pública disponible).",
"popup_source_ine": "Font: INE, Cens de Població i Vivendes 2021, taula 59531",
"layers_button": "Capes",
"consumo_footnote": "* Percentils de consum elèctric del Cens 2021. S'actualitzaran amb el proper Cens de Vivendes de l'INE (~3-4 anys).",
"popup_source_airbnb": "Font: Inside Airbnb (Mallorca i Menorca, juny 2026)",
"popup_source_hut_eivissa": "Font: Registre HUT, Consell d'Eivissa (juliol 2026)",
"popup_source_ibestat_formentera": "Font: IBESTAT, establiments turístics (2019, agregat illa)",
+2
View File
@@ -77,6 +77,8 @@
"variable_pressio_turistica": "Presión turística",
"variable_pressio_turistica_desc": "Cada isla tiene su fuente oficial. Mallorca y Menorca → Airbnb (Inside Airbnb, junio 2026). Eivissa → HUT legal (Consell d'Eivissa, julio 2026). Formentera → IBESTAT 2019 (única fuente pública disponible).",
"popup_source_ine": "Fuente: INE, Censo de Población y Viviendas 2021, tabla 59531",
"layers_button": "Capas",
"consumo_footnote": "* Percentiles de consumo eléctrico del Censo 2021. Se actualizarán con el próximo Censo de Viviendas del INE (~3-4 años).",
"popup_source_airbnb": "Fuente: Inside Airbnb (Mallorca y Menorca, junio 2026)",
"popup_source_hut_eivissa": "Fuente: Registro HUT, Consell d'Eivissa (julio 2026)",
"popup_source_ibestat_formentera": "Fuente: IBESTAT, establecimientos turísticos (2019, agregado isla)",