Where ideas percolate and thoughts brew

The Optionality Trap

About This Sketch

A split canvas showing optionality versus commitment. On the left, a particle oscillates between three dim targets — never fully reaching any of them. On the right, a particle commits to a single destination: the target blooms as it arrives, then new connections cascade outward. The contrast captures the core argument: hedging spreads attention thin and keeps everything dim, while commitment concentrates energy and unlocks compounding.

Algorithm

Split-canvas animation contrasting two decision strategies. Left side shows a particle cycling between three dim option nodes, drawing faint connections to each but never arriving. Right side shows a particle committing to a single target: it moves with eased motion, the target grows brighter and larger as the particle approaches, and on arrival a cascade of new connections blooms outward. The animation resets every 12 seconds.

Pseudocode

SETUP:
  Place 3 option nodes on left half of canvas
  Define 4 branch vectors for committed target cascade
  Set committed target position on right half

DRAW:
  Increment time counter; reset at 12s

  Draw dividing line and side labels

  LEFT (hedging):
    Compute oscillating position cycling through options (smoothstepped lerp)
    Draw dim lines from hedger to all option nodes
    Draw dim option nodes
    Draw dim hedger particle

  RIGHT (committed):
    Compute progress (0→1 over 5 seconds, then hold)
    Apply smoothstep easing to committer position
    Draw expanding glow rings around target (scale with progress)
    Draw brightening target core
    If progress > 75%: animate cascade branches sprouting from target
    Draw trail line from committer to target
    Draw bright committer particle at current position

Source Code

let t = 0;
let options = [];
let hedgerX, hedgerY;
let targetX = 310, targetY = 140;
let startX = 240, startY = 165;
let branches = [];

p.setup = function() {
    p.createCanvas(400, 300);
    options = [
        {x: 70, y: 75},
        {x: 135, y: 215},
        {x: 48, y: 195}
    ];
    branches = [
        {dx: 38, dy: -52},
        {dx: 58, dy: 22},
        {dx: -18, dy: 58},
        {dx: 52, dy: -22}
    ];
};

p.draw = function() {
    const colors = getThemeColors();
    p.background(...colors.bg);
    t += 0.015;
    if (t > 12) t = 0;

    p.stroke(...colors.accent3, 50);
    p.strokeWeight(1);
    p.line(200, 15, 200, 280);

    p.noStroke();
    p.textSize(9);
    p.textAlign(p.CENTER);
    p.fill(...colors.accent3, 160);
    p.text("optionality", 100, 272);
    p.text("commitment", 300, 272);

    let phase = (t * 0.75) % options.length;
    let fromIdx = Math.floor(phase);
    let toIdx = (fromIdx + 1) % options.length;
    let raw = phase - fromIdx;
    let eased = raw * raw * (3 - 2 * raw);

    hedgerX = p.lerp(options[fromIdx].x, options[toIdx].x, eased);
    hedgerY = p.lerp(options[fromIdx].y, options[toIdx].y, eased);

    for (let i = 0; i < options.length; i++) {
        p.stroke(...colors.accent1, 45);
        p.strokeWeight(0.8);
        p.line(hedgerX, hedgerY, options[i].x, options[i].y);

        p.noStroke();
        p.fill(...colors.accent2, 55);
        p.ellipse(options[i].x, options[i].y, 24, 24);
        p.fill(...colors.accent2, 90);
        p.ellipse(options[i].x, options[i].y, 13, 13);
    }

    p.noStroke();
    p.fill(...colors.accent1, 95);
    p.ellipse(hedgerX, hedgerY, 11, 11);

    let progress = Math.min(t / 5.0, 1.0);
    let easeP = progress * progress * (3 - 2 * progress);

    let cx = p.lerp(startX, targetX, easeP);
    let cy = p.lerp(startY, targetY, easeP);

    p.noStroke();
    p.fill(...colors.accent2, 18 * easeP);
    p.ellipse(targetX, targetY, 95 + 30 * easeP, 95 + 30 * easeP);
    p.fill(...colors.accent2, 35 * easeP);
    p.ellipse(targetX, targetY, 55 + 20 * easeP, 55 + 20 * easeP);

    p.fill(...colors.accent2, 85 + 115 * easeP);
    p.ellipse(targetX, targetY, 20 + 14 * easeP, 20 + 14 * easeP);

    if (progress > 0.75) {
        let bAlpha = (progress - 0.75) / 0.25;
        for (let i = 0; i < branches.length; i++) {
            let delay = i * 0.2;
            let bp = Math.max(0, bAlpha - delay);
            if (bp > 0) {
                let bpE = Math.min(bp * 3, 1);
                let bx = targetX + branches[i].dx * bpE;
                let by = targetY + branches[i].dy * bpE;
                p.stroke(...colors.accent1, 110 * bpE);
                p.strokeWeight(1);
                p.line(targetX, targetY, bx, by);
                p.noStroke();
                p.fill(...colors.accent1, 170 * bpE);
                p.ellipse(bx, by, 9 * bpE, 9 * bpE);
            }
        }
    }

    p.stroke(...colors.accent1, 70 + 50 * easeP);
    p.strokeWeight(1);
    p.line(cx, cy, targetX, targetY);

    p.noStroke();
    p.fill(...colors.accent1, 225);
    p.ellipse(cx, cy, 12, 12);
};