Where ideas percolate and thoughts brew

The Empathy Trap

About This Sketch

Seventy dots, each representing a person. A spotlight drifts between them one at a time, illuminating whoever it lands on while leaving the rest in near-darkness. Attention particles flow from all directions toward the single lit figure. The statistics in the corner stay stubbornly fixed: one in focus, sixty-nine in shadow. The spotlight will move again soon — but never to more than one.

Algorithm

Seventy people-dots are scattered randomly across the canvas. A spotlight drifts between them, randomly selecting a new target every 150 frames and smoothly lerping toward it. The person nearest the spotlight glows brightly while all others remain near-invisible. Attention particles spawn from the canvas edges and stream toward the spotlight position, representing how resources and concern flow toward whoever is currently visible. A readout tracks how many are in focus versus in shadow at any moment.

Pseudocode

SETUP:
  Place 70 person-dots at random positions across canvas
  Initialize spotlight at first random target

DRAW each frame:
  Increment spotlight timer
  If timer > 150:
    Choose new random target (different from current)
    Reset timer
  Lerp spotlight position toward current target

  For each person:
    Compute distance to spotlight
    Set target brightness: high if near spotlight, near-zero otherwise
    Lerp current brightness toward target

  Every 8 frames: spawn attention particle from random canvas edge
  For each attention particle:
    Move toward spotlight position
    Fade alpha over time
    Remove if reached spotlight or fully faded

  Draw ambient glow rings under spotlight
  Draw all person-dots sized and colored by brightness
  Draw attention particles as tiny dots
  Display in-focus / in-shadow count
  Draw caption

Source Code

let sketch = function(p) {
    const W = 400, H = 300;
    const N = 70;

    let people = [];
    let spotlight = { x: 200, y: 150, targetIdx: 0, timer: 0 };
    let attention = [];
    let attentionTimer = 0;

    p.setup = function() {
        p.createCanvas(W, H);
        p.randomSeed(42);
        for (let i = 0; i < N; i++) {
            people.push({
                x: 15 + p.random(370),
                y: 15 + p.random(260),
                brightness: 0.05,
                size: 2 + p.random(3)
            });
        }
        spotlight.targetIdx = Math.floor(p.random(N));
        spotlight.x = people[spotlight.targetIdx].x;
        spotlight.y = people[spotlight.targetIdx].y;
    };

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

        spotlight.timer++;

        if (spotlight.timer > 150) {
            spotlight.timer = 0;
            let newTarget;
            do { newTarget = Math.floor(p.random(N)); } while (newTarget === spotlight.targetIdx);
            spotlight.targetIdx = newTarget;
        }

        let tgt = people[spotlight.targetIdx];
        spotlight.x = p.lerp(spotlight.x, tgt.x, 0.04);
        spotlight.y = p.lerp(spotlight.y, tgt.y, 0.04);

        for (let i = 0; i < N; i++) {
            let d = p.dist(people[i].x, people[i].y, spotlight.x, spotlight.y);
            let targetB = d < 25 ? p.map(d, 0, 25, 1.0, 0.15) : 0.04;
            people[i].brightness = p.lerp(people[i].brightness, targetB, 0.06);
        }

        attentionTimer++;
        if (attentionTimer > 8) {
            attentionTimer = 0;
            let edge = Math.floor(p.random(4));
            let ax, ay;
            if (edge === 0) { ax = p.random(W); ay = 0; }
            else if (edge === 1) { ax = p.random(W); ay = H; }
            else if (edge === 2) { ax = 0; ay = p.random(H); }
            else { ax = W; ay = p.random(H); }
            attention.push({ x: ax, y: ay, alpha: 130, speed: 1.5 + p.random(1.5) });
        }

        for (let r = 52; r > 0; r -= 6) {
            let a = p.map(r, 0, 52, 32, 0);
            p.noStroke();
            p.fill(...colors.accent1, a);
            p.ellipse(spotlight.x, spotlight.y, r * 2);
        }

        p.noStroke();
        for (let i = 0; i < N; i++) {
            let b = people[i].brightness;
            let sz = people[i].size;
            if (b > 0.5) {
                p.fill(...colors.accent1, b * 55);
                p.ellipse(people[i].x, people[i].y, sz * 5);
            }
            p.fill(colors.accent2[0], colors.accent2[1], colors.accent2[2], b * 220 + 10);
            p.ellipse(people[i].x, people[i].y, sz * (1 + b * 0.8));
        }

        p.noStroke();
        for (let i = attention.length - 1; i >= 0; i--) {
            let a = attention[i];
            let dx = spotlight.x - a.x;
            let dy = spotlight.y - a.y;
            let d = Math.sqrt(dx * dx + dy * dy);
            if (d < 5) { attention.splice(i, 1); continue; }
            a.x += (dx / d) * a.speed;
            a.y += (dy / d) * a.speed;
            a.alpha -= 2;
            if (a.alpha <= 0) { attention.splice(i, 1); continue; }
            p.fill(...colors.accent1, a.alpha * 0.55);
            p.ellipse(a.x, a.y, 2);
        }

        let litCount = 0;
        for (let i = 0; i < N; i++) { if (people[i].brightness > 0.4) litCount++; }
        p.noStroke();
        p.textAlign(p.LEFT);
        p.textSize(8);
        p.fill(...colors.accent2, 140);
        p.text('in focus: ' + litCount, 10, 14);
        p.fill(...colors.accent3, 90);
        p.text('in shadow: ' + (N - litCount), 10, 26);

        p.noStroke();
        p.fill(...colors.accent3, 55);
        p.textAlign(p.CENTER);
        p.textSize(8);
        p.text('one face visible \u00b7 thousands in the dark', W / 2, H - 5);
    };
};