The Generosity Performance
About This Sketch
This sketch explores the theme of the blog post "The Generosity Performance" through the temporal dynamics of giving. Watch how the flashy, performative acts (with their radiating attention rays) appear large and impressive but fade quickly, leaving little lasting impact. Meanwhile, smaller acts of real generosity persist, pulse quietly, and create expanding ripples of sustained impact.
The 70/30 ratio of performative to real acts reflects the reality that visibility creates incentives—we see far more performed generosity than quiet, sustained help. The visualization makes visible what's usually invisible: the temporal dimension of impact, and the inverse relationship between visibility and lasting effect.
Algorithm
This sketch visualizes the contrast between performed generosity and real generosity through two distinct visual patterns.
Performative acts are represented as large, hollow circles with radiating "attention rays" (symbolizing cameras, social media, and public visibility). They flash brightly when they appear, expand their rays of attention quickly, then fade rapidly. Their hollow centers represent low actual impact despite high visibility.
Real generosity appears as small, solid circles that pulse gently and persist for much longer. They create subtle ripples of lasting impact that expand slowly over time. No attention rays, no flash—just sustained, solid presence. Their filled centers represent high actual impact despite low visibility.
The sketch generates acts randomly (weighted 70% performative to match reality), letting viewers watch the temporal dynamics: performative acts burn bright and fade fast, while real generosity glows steadily and leaves lasting ripples.
Pseudocode
SETUP:
Initialize canvas (400x300)
Create empty array for generous acts
DRAW (every frame):
Get current theme colors
Draw semi-transparent background (creates trails)
Randomly spawn new acts (70% performative, 30% real):
IF performative:
Low actual help (10-30%)
High visibility (70-100%)
Large size (8-14px)
Short lifespan (30-60 frames)
6-12 attention rays
ELSE (real generosity):
High actual help (70-100%)
Low visibility (10-30%)
Small size (3-6px)
Long lifespan (120-240 frames)
No rays
For each act:
Update age and appearance:
IF performative:
Expand attention rays quickly (first 20 frames)
Then retract rays
Fade out quickly after midpoint
ELSE:
Gentle pulse animation
Create expanding ripples
Fade out slowly after 70% of lifespan
Display based on type:
IF performative:
Draw rotating attention rays
Draw hollow circle (appearance without substance)
Draw tiny center (low impact)
ELSE:
Draw solid circle (substance)
Draw warm inner glow (genuine help)
Draw expanding ripples (lasting impact)
Remove if lifespan exceeded
Draw legend explaining visual encoding
Source Code
let sketch = function(p) {
let acts = [];
let maxActs = 60;
class GenerousAct {
constructor(x, y, isPerformative) {
this.x = x;
this.y = y;
this.isPerformative = isPerformative;
// Performative acts
if (isPerformative) {
this.actualHelp = p.random(0.1, 0.3); // Low actual impact
this.visibility = p.random(0.7, 1.0); // High visibility
this.size = p.random(8, 14); // Large, showy
this.lifespan = p.random(30, 60); // Short-lived impact
}
// Real generosity
else {
this.actualHelp = p.random(0.7, 1.0); // High actual impact
this.visibility = p.random(0.1, 0.3); // Low visibility
this.size = p.random(3, 6); // Small, modest
this.lifespan = p.random(120, 240); // Long-lasting impact
}
this.age = 0;
this.alpha = 255;
this.pulsePhase = p.random(p.TWO_PI);
// Rays for performative acts (cameras, attention)
this.numRays = isPerformative ? Math.floor(p.random(6, 12)) : 0;
this.rayLength = 0;
this.maxRayLength = this.size * 2;
}
update() {
this.age++;
// Performative acts: Quick flash then fade
if (this.isPerformative) {
// Rapid expansion of "attention rays"
if (this.age < 20) {
this.rayLength = p.map(this.age, 0, 20, 0, this.maxRayLength);
} else {
this.rayLength = p.max(0, this.rayLength - 0.5);
}
// Fade out quickly
if (this.age > this.lifespan * 0.5) {
this.alpha = p.map(this.age, this.lifespan * 0.5, this.lifespan, 255, 0);
}
}
// Real generosity: Slow fade, lasting impact
else {
// Gentle pulse
this.pulsePhase += 0.02;
// Fade out slowly, much later
if (this.age > this.lifespan * 0.7) {
this.alpha = p.map(this.age, this.lifespan * 0.7, this.lifespan, 255, 0);
}
}
return this.age < this.lifespan;
}
display(colors) {
let currentAlpha = this.alpha;
if (this.isPerformative) {
// Draw attention rays (cameras, social media attention)
if (this.rayLength > 0) {
p.stroke(...colors.accent3, currentAlpha * 0.6);
p.strokeWeight(1);
for (let i = 0; i < this.numRays; i++) {
let angle = (p.TWO_PI / this.numRays) * i + this.age * 0.05;
let x2 = this.x + p.cos(angle) * this.rayLength;
let y2 = this.y + p.sin(angle) * this.rayLength;
p.line(this.x, this.y, x2, y2);
}
}
// Hollow circle (appearance without substance)
p.noFill();
p.stroke(...colors.accent3, currentAlpha);
p.strokeWeight(2);
p.circle(this.x, this.y, this.size);
// Tiny center (low actual impact)
let impactSize = this.size * this.actualHelp;
p.fill(...colors.accent2, currentAlpha * 0.5);
p.noStroke();
p.circle(this.x, this.y, impactSize);
} else {
// Real generosity: Solid, understated, lasting
let pulse = p.sin(this.pulsePhase) * 0.2 + 1;
let impactSize = this.size * this.actualHelp * pulse;
// Solid circle (substance over appearance)
p.fill(...colors.accent1, currentAlpha * 0.8);
p.noStroke();
p.circle(this.x, this.y, impactSize);
// Warm inner glow (real help)
p.fill(...colors.accent2, currentAlpha * 0.5);
p.circle(this.x, this.y, impactSize * 0.6);
// Subtle ripple of lasting impact
if (this.age > 20) {
let rippleSize = p.map(this.age, 20, this.lifespan, this.size, this.size * 3);
let rippleAlpha = p.map(this.age, 20, this.lifespan, currentAlpha * 0.3, 0);
p.noFill();
p.stroke(...colors.accent1, rippleAlpha);
p.strokeWeight(1);
p.circle(this.x, this.y, rippleSize);
}
}
}
}
p.setup = function() {
p.createCanvas(400, 300);
};
p.draw = function() {
const colors = getThemeColors();
p.background(...colors.bg, 40); // Trails to show lasting impact
// Title
p.noStroke();
p.fill(...colors.accent3);
p.textAlign(p.CENTER);
p.textSize(12);
p.text('The Generosity Performance', 200, 20);
// Create new acts randomly
// 70% performative (reflects reality)
if (p.random() < 0.03 && acts.length < maxActs) {
let isPerformative = p.random() < 0.7;
let x = p.random(40, 360);
let y = p.random(50, 260);
acts.push(new GenerousAct(x, y, isPerformative));
}
// Update and display acts
for (let i = acts.length - 1; i >= 0; i--) {
if (!acts[i].update()) {
acts.splice(i, 1);
} else {
acts[i].display(colors);
}
}
// Legend
p.textAlign(p.LEFT);
p.textSize(8);
// Performative generosity
p.noFill();
p.stroke(...colors.accent3, 200);
p.strokeWeight(1.5);
p.circle(15, 45, 10);
// Rays
for (let i = 0; i < 6; i++) {
let angle = (p.TWO_PI / 6) * i;
p.line(15, 45, 15 + p.cos(angle) * 8, 45 + p.sin(angle) * 8);
}
p.fill(...colors.accent2, 100);
p.noStroke();
p.circle(15, 45, 3);
p.fill(...colors.accent3);
p.text('Performed: Large, flashy, short-lived', 25, 48);
// Real generosity
p.fill(...colors.accent1, 200);
p.circle(15, 65, 5);
p.fill(...colors.accent2, 150);
p.circle(15, 65, 3);
p.fill(...colors.accent3);
p.text('Real: Small, solid, lasting ripples', 25, 68);
// Bottom note
p.textSize(7);
p.fill(...colors.accent3, 150);
p.textAlign(p.CENTER);
p.text('Rays = cameras, attention, visibility. Notice what lasts.', 200, 288);
};
};