← back to Model Arena
data/artifacts/0ffd60ff20d4/qwen3-14b.html
395 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Flooded City: Scrollytelling</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html, body {
height: 100%;
font-family: 'Helvetica Neue', sans-serif;
background: #e0f7fa;
color: #0d47a1;
overflow-x: hidden;
}
body {
display: flex;
flex-direction: column;
}
header {
padding: 20px 40px;
background: #0d47a1;
color: white;
font-size: 2rem;
text-align: center;
}
main {
flex: 1;
display: flex;
flex-direction: column;
padding: 40px;
overflow-y: auto;
}
.scrollytelling {
position: relative;
min-height: 400px;
background: #f5f5f5;
border: 2px solid #ccc;
padding: 20px;
border-radius: 8px;
}
.section {
position: relative;
min-height: 300px;
margin-bottom: 20px;
}
.slider-container {
position: relative;
margin: 30px 0;
}
input[type=range] {
width: 100%;
height: 8px;
background: #ccc;
border-radius: 5px;
outline: none;
cursor: pointer;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
background: #0d47a1;
border-radius: 50%;
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
}
input[type=range]::-moz-range-thumb {
width: 20px;
height: 20px;
background: #0d47a1;
border-radius: 50%;
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
}
.chart {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.95);
border: 2px solid #ccc;
padding: 20px;
border-radius: 8px;
transition: opacity 0.5s;
opacity: 0;
pointer-events: none;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2rem;
}
.chart.visible {
opacity: 1;
pointer-events: auto;
}
.annotation {
position: absolute;
bottom: 10px;
left: 10px;
background: rgba(255,255,255,0.95);
padding: 10px;
border-radius: 6px;
font-size: 0.9rem;
}
.footer {
text-align: center;
font-size: 0.9rem;
padding: 20px;
color: #555;
}
.defense-interactive {
position: relative;
margin: 40px auto;
max-width: 800px;
min-height: 200px;
background: #f0f0f0;
border: 2px dashed #0d47a1;
padding: 20px;
border-radius: 12px;
}
.defense-item {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.defense-item label {
flex: 1;
font-size: 1rem;
cursor: pointer;
}
.defense-item input {
margin-left: 10px;
}
.defense-chart {
margin-top: 30px;
height: 200px;
background: #f5f5f5;
border: 2px solid #ccc;
border-radius: 8px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.1rem;
}
.interactive-section {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
pointer-events: auto;
}
.interactive-section h3 {
margin-bottom: 20px;
}
.interactive-section .buttons {
display: flex;
gap: 10px;
flex-wrap: wrap;
justify-content: center;
}
.interactive-section .buttons button {
padding: 10px 20px;
font-size: 1rem;
border: 2px solid #0d47a1;
background: #0d47a1;
color: white;
border-radius: 6px;
cursor: pointer;
transition: background 0.3s;
}
.interactive-section .buttons button:hover {
background: #003399;
}
.interactive-section .empty {
display: none;
}
.interactive-section .loading {
display: none;
}
.interactive-section .complete {
display: none;
}
.interactive-section input:focus,
.interactive-section button:focus {
outline: 2px solid #0d47a1;
}
.no-preference {
animation: none;
}
</style>
</head>
<body>
<header>Flooded City: Interactive Scrollytelling</header>
<main>
<div class="scrollytelling">
<div class="section" id="section0">
<h2>Before the Flood</h2>
<p>Meet Eldoria, a coastal city of 1.2 million, known for its historic architecture, vibrant culture, and bustling port.</p>
<div class="slider-container">
<input type="range" id="floodSlider" min="0" max="100" value="0" step="1">
</div>
</div>
<div class="section" id="section1">
<h2>10% Sea Level Rise</h2>
<p>Minor flooding begins in the coastal districts. Roads in the downtown area are affected by high tides.</p>
<div class="chart" id="chart1" style="opacity: 0">
<div>
<strong>Flooding Impact:</strong> 2% of land area <br>
<strong>Displaced Residents:</strong> 20,000 <br>
<strong>Infrastructure Damage:</strong> $50 million
</div>
</div>
</div>
<div class="section" id="section2">
<h2>30% Sea Level Rise</h2>
<p>Storm surges and regular high tides inundate low-lying areas. The city begins emergency water management operations.</p>
<div class="chart" id="chart2" style="opacity: 0">
<div>
<strong>Flooding Impact:</strong> 12% of land area <br>
<strong>Displaced Residents:</strong> 150,000 <br>
<strong>Infrastructure Damage:</strong> $350 million
</div>
</div>
</div>
<div class="section" id="section3">
<h2>50% Sea Level Rise</h2>
<p>Major portions of the city are underwater. Evacuations become frequent and widespread. The city's power grid is compromised.</p>
<div class="chart" id="chart3" style="opacity: 0">
<div>
<strong>Flooding Impact:</strong> 30% of land area <br>
<strong>Displaced Residents:</strong> 600,000 <br>
<strong>Infrastructure Damage:</strong> $1.3 billion
</div>
</div>
</div>
<div class="section" id="section4">
<h2>80% Sea Level Rise</h2>
<p>Only the highest ground remains habitable. The city is effectively abandoned. A few thousand residents remain in elevated buildings.</p>
<div class="chart" id="chart4" style="opacity: 0">
<div>
<strong>Flooding Impact:</strong> 85% of land area <br>
<strong>Displaced Residents:</strong> 1.1 million <br>
<strong>Infrastructure Damage:</strong> $3 billion
</div>
</div>
</div>
<div class="section" id="section5">
<h2>Defense Interactive</h2>
<p>Now, you get to choose: what defenses can you build to save Eldoria from rising seas?</p>
<div class="defense-interactive">
<div class="interactive-section">
<h3>Choose your defenses</h3>
<div class="buttons">
<button onclick="addDefense('Sea Walls')">Sea Walls</button>
<button onclick="addDefense('Levees')">Levees</button>
<button onclick="addDefense('Floodgates')">Floodgates</button>
<button onclick="addDefense('Dams')">Dams</button>
</div>
<div id="defenseList" class="defense-list">
<p class="empty">No defenses chosen yet. Try selecting some to see their impact.</p>
</div>
<div class="defense-chart" id="defenseChart">
<p class="empty">Impact chart will appear here once you choose a defense.</p>
</div>
</div>
</div>
</div>
</div>
<div class="footer">
© 2025 Data Journalism. All rights reserved. <br>
Built for demonstration only. <br>
Press Esc to exit reduced motion mode (if enabled).
</div>
</main>
<script>
const slider = document.getElementById("floodSlider");
const sections = Array.from(document.querySelectorAll(".section"));
const charts = {
0: null,
1: document.getElementById("chart1"),
2: document.getElementById("chart2"),
3: document.getElementById("chart3"),
4: document.getElementById("chart4"),
};
let scrollState = 0;
// Scroll and resize tracking
function updateChart() {
const value = parseInt(slider.value);
scrollState = Math.min(Math.max(Math.floor(value * 0.05), 0), 4);
sections.forEach((section, i) => {
if (i === scrollState) {
section.classList.add("active");
} else {
section.classList.remove("active");
}
});
Object.values(charts).forEach(chart => {
if (chart) {
chart.classList.remove("visible");
}
});
if (charts[scrollState]) {
charts[scrollState].classList.add("visible");
}
}
slider.addEventListener("input", updateChart);
window.addEventListener("resize", updateChart);
window.addEventListener("load", updateChart);
// Defense interactive state
const defenseList = document.getElementById("defenseList");
const defenseChart = document.getElementById("defenseChart");
const defenseItems = [];
function addDefense(type) {
const found = defenseItems.find(d => d.type === type);
if (found) {
found.count++;
} else {
defenseItems.push({ type, count: 1 });
}
updateDefenseList();
updateDefenseChart();
}
function updateDefenseList() {
if (defenseItems.length === 0) {
defenseList.innerHTML = '<p class="empty">No defenses chosen yet. Try selecting some to see their impact.</p>';
defenseChart.innerHTML = '<p class="empty">Impact chart will appear here once you choose a defense.</p>';
} else {
defenseList.innerHTML = '<h4>Selected Defenses:</h4>';
defenseItems.forEach(item => {
const div = document.createElement("div");
div.classList.add("defense-item");
div.innerHTML = `<label>${item.type} (${item.count})</label><input type="checkbox" checked disabled>`;
defenseList.appendChild(div);
});
}
}
function updateDefenseChart() {
if (defenseItems.length === 0) {
return;
}
let totalImpact = 0;
let totalCost = 0;
defenseItems.forEach(item => {
switch (item.type) {
case "Sea Walls":
totalImpact += 20;
totalCost += 150;
break;
case "Levees":
totalImpact += 15;
totalCost += 100;
break;
case "Floodgates":
totalImpact += 25;
totalCost += 200;
break;
case "Dams":
totalImpact += 30;
totalCost += 250;
break;
}
});
defenseChart.innerHTML = `
<strong>Overall Flood Impact Reduction:</strong> ${totalImpact}%
<br><strong>Total Cost of Defenses:</strong> $${totalCost} million
`;
}
// Key navigation (for accessibility)
document.addEventListener("keydown", e => {
if (e.key === "Escape") {
document.body.classList.toggle("no-preference");
}
});
</script>
</body>
</html>