Files
internetforkids/layouts/shortcodes/world-map.html
Christian Gick a0b862acf4
All checks were successful
Deploy Internet for Kids / Build & Push (push) Successful in 17s
Deploy Internet for Kids / Deploy (push) Successful in 6s
Deploy Internet for Kids / Health Check (push) Successful in 2s
Deploy Internet for Kids / Smoke Tests (push) Successful in 3s
Deploy Internet for Kids / IndexNow Ping (push) Successful in 8s
Deploy Internet for Kids / Promote to Latest (push) Successful in 2s
Deploy Internet for Kids / Rollback (push) Has been skipped
Deploy Internet for Kids / Audit (push) Successful in 2s
fix: switch map from MapLibre GL to Leaflet SVG, eliminates tile seams
MapLibre GL WebGL renderer tiles all sources internally, causing
visible seam lines on retina displays. Leaflet renders GeoJSON as
SVG paths in a single layer - zero tiling, zero seams.

Also replaces child-safety-map with world-map on all homepages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 08:37:10 +03:00

163 lines
7.8 KiB
HTML

{{ $lang := .Page.Language.Lang }}
{{ $countries := hugo.Data.countries }}
<link rel="stylesheet" href="{{ "css/leaflet.min.css" | relURL }}" />
<style>
.ifk-map-wrap { max-width: 100%; margin: 2rem 0; }
.ifk-map-wrap h3 { text-align: center; margin-bottom: 0.5rem; font-size: 1.25rem; }
.ifk-map-subtitle { text-align: center; color: #666; font-size: 0.85rem; margin-bottom: 1rem; }
#ifk-world-map { width: 100%; height: 420px; border-radius: 8px; background: #dce4ec; }
.ifk-map-legend {
display: flex; flex-wrap: wrap; gap: 1rem; justify-content: center;
margin-top: 1rem; font-size: 0.8rem;
}
.ifk-map-legend-item { display: flex; align-items: center; gap: 0.35rem; }
.ifk-map-legend-swatch { width: 14px; height: 14px; border-radius: 3px; border: 1px solid rgba(0,0,0,0.1); }
.ifk-popup-status { display: inline-block; padding: 0.15rem 0.5rem; border-radius: 4px; font-weight: 600; font-size: 0.8rem; color: white; margin: 0.3rem 0; }
.ifk-popup-age { display: flex; align-items: center; gap: 0.35rem; margin: 0.3rem 0; font-size: 0.8rem; color: #555; }
.ifk-popup-detail { margin: 0.4rem 0; font-size: 0.82rem; color: #444; }
.ifk-popup-link { display: inline-block; margin-top: 0.4rem; font-size: 0.8rem; color: #667eea; text-decoration: none; font-weight: 600; }
.ifk-popup-link:hover { text-decoration: underline; }
@media (max-width: 640px) { #ifk-world-map { height: 280px; } }
</style>
<div class="ifk-map-wrap">
<h3>{{ if eq $lang "de" }}Interaktive Weltkarte: Kinderschutzgesetze{{ else if eq $lang "fr" }}Carte interactive : lois de protection de l'enfance{{ else }}Interactive Map: Child Protection Laws{{ end }}</h3>
<p class="ifk-map-subtitle">{{ if eq $lang "de" }}Klicken Sie auf ein Land für Details{{ else if eq $lang "fr" }}Cliquez sur un pays pour plus de détails{{ else }}Click a country for details{{ end }}</p>
<div id="ifk-world-map"></div>
<div class="ifk-map-legend">
<div class="ifk-map-legend-item"><div class="ifk-map-legend-swatch" style="background:#667eea"></div>{{ if eq $lang "de" }}In Kraft{{ else if eq $lang "fr" }}En vigueur{{ else }}Enforced{{ end }}</div>
<div class="ifk-map-legend-item"><div class="ifk-map-legend-swatch" style="background:#764ba2"></div>{{ if eq $lang "de" }}Verabschiedet{{ else if eq $lang "fr" }}Adopté{{ else }}Passed{{ end }}</div>
<div class="ifk-map-legend-item"><div class="ifk-map-legend-swatch" style="background:#a5b4fc"></div>{{ if eq $lang "de" }}In Bearbeitung{{ else if eq $lang "fr" }}En cours{{ else }}In Progress{{ end }}</div>
<div class="ifk-map-legend-item"><div class="ifk-map-legend-swatch" style="background:#e0e7ff"></div>{{ if eq $lang "de" }}Richtlinien{{ else if eq $lang "fr" }}Directives{{ else }}Guidelines{{ end }}</div>
<div class="ifk-map-legend-item"><div class="ifk-map-legend-swatch" style="background:#e2e8f0"></div>{{ if eq $lang "de" }}Keine Daten{{ else if eq $lang "fr" }}Pas de données{{ else }}No Data{{ end }}</div>
</div>
</div>
<script src="{{ "js/leaflet.min.js" | relURL }}"></script>
<script src="{{ "js/topojson-client.min.js" | relURL }}"></script>
<script>
(function() {
var LANG = "{{ $lang }}";
var STATUS_COLORS = { enforced: '#667eea', passed: '#764ba2', progress: '#a5b4fc', guidelines: '#e0e7ff' };
var STATUS_LABELS = {
en: { enforced: 'Enforced', passed: 'Passed', progress: 'In Progress', guidelines: 'Guidelines' },
de: { enforced: 'In Kraft', passed: 'Verabschiedet', progress: 'In Bearbeitung', guidelines: 'Richtlinien' },
fr: { enforced: 'En vigueur', passed: 'Adopté', progress: 'En cours', guidelines: 'Directives' }
};
var AGE_LABELS = { en: 'Min. social media age', de: 'Min. Social-Media-Alter', fr: 'Âge min. réseaux sociaux' };
var ARTICLE_ANCHORS = {
AUS: 'australia', DEU: 'germany', USA: 'the-united-states',
FRA: 'france', BRA: 'brazil'
};
if (LANG === 'de') {
ARTICLE_ANCHORS.AUS = 'australien';
ARTICLE_ANCHORS.USA = 'die-vereinigten-staaten';
ARTICLE_ANCHORS.DEU = 'deutschland';
ARTICLE_ANCHORS.BRA = 'brasilien';
}
var countries = {{ $countries | jsonify | safeJS }};
var byIsoNum = {};
countries.forEach(function(c) { byIsoNum[c.isoNum] = c; });
var map = L.map('ifk-world-map', {
center: [20, 20],
zoom: 2,
minZoom: 2,
maxZoom: 6,
scrollWheelZoom: false,
attributionControl: false,
zoomControl: false
});
L.control.zoom({ position: 'topright' }).addTo(map);
// Enable scroll zoom in fullscreen
document.addEventListener('fullscreenchange', function() {
if (document.fullscreenElement) map.scrollWheelZoom.enable();
else map.scrollWheelZoom.disable();
});
// Fullscreen button
var FsControl = L.Control.extend({
options: { position: 'topright' },
onAdd: function() {
var btn = L.DomUtil.create('div', 'leaflet-bar');
var a = L.DomUtil.create('a', '', btn);
a.href = '#'; a.title = 'Fullscreen'; a.setAttribute('role', 'button');
a.innerHTML = '<svg width="14" height="14" viewBox="0 0 14 14"><path d="M2 9H0v5h5v-2H2V9zm0-4h3V3H2V0H0v5zm10 7H9v2h5V9h-2v3zM9 0v2h3v3h2V0H9z" fill="currentColor"/></svg>';
a.style.cssText = 'display:flex;align-items:center;justify-content:center;width:30px;height:30px;';
L.DomEvent.on(a, 'click', function(e) {
L.DomEvent.stop(e);
var el = document.getElementById('ifk-world-map');
if (!document.fullscreenElement) el.requestFullscreen();
else document.exitFullscreen();
});
return btn;
}
});
new FsControl().addTo(map);
var hoveredLayer = null;
function resetStyle(layer) {
layer.setStyle({ weight: 0.5, color: '#94a3b8' });
}
function highlightStyle(layer) {
layer.setStyle({ weight: 2.5, color: '#667eea' });
if (!L.Browser.ie && !L.Browser.opera && !L.Browser.edge) layer.bringToFront();
}
fetch('{{ "data/countries-50m.json" | relURL }}')
.then(function(r) { return r.json(); })
.then(function(topo) {
var geo = topojson.feature(topo, topo.objects.countries);
L.geoJSON(geo, {
style: function(feature) {
var id = String(feature.id).padStart(3, '0');
var c = byIsoNum[id];
return {
fillColor: c ? STATUS_COLORS[c.status] : '#e2e8f0',
fillOpacity: c ? 0.65 : 0.15,
weight: 0.5,
color: '#94a3b8'
};
},
onEachFeature: function(feature, layer) {
var id = String(feature.id).padStart(3, '0');
var c = byIsoNum[id];
if (!c) return;
layer.on('mouseover', function() {
highlightStyle(layer);
hoveredLayer = layer;
});
layer.on('mouseout', function() {
resetStyle(layer);
hoveredLayer = null;
});
layer.on('click', function(e) {
var loc = c[LANG] || c.en;
var labels = STATUS_LABELS[LANG] || STATUS_LABELS.en;
var ageLabel = AGE_LABELS[LANG] || AGE_LABELS.en;
var html = '<strong>' + c.flag + ' ' + loc.name + '</strong><br>' +
'<em>' + loc.law + '</em><br>' +
'<span class="ifk-popup-status" style="background:' + STATUS_COLORS[c.status] + '">' + labels[c.status] + ' (' + c.year + ')</span>' +
'<div class="ifk-popup-age">&#x1F464; ' + ageLabel + ': <strong>' + c.ageLimitSocial + '+</strong></div>' +
'<div class="ifk-popup-detail">' + loc.detail + '</div>';
var anchor = ARTICLE_ANCHORS[c.iso3];
if (anchor) {
var readMore = { en: 'Read more &darr;', de: 'Weiterlesen &darr;', fr: 'En savoir plus &darr;' };
html += '<a class="ifk-popup-link" href="#' + anchor + '">' + (readMore[LANG] || readMore.en) + '</a>';
}
L.popup({ maxWidth: 280 }).setLatLng(e.latlng).setContent(html).openOn(map);
});
}
}).addTo(map);
});
})();
</script>