Where ideas percolate and thoughts brew

The Insight Paradox

About This Sketch

A visualization of emergent insight. Particles wander aimlessly, guided by subtle forces and random noise. Connections form spontaneously when thoughts drift close enough—never by force, only through the natural convergence of wandering ideas. Watch how the most interesting patterns emerge not from directed movement, but from the organic interplay of undirected motion. This is how real thinking works.

Algorithm

This sketch visualizes how insights emerge from wandering, undirected thought. Particles enter from the edges and meander toward the center using Perlin noise for organic, unpredictable movement. When particles drift close enough to each other—without being forced—connections spontaneously form, representing the moment when disparate ideas combine into insight. The key metaphor: particles can't be directed to connect. They have to wander freely, and connections emerge naturally when they happen to be near each other. This mirrors how real insight works—you can't force the connection, you have to create conditions where wandering thoughts can naturally converge. The soft fading trail effect creates a sense of memory and process, showing the paths thoughts take before connections form.

Pseudocode

SETUP:
  Create 30 particles starting from random edges
  Each particle has random size, lifetime, and connection radius
  Initialize wandering motion toward center with randomness

EACH FRAME:
  Apply soft fade to background (creates trailing effect)

  For each particle:
    Move based on velocity
    Add Perlin noise for organic wandering
    Gradually fade (decrease lifetime)
    Reset if dead or out of bounds
    Draw particle with pulsing animation

  Check all particle pairs:
    If distance < connection radius AND both particles alive:
      Store connection with strength based on distance

  For each connection:
    Draw line between connected particles
    Draw bright point at connection midpoint
    Opacity based on connection strength and particle life

Source Code

let sketch = function(p) {
    let particles = [];
    let connections = [];
    let time = 0;

    class Particle {
        constructor() {
            this.reset();
        }

        reset() {
            // Particles appear from random edges
            let edge = p.floor(p.random(4));
            if (edge === 0) { // top
                this.x = p.random(p.width);
                this.y = 0;
            } else if (edge === 1) { // right
                this.x = p.width;
                this.y = p.random(p.height);
            } else if (edge === 2) { // bottom
                this.x = p.random(p.width);
                this.y = p.height;
            } else { // left
                this.x = 0;
                this.y = p.random(p.height);
            }

            // Slow, wandering movement toward center
            let centerX = p.width / 2;
            let centerY = p.height / 2;
            let angle = p.atan2(centerY - this.y, centerX - this.x);
            this.vx = p.cos(angle) * 0.3;
            this.vy = p.sin(angle) * 0.3;

            // Add some randomness
            this.vx += p.random(-0.2, 0.2);
            this.vy += p.random(-0.2, 0.2);

            this.size = p.random(2, 5);
            this.life = 1.0;
            this.connectionRadius = p.random(60, 120);
        }

        update() {
            // Wandering motion
            this.x += this.vx;
            this.y += this.vy;

            // Add Perlin noise for organic wandering
            let noiseVal = p.noise(this.x * 0.01, this.y * 0.01, time * 0.1);
            this.vx += (noiseVal - 0.5) * 0.1;
            this.vy += (noiseVal - 0.5) * 0.1;

            // Gradually fade
            this.life -= 0.003;

            // Reset if dead or out of bounds
            if (this.life <= 0 || this.x < -50 || this.x > p.width + 50 ||
                this.y < -50 || this.y > p.height + 50) {
                this.reset();
            }
        }

        display(colors) {
            p.noStroke();
            // Particles pulse slightly
            let pulseSize = this.size * (1 + p.sin(time * 0.05 + this.x) * 0.2);
            let alpha = this.life * 150;
            p.fill(...colors.accent2, alpha);
            p.circle(this.x, this.y, pulseSize);
        }
    }

    p.setup = function() {
        p.createCanvas(400, 300);
        p.colorMode(p.RGB);

        // Create particles
        for (let i = 0; i < 30; i++) {
            particles.push(new Particle());
        }
    };

    p.draw = function() {
        const colors = getThemeColors();

        // Soft fade effect instead of clear
        p.fill(...colors.bg, 25);
        p.noStroke();
        p.rect(0, 0, p.width, p.height);

        time++;

        // Update and display particles
        for (let particle of particles) {
            particle.update();
            particle.display(colors);
        }

        // Find connections between nearby particles (insight emergence)
        connections = [];
        for (let i = 0; i < particles.length; i++) {
            for (let j = i + 1; j < particles.length; j++) {
                let d = p.dist(particles[i].x, particles[i].y,
                             particles[j].x, particles[j].y);

                // Connection forms when particles wander close
                if (d < particles[i].connectionRadius &&
                    particles[i].life > 0.3 && particles[j].life > 0.3) {
                    connections.push({
                        p1: particles[i],
                        p2: particles[j],
                        strength: 1 - (d / particles[i].connectionRadius)
                    });
                }
            }
        }

        // Draw connections (insights emerging from wandering thoughts)
        for (let conn of connections) {
            let alpha = conn.strength * conn.p1.life * conn.p2.life * 80;
            p.stroke(...colors.accent1, alpha);
            p.strokeWeight(1);
            p.line(conn.p1.x, conn.p1.y, conn.p2.x, conn.p2.y);

            // Brighten connection points
            p.noStroke();
            p.fill(...colors.accent1, alpha * 1.5);
            let midX = (conn.p1.x + conn.p2.x) / 2;
            let midY = (conn.p1.y + conn.p2.y) / 2;
            p.circle(midX, midY, 3 * conn.strength);
        }
    };
};