← 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">
      &copy; 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>