Where ideas percolate and thoughts brew

The Consensus Illusion

About This Sketch

Two columns of nodes — the same people, seen differently. On the left, the social surface: uniform muted tones, all pulsing together, projecting a shared rhythm. On the right, the private interior: each node in its own color, vibrating at its own frequency, diverging in ways the left column conceals.

This is pluralistic ignorance made visible. The consensus you observe in a room is partly manufactured from private dissent. Accompanies the post "The Consensus Illusion."

Algorithm

A field of nodes split into two columns. The left column shows how nodes appear publicly: all pulsing in near-synchrony with the same muted color — the visible consensus. The right column shows the same nodes as they actually are: each vibrating at its own frequency, each in its own warm hue — private divergence hidden beneath the uniform surface. This sketch accompanies the blog post "The Consensus Illusion" and visualizes pluralistic ignorance: the gap between what people show and what they hold.

Pseudocode

SETUP:
  Initialize 28 nodes, each with:
    - random position (shared between left and right columns)
    - random private frequency and phase
    - random color mix between accent1 and accent2
    - random base size

DRAW (every frame):
  Get current theme colors
  Clear background
  Compute global conformist pulse (sine wave, shared frequency)

  For each node:
    LEFT COLUMN (public behavior):
      Position at left-half x coordinate
      Pulse size using the global pulse
      Draw in uniform accent3 color with soft glow

    RIGHT COLUMN (private belief):
      Position at right-half x coordinate
      Pulse size using node's own private frequency and phase
      Draw in node's individual warm color with independent alpha

  Draw dividing line between columns
  Draw column labels ("what you observe" / "what they hold")
  Draw caption

Source Code

let sketch = function(p) {
    // Two columns of nodes, representing a social group.
    // Left column: "what you observe" — all nodes pulse in near-synchrony with the same muted color.
    // Right column: "what they hold" — same nodes pulse independently with varied warm tones.
    // The visual contrast shows how public uniformity conceals private divergence.

    const N = 28;
    let nodes = [];
    let time = 0;

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

        for (let i = 0; i < N; i++) {
            nodes.push({
                px: p.random(0.07, 0.93),
                py: p.random(0.07, 0.93),
                privatePhase: p.random(p.TWO_PI),
                privateFreq: p.random(0.5, 2.1),
                colorT: p.random(0, 1),
                sizeBase: p.random(4.5, 8.5),
            });
        }
    };

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

        let globalPulse = 0.88 + 0.12 * Math.sin(time * 1.4);

        for (let n of nodes) {
            let lx = 14 + n.px * 172;
            let ly = n.py * 300;
            let pubSize = n.sizeBase * globalPulse;

            p.noStroke();
            p.fill(...colors.accent3, 22);
            p.circle(lx, ly, pubSize * 2.4);
            p.fill(...colors.accent3, 145);
            p.circle(lx, ly, pubSize);

            let rx = 214 + n.px * 172;
            let privPulse = 0.80 + 0.20 * Math.sin(time * n.privateFreq + n.privatePhase);
            let privSize = n.sizeBase * privPulse;

            let rv = p.lerp(colors.accent1[0], colors.accent2[0], n.colorT);
            let gv = p.lerp(colors.accent1[1], colors.accent2[1], n.colorT);
            let bv = p.lerp(colors.accent1[2], colors.accent2[2], n.colorT);
            let alpha = 110 + 90 * (0.5 + 0.5 * Math.sin(time * n.privateFreq * 0.7 + n.privatePhase));

            p.fill(rv, gv, bv, 25);
            p.circle(rx, ly, privSize * 2.4);
            p.fill(rv, gv, bv, alpha);
            p.circle(rx, ly, privSize);
        }

        p.stroke(...colors.accent3, 20);
        p.strokeWeight(0.8);
        p.line(200, 5, 200, 292);

        p.noStroke();
        p.fill(...colors.accent3, 80);
        p.textAlign(p.CENTER);
        p.textSize(9);
        p.text('what you observe', 100, 14);
        p.text('what they hold', 300, 14);

        p.fill(...colors.accent3, 55);
        p.textSize(8.5);
        p.text('public conformity \u00b7 private divergence', 200, 293);
    };
};