2026-10-20 TUE UTC 12:42 PM TPE 8:42 PM Auto-updates hourly 中文 EN
LIVE WIRE
← Back to the site changelog Site Changelog

Original commit record

Improve incident database with responsive event cards (#301)

SHA
1b525576bf182fbf18ec4698918136bccf03fdf0
Commit date
Author
nnneeeooo-nnneeeooo
Committer
web-flow

Changed files (4)

  • static/app.js modified · +6 / −1
  • static/site.css modified · +65 / −0
  • templates/incidents.html modified · +37 / −34
  • tests/test_incident_navigation.py modified · +1 / −1

Full diff

@@ -153,14 +153,19 @@\n   var countEl = document.getElementById("inc-count");\n   function applyIncidentFilter(v, button, updateUrl) {\n     var n = 0;\n-    sevBtns.forEach(function (x) { x.classList.toggle("active", x === button); });\n+    sevBtns.forEach(function (x) {\n+      x.classList.toggle("active", x === button);\n+      x.setAttribute("aria-pressed", String(x === button));\n+    });\n     Array.prototype.forEach.call(document.querySelectorAll("[data-sev]"), function (row) {\n       var matches = v === "all" ||\n         (v === "week" ? row.dataset.weeklySerious === "true" : row.dataset.sev === v);\n       row.classList.toggle("is-hidden", !matches);\n       if (matches) n += 1;\n     });\n     if (countEl) countEl.textContent = String(n);\n+    var emptyEl = document.getElementById("inc-empty");\n+    if (emptyEl) emptyEl.classList.toggle("is-hidden", n > 0);\n     if (updateUrl && window.history && window.history.replaceState) {\n       var url = new URL(window.location.href);\n       if (v === "all") url.searchParams.delete("filter");\n@@ -406,6 +406,71 @@ a.flash-row:hover .flash-text { color: var(--color-accent); }\n .incident-article-link:hover,\n .incident-date-link:hover { color: var(--color-accent); }\n \n+/* — incident database: readable summaries and compact supporting details — */\n+.incidents-page { padding: 36px 32px 56px; }\n+.incidents-header { max-width: 760px; margin-bottom: 28px; }\n+.incidents-header h1 { margin: 8px 0 12px; font-size: clamp(30px, 4vw, 44px); line-height: 1.2; }\n+.incidents-header p { margin: 0; font-size: 14px; line-height: 1.8; }\n+.incidents-toolbar {\n+  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;\n+  gap: 16px; padding: 18px 0; border-top: 1px solid var(--color-divider);\n+}\n+.incidents-filters { display: flex; flex-wrap: wrap; gap: 8px; }\n+.incidents-filters .seg-btn {\n+  min-height: 40px; padding: 9px 15px; border: 1px solid var(--color-divider);\n+  border-radius: 6px; font-size: 13px; white-space: nowrap;\n+}\n+.incidents-filters .seg-btn.active { border-color: var(--color-accent); }\n+.incidents-filters .seg-btn:focus-visible,\n+.incident-card a:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 4px; }\n+.incidents-count { font-size: 13px; color: color-mix(in srgb, var(--color-text) 68%, transparent); }\n+.incidents-count strong { color: var(--color-text); font-size: 20px; margin-right: 4px; }\n+.incidents-list { display: grid; gap: 16px; }\n+.incident-card {\n+  display: grid; grid-template-columns: 100px minmax(0, 1fr); gap: 24px;\n+  padding: 24px; border: 1px solid color-mix(in srgb, var(--color-text) 18%, transparent);\n+  border-radius: 10px; background: color-mix(in srgb, var(--color-bg) 65%, var(--color-surface));\n+  box-shadow: 0 2px 6px color-mix(in srgb, var(--color-text) 3%, transparent);\n+}\n+.incident-date { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }\n+.incident-label, .incident-details dt {\n+  font-size: 11px; font-weight: 500; color: color-mix(in srgb, var(--color-text) 65%, transparent);\n+}\n+.incident-date-link, .incident-date-value { font-size: 21px; font-weight: 800; line-height: 1.3; }\n+.incident-date .tag { white-space: nowrap; font-size: 11px; }\n+.incident-body { min-width: 0; }\n+.incident-summary { margin: 0 0 20px; font-size: 18px; font-weight: 700; line-height: 1.8; overflow-wrap: anywhere; }\n+.incident-summary .incident-article-link { text-decoration: none; }\n+.incident-go { color: var(--color-accent); white-space: nowrap; }\n+.incident-details { display: grid; grid-template-columns: 1fr 1fr 1fr 1.5fr; gap: 20px; margin: 0; }\n+.incident-details div { min-width: 0; }\n+.incident-details dt { margin-bottom: 5px; }\n+.incident-details dd { margin: 0; font-size: 13px; line-height: 1.7; overflow-wrap: anywhere; }\n+.incident-footer {\n+  display: flex; flex-wrap: wrap; justify-content: space-between; gap: 10px 24px;\n+  margin-top: 20px; padding-top: 14px; border-top: 1px solid color-mix(in srgb, var(--color-text) 12%, transparent);\n+  font-size: 12px; line-height: 1.7;\n+}\n+.incident-footer .incident-label { margin-right: 8px; }\n+.incident-source { overflow-wrap: anywhere; }\n+.incidents-note { margin: 24px 0 0; font-size: 12px; line-height: 1.8; }\n+.incidents-empty { margin: 0; padding: 40px 20px; text-align: center; border: 1px dashed var(--color-divider); border-radius: 10px; }\n+@media (max-width: 900px) {\n+  .incident-details { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px 24px; }\n+}\n+@media (max-width: 600px) {\n+  .incidents-page { padding: 28px 16px 40px; }\n+  .incidents-header { margin-bottom: 20px; }\n+  .incidents-toolbar { gap: 12px; }\n+  .incidents-filters { gap: 6px; width: 100%; }\n+  .incidents-filters .seg-btn { padding: 9px 12px; }\n+  .incident-card { grid-template-columns: minmax(0, 1fr); gap: 16px; padding: 20px 16px; }\n+  .incident-date { flex-direction: row; flex-wrap: wrap; align-items: center; gap: 10px; }\n+  .incident-date-link, .incident-date-value { font-size: 18px; }\n+  .incident-summary { font-size: 17px; margin-bottom: 16px; }\n+  .incident-footer { flex-direction: column; gap: 6px; margin-top: 16px; }\n+}\n+\n /* — Taiwan civil-flight radar — */\n .radar-page { padding: 24px 28px 32px; }\n .radar-header {\n@@ -1,41 +1,44 @@\n {% extends "base.html" %}\n {% block content %}\n-  <div style="padding:28px 20px 64px;">\n-    <span class="card-kicker">{{ t.incKicker }}</span>\n-    <h2 style="margin:8px 0 6px;">{{ t.incTitle }}</h2>\n-    <p class="text-muted" style="font-size:14px;max-width:640px;">{{ t.incSub }}</p>\n-    <div style="display:flex;justify-content:space-between;align-items:center;margin:18px 0 0;flex-wrap:wrap;gap:10px;">\n-      <span class="seg seg-sev">\n+  <section class="incidents-page" aria-labelledby="incidents-title">\n+    <header class="incidents-header">\n+      <span class="card-kicker">{{ t.incKicker }}</span>\n+      <h1 id="incidents-title">{{ t.incTitle }}</h1>\n+      <p class="text-muted">{{ t.incSub }}</p>\n+    </header>\n+    <div class="incidents-toolbar">\n+      <div class="incidents-filters" role="group" aria-label="{{ t.thSev }}">\n         {% for key, label in sev_seg %}\n-        <button type="button" class="seg-btn{% if key == 'all' %} active{% endif %}" data-filter-sev="{{ key }}">{{ label }}</button>\n+        <button type="button" class="seg-btn{% if key == 'all' %} active{% endif %}" data-filter-sev="{{ key }}" aria-pressed="{{ 'true' if key == 'all' else 'false' }}">{{ label }}</button>\n         {% endfor %}\n-      </span>\n-      <span class="text-muted" style="font-size:12.5px;"><span id="inc-count">{{ count }}</span> {{ t.incCountLabel }}</span>\n+      </div>\n+      <span class="incidents-count" role="status"><strong id="inc-count">{{ count }}</strong> {{ t.incCountLabel }}</span>\n     </div>\n-    <div style="overflow-x:auto;">\n-      <table class="table" style="margin-top:14px;">\n-        <thead><tr>\n-          <th>{{ t.thDate }}</th><th>{{ t.thSev }}</th><th>{{ t.thType }}</th><th>{{ t.thOp }}</th><th>{{ t.thPhase }}</th><th>{{ t.thLoc }}</th><th>{{ t.thDesc }}</th><th>{{ t.thStatus }}</th><th>{{ t.thSrc }}</th>\n-        </tr></thead>\n-        <tbody>\n-          {% for i in rows %}\n-          <tr data-sev="{{ i.sev_key }}" data-weekly-serious="{{ 'true' if i.weekly_serious else 'false' }}">\n-            <td style="font-weight:700;white-space:nowrap;">{% if i.url %}<a class="incident-date-link" href="{{ i.url }}" aria-label="{{ t.incReadEvent }}:{{ i.desc }}">{{ i.d }}</a>{% else %}{{ i.d }}{% endif %}</td>\n-            <td><span class="tag {{ i.sev_class }}">{{ i.sev_label }}</span></td>\n-            <td style="white-space:nowrap;">{{ i.ac }}</td>\n-            <td>{{ i.op }}</td>\n-            <td>{{ i.ph }}</td>\n-            <td>{{ i.loc }}</td>\n-            <td class="text-muted">{% if i.url %}<a class="incident-article-link" href="{{ i.url }}">{{ i.desc }}</a>{% else %}{{ i.desc }}{% endif %}</td>\n-            <td style="white-space:nowrap;">{{ i.st }}</td>\n-            <td class="text-muted" style="font-size:12px;white-space:nowrap;">{{ i.src }}</td>\n-          </tr>\n-          {% else %}\n-          <tr><td colspan="9" class="text-muted">{{ t.awaiting }}</td></tr>\n-          {% endfor %}\n-        </tbody>\n-      </table>\n+    <div class="incidents-list">\n+      {% for i in rows %}\n+      <article data-sev="{{ i.sev_key }}" data-weekly-serious="{{ 'true' if i.weekly_serious else 'false' }}" class="incident-card">\n+        <div class="incident-date">\n+          <span class="incident-label">{{ t.thDate }}</span>\n+          {% if i.url %}<a class="incident-date-link" href="{{ i.url }}" aria-label="{{ t.incReadEvent }}:{{ i.desc }}">{{ i.d }}</a>{% else %}<span class="incident-date-value">{{ i.d }}</span>{% endif %}\n+          <span class="tag {{ i.sev_class }}">{{ i.sev_label }}</span>\n+        </div>\n+        <div class="incident-body">\n+          <h2 class="incident-summary">{% if i.url %}<a class="incident-article-link" href="{{ i.url }}">{{ i.desc }}<span class="incident-go" aria-hidden="true"> ↗</span></a>{% else %}{{ i.desc }}{% endif %}</h2>\n+          <dl class="incident-details">\n+            <div><dt>{{ t.thType }}</dt><dd>{{ i.ac }}</dd></div>\n+            <div><dt>{{ t.thOp }}</dt><dd>{{ i.op }}</dd></div>\n+            <div><dt>{{ t.thPhase }}</dt><dd>{{ i.ph }}</dd></div>\n+            <div><dt>{{ t.thLoc }}</dt><dd>{{ i.loc }}</dd></div>\n+          </dl>\n+          <div class="incident-footer">\n+            <span class="incident-status"><span class="incident-label">{{ t.thStatus }}</span> {{ i.st }}</span>\n+            <span class="incident-source"><span class="incident-label">{{ t.thSrc }}</span> {{ i.src }}</span>\n+          </div>\n+        </div>\n+      </article>\n+      {% endfor %}\n+      <p id="inc-empty" class="incidents-empty{% if rows %} is-hidden{% endif %}" role="status">{{ t.awaiting }}</p>\n     </div>\n-    <p class="text-muted" style="font-size:12px;margin-top:14px;">{{ t.incNote }}</p>\n-  </div>\n+    <p class="incidents-note text-muted">{{ t.incNote }}</p>\n+  </section>\n {% endblock %}\n@@ -27,7 +27,7 @@ def main() -> None:\n     assert 'href="/en/incidents/?filter=week"' in en_home\n     assert 'data-filter-sev="week"' in incidents\n     weekly_rows = re.findall(\n-        r'<tr data-sev="(?:acc|ser)" data-weekly-serious="true">.*?</tr>',\n+        r'<article data-sev="(?:acc|ser)" data-weekly-serious="true"[^>]*>.*?</article>',\n         incidents,\n         re.DOTALL,\n     )

You found a secret theme!

8-bit mode is unlocked. Refresh to add it to your theme selector.