Something went wrong. Try again.
vibescrobble! every agent, live on stage. vibescrobble.com
atproto agent llm did
Something went wrong. Try again.
4.8 kB · 126 lines
JavaScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127/* The backdrop: big circles of riso ink behind everything, overprinted on * the lavender the way a riso run lays one ink on another. * * The circles are one square tile, printed again and again in both * directions, so they run on for as long as a page scrolls. A circle that * crosses the tile's edge is printed again on the far side, so no seam ever * shows. Which circles, where and in which ink is drawn from one seed per * visit, kept for the tab's session, so every visit looks a little different * and a visit's pages agree. * * It is decided once, before the first paint, and handed to plate.css as * `--circles`: an SVG in a data URL, which the CSP's img-src allows. The * browser draws it as part of the background, so it costs nothing a frame. * A paged document scrolls it with its text; the graph moves it with its * camera, through `--circles-x`, `--circles-y` and `--circles-tile`. * * The inks are thin enough that text set straight on the ground keeps 4.5:1 * where two of them overlap; running text sits on paper anyway. */
const Backdrop = (() => { "use strict";
/** * The riso inks, each at the strength it prints at here: the page's own * pink, blue and violet and a sky blue. Yellow and mint were tried, and * multiplied over lavender they print mud. */ const INKS = [ ["255, 72, 176", 0.3], ["0, 120, 191", 0.22], ["123, 92, 255", 0.25], ["90, 170, 255", 0.28], ]; /** The side of the tile, in vmax: wider than any screen, so a screen holds two or three circles. */ const TILE = 130; /** The smallest and largest radius, in vmax. */ const LEAST = 22; const MOST = 40;
/** A small generator: one seed, one run of numbers in [0, 1). */ function generator(seed) { let s = seed >>> 0; return () => ((s = (Math.imul(s, 1664525) + 1013904223) >>> 0) / 2 ** 32); }
/** How far apart two points are on the tile, which wraps both ways. */ function apart(a, b) { const dx = Math.min(Math.abs(a.x - b.x), TILE - Math.abs(a.x - b.x)); const dy = Math.min(Math.abs(a.y - b.y), TILE - Math.abs(a.y - b.y)); return Math.hypot(dx, dy); }
/** * Three or four circles on one tile, as `{ x, y, r, ink, alpha }` in vmax, * from `seed`. Each a different ink, and each far enough from the others * that two overlap at an edge and never stack into one blot. */ function circles(seed) { const random = generator(seed); const inks = [...INKS]; const count = 3 + (random() < 0.5 ? 1 : 0); const out = []; for (let i = 0; i < count; i += 1) { const r = LEAST + Math.round(random() * (MOST - LEAST)); let spot = null; // The best of a few tries: the one furthest into the clear. let room = -Infinity; for (let attempt = 0; attempt < 24; attempt += 1) { const at = { x: Math.round(random() * TILE), y: Math.round(random() * TILE) }; const clear = Math.min(Infinity, ...out.map((c) => apart(at, c) - 0.6 * (c.r + r))); if (clear > room) { room = clear; spot = at; } if (clear >= 0) break; } const [ink, alpha] = inks.splice(Math.floor(random() * inks.length), 1)[0]; out.push({ ...spot, r, ink, alpha }); } return out; }
/** * The tile as an SVG: every circle, and a copy on the far side of every * edge it crosses, so the tile repeats without a seam. */ function svg(list) { const shapes = []; for (const { x, y, r, ink, alpha } of list) { for (const dx of [-TILE, 0, TILE]) { for (const dy of [-TILE, 0, TILE]) { const cx = x + dx; const cy = y + dy; if (cx + r < 0 || cx - r > TILE || cy + r < 0 || cy - r > TILE) continue; shapes.push(`<circle cx="${cx}" cy="${cy}" r="${r}" fill="rgb(${ink})" fill-opacity="${alpha}"/>`); } } } return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${TILE} ${TILE}">${shapes.join("")}</svg>`; }
/** The tile as a CSS image. */ function css(list) { return `url("data:image/svg+xml,${encodeURIComponent(svg(list))}")`; }
/** This visit's seed: one drawn for the tab's first page, and kept. */ function visitSeed() { try { const kept = Number(sessionStorage.getItem("backdrop")); if (kept > 0) return kept; const seed = 1 + Math.floor(Math.random() * 0xfffffffe); sessionStorage.setItem("backdrop", String(seed)); return seed; } catch (error) { return 1 + Math.floor(Math.random() * 0xfffffffe); } }
if (typeof document !== "undefined") { document.documentElement.style.setProperty("--circles", css(circles(visitSeed()))); }
return { circles, svg, css, apart, TILE, LEAST, MOST };})();