// World map web component for brewlog stats page. // Map data: Al MacDonald, edited by Fritz Lekschas. License: CC BY-SA 3.0 const WORLD_SVG = ` Author: Al MacDonald Editor: Fritz Lekschas License: CC BY-SA 3.0 ID: ISO 3166-1 or "_[a-zA-Z]" if an ISO code is not available`; customElements.define("world-map", class extends HTMLElement { static get observedAttributes() { return ["data-countries", "data-max", "data-selected"]; } connectedCallback() { this._scheduleRender(); this._themeObserver = new MutationObserver(() => requestAnimationFrame(() => this._recolor())); this._themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] }); } disconnectedCallback() { this._themeObserver?.disconnect(); this._themeObserver = null; } attributeChangedCallback(name) { if (!this.isConnected) return; if (name === "data-selected") { this._recolor(); } else { this._scheduleRender(); } } _scheduleRender() { if (this._pendingRender) return; this._pendingRender = true; requestAnimationFrame(() => { this._pendingRender = false; this._render(); }); } _parseCountries() { const attr = this.getAttribute("data-countries") || ""; const counts = new Map(); if (attr) { attr.split(",").forEach((pair) => { const [code, count] = pair.split(":"); if (code && count) counts.set(code.trim().toLowerCase(), parseInt(count, 10)); }); } this._counts = counts; this._max = parseInt(this.getAttribute("data-max") || "1", 10) || 1; } _render() { this._parseCountries(); this.innerHTML = ""; const container = document.createElement("div"); container.innerHTML = WORLD_SVG; const svg = container.querySelector("svg"); if (!svg) return; svg.style.width = "100%"; svg.style.height = "auto"; svg.style.display = "block"; this.appendChild(svg); this._recolor(); } _recolor() { const svg = this.querySelector("svg"); if (!svg) return; if (!this._counts) this._parseCountries(); const selected = (this.getAttribute("data-selected") || "").toLowerCase(); const counts = this._counts; const max = this._max; const styles = getComputedStyle(document.documentElement); const rgb = styles.getPropertyValue('--highlight-rgb').trim() || '185, 28, 28'; const borderColor = styles.getPropertyValue('--text-muted').trim() || '#9ca3af'; const surfaceAlt = styles.getPropertyValue('--surface-alt').trim() || '#f5f5f4'; const borderMuted = styles.getPropertyValue('--border').trim() || '#d6d3d1'; const applyStyle = (el, fill) => { el.style.fill = fill; el.style.stroke = borderColor; el.style.strokeWidth = "0.3"; }; const colorFor = (code) => { const count = counts.get(code); if (selected) { if (code === selected && count) return `rgba(${rgb}, 1)`; return count ? borderMuted : surfaceAlt; } if (count) { const alpha = (0.15 + 0.85 * (count / max)).toFixed(2); return `rgba(${rgb}, ${alpha})`; } return surfaceAlt; }; svg.querySelectorAll("path[id], g[id]").forEach((el) => { const code = el.id.toLowerCase(); const fill = colorFor(code); if (el.tagName === "g") { el.querySelectorAll("path").forEach((p) => applyStyle(p, fill)); } else { applyStyle(el, fill); } }); } });