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