Where ideas percolate and thoughts brew

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

This sketch visualizes the contrast between legible and illegible work through two types of particles: Visible particles (legible work) orbit predictably around fixed points. They're easy to see, their paths are clear, and their behavior is simple to understand. Their orbital paths are drawn to emphasize their predictability. Invisible particles (illegible work) move using Perlin noise—organically, unpredictably, and with purpose that's not immediately apparent. They start nearly invisible and gradually fade in and out, revealing a hidden network of connections. Most importantly, they grow over time, representing how illegible work compounds in ways that aren't immediately visible. The network of connections between invisible particles represents the hidden structure of deep work—relationships and patterns that only become apparent with sustained attention and that exist below the surface of what's easily observable. The sketch cycles between revealing and concealing the invisible particles, suggesting that illegible work exists whether or not we can see it, and that our ability to perceive it is itself variable.

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);
    };
};