Commit a606d992 by PLN (Algolia)

docs(status): five buckets settled, the rehearsal is the only one still open

Rebuilt for Wed 23. Audio, surface and observability all moved this morning:
the laptop card is back on HiFi and the NVIDIA pin is retired, the LCXL3
driver's 1077 silent failures are explained and surface-state.json is live for
the first time, and the MIDI log binds a port at last after nine dead sessions.

The countdown computes from the real clock at every stop, so the page cannot
go stale into the gig; checked at five instants from today through three days
after.
parent 6ec3ca09
...@@ -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>Thursday 24 &middot; AI ENGINEER &middot; 2-hour set</title> <title>AI ENGINEER &middot; 2h livecoding set</title>
<style> <style>
:root{ :root{
--paper:#f6f1e5; --paper:#f6f1e5;
...@@ -17,8 +17,6 @@ ...@@ -17,8 +17,6 @@
--ribbon-soft:#d98f76; --ribbon-soft:#d98f76;
--sage:#6f8055; --sage:#6f8055;
--amber:#9d7517; --amber:#9d7517;
--amber-soft:#e0c98e;
--slate:#5b6f8a;
--shadow:rgba(90,74,48,.13); --shadow:rgba(90,74,48,.13);
} }
*{box-sizing:border-box} *{box-sizing:border-box}
...@@ -45,6 +43,8 @@ ...@@ -45,6 +43,8 @@
h1{font-size:2.05rem;line-height:1.2;margin:0 0 6px;letter-spacing:.1px} h1{font-size:2.05rem;line-height:1.2;margin:0 0 6px;letter-spacing:.1px}
h1 .goal{color:var(--ribbon)} h1 .goal{color:var(--ribbon)}
.sub{margin:0;color:var(--ink-soft);font-size:.96rem} .sub{margin:0;color:var(--ink-soft);font-size:.96rem}
.sub b{color:var(--ink);font-weight:600}
.sub .clause{white-space:nowrap}
.nb{white-space:nowrap} .nb{white-space:nowrap}
.counters{display:flex;flex-wrap:wrap;gap:10px;margin-top:15px} .counters{display:flex;flex-wrap:wrap;gap:10px;margin-top:15px}
.counter{ .counter{
...@@ -72,7 +72,7 @@ ...@@ -72,7 +72,7 @@
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:300px} .track{display:flex;align-items:flex-start;gap:0;min-width:260px}
.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,11 +88,11 @@ ...@@ -88,11 +88,11 @@
} }
.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}
/* two text lines reserved, so every countdown sits on one baseline */ /* one line reserved per stop: both labels fit on one line here, so both countdowns share a baseline */
.what{display:block;font-size:.88rem;color:var(--ink);padding:0 6px;line-height:1.35;min-height:2.5em} .what{display:block;font-size:.88rem;color:var(--ink);padding:0 6px;line-height:1.35;min-height:1.4em}
.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)} .stop.finale .cd{color:var(--ink-soft);font-weight:600}
/* ribbon on today */ /* ribbon on today */
.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}
...@@ -109,25 +109,10 @@ ...@@ -109,25 +109,10 @@
} }
/* ---------- buckets ---------- */ /* ---------- buckets ---------- */
/* five cards: a true 3 + 2, never a 4 + 1 orphan */ /* six cards: 1 / 2+2+2 / 3+3. No breakpoint can leave a card alone on a row. */
.buckets{ .buckets{display:grid;grid-template-columns:1fr;gap:18px}
display:grid; @media (min-width:640px){ .buckets{grid-template-columns:repeat(2,1fr)} }
grid-template-columns:1fr; @media (min-width:1000px){ .buckets{grid-template-columns:repeat(3,1fr)} }
gap:18px;
align-items:start;
}
/* two columns: the fifth card would sit alone, so it spans the row instead */
@media (min-width:660px){
.buckets{grid-template-columns:repeat(2,1fr)}
.card:nth-child(5){grid-column:1 / -1}
}
/* three columns: 3 on top, the last 2 centred beneath at the same width */
@media (min-width:1000px){
.buckets{grid-template-columns:repeat(6,1fr)}
.card{grid-column:span 2}
.card:nth-child(4){grid-column:2 / 4}
.card:nth-child(5){grid-column:4 / 6}
}
.card{ .card{
background:var(--paper-card); background:var(--paper-card);
border:1px solid var(--rule); border:1px solid var(--rule);
...@@ -137,15 +122,18 @@ ...@@ -137,15 +122,18 @@
} }
.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 gate leads the risk, so it wears the ribbon edge */ /* the open 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{font-size:.95rem;letter-spacing:.14em;text-transform:uppercase;margin:0;color:var(--ink-soft)} .card h2{
font-size:.95rem;letter-spacing:.14em;text-transform:uppercase;margin:0;color:var(--ink-soft);
display:flex;align-items:center;gap:7px;flex-wrap:wrap;
}
.card h2 .step{font-family:Georgia,serif;font-size:.85rem;letter-spacing:0;color:var(--ink-faint);text-transform:none}
.card.gate h2{color:var(--ribbon)} .card.gate h2{color:var(--ribbon)}
.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 small{font-size:.9rem;font-family:"Segoe UI",Roboto,Helvetica,Arial,sans-serif;color:var(--ink-faint);letter-spacing:.03em}
.card.gate .figure{color:var(--ribbon)}
.figure.done{color:var(--sage)} .figure.done{color:var(--sage)}
.figure.yours{color:var(--slate)} .figure.near{color:var(--amber)}
.card.gate .figure{color:var(--ribbon)}
.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;
...@@ -156,12 +144,13 @@ ...@@ -156,12 +144,13 @@
background-color:var(--sage); background-color:var(--sage);
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.low{background-color:var(--ribbon-soft)} .fill.near{background-color:var(--amber)}
/* the gate's own bar carries the full ribbon, so 20% still reads from a metre away */ /* nothing run yet: the track itself is pencilled in, so 0% reads as "waiting", not "broken" */
.card.gate .fill.low{background-color:var(--ribbon)} .bar.pending{
/* halfway: neither shipped nor stalled */ background-color:#f2e6dc;
.fill.mid{background-color:var(--amber)} background-image:repeating-linear-gradient(45deg, rgba(180,85,60,.26) 0 2px, transparent 2px 6px);
.fill.wait{background-color:var(--slate)} border-color:var(--ribbon-soft);
}
ul{list-style:none;margin:0;padding:0} ul{list-style:none;margin:0;padding:0}
li{display:flex;gap:8px;align-items:flex-start;padding:4px 0;font-size:.9rem;line-height:1.42} li{display:flex;gap:8px;align-items:flex-start;padding:4px 0;font-size:.9rem;line-height:1.42}
li+li{border-top:1px solid rgba(180,160,120,.16)} li+li{border-top:1px solid rgba(180,160,120,.16)}
...@@ -178,17 +167,16 @@ ...@@ -178,17 +167,16 @@
padding:1px 5px;vertical-align:1.5px; padding:1px 5px;vertical-align:1.5px;
text-transform:uppercase; text-transform:uppercase;
} }
h2 .badge{margin-right:0;vertical-align:0}
.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;
} }
svg{display:block} .ic{display:block;width:16px;height:16px}
.box{fill:none;stroke:var(--ink-faint);stroke-width:1.4}
.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}
/* half-done: the tick's first stroke landed, the rest is still pencilled in */ /* not done: the ring is drawn, the tick is not. Terracotta inside the open bucket. */
.half{fill:none;stroke:var(--ribbon);stroke-width:2.1;stroke-linecap:round} .open{fill:none;stroke:var(--ink-faint);stroke-width:1.6}
.half-rest{fill:none;stroke:var(--ribbon-soft);stroke-width:1.7;stroke-linecap:round;stroke-dasharray:1.6 2.2} .card.gate .open{stroke:var(--ribbon-soft)}
.warn{fill:none;stroke:var(--amber);stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}
/* ---------- next moves ---------- */ /* ---------- next moves ---------- */
.moves{ .moves{
...@@ -202,7 +190,7 @@ ...@@ -202,7 +190,7 @@
transform:rotate(-.3deg); transform:rotate(-.3deg);
position:relative; position:relative;
} }
.moves h2{font-size:1.25rem;text-transform:none;letter-spacing:0;color:var(--ink);margin:0 0 10px} .moves h2{font-size:1.25rem;text-transform:none;letter-spacing:0;color:var(--ink);margin:0 0 10px;display:block}
.moves ol{list-style:none;margin:0;padding:0;counter-reset:m} .moves ol{list-style:none;margin:0;padding:0;counter-reset:m}
.moves li{ .moves li{
counter-increment:m; counter-increment:m;
...@@ -240,33 +228,43 @@ ...@@ -240,33 +228,43 @@
footer p.path{color:var(--ink)} footer p.path{color:var(--ink)}
footer p.path b{font-family:Georgia,serif;color:var(--ribbon);font-weight:600} footer p.path b{font-family:Georgia,serif;color:var(--ribbon);font-weight:600}
footer p.tiny{color:var(--ink-faint);font-size:.82rem} footer p.tiny{color:var(--ink-faint);font-size:.82rem}
footer a{color:var(--ribbon);text-decoration:none;border-bottom:1px solid var(--ribbon-soft)}
footer a:hover{background:var(--paper-warm)}
@media (max-width:659px){ @media (max-width:639px){
.wrap{padding:24px 16px 44px} .wrap{padding:24px 16px 44px}
h1{font-size:1.55rem} h1{font-size:1.5rem}
.card,.moves{transform:none} .card,.moves{transform:none}
.counter{white-space:normal} .counter{white-space:normal}
.timeline{padding:32px 7px 14px} .sub .clause{display:block;white-space:normal}
.sub .sep{display:none}
.timeline{padding:32px 8px 14px}
.day{font-size:.72rem;letter-spacing:.03em} .day{font-size:.72rem;letter-spacing:.03em}
/* three text lines reserved at phone width, so the countdowns keep one baseline */ .what{font-size:.82rem;padding:0 4px;min-height:2.8em}
.what{font-size:.81rem;padding:0 3px;min-height:5em}
.cd{font-size:.74rem} .cd{font-size:.74rem}
.figure{font-size:2.1rem} .figure{font-size:2.1rem}
.moves{padding:18px 16px 15px}
.moves h2{font-size:1.12rem}
} }
</style> </style>
</head> </head>
<body> <body>
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
<symbol id="i-tick" viewBox="0 0 16 16">
<path class="tick" d="M2.4 9.2 C4.1 10.4 5.2 11.7 6.1 13.4 C8.2 8.4 10.6 5.1 14 2.6"/>
</symbol>
<symbol id="i-open" viewBox="0 0 16 16">
<circle class="open" cx="8" cy="8" r="5.6"/>
</symbol>
</svg>
<div class="wrap"> <div class="wrap">
<header> <header>
<h1><span class="nb">Thursday 24</span> &middot; <span class="goal nb">AI ENGINEER</span></h1> <h1><span class="goal nb">AI ENGINEER</span> &middot; <span class="nb">2h livecoding set</span></h1>
<p class="sub"><span class="nb">ParVagues &middot; 2-hour set</span> &middot; <span class="nb">five buckets, one gate</span> &middot; <span class="nb">glance and close</span></p> <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>
<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 class="lbl">&middot; <span id="c-gig-day">Thu 24 Sept</span></span></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>
<span class="counter"><b id="c-yours">&mdash;</b> <span class="lbl">only you can do</span></span>
</div> </div>
</header> </header>
...@@ -275,19 +273,13 @@ ...@@ -275,19 +273,13 @@
<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">Set settled &middot; faders back</span> <span class="what">The 2h arc is yours</span>
<span class="cd">&mdash;</span>
</div>
<div class="stop">
<span class="dot"></span>
<span class="day">&mdash;</span>
<span class="what">Full run &middot; preflight</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; 2-hour set</span> <span class="what">AI ENGINEER &middot; doors, evening</span>
<span class="cd">&mdash;</span> <span class="cd">&mdash;</span>
</div> </div>
</div> </div>
...@@ -296,79 +288,80 @@ ...@@ -296,79 +288,80 @@
<section class="buckets" aria-label="Buckets"> <section class="buckets" aria-label="Buckets">
<article class="card"> <article class="card">
<h2>Set</h2> <h2>Set <span class="step">1</span></h2>
<p class="figure done">100%</p> <p class="figure done">100%</p>
<p class="mood">Settled. Seventeen tracks, in order.</p> <p class="mood">Finished. Nothing left to choose.</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 width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path class="tick" d="M2.4 9.2 C4.1 10.4 5.2 11.7 6.1 13.4 C8.2 8.4 10.6 5.1 14 2.6"/></svg></span><span>17 tracks, 3 movements, ~9 min each</span></li> <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 width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path class="tick" d="M2.4 9.2 C4.1 10.4 5.2 11.7 6.1 13.4 C8.2 8.4 10.6 5.1 14 2.6"/></svg></span><span><span class="mono">setlist_thu24.txt</span>, in play order</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 width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path class="tick" d="M2.4 9.2 C4.1 10.4 5.2 11.7 6.1 13.4 C8.2 8.4 10.6 5.1 14 2.6"/></svg></span><span>All 17 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">pvlint</span> over the set: 0 errors</span></li>
<li class="did"><span class="mark"><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path class="tick" d="M2.4 9.2 C4.1 10.4 5.2 11.7 6.1 13.4 C8.2 8.4 10.6 5.1 14 2.6"/></svg></span><span>pvlint over the set: 0 errors</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 width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path class="tick" d="M2.4 9.2 C4.1 10.4 5.2 11.7 6.1 13.4 C8.2 8.4 10.6 5.1 14 2.6"/></svg></span><span>Sample-heavy techno cut by ear</span></li>
<li><span class="mark"><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><rect class="box" x="2.2" y="2.6" width="11.6" height="11.6" rx="2.4"/></svg></span><span><span class="badge">you</span>Confirm <span class="mono">ete_a_mauerpark</span> d9 effects</span></li>
<li><span class="mark"><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><rect class="box" x="2.2" y="2.6" width="11.6" height="11.6" rx="2.4"/></svg></span><span><span class="badge">you</span>Fix <span class="mono">sept1</span> to the new controller</span></li>
</ul> </ul>
</article> </article>
<article class="card"> <article class="card">
<h2>Rig</h2> <h2>Corpus <span class="step">2</span></h2>
<p class="figure">95%</p> <p class="figure done">100%</p>
<p class="mood">One launcher, one gate. Never pressed.</p> <p class="mood">Settled. Re-plan finds nothing left.</p>
<div class="bar" role="img" aria-label="rig 95 percent"><div class="fill" style="width:95%"></div></div> <div class="bar" role="img" aria-label="corpus 100 percent"><div class="fill" style="width:100%"></div></div>
<ul> <ul>
<li class="did"><span class="mark"><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path class="tick" d="M2.4 9.2 C4.1 10.4 5.2 11.7 6.1 13.4 C8.2 8.4 10.6 5.1 14 2.6"/></svg></span><span>Column remap finished: 148 files</span></li> <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 width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path class="tick" d="M2.4 9.2 C4.1 10.4 5.2 11.7 6.1 13.4 C8.2 8.4 10.6 5.1 14 2.6"/></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>Button conformance 78.9% &rarr; 93.3%</span></li>
<li class="did"><span class="mark"><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path class="tick" d="M2.4 9.2 C4.1 10.4 5.2 11.7 6.1 13.4 C8.2 8.4 10.6 5.1 14 2.6"/></svg></span><span>Orbit compaction: 150 moves, no d6 gap</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 class="did"><span class="mark"><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path class="tick" d="M2.4 9.2 C4.1 10.4 5.2 11.7 6.1 13.4 C8.2 8.4 10.6 5.1 14 2.6"/></svg></span><span>Both migrations: zero playability regressions</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 class="did"><span class="mark"><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path class="tick" d="M2.4 9.2 C4.1 10.4 5.2 11.7 6.1 13.4 C8.2 8.4 10.6 5.1 14 2.6"/></svg></span><span>OLED tracks the hand, holds at rest</span></li>
<li class="did"><span class="mark"><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path class="tick" d="M2.4 9.2 C4.1 10.4 5.2 11.7 6.1 13.4 C8.2 8.4 10.6 5.1 14 2.6"/></svg></span><span>Preload 132 banks, covers the set</span></li>
<li class="did"><span class="mark"><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path class="tick" d="M2.4 9.2 C4.1 10.4 5.2 11.7 6.1 13.4 C8.2 8.4 10.6 5.1 14 2.6"/></svg></span><span>Latency 0.1 s live, CPU cap lifted</span></li>
<li class="did"><span class="mark"><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path class="tick" d="M2.4 9.2 C4.1 10.4 5.2 11.7 6.1 13.4 C8.2 8.4 10.6 5.1 14 2.6"/></svg></span><span>One launcher, one gate &mdash; 39 checks, 3.4 s</span></li>
<li class="did"><span class="mark"><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path class="tick" d="M2.4 9.2 C4.1 10.4 5.2 11.7 6.1 13.4 C8.2 8.4 10.6 5.1 14 2.6"/></svg></span><span>Caught: preload rewritten from the Sept set</span></li>
<li class="did"><span class="mark"><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path class="tick" d="M2.4 9.2 C4.1 10.4 5.2 11.7 6.1 13.4 C8.2 8.4 10.6 5.1 14 2.6"/></svg></span><span>SuperDirt boots as the unit, not raw sclang</span></li>
<li><span class="mark"><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><rect class="box" x="2.2" y="2.6" width="11.6" height="11.6" rx="2.4"/></svg></span><span><span class="badge">you</span>Press the launcher once from the icon</span></li>
</ul> </ul>
</article> </article>
<article class="card"> <article class="card">
<h2>Sound</h2> <h2>Audio <span class="step">3</span></h2>
<p class="figure done">85%</p> <p class="figure near">85%</p>
<p class="mood">Faders restored. Twelve reach master.</p> <p class="mood">Repaired this morning. One leg unproven.</p>
<div class="bar" role="img" aria-label="sound 85 percent"><div class="fill" style="width:85%"></div></div> <div class="bar" role="img" aria-label="audio 85 percent"><div class="fill near" style="width:85%"></div></div>
<ul> <ul>
<li class="did"><span class="mark"><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path class="tick" d="M2.4 9.2 C4.1 10.4 5.2 11.7 6.1 13.4 C8.2 8.4 10.6 5.1 14 2.6"/></svg></span><span>Fader baseline restored by gig-up</span></li> <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 width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path class="tick" d="M2.4 9.2 C4.1 10.4 5.2 11.7 6.1 13.4 C8.2 8.4 10.6 5.1 14 2.6"/></svg></span><span>All 12 orbits reach the master bus</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 width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path class="tick" d="M2.4 9.2 C4.1 10.4 5.2 11.7 6.1 13.4 C8.2 8.4 10.6 5.1 14 2.6"/></svg></span><span>Master at &minus;0.5 dB, not &minus;30</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><span class="mark"><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><rect class="box" x="2.2" y="2.6" width="11.6" height="11.6" rx="2.4"/></svg></span><span><span class="badge">you</span>A/B two moved crush buses</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><span class="mark"><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><rect class="box" x="2.2" y="2.6" width="11.6" height="11.6" rx="2.4"/></svg></span><span><span class="badge">you</span>Master chain: limiter the only guard</span></li>
</ul> </ul>
</article> </article>
<article class="card"> <article class="card">
<h2>Stage</h2> <h2>Surface <span class="step">4</span></h2>
<p class="figure">40%</p> <p class="figure near">95%</p>
<p class="mood">Readable in daylight now. Still untested.</p> <p class="mood">Live for the first time.</p>
<div class="bar" role="img" aria-label="stage 40 percent"><div class="fill low" style="width:40%"></div></div> <div class="bar" role="img" aria-label="surface 95 percent"><div class="fill near" style="width:95%"></div></div>
<ul> <ul>
<li class="did"><span class="mark"><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path class="tick" d="M2.4 9.2 C4.1 10.4 5.2 11.7 6.1 13.4 C8.2 8.4 10.6 5.1 14 2.6"/></svg></span><span>QjackCtl in the tray, pw-jack wrapped</span></li> <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 width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path class="tick" d="M2.4 9.2 C4.1 10.4 5.2 11.7 6.1 13.4 C8.2 8.4 10.6 5.1 14 2.6"/></svg></span><span>One gate proves the set and the machine</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><span class="mark"><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><rect class="box" x="2.2" y="2.6" width="11.6" height="11.6" rx="2.4"/></svg></span><span>0 of 28 headphone fold links</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 width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><rect class="box" x="2.2" y="2.6" width="11.6" height="11.6" rx="2.4"/></svg></span><span>Docker off, Pulsar launch unchecked</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 class="did"><span class="mark"><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path class="tick" d="M2.4 9.2 C4.1 10.4 5.2 11.7 6.1 13.4 C8.2 8.4 10.6 5.1 14 2.6"/></svg></span><span>midiviz: sun theme, corner grabs, tray</span></li> </ul>
</article>
<article class="card">
<h2>Observability <span class="step">5</span></h2>
<p class="figure near">90%</p>
<p class="mood">The lies are out of the checks.</p>
<div class="bar" role="img" aria-label="observability 90 percent"><div class="fill near" style="width:90%"></div></div>
<ul>
<li class="did"><span class="mark"><svg class="ic"><use href="#i-tick"/></svg></span><span>MIDI log binds at last, 9 dead sessions</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 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 gate">
<h2>Rehearsal &middot; critical path</h2> <h2>Rehearsal <span class="step">6</span> <span class="badge">you</span></h2>
<p class="figure">40%</p> <p class="figure">0%</p>
<p class="mood">The only thing left that counts.</p> <p class="mood">The critical path. Yours alone.</p>
<div class="bar" role="img" aria-label="rehearsal 40 percent"><div class="fill low" style="width:40%"></div></div> <div class="bar pending" role="img" aria-label="rehearsal 0 percent, not started"></div>
<ul> <ul>
<li class="did"><span class="mark"><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path class="half" d="M2.4 9.2 C4.1 10.4 5.2 11.7 6.1 13.4"/><path class="half-rest" d="M6.1 13.4 C8.2 8.4 10.6 5.1 14 2.6"/></svg></span><span>Tracks touched tonight, not a full run</span></li> <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><span class="mark"><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><rect class="box" x="2.2" y="2.6" width="11.6" height="11.6" rx="2.4"/></svg></span><span><span class="badge">you</span>Run the 2h arc, Ardour recording</span></li> <li><span class="mark"><svg class="ic"><use href="#i-open"/></svg></span><span>Ardour recording all orbits</span></li>
<li><span class="mark"><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><rect class="box" x="2.2" y="2.6" width="11.6" height="11.6" rx="2.4"/></svg></span><span>Tempo ramp across movements untried</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 width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><rect class="box" x="2.2" y="2.6" width="11.6" height="11.6" rx="2.4"/></svg></span><span>Transitions unrehearsed</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>
...@@ -379,32 +372,31 @@ ...@@ -379,32 +372,31 @@
<ol> <ol>
<li> <li>
<label> <label>
<input type="checkbox" data-move="press"> <input type="checkbox" data-move="arc2h">
<span class="num"></span> <span class="num"></span>
<span class="mtext"><b>Press the launcher once, from the icon</b><span class="why">Starts SC, warms 132 banks, runs 39 checks</span></span> <span class="mtext"><b>Run the 2h arc, Ardour recording</b><span class="why">Unblocks everything</span></span>
</label> </label>
</li> </li>
<li> <li>
<label> <label>
<input type="checkbox" data-move="run2h2"> <input type="checkbox" data-move="umc">
<span class="num"></span> <span class="num"></span>
<span class="mtext"><b>Run the full 2h with Ardour recording</b><span class="why">The only test that counts</span></span> <span class="mtext"><b>Plug the UMC for the last stretch</b><span class="why">The only untested path</span></span>
</label> </label>
</li> </li>
<li> <li>
<label> <label>
<input type="checkbox" data-move="crush"> <input type="checkbox" data-move="push">
<span class="num"></span> <span class="num"></span>
<span class="mtext"><b>A/B the two moved crush buses</b><span class="why">Bass and melody stop trading</span></span> <span class="mtext"><b><span class="mono">ssh-add</span>, then the push lands</b><span class="why">1 commit waiting</span></span>
</label> </label>
</li> </li>
</ol> </ol>
</section> </section>
<footer> <footer>
<p class="path">Critical path: <b>one launch</b> &rarr; <b>2h rehearsal</b> &rarr; <b>doors</b></p> <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="tiny">Not this project: mastering (tidal-ears), Opal setlist.</p> <p class="tiny">Not this project: post-gig cleanup (11 items), corpus-wide remap, Ardour OSC.</p>
<p class="tiny">Companion: <a href="/setbuilder.html">Set Builder</a></p>
</footer> </footer>
</div> </div>
...@@ -416,65 +408,69 @@ ...@@ -416,65 +408,69 @@
var WD = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]; var WD = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"];
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-24T21:00" -> a local-wall-clock Date, never UTC-shifted. */ /* "2026-09-24" or "2026-09-24T19: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("-");
var t = (m[1] || "00:00").split(":"); var t = (m[1] || "00:00").split(":");
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. */
function daysBetween(a, b){ return Math.round((midnight(b) - midnight(a)) / MS); } function daysBetween(a, b){ return Math.round((midnight(b) - midnight(a)) / MS); }
var GIG = parseDt("2026-09-24"); /* Doors, as the evening the set is announced for. */
/* Timeline stops, in DOM order. Dates live here only, never typed into the markup. */ var GIG = parseDt("2026-09-24T19:00");
var STOPS = [
{ dt: "2026-09-22", what: "locked" },
{ dt: "2026-09-23", what: "prep" },
{ dt: "2026-09-24", what: "goal", finale: true }
];
/* Days-to-gig, signed. The phrase layer is what refuses to render a negative. */
function gigDays(now){ return daysBetween(now, GIG); } function gigDays(now){ return daysBetween(now, GIG); }
/* The one live string in the header. Never negative, never "in -1 days". */ /* The one live phrase in the header. Never negative, never "in 1 days". */
function gigPhrase(now){ function gigPhrase(now){
var d = gigDays(now); var d = gigDays(now);
if (d > 1) return "gig in " + d + " days"; if (d > 1) return "gig in " + d + " days";
if (d === 1) return "gig tomorrow"; if (d === 1) return "gig tomorrow evening";
if (d === 0) return "gig today"; if (d === 0){
return "gig played"; var h = Math.floor((GIG.getTime() - now.getTime()) / 3600000);
if (h >= 1) return "tonight &middot; " + h + "h to doors";
if (GIG.getTime() - now.getTime() > 0) return "doors within the hour";
return "on stage now";
}
if (d === -1) return "played last night";
return "played " + (-d) + " days ago";
} }
function dayLabel(d){ return WD[d.getDay()] + " " + d.getDate() + " Sept"; } 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. Past tense for days gone, plain words for days ahead. */
function stopPhrase(now, target, finale){ function stopPhrase(now, target, finale){
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 "tomorrow"; if (d === 1) return finale ? "tomorrow evening" : "tomorrow";
if (d === 0) return finale ? "doors today" : "today"; if (d === 0) return finale ? "doors tonight" : "today";
if (d === -1) return "yesterday"; if (d === -1) return "yesterday";
return (-d) + " days ago"; return (-d) + " days ago";
} }
/* Stops in DOM order. Dates live here only, never typed into the markup. */
var STOPS = [
{ dt: "2026-09-23", finale: false },
{ dt: "2026-09-24T19:00", 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 = { var out = { gig: gigPhrase(now), gigDay: dayLabel(GIG), days: gigDays(now), stops: [] };
gig: gigPhrase(now),
gigDay: dayLabel(GIG),
days: gigDays(now),
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);
out.stops.push({ out.stops.push({
day: dayLabel(d), day: dayLabel(d),
cd: stopPhrase(now, d, !!s.finale), cd: stopPhrase(now, d, s.finale),
sameDay: delta === 0, sameDay: delta === 0,
past: delta < 0 past: delta < 0
}); });
} }
/* "Today" belongs to whichever stop the clock says it is; past the gig, nobody wears it. */
return out; return out;
} }
...@@ -485,41 +481,48 @@ ...@@ -485,41 +481,48 @@
}; };
var fails = []; var fails = [];
/* The three dates the page must survive, plus the day after. */ [["2026-09-21",3],["2026-09-23",1],["2026-09-24",0],["2026-09-25",-1],["2026-09-30",-6]]
[["2026-09-22",2],["2026-09-23",1],["2026-09-24",0],["2026-09-25",-1],["2026-09-20",4]]
.forEach(function(c){ .forEach(function(c){
var got = gigDays(parseDt(c[0])); var got = gigDays(parseDt(c[0]));
if (got !== c[1]) fails.push("gigDays " + c[0] + " expected " + c[1] + ", got " + got); if (got !== c[1]) fails.push("gigDays " + c[0] + " expected " + c[1] + ", got " + got);
}); });
[["2026-09-22T09:00","gig in 2 days"], [["2026-09-21T09:00","gig in 3 days"],
["2026-09-22T23:59","gig in 2 days"], ["2026-09-23T00:05","gig tomorrow evening"],
["2026-09-23T07:30","gig tomorrow"], ["2026-09-23T18:00","gig tomorrow evening"],
["2026-09-24T00:05","gig today"], ["2026-09-23T23:59","gig tomorrow evening"],
["2026-09-24T23:30","gig today"], ["2026-09-24T09:00","tonight &middot; 10h to doors"],
["2026-09-25T10:00","gig played"], ["2026-09-24T18:00","tonight &middot; 1h to doors"],
["2026-10-02T10:00","gig played"]].forEach(function(c){ ["2026-09-24T18:30","doors within the hour"],
["2026-09-24T19:00","on stage now"],
["2026-09-24T21:30","on stage now"],
["2026-09-25T10:00","played last night"],
["2026-09-28T10:00","played 4 days ago"]].forEach(function(c){
var got = gigPhrase(parseDt(c[0])); var got = gigPhrase(parseDt(c[0]));
if (got !== c[1]) fails.push("gigPhrase " + c[0] + " expected " + c[1] + ", got " + got); if (got !== c[1]) fails.push("gigPhrase " + c[0] + " expected " + c[1] + ", got " + got);
}); });
/* Every stop's own countdown, on the day before, the day of, and the day after the gig. */ [["2026-09-23T10:00",["today","tomorrow evening"]],
[["2026-09-23T10:00",["yesterday","today","tomorrow"]], ["2026-09-24T10:00",["yesterday","doors tonight"]],
["2026-09-24T10:00",["2 days ago","yesterday","doors today"]], ["2026-09-26T10:00",["3 days ago","2 days ago"]],
["2026-09-25T10:00",["3 days ago","2 days ago","yesterday"]], ["2026-09-21T10:00",["in 2 days","in 3 days"]]].forEach(function(c){
["2026-09-22T10:00",["today","tomorrow","in 2 days"]]].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: 22 Sept 2026 is a Tuesday, 24 Sept a Thursday. */ /* Weekdays are derived, never typed: 23 Sept 2026 is a Wednesday, 24 Sept a Thursday. */
[["2026-09-22",2],["2026-09-23",3],["2026-09-24",4]].forEach(function(c){ [["2026-09-23",3],["2026-09-24",4]].forEach(function(c){
var got = parseDt(c[0]).getDay(); var got = parseDt(c[0]).getDay();
if (got !== c[1]) fails.push(c[0] + " weekday expected " + c[1] + ", got " + got); if (got !== c[1]) fails.push(c[0] + " weekday expected " + c[1] + ", got " + got);
}); });
/* Month rollover must not break the day count. */
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");
if (STOPS.length !== 3) fails.push("STOPS length changed without updating the tests"); /* Exactly one stop may claim today, and only while the gig is not yet past. */
/* No phrase may ever contain a stale-wording artefact, on any hour of any nearby day. */ [["2026-09-23T10:00",1],["2026-09-24T10:00",1],["2026-09-25T10:00",0],["2026-09-21T10:00",0]]
var BAD = /(\bin -|\bin 0\b|\bin 1 days\b|-1|NaN|undefined)/; .forEach(function(c){
for (var probe = parseDt("2026-09-18").getTime(); probe <= parseDt("2026-09-30").getTime(); probe += 3600000){ 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 (STOPS.length !== 2) fails.push("STOPS length changed without updating the tests");
/* No phrase may carry a stale-wording artefact, on any hour of any nearby day. */
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 pp = phrases(new Date(probe)); var pp = phrases(new Date(probe));
var strs = [pp.gig, pp.gigDay]; var strs = [pp.gig, pp.gigDay];
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);
...@@ -529,29 +532,28 @@ ...@@ -529,29 +532,28 @@
} }
if (fails.length){ if (fails.length){
if (typeof console !== "undefined") console.error("DATE-MATH CHECK FAILED:\n " + fails.join("\n ")); if (typeof console !== "undefined") console.error("DATE-MATH CHECK FAILED:\n " + fails.join("\n "));
throw new Error("date-math check failed (" + fails.length + ")"); } else if (typeof console !== "undefined") {
console.log("date-math check: PASS");
} }
if (typeof console !== "undefined") console.log("date-math check: PASS"); window.gigClock.selfTest = fails;
})(); })();
</script> </script>
<script> <script>
(function(){ (function(){
var C = window.gigClock; var C = window.gigClock;
var p = C.phrases(new Date()); var p = C.phrases(new Date());
function put(id, text){ var el = document.getElementById(id); if (el) el.textContent = text; } function put(id, html){ var el = document.getElementById(id); if (el) el.innerHTML = html; }
put("c-gig", p.gig); put("c-gig", p.gig);
put("c-gig-day", p.gigDay); put("c-gig-day", p.gigDay);
var mine = document.querySelectorAll(".buckets .badge").length;
put("c-yours", mine + (mine === 1 ? " item" : " items"));
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.textContent = q.cd; var cd = s.querySelector(".cd"); if (cd) cd.innerHTML = q.cd;
if (q.sameDay) todayEl = s; if (q.sameDay && !todayEl) todayEl = s;
} }
/* One ribbon only, on the stop the clock says is today. */ /* One ribbon only, on the stop the clock says is today. */
if (todayEl){ if (todayEl){
...@@ -564,7 +566,7 @@ ...@@ -564,7 +566,7 @@
/* 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.0922c"; var KEY = "pv.gig.thu24.moves.0923a";
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