The Legibility Trap
About This Sketch
A visualization exploring the distinction between legible work (visible, predictable, easily measured) and illegible work (hidden, organic, compounding). The large, bright particles orbit predictably—this is work that's easy to see and evaluate. The smaller, fading particles move organically through noise fields, growing slowly and forming hidden networks—this is deep work that happens below the surface of visibility.
Watch how the invisible particles gradually reveal themselves, form connections, and grow over time. This represents how illegible work—deep thinking, genuine relationships, skill development—compounds in ways that aren't immediately visible but create lasting value.
The sketch accompanies the blog post "The Legibility Trap" which explores how we've structured modern life around what's easy to see rather than what actually matters.
Algorithm
Pseudocode
SETUP:
Create 6 visible particles in predictable orbits
Create 40 invisible particles that move organically
Initialize all particles with position, movement parameters
DRAW (every frame):
Get theme colors for adaptation
Draw background
FOR each pair of invisible particles:
IF distance < threshold:
Draw connection line (the hidden network)
Alpha based on distance
FOR each invisible particle:
Move using Perlin noise (organic, unpredictable)
Wrap around edges (continuous field)
Grow over time (compounding value)
Fade in/out in cycle (revelation and concealment)
Draw particle with current opacity
Draw subtle motion trail
FOR each visible particle:
Draw orbital path (predictable trajectory)
Move in circular orbit
Draw particle (always fully visible)
Display subtle text hint
Source Code
let sketch = function(p) {
let visibleParticles = [];
let invisibleParticles = [];
let time = 0;
class Particle {
constructor(x, y, visible) {
this.x = x;
this.y = y;
this.visible = visible;
this.baseX = x;
this.baseY = y;
this.angle = p.random(p.TWO_PI);
this.speed = visible ? p.random(0.5, 1.5) : p.random(2, 4);
this.size = visible ? p.random(4, 8) : p.random(2, 4);
this.orbitRadius = p.random(20, 60);
this.phaseOffset = p.random(p.TWO_PI);
this.opacity = visible ? 255 : 0;
this.targetOpacity = visible ? 255 : 120;
this.growth = 0;
}
update(t) {
// Visible particles orbit predictably
if (this.visible) {
this.angle += 0.02 * this.speed;
this.x = this.baseX + p.cos(this.angle) * this.orbitRadius;
this.y = this.baseY + p.sin(this.angle) * this.orbitRadius;
} else {
// Invisible particles move more dynamically
let noise = p.noise(this.x * 0.01, this.y * 0.01, t * 0.001);
this.angle = noise * p.TWO_PI * 2;
this.x += p.cos(this.angle) * this.speed;
this.y += p.sin(this.angle) * this.speed;
// Wrap around
if (this.x < 0) this.x = 400;
if (this.x > 400) this.x = 0;
if (this.y < 0) this.y = 300;
if (this.y > 300) this.y = 0;
// Grow over time (illegible work compounds)
this.growth += 0.005;
}
// Fade in/out based on time
let revealCycle = p.sin(t * 0.002) * 0.5 + 0.5;
this.targetOpacity = this.visible ? 255 : revealCycle * 180;
this.opacity = p.lerp(this.opacity, this.targetOpacity, 0.1);
}
display(colors) {
let col = this.visible ? colors.accent2 : colors.accent1;
p.noStroke();
p.fill(col[0], col[1], col[2], this.opacity);
let displaySize = this.size;
if (!this.visible) {
displaySize += this.growth * 5;
}
p.circle(this.x, this.y, displaySize);
// Invisible particles leave subtle trails
if (!this.visible && this.opacity > 50) {
p.stroke(col[0], col[1], col[2], this.opacity * 0.3);
p.strokeWeight(1);
p.line(
this.x,
this.y,
this.x - p.cos(this.angle) * 15,
this.y - p.sin(this.angle) * 15
);
}
}
}
p.setup = function() {
p.createCanvas(400, 300);
p.colorMode(p.RGB);
// Create visible particles (legible work)
for (let i = 0; i < 6; i++) {
let x = p.random(100, 300);
let y = p.random(80, 220);
visibleParticles.push(new Particle(x, y, true));
}
// Create invisible particles (illegible work)
for (let i = 0; i < 40; i++) {
invisibleParticles.push(new Particle(
p.random(400),
p.random(300),
false
));
}
};
p.draw = function() {
const colors = getThemeColors();
p.background(...colors.bg);
time++;
// Draw connection lines between invisible particles
// (the hidden network of illegible work)
p.stroke(...colors.accent3, 20);
p.strokeWeight(1);
for (let i = 0; i < invisibleParticles.length; i++) {
for (let j = i + 1; j < invisibleParticles.length; j++) {
let d = p.dist(
invisibleParticles[i].x,
invisibleParticles[i].y,
invisibleParticles[j].x,
invisibleParticles[j].y
);
if (d < 50) {
let alpha = p.map(d, 0, 50, 30, 0);
p.stroke(...colors.accent3, alpha);
p.line(
invisibleParticles[i].x,
invisibleParticles[i].y,
invisibleParticles[j].x,
invisibleParticles[j].y
);
}
}
}
// Update and display invisible particles (they move more, grow more)
for (let p of invisibleParticles) {
p.update(time);
p.display(colors);
}
// Draw orbital paths for visible particles
p.noFill();
p.stroke(...colors.accent2, 30);
p.strokeWeight(1);
for (let p of visibleParticles) {
p.circle(p.baseX, p.baseY, p.orbitRadius * 2);
}
// Update and display visible particles (predictable orbits)
for (let p of visibleParticles) {
p.update(time);
p.display(colors);
}
// Subtle text hint
let textOpacity = (p.sin(time * 0.01) * 0.3 + 0.3) * 255;
p.fill(...colors.accent3, textOpacity);
p.noStroke();
p.textAlign(p.CENTER, p.BOTTOM);
p.textSize(10);
p.text("The visible orbits predictably. The invisible grows.", 200, 290);
};
};