Files
internetforkids/layouts/shortcodes/law-charts.html
Christian Gick 308034e40d
All checks were successful
Deploy Internet for Kids / Build & Push (push) Successful in 9s
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 4s
Deploy Internet for Kids / IndexNow Ping (push) Successful in 8s
Deploy Internet for Kids / Promote to Latest (push) Successful in 1s
Deploy Internet for Kids / Rollback (push) Has been skipped
Deploy Internet for Kids / Audit (push) Successful in 2s
feat(IFK-9): interactive world map, charts, stats banner, timeline
- Add centralized data/countries.json with all 17 countries (EN/DE/FR)
- Add Leaflet choropleth world map with TopoJSON boundaries
- Add stats banner shortcode (enforced/passed/progress/guidelines counts)
- Add Chart.js charts (status donut, age limits bar, legislation timeline)
- Add Mermaid timeline of legislation milestones 2020-2026
- Refactor child-safety-map table to use data file (DRY)
- Update all 3 language articles (EN/DE/FR)

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

125 lines
5.1 KiB
HTML

{{ $lang := .Page.Language.Lang }}
{{ $countries := hugo.Data.countries }}
<style>
.ifk-charts { margin: 2rem 0; }
.ifk-charts h3 { text-align: center; margin-bottom: 1.5rem; font-size: 1.25rem; }
.ifk-chart-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; }
.ifk-chart-card {
background: #fafbfc; border: 1px solid #e2e8f0; border-radius: 10px;
padding: 1.25rem; text-align: center;
}
.ifk-chart-card h4 { margin: 0 0 1rem; font-size: 0.95rem; color: #334155; }
.ifk-chart-card canvas { max-height: 280px; }
@media (max-width: 640px) {
.ifk-chart-grid { grid-template-columns: 1fr; }
}
</style>
<div class="ifk-charts">
<h3>{{ if eq $lang "de" }}Analyse der Kinderschutzgesetze{{ else if eq $lang "fr" }}Analyse des lois de protection{{ else }}Child Protection Law Analysis{{ end }}</h3>
<div class="ifk-chart-grid">
<div class="ifk-chart-card">
<h4>{{ if eq $lang "de" }}Gesetzgebungsstatus{{ else if eq $lang "fr" }}Statut législatif{{ else }}Legislative Status{{ end }}</h4>
<canvas id="ifk-chart-status"></canvas>
</div>
<div class="ifk-chart-card">
<h4>{{ if eq $lang "de" }}Mindestalter für soziale Medien{{ else if eq $lang "fr" }}Âge minimum réseaux sociaux{{ else }}Social Media Age Limits{{ end }}</h4>
<canvas id="ifk-chart-age"></canvas>
</div>
<div class="ifk-chart-card" style="grid-column: 1 / -1;">
<h4>{{ if eq $lang "de" }}Zeitachse der Gesetzgebung{{ else if eq $lang "fr" }}Chronologie législative{{ else }}Legislation Timeline{{ end }}</h4>
<canvas id="ifk-chart-timeline"></canvas>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script>
<script>
(function() {
var LANG = {{ $lang }};
var countries = {{ $countries | jsonify }};
var statusLabels = {
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 labels = statusLabels[LANG] || statusLabels.en;
/* Pie: status distribution */
var counts = { enforced: 0, passed: 0, progress: 0, guidelines: 0 };
countries.forEach(function(c) { counts[c.status]++; });
new Chart(document.getElementById('ifk-chart-status'), {
type: 'doughnut',
data: {
labels: [labels.enforced, labels.passed, labels.progress, labels.guidelines],
datasets: [{
data: [counts.enforced, counts.passed, counts.progress, counts.guidelines],
backgroundColor: ['#1e40af', '#3b82f6', '#93c5fd', '#dbeafe'],
borderWidth: 2, borderColor: '#fff'
}]
},
options: {
responsive: true,
plugins: {
legend: { position: 'bottom', labels: { padding: 16, usePointStyle: true } }
}
}
});
/* Horizontal bar: age limits */
var sorted = countries.slice().sort(function(a, b) { return b.ageLimitSocial - a.ageLimitSocial; });
var colors = { enforced: '#1e40af', passed: '#3b82f6', progress: '#93c5fd', guidelines: '#dbeafe' };
new Chart(document.getElementById('ifk-chart-age'), {
type: 'bar',
data: {
labels: sorted.map(function(c) { return c.flag + ' ' + (c[LANG] || c.en).name; }),
datasets: [{
label: LANG === 'de' ? 'Mindestalter' : LANG === 'fr' ? 'Âge minimum' : 'Min. Age',
data: sorted.map(function(c) { return c.ageLimitSocial; }),
backgroundColor: sorted.map(function(c) { return colors[c.status]; }),
borderRadius: 4
}]
},
options: {
indexAxis: 'y', responsive: true,
scales: {
x: { min: 0, max: 20, title: { display: true, text: LANG === 'de' ? 'Jahre' : LANG === 'fr' ? 'Ans' : 'Years' } }
},
plugins: { legend: { display: false } }
}
});
/* Bar: timeline by year */
var yearCounts = {};
countries.forEach(function(c) {
if (!yearCounts[c.year]) yearCounts[c.year] = { enforced: 0, passed: 0, progress: 0, guidelines: 0 };
yearCounts[c.year][c.status]++;
});
var years = Object.keys(yearCounts).sort();
new Chart(document.getElementById('ifk-chart-timeline'), {
type: 'bar',
data: {
labels: years,
datasets: [
{ label: labels.enforced, data: years.map(function(y) { return yearCounts[y].enforced; }), backgroundColor: '#1e40af', borderRadius: 4 },
{ label: labels.passed, data: years.map(function(y) { return yearCounts[y].passed; }), backgroundColor: '#3b82f6', borderRadius: 4 },
{ label: labels.progress, data: years.map(function(y) { return yearCounts[y].progress; }), backgroundColor: '#93c5fd', borderRadius: 4 },
{ label: labels.guidelines, data: years.map(function(y) { return yearCounts[y].guidelines; }), backgroundColor: '#dbeafe', borderRadius: 4 }
]
},
options: {
responsive: true,
scales: {
x: { stacked: true }, y: { stacked: true, beginAtZero: true, ticks: { stepSize: 1 } }
},
plugins: { legend: { position: 'bottom', labels: { padding: 16, usePointStyle: true } } }
}
});
})();
</script>