Where ideas percolate and thoughts brew

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;
        }
    };
};