Commit 89bef9dd by PLN (Algolia)

feat(armada): gig status page — five buckets, one gate, glance and close

Where Thursday stands on paper: rig, set, sound, stage, rehearsal, each with an honest
figure and the items only PLN can do, over a live countdown to doors.
parent 859c9cbd
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Thursday 24 &middot; 2-hour livecoding set</title>
<style>
:root{
--paper:#f6f1e5;
--paper-card:#fbf8f0;
--paper-warm:#fdf3dd;
--ink:#3a3226;
--ink-soft:#6b6152;
--ink-faint:#8b8070;
--rule:#e2d9c6;
--ribbon:#b4553c;
--ribbon-soft:#d98f76;
--sage:#6f8055;
--amber:#9d7517;
--amber-soft:#e0c98e;
--slate:#5b6f8a;
--shadow:rgba(90,74,48,.13);
}
*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
margin:0;
background-color:var(--paper);
background-image:
radial-gradient(circle at 18% 22%, rgba(255,255,255,.55), transparent 42%),
radial-gradient(circle at 82% 8%, rgba(190,170,130,.10), transparent 38%),
repeating-linear-gradient(0deg, rgba(150,130,95,.030) 0 1px, transparent 1px 4px),
repeating-linear-gradient(90deg, rgba(150,130,95,.022) 0 1px, transparent 1px 5px);
color:var(--ink);
font-family:"Segoe UI", Roboto, Helvetica, Arial, system-ui, sans-serif;
font-size:16px;
line-height:1.65;
overflow-x:hidden;
}
.wrap{max-width:1180px;margin:0 auto;padding:34px 22px 56px}
h1,h2,h3,.serif{font-family:Georgia,"Iowan Old Style",Charter,"Times New Roman",serif;font-weight:600}
/* ---------- header ---------- */
header{margin-bottom:22px}
h1{font-size:2.05rem;line-height:1.2;margin:0 0 6px;letter-spacing:.1px}
h1 .goal{color:var(--ribbon)}
.sub{margin:0;color:var(--ink-soft);font-size:.96rem}
.nb{white-space:nowrap}
.counters{display:flex;flex-wrap:wrap;gap:10px;margin-top:15px}
.counter{
background:var(--paper-card);
border:1px solid var(--rule);
border-radius:11px;
padding:7px 14px;
font-size:.92rem;
box-shadow:0 1px 2px var(--shadow);
white-space:nowrap;
}
.counter b{font-family:Georgia,serif;font-size:1.05rem}
.counter .lbl{color:var(--ink-soft)}
.counter.hot{background:var(--paper-warm);border-color:#ecd9b4}
.counter.hot b{color:var(--ribbon)}
/* ---------- timeline ---------- */
.timeline{
position:relative;
margin:26px 0 30px;
padding:36px 14px 16px;
background:var(--paper-card);
border:1px solid var(--rule);
border-radius:13px;
box-shadow:0 1px 3px var(--shadow);
overflow-x:auto;
}
.track{display:flex;align-items:flex-start;gap:0;min-width:300px}
.stop{flex:1 1 0;text-align:center;position:relative;padding-top:16px}
.stop::before{
content:"";position:absolute;top:5px;left:0;right:0;height:2px;
background:var(--rule);
}
.stop:first-child::before{left:50%}
.stop:last-child::before{right:50%}
.dot{
position:absolute;top:0;left:50%;transform:translateX(-50%);
width:12px;height:12px;border-radius:50%;
background:var(--paper-card);border:2px solid var(--ink-faint);
z-index:2;
}
.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}
/* two text lines reserved, so every countdown sits on one baseline */
.what{display:block;font-size:.88rem;color:var(--ink);padding:0 6px;line-height:1.35;min-height:2.5em}
.cd{display:block;font-size:.77rem;color:var(--ink-faint);margin-top:3px;letter-spacing:.02em}
.stop.finale .what{font-weight:600}
.stop.finale .cd{color:var(--ink-soft)}
/* ribbon on today */
.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 .what{font-weight:600}
.stop.today .cd{color:var(--ribbon);font-weight:600}
.ribbon{
position:absolute;left:50%;transform:translateX(-50%);
top:-28px;width:13px;height:30px;z-index:1;
background:var(--ribbon);
border-radius:2px 2px 0 0;
clip-path:polygon(0 0,100% 0,100% 100%,50% 76%,0 100%);
box-shadow:0 1px 2px var(--shadow);
}
/* ---------- buckets ---------- */
/* five cards: a true 3 + 2, never a 4 + 1 orphan */
.buckets{
display:grid;
grid-template-columns: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{
background:var(--paper-card);
border:1px solid var(--rule);
border-radius:14px;
padding:17px 16px 15px;
box-shadow:0 2px 6px var(--shadow);
}
.card:nth-child(odd){transform:rotate(-.3deg)}
.card:nth-child(even){transform:rotate(.3deg)}
/* the gate 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 h2{font-size:.95rem;letter-spacing:.14em;text-transform:uppercase;margin:0;color:var(--ink-soft)}
.card.gate h2{color:var(--ribbon)}
.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.yours{color:var(--slate)}
.mood{margin:2px 0 12px;font-size:.9rem;color:var(--ink-soft);font-style:italic}
.bar{
height:9px;border-radius:5px;background:#eee5d2;overflow:hidden;
border:1px solid var(--rule);margin-bottom:12px;
}
.fill{
height:100%;
background-color:var(--sage);
background-image:repeating-linear-gradient(45deg, rgba(255,255,255,.30) 0 2px, transparent 2px 5px);
}
.fill.low{background-color:var(--ribbon-soft)}
.fill.wait{background-color:var(--slate)}
/* nothing has run end to end, so nothing is drawn */
.bar.empty{border-color:var(--ribbon-soft);background:#f7e3dc}
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+li{border-top:1px solid rgba(180,160,120,.16)}
li .mark{flex:0 0 16px;margin-top:3px}
/* let the text column shrink, so a long line wraps instead of pushing the card wide */
li>span+span{flex:1 1 auto;min-width:0}
/* shipped work steps back; what is left keeps full ink */
li.did{color:var(--ink-soft)}
.badge{
display:inline-block;margin-right:6px;
font-size:.64rem;letter-spacing:.11em;font-weight:800;
background:var(--ribbon);color:#fbf8f0;
border:1px solid var(--ribbon);border-radius:5px;
padding:1px 5px;vertical-align:1.5px;
text-transform:uppercase;
}
.rcpt{
font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,"Liberation Mono",monospace;
font-size:.76rem;color:var(--ink-faint);margin-left:5px;white-space:nowrap;
}
svg{display:block}
.box{fill:none;stroke:var(--ink-faint);stroke-width:1.4}
.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 */
.half{fill:none;stroke:var(--ribbon);stroke-width:2.1;stroke-linecap:round}
.half-rest{fill:none;stroke:var(--ribbon-soft);stroke-width:1.7;stroke-linecap:round;stroke-dasharray:1.6 2.2}
.warn{fill:none;stroke:var(--amber);stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}
/* ---------- next moves ---------- */
.moves{
margin:28px 0 24px;
background-color:var(--paper-warm);
background-image:linear-gradient(160deg, rgba(255,255,255,.5), transparent 60%);
border:1px solid #ecd9b4;
border-radius:14px;
padding:20px 22px 18px;
box-shadow:0 3px 10px var(--shadow);
transform:rotate(-.3deg);
position:relative;
}
.moves h2{font-size:1.25rem;text-transform:none;letter-spacing:0;color:var(--ink);margin:0 0 10px}
.moves ol{list-style:none;margin:0;padding:0;counter-reset:m}
.moves li{
counter-increment:m;
display:flex;gap:12px;align-items:flex-start;
padding:9px 0;border-top:1px solid rgba(180,160,120,.28);
font-size:1rem;
}
.moves li:first-child{border-top:0}
.moves label{display:flex;gap:12px;align-items:flex-start;cursor:pointer;width:100%}
.moves input{
appearance:none;-webkit-appearance:none;
flex:0 0 20px;width:20px;height:20px;margin:2px 0 0;
border:1.6px solid var(--ink-faint);border-radius:5px;
background:var(--paper-card);cursor:pointer;position:relative;
}
.moves input:checked{border-color:var(--sage)}
.moves input:checked::after{
content:"";position:absolute;left:4px;top:8px;
width:5px;height:9px;
border-right:2.2px solid var(--sage);border-bottom:2.2px solid var(--sage);
transform:rotate(42deg);transform-origin:center;
}
.moves input:focus-visible{outline:2px solid var(--ribbon);outline-offset:2px}
.mtext{flex:1 1 auto;min-width:0}
.mtext b{font-family:Georgia,serif;font-weight:600}
.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 .why{color:var(--ink-faint)}
.num{font-family:Georgia,serif;color:var(--ribbon);flex:0 0 auto;margin-top:1px}
.num::before{content:counter(m) "."}
/* ---------- footer ---------- */
footer{margin-top:26px;padding-top:14px;border-top:1px solid var(--rule)}
footer p{margin:0 0 5px;font-size:.92rem}
footer p.path{color:var(--ink)}
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 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){
.wrap{padding:24px 14px 44px}
h1{font-size:1.55rem}
.card,.moves{transform:none}
.counter{white-space:normal}
.timeline{padding:32px 7px 14px}
.day{font-size:.72rem;letter-spacing:.03em}
/* three text lines reserved at phone width, so the countdowns keep one baseline */
.what{font-size:.81rem;padding:0 3px;min-height:5em}
.cd{font-size:.74rem}
.figure{font-size:2.1rem}
}
</style>
</head>
<body>
<div class="wrap">
<header>
<h1><span class="nb">Thursday 24</span> &middot; <span class="goal nb">2-hour livecoding set</span></h1>
<p class="sub"><span class="nb">ParVagues gig prep</span> &middot; <span class="nb">five buckets, one gate</span> &middot; <span class="nb">glance and close</span></p>
<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"><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>
</header>
<section class="timeline" aria-label="Timeline">
<div class="track">
<div class="stop">
<span class="dot"></span>
<span class="day">&mdash;</span>
<span class="what">Rig bench &middot; set shortlist</span>
<span class="cd">&mdash;</span>
</div>
<div class="stop">
<span class="dot"></span>
<span class="day">&mdash;</span>
<span class="what">Ardour, folds, drills</span>
<span class="cd">&mdash;</span>
</div>
<div class="stop finale">
<span class="dot"></span>
<span class="day">&mdash;</span>
<span class="what">GOAL &middot; 2-hour set</span>
<span class="cd">&mdash;</span>
</div>
</div>
</section>
<section class="buckets" aria-label="Buckets">
<article class="card">
<h2>Rig</h2>
<p class="figure">75%</p>
<p class="mood">Fast now. One cap still throttles.</p>
<div class="bar" role="img" aria-label="rig 75 percent"><div class="fill" style="width:75%"></div></div>
<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>OLED repaints on every write<span class="rcpt">9507ad6</span></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 32 knobs on our overlay<span class="rcpt">9a4918a</span></span></li>
<li><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>Stale-value heal on re-arm &mdash; shipping</span></li>
<li><span class="mark"><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path class="warn" d="M8 2.7 L14.3 13.3 H1.7 Z"/><path class="warn" d="M8 6.8 V9.7"/><path class="warn" d="M8 11.5 V11.6"/></svg></span><span>End-stop slam shipped but unmeasured<span class="rcpt">8e7b713</span></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>Lift the 55% CPU cap (sudo)</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>Restart SC to load 0.1s latency<span class="rcpt">aa41686</span></span></li>
</ul>
</article>
<article class="card">
<h2>Set</h2>
<p class="figure yours">yours</p>
<p class="mood">Yours now. The tracks are ready.</p>
<div class="bar" role="img" aria-label="set is yours to pick"><div class="fill wait" style="width:100%"></div></div>
<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>Nine candidates validated, silently</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>cyber_hump resurrected from one typo<span class="rcpt">0f99fba</span></span></li>
<li><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>Five play-history tracks validating now</span></li>
<li><span class="mark"><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path class="warn" d="M8 2.7 L14.3 13.3 H1.7 Z"/><path class="warn" d="M8 6.8 V9.7"/><path class="warn" d="M8 11.5 V11.6"/></svg></span><span>techno_orage d2 silent &mdash; fix or cut</span></li>
<li><span class="mark"><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path class="warn" d="M8 2.7 L14.3 13.3 H1.7 Z"/><path class="warn" d="M8 6.8 V9.7"/><path class="warn" d="M8 11.5 V11.6"/></svg></span><span>Preload misses bass2 and humps</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>Pick and order the 2h arc</span></li>
</ul>
</article>
<article class="card">
<h2>Sound</h2>
<p class="figure">10%</p>
<p class="mood">Untouched. Ardour is still asleep.</p>
<div class="bar" role="img" aria-label="sound 10 percent"><div class="fill low" style="width:10%"></div></div>
<ul>
<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>Ten of twelve faders boot at -inf</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>Master sits near -30 dB</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>Ardour OSC off &mdash; preflight flies blind</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>Eight dead sends on three 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>Quantum 512 listen test pending</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>Limiter is the only PA guard</span></li>
</ul>
</article>
<article class="card">
<h2>Stage</h2>
<p class="figure">15%</p>
<p class="mood">Muscle memory, not yet drilled.</p>
<div class="bar" role="img" aria-label="stage 15 percent"><div class="fill low" style="width:15%"></div></div>
<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>gig-up --bt-off kills the radio<span class="rcpt">aa41686</span></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>Zero of 28 headphone fold links</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>Record-arm is fifteen manual clicks</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>Preflight still shows six warnings</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>Pause the corporate agents Thursday</span></li>
</ul>
</article>
<article class="card gate">
<h2>Rehearsal</h2>
<p class="figure">0%</p>
<p class="mood">The gate. Nothing ran end to end.</p>
<div class="bar empty" role="img" aria-label="rehearsal 0 percent"></div>
<ul>
<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>Full 2h run, Ardour recording</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>Watch late-count after SC restart</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>Re-measure xruns after the cap lift</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>Eyes on a DJF sweep</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>Veto or keep the slam bands</span></li>
</ul>
</article>
</section>
<section class="moves" aria-label="Next three moves">
<h2 class="serif">Next 3 moves</h2>
<ol>
<li>
<label>
<input type="checkbox" data-move="cap">
<span class="num"></span>
<span class="mtext"><b>Lift the 55% CPU cap</b><span class="why">Every audio number is a lie until then</span></span>
</label>
</li>
<li>
<label>
<input type="checkbox" data-move="arc">
<span class="num"></span>
<span class="mtext"><b>Pick the arc in Set Builder</b><span class="why">Unblocks preload, stems, rehearsal</span></span>
</label>
</li>
<li>
<label>
<input type="checkbox" data-move="run">
<span class="num"></span>
<span class="mtext"><b>Restart SC, then run the full 2h</b><span class="why">The only end-to-end proof</span></span>
</label>
</li>
</ol>
</section>
<footer>
<p class="path">Critical path: <b>CPU cap</b> &rarr; <b>SC restart</b> &rarr; <b>2h rehearsal</b> &rarr; <b>doors</b></p>
<p class="tiny">Not this project: mastering (tidal-ears), Opal setlist.</p>
<p class="tiny">Companion: <a href="/setbuilder.html">Set Builder</a></p>
</footer>
</div>
<script>
/* Date math, isolated so a failed assertion cannot take the page's wiring down. */
(function(){
var MS = 86400000;
var WD = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"];
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. */
function parseDt(s){
var m = String(s).split("T");
var p = m[0].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);
}
/* Whole calendar days from a to b: positive when b is in the future. */
function daysBetween(a, b){ return Math.round((midnight(b) - midnight(a)) / MS); }
var GIG = parseDt("2026-09-24");
/* Timeline stops, in DOM order. Dates live here only, never typed into the markup. */
var STOPS = [
{ dt: "2026-09-22", what: "bench" },
{ 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); }
/* The one live string in the header. Never negative, never "in -1 days". */
function gigPhrase(now){
var d = gigDays(now);
if (d > 1) return "gig in " + d + " days";
if (d === 1) return "gig tomorrow";
if (d === 0) return "gig today";
return "gig played";
}
function dayLabel(d){ return WD[d.getDay()] + " " + d.getDate() + " Sept"; }
/* Per-stop countdown. Past tense for days gone, plain words for days ahead. */
function stopPhrase(now, target, finale){
var d = daysBetween(now, target);
if (d > 1) return "in " + d + " days";
if (d === 1) return "tomorrow";
if (d === 0) return finale ? "doors today" : "today";
if (d === -1) return "yesterday";
return (-d) + " days ago";
}
/* Pure: every string the page shows, derived from one instant. Testable on its own. */
function phrases(now){
var out = {
gig: gigPhrase(now),
gigDay: dayLabel(GIG),
days: gigDays(now),
stops: []
};
for (var i = 0; i < STOPS.length; i++){
var s = STOPS[i];
var d = parseDt(s.dt);
var delta = daysBetween(now, d);
out.stops.push({
day: dayLabel(d),
cd: stopPhrase(now, d, !!s.finale),
sameDay: delta === 0,
past: delta < 0
});
}
return out;
}
window.gigClock = {
parseDt: parseDt, midnight: midnight, daysBetween: daysBetween,
gigDays: gigDays, gigPhrase: gigPhrase, stopPhrase: stopPhrase,
dayLabel: dayLabel, phrases: phrases, stopCount: STOPS.length
};
var fails = [];
/* The three dates the page must survive, plus the day after. */
[["2026-09-22",2],["2026-09-23",1],["2026-09-24",0],["2026-09-25",-1],["2026-09-20",4]]
.forEach(function(c){
var got = gigDays(parseDt(c[0]));
if (got !== c[1]) fails.push("gigDays " + c[0] + " expected " + c[1] + ", got " + got);
});
[["2026-09-22T09:00","gig in 2 days"],
["2026-09-22T23:59","gig in 2 days"],
["2026-09-23T07:30","gig tomorrow"],
["2026-09-24T00:05","gig today"],
["2026-09-24T23:30","gig today"],
["2026-09-25T10:00","gig played"],
["2026-10-02T10:00","gig played"]].forEach(function(c){
var got = gigPhrase(parseDt(c[0]));
if (got !== c[1]) fails.push("gigPhrase " + c[0] + " expected " + c[1] + ", got " + got);
});
/* Weekdays are derived, never typed: 22 Sept 2026 is a Tuesday, 24 Sept a Thursday. */
[["2026-09-22",2],["2026-09-23",3],["2026-09-24",4]].forEach(function(c){
var got = parseDt(c[0]).getDay();
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 (STOPS.length !== 3) fails.push("STOPS length changed without updating the tests");
/* No phrase may ever contain a stale-wording artefact, on any hour of any nearby day. */
var BAD = /(\bin -|\bin 0\b|\bin 1 days\b|-1|NaN|undefined)/;
for (var probe = parseDt("2026-09-18").getTime(); probe <= parseDt("2026-09-30").getTime(); probe += 3600000){
var pp = phrases(new Date(probe));
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 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 (fails.length){
if (typeof console !== "undefined") console.error("DATE-MATH CHECK FAILED:\n " + fails.join("\n "));
throw new Error("date-math check failed (" + fails.length + ")");
}
if (typeof console !== "undefined") console.log("date-math check: PASS");
})();
</script>
<script>
(function(){
var C = window.gigClock;
var p = C.phrases(new Date());
function put(id, text){ var el = document.getElementById(id); if (el) el.textContent = text; }
put("c-gig", p.gig);
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 todayEl = null;
for (var i = 0; i < stops.length && i < p.stops.length; i++){
var s = stops[i], q = p.stops[i];
var day = s.querySelector(".day"); if (day) day.textContent = q.day;
var cd = s.querySelector(".cd"); if (cd) cd.textContent = q.cd;
if (q.sameDay) todayEl = s;
}
/* One ribbon only, on the stop the clock says is today. */
if (todayEl){
todayEl.classList.add("today");
var r = document.createElement("span");
r.className = "ribbon";
r.setAttribute("aria-hidden", "true");
todayEl.insertBefore(r, todayEl.firstChild);
}
/* Next-3-moves ticks, remembered locally. Storage is optional: every access guarded. */
var KEY = "pv.gig.thu24.moves";
var boxes = document.querySelectorAll(".moves input[type=checkbox]");
var saved = {};
try {
var raw = window.localStorage.getItem(KEY);
if (raw) { saved = JSON.parse(raw) || {}; }
} catch (e) { saved = {}; }
for (var k = 0; k < boxes.length; k++){
var id = boxes[k].getAttribute("data-move");
if (saved && saved[id] === true) { boxes[k].checked = true; }
boxes[k].addEventListener("change", function(){
var state = {};
for (var n = 0; n < boxes.length; n++){
state[boxes[n].getAttribute("data-move")] = boxes[n].checked;
}
try { window.localStorage.setItem(KEY, JSON.stringify(state)); } catch (e) {}
});
}
})();
</script>
</body>
</html>
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