[object Object]

← back to Model Arena

Build Crazy News (WNN) from qwen2.5vl arena attempt: escalating stories, live ticker, klaxon, mood filter, empty/error/completion states

798c0fc8c0894212ccb7a684ece54cef08ae86dc · 2026-09-23 14:20:18 -0700 · Steve Abrams

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLjoHsyGTDeVCa1rdrAxnk

Files touched

Diff

commit 798c0fc8c0894212ccb7a684ece54cef08ae86dc
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Sep 23 14:20:18 2026 -0700

    Build Crazy News (WNN) from qwen2.5vl arena attempt: escalating stories, live ticker, klaxon, mood filter, empty/error/completion states
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01VLjoHsyGTDeVCa1rdrAxnk
---
 crazy-news.html | 780 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 780 insertions(+)

diff --git a/crazy-news.html b/crazy-news.html
new file mode 100644
index 0000000..d2d2b60
--- /dev/null
+++ b/crazy-news.html
@@ -0,0 +1,780 @@
+<!DOCTYPE html>
+<html lang="en" data-mood="all">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>WNN Live</title>
+<meta name="description" content="Whatever News Network — a satirical, entirely invented 24-hour news channel where three breaking stories escalate absurdly in real time.">
+<style>
+/* ---------- mood tokens (the category filter swaps the whole channel) ---------- */
+:root{
+  --bg:#0b1020; --bg2:#10183a; --panel:#131b36; --panel2:#1a2447; --ink:#eef2ff; --muted:#a9b4d6;
+  --line:#2a3563; --accent:#e11d48; --accent-ink:#fff; --accent2:#fbbf24; --focus:#fbbf24;
+  --head:"Helvetica Neue",Arial,sans-serif; --body:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
+  --radius:10px; --tilt-step:0deg;
+  color-scheme:dark;
+}
+[data-mood="politics"]{ --bg:#1a0c10; --bg2:#2a1017; --panel:#2b1319; --panel2:#3a1a21; --ink:#fdf3e7; --muted:#d9bfae;
+  --line:#5a2a31; --accent:#c8102e; --accent-ink:#fff; --accent2:#f5d58a; --focus:#f5d58a; --head:Georgia,"Times New Roman",serif; }
+[data-mood="science"]{ --bg:#020d07; --bg2:#051a0d; --panel:#07200f; --panel2:#0b2c16; --ink:#b9ffd0; --muted:#7fcf98;
+  --line:#155d2e; --accent:#22c55e; --accent-ink:#021006; --accent2:#d9f99d; --focus:#d9f99d;
+  --head:ui-monospace,"SF Mono",Menlo,Consolas,monospace; --body:ui-monospace,"SF Mono",Menlo,Consolas,monospace; }
+[data-mood="food"]{ --bg:#fff4df; --bg2:#ffe8bf; --panel:#fffdf7; --panel2:#fff3dc; --ink:#2b1a0e; --muted:#6b4a2f;
+  --line:#e6c79a; --accent:#c2410c; --accent-ink:#fff; --accent2:#9a3412; --focus:#1d4ed8;
+  --head:"Cooper Black","Rockwell","Georgia",serif; color-scheme:light; }
+[data-mood="weather"]{ --bg:#0d1a24; --bg2:#12263a; --panel:#152a3d; --panel2:#1c3650; --ink:#e0f2fe; --muted:#9cc3dc;
+  --line:#2c5173; --accent:#38bdf8; --accent-ink:#04121c; --accent2:#cbd5e1; --focus:#fde047; }
+
+*{box-sizing:border-box}
+html,body{margin:0}
+body{
+  background:radial-gradient(1200px 600px at 10% -10%,var(--bg2),transparent 60%),var(--bg);
+  color:var(--ink); font:16px/1.5 var(--body); min-height:100vh; padding-bottom:64px; overflow-x:hidden;
+  transition:background-color .6s,color .6s;
+}
+@media (prefers-reduced-motion:reduce){ body{transition:none} }
+button,input{font:inherit;color:inherit}
+:focus-visible{outline:3px solid var(--focus);outline-offset:3px;border-radius:6px}
+.sr-only{position:absolute!important;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
+.skip{position:absolute;left:-999px;top:8px;background:var(--accent);color:var(--accent-ink);padding:8px 12px;border-radius:6px;z-index:50}
+.skip:focus{left:8px}
+.wrap{max-width:1440px;margin:0 auto;padding:0 16px}
+
+/* ---------- masthead ---------- */
+.masthead{display:flex;flex-wrap:wrap;align-items:center;gap:12px 24px;padding:16px 0 10px;border-bottom:1px solid var(--line)}
+.brand{display:flex;align-items:center;gap:12px;min-width:0;flex:1 1 280px}
+.logo{font:900 26px/1 var(--head);letter-spacing:-.04em;background:var(--accent);color:var(--accent-ink);padding:10px 12px;border-radius:8px;
+  box-shadow:4px 4px 0 var(--accent2);flex:none}
+.net{margin:0;font:800 clamp(17px,2.2vw,22px)/1.15 var(--head);letter-spacing:-.01em}
+.tag{margin:2px 0 0;color:var(--muted);font-size:13px;text-transform:uppercase;letter-spacing:.08em}
+.live{display:flex;align-items:center;gap:8px;font-weight:800;letter-spacing:.1em;font-size:14px}
+.live .dot{width:10px;height:10px;border-radius:50%;background:var(--accent);box-shadow:0 0 0 0 var(--accent);animation:pulse 2s infinite}
+.live time{font-variant-numeric:tabular-nums;color:var(--muted);letter-spacing:.04em;font-weight:600}
+.meta{color:var(--muted);font-size:13px}
+.meta b{color:var(--ink);font-variant-numeric:tabular-nums}
+@keyframes pulse{0%{box-shadow:0 0 0 0 color-mix(in srgb,var(--accent) 70%,transparent)}70%{box-shadow:0 0 0 10px transparent}100%{box-shadow:0 0 0 0 transparent}}
+
+/* ---------- controls ---------- */
+.controls{display:grid;gap:12px;padding:14px 0;border-bottom:1px solid var(--line)}
+.moods{border:0;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:8px;align-items:center;min-width:0}
+.moods legend{float:left;margin-right:6px;font-size:12px;text-transform:uppercase;letter-spacing:.1em;color:var(--muted);padding:0}
+.chip{position:relative}
+.chip input{position:absolute;opacity:0;inset:0;margin:0;cursor:pointer}
+.chip span{display:inline-flex;align-items:center;gap:7px;padding:7px 12px;border:1px solid var(--line);border-radius:999px;background:var(--panel);
+  font-size:14px;font-weight:600;cursor:pointer;transition:background-color .2s,border-color .2s}
+.chip span i{width:12px;height:12px;border-radius:50%;display:inline-block;border:1px solid rgba(0,0,0,.25)}
+.chip input:checked + span{background:var(--accent);color:var(--accent-ink);border-color:var(--accent)}
+.chip input:focus-visible + span{outline:3px solid var(--focus);outline-offset:2px}
+.chip span:hover{border-color:var(--accent)}
+.switches{display:flex;flex-wrap:wrap;gap:8px;align-items:center}
+.btn{appearance:none;border:1px solid var(--line);background:var(--panel);padding:8px 14px;border-radius:8px;font-weight:700;font-size:14px;cursor:pointer;
+  display:inline-flex;align-items:center;gap:8px;min-height:40px}
+.btn:hover{border-color:var(--accent)}
+.btn:disabled{opacity:.55;cursor:not-allowed}
+.btn[aria-pressed="true"]{background:var(--ink);color:var(--bg);border-color:var(--ink)}
+.klaxon-btn{background:#b91c1c;color:#fff;border-color:#7f1d1d;text-transform:uppercase;letter-spacing:.06em}
+.klaxon-btn[aria-pressed="true"]{background:#fff;color:#b91c1c;border-color:#fff;box-shadow:0 0 0 3px #b91c1c inset}
+.klaxon-btn .st{font-size:11px;background:rgba(0,0,0,.25);padding:2px 6px;border-radius:4px}
+.klaxon-btn[aria-pressed="true"] .st{background:#b91c1c;color:#fff}
+.check{display:inline-flex;align-items:center;gap:6px;font-size:14px;color:var(--muted);min-height:40px;cursor:pointer}
+.check input{width:18px;height:18px;accent-color:var(--accent)}
+.cycle{display:flex;align-items:center;gap:12px;font-size:13px;color:var(--muted);flex-wrap:wrap}
+.cycle .bar{flex:1 1 160px;height:6px;background:var(--panel2);border-radius:99px;overflow:hidden;max-width:420px}
+.cycle .bar div{height:100%;width:0;background:var(--accent);border-radius:99px}
+.status{margin:0;min-height:1.4em;font-size:14px;font-weight:600;color:var(--accent2)}
+[data-mood="food"] .status{color:#9a3412}
+
+/* ---------- klaxon mode ---------- */
+.beacon{position:fixed;inset:0;pointer-events:none;z-index:40;opacity:0;box-shadow:inset 0 0 0 6px #dc2626,inset 0 0 90px rgba(220,38,38,.45)}
+body.klaxon .beacon{opacity:1;animation:beacon 1.1s ease-in-out infinite}  /* <1 flash/sec, well under WCAG 3/sec */
+@keyframes beacon{50%{opacity:.35}}
+.breaking{display:none;margin:12px 0 0;background:#dc2626;color:#fff;border-radius:8px;overflow:hidden;align-items:stretch}
+body.klaxon .breaking{display:flex}
+.breaking strong{background:#fff;color:#b91c1c;padding:8px 12px;font:900 15px/1.2 var(--head);letter-spacing:.08em;display:flex;align-items:center;flex:none}
+.breaking span{padding:8px 12px;font-weight:700;min-width:0}
+@media (prefers-reduced-motion:reduce){
+  body.klaxon .beacon{animation:none;opacity:.8}
+  .live .dot{animation:none}
+}
+
+/* ---------- layout ---------- */
+.layout{display:grid;grid-template-columns:minmax(0,1fr);gap:20px;padding:20px 0}
+@media (min-width:1100px){ .layout{grid-template-columns:minmax(0,1fr) 300px} }
+.stories{display:grid;gap:16px;grid-template-columns:minmax(0,1fr);align-items:start}
+@media (min-width:760px){
+  .stories{grid-template-columns:minmax(0,1.6fr) minmax(0,1fr)}
+  .stories .story.lead{grid-row:span 2}
+  .stories.single{grid-template-columns:minmax(0,1fr)}
+}
+
+/* ---------- story cards ---------- */
+.story{position:relative;background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:16px;min-width:0;
+  rotate:var(--tilt,0deg);transition:rotate .6s,border-color .3s;display:flex;flex-direction:column;gap:10px}
+@media (prefers-reduced-motion:reduce){ .story{transition:none} }
+.story.lead{border-top:6px solid var(--accent);padding:20px}
+.story-top{display:flex;justify-content:space-between;gap:8px;flex-wrap:wrap;align-items:center}
+.kicker{font-size:12px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;color:var(--accent)}
+[data-mood="food"] .kicker{color:#9a3412}
+.lead-tag{display:none;font-size:11px;font-weight:900;letter-spacing:.12em;background:var(--accent);color:var(--accent-ink);padding:2px 7px;border-radius:4px;margin-right:6px}
+.story.lead .lead-tag{display:inline-block}
+.level{font-size:12px;font-weight:700;color:var(--muted);font-variant-numeric:tabular-nums}
+.pips{display:flex;gap:4px}
+.pips i{flex:1;height:6px;border-radius:3px;background:var(--panel2)}
+.pips i.on{background:var(--accent)}
+.pips i.hot{background:#dc2626}
+.revisions{display:flex;flex-direction:column;gap:2px}
+.revisions del{color:var(--muted);font-size:13px;text-decoration-color:var(--accent);text-decoration-thickness:2px}
+.revisions del small{font-size:10px;letter-spacing:.08em;text-transform:uppercase;margin-right:6px;text-decoration:none;display:inline-block}
+.headline{margin:0;font:800 clamp(20px,2.3vw,24px)/1.15 var(--head);letter-spacing:-.015em;overflow-wrap:anywhere}
+.story.lead .headline{font-size:clamp(24px,3.4vw,38px)}
+.headline.fresh{animation:fresh 1.2s ease-out}
+@keyframes fresh{0%{background:color-mix(in srgb,var(--accent2) 55%,transparent)}100%{background:transparent}}
+@media (prefers-reduced-motion:reduce){ .headline.fresh{animation:none;text-decoration:underline 3px var(--accent2);text-underline-offset:6px} }
+.dek{margin:0;color:var(--muted)}
+.story.lead .dek{font-size:17px}
+.timeline{list-style:none;margin:0;padding:0;border-left:2px solid var(--line);display:grid;gap:6px}
+.timeline li{padding-left:10px;font-size:13px;position:relative}
+.timeline li::before{content:"";position:absolute;left:-6px;top:6px;width:10px;height:10px;border-radius:50%;background:var(--panel);border:2px solid var(--line)}
+.timeline li:first-child::before{background:var(--accent);border-color:var(--accent)}
+.timeline time{font-variant-numeric:tabular-nums;color:var(--muted);margin-right:6px;font-weight:700}
+.actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:auto}
+.reporter:empty{display:none}
+.reporter{border-radius:8px;padding:10px 12px;font-size:14px;background:var(--panel2);border:1px solid var(--line)}
+.reporter.err{border-color:#dc2626;background:color-mix(in srgb,#dc2626 14%,var(--panel))}
+.reporter.ok blockquote{margin:0;font-style:italic}
+.reporter.ok cite{display:block;margin-top:4px;font-style:normal;font-size:12px;color:var(--muted)}
+.reporter .btn{margin-top:8px}
+.stamp{align-self:flex-start;font:900 13px/1 var(--head);letter-spacing:.14em;text-transform:uppercase;color:#dc2626;
+  border:3px solid #dc2626;padding:6px 8px;border-radius:6px;rotate:-4deg}
+.stamp[hidden]{display:none}
+.story.done{border-style:dashed}
+.story.flash{box-shadow:0 0 0 3px var(--accent2)}
+
+/* ---------- sidebar ---------- */
+.side{display:grid;gap:16px;align-content:start}
+@media (min-width:760px) and (max-width:1099px){ .side{grid-template-columns:repeat(3,minmax(0,1fr))} }
+.panel{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:14px}
+.panel h2{margin:0 0 8px;font:800 13px/1.2 var(--body);text-transform:uppercase;letter-spacing:.12em;color:var(--muted)}
+.anchor q{font:600 17px/1.35 var(--head);display:block;quotes:"“" "”"}
+.anchor p{margin:8px 0 0;font-size:12px;color:var(--muted)}
+.sweat{display:inline-block;margin-left:4px}
+.meter{height:14px;border-radius:99px;background:var(--panel2);overflow:hidden;border:1px solid var(--line)}
+.meter div{height:100%;width:0;background:linear-gradient(90deg,#22c55e,#fbbf24 45%,#ef4444 80%,#a855f7);background-size:420px 100%;transition:width .6s}
+@media (prefers-reduced-motion:reduce){ .meter div{transition:none} }
+.meter-label{display:flex;justify-content:space-between;margin-top:6px;font-size:13px;font-weight:700}
+.markets{list-style:none;margin:0;padding:0;display:grid;gap:4px;font-size:13px;font-variant-numeric:tabular-nums}
+.markets li{display:flex;justify-content:space-between;gap:8px;border-bottom:1px dotted var(--line);padding:3px 0}
+.up{color:#16a34a}.down{color:#dc2626}
+[data-mood="science"] .up{color:#86efac}
+.keys dl{margin:0;display:grid;grid-template-columns:auto 1fr;gap:4px 10px;font-size:13px}
+.keys dt kbd{font:700 12px ui-monospace,Menlo,monospace;border:1px solid var(--line);border-bottom-width:3px;border-radius:5px;padding:1px 6px;background:var(--panel2)}
+.keys dd{margin:0;color:var(--muted)}
+
+/* ---------- empty + sign-off ---------- */
+.empty{margin:20px 0;text-align:center;padding:40px 16px;border:2px dashed var(--line);border-radius:var(--radius);background:var(--panel)}
+.empty .icon{font-size:48px;line-height:1}
+.empty h2{font:800 24px/1.2 var(--head);margin:10px 0 6px}
+.empty p{color:var(--muted);max-width:520px;margin:0 auto 16px}
+.signoff{margin:20px 0;border-radius:var(--radius);overflow:hidden;border:1px solid var(--line);background:var(--panel)}
+.signoff:focus{outline:none}
+.bars{display:grid;grid-template-columns:repeat(7,1fr);height:120px}
+.bars i:nth-child(1){background:#c0c0c0}.bars i:nth-child(2){background:#c0c000}.bars i:nth-child(3){background:#00c0c0}
+.bars i:nth-child(4){background:#00c000}.bars i:nth-child(5){background:#c000c0}.bars i:nth-child(6){background:#c00000}.bars i:nth-child(7){background:#0000c0}
+.bars2{display:grid;grid-template-columns:repeat(7,1fr);height:18px}
+.bars2 i:nth-child(odd){background:#131313}.bars2 i:nth-child(1),.bars2 i:nth-child(3){background:#0000c0}.bars2 i:nth-child(5){background:#c000c0}.bars2 i:nth-child(7){background:#c0c0c0}
+.signoff-body{padding:20px;text-align:center}
+.signoff-body h2{font:900 clamp(24px,4vw,40px)/1.1 var(--head);margin:0 0 6px}
+.signoff-body p{color:var(--muted);margin:0 0 14px}
+.stats{display:flex;flex-wrap:wrap;justify-content:center;gap:10px;margin:0 0 16px;padding:0;list-style:none}
+.stats li{background:var(--panel2);border:1px solid var(--line);border-radius:8px;padding:8px 12px;font-size:13px}
+.stats b{display:block;font-size:22px;font-variant-numeric:tabular-nums}
+.btn.primary{background:var(--accent);color:var(--accent-ink);border-color:var(--accent)}
+
+/* ---------- ticker ---------- */
+.ticker{position:fixed;left:0;right:0;bottom:0;z-index:30;display:flex;align-items:stretch;height:48px;background:#000;color:#fff;border-top:3px solid var(--accent)}
+.ticker-label{flex:none;display:flex;align-items:center;padding:0 12px;background:var(--accent);color:var(--accent-ink);font:900 14px/1 var(--head);letter-spacing:.08em}
+.ticker-view{flex:1;overflow:hidden;position:relative;min-width:0}
+.ticker-track{position:absolute;left:0;top:0;height:100%;display:flex;align-items:center;white-space:nowrap;will-change:transform}
+.ticker-track span{padding:0 22px;font-weight:700;font-size:15px;letter-spacing:.02em}
+.ticker-track span::after{content:"◆";margin-left:22px;color:var(--accent2);font-size:10px;vertical-align:middle}
+.ticker-track span.news{color:#fde68a}
+.ticker-track span.static{position:static}
+.ticker.static .ticker-track{position:static;padding:0 12px;overflow:hidden;text-overflow:ellipsis}
+.ticker.static .ticker-track span{padding:0;overflow:hidden;text-overflow:ellipsis}
+.ticker.static .ticker-track span::after{content:none}
+.ticker-btn{flex:none;width:48px;border:0;background:#111;color:#fff;cursor:pointer;font-size:16px;border-left:1px solid #333}
+.ticker-btn:focus-visible{outline-offset:-4px}
+</style>
+</head>
+<body>
+<a class="skip" href="#stories">Skip to stories</a>
+<div class="beacon" aria-hidden="true"></div>
+
+<div class="wrap">
+  <header class="masthead">
+    <div class="brand">
+      <div class="logo" aria-hidden="true">WNN</div>
+      <div>
+        <h1 class="net" id="netName">Whatever News Network</h1>
+        <p class="tag" id="tagline">24 hours · Facts, mostly</p>
+      </div>
+    </div>
+    <div class="live" aria-label="Live broadcast"><span class="dot" aria-hidden="true"></span>LIVE <time id="clock"></time></div>
+    <div class="meta"><b id="viewers">1,204,331</b> watching · <span id="moodLabel">Main desk</span></div>
+  </header>
+
+  <section class="controls" aria-label="Broadcast controls">
+    <fieldset class="moods">
+      <legend>Channel mood</legend>
+      <label class="chip"><input type="radio" name="mood" value="all" checked><span><i style="background:#e11d48"></i>All desks</span></label>
+      <label class="chip"><input type="radio" name="mood" value="politics"><span><i style="background:#c8102e"></i>Politics</span></label>
+      <label class="chip"><input type="radio" name="mood" value="science"><span><i style="background:#22c55e"></i>Science</span></label>
+      <label class="chip"><input type="radio" name="mood" value="food"><span><i style="background:#f59e0b"></i>Food</span></label>
+      <label class="chip"><input type="radio" name="mood" value="weather"><span><i style="background:#38bdf8"></i>Weather</span></label>
+    </fieldset>
+    <div class="switches">
+      <button type="button" class="btn klaxon-btn" id="klaxonBtn" aria-pressed="false">🚨 Breaking klaxon <span class="st" id="klaxonState">Off</span></button>
+      <label class="check"><input type="checkbox" id="soundChk" checked> Klaxon sound</label>
+      <button type="button" class="btn" id="pauseBtn" aria-pressed="false">⏸ Pause news cycle</button>
+      <button type="button" class="btn" id="resetBtn">↺ New news cycle</button>
+    </div>
+    <div class="cycle">
+      <span id="cycleText">Next development in 9s</span>
+      <div class="bar" aria-hidden="true"><div id="cycleBar"></div></div>
+    </div>
+    <p class="status" id="status" role="status"></p>
+    <div class="breaking" id="breaking"><strong>BREAKING</strong><span id="breakingText"></span></div>
+  </section>
+
+  <main id="main">
+    <section class="empty" id="empty" hidden aria-labelledby="emptyH">
+      <div class="icon" aria-hidden="true">🌫️</div>
+      <h2 id="emptyH">No weather tonight.</h2>
+      <p>Our meteorologist, Tom Brisket, was pre-empted by a goose. All weather has been postponed until the news calms down. The sky has been asked to hold.</p>
+      <button type="button" class="btn primary" id="emptyBack">Return to all desks</button>
+    </section>
+
+    <section class="signoff" id="signoff" hidden tabindex="-1" aria-labelledby="signoffH">
+      <div class="bars" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
+      <div class="bars2" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
+      <div class="signoff-body">
+        <h2 id="signoffH">That's the news. Tomorrow has been cancelled.</h2>
+        <p>All three stories reached their conclusion. WNN is now off the air until something else happens, which, statistically, is soon.</p>
+        <ul class="stats" id="stats"></ul>
+        <button type="button" class="btn primary" id="restartBtn">↺ Start a new news cycle</button>
+      </div>
+    </section>
+
+    <div class="layout">
+      <section id="stories" class="stories" aria-label="Top stories" tabindex="-1"></section>
+      <aside class="side" aria-label="Channel sidebar">
+        <section class="panel anchor" aria-labelledby="anchorH">
+          <h2 id="anchorH">At the desk</h2>
+          <q id="anchorQuote">Good evening. Some news is happening. We'll have it for you all night.</q>
+          <p>— Brenda Kowalczyk, anchor <span class="sweat" id="sweat" aria-hidden="true"></span></p>
+        </section>
+        <section class="panel" aria-labelledby="indexH">
+          <h2 id="indexH">Absurdity Index</h2>
+          <div class="meter" id="meter" role="meter" aria-labelledby="indexH" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"><div id="meterFill"></div></div>
+          <div class="meter-label"><span id="meterText">Calm</span><span id="meterPct">0%</span></div>
+        </section>
+        <section class="panel" aria-labelledby="mktH">
+          <h2 id="mktH">WNN Markets</h2>
+          <ul class="markets" id="markets"></ul>
+        </section>
+        <section class="panel keys" aria-labelledby="keysH">
+          <h2 id="keysH">Keyboard</h2>
+          <dl>
+            <dt><kbd>K</kbd></dt><dd>Toggle breaking klaxon</dd>
+            <dt><kbd>P</kbd></dt><dd>Pause / resume news cycle</dd>
+            <dt><kbd>T</kbd></dt><dd>Pause / resume ticker</dd>
+            <dt><kbd>E</kbd></dt><dd>Escalate the lead story</dd>
+            <dt><kbd>1</kbd>–<kbd>5</kbd></dt><dd>Switch channel mood</dd>
+          </dl>
+        </section>
+      </aside>
+    </div>
+  </main>
+</div>
+
+<footer class="ticker" id="ticker" aria-label="News ticker">
+  <span class="ticker-label">WNN WIRE</span>
+  <div class="ticker-view" id="tickerView"><div class="ticker-track" id="tickerTrack" aria-hidden="true"></div></div>
+  <button type="button" class="ticker-btn" id="tickerBtn" aria-pressed="false" aria-label="Pause ticker">❚❚</button>
+</footer>
+<div class="sr-only" id="announcer" aria-live="polite"></div>
+
+<script>
+(() => {
+'use strict';
+const $ = (s, r = document) => r.querySelector(s);
+const MAX = 7;                       // escalation stages per story
+const BASE_MS = 9000, KLAXON_MS = 4500;
+const reduceMQ = matchMedia('(prefers-reduced-motion: reduce)');
+let reduced = reduceMQ.matches;
+
+/* ------------------------------ content ------------------------------ */
+const STORIES = [
+  { id:'goose', cat:'politics', catLabel:'Politics', place:'Duluth, Minn.', reporter:'Dale Minks', from:'the pond outside City Hall',
+    fail:'Signal lost — Dale has been chased off the pond. He is fine. He is running.',
+    quotes:['The goose has not taken questions. The goose has taken my sandwich.',
+            'I’m standing where the bread aisle used to be. It is now a moat.',
+            'Officials tell me the new anthem is one honk, held for four minutes. People are weeping.'],
+    stages:[
+      ['Goose narrowly wins Duluth City Council seat in write-in upset','Councilmember Honk defeated a 12-year incumbent by 11 votes. The concession speech was interrupted by hissing from the winner.','GOOSE WINS DULUTH COUNCIL SEAT BY 11 VOTES'],
+      ['Councilmember Honk’s first act: a ban on bread litter, and then on bread','The ordinance passed 5–4 after the goose sat on the vote-counting machine. Bakeries have 48 hours to comply.','DULUTH BANS BREAD; BAKERS GIVEN 48 HOURS'],
+      ['Goose filibusters for 14 hours; transcript is entirely “HONK”','The city stenographer was treated for wrist strain. Parliamentarians confirm the filibuster was, technically, on topic.','14-HOUR GOOSE FILIBUSTER ENTERS RECORD BOOKS'],
+      ['Duluth secedes from Minnesota on a procedural technicality','The motion passed while the other council members were “just checking on their cars.” Minnesota has requested the city back.','DULUTH SECEDES; MINNESOTA "WOULD LIKE IT BACK"'],
+      ['Canada recognizes the Sovereign Goosedom of Duluth','Ottawa offers a trade deal in lettuce and an honorary seat at hockey games. The goose has not returned the call.','CANADA RECOGNIZES GOOSEDOM, OFFERS LETTUCE'],
+      ['UN emergency session: goose delegation demands a seat, and a pond','Security council members report being “aggressively followed” in the lobby. France abstained.','UN EMERGENCY SESSION ON GOOSE; FRANCE ABSTAINS'],
+      ['Goose resigns abruptly, flies south for winter; Duluth quietly rejoins Minnesota','No explanation was given. City officials have asked residents not to bring it up. Bread is legal again.','GOOSE RESIGNS, FLIES SOUTH. DULUTH IS BACK.']
+    ]},
+  { id:'tuesday', cat:'science', catLabel:'Science', place:'Boulder, Colo.', reporter:'Priya Anand', from:'the atomic clock lab at NIST',
+    fail:'Signal lost — Priya’s feed arrived eleven minutes late and was therefore technically Wednesday.',
+    quotes:['The clocks here all agree, which the scientists say is the scariest part.',
+            'I’ve been inside the extra time. It smells faintly like a dentist’s office.',
+            'Gary waved at me. I waved back. I think we are friends now.'],
+    stages:[
+      ['Physicists confirm Tuesdays now run 11 minutes long','Atomic clocks at three labs independently measured the surplus. “It’s just sort of… there,” said one researcher.','TUESDAY MEASURED 11 MINUTES LONG'],
+      ['Tuesday gap grows to 47 minutes; payroll departments demand answers','Hourly workers are asking whether the extra time counts. HR has scheduled a meeting about it for, ominously, Tuesday.','TUESDAY NOW +47 MIN; PAYROLL IN CRISIS'],
+      ['Researchers find a small room inside the extra time. It has a chair.','Team members describe beige walls, a ficus, and “a very specific hum.” No one has sat in the chair.','ROOM FOUND INSIDE TUESDAY. IT HAS A CHAIR.'],
+      ['Someone is sitting in the chair. Scientists “would rather not say more”','A follow-up expedition returned early and “will need a minute.” The lab has put up a paper sign reading PRIVATE.','SOMEONE IS IN THE CHAIR'],
+      ['Person in the chair identified as Gary, 52, who “just needed a minute”','Gary, an accounts receivable manager from Ohio, says he found the room in March and “didn’t think it was a big deal.”','CHAIR OCCUPANT IS GARY, 52. HE NEEDED A MINUTE.'],
+      ['Petition to make Gary’s minute permanent passes 40 million signatures','Economists warn a universal extra hour could “fundamentally improve everyone’s mood.” Markets are cautious.','GARY PETITION HITS 40 MILLION'],
+      ['Congress grants every citizen one weekly “Gary Hour”; Tuesdays return to normal','The bill passed unanimously in a session that ran exactly the right length. Gary is thriving.','GARY HOUR SIGNED INTO LAW. TUESDAY FIXED.']
+    ]},
+  { id:'dough', cat:'food', catLabel:'Food', place:'Tacoma, Wash.', reporter:'Marcus Bell', from:'a studio apartment kitchen in Tacoma',
+    fail:'Signal lost — the starter unplugged our camera. Its attorney says this was “self-expression.”',
+    quotes:['The starter is bubbling in what I can only describe as a confident rhythm.',
+            'The fridge has been declared neutral territory. Nobody is touching the butter.',
+            'Doughmitri’s campaign slogan is simply “Rise.” It is testing extremely well.'],
+    stages:[
+      ['Nine-year-old sourdough starter begins bubbling in Morse code','Owner Kevin Olsen, 34, noticed the pattern while making toast. A ham radio club confirmed the dots and dashes.','SOURDOUGH STARTER TAPPING MORSE CODE'],
+      ['Message decoded: “FEED ME BETTER FLOUR.” Local bakeries sell out of rye','Olsen says he had been using “the store brand, which, fair.” Rye futures jumped on the news.','STARTER DEMANDS BETTER FLOUR; RYE SELLS OUT'],
+      ['Starter, now going by “Doughmitri,” retains an attorney','Counsel confirmed the retainer was paid in a single, excellent boule. Olsen has declined to comment through his own lawyer.','DOUGHMITRI LAWYERS UP'],
+      ['Doughmitri sues owner for emotional neglect, seeks custody of the fridge','Court filings cite “months on the counter, unfed, during a heat wave.” The fridge has not taken sides.','STARTER SUES OWNER FOR FRIDGE CUSTODY'],
+      ['Judge rules starter is “legally a roommate,” owes a third of rent in bread','Legal scholars call the ruling “unprecedented” and “honestly kind of fair.” Rent is due on the first.','JUDGE: STARTER IS A ROOMMATE'],
+      ['Doughmitri announces mayoral run; polls at 38%, ahead of a goose','The campaign has raised $2.1M, mostly in carbs. Opponents question whether it can be trusted near an oven.','DOUGHMITRI RUNS FOR MAYOR, POLLING 38%'],
+      ['Doughmitri withdraws to “focus on rising”; starter and owner reconcile','The pair were seen sharing a Sunday bake. Sources describe the loaf as “the best thing to come out of this.”','DOUGHMITRI WITHDRAWS. THE LOAF WAS GREAT.']
+    ]}
+];
+const TIERS = {
+  net:['Whatever News Network','Whatever?? News Network','We Honestly Don’t Know Network','WNN — Please Remain Seated'],
+  tag:['24 hours · Facts, mostly','24 hours · Facts, occasionally','25 hours (it’s Tuesday) · Facts, pending','∞ hours · Facts have left the building'],
+  anchor:['Good evening. Some news is happening. We’ll have it for you all night.',
+          'I’m being told these are real stories. I have asked twice.',
+          'Our producers have left the building. I am now also the producer.',
+          'I’m going to read this exactly as written: “honk.” Back to you, me.'],
+  sweat:['','💧','💧💧','💧💧💧🫠'],
+  meter:['Calm','Elevated','Severe','Unprecedented','HONK']
+};
+const MOODS = { all:'Main desk', politics:'Politics desk', science:'Science desk', food:'Food desk', weather:'Weather desk' };
+const FILLER = () => {
+  const L = state.levels;
+  const tMin = [11,47,63,63,63,63,0][L.tuesday];
+  return [
+    `GOOSE FUTURES ${L.goose > 3 ? '▲' : '▼'} ${(L.goose * 7.3 + 1.2).toFixed(1)}%`,
+    L.tuesday < 6 ? `TUESDAY LENGTH: 24H ${tMin}M` : 'TUESDAY LENGTH: 24H 0M (NORMAL)',
+    `RYE FLOUR ▲ ${4 + L.dough * 9}% ON STARTER DEMAND`,
+    'SPORTS: LOCAL TEAM WINS, LOSES; REFEREE “NOT SURE”',
+    L.goose >= 2 ? 'TRAFFIC: I-35 BACKED UP BY 400 GEESE AND ONE REPORTER' : 'TRAFFIC: LIGHT, SUSPICIOUSLY',
+    'CORRECTION: YESTERDAY’S CORRECTION WAS CORRECT',
+    'WEATHER: POSTPONED',
+    `VIEWERS ADVISED TO REMAIN CALM (${TIERS.meter[meterTier()].toUpperCase()})`
+  ];
+};
+
+/* ------------------------------ state ------------------------------ */
+const state = {};
+function freshState(){
+  Object.assign(state, {
+    levels:{goose:0,tuesday:0,dough:0}, touched:{goose:0,tuesday:0,dough:0},
+    revisions:{goose:[],tuesday:[],dough:[]}, updates:{goose:[],tuesday:[],dough:[]},
+    attempts:{goose:0,tuesday:0,dough:0}, liveShots:0, escalations:0, byYou:0, klaxonCount:0,
+    paused:false, nextAt:0, remaining:BASE_MS, startedAt:Date.now(), finished:false
+  });
+  const now = new Date();
+  for (const s of STORIES) state.updates[s.id].push({t:now, text:s.stages[0][2]});
+}
+freshState();
+state.mood = 'all'; state.klaxon = false; state.sound = true;
+try {
+  const saved = JSON.parse(localStorage.getItem('wnn-prefs') || '{}');
+  if (MOODS[saved.mood]) state.mood = saved.mood;
+  if (typeof saved.sound === 'boolean') state.sound = saved.sound;
+} catch (_) {}
+const savePrefs = () => { try { localStorage.setItem('wnn-prefs', JSON.stringify({mood:state.mood, sound:state.sound})); } catch (_) {} };
+
+/* ------------------------------ helpers ------------------------------ */
+const PROPER = /\b(gary|duluth|doughmitri|minnesota|canada|un|france|tuesday|goosedom|nist)\b/g;
+const sentence = t => (t.charAt(0) + t.slice(1).toLowerCase()).replace(PROPER, w => w === 'un' ? 'UN' : w === 'nist' ? 'NIST' : w[0].toUpperCase() + w.slice(1));
+const fmtTime = d => d.toLocaleTimeString([], {hour:'2-digit', minute:'2-digit', second:'2-digit'});
+const total = () => STORIES.reduce((n, s) => n + state.levels[s.id], 0);
+const maxTotal = STORIES.length * (MAX - 1);
+const tier = () => Math.min(3, Math.floor(total() / 5));
+const meterTier = () => Math.min(4, Math.floor(total() / maxTotal * 4.999));
+const isDone = id => state.levels[id] >= MAX - 1;
+const allDone = () => STORIES.every(s => isDone(s.id));
+const visible = () => STORIES.filter(s => state.mood === 'all' || s.cat === state.mood);
+const statusEl = $('#status'), announcer = $('#announcer');
+let statusTimer;
+function status(msg){ statusEl.textContent = msg; clearTimeout(statusTimer); statusTimer = setTimeout(() => statusEl.textContent = '', 6000); }
+function announce(msg){ announcer.textContent = ''; setTimeout(() => announcer.textContent = msg, 30); }
+
+/* ------------------------------ audio (Web Audio, no files) ------------------------------ */
+let actx = null, audioFailed = false;
+function ctx(){
+  if (audioFailed) return null;
+  try {
+    if (!actx) { const AC = window.AudioContext || window.webkitAudioContext; if (!AC) throw 0; actx = new AC(); }
+    if (actx.state === 'suspended') actx.resume();
+    return actx;
+  } catch (_) { audioFailed = true; return null; }
+}
+function siren(dur = 2.4){
+  if (!state.klaxon || !state.sound) return;
+  const c = ctx();
+  if (!c) { status('Klaxon is visual-only: this browser has no audio support.'); return; }
+  const o = c.createOscillator(), f = c.createBiquadFilter(), g = c.createGain(), t = c.currentTime;
+  o.type = 'sawtooth'; f.type = 'lowpass'; f.frequency.value = 1800;
+  for (let i = 0; i < dur / .6; i++) {
+    o.frequency.setValueAtTime(620, t + i * .6);
+    o.frequency.linearRampToValueAtTime(960, t + i * .6 + .3);
+    o.frequency.linearRampToValueAtTime(620, t + i * .6 + .6);
+  }
+  g.gain.setValueAtTime(.0001, t); g.gain.exponentialRampToValueAtTime(.05, t + .05);
+  g.gain.setValueAtTime(.05, t + dur - .25); g.gain.exponentialRampToValueAtTime(.0001, t + dur);
+  o.connect(f).connect(g).connect(c.destination); o.start(t); o.stop(t + dur);
+  state.klaxonCount++;
+}
+function tone(){
+  if (!state.klaxon || !state.sound) return;
+  const c = ctx(); if (!c) return;
+  const o = c.createOscillator(), g = c.createGain(), t = c.currentTime;
+  o.frequency.value = 1000; g.gain.setValueAtTime(.0001, t); g.gain.exponentialRampToValueAtTime(.04, t + .05);
+  g.gain.setValueAtTime(.04, t + 1.4); g.gain.exponentialRampToValueAtTime(.0001, t + 1.6);
+  o.connect(g).connect(c.destination); o.start(t); o.stop(t + 1.7);
+}
+
+/* ------------------------------ story cards ------------------------------ */
+const storiesEl = $('#stories');
+const cards = {};
+for (const s of STORIES) {
+  const a = document.createElement('article');
+  a.className = 'story'; a.id = 'story-' + s.id; a.setAttribute('aria-labelledby', 'h-' + s.id);
+  a.innerHTML = `
+    <div class="story-top">
+      <span class="kicker"><span class="lead-tag">LEAD</span>${s.catLabel} · ${s.place}</span>
+      <span class="level">Escalation <b></b>/${MAX}</span>
+    </div>
+    <div class="pips" aria-hidden="true">${'<i></i>'.repeat(MAX)}</div>
+    <div class="stamp" hidden>✓ Story concluded</div>
+    <div class="revisions"></div>
+    <h2 class="headline" id="h-${s.id}"></h2>
+    <p class="dek"></p>
+    <ol class="timeline" aria-label="Latest updates"></ol>
+    <div class="actions">
+      <button type="button" class="btn esc">⚡ Escalate now</button>
+      <button type="button" class="btn go">📡 Go live to ${s.reporter}</button>
+    </div>
+    <div class="reporter" aria-live="polite"></div>`;
+  a.querySelector('.esc').addEventListener('click', () => escalate(s.id, true));
+  a.querySelector('.go').addEventListener('click', () => goLive(s.id));
+  storiesEl.appendChild(a);
+  cards[s.id] = a;
+}
+
+function renderCard(s, fresh){
+  const a = cards[s.id], lvl = state.levels[s.id], [h, dek] = s.stages[lvl];
+  a.querySelector('.level b').textContent = lvl + 1;
+  a.querySelectorAll('.pips i').forEach((p, i) => { p.className = i <= lvl ? (i >= 5 ? 'on hot' : 'on') : ''; });
+  a.querySelector('.revisions').innerHTML = state.revisions[s.id].slice(-2).map(r =>
+    `<del><small>Updated ${fmtTime(r.t)}</small>${r.h}</del>`).join('');
+  const hEl = a.querySelector('.headline');
+  hEl.textContent = h;
+  if (fresh) { hEl.classList.remove('fresh'); void hEl.offsetWidth; hEl.classList.add('fresh'); }
+  a.querySelector('.dek').textContent = dek;
+  a.querySelector('.timeline').innerHTML = state.updates[s.id].slice(-4).reverse().map(u =>
+    `<li><time>${fmtTime(u.t)}</time>${sentence(u.text)}</li>`).join('');
+  const done = isDone(s.id), esc = a.querySelector('.esc');
+  esc.disabled = done; esc.textContent = done ? '✓ Concluded' : '⚡ Escalate now';
+  a.querySelector('.stamp').hidden = !done;
+  a.classList.toggle('done', done);
+}
+
+let lastVisKey = '';
+/* reorder so the most-escalated visible story leads; FLIP-animated unless reduced motion */
+function layout(){
+  const vis = visible();
+  STORIES.forEach(s => cards[s.id].hidden = !vis.includes(s));
+  const before = new Map(vis.map(s => [s.id, cards[s.id].getBoundingClientRect()]));
+  const order = [...vis].sort((a, b) => (state.levels[b.id] - state.levels[a.id]) || (state.touched[b.id] - state.touched[a.id]));
+  order.forEach((s, i) => { cards[s.id].style.order = i; cards[s.id].classList.toggle('lead', i === 0); });
+  storiesEl.classList.toggle('single', vis.length === 1);
+  const empty = vis.length === 0;
+  $('#empty').hidden = !empty;
+  const key = vis.map(s => s.id).join();
+  const sameSet = key === lastVisKey; lastVisKey = key;
+  if (reduced || !sameSet) return;          // only animate pure reorders, never filter swaps
+  for (const s of vis) {
+    const el = cards[s.id], b = before.get(s.id), a = el.getBoundingClientRect();
+    if (!b || !b.width) continue;
+    const dx = b.left - a.left, dy = b.top - a.top;
+    if (Math.abs(dx) < 2 && Math.abs(dy) < 2) continue;
+    el.style.transition = 'none'; el.style.transform = `translate(${dx}px,${dy}px)`;
+    requestAnimationFrame(() => { el.style.transition = 'transform .55s cubic-bezier(.2,.8,.2,1), rotate .6s'; el.style.transform = ''; });
+  }
+}
+
+/* the channel itself degrades as absurdity rises */
+function renderChrome(){
+  const t = tier(), mt = meterTier(), pct = Math.round(total() / maxTotal * 100);
+  $('#netName').textContent = TIERS.net[t];
+  $('#tagline').textContent = TIERS.tag[t];
+  $('#anchorQuote').textContent = TIERS.anchor[t];
+  $('#sweat').textContent = TIERS.sweat[t];
+  $('#meterFill').style.width = pct + '%';
+  const meter = $('#meter');
+  meter.setAttribute('aria-valuenow', pct);
+  meter.setAttribute('aria-valuetext', `${pct}% — ${TIERS.meter[mt]}`);
+  $('#meterText').textContent = TIERS.meter[mt];
+  $('#meterPct').textContent = pct + '%';
+  $('#viewers').textContent = (1204331 + total() * 587113 + state.liveShots * 40211).toLocaleString();
+  STORIES.forEach((s, i) => cards[s.id].style.setProperty('--tilt', `${(i % 2 ? -1 : 1) * t * 0.35}deg`));
+  const L = state.levels, mk = [
+    ['GOOSE', (L.goose * 7.3 + 1.2), L.goose > 3],
+    ['BREAD', -(L.goose * 4.1 + .4) + L.dough * 3, L.dough * 3 > L.goose * 4.1],
+    ['RYE', 4 + L.dough * 9, true],
+    ['TUE-DUR', [11,47,63,63,63,63,0][L.tuesday] / 14.4, L.tuesday < 6],
+    ['GARY', L.tuesday * 11.1, L.tuesday >= 4]
+  ];
+  $('#markets').innerHTML = mk.map(([n, v, up]) =>
+    `<li><span>${n}</span><span class="${up ? 'up' : 'down'}">${up ? '▲' : '▼'} ${Math.abs(v).toFixed(1)}%</span></li>`).join('');
+  const lead = visible().sort((a, b) => state.levels[b.id] - state.levels[a.id])[0] || STORIES[0];
+  $('#breakingText').textContent = lead.stages[state.levels[lead.id]][0];
+}
+function renderAll(){ STORIES.forEach(s => renderCard(s, false)); layout(); renderChrome(); }
+
+/* ------------------------------ escalation ------------------------------ */
+function escalate(id, manual){
+  if (isDone(id)) { status(`That story has already concluded. There is nothing left to escalate.`); return; }
+  const s = STORIES.find(x => x.id === id);
+  const prev = s.stages[state.levels[id]][0];
+  state.revisions[id].push({t:new Date(), h:prev});
+  state.levels[id]++; state.touched[id] = Date.now();
+  state.escalations++; if (manual) state.byYou++;
+  const [h, , tick] = s.stages[state.levels[id]];
+  state.updates[id].push({t:new Date(), text:tick});
+  ticker.push(`${s.catLabel.toUpperCase()} · ${tick}`);
+  cards[id].querySelector('.reporter').textContent = '';
+  cards[id].querySelector('.reporter').className = 'reporter';
+  renderCard(s, true); layout(); renderChrome();
+  const done = isDone(id);
+  announce(`${done ? 'Story concluded' : 'Breaking, escalation ' + (state.levels[id] + 1) + ' of ' + MAX}: ${h}`);
+  if (manual) status(done ? `You pushed “${s.catLabel}” to its conclusion.` : `Escalated: ${s.catLabel} is now at level ${state.levels[id] + 1} of ${MAX}.`);
+  if (!visible().includes(s)) status(`Meanwhile, off-desk: ${s.catLabel} just escalated (switch to All desks to see it).`);
+  siren(done ? 1.2 : 2.4);
+  scheduleNext();
+  if (allDone()) finish();
+}
+function autoEscalate(){
+  const open = STORIES.filter(s => !isDone(s.id));
+  if (!open.length) return;
+  const vis = open.filter(s => visible().includes(s));
+  const pool = vis.length ? vis : open;
+  escalate(pool[Math.floor(Math.random() * pool.length)].id, false);
+}
+const interval = () => state.klaxon ? KLAXON_MS : BASE_MS;
+function scheduleNext(){ state.nextAt = Date.now() + interval(); state.remaining = interval(); }
+
+/* ------------------------------ field reporters (loading / error / success) ------------------------------ */
+function goLive(id){
+  const s = STORIES.find(x => x.id === id), a = cards[id], btn = a.querySelector('.go'), box = a.querySelector('.reporter');
+  btn.disabled = true; btn.textContent = '📡 Connecting…'; a.setAttribute('aria-busy', 'true');
+  box.className = 'reporter'; box.textContent = `Establishing satellite link to ${s.from}…`;
+  setTimeout(() => {
+    a.removeAttribute('aria-busy'); btn.disabled = false; btn.textContent = `📡 Go live to ${s.reporter}`;
+    const attempt = state.attempts[id]++;
+    if (attempt === 0) {                       // first live shot always drops — it's that kind of night
+      box.className = 'reporter err';
+      box.innerHTML = `<strong>⚠ ${s.fail}</strong><br><button type="button" class="btn retry">↻ Retry the satellite</button>`;
+      box.querySelector('.retry').addEventListener('click', () => { goLive(id); btn.focus(); });
+      box.querySelector('.retry').focus();
+      status(`Live shot failed: ${s.reporter} is unavailable. Retry is available.`);
+      return;
+    }
+    state.liveShots++;
+    const q = s.quotes[Math.min(s.quotes.length - 1, Math.floor(state.levels[id] / 2.4))];
+    box.className = 'reporter ok';
+    box.innerHTML = `<blockquote>“${q}”</blockquote><cite>— ${s.reporter}, live from ${s.from}</cite>`;
+    status(`Now live: ${s.reporter} from ${s.place}.`);
+    renderChrome();
+  }, 1100);
+}
+
+/* ------------------------------ completion ------------------------------ */
+function finish(){
+  if (state.finished) return;
+  state.finished = true;
+  const secs = Math.round((Date.now() - state.startedAt) / 1000);
+  $('#stats').innerHTML = [
+    [state.escalations, 'escalations'], [state.byYou, 'escalated by you'], [state.liveShots, 'live shots'],
+    [state.klaxonCount, 'klaxon blasts'], [`${Math.floor(secs / 60)}:${String(secs % 60).padStart(2, '0')}`, 'broadcast length']
+  ].map(([v, l]) => `<li><b>${v}</b>${l}</li>`).join('');
+  const so = $('#signoff'); so.hidden = false;
+  ticker.push('END OF BROADCAST · PLEASE STAND BY · TOMORROW HAS BEEN CANCELLED');
+  $('#cycleText').textContent = 'Broadcast complete — no further developments.';
+  $('#cycleBar').style.width = '100%';
+  $('#pauseBtn').disabled = true;
+  tone();
+  announce('All three stories have concluded. Broadcast complete.');
+  setTimeout(() => { so.focus({preventScroll:true}); so.scrollIntoView({behavior: reduced ? 'auto' : 'smooth', block:'start'}); }, 400);
+}
+function restart(){
+  freshState();
+  $('#signoff').hidden = true; $('#pauseBtn').disabled = false;
+  setPaused(false, true);
+  STORIES.forEach(s => { const r = cards[s.id].querySelector('.reporter'); r.className = 'reporter'; r.textContent = ''; });
+  ticker.reset();
+  scheduleNext(); renderAll();
+  status('A new news cycle has begun. Everything is fine, for now.');
+  $('#stories').focus({preventScroll:true});
+}
+
+/* ------------------------------ controls ------------------------------ */
+function setMood(m, fromKey){
+  state.mood = m; document.documentElement.dataset.mood = m; savePrefs();
+  const r = document.querySelector(`input[name=mood][value=${m}]`); if (r) r.checked = true;
+  if (fromKey && r) r.focus();
+  $('#moodLabel').textContent = MOODS[m];
+  layout(); renderChrome();
+  const n = visible().length;
+  status(n ? `Tuned to the ${MOODS[m]}. Showing ${n} ${n === 1 ? 'story' : 'stories'}.` : `Tuned to the ${MOODS[m]}. Nothing is airing here.`);
+}
+document.querySelectorAll('input[name=mood]').forEach(r => r.addEventListener('change', () => setMood(r.value)));
+$('#emptyBack').addEventListener('click', () => setMood('all', true));
+
+function setKlaxon(on){
+  state.klaxon = on;
+  const b = $('#klaxonBtn'); b.setAttribute('aria-pressed', on); $('#klaxonState').textContent = on ? 'On' : 'Off';
+  document.body.classList.toggle('klaxon', on);
+  if (!state.paused && !state.finished) { const left = Math.max(0, state.nextAt - Date.now()); state.nextAt = Date.now() + Math.min(left, interval()); }
+  ticker.speed = on ? 130 : 80;
+  if (on) { siren(); status(`Klaxon ON: developments now arrive twice as fast${state.sound ? '' : ' (sound muted)'}.`); }
+  else status('Klaxon off. The newsroom exhales.');
+}
+$('#klaxonBtn').addEventListener('click', () => setKlaxon(!state.klaxon));
+const soundChk = $('#soundChk'); soundChk.checked = state.sound;
+soundChk.addEventListener('change', () => {
+  state.sound = soundChk.checked; savePrefs();
+  status(state.sound ? 'Klaxon sound enabled.' : 'Klaxon sound muted — the alarm will be visual only.');
+  if (state.sound) siren(1.2);
+});
+
+function setPaused(p, quiet){
+  if (state.finished && p) return;
+  state.paused = p;
+  if (p) state.remaining = Math.max(0, state.nextAt - Date.now()); else state.nextAt = Date.now() + state.remaining;
+  const b = $('#pauseBtn'); b.setAttribute('aria-pressed', p); b.textContent = p ? '▶ Resume news cycle' : '⏸ Pause news cycle';
+  if (!quiet) status(p ? 'News cycle paused. Reality is holding still.' : 'News cycle resumed.');
+}
+$('#pauseBtn').addEventListener('click', () => setPaused(!state.paused));
+$('#resetBtn').addEventListener('click', restart);
+$('#restartBtn').addEventListener('click', restart);
+
+document.addEventListener('keydown', e => {
+  if (e.metaKey || e.ctrlKey || e.altKey || e.repeat) return;
+  const k = e.key.toLowerCase();
+  if (k === 'k') setKlaxon(!state.klaxon);
+  else if (k === 'p') { if (!state.finished) setPaused(!state.paused); }
+  else if (k === 't') ticker.toggle();
+  else if (k === 'e') { const lead = visible().filter(s => !isDone(s.id)).sort((a, b) => state.levels[b.id] - state.levels[a.id])[0];
+    lead ? escalate(lead.id, true) : status('Nothing left to escalate on this desk.'); }
+  else if ('12345'.includes(k) && k.length === 1) setMood(Object.keys(MOODS)[+k - 1], true);
+  else return;
+  e.preventDefault();
+});
+
+/* ------------------------------ ticker ------------------------------ */
+const ticker = {
+  root: $('#ticker'), view: $('#tickerView'), track: $('#tickerTrack'), btn: $('#tickerBtn'),
+  offset:0, last:0, paused:false, speed:80, news:[], staticIdx:0, staticTimer:null, raf:0,
+  span(text, news){ const s = document.createElement('span'); s.textContent = text; if (news) { s.className = 'news'; s.dataset.loops = 0; } return s; },
+  fillerIdx:0,
+  nextFiller(){ const f = FILLER(); return f[this.fillerIdx++ % f.length]; },
+  push(text){
+    this.news.push(text); if (this.news.length > 10) this.news.shift();
+    if (reduced) { this.staticIdx = -1; this.showStatic(text, true); return; }
+    // insert just past the visible edge so fresh news arrives within seconds
+    const vw = this.view.clientWidth, el = this.span(text, true);
+    const after = [...this.track.children].find(c => c.offsetLeft - this.offset > vw);
+    after ? this.track.insertBefore(el, after) : this.track.appendChild(el);
+  },
+  fill(){ while (this.track.scrollWidth - this.offset < this.view.clientWidth * 2) this.track.appendChild(this.span(this.nextFiller(), false)); },
+  frame(ts){
+    const dt = this.last ? Math.min(100, ts - this.last) : 0; this.last = ts;
+    if (!this.paused) {
+      this.offset += this.speed * dt / 1000;
+      let first = this.track.firstElementChild;
+      while (first && first.offsetWidth && first.offsetWidth < this.offset) {
+        this.offset -= first.offsetWidth; first.remove();
+        if (first.classList.contains('news') && +first.dataset.loops < 2) { first.dataset.loops++; this.track.appendChild(first); }
+        first = this.track.firstElementChild;
+      }
+      this.fill();
+      this.track.style.transform = `translateX(${-this.offset}px)`;
+    }
+    this.raf = requestAnimationFrame(t => this.frame(t));
+  },
+  showStatic(text, isNews){
+    this.track.innerHTML = ''; const s = this.span(text, isNews); s.classList.add('static'); this.track.appendChild(s);
+  },
+  rotateStatic(){
+    if (this.paused) return;
+    const pool = [...this.news.slice(-4), ...FILLER()];
+    this.staticIdx = (this.staticIdx + 1) % pool.length;
+    this.showStatic(pool[this.staticIdx], this.staticIdx < Math.min(4, this.news.length));
+  },
+  setMode(){
+    cancelAnimationFrame(this.raf); clearInterval(this.staticTimer);
+    this.track.innerHTML = ''; this.offset = 0; this.last = 0; this.track.style.transform = '';
+    this.root.classList.toggle('static', reduced);
+    if (reduced) { this.staticIdx = -1; this.rotateStatic(); this.staticTimer = setInterval(() => this.rotateStatic(), 6000); }
+    else { this.news.slice(-3).forEach(n => this.track.appendChild(this.span(n, true))); this.fill(); this.raf = requestAnimationFrame(t => this.frame(t)); }
+  },
+  toggle(){
+    this.paused = !this.paused;
+    this.btn.setAttribute('aria-pressed', this.paused);
+    this.btn.setAttribute('aria-label', this.paused ? 'Resume ticker' : 'Pause ticker');
+    this.btn.textContent = this.paused ? '▶' : '❚❚';
+    status(this.paused ? 'Ticker paused.' : 'Ticker resumed.');
+  },
+  reset(){ this.news = []; STORIES.forEach(s => this.news.push(`${s.catLabel.toUpperCase()} · ${s.stages[0][2]}`)); this.setMode(); }
+};
+ticker.btn.addEventListener('click', () => ticker.toggle());
+ticker.view.addEventListener('mouseenter', () => { ticker.hover = ticker.paused; if (!ticker.paused) { ticker.paused = true; ticker.hoverPaused = true; } });
+ticker.view.addEventListener('mouseleave', () => { if (ticker.hoverPaused) { ticker.paused = false; ticker.hoverPaused = false; } });
+reduceMQ.addEventListener && reduceMQ.addEventListener('change', e => { reduced = e.matches; ticker.setMode(); });
+
+/* ------------------------------ clock + cycle loop ------------------------------ */
+function tick(){
+  $('#clock').textContent = fmtTime(new Date());
+  if (state.finished) return;
+  if (state.paused) { $('#cycleText').textContent = `News cycle paused — next development held at ${Math.ceil(state.remaining / 1000)}s`; return; }
+  const left = state.nextAt - Date.now();
+  if (left <= 0) { autoEscalate(); return; }
+  $('#cycleText').textContent = `Next development in ${Math.ceil(left / 1000)}s${state.klaxon ? ' (klaxon speed)' : ''}`;
+  $('#cycleBar').style.width = (100 - left / interval() * 100).toFixed(1) + '%';
+}
+
+/* ------------------------------ boot ------------------------------ */
+document.documentElement.dataset.mood = state.mood;
+document.querySelector(`input[name=mood][value=${state.mood}]`).checked = true;
+$('#moodLabel').textContent = MOODS[state.mood];
+scheduleNext(); renderAll(); ticker.reset();
+setInterval(tick, 250); tick();
+window.WNN = { state, escalate, finish };   // debug handle for testing
+})();
+</script>
+</body>
+</html>

← a327c5d auto-data-snapshot: 2026-09-23T14:17:00 (1 data files) — dat  ·  back to Model Arena  ·  auto-data-snapshot: 2026-09-23T15:52:37 (1 data files) — dat 6e3695c →