← back to Particle Text
Starling: save motion graphic as animated GIF (dependency-free GIF89a: direct-render capture, median-cut palette, LZW) + copy source code
76daa36ba0f981ac216772f1ce3752b6b97b2557 · 2026-07-25 09:14:33 -0700 · Steve
Files touched
Diff
commit 76daa36ba0f981ac216772f1ce3752b6b97b2557
Author: Steve <steve@designerwallcoverings.com>
Date: Sat Jul 25 09:14:33 2026 -0700
Starling: save motion graphic as animated GIF (dependency-free GIF89a: direct-render capture, median-cut palette, LZW) + copy source code
---
index.html | 53 +++++++++++++++++++++++++++++++++++++----------------
1 file changed, 37 insertions(+), 16 deletions(-)
diff --git a/index.html b/index.html
index e028b39..9752e14 100644
--- a/index.html
+++ b/index.html
@@ -469,23 +469,22 @@
function put(code, size) { cur |= code << bits; bits += size; while (bits >= 8) { out.push(cur & 255); cur = cur >>> 8; bits -= 8; } }
var clear = 1 << minCode, eoi = clear + 1;
var size, next, dict;
- function reset() { size = minCode + 1; next = eoi + 1; dict = new Map(); }
- reset();
+ function init() { size = minCode + 1; next = eoi + 1; dict = new Map(); }
+ init();
put(clear, size);
if (idx.length) {
- var prev = idx[0];
+ var key = idx[0]; // running code (starts as the first literal)
for (var i = 1; i < idx.length; i++) {
- var c = idx[i], key = (prev << 8) | c;
- if (dict.has(key)) { prev = dict.get(key); }
+ var k = idx[i], ck = (key << 8) | k;
+ if (dict.has(ck)) { key = dict.get(ck); }
else {
- put(prev, size);
- dict.set(key, next++);
- if (next === (1 << size) && size < 12) size++;
- if (next > 4095) { put(clear, size); reset(); }
- prev = c;
+ put(key, size);
+ if (next === 4096) { put(clear, size); init(); } // dict full → reset
+ else { if (next >= (1 << size)) size++; dict.set(ck, next++); } // grow size BEFORE assigning
+ key = k;
}
}
- put(prev, size);
+ put(key, size);
}
put(eoi, size);
if (bits > 0) out.push(cur & 255);
@@ -522,11 +521,24 @@
gw -= gw % 2; gh -= gh % 2;
var off = document.createElement("canvas"); off.width = gw; off.height = gh;
var octx = off.getContext("2d");
- var frames = [], TOTAL = 40, DELAY = 6; // ~16fps, ~2.4s
+ var sx = gw / W, sy = gh / H; // map particle coords into the GIF frame
+ var dotR = Math.max(1, Math.min(gw, gh) / 230); // crisp, visible dot at GIF scale
+ var frames = [], TOTAL = 40, DELAY = 6; // ~16fps, ~2.4s
+ octx.fillStyle = "#05060a"; octx.fillRect(0, 0, gw, gh);
scatterAll();
var f = 0;
var iv = setInterval(function () {
- octx.drawImage(canvas, 0, 0, gw, gh);
+ // render particles straight at GIF resolution (preserves brightness + additive glow)
+ octx.globalCompositeOperation = "source-over";
+ octx.fillStyle = "rgba(5,6,10,0.34)"; octx.fillRect(0, 0, gw, gh); // motion trail
+ octx.globalCompositeOperation = "lighter";
+ for (var pi = 0; pi < particles.length; pi++) {
+ var pp = particles[pi];
+ octx.fillStyle = pp.color;
+ octx.beginPath();
+ octx.arc(pp.x * sx, pp.y * sy, dotR, 0, 6.2832);
+ octx.fill();
+ }
frames.push(octx.getImageData(0, 0, gw, gh).data.slice());
if (++f >= TOTAL) { clearInterval(iv); encode(); }
else toast("Recording GIF… " + Math.round(f / TOTAL * 100) + "%", 900);
@@ -535,11 +547,20 @@
function encode() {
toast("Encoding GIF…", 6000);
setTimeout(function () {
- var cols = [];
- for (var fi = 0; fi < frames.length; fi += 3) {
+ // Sample for the palette: keep EVERY bright particle pixel (they're sparse but are the
+ // whole point), and only a thin slice of the dark background — otherwise median-cut spends
+ // all 256 slots on near-black and quantizes the glow away.
+ var cols = [], dc = 0;
+ for (var fi = 0; fi < frames.length; fi += 2) {
var d = frames[fi];
- for (var i = 0; i < d.length; i += 28) cols.push((d[i] << 16) | (d[i + 1] << 8) | d[i + 2]);
+ for (var i = 0; i < d.length; i += 4) {
+ var r = d[i], g = d[i + 1], b = d[i + 2];
+ var bright = r > g ? r : g; if (b > bright) bright = b;
+ if (bright > 40) cols.push((r << 16) | (g << 8) | b);
+ else if ((dc++ & 15) === 0) cols.push((r << 16) | (g << 8) | b);
+ }
}
+ if (cols.length < 512) for (var i = 0; i < 512; i++) cols.push(0x05060a);
var pal = buildPalette(cols);
var cache = new Int16Array(262144); for (var i = 0; i < cache.length; i++) cache[i] = -1;
var gif = GifWriter(gw, gh, pal);
← 7a75395 auto-save: 2026-07-25T09:05:11 (1 files) — index.html
·
back to Particle Text
·
5x sweep 1: add inline favicon (kills /favicon.ico 404 conso 34f7be7 →