The Gratitude Performance
About This Sketch
Visualizing the gratitude performance Hearts (gratitude symbols) appearing on a schedule vs. spontaneously Left: Scheduled/forced gratitude - hearts appear mechanically on timer Right: Spontaneous gratitude - hearts appear organically, varied in size and timing Shows the difference between performance and genuine feeling
This sketch accompanies the blog post "The Gratitude Performance" and visualizes its core concepts through generative art.
Algorithm
Visualizing the gratitude performance Hearts (gratitude symbols) appearing on a schedule vs. spontaneously Left: Scheduled/forced gratitude - hearts appear mechanically on timer Right: Spontaneous gratitude - hearts appear organically, varied in size and timing Shows the difference between performance and genuine feeling
This sketch was originally created as a visual companion to the blog post "The Gratitude Performance" and explores its themes through generative art.
Pseudocode
SETUP:
Initialize canvas (400x300)
Set up drawing parameters
DRAW (every frame):
Get current theme colors
Clear background
Draw generative visualization
Update animation state
Source Code
let sketch = function(p) {
// Visualizing the gratitude performance
// Hearts (gratitude symbols) appearing on a schedule vs. spontaneously
// Left: Scheduled/forced gratitude - hearts appear mechanically on timer
// Right: Spontaneous gratitude - hearts appear organically, varied in size and timing
// Shows the difference between performance and genuine feeling
let time = 0;
let scheduledHearts = [];
let spontaneousHearts = [];
let lastScheduledTime = 0;
class Heart {
constructor(x, y, size, spontaneous = false) {
this.x = x;
this.y = y;
this.size = size;
this.alpha = 255;
this.spontaneous = spontaneous;
this.fadeSpeed = spontaneous ? 1 : 2;
this.wobble = spontaneous ? p.random(-0.3, 0.3) : 0;
}
update() {
this.y -= spontaneous ? 1.2 : 0.8;
this.alpha -= this.fadeSpeed;
this.x += p.sin(this.y * 0.1) * this.wobble;
}
display(colors) {
if (this.alpha <= 0) return;
p.push();
p.translate(this.x, this.y);
p.noStroke();
let color = this.spontaneous ? colors.accent1 : colors.accent3;
p.fill(...color, this.alpha);
// Draw heart shape
p.beginShape();
for (let a = 0; a < p.TWO_PI; a += 0.1) {
let r = this.size * (
13 * p.cos(a) -
5 * p.cos(2 * a) -
2 * p.cos(3 * a) -
p.cos(4 * a)
) / 16;
let x = r * p.sin(a);
let y = -r * p.cos(a);
p.vertex(x, y);
}
p.endShape(p.CLOSE);
p.pop();
}
isDead() {
return this.alpha <= 0 || this.y < -20;
}
}
p.setup = function() {
p.createCanvas(400, 300);
p.colorMode(p.RGB);
};
p.draw = function() {
const colors = getThemeColors();
p.background(...colors.bg);
// Title
p.noStroke();
p.fill(...colors.accent3, 180);
p.textAlign(p.CENTER, p.TOP);
p.textSize(11);
p.text("The Gratitude Performance", 200, 15);
// Dividing line
p.stroke(...colors.accent3, 60);
p.strokeWeight(1);
p.line(200, 50, 200, 280);
// Labels
p.textAlign(p.CENTER, p.TOP);
p.textSize(9);
p.noStroke();
p.fill(...colors.accent3, 160);
p.text("Scheduled", 100, 35);
p.text("Spontaneous", 300, 35);
p.textSize(7);
p.fill(...colors.accent3, 120);
p.text("(forced on timer)", 100, 48);
p.text("(genuine feeling)", 300, 48);
// Left side: Scheduled/forced gratitude
// Hearts appear mechanically every N frames
if (time - lastScheduledTime > 60) {
scheduledHearts.push(new Heart(100, 260, 3, false));
lastScheduledTime = time;
}
// Draw checkbox/reminder icon
if (time % 120 < 60) {
p.stroke(...colors.accent3, 150);
p.strokeWeight(2);
p.noFill();
p.rect(85, 240, 15, 15, 2);
// Checkmark appears
if ((time - lastScheduledTime) < 10) {
p.stroke(...colors.accent2);
p.strokeWeight(2);
p.line(88, 247, 93, 252);
p.line(93, 252, 98, 242);
}
// Text label
p.noStroke();
p.fill(...colors.accent3, 130);
p.textAlign(p.LEFT, p.CENTER);
p.textSize(6);
p.text("Daily reminder", 105, 247);
}
// Update and draw scheduled hearts
for (let i = scheduledHearts.length - 1; i >= 0; i--) {
scheduledHearts[i].update();
scheduledHearts[i].display(colors);
if (scheduledHearts[i].isDead()) {
scheduledHearts.splice(i, 1);
}
}
// Annotation for scheduled side
if (time > 120 && time < 180) {
p.noStroke();
p.fill(...colors.accent3, (time - 120) * 2);
p.textAlign(p.CENTER, p.CENTER);
p.textSize(7);
p.text("Same size\nSame timing\nBox to check", 100, 140);
}
// Right side: Spontaneous gratitude
// Hearts appear randomly, varied sizes, natural timing
if (p.random() < 0.03) {
let size = p.random(2, 5);
spontaneousHearts.push(new Heart(300 + p.random(-20, 20), 260, size, true));
}
// Update and draw spontaneous hearts
for (let i = spontaneousHearts.length - 1; i >= 0; i--) {
spontaneousHearts[i].update();
spontaneousHearts[i].display(colors);
if (spontaneousHearts[i].isDead()) {
spontaneousHearts.splice(i, 1);
}
}
// Draw some sparkles for spontaneous side
if (p.random() < 0.1) {
let sparkleX = 300 + p.random(-30, 30);
let sparkleY = p.random(80, 240);
p.stroke(...colors.accent1, 100);
p.strokeWeight(1);
let size = p.random(2, 4);
p.line(sparkleX - size, sparkleY, sparkleX + size, sparkleY);
p.line(sparkleX, sparkleY - size, sparkleX, sparkleY + size);
}
// Annotation for spontaneous side
if (time > 180 && time < 240) {
p.noStroke();
p.fill(...colors.accent1, (time - 180) * 2);
p.textAlign(p.CENTER, p.CENTER);
p.textSize(7);
p.text("Varied size\nNatural timing\nActual feeling", 300, 140);
}
// Bottom comparison
if (time > 240) {
let msgAlpha = Math.min((time - 240) * 1.5, 160);
p.textAlign(p.CENTER, p.BOTTOM);
p.textSize(9);
p.fill(...colors.accent3, msgAlpha);
p.text("One is compliance. One is genuine.", 200, 272);
p.textSize(8);
p.fill(...colors.accent2, msgAlpha);
p.text("You can't schedule authentic appreciation", 200, 290);
}
// Show the mechanical nature of scheduled gratitude
if (time > 300 && scheduledHearts.length > 2) {
// Draw lines showing uniform spacing
p.stroke(...colors.accent3, 80);
p.strokeWeight(1);
p.drawingContext.setLineDash([3, 3]);
for (let i = 0; i < scheduledHearts.length - 1; i++) {
p.line(
scheduledHearts[i].x, scheduledHearts[i].y,
scheduledHearts[i + 1].x, scheduledHearts[i + 1].y
);
}
p.drawingContext.setLineDash([]);
}
time++;
// Clean up old hearts to prevent memory issues
if (scheduledHearts.length > 20) {
scheduledHearts.shift();
}
if (spontaneousHearts.length > 30) {
spontaneousHearts.shift();
}
// Loop after a while
if (time > 600) {
time = 0;
lastScheduledTime = 0;
}
};
};