feat(IFK-9): interactive world map, charts, stats banner, timeline
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
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
- 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>
This commit is contained in:
124
layouts/shortcodes/law-charts.html
Normal file
124
layouts/shortcodes/law-charts.html
Normal file
@@ -0,0 +1,124 @@
|
||||
{{ $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>
|
||||
Reference in New Issue
Block a user