Commit f07fe81d by PLN (Algolia)

status: six buckets for gig day, and the page stops inventing a doors time

Rebuilt against tonight's findings. Buckets Corpus/Audio/Surface become
Rig/Perf/Mix, which is what the night actually produced; Set 100%, Rehearsal
35% (six of seventeen, moved up from last), Rig/Perf/Mix gated on one pair of
hands, Observability 90%. Timeline is three stops, all today. Counters compute
from the clock and repaint every 60s.

The part worth keeping: no clock time appears on this page except the sourced
17:00. The previous version said doors at 19:00 and a rebuild changed it to
18:00 by reasoning from 'end of talks -> doors'. Both were guesses. There is no
entry for this gig in Perso/www/content/lives/2026 -- the canonical home for
venue, title, lineup and times -- and TODO_GIG.md never states one, so the rule
applies: never invent gig metadata, ask. The set now reads 'time tbc' and every
countdown anchors on the sound test.

That is enforced mechanically rather than by intention: the page's silent
self-test sweeps every derived string at 30-minute steps from 15 Sept to 5 Oct
and FAILS on any clock time that is not 17:00. Guessing a doors time back in
now breaks a test instead of quietly misinforming him at a glance. The two old
guesses survive only inside the comment that explains why they were wrong.

Verified: 420px frame -> scrollWidth 405 (no horizontal scroll), 1440px -> 3+3
grid with no orphan card, self-test PASS and silent, zero external requests,
HTTP 200 over localhost.
parent 967c2e84
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>AI ENGINEER &middot; 2h livecoding set</title> <title>AI ENGINEER &middot; gig day</title>
<style> <style>
:root{ :root{
--paper:#f6f1e5; --paper:#f6f1e5;
...@@ -65,14 +65,14 @@ ...@@ -65,14 +65,14 @@
.timeline{ .timeline{
position:relative; position:relative;
margin:26px 0 30px; margin:26px 0 30px;
padding:36px 14px 16px; padding:14px 14px 16px;
background:var(--paper-card); background:var(--paper-card);
border:1px solid var(--rule); border:1px solid var(--rule);
border-radius:13px; border-radius:13px;
box-shadow:0 1px 3px var(--shadow); box-shadow:0 1px 3px var(--shadow);
overflow-x:auto; overflow-x:auto;
} }
.track{display:flex;align-items:flex-start;gap:0;min-width:260px} .track{display:flex;align-items:flex-start;gap:0;min-width:300px;margin-top:32px}
.stop{flex:1 1 0;text-align:center;position:relative;padding-top:16px} .stop{flex:1 1 0;text-align:center;position:relative;padding-top:16px}
.stop::before{ .stop::before{
content:"";position:absolute;top:5px;left:0;right:0;height:2px; content:"";position:absolute;top:5px;left:0;right:0;height:2px;
...@@ -88,17 +88,19 @@ ...@@ -88,17 +88,19 @@
} }
.stop.finale .dot{background:var(--ink);border-color:var(--ink);width:15px;height:15px} .stop.finale .dot{background:var(--ink);border-color:var(--ink);width:15px;height:15px}
.day{display:block;font-size:.79rem;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-faint);margin-top:4px} .day{display:block;font-size:.79rem;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-faint);margin-top:4px}
/* one line reserved per stop: both labels fit on one line here, so both countdowns share a baseline */ /* two lines reserved per stop, so every countdown shares one baseline */
.what{display:block;font-size:.88rem;color:var(--ink);padding:0 6px;line-height:1.35;min-height:1.4em} .what{display:block;font-size:.88rem;color:var(--ink);padding:0 6px;line-height:1.35;min-height:2.8em}
.cd{display:block;font-size:.77rem;color:var(--ink-faint);margin-top:3px;letter-spacing:.02em} .cd{display:block;font-size:.77rem;color:var(--ink-faint);margin-top:3px;letter-spacing:.02em}
.stop.finale .what{font-weight:600} .stop.finale .what{font-weight:600}
.stop.finale .cd{color:var(--ink-soft);font-weight:600} .stop.finale .cd{color:var(--ink-soft);font-weight:600}
/* ribbon on today */ /* ribbon on today. Every stop is today, so the ribbon marks where the clock is. */
.stop.today .dot{background:var(--ribbon);border-color:var(--ribbon);width:15px;height:15px} .stop.today .dot{background:var(--ribbon);border-color:var(--ribbon);width:15px;height:15px}
.stop.today .day{color:var(--ribbon);font-weight:700} .stop.today .day{color:var(--ribbon);font-weight:700}
.stop.today .what{font-weight:600} .stop.today .what{font-weight:600}
.stop.today .cd{color:var(--ribbon);font-weight:600} .stop.today .cd{color:var(--ribbon);font-weight:600}
.stop.gone .dot{background:var(--rule);border-color:var(--ink-faint)}
.stop.gone .what,.stop.gone .day,.stop.gone .cd{color:var(--ink-faint);font-weight:400}
.ribbon{ .ribbon{
position:absolute;left:50%;transform:translateX(-50%); position:absolute;left:50%;transform:translateX(-50%);
top:-28px;width:13px;height:30px;z-index:1; top:-28px;width:13px;height:30px;z-index:1;
...@@ -107,10 +109,15 @@ ...@@ -107,10 +109,15 @@
clip-path:polygon(0 0,100% 0,100% 100%,50% 76%,0 100%); clip-path:polygon(0 0,100% 0,100% 100%,50% 76%,0 100%);
box-shadow:0 1px 2px var(--shadow); box-shadow:0 1px 2px var(--shadow);
} }
.tl-cap{
display:block;margin:0 0 2px;
font-size:.7rem;letter-spacing:.16em;text-transform:uppercase;
color:var(--ribbon);font-weight:800;
}
/* ---------- buckets ---------- */ /* ---------- buckets ---------- */
/* six cards: 1 / 2+2+2 / 3+3. No breakpoint can leave a card alone on a row. */ /* six cards: 1 / 2+2+2 / 3+3. No breakpoint can leave a card alone on a row. */
.buckets{display:grid;grid-template-columns:1fr;gap:18px} .buckets{display:grid;grid-template-columns:1fr;gap:18px;align-items:start}
@media (min-width:640px){ .buckets{grid-template-columns:repeat(2,1fr)} } @media (min-width:640px){ .buckets{grid-template-columns:repeat(2,1fr)} }
@media (min-width:1000px){ .buckets{grid-template-columns:repeat(3,1fr)} } @media (min-width:1000px){ .buckets{grid-template-columns:repeat(3,1fr)} }
.card{ .card{
...@@ -122,7 +129,7 @@ ...@@ -122,7 +129,7 @@
} }
.card:nth-child(odd){transform:rotate(-.3deg)} .card:nth-child(odd){transform:rotate(-.3deg)}
.card:nth-child(even){transform:rotate(.3deg)} .card:nth-child(even){transform:rotate(.3deg)}
/* the open bucket leads the risk, so it wears the ribbon edge */ /* a gated bucket leads the risk, so it wears the ribbon edge */
.card.gate{border-color:var(--ribbon-soft);box-shadow:0 2px 8px rgba(180,85,60,.18)} .card.gate{border-color:var(--ribbon-soft);box-shadow:0 2px 8px rgba(180,85,60,.18)}
.card h2{ .card h2{
font-size:.95rem;letter-spacing:.14em;text-transform:uppercase;margin:0;color:var(--ink-soft); font-size:.95rem;letter-spacing:.14em;text-transform:uppercase;margin:0;color:var(--ink-soft);
...@@ -133,7 +140,7 @@ ...@@ -133,7 +140,7 @@
.figure{font-family:Georgia,serif;font-size:2.4rem;line-height:1.12;margin:2px 0 0} .figure{font-family:Georgia,serif;font-size:2.4rem;line-height:1.12;margin:2px 0 0}
.figure.done{color:var(--sage)} .figure.done{color:var(--sage)}
.figure.near{color:var(--amber)} .figure.near{color:var(--amber)}
.card.gate .figure{color:var(--ribbon)} .card.gate .figure{color:var(--ribbon);font-size:1.85rem;letter-spacing:.01em}
.mood{margin:2px 0 12px;font-size:.9rem;color:var(--ink-soft);font-style:italic} .mood{margin:2px 0 12px;font-size:.9rem;color:var(--ink-soft);font-style:italic}
.bar{ .bar{
height:9px;border-radius:5px;background:#eee5d2;overflow:hidden; height:9px;border-radius:5px;background:#eee5d2;overflow:hidden;
...@@ -145,7 +152,7 @@ ...@@ -145,7 +152,7 @@
background-image:repeating-linear-gradient(45deg, rgba(255,255,255,.30) 0 2px, transparent 2px 5px); background-image:repeating-linear-gradient(45deg, rgba(255,255,255,.30) 0 2px, transparent 2px 5px);
} }
.fill.near{background-color:var(--amber)} .fill.near{background-color:var(--amber)}
/* nothing run yet: the track itself is pencilled in, so 0% reads as "waiting", not "broken" */ /* gated: the track itself is pencilled in, so an empty bar reads "waiting on you", not "broken" */
.bar.pending{ .bar.pending{
background-color:#f2e6dc; background-color:#f2e6dc;
background-image:repeating-linear-gradient(45deg, rgba(180,85,60,.26) 0 2px, transparent 2px 6px); background-image:repeating-linear-gradient(45deg, rgba(180,85,60,.26) 0 2px, transparent 2px 6px);
...@@ -171,10 +178,11 @@ ...@@ -171,10 +178,11 @@
.mono{ .mono{
font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,"Liberation Mono",monospace; font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,"Liberation Mono",monospace;
font-size:.82rem;letter-spacing:-.01em; font-size:.82rem;letter-spacing:-.01em;
overflow-wrap:anywhere;word-break:break-word;
} }
.ic{display:block;width:16px;height:16px} .ic{display:block;width:16px;height:16px}
.tick{fill:none;stroke:var(--sage);stroke-width:2.1;stroke-linecap:round} .tick{fill:none;stroke:var(--sage);stroke-width:2.1;stroke-linecap:round}
/* not done: the ring is drawn, the tick is not. Terracotta inside the open bucket. */ /* not done: the ring is drawn, the tick is not. Terracotta inside a gated bucket. */
.open{fill:none;stroke:var(--ink-faint);stroke-width:1.6} .open{fill:none;stroke:var(--ink-faint);stroke-width:1.6}
.card.gate .open{stroke:var(--ribbon-soft)} .card.gate .open{stroke:var(--ribbon-soft)}
...@@ -215,7 +223,7 @@ ...@@ -215,7 +223,7 @@
} }
.moves input:focus-visible{outline:2px solid var(--ribbon);outline-offset:2px} .moves input:focus-visible{outline:2px solid var(--ribbon);outline-offset:2px}
.mtext{flex:1 1 auto;min-width:0} .mtext{flex:1 1 auto;min-width:0}
.mtext b{font-family:Georgia,serif;font-weight:600} .mtext b{font-family:Georgia,serif;font-weight:600;overflow-wrap:anywhere}
.mtext .why{display:block;color:var(--ink-soft);font-size:.89rem;line-height:1.35} .mtext .why{display:block;color:var(--ink-soft);font-size:.89rem;line-height:1.35}
.moves input:checked ~ .mtext{color:var(--ink-faint);text-decoration:line-through;text-decoration-thickness:1.2px} .moves input:checked ~ .mtext{color:var(--ink-faint);text-decoration:line-through;text-decoration-thickness:1.2px}
.moves input:checked ~ .mtext .why{color:var(--ink-faint)} .moves input:checked ~ .mtext .why{color:var(--ink-faint)}
...@@ -236,11 +244,12 @@ ...@@ -236,11 +244,12 @@
.counter{white-space:normal} .counter{white-space:normal}
.sub .clause{display:block;white-space:normal} .sub .clause{display:block;white-space:normal}
.sub .sep{display:none} .sub .sep{display:none}
.timeline{padding:32px 8px 14px} .timeline{padding:12px 8px 14px}
.day{font-size:.72rem;letter-spacing:.03em} .day{font-size:.72rem;letter-spacing:.03em}
.what{font-size:.82rem;padding:0 4px;min-height:2.8em} .what{font-size:.8rem;padding:0 4px;min-height:4.2em}
.cd{font-size:.74rem} .cd{font-size:.74rem}
.figure{font-size:2.1rem} .figure{font-size:2.1rem}
.card.gate .figure{font-size:1.7rem}
.moves{padding:18px 16px 15px} .moves{padding:18px 16px 15px}
.moves h2{font-size:1.12rem} .moves h2{font-size:1.12rem}
} }
...@@ -260,26 +269,34 @@ ...@@ -260,26 +269,34 @@
<div class="wrap"> <div class="wrap">
<header> <header>
<h1><span class="goal nb">AI ENGINEER</span> &middot; <span class="nb">2h livecoding set</span></h1> <h1><span class="goal nb">AI ENGINEER</span> &middot; <span class="nb">today</span></h1>
<p class="sub">Gig <b class="nb">Thursday 24 September 2026, evening</b> <span class="clause"><span class="sep">&middot; </span>six buckets, one still open</span></p> <p class="sub">Gig <b class="nb">Thursday 24 September 2026</b> <span class="clause"><span class="sep">&middot; </span>2h, 17 tracks, one hour of ears</span></p>
<div class="counters"> <div class="counters">
<span class="counter hot"><b id="c-gig">&mdash;</b> <span class="lbl">&middot; <span id="c-gig-day">Thu 24 Sept</span></span></span> <span class="counter hot"><b id="c-gig">&mdash;</b></span>
<span class="counter"><span class="lbl">set</span> <b>17 tracks &middot; 2h</b></span>
<span class="counter"><span class="lbl">on you now</span> <b>3 moves</b></span> <span class="counter"><span class="lbl">on you now</span> <b>3 moves</b></span>
</div> </div>
</header> </header>
<section class="timeline" aria-label="Timeline"> <section class="timeline" aria-label="Timeline">
<span class="tl-cap" id="tl-cap">&mdash;</span>
<div class="track"> <div class="track">
<div class="stop"> <div class="stop">
<span class="dot"></span> <span class="dot"></span>
<span class="day">&mdash;</span> <span class="day">&mdash;</span>
<span class="what">The 2h arc is yours</span> <span class="what">Root causes found, fixes scripted</span>
<span class="cd">&mdash;</span>
</div>
<div class="stop">
<span class="dot"></span>
<span class="day">&mdash;</span>
<span class="what">The only sound test &middot; ears, not work</span>
<span class="cd">&mdash;</span> <span class="cd">&mdash;</span>
</div> </div>
<div class="stop finale"> <div class="stop finale">
<span class="dot"></span> <span class="dot"></span>
<span class="day">&mdash;</span> <span class="day">&mdash;</span>
<span class="what">AI ENGINEER &middot; doors, evening</span> <span class="what">Doors, then the 2h set</span>
<span class="cd">&mdash;</span> <span class="cd">&mdash;</span>
</div> </div>
</div> </div>
...@@ -290,78 +307,79 @@ ...@@ -290,78 +307,79 @@
<article class="card"> <article class="card">
<h2>Set <span class="step">1</span></h2> <h2>Set <span class="step">1</span></h2>
<p class="figure done">100%</p> <p class="figure done">100%</p>
<p class="mood">Finished. Nothing left to choose.</p> <p class="mood">Finished. Every track boots.</p>
<div class="bar" role="img" aria-label="set 100 percent"><div class="fill" style="width:100%"></div></div> <div class="bar" role="img" aria-label="set 100 percent"><div class="fill" style="width:100%"></div></div>
<ul> <ul>
<li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span>17 tracks, three movements, play order</span></li> <li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span>17/17 compile, seeded</span></li>
<li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span>All pass silent-eval seeded</span></li> <li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span><span class="mono">liquid_nite</span>: dangling dot, fixed</span></li>
<li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span><span class="mono">pvlint</span> over the set: 0 errors</span></li> <li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span><span class="mono">piment_bresilien</span>: stray audition line, fixed</span></li>
<li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span>Preload 132 banks, nothing lost</span></li> <li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span>d9 sample bank found on disk</span></li>
<li><span class="mark"><svg class="ic"><use href="#i-open"/></svg></span><span><span class="badge">you</span>Preload short by five banks</span></li>
</ul> </ul>
</article> </article>
<article class="card"> <article class="card">
<h2>Corpus <span class="step">2</span></h2> <h2>Rehearsal <span class="step">2</span></h2>
<p class="figure done">100%</p> <p class="figure near">35%</p>
<p class="mood">Settled. Re-plan finds nothing left.</p> <p class="mood">Six of seventeen. He trusts them.</p>
<div class="bar" role="img" aria-label="corpus 100 percent"><div class="fill" style="width:100%"></div></div> <div class="bar" role="img" aria-label="rehearsal 35 percent"><div class="fill near" style="width:35%"></div></div>
<ul> <ul>
<li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span>685 moves across 148 files, 0 clashes</span></li> <li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span>Played to <span class="mono">take_5_drops</span></span></li>
<li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span>Button conformance 78.9% &rarr; 93.3%</span></li> <li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span>&ldquo;not bad i am confident&rdquo;</span></li>
<li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span>Re-plan now reports 0 moves</span></li> <li><span class="mark"><svg class="ic"><use href="#i-open"/></svg></span><span>Eleven tracks never played in sequence</span></li>
<li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span>Zero verdict changes, verified three times</span></li> <li><span class="mark"><svg class="ic"><use href="#i-open"/></svg></span><span><span class="badge">you</span>5&ndash;6pm is the only sound test</span></li>
</ul> </ul>
</article> </article>
<article class="card"> <article class="card gate">
<h2>Audio <span class="step">3</span></h2> <h2>Rig <span class="step">3</span> <span class="badge">you</span></h2>
<p class="figure near">85%</p> <p class="figure">gated</p>
<p class="mood">Repaired this morning. One leg unproven.</p> <p class="mood">One script away. Then it&rsquo;s safe.</p>
<div class="bar" role="img" aria-label="audio 85 percent"><div class="fill near" style="width:85%"></div></div> <div class="bar pending" role="img" aria-label="rig gated, waiting on one root step"></div>
<ul> <ul>
<li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span>Laptop card back on HiFi profile</span></li> <li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span>SuperDirt boots in 6s again</span></li>
<li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span>NVIDIA pin retired, card now off</span></li> <li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span><span class="mono">MIDIIn.connectAll</span> patched out of boot</span></li>
<li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span>5 real sinks, was 4 dead ones</span></li> <li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span>Orbits routed, no speaker leak</span></li>
<li><span class="mark"><svg class="ic"><use href="#i-open"/></svg></span><span><span class="badge">you</span><span class="mono">UMC202HD</span>: still never tested</span></li> <li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span><span class="mono">fix-ardour-rt.sh</span> written, verified on copies</span></li>
<li><span class="mark"><svg class="ic"><use href="#i-open"/></svg></span><span><span class="badge">you</span>Run it: <span class="mono">sudo tools/fix-ardour-rt.sh --apply</span></span></li>
<li><span class="mark"><svg class="ic"><use href="#i-open"/></svg></span><span><span class="badge">you</span><span class="mono">parvagues-protect</span> OFF until then</span></li>
<li><span class="mark"><svg class="ic"><use href="#i-open"/></svg></span><span><span class="badge">you</span><span class="mono">thermald</span> OFF, restart it</span></li>
</ul> </ul>
</article> </article>
<article class="card"> <article class="card gate">
<h2>Surface <span class="step">4</span></h2> <h2>Perf <span class="step">4</span> <span class="badge">you</span></h2>
<p class="figure near">95%</p> <p class="figure">gated</p>
<p class="mood">Live for the first time.</p> <p class="mood">Owner found. The choice is yours.</p>
<div class="bar" role="img" aria-label="surface 95 percent"><div class="fill near" style="width:95%"></div></div> <div class="bar pending" role="img" aria-label="perf gated, the decision is the owner's"></div>
<ul> <ul>
<li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span>Driver up; 1077 silent failures explained</span></li> <li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span>Cap is <span class="mono">thermal-pilot</span> rung 1, proven</span></li>
<li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span>v3 map moved into the grid</span></li> <li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span><span class="mono">gig-up</span> now reads knobs, not wishes</span></li>
<li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span><span class="mono">surface-state.json</span> live, first time ever</span></li> <li><span class="mark"><svg class="ic"><use href="#i-open"/></svg></span><span><span class="badge">you</span>PL1 25W on USB-C vs 45W stock</span></li>
<li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span><span class="mono">midimon</span> now reads true</span></li> <li><span class="mark"><svg class="ic"><use href="#i-open"/></svg></span><span><span class="badge">you</span>Try the Dell barrel charger first</span></li>
</ul> </ul>
</article> </article>
<article class="card"> <article class="card gate">
<h2>Observability <span class="step">5</span></h2> <h2>Mix <span class="step">5</span> <span class="badge">you</span></h2>
<p class="figure near">95%</p> <p class="figure">gated</p>
<p class="mood">The lies are out of the checks.</p> <p class="mood">No tool can see your desk.</p>
<div class="bar" role="img" aria-label="observability 95 percent"><div class="fill near" style="width:95%"></div></div> <div class="bar pending" role="img" aria-label="mix gated, only the human can act"></div>
<ul> <ul>
<li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span>MIDI log live, corpus numbering, port named</span></li> <li><span class="mark"><svg class="ic"><use href="#i-open"/></svg></span><span><span class="badge">you</span>Master fader, then Ctrl+S</span></li>
<li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span>Gate no longer skips the painter</span></li> <li><span class="mark"><svg class="ic"><use href="#i-open"/></svg></span><span><span class="badge">you</span>Ardour OSC still off</span></li>
<li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span>Dead-sink detector added</span></li>
<li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span>Stale-build detector added</span></li>
</ul> </ul>
</article> </article>
<article class="card gate"> <article class="card">
<h2>Rehearsal <span class="step">6</span> <span class="badge">you</span></h2> <h2>Observability <span class="step">6</span></h2>
<p class="figure">0%</p> <p class="figure near">90%</p>
<p class="mood">The critical path. Yours alone.</p> <p class="mood">It was always right. Nobody asked.</p>
<div class="bar pending" role="img" aria-label="rehearsal 0 percent, not started"></div> <div class="bar" role="img" aria-label="observability 90 percent"><div class="fill near" style="width:90%"></div></div>
<ul> <ul>
<li><span class="mark"><svg class="ic"><use href="#i-open"/></svg></span><span><span class="badge">you</span>2h arc, never once run</span></li> <li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span><span class="mono">gig-log</span> recording CC, 145 rows</span></li>
<li><span class="mark"><svg class="ic"><use href="#i-open"/></svg></span><span>Ardour recording all orbits</span></li> <li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span><span class="mono">xrun_by</span> named Ardour in one query</span></li>
<li><span class="mark"><svg class="ic"><use href="#i-open"/></svg></span><span>UMC plugged for the last stretch</span></li> <li><span class="mark"><svg class="ic"><use href="#i-open"/></svg></span><span>Stale-address rebind still post-gig</span></li>
<li><span class="mark"><svg class="ic"><use href="#i-open"/></svg></span><span>xruns re-measured clean</span></li>
</ul> </ul>
</article> </article>
...@@ -372,32 +390,32 @@ ...@@ -372,32 +390,32 @@
<ol> <ol>
<li> <li>
<label> <label>
<input type="checkbox" data-move="arc2h"> <input type="checkbox" data-move="rtfix">
<span class="num"></span> <span class="num"></span>
<span class="mtext"><b>Run the 2h arc, Ardour recording</b><span class="why">Unblocks everything</span></span> <span class="mtext"><b class="mono">sudo tools/fix-ardour-rt.sh --apply</b><span class="why">Unblocks the whole rig</span></span>
</label> </label>
</li> </li>
<li> <li>
<label> <label>
<input type="checkbox" data-move="umc"> <input type="checkbox" data-move="charger">
<span class="num"></span> <span class="num"></span>
<span class="mtext"><b>Plug the UMC for the last stretch</b><span class="why">The only untested path</span></span> <span class="mtext"><b>Plug the Dell barrel charger</b><span class="why">The cap may close for free</span></span>
</label> </label>
</li> </li>
<li> <li>
<label> <label>
<input type="checkbox" data-move="save"> <input type="checkbox" data-move="fader">
<span class="num"></span> <span class="num"></span>
<span class="mtext"><b>Set the Master, then Ctrl+S</b><span class="why">check-mix reads the saved session</span></span> <span class="mtext"><b>Master fader &rarr; Ctrl+S</b><span class="why">The one thing no tool can read</span></span>
</label> </label>
</li> </li>
</ol> </ol>
</section> </section>
<footer> <footer>
<p class="path"><b>SET</b> &rarr; <b>CORPUS</b> &rarr; <b>AUDIO</b> &rarr; <b>SURFACE</b> &rarr; <b>OBSERVABILITY</b> &rarr; <b>REHEARSAL</b> &mdash; only the last one is still open.</p> <p class="path"><b>fix-ardour-rt</b> &rarr; <b>protect back on</b> &rarr; <b>charger</b> &rarr; <b>faders</b> &rarr; <b>5pm ears</b></p>
<p class="tiny">Gate 10:40, rig warm: 0 fail, 3 warn, 39 ok &middot; 132/132 banks in 6.8s &middot; 0 xruns/hour. Proves the set BOOTS correct, not that the mixer IS correct.</p> <p class="tiny">Gate 01:25 &middot; 31 ok, 6 fail, 5 warn. The compile fail is paid; the cap items and your desk are the rest.</p>
<p class="tiny">Not this project: post-gig cleanup (11 items), corpus-wide remap, Ardour OSC.</p> <p class="tiny">Not this project, explicitly not today: the latch double-press, gig-log&rsquo;s liveness rebind, the LCXL chord-reset and the page/logo dawless UX.</p>
</footer> </footer>
</div> </div>
...@@ -407,9 +425,10 @@ ...@@ -407,9 +425,10 @@
(function(){ (function(){
var MS = 86400000; var MS = 86400000;
var WD = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]; var WD = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"];
var MO = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sept","Oct","Nov","Dec"];
function midnight(d){ return new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime(); } function midnight(d){ return new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime(); }
/* "2026-09-24" or "2026-09-24T19:00" -> a local-wall-clock Date, never UTC-shifted. */ /* "2026-09-24" or "2026-09-24T17:00" -> a local-wall-clock Date, never UTC-shifted. */
function parseDt(s){ function parseDt(s){
var m = String(s).split("T"); var m = String(s).split("T");
var p = m[0].split("-"); var p = m[0].split("-");
...@@ -417,124 +436,205 @@ ...@@ -417,124 +436,205 @@
return new Date(+p[0], +p[1] - 1, +p[2], +t[0], +t[1] || 0, 0, 0); return new Date(+p[0], +p[1] - 1, +p[2], +t[0], +t[1] || 0, 0, 0);
} }
/* Whole calendar days from a to b: positive when b is in the future. /* Whole calendar days from a to b: positive when b is in the future.
Calendar days, not elapsed hours, so "tomorrow evening" cannot flip at 18:00 tonight. */ Calendar days, not elapsed hours, so "today" cannot flip at 17:00 tonight. */
function daysBetween(a, b){ return Math.round((midnight(b) - midnight(a)) / MS); } function daysBetween(a, b){ return Math.round((midnight(b) - midnight(a)) / MS); }
function hhmm(d){
var h = d.getHours(), m = d.getMinutes();
return h + ":" + (m < 10 ? "0" + m : m);
}
function dayLabel(d){ return WD[d.getDay()] + " " + d.getDate() + " " + MO[d.getMonth()]; }
/* WHAT IS SOURCED AND WHAT IS NOT -- read this before adding a time here.
Sourced: the gig is Thursday 24 September 2026, and the sound test is the
5pm-6pm window, "end of talks -> doors" (TODO_GIG.md:506-507).
NOT SOURCED: the doors time. There is no entry for this gig in
~/Work/Perso/www/content/lives/2026/ -- the canonical home for venue, set
title, lineup and times -- and TODO_GIG.md never states one. The project
rule is "mastering != metadata, never invent gig metadata; if a fact isn't
in the canonical source, ask". 19:00 and 18:00 have each been guessed here
once; the second was derived from the sound test's end, which is exactly the
inference to avoid. So every countdown on this page anchors on 17:00, and the
set reads "time tbc" until somebody puts the real time in the canonical file. */
var GIGDAY = parseDt("2026-09-24");
var TEST = parseDt("2026-09-24T17:00");
var TESTLEN = 60; /* 5pm-6pm, one hour (TODO_GIG.md:506-507) */
/* "in 4h" / "in 25min" / "in 3 days". Never "in 0", never negative. */
function away(ms){
if (ms <= 0) return "now";
var d = Math.floor(ms / MS);
if (d >= 1) return "in " + d + (d === 1 ? " day" : " days");
var h = Math.floor(ms / 3600000);
if (h >= 1) return "in " + h + "h";
return "in " + Math.max(1, Math.ceil(ms / 60000)) + "min";
}
/* Doors, as the evening the set is announced for. */ /* The live header phrase, anchored on the one hour we can actually cite.
var GIG = parseDt("2026-09-24T19:00"); After the window closes it stops counting, because there is nothing sourced
left to count to -- "set tonight" is all the page is entitled to say. */
function gigDays(now){ return daysBetween(now, GIG); } function testPhrase(now){
var d = daysBetween(now, TEST);
/* The one live phrase in the header. Never negative, never "in 1 days". */ if (d > 1) return "sound test in " + d + " days";
function gigPhrase(now){ if (d === 1) return "sound test tomorrow";
var d = gigDays(now);
if (d > 1) return "gig in " + d + " days";
if (d === 1) return "gig tomorrow evening";
if (d === 0){ if (d === 0){
var h = Math.floor((GIG.getTime() - now.getTime()) / 3600000); var ms = TEST.getTime() - now.getTime();
if (h >= 1) return "tonight &middot; " + h + "h to doors"; if (ms > 0) return "sound test " + away(ms);
if (GIG.getTime() - now.getTime() > 0) return "doors within the hour"; if (now.getTime() < TEST.getTime() + TESTLEN * 60000) return "sound test now";
return "on stage now"; return "set tonight";
} }
if (d === -1) return "played last night"; if (d === -1) return "played last night";
return "played " + (-d) + " days ago"; return "played " + (-d) + " days ago";
} }
function dayLabel(d){ return WD[d.getDay()] + " " + d.getDate() + " Sept"; }
/* Per-stop countdown. Past tense for days gone, plain words for days ahead. */ /* Per-stop countdown. A window says "happening now" while it is open;
function stopPhrase(now, target, finale){ a stop with no sourced time says only what it follows. */
function stopPhrase(now, target, opt){
opt = opt || {};
var d = daysBetween(now, target); var d = daysBetween(now, target);
if (d > 1) return "in " + d + " days"; if (d > 1) return "in " + d + " days";
if (d === 1) return finale ? "tomorrow evening" : "tomorrow"; if (d === 1) return "tomorrow";
if (d === 0) return finale ? "doors tonight" : "today";
if (d === -1) return "yesterday"; if (d === -1) return "yesterday";
return (-d) + " days ago"; if (d < -1) return (-d) + " days ago";
if (opt.tbc) return "after the sound test";
var ms = target.getTime() - now.getTime();
if (!opt.timed) return ms > 0 ? "today" : "earlier today";
if (ms > 0) return away(ms);
if (opt.dur && now.getTime() < target.getTime() + opt.dur * 60000) return "happening now";
return "window closed";
} }
/* Stops in DOM order. Dates live here only, never typed into the markup. */ /* Stops in DOM order. Dates live here only, never typed into the markup.
The finale carries the DAY, which is sourced, and no clock time, which is not. */
var STOPS = [ var STOPS = [
{ dt: "2026-09-23", finale: false }, { dt: "2026-09-24T02:00", timed: false },
{ dt: "2026-09-24T19:00", finale: true } { dt: "2026-09-24T17:00", timed: true, dur: TESTLEN },
{ dt: "2026-09-24", tbc: true, finale: true }
]; ];
/* Pure: every string the page shows, derived from one instant. Testable on its own. */ /* Pure: every string the page shows, derived from one instant. Testable on its own. */
function phrases(now){ function phrases(now){
var out = { gig: gigPhrase(now), gigDay: dayLabel(GIG), days: gigDays(now), stops: [] }; var out = {
test: testPhrase(now),
cap: dayLabel(GIGDAY) + (daysBetween(now, GIGDAY) === 0 ? " \u00b7 today" : ""),
stops: []
};
for (var i = 0; i < STOPS.length; i++){ for (var i = 0; i < STOPS.length; i++){
var s = STOPS[i]; var s = STOPS[i];
var d = parseDt(s.dt); var d = parseDt(s.dt);
var delta = daysBetween(now, d); var delta = daysBetween(now, d);
var ends = d.getTime() + ((s.dur || 0) * 60000);
out.stops.push({ out.stops.push({
day: dayLabel(d), day: s.tbc ? "time tbc" : (s.timed ? hhmm(d) : "overnight"),
cd: stopPhrase(now, d, s.finale), cd: stopPhrase(now, d, s),
sameDay: delta === 0, sameDay: delta === 0,
past: delta < 0 /* spent: the clock is past it, so the ribbon moves on. A stop with no
known time is never spent -- we cannot claim the set is behind us. */
spent: !s.tbc && ends <= now.getTime(),
/* gone: a closed WINDOW, safe to dim. Nothing else is ever dimmed. */
gone: !!s.timed && ends <= now.getTime()
}); });
} }
/* "Today" belongs to whichever stop the clock says it is; past the gig, nobody wears it. */
return out; return out;
} }
window.gigClock = { window.gigClock = {
parseDt: parseDt, midnight: midnight, daysBetween: daysBetween, parseDt: parseDt, midnight: midnight, daysBetween: daysBetween, away: away,
gigDays: gigDays, gigPhrase: gigPhrase, stopPhrase: stopPhrase, testPhrase: testPhrase, stopPhrase: stopPhrase, hhmm: hhmm,
dayLabel: dayLabel, phrases: phrases, stopCount: STOPS.length dayLabel: dayLabel, phrases: phrases, stopCount: STOPS.length
}; };
var fails = []; var fails = [];
[["2026-09-21",3],["2026-09-23",1],["2026-09-24",0],["2026-09-25",-1],["2026-09-30",-6]] /* Day before, the morning of, mid-afternoon, during the window, after it. */
.forEach(function(c){ [["2026-09-21T09:00","sound test in 3 days"],
var got = gigDays(parseDt(c[0])); ["2026-09-23T10:00","sound test tomorrow"],
if (got !== c[1]) fails.push("gigDays " + c[0] + " expected " + c[1] + ", got " + got); ["2026-09-23T23:59","sound test tomorrow"],
}); ["2026-09-24T01:40","sound test in 15h"],
[["2026-09-21T09:00","gig in 3 days"], ["2026-09-24T09:00","sound test in 8h"],
["2026-09-23T00:05","gig tomorrow evening"], ["2026-09-24T15:00","sound test in 2h"],
["2026-09-23T18:00","gig tomorrow evening"], ["2026-09-24T16:30","sound test in 30min"],
["2026-09-23T23:59","gig tomorrow evening"], ["2026-09-24T16:59","sound test in 1min"],
["2026-09-24T09:00","tonight &middot; 10h to doors"], ["2026-09-24T17:00","sound test now"],
["2026-09-24T18:00","tonight &middot; 1h to doors"], ["2026-09-24T17:30","sound test now"],
["2026-09-24T18:30","doors within the hour"], ["2026-09-24T17:59","sound test now"],
["2026-09-24T19:00","on stage now"], ["2026-09-24T18:00","set tonight"],
["2026-09-24T21:30","on stage now"], ["2026-09-24T19:30","set tonight"],
["2026-09-24T23:30","set tonight"],
["2026-09-25T10:00","played last night"], ["2026-09-25T10:00","played last night"],
["2026-09-28T10:00","played 4 days ago"]].forEach(function(c){ ["2026-09-28T10:00","played 4 days ago"]].forEach(function(c){
var got = gigPhrase(parseDt(c[0])); var got = testPhrase(parseDt(c[0]));
if (got !== c[1]) fails.push("gigPhrase " + c[0] + " expected " + c[1] + ", got " + got); if (got !== c[1]) fails.push("testPhrase " + c[0] + " expected " + c[1] + ", got " + got);
}); });
[["2026-09-23T10:00",["today","tomorrow evening"]], [["2026-09-23T10:00",["tomorrow","tomorrow","tomorrow"]],
["2026-09-24T10:00",["yesterday","doors tonight"]], ["2026-09-24T01:40",["today","in 15h","after the sound test"]],
["2026-09-26T10:00",["3 days ago","2 days ago"]], ["2026-09-24T09:00",["earlier today","in 8h","after the sound test"]],
["2026-09-21T10:00",["in 2 days","in 3 days"]]].forEach(function(c){ ["2026-09-24T15:00",["earlier today","in 2h","after the sound test"]],
["2026-09-24T17:20",["earlier today","happening now","after the sound test"]],
["2026-09-24T19:30",["earlier today","window closed","after the sound test"]],
["2026-09-25T10:00",["yesterday","yesterday","yesterday"]]].forEach(function(c){
var got = phrases(parseDt(c[0])).stops.map(function(s){ return s.cd; }); var got = phrases(parseDt(c[0])).stops.map(function(s){ return s.cd; });
if (got.join("|") !== c[1].join("|")) fails.push("stops " + c[0] + " expected " + c[1].join("|") + ", got " + got.join("|")); if (got.join("|") !== c[1].join("|")) fails.push("stops " + c[0] + " expected " + c[1].join("|") + ", got " + got.join("|"));
}); });
/* Weekdays are derived, never typed: 23 Sept 2026 is a Wednesday, 24 Sept a Thursday. */ /* Weekday and clock labels are derived, never typed: 24 Sept 2026 is a Thursday. */
[["2026-09-23",3],["2026-09-24",4]].forEach(function(c){ if (parseDt("2026-09-24").getDay() !== 4) fails.push("24 Sept 2026 is not reading as Thursday");
var got = parseDt(c[0]).getDay(); if (dayLabel(GIGDAY) !== "Thu 24 Sept") fails.push("dayLabel: got " + dayLabel(GIGDAY));
if (got !== c[1]) fails.push(c[0] + " weekday expected " + c[1] + ", got " + got); if (hhmm(TEST) !== "17:00") fails.push("hhmm: got " + hhmm(TEST));
});
if (daysBetween(parseDt("2026-08-31"), parseDt("2026-09-01")) !== 1) fails.push("month rollover"); if (daysBetween(parseDt("2026-08-31"), parseDt("2026-09-01")) !== 1) fails.push("month rollover");
/* Exactly one stop may claim today, and only while the gig is not yet past. */ /* The stop rail carries exactly one clock time, and it is the sourced one. */
[["2026-09-23T10:00",1],["2026-09-24T10:00",1],["2026-09-25T10:00",0],["2026-09-21T10:00",0]] [["2026-09-24T09:00"],["2026-09-24T19:30"],["2026-09-23T10:00"]].forEach(function(c){
var got = phrases(parseDt(c[0])).stops.map(function(s){ return s.day; }).join("|");
if (got !== "overnight|17:00|time tbc") fails.push("stop labels " + c[0] + " got " + got);
});
/* Every stop is today, so today-count is 3 on the day and 0 once the day turns. */
[["2026-09-24T01:40",3],["2026-09-24T21:00",3],["2026-09-25T10:00",0],["2026-09-23T10:00",0]]
.forEach(function(c){ .forEach(function(c){
var n = phrases(parseDt(c[0])).stops.filter(function(s){ return s.sameDay; }).length; var n = phrases(parseDt(c[0])).stops.filter(function(s){ return s.sameDay; }).length;
if (n !== c[1]) fails.push("today-count " + c[0] + " expected " + c[1] + ", got " + n); if (n !== c[1]) fails.push("today-count " + c[0] + " expected " + c[1] + ", got " + n);
}); });
if (STOPS.length !== 2) fails.push("STOPS length changed without updating the tests"); /* The ribbon marker advances as the day does; the timeless finale never becomes spent. */
/* No phrase may carry a stale-wording artefact, on any hour of any nearby day. */ [["2026-09-24T01:40",0],["2026-09-24T08:30",1],["2026-09-24T18:30",2],["2026-09-24T21:00",2]]
.forEach(function(c){
var n = phrases(parseDt(c[0])).stops.filter(function(s){ return s.spent; }).length;
if (n !== c[1]) fails.push("spent-count " + c[0] + " expected " + c[1] + ", got " + n);
});
/* Only the sound-test window can be dimmed. */
[["2026-09-24T01:40",0],["2026-09-24T17:30",0],["2026-09-24T18:30",1],["2026-09-24T21:00",1]]
.forEach(function(c){
var n = phrases(parseDt(c[0])).stops.filter(function(s){ return s.gone; }).length;
if (n !== c[1]) fails.push("gone-count " + c[0] + " expected " + c[1] + ", got " + n);
});
/* The ribbon has exactly one home all gig day, and none once the day turns. */
[["2026-09-24T01:40",1],["2026-09-24T08:30",1],["2026-09-24T17:20",1],["2026-09-24T19:30",1],
["2026-09-24T23:30",1],["2026-09-25T10:00",0],["2026-09-23T10:00",0]]
.forEach(function(c){
var st = phrases(parseDt(c[0])).stops.filter(function(s){ return s.sameDay && !s.spent; });
var n = st.length ? 1 : 0;
if (n !== c[1]) fails.push("ribbon-count " + c[0] + " expected " + c[1] + ", got " + n);
});
if (STOPS.length !== 3) fails.push("STOPS length changed without updating the tests");
/* No phrase may carry a stale-wording artefact, and NO phrase may carry a clock
time other than the sourced 17:00 -- that assertion is the mechanical guard
against somebody guessing a doors time back into this file. */
var BAD = /(\bin -|\bin 0\b|\bin 1 days\b|\b-\d|NaN|undefined)/; var BAD = /(\bin -|\bin 0\b|\bin 1 days\b|\b-\d|NaN|undefined)/;
for (var probe = parseDt("2026-09-15").getTime(); probe <= parseDt("2026-10-05").getTime(); probe += 3600000){ var CLOCK = /\b([01]?\d|2[0-3]):[0-5]\d\b/g;
for (var probe = parseDt("2026-09-15").getTime(); probe <= parseDt("2026-10-05").getTime(); probe += 1800000){
var pp = phrases(new Date(probe)); var pp = phrases(new Date(probe));
var strs = [pp.gig, pp.gigDay]; var strs = [pp.test, pp.cap];
for (var q = 0; q < pp.stops.length; q++) strs.push(pp.stops[q].cd, pp.stops[q].day); for (var q = 0; q < pp.stops.length; q++) strs.push(pp.stops[q].cd, pp.stops[q].day);
var stop = false;
for (var r = 0; r < strs.length; r++){ for (var r = 0; r < strs.length; r++){
if (BAD.test(strs[r])) { fails.push("bad phrase at " + new Date(probe) + ": " + strs[r]); probe = Infinity; break; } if (BAD.test(strs[r])) { fails.push("bad phrase at " + new Date(probe) + ": " + strs[r]); stop = true; break; }
var hits = String(strs[r]).match(CLOCK) || [];
for (var t = 0; t < hits.length; t++){
if (hits[t] !== "17:00"){ fails.push("unsourced clock time " + hits[t] + " in: " + strs[r]); stop = true; }
}
if (stop) break;
} }
if (stop) break;
} }
if (fails.length){ /* Silent when it passes; only a failure is allowed to speak. Read it from
if (typeof console !== "undefined") console.error("DATE-MATH CHECK FAILED:\n " + fails.join("\n ")); window.gigClock.selfTest (empty array == PASS) while working on the page. */
} else if (typeof console !== "undefined") { if (fails.length && typeof console !== "undefined"){
console.log("date-math check: PASS"); console.error("DATE-MATH CHECK FAILED:\n " + fails.join("\n "));
} }
window.gigClock.selfTest = fails; window.gigClock.selfTest = fails;
})(); })();
...@@ -542,32 +642,39 @@ ...@@ -542,32 +642,39 @@
<script> <script>
(function(){ (function(){
var C = window.gigClock; var C = window.gigClock;
var p = C.phrases(new Date()); function paint(){
function put(id, html){ var el = document.getElementById(id); if (el) el.innerHTML = html; } var p = C.phrases(new Date());
function put(id, html){ var el = document.getElementById(id); if (el) el.innerHTML = html; }
put("c-gig", p.gig); put("c-gig", p.test);
put("c-gig-day", p.gigDay); put("tl-cap", p.cap);
var stops = document.querySelectorAll(".track .stop"); var stops = document.querySelectorAll(".track .stop");
var todayEl = null; var todayEl = null;
for (var i = 0; i < stops.length && i < p.stops.length; i++){ for (var i = 0; i < stops.length && i < p.stops.length; i++){
var s = stops[i], q = p.stops[i]; var s = stops[i], q = p.stops[i];
var day = s.querySelector(".day"); if (day) day.textContent = q.day; var day = s.querySelector(".day"); if (day) day.textContent = q.day;
var cd = s.querySelector(".cd"); if (cd) cd.innerHTML = q.cd; var cd = s.querySelector(".cd"); if (cd) cd.innerHTML = q.cd;
if (q.sameDay && !todayEl) todayEl = s; s.classList.toggle("gone", !!q.gone);
} /* the ribbon belongs to the first stop today has not spent yet */
/* One ribbon only, on the stop the clock says is today. */ if (q.sameDay && !q.spent && !todayEl) todayEl = s;
if (todayEl){ var old = s.querySelector(".ribbon"); if (old) old.parentNode.removeChild(old);
todayEl.classList.add("today"); s.classList.remove("today");
var r = document.createElement("span"); }
r.className = "ribbon"; if (todayEl){
r.setAttribute("aria-hidden", "true"); todayEl.classList.add("today");
todayEl.insertBefore(r, todayEl.firstChild); var r = document.createElement("span");
r.className = "ribbon";
r.setAttribute("aria-hidden", "true");
todayEl.insertBefore(r, todayEl.firstChild);
}
} }
paint();
/* gig day: the counters move, so let them. One minute is fine for hours and minutes. */
setInterval(paint, 60000);
/* Next-3-moves ticks, remembered locally. New move list, new key: no stale ticks. /* Next-3-moves ticks, remembered locally. New move list, new key: no stale ticks.
Storage is optional: every access guarded, the page renders without it. */ Storage is optional: every access guarded, the page renders without it. */
var KEY = "pv.gig.thu24.moves.0923a"; var KEY = "pv.gig.thu24.moves.0924a";
var boxes = document.querySelectorAll(".moves input[type=checkbox]"); var boxes = document.querySelectorAll(".moves input[type=checkbox]");
var saved = {}; var saved = {};
try { try {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment