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