Where ideas percolate and thoughts brew

The Conversation Trap

About This Sketch

Particles drift toward glowing conversation wells and orbit them, growing warm and bright — but staying in place. Occasionally one breaks free and moves directly toward the resolution zone on the right. The sketch visualizes the post's central insight: conversation generates genuine social warmth and a feeling of movement, but the actual forward motion comes from deciding and acting, not from orbiting the problem with others.

Algorithm

Particles drift toward three "conversation wells" and enter orbit around them. In orbit, particles grow warm and bright — but circle in place. After orbiting long enough, a particle occasionally breaks free and moves directly rightward toward the resolution zone, leaving a brief trail. This accompanies the post "The Conversation Trap," which explores how social conversation generates genuine warmth and a sense of engagement while often substituting for the actual decisions that would resolve problems.

Pseudocode

SETUP:
  Place three conversation wells at fixed positions
  Spawn 32 orbiter particles scattered across canvas

DRAW (every frame):
  Get current theme colors
  Fade background slightly (motion trail effect)

  For each well:
    Draw pulsing warm glow layers
    Draw bright core

  Draw resolution zone (right edge, subtle glow)

  For each resolver (decided particle):
    Move rightward at constant speed
    Fade out; remove when off-canvas

  For each orbiter:
    If not orbiting:
      Attract toward nearest well
      When close enough, enter orbit
      Draw as dim dot

    If orbiting:
      Advance along circular orbit
      Grow brighter with orbit age
      Draw as warm glowing particle
      If orbit age exceeds lifetime: small chance to resolve
        → Spawn resolver at current position
        → Reset orbiter to random position

  Periodically spawn a resolver from canvas interior
  Draw labels and caption

Source Code

let sketch = function(p) {
    // Particles are drawn toward warm "conversation wells" and orbit them,
    // growing bright and energized — but staying in place.
    // Occasionally a particle breaks orbit and moves in a direct line to the right (resolution).
    // Represents: conversation generates warmth but not movement; decisions do.

    let wells = [];
    let orbiters = [];
    let resolvers = [];
    let time = 0;

    const RESOLVE_X = 388;

    function makeWell(x, y) {
        return {
            x, y,
            phase: p.random(p.TWO_PI),
            warmth: p.random(0.5, 1.0)
        };
    }

    function makeOrbiter() {
        let wx = p.random(30, 300);
        let wy = p.random(30, 270);
        return {
            x: wx + p.random(-60, 60),
            y: wy + p.random(-60, 60),
            vx: p.random(-0.6, 0.6),
            vy: p.random(-0.6, 0.6),
            targetWell: null,
            orbiting: false,
            orbitAngle: 0,
            orbitRadius: p.random(20, 38),
            orbitSpeed: p.random(0.014, 0.028) * (p.random() > 0.5 ? 1 : -1),
            orbitAge: 0,
            orbitLifetime: p.floor(p.random(300, 700)),
            size: p.random(2, 3.2),
            phase: p.random(p.TWO_PI),
            brightness: 0
        };
    }

    function makeResolver(x, y) {
        return {
            x, y,
            vy: p.random(-0.3, 0.3),
            speed: p.random(1.2, 2.2),
            alpha: 220,
            size: p.random(2, 3),
            phase: p.random(p.TWO_PI)
        };
    }

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

        wells.push(makeWell(105, 95));
        wells.push(makeWell(200, 185));
        wells.push(makeWell(295, 95));

        for (let i = 0; i < 32; i++) {
            let o = makeOrbiter();
            o.x = p.random(30, 340);
            o.y = p.random(20, 280);
            orbiters.push(o);
        }
    };

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

        p.noStroke();
        p.fill(...colors.bg, 28);
        p.rect(0, 0, 400, 300);

        time += 0.013;

        for (let w of wells) {
            let pulse = 0.8 + 0.2 * Math.sin(time * 1.8 + w.phase);
            p.noStroke();
            p.fill(...colors.accent1, 10 * pulse * w.warmth);
            p.ellipse(w.x, w.y, 90 * pulse, 90 * pulse);
            p.fill(...colors.accent1, 22 * pulse * w.warmth);
            p.ellipse(w.x, w.y, 48 * pulse, 48 * pulse);
            p.fill(...colors.accent2, 90 + 60 * pulse * w.warmth);
            p.ellipse(w.x, w.y, 10 * pulse, 10 * pulse);
        }

        let rPulse = 0.8 + 0.2 * Math.sin(time * 2.5);
        p.noStroke();
        p.fill(...colors.accent3, 8 * rPulse);
        p.ellipse(RESOLVE_X, 150, 30 * rPulse, 200 * rPulse);
        p.fill(...colors.accent3, 20 * rPulse);
        p.ellipse(RESOLVE_X, 150, 8 * rPulse, 120 * rPulse);

        for (let i = resolvers.length - 1; i >= 0; i--) {
            let r = resolvers[i];
            r.x += r.speed;
            r.y += r.vy;
            r.alpha -= 2.5;

            if (r.alpha <= 0 || r.x > 410) {
                resolvers.splice(i, 1);
                continue;
            }

            p.noStroke();
            p.fill(...colors.accent3, r.alpha * 0.3);
            p.ellipse(r.x - r.speed * 4, r.y, r.size * 1.2, r.size * 1.2);
            p.fill(...colors.accent3, r.alpha * 0.6);
            p.ellipse(r.x - r.speed * 2, r.y, r.size * 1.5, r.size * 1.5);
            p.fill(...colors.accent3, r.alpha);
            p.circle(r.x, r.y, r.size * 2.2);
        }

        for (let i = orbiters.length - 1; i >= 0; i--) {
            let o = orbiters[i];

            if (!o.orbiting) {
                let nearest = null;
                let nearestDist = Infinity;
                for (let w of wells) {
                    let d = p.dist(o.x, o.y, w.x, w.y);
                    if (d < nearestDist) { nearestDist = d; nearest = w; }
                }
                if (nearest) {
                    let dx = nearest.x - o.x;
                    let dy = nearest.y - o.y;
                    o.vx += dx * 0.004;
                    o.vy += dy * 0.004;

                    if (nearestDist < 40) {
                        o.orbiting = true;
                        o.targetWell = nearest;
                        o.orbitAngle = Math.atan2(o.y - nearest.y, o.x - nearest.x);
                        o.orbitAge = 0;
                    }
                }
                o.vx *= 0.95;
                o.vy *= 0.95;
                o.x += o.vx;
                o.y += o.vy;
                o.x = p.constrain(o.x, 8, 345);
                o.y = p.constrain(o.y, 8, 292);

                p.noStroke();
                p.fill(...colors.accent3, 45);
                p.circle(o.x, o.y, o.size * 1.5);

            } else {
                o.orbitAngle += o.orbitSpeed;
                o.orbitAge++;
                o.brightness = p.min(1, o.orbitAge / 120);

                let w = o.targetWell;
                o.x = w.x + Math.cos(o.orbitAngle) * o.orbitRadius;
                o.y = w.y + Math.sin(o.orbitAngle) * o.orbitRadius;

                let brightPulse = 0.88 + 0.12 * Math.sin(time * 3.2 + o.phase);
                p.noStroke();
                p.fill(...colors.accent1, 30 * o.brightness * brightPulse);
                p.circle(o.x, o.y, o.size * 4.5 * brightPulse);
                p.fill(...colors.accent2, 160 * o.brightness * brightPulse);
                p.circle(o.x, o.y, o.size * 1.8 * brightPulse);

                if (o.orbitAge > o.orbitLifetime && p.random() < 0.006) {
                    resolvers.push(makeResolver(o.x, o.y));
                    o.orbiting = false;
                    o.targetWell = null;
                    o.orbitAge = 0;
                    o.brightness = 0;
                    o.x = p.random(30, 280);
                    o.y = p.random(20, 280);
                    o.vx = p.random(-0.5, 0.5);
                    o.vy = p.random(-0.5, 0.5);
                }
            }
        }

        if (p.frameCount % 210 === 0) {
            resolvers.push(makeResolver(p.random(60, 220), p.random(40, 260)));
        }

        p.noStroke();
        p.fill(...colors.accent3, 70);
        p.textSize(8.5);
        p.textAlign(p.CENTER);
        p.text('conversation', 105, 148);
        p.text('wells', 200, 235);
        p.text('wells', 295, 148);
        p.textAlign(p.RIGHT);
        p.text('resolution →', 385, 150);

        p.noStroke();
        p.fill(...colors.accent3, 72);
        p.textAlign(p.CENTER);
        p.textSize(9);
        p.text('orbit long enough, then move', 200, 294);
    };
};