01
type Grid = HTMLSpanElement[][];
type GridFn = (x: number, y: number) => { val: string; bold?: boolean };
const pre = document.createElement('pre')
document.body.appendChild(pre)
const cols = 40;
const rows = 18;
const start = Date.now();
function elapsed() {
return Date.now() - start;
}
function osc(freq: number) {
return Math.sin(elapsed() / (1000 / freq))
}
const grid = generateCols(cols, rows, "x", pre);
frame();
const int = setInterval(frame, 80);
function frame() {
runGridFn(grid, simpleCircle);
}
function circleFn(
freq: number,
centerX: number,
centerY: number,
tMax: number,
tMin: number
) {
return (x: number, y: number) => {
const prog = Math.abs(Math.sin(elapsed() / (1000 / freq)));
const d = dist(centerX, centerY, x, y * 1.8);
return d > tMax * prog || d < tMin * prog;
};
}
function wavesBool(x: number, y: number) {
const d = dist(0.5, 0.75, x, y * 1.5)
const a = Math.atan2(x - 0.5, (y - 0.5) * 1.5)
return Math.abs(osc(4) * d) > 0.1
}
function roughlyEqual(a: number, b: number, tolerance: number) {
return a > b - (tolerance / 2) && a < b + (tolerance / 2)
}
function waves(x: number, y: number) {
const yes = wavesBool(x, y)
if (yes) {
return { val: rand("OXCEBA".split("")), bold: true };
} else {
return { val: rand(" ...,,,--*".split("")) };
}
}
function circles2(x: number, y: number) {
const fns = [
circleFn(2.1, 0.3, 0.3, 0.2, 0.1)(x, y),
circleFn(3, 0.3, 0.7, 0.3, 0.05)(x, y),
circleFn(3.2, 0.3, 1.3, 0.1, 0.01)(x, y),
circleFn(3.7, 0.7, 0.3, 0.15, 0.02)(x, y),
circleFn(4.1, 0.7, 0.7, 0.2, 0.1)(x, y),
circleFn(3.5, 0.7, 1.3, 0.24, 0.15)(x, y),
];
const yes = fns.filter(Boolean).length % 2 == 0;
if (wavesBool(x, y)) {
return { val: rand("OXCEBA".split("")), bold: true };
} else {
return { val: rand(".....,-*".split("")) };
}
}
function circles(x: number, y: number) {
const d1 = dist(0.3, 0.5, x, (y - 0.1) * 1.5);
const d2 = dist(0.7, 0.5, x, (y - 0.2) * 1.6);
const tMax1 = Math.sin(elapsed() / 500) * 0.18 + 0.22;
const tMin1 = Math.sin(elapsed() / 500) * 0.15;
const tMax2 = Math.sin(elapsed() / 400) * 0.18 + 0.22;
const tMin2 = Math.sin(elapsed() / 400) * 0.15;
const in1 = d1 > tMax1 || d1 < tMin1;
const in2 = d2 > tMax2 || d2 < tMin2;
const border = x < 0.02 || x > 0.98 || y < 0.02 || y > 0.98;
if (border) {
return { val: rand("'[]{}'".split("")) };
}
if (in1 == in2) {
return { val: rand(".....,-*".split("")) };
} else {
return { val: rand("OXCEBA".split("")), bold: true };
}
}
function simpleCircle(x: number, y: number) {
const d = dist(x, y, 0.5, 0.5);
if (roughlyEqual(d, unipolar(Math.tan(elapsed() / 5000)) / 2), 4) {
return { val: rand(".....,-*".split("")) };
} else {
return { val: rand("OXCEBA".split("")), bold: true };
}
}
function rand<T>(arr: T[]) {
return arr[Math.floor(Math.random() * arr.length)];
}
function dist(ax: number, ay: number, bx: number, by: number) {
return Math.hypot(bx - ax, by - ay);
}
function runGridFn(g: Grid, f: GridFn) {
const height = g.length
const width = g[0].length
console.log({
cols,
rows,
min: computeYP(cols, rows, 0),
max: computeYP(cols, rows, cols),
stocazzo: true,
})
return
for (let y = 0; y < g.length; y++) {
const row = g[y];
for (let x = 0; x < row.length; x++) {
const cell = row[x];
const yp = computeYP(width, height, y);
const xp = x / row.length;
const result = f(xp, yp);
cell.textContent = result.val;
cell.style.fontWeight = result.bold ? "bold" : "normal";
}
}
}
function computeYP(width: number, height: number, y: number) {
const aspectRatio = width / (height * 2)
const shorterSide = 1.0
const isPortrait = aspectRatio <= 1.0
const longerSide = 1 / (isPortrait ? aspectRatio : 1 / aspectRatio)
const longerSideOffset = longerSide / 2 - 0.5
console.log({ aspectRatio, isPortrait, longerSide })
if (!isPortrait) {
console.log('its not portrait mode')
return y / height;
}
console.log('it IS portrait mode')
return map(y/height, 0, 1, 0, longerSide) - longerSideOffset
}
function computeXP(width: number, height: number, x: number) {
const aspectRatio = width / (height * 2)
const shorterSide = 1.0
const isPortrait = aspectRatio <= 1.0
const longerSide = 1 / (isPortrait ? aspectRatio : 1 / aspectRatio)
const longerSideOffset = longerSide / 2 - 0.5
if (!isPortrait) {
return y / height;
}
return map(x/width, 0, 1, 0, longerSide) - (longerSide / 2 - 0.5)
}
function map(s: number, f0: number, f1: number, t0: number, t1: number): number {
return t0 + ((s - f0) / (f1 - f0)) * (t1 - t0);
}
function lerp(a: number, b: number, t: number): number {
return a + (b - a) * t;
}
function generateCols(
cn: number,
rn: number,
text: string,
container: HTMLElement
): Grid {
const spans = [];
for (let r = 0; r < rn; r++) {
const spanRow = [];
for (let c = 0; c < cn; c++) {
const sp = document.createElement("span");
sp.textContent = text;
spanRow.push(sp);
if (container) {
container.appendChild(sp);
}
}
spans.push(spanRow);
if (container) {
container.appendChild(document.createElement("br"));
}
}
return spans;
}
function unipolar(s: number) {
return s * 0.5 + 0.5
}
function bipolar(s: number) {
return s * 2 - 1
}
02
let seed;
let palette;
let nodes = [];
let edges = [];
let layers = [];
let config = {
nodeCount: 18,
extraPoints: 60,
radialSymmetry: 6,
warpScale: 0.0015,
curvesPerEdge: 120,
lineWeightBase: 0.6,
layerCount: 5,
};
function setup() {
createCanvas(min(windowWidth, 1000), min(windowHeight, 800));
pixelDensity(max(1, displayDensity()));
frameRate(30);
noLoop();
init();
}
function init() {
seed = floor(random(1e9));
randomSeed(seed);
noiseSeed(seed);
palette = generatePalette();
nodes = generateNodes(config.nodeCount);
edges = generateEdges(nodes);
layers = generateLayers(config.layerCount);
background(palette.bg);
drawDiagram();
}
function draw() {
}
function windowResized() {
resizeCanvas(min(windowWidth, 1000), min(windowHeight, 800));
init();
}
function keyPressed() {
if (key === ' ') {
init();
redraw();
} else if (key === 'S' || key === 's') {
saveCanvas('diagram_' + seed, 'png');
}
}
function generatePalette() {
let p = {};
p.bg = color(18, 20, 28);
p.accents = [
color(230, 120, 150),
color(120, 200, 230),
color(180, 230, 150),
color(255, 200, 120),
];
p.stroke = color(200, 200, 210, 200);
p.soft = color(100, 110, 140, 36);
return p;
}
function generateNodes(n) {
let pts = [];
let cx = width / 2;
let cy = height / 2;
let r = min(width, height) * 0.38;
for (let i = 0; i < n; i++) {
let angle = random(TWO_PI);
let radius = r * (0.35 + pow(random(), 1.8) * 0.65);
let x = cx + cos(angle) * radius;
let y = cy + sin(angle) * radius;
let size = map(random(), 0, 1, min(width, height) * 0.02, min(width, height) * 0.06);
pts.push({ x, y, size, angle, radius });
}
for (let i = 0; i < config.extraPoints; i++) {
let a = random(TWO_PI);
let rr = r * (0.05 + random() * 0.95);
pts.push({ x: cx + cos(a) * rr, y: cy + sin(a) * rr, size: random() * 6 + 2, tiny: true });
}
return pts;
}
function generateEdges(pts) {
let e = [];
let majors = pts.filter(p => !p.tiny);
for (let i = 0; i < majors.length; i++) {
let a = majors[i];
let dists = majors.map((b, j) => ({ j, d: dist(a.x, a.y, b.x, b.y) }));
dists.sort((p, q) => p.d - q.d);
let nConnections = 2 + floor(random() * 3);
for (let k = 1; k <= nConnections; k++) {
let b = majors[dists[k].j];
if (!b) continue;
if (random() < 0.12) continue;
e.push({ a, b, weight: map(random(), 0, 1, 0.3, 2.0) });
}
}
for (let i = 0; i < 6; i++) {
let p1 = random(majors);
let p2 = random(majors);
if (p1 !== p2) e.push({ a: p1, b: p2, weight: map(random(), 0, 1, 0.4, 1.8) });
}
return e;
}
function generateLayers(count) {
let arr = [];
for (let i = 0; i < count; i++) {
arr.push({ opacity: map(i, 0, count - 1, 60, 220), jitter: map(i, 0, count - 1, 0.6, 2.6), scale: map(i, 0, count - 1, 0.96, 1.03) });
}
return arr;
}
function drawDiagram() {
background(palette.bg);
push();
translate(0, 0);
drawSoftNoiseLayer();
for (let i = 0; i < config.radialSymmetry; i++) {
push();
translate(width / 2, height / 2);
rotate((TWO_PI / config.radialSymmetry) * i + (noise(i) - 0.5) * 0.4);
translate(-width / 2, -height / 2);
drawGhostShape(i);
pop();
}
for (let li = 0; li < layers.length; li++) {
let L = layers[li];
strokeWeight(config.lineWeightBase * (1 + li * 0.9));
stroke(palette.stroke);
drawEdges(L);
}
drawNodes();
drawMicroGraphistry();
drawAccents();
pop();
}
function drawSoftNoiseLayer() {
noStroke();
for (let i = 0; i < 1200; i++) {
let x = random(width);
let y = random(height);
let s = random(6);
fill(red(palette.soft), green(palette.soft), blue(palette.soft), random(6, 18));
ellipse(x, y, s, s);
}
}
function drawGhostShape(i) {
noFill();
strokeWeight(1.2);
let c = palette.accents[i % palette.accents.length];
stroke(red(c), green(c), blue(c), 28);
beginShape();
let cx = width / 2, cy = height / 2;
let R = min(width, height) * (0.25 + 0.12 * i);
for (let a = 0; a < TWO_PI + 0.1; a += 0.12) {
let w = noise(cos(a) * 0.5 + i * 10, sin(a) * 0.5 + i * 10, seed * 0.00001);
let rr = R * (0.7 + w * 0.6);
let x = cx + cos(a) * rr;
let y = cy + sin(a) * rr;
curveVertex(x, y);
}
endShape(CLOSE);
}
function drawEdges(layer) {
for (let e of edges) {
let c = palette.accents[floor(random(palette.accents.length))];
let alpha = layer.opacity * (0.6 + random() * 0.8);
stroke(red(c), green(c), blue(c), alpha);
let pts = edgePoints(e.a.x, e.a.y, e.b.x, e.b.y, e.weight * layer.jitter);
beginShape();
for (let p of pts) {
vertex(p.x, p.y);
}
endShape();
}
}
function edgePoints(x1, y1, x2, y2, jitter) {
let pts = [];
let steps = config.curvesPerEdge;
for (let i = 0; i <= steps; i++) {
let t = i / steps;
let bx = lerp(x1, x2, t);
let by = lerp(y1, y2, t);
let n = noise((bx + seed) * config.warpScale, (by + seed) * config.warpScale, t * 2.0);
let ang = TAU * (n - 0.5);
let mag = (noise(t * 3.14 + seed * 0.0001) - 0.5) * 60 * jitter;
let ox = cos(ang) * mag;
let oy = sin(ang) * mag;
let cx = width / 2, cy = height / 2;
let pull = map(dist(bx, by, cx, cy), 0, max(width, height), 0.04, 0.001);
bx += (cx - bx) * pull * random(0.2, 0.6);
by += (cy - by) * pull * random(0.2, 0.6);
pts.push({ x: bx + ox, y: by + oy });
}
return pts;
}
function drawNodes() {
for (let n of nodes) {
if (n.tiny) {
noStroke();
fill(255, 255, 255, 28 + random(28));
ellipse(n.x + random(-1, 1), n.y + random(-1, 1), n.size * random(0.6, 1.6));
continue;
}
noFill();
strokeWeight(1);
let c = palette.accents[floor(random(palette.accents.length))];
stroke(red(c), green(c), blue(c), 60);
ellipse(n.x, n.y, n.size * random(2.0, 3.6));
noFill();
let pieces = 3 + floor(random() * 4);
for (let i = 0; i < pieces; i++) {
let ang = random(TWO_PI);
let r = n.size * (0.6 + i * 0.45);
stroke(red(c), green(c), blue(c), 160 - i * 30);
strokeWeight(1 + i * 0.8);
arc(n.x, n.y, r, r, ang, ang + PI * (0.4 + random() * 0.9));
}
noStroke();
let cc = palette.accents[(floor((n.x + n.y) + seed) % palette.accents.length + palette.accents.length) % palette.accents.length];
fill(red(cc), green(cc), blue(cc), 200);
ellipse(n.x, n.y, n.size * 0.45, n.size * 0.45);
}
}
function drawMicroGraphistry() {
strokeWeight(0.6);
for (let i = 0; i < 240; i++) {
let a = random(nodes);
let b = random(nodes);
if (random() < 0.6) continue;
stroke(180, 200);
let pts = edgePoints(a.x, a.y, b.x, b.y, 0.7);
beginShape();
for (let p of pts) vertex(p.x, p.y);
endShape();
}
}
function drawAccents() {
noStroke();
for (let i = 0; i < 40; i++) {
let pick = random(nodes.filter(n => !n.tiny));
if (!pick) continue;
let c = palette.accents[i % palette.accents.length];
let s = pick.size * random(0.5, 1.6);
fill(red(c), green(c), blue(c), 170);
ellipse(pick.x + random(-s * 0.2, s * 0.2), pick.y + random(-s * 0.2, s * 0.2), s, s);
}
}
function randChoice(arr) { return arr[floor(random(arr.length))]; }