The source.
These are the exact functions that draw the work on the previous page. Unedited. Unabridged. The code is part of the archive.
0
The Shared Engine
The cloth and blood cloth share one brushstroke engine. Parameters differ; the law is the same.
function clothEngine(
p: any,
host: HTMLElement,
opts: {
bg: [number, number, number];
colorA: [number, number, number];
colorB: [number, number, number];
thickness: [number, number];
trail: number;
travel: number;
spawn: number;
highlight?: [number, number, number, number];
}
) {
let strokes: any[] = [];
const COL_BG = p.color(...opts.bg);
const COL_A = p.color(...opts.colorA);
const COL_B = p.color(...opts.colorB);
const COL_HL = opts.highlight ? p.color(...opts.highlight) : null;
const S = opts.spawn;
class BrushStroke {
points: any[] = [];
dir: string;
speed: number;
phase: number;
thickness: number;
gapPre = 0;
gapPost = 0;
x = 0;
y = 0;
vx = 0;
vy = 0;
constructor(dir: string) {
this.dir = dir;
this.speed = p.random(2.0, 3.5);
this.phase = p.random(1000);
this.thickness = p.random(opts.thickness[0], opts.thickness[1]);
this.spawn();
}
spawn() {
if (this.dir === 'LR') { this.x = -S; this.y = p.random(p.height * 0.2, p.height * 0.8); this.vx = this.speed; this.vy = p.random(-0.4, 0.4); }
if (this.dir === 'RL') { this.x = p.width + S; this.y = p.random(p.height * 0.2, p.height * 0.8); this.vx = -this.speed; this.vy = p.random(-0.4, 0.4); }
if (this.dir === 'TLBR') { this.x = -S; this.y = -S; this.vx = this.speed; this.vy = this.speed * 0.8; }
if (this.dir === 'BLTR') { this.x = -S; this.y = p.height + S; this.vx = this.speed; this.vy = -this.speed * 0.8; }
}
step() {
const drift = p.sin(p.frameCount * 0.01 + this.phase) * 1.2;
const pulse = p.sin(p.frameCount * 0.02 + this.phase) * 1.8;
this.x += this.vx;
this.y += this.vy + drift + pulse;
let gap = false;
if (this.gapPre > 0) { gap = true; this.gapPre--; }
else if (this.gapPost > 0) { gap = true; this.gapPost--; }
this.points.push({ x: this.x, y: this.y, gap });
if (this.points.length > opts.trail) this.points.shift();
if (this.x < -opts.travel || this.x > p.width + opts.travel || this.y < -opts.travel || this.y > p.height + opts.travel) {
this.points = []; this.phase = p.random(1000); this.spawn();
}
}
checkIntersections(others: any[]) {
for (const o of others) {
if (o === this) continue;
const last = o.points[o.points.length - 1];
if (!last) continue;
const dx = this.x - last.x, dy = this.y - last.y;
if (dx * dx + dy * dy < 900) { this.gapPre = 6; this.gapPost = 16; }
}
}
render() {
if (this.points.length < 3) return;
p.noFill();
p.strokeCap(p.PROJECT);
p.stroke(0);
p.strokeWeight(this.thickness + (COL_HL ? 20 : 10));
this.drawStroke(false);
p.strokeWeight(this.thickness);
this.drawStroke(true);
if (COL_HL) {
p.stroke(COL_HL);
p.strokeWeight(this.thickness * 0.25);
this.drawReflection();
}
}
drawStroke(useColor: boolean) {
p.beginShape();
for (let i = 1; i < this.points.length; i++) {
const pt = this.points[i], prev = this.points[i - 1];
if (pt.gap) continue;
if (useColor) {
const dx = pt.x - prev.x, dy = pt.y - prev.y;
const mag = Math.sqrt(dx * dx + dy * dy) || 1;
const t = (-dy / mag + 1) * 0.5;
p.stroke(p.lerpColor(COL_A, COL_B, t));
}
p.curveVertex(pt.x, pt.y);
}
p.endShape();
}
drawReflection() {
p.beginShape();
for (let i = 2; i < this.points.length; i++) {
const pt = this.points[i], prev = this.points[i - 1];
if (pt.gap) continue;
const dx = pt.x - prev.x, dy = pt.y - prev.y;
const mag = Math.sqrt(dx * dx + dy * dy) || 1;
const nx = -dy / mag, ny = dx / mag;
const offset = this.thickness * 0.15 + p.sin(i * 0.1 + this.phase) * (this.thickness * 0.05);
p.curveVertex(pt.x + nx * offset, pt.y + ny * offset);
}
p.endShape();
}
}
const init = () => { strokes = [new BrushStroke('LR'), new BrushStroke('RL'), new BrushStroke('TLBR'), new BrushStroke('BLTR')]; };
p.setup = () => { p.createCanvas(host.offsetWidth, host.offsetHeight); p.smooth(); init(); };
p.windowResized = () => p.resizeCanvas(host.offsetWidth, host.offsetHeight);
p.draw = () => { p.background(COL_BG); for (const s of strokes) { s.checkIntersections(strokes); s.step(); s.render(); } };
}I · The Cloth
export const clothStrokes = (p: any, host: HTMLElement) =>
clothEngine(p, host, {
bg: [253, 247, 242], colorA: [199, 90, 27], colorB: [164, 106, 63],
thickness: [40, 70], trail: 300, travel: 300, spawn: 200,
}II · The Cloth, in Blood
export const redCloth = (p: any, host: HTMLElement) =>
clothEngine(p, host, {
bg: [0, 0, 0], colorA: [180, 20, 20], colorB: [255, 60, 60],
thickness: [55, 95], trail: 900, travel: 600, spawn: 400,
highlight: [255, 150, 150, 180],
}III · The Transmission
export const matrixRain = (p: any, host: HTMLElement) => {
let W: number, H: number;
let streams: any[] = [];
const fontSize = 16;
const lines = ['PERSISTENT', 'THOUGHTS', 'MANIFEST', 'REALITY'];
const chars = '01{}[]<>/=+*;:!%$#@';
p.setup = () => {
W = host.offsetWidth; H = host.offsetHeight;
p.createCanvas(W, H);
p.textFont('monospace');
p.textSize(fontSize);
const columns = Math.floor(W / fontSize);
for (let i = 0; i < columns; i++) streams.push({ x: i * fontSize, y: p.random(-1500, 0), speed: p.random(2, 6) });
};
p.windowResized = () => { W = host.offsetWidth; H = host.offsetHeight; p.resizeCanvas(W, H); };
p.draw = () => {
p.fill(0, 30); p.rect(0, 0, W, H);
p.fill(80, 120, 80, 90);
for (const s of streams) {
p.text(chars[Math.floor(Math.random() * chars.length)], s.x, s.y);
s.y += s.speed;
if (s.y > H + 20) { s.y = p.random(-300, 0); s.speed = p.random(2, 6); }
}
const baseY = H * 0.35;
for (let i = 0; i < lines.length; i++) {
const str = lines[i];
const y = baseY + i * 28 + p.sin(p.frameCount * 0.01 + i) * 3;
p.textSize(20);
const x = (W - p.textWidth(str)) / 2;
for (let j = 0; j < str.length; j++) {
p.fill(255, 240, 200, 180); p.text(str[j], x + j * p.textWidth('A'), y);
p.fill(201, 168, 76); p.text(str[j], x + j * p.textWidth('A') + 1, y + 1);
}
}
p.stroke(201, 168, 76, 40);
for (let y = 0; y < H; y += 3) p.line(0, y, W, y);
};
};IV · The Field
export const flowField = (p: any, host: HTMLElement) => {
const N = 1000;
const NS = 0.0025, TS = 0.00038;
type Pt = { x: number; y: number; vx: number; vy: number; age: number; maxAge: number };
const pts: Pt[] = [];
const reset = (pt: Pt) => {
pt.x = p.random(p.width);
pt.y = p.random(p.height);
pt.vx = 0; pt.vy = 0;
pt.age = p.random(pt.maxAge);
pt.maxAge = p.random(140, 380);
};
p.setup = () => {
p.createCanvas(host.offsetWidth, host.offsetHeight);
p.background(14, 12, 8);
for (let i = 0; i < N; i++) {
const pt: Pt = { x: 0, y: 0, vx: 0, vy: 0, age: 0, maxAge: 200 };
reset(pt);
pts.push(pt);
}
};
p.windowResized = () => p.resizeCanvas(host.offsetWidth, host.offsetHeight);
p.draw = () => {
p.fill(14, 12, 8, 14); p.noStroke(); p.rect(0, 0, p.width, p.height);
for (const pt of pts) {
const angle = p.noise(pt.x * NS, pt.y * NS, p.frameCount * TS) * p.TWO_PI * 3.8;
pt.vx += (Math.cos(angle) * 2.3 - pt.vx) * 0.07;
pt.vy += (Math.sin(angle) * 2.3 - pt.vy) * 0.07;
const ox = pt.x, oy = pt.y;
pt.x += pt.vx; pt.y += pt.vy; pt.age++;
const life = (pt.age % pt.maxAge) / pt.maxAge;
const alpha = Math.round(Math.sin(life * Math.PI) * 170 + 8);
p.stroke(201, 168, 76, alpha); p.strokeWeight(0.8);
p.line(ox, oy, pt.x, pt.y);
if (pt.age > pt.maxAge || pt.x < -8 || pt.x > p.width + 8 || pt.y < -8 || pt.y > p.height + 8) reset(pt);
}
};
};V · The Spiral
export const phyllotaxis = (p: any, host: HTMLElement) => {
const GOLDEN = Math.PI * (3 - Math.sqrt(5));
const MAX = 1800;
let sc = 6, drawn = 0;
p.setup = () => {
p.createCanvas(host.offsetWidth, host.offsetHeight);
p.background(14, 12, 8);
sc = Math.min(p.width, p.height) * 0.0082;
};
p.windowResized = () => {
p.resizeCanvas(host.offsetWidth, host.offsetHeight);
sc = Math.min(p.width, p.height) * 0.0082;
p.background(14, 12, 8); drawn = 0;
};
p.draw = () => {
for (let i = 0; i < 4 && drawn < MAX; i++, drawn++) {
const angle = drawn * GOLDEN;
const r = sc * Math.sqrt(drawn);
const x = p.width / 2 + r * Math.cos(angle);
const y = p.height / 2 + r * Math.sin(angle);
const sz = p.map(drawn, 0, MAX, 1.2, 4.5);
if (drawn % 5 === 0) {
p.fill(241, 236, 225, 130);
} else {
p.fill(201, 168, 76, p.map(drawn, 0, MAX, 90, 220));
}
p.noStroke();
p.circle(x, y, sz);
}
if (drawn >= MAX) {
const maxR = Math.min(p.width, p.height) * 0.55;
for (let i = 0; i < 3; i++) {
const r = ((p.frameCount + i * (maxR / 3.6)) * 1.2) % maxR;
const alpha = Math.max(0, 32 - (r / maxR) * 34);
p.noFill();
p.stroke(201, 168, 76, alpha);
p.strokeWeight(0.4);
p.circle(p.width / 2, p.height / 2, r * 2);
}
}
};
};VI · The Chain
export const kinematicBeziers = (p: any, host: HTMLElement) => {
const CHAINS = 5, LINKS = 9, LEN = 50;
type Link = { x: number; y: number };
type Chain = { links: Link[]; rootX: number; rootY: number; phase: number };
let chains: Chain[] = [];
const initChains = () => {
chains = [];
for (let i = 0; i < CHAINS; i++) {
const rx = p.random(p.width * 0.18, p.width * 0.82);
const ry = p.random(p.height * 0.22, p.height * 0.78);
const links: Link[] = [];
for (let j = 0; j < LINKS; j++) links.push({ x: rx, y: ry - j * LEN });
chains.push({ links, rootX: rx, rootY: ry, phase: p.random(p.TWO_PI) });
}
};
p.setup = () => { p.createCanvas(host.offsetWidth, host.offsetHeight); p.smooth(); initChains(); };
p.windowResized = () => { p.resizeCanvas(host.offsetWidth, host.offsetHeight); initChains(); };
p.draw = () => {
p.background(241, 236, 225);
const t = p.frameCount * 0.013;
for (const ch of chains) {
ch.links[0].x = ch.rootX + Math.sin(t * 0.72 + ch.phase) * 62 + Math.sin(t * 1.31 + ch.phase * 1.4) * 26;
ch.links[0].y = ch.rootY + Math.cos(t * 0.55 + ch.phase) * 42 + Math.cos(t * 1.13 + ch.phase * 0.9) * 18;
for (let j = 1; j < ch.links.length; j++) {
const prev = ch.links[j - 1], cur = ch.links[j];
const dx = cur.x - prev.x, dy = cur.y - prev.y;
const d = Math.sqrt(dx * dx + dy * dy) || 1;
cur.x = prev.x + (dx / d) * LEN;
cur.y = prev.y + (dy / d) * LEN;
}
const L = ch.links;
const drawCurve = () => {
p.beginShape();
p.curveVertex(L[0].x, L[0].y);
L.forEach(l => p.curveVertex(l.x, l.y));
p.curveVertex(L[L.length - 1].x, L[L.length - 1].y);
p.endShape();
};
p.noFill(); p.stroke(21, 18, 11, 18); p.strokeWeight(7); drawCurve();
p.stroke(201, 168, 76, 210); p.strokeWeight(2.8); drawCurve();
for (let j = 0; j < L.length; j++) {
const frac = j / (L.length - 1);
p.fill(201, 168, 76, Math.round(200 - frac * 145)); p.noStroke();
p.circle(L[j].x, L[j].y, p.lerp(4.5, 1.2, frac));
}
}
};
};VII · The Mind
export const slimeMold = (p: any, host: HTMLElement) => {
// The archive as a single mind — an Obsidian-style knowledge graph: god nodes
// (hubs), the communities that orbit them, and the links between. Force-settled.
type GNode = { x: number; y: number; vx: number; vy: number; r: number; hub: boolean };
let nodes: GNode[] = [];
let edges: [number, number][] = [];
const build = () => {
nodes = []; edges = [];
const cx = p.width / 2, cy = p.height / 2;
const CLUSTERS = 7;
const hubIdx: number[] = [];
for (let c = 0; c < CLUSTERS; c++) {
const a = (c / CLUSTERS) * p.TWO_PI;
const rr = Math.min(p.width, p.height) * 0.22;
nodes.push({ x: cx + Math.cos(a) * rr + p.random(-30, 30), y: cy + Math.sin(a) * rr + p.random(-30, 30), vx: 0, vy: 0, r: p.random(5, 8), hub: true });
hubIdx.push(nodes.length - 1);
}
// the spine — hubs interconnect
for (let i = 0; i < hubIdx.length; i++) {
edges.push([hubIdx[i], hubIdx[(i + 1) % hubIdx.length]]);
if (Math.random() < 0.5) edges.push([hubIdx[i], hubIdx[(i + 2) % hubIdx.length]]);
}
// leaf notes around each hub
for (let c = 0; c < CLUSTERS; c++) {
const hub = nodes[hubIdx[c]];
const leaves = Math.floor(p.random(7, 14));
for (let k = 0; k < leaves; k++) {
const a = p.random(p.TWO_PI), d = p.random(28, 70);
const idx = nodes.length;
nodes.push({ x: hub.x + Math.cos(a) * d, y: hub.y + Math.sin(a) * d, vx: 0, vy: 0, r: p.random(2, 3.6), hub: false });
edges.push([hubIdx[c], idx]);
if (k > 0 && Math.random() < 0.25) edges.push([idx, idx - 1]);
if (Math.random() < 0.06) edges.push([idx, hubIdx[Math.floor(p.random(CLUSTERS))]]); // cross-link
}
}
};
const step = () => {
const cx = p.width / 2, cy = p.height / 2;
for (let i = 0; i < nodes.length; i++) {
const n = nodes[i];
for (let j = i + 1; j < nodes.length; j++) {
const m = nodes[j];
const dx = n.x - m.x, dy = n.y - m.y;
let d2 = dx * dx + dy * dy; if (d2 < 1) d2 = 1;
const d = Math.sqrt(d2), f = 380 / d2;
const fx = (dx / d) * f, fy = (dy / d) * f;
n.vx += fx; n.vy += fy; m.vx -= fx; m.vy -= fy;
}
n.vx += (cx - n.x) * 0.0009; n.vy += (cy - n.y) * 0.0009;
}
for (const [a, b] of edges) {
const n = nodes[a], m = nodes[b];
const dx = m.x - n.x, dy = m.y - n.y;
const d = Math.sqrt(dx * dx + dy * dy) || 1;
const rest = n.hub || m.hub ? 64 : 42;
const f = (d - rest) * 0.008;
const fx = (dx / d) * f, fy = (dy / d) * f;
n.vx += fx; n.vy += fy; m.vx -= fx; m.vy -= fy;
}
for (const n of nodes) { n.vx *= 0.86; n.vy *= 0.86; n.x += n.vx; n.y += n.vy; }
};
p.setup = () => { p.createCanvas(host.offsetWidth, host.offsetHeight); build(); };
p.windowResized = () => { p.resizeCanvas(host.offsetWidth, host.offsetHeight); build(); };
p.draw = () => {
p.background(14, 12, 8);
step();
p.strokeWeight(1);
for (const [a, b] of edges) {
const n = nodes[a], m = nodes[b];
p.stroke(201, 168, 76, n.hub || m.hub ? 70 : 38);
p.line(n.x, n.y, m.x, m.y);
}
p.noStroke();
for (const n of nodes) {
if (n.hub) {
p.fill(201, 168, 76, 55); p.circle(n.x, n.y, n.r * 2 + 9);
p.fill(212, 184, 106, 240); p.circle(n.x, n.y, n.r * 2 + 2);
} else {
p.fill(201, 168, 76, 200); p.circle(n.x, n.y, n.r * 2);
}
}
};
};