rastrillo / fiesta Public

Let a drive hold the bunting's breeze and read how far its cloth strays

Two hooks, read once before the first frame, so a drive can see what scrolling does to the cloth. siteBuntingWindless keeps the breeze at zero: with the breeze blowing the cloth never rests, so there is no rest to measure a scroll against, and the loop only sleeps after eight idle seconds. siteBuntingProbe becomes a function reporting every point's extremes since the page opened, the worst link stretch, and whether the loop is running, so a violent shake can be held to the row without sampling frames from outside and missing the worst one. With neither set the page behaves exactly as before.
Human

Prompt

Can we also have the flags on the homepage react to scrolling? Like when I scroll back up they should react to the 'current' created by the scrolling or if I scroll up and down quickly to 'shake' the page they should react.

From this commit's own message. The prompt ledger has no entries for it — which is what a squashed, rebased or amended commit looks like, and what every commit looks like in a repo without a ledger.

Summary

bunting.js gains the windless and probe test hooks; no change to the page without them.
Agent notes
The scroll physics and the drives that use these hooks come in the next commit; this one is split out so those drives can be run red against today's physics.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 176c5e5a36d72629c647898ebd575978835f45e1 parent 04d01e1
1 file changed, +30 −2
  • site/static/bunting.js +30 −2
diff --git a/site/static/bunting.js b/site/static/bunting.js
index 90dd26f..1a2b3f2 100644
--- a/site/static/bunting.js
+++ b/site/static/bunting.js
@@ -40,6 +40,14 @@
var D0 = 200, CUT = 600, KW = 1.15, TAU = 0.07, RIPPLE = 0.0007, PUFF_MS = 160, MAX_PUFFS = 6, docTop = 0, docLeft = 0;
var IDLE = 8000, CALM = 1500;
var lastInput = performance.now(), breeze = 1, motion = 1;
+ // Two hooks for the tests, read once, before the first frame.
+ // siteBuntingWindless: the breeze never blows, so the cloth hangs at
+ // rest until something else moves it and a drive sees that alone.
+ // siteBuntingProbe: replaced by a function that reports how far the
+ // cloth has strayed since the page opened, for holding a violent
+ // shake to the row.
+ var windless = !!window.siteBuntingWindless, probe = window.siteBuntingProbe ? { minY: Infinity, maxY: -Infinity, minX: Infinity, maxX: -Infinity, maxZ: 0, stretch: 0, finite: true } : null;
+ if (windless) breeze = 0;
var colours = Array.prototype.map.call(svg.querySelectorAll(".site-flag"), function (g) { return +g.getAttribute("data-c"); });
var ncol = 6, tex = [], stringColour = "currentColor";
var X, Y, Z, PX, PY, PZ, INV, DEP, COL, U, V, cons, flags = [], ropeN = 0, scale = 1, offset = 0, dpr = 1;
@@ -206,7 +214,7 @@
}
while (puffs.length && tn - puffs[0].t > 1.5) puffs.shift();
gust *= 0.97;
- breeze += ((now - lastInput < IDLE ? 1 : 0) - breeze) * 0.02;
+ breeze = windless ? 0 : breeze + ((now - lastInput < IDLE ? 1 : 0) - breeze) * 0.02;
}
var p = !calm && touch && now - touch.t < 90 ? touch : null;
for (f = 0; f < flags.length; f++) {
@@ -374,12 +382,32 @@
var t = (now - t0) / 1000, n = 0;
motion = 0;
while (acc >= DT && n < 3) { step(t); acc -= DT; n++; }
+ if (probe && n) record();
// Still cloth and no breeze left: draw this last frame and sleep.
var still = n > 0 && !pending && !(touch && now - touch.t < 90) && breeze < 0.01 && gust < 0.01 && motion < 1e-4 && !(puffs.length && now / 1000 - puffs[puffs.length - 1].t < 0.6);
if (n && (still || now - lastInput < CALM || (drawn = !drawn))) draw();
if (still) { running = false; return; }
requestAnimationFrame(frame);
}
+ // record widens the probe's extremes to this frame's cloth: every
+ // point's place, and the worst stretch of any link over its length.
+ function record() {
+ for (var i = 0; i < X.length; i++) {
+ if (!isFinite(X[i] + Y[i] + Z[i])) probe.finite = false;
+ probe.minY = Math.min(probe.minY, Y[i]); probe.maxY = Math.max(probe.maxY, Y[i]);
+ probe.minX = Math.min(probe.minX, X[i]); probe.maxX = Math.max(probe.maxX, X[i]);
+ probe.maxZ = Math.max(probe.maxZ, Math.abs(Z[i]));
+ }
+ for (var k = 0; k < cons.length; k += 4) {
+ var a = cons[k], c = cons[k + 1];
+ probe.stretch = Math.max(probe.stretch, Math.hypot(X[c] - X[a], Y[c] - Y[a], Z[c] - Z[a]) / cons[k + 2]);
+ }
+ }
+ if (probe) window.siteBuntingProbe = function () {
+ return JSON.stringify({ MinY: probe.minY, MaxY: probe.maxY, MinX: probe.minX, MaxX: probe.maxX, MaxZ: probe.maxZ, Stretch: probe.stretch, Finite: probe.finite,
+ Height: canvas.getBoundingClientRect().height / scale, From: X ? X[0] : 0, To: X ? X[ropeN - 1] : 0, Running: running });
+ };
+
// window.siteBuntingStill is a hook for the tests: set before this
// runs, the canvas is hung at rest and never moves, so a drive can
// hold the still SVG to the first frame.
@@ -454,7 +482,7 @@
draw();
row.classList.add("site-bunting-live");
lastInput = performance.now();
- breeze = 1;
+ breeze = windless ? 0 : 1;
start();
});
}