Where ideas percolate and thoughts brew

The Ritual Economy

About This Sketch

Visualizing ritual: living practice vs. purchased product Left: Traditional ritual - messy, overlapping, organic, people actively doing things together Right: Commodified ritual - clean boxes, separated, app interfaces, passive consumption Shows the transformation from embodied practice to mediated consumption

This sketch accompanies the blog post "The Ritual Economy" and visualizes its core concepts through generative art.

Algorithm

Visualizing ritual: living practice vs. purchased product Left: Traditional ritual - messy, overlapping, organic, people actively doing things together Right: Commodified ritual - clean boxes, separated, app interfaces, passive consumption Shows the transformation from embodied practice to mediated consumption This sketch was originally created as a visual companion to the blog post "The Ritual Economy" 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 ritual: living practice vs. purchased product
    // Left: Traditional ritual - messy, overlapping, organic, people actively doing things together
    // Right: Commodified ritual - clean boxes, separated, app interfaces, passive consumption
    // Shows the transformation from embodied practice to mediated consumption

    let time = 0;
    let traditionalParticles = [];
    let commodifiedBoxes = [];

    class TraditionalParticle {
        constructor(x, y) {
            this.x = x;
            this.y = y;
            this.vx = p.random(-0.5, 0.5);
            this.vy = p.random(-0.5, 0.5);
            this.size = p.random(4, 8);
            this.life = 255;
        }

        update() {
            // Particles move organically, interact with neighbors
            for (let other of traditionalParticles) {
                if (other !== this) {
                    let d = p.dist(this.x, this.y, other.x, other.y);
                    if (d < 30 && d > 0) {
                        // Particles attract each other - symbolizing connection
                        let angle = p.atan2(other.y - this.y, other.x - this.x);
                        this.vx += p.cos(angle) * 0.02;
                        this.vy += p.sin(angle) * 0.02;
                    }
                }
            }

            this.x += this.vx;
            this.y += this.vy;

            // Stay in bounds (left half)
            if (this.x < 20) { this.x = 20; this.vx *= -0.8; }
            if (this.x > 180) { this.x = 180; this.vx *= -0.8; }
            if (this.y < 80) { this.y = 80; this.vy *= -0.8; }
            if (this.y > 260) { this.y = 260; this.vy *= -0.8; }

            // Slow down
            this.vx *= 0.98;
            this.vy *= 0.98;
        }

        display(colors) {
            p.noStroke();
            p.fill(...colors.accent1, 180);
            p.circle(this.x, this.y, this.size);

            // Draw connections to nearby particles
            for (let other of traditionalParticles) {
                if (other !== this) {
                    let d = p.dist(this.x, this.y, other.x, other.y);
                    if (d < 40) {
                        let alpha = p.map(d, 0, 40, 100, 0);
                        p.stroke(...colors.accent1, alpha);
                        p.strokeWeight(1);
                        p.line(this.x, this.y, other.x, other.y);
                    }
                }
            }
        }
    }

    class CommodifiedBox {
        constructor(x, y, label) {
            this.x = x;
            this.y = y;
            this.label = label;
            this.pulse = 0;
            this.clicked = false;
        }

        update() {
            this.pulse = (this.pulse + 0.05) % (p.TWO_PI);
            if (time % 180 === Math.floor(this.y) % 180) {
                this.clicked = true;
                setTimeout(() => this.clicked = false, 500);
            }
        }

        display(colors) {
            p.push();
            p.translate(this.x, this.y);

            // Box outline (app-like interface)
            let pulseSize = this.clicked ? 5 : p.sin(this.pulse) * 2;
            p.stroke(...colors.accent3, 150);
            p.strokeWeight(2);
            p.noFill();
            p.rect(-25 + pulseSize/2, -20 + pulseSize/2, 50 - pulseSize, 40 - pulseSize, 4);

            // Icon/label
            p.noStroke();
            p.fill(...colors.accent3, this.clicked ? 220 : 160);
            p.textAlign(p.CENTER, p.CENTER);
            p.textSize(7);
            p.text(this.label, 0, 0);

            // Checkmark when "clicked"
            if (this.clicked) {
                p.stroke(...colors.accent2, 200);
                p.strokeWeight(2);
                p.line(-10, 0, -5, 5);
                p.line(-5, 5, 10, -5);
            }

            p.pop();
        }
    }

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

        // Initialize traditional particles (representing people actively doing ritual together)
        for (let i = 0; i < 15; i++) {
            traditionalParticles.push(
                new TraditionalParticle(
                    p.random(40, 160),
                    p.random(100, 240)
                )
            );
        }

        // Initialize commodified boxes (representing apps/products)
        commodifiedBoxes.push(new CommodifiedBox(280, 100, "Meditation\nApp"));
        commodifiedBoxes.push(new CommodifiedBox(280, 160, "Meal\nKit"));
        commodifiedBoxes.push(new CommodifiedBox(280, 220, "Zoom\nCall"));
    };

    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("Ritual: Living Practice vs. Purchased Product", 200, 15);

        // Dividing line
        p.stroke(...colors.accent3, 60);
        p.strokeWeight(1);
        p.line(200, 60, 200, 280);

        // Labels
        p.textAlign(p.CENTER, p.TOP);
        p.textSize(9);
        p.noStroke();
        p.fill(...colors.accent1, 200);
        p.text("Living Practice", 100, 45);
        p.textSize(7);
        p.fill(...colors.accent3, 150);
        p.text("(messy, embodied, connected)", 100, 58);

        p.textSize(9);
        p.fill(...colors.accent3, 200);
        p.text("Purchased Product", 300, 45);
        p.textSize(7);
        p.fill(...colors.accent3, 150);
        p.text("(clean, mediated, separate)", 300, 58);

        // Left side: Traditional ritual - living practice
        // Update and draw particles
        for (let particle of traditionalParticles) {
            particle.update();
            particle.display(colors);
        }

        // Add annotation for left side
        if (time > 120 && time < 240) {
            let alpha = Math.min((time - 120) * 2, 150);
            p.noStroke();
            p.fill(...colors.accent1, alpha);
            p.textAlign(p.CENTER, p.CENTER);
            p.textSize(7);
            p.text("Overlapping\nConnected\nOrganic", 100, 270);
        }

        // Right side: Commodified ritual - separate consumption
        // Update and draw boxes
        for (let box of commodifiedBoxes) {
            box.update();
            box.display(colors);
        }

        // User icon consuming products
        if (time > 60) {
            let userAlpha = Math.min((time - 60) * 2, 180);
            p.noStroke();
            p.fill(...colors.accent2, userAlpha);
            p.circle(340, 160, 8);
            p.fill(...colors.accent3, userAlpha);
            p.textAlign(p.LEFT, p.CENTER);
            p.textSize(6);
            p.text("User", 350, 160);

            // Lines to boxes showing consumption
            if (time % 60 < 30) {
                p.stroke(...colors.accent3, userAlpha * 0.5);
                p.strokeWeight(1);
                p.line(340, 160, 305, 100);
                p.line(340, 160, 305, 220);
            }
        }

        // Add annotation for right side
        if (time > 180 && time < 300) {
            let alpha = Math.min((time - 180) * 2, 150);
            p.noStroke();
            p.fill(...colors.accent3, alpha);
            p.textAlign(p.CENTER, p.CENTER);
            p.textSize(7);
            p.text("Separated\nMediated\nOptimized", 300, 270);
        }

        // Comparative annotations
        if (time > 240) {
            let msgAlpha = Math.min((time - 240) * 1.5, 170);

            p.textAlign(p.CENTER, p.BOTTOM);
            p.textSize(9);
            p.fill(...colors.accent3, msgAlpha);
            p.text("Infrastructure ≠ Practice", 200, 260);

            p.textSize(8);
            p.fill(...colors.accent1, msgAlpha);
            p.text("Living ritual requires actual doing, not just access", 200, 278);
        }

        // Final insight
        if (time > 360) {
            let insightAlpha = Math.min((time - 360) * 2, 160);

            p.textAlign(p.CENTER, p.BOTTOM);
            p.textSize(8);
            p.fill(...colors.accent2, insightAlpha);
            p.text("You can't consume your way to meaning", 200, 295);
        }

        time++;

        // Reset animation periodically
        if (time > 600) {
            time = 0;
        }
    };
};