The Learning Fetish
About This Sketch
Visualizing the learning trap vs. doing Left side: Growing pile of books/courses (learning), never reaching the "build" goal Right side: Small foundation of knowledge, but actively building upward (doing)
This sketch accompanies the blog post "The Learning Fetish" and visualizes its core concepts through generative art.
Algorithm
Visualizing the learning trap vs. doing Left side: Growing pile of books/courses (learning), never reaching the "build" goal Right side: Small foundation of knowledge, but actively building upward (doing)
This sketch was originally created as a visual companion to the blog post "The Learning Fetish" 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 learning trap vs. doing
// Left side: Growing pile of books/courses (learning), never reaching the "build" goal
// Right side: Small foundation of knowledge, but actively building upward (doing)
let time = 0;
let learningPile = [];
let buildingBlocks = [];
class LearnItem {
constructor(x, y, type, delay) {
this.x = x;
this.y = y;
this.width = 35;
this.height = 6;
this.type = type; // 'book', 'course', 'tutorial'
this.delay = delay;
this.alpha = 0;
this.settled = false;
this.finalY = y;
this.startY = y - 100;
}
update() {
if (time > this.delay) {
if (!this.settled) {
this.y = p.lerp(this.startY, this.finalY, p.min((time - this.delay) / 20, 1));
this.alpha = p.min((time - this.delay) * 10, 180);
if (this.y >= this.finalY - 1) {
this.settled = true;
}
}
}
}
display(colors) {
if (this.alpha === 0) return;
p.noStroke();
// Different visual for each type
if (this.type === 'book') {
p.fill(...colors.accent3, this.alpha);
p.rect(this.x, this.y, this.width, this.height, 1);
p.fill(...colors.accent3, this.alpha * 0.6);
p.rect(this.x + 2, this.y, 2, this.height);
} else if (this.type === 'course') {
p.fill(...colors.accent2, this.alpha * 0.8);
p.rect(this.x, this.y, this.width, this.height, 1);
// Progress bar showing incomplete
p.fill(...colors.accent1, this.alpha * 0.5);
p.rect(this.x + 2, this.y + 1, this.width * 0.15, this.height - 2);
} else if (this.type === 'tutorial') {
p.fill(...colors.accent1, this.alpha * 0.7);
p.rect(this.x, this.y, this.width, this.height, 1);
// Play icon
p.fill(...colors.bg, this.alpha * 0.5);
p.triangle(this.x + 5, this.y + 1, this.x + 5, this.y + 5, this.x + 9, this.y + 3);
}
}
}
class BuildBlock {
constructor(x, y, size, delay, isFoundation) {
this.x = x;
this.y = y;
this.size = size;
this.delay = delay;
this.alpha = 0;
this.isFoundation = isFoundation;
this.settled = false;
this.finalY = y;
this.startY = y + 50;
}
update() {
if (time > this.delay) {
if (!this.settled) {
this.y = p.lerp(this.startY, this.finalY, p.min((time - this.delay) / 15, 1));
this.alpha = p.min((time - this.delay) * 15, 220);
if (Math.abs(this.y - this.finalY) < 1) {
this.settled = true;
this.y = this.finalY;
}
}
}
}
display(colors) {
if (this.alpha === 0) return;
p.noStroke();
if (this.isFoundation) {
// Foundation is small but solid
p.fill(...colors.accent3, this.alpha);
p.rect(this.x - this.size/2, this.y - this.size/2, this.size, this.size, 2);
} else {
// Building blocks are vibrant and stacked
p.fill(...colors.accent1, this.alpha);
p.rect(this.x - this.size/2, this.y - this.size/2, this.size, this.size, 2);
// Add highlight to show these are active/used
p.fill(...colors.accent2, this.alpha * 0.3);
p.rect(this.x - this.size/2 + 2, this.y - this.size/2 + 2, this.size - 4, this.size/3);
}
}
}
p.setup = function() {
p.createCanvas(400, 300);
p.colorMode(p.RGB);
// Left side: Accumulating learning items that never lead anywhere
let types = ['book', 'course', 'tutorial'];
for (let i = 0; i < 20; i++) {
let row = Math.floor(i / 3);
let col = i % 3;
let x = 40 + col * 15;
let y = 240 - row * 8;
let type = types[Math.floor(Math.random() * types.length)];
learningPile.push(new LearnItem(x, y, type, 20 + i * 8));
}
// Right side: Small foundation of learning, then building upward
// Foundation (small amount of learning)
buildingBlocks.push(new BuildBlock(300, 235, 25, 20, true));
buildingBlocks.push(new BuildBlock(330, 235, 25, 25, true));
// Building upward (actual projects/doing)
let buildHeight = [
{x: 315, y: 210, delay: 60},
{x: 285, y: 185, delay: 80},
{x: 315, y: 185, delay: 85},
{x: 345, y: 185, delay: 90},
{x: 300, y: 160, delay: 110},
{x: 330, y: 160, delay: 115},
{x: 315, y: 135, delay: 140},
{x: 300, y: 110, delay: 165},
{x: 330, y: 110, delay: 170},
{x: 315, y: 85, delay: 195},
{x: 315, y: 60, delay: 220}
];
for (let block of buildHeight) {
buildingBlocks.push(new BuildBlock(block.x, block.y, 28, block.delay, false));
}
};
p.draw = function() {
const colors = getThemeColors();
p.background(...colors.bg);
// Dividing line
p.stroke(...colors.accent3, 60);
p.strokeWeight(1);
p.line(200, 0, 200, 300);
// Labels
p.noStroke();
p.fill(...colors.accent3, 200);
p.textAlign(p.CENTER, p.TOP);
p.textSize(11);
p.text("Always Learning", 100, 15);
p.text("Learning + Doing", 300, 15);
// Descriptions
p.textSize(8);
p.fill(...colors.accent3, 150);
p.text("Accumulating knowledge", 100, 32);
p.text("Never shipping", 100, 43);
p.text("Small foundation", 300, 32);
p.text("Building upward", 300, 43);
// Goal line on left side (never reached)
if (time > 100) {
p.stroke(...colors.accent2, p.min((time - 100) * 2, 100));
p.strokeWeight(1);
p.drawingContext.setLineDash([5, 5]);
p.line(20, 100, 180, 100);
p.drawingContext.setLineDash([]);
p.noStroke();
p.fill(...colors.accent2, p.min((time - 100) * 2, 100));
p.textSize(8);
p.textAlign(p.LEFT);
p.text("Goal: Start building", 25, 88);
}
// Arrow showing never reaching goal
if (time > 180) {
let arrowAlpha = p.min((time - 180) * 3, 120);
p.stroke(...colors.accent2, arrowAlpha);
p.strokeWeight(1);
p.noFill();
// Curved arrow from pile to unreached goal
p.beginShape();
p.vertex(70, 220);
p.bezierVertex(90, 200, 110, 170, 120, 120);
p.endShape();
// Arrow head
p.line(120, 120, 115, 125);
p.line(120, 120, 125, 123);
}
// Base line
p.noStroke();
p.fill(...colors.accent3, 80);
p.rect(0, 250, 200, 50);
p.rect(200, 250, 200, 50);
// Ground line
p.stroke(...colors.accent3, 120);
p.strokeWeight(2);
p.line(0, 250, 400, 250);
// Update and display learning pile
for (let item of learningPile) {
item.update();
item.display(colors);
}
// Update and display building blocks
for (let block of buildingBlocks) {
block.update();
block.display(colors);
}
// Labels at bottom
p.textAlign(p.CENTER, p.BOTTOM);
p.textSize(9);
p.noStroke();
if (time > 50) {
p.fill(...colors.accent3, p.min((time - 50) * 3, 160));
p.text("20 courses", 70, 270);
p.text("0 projects", 70, 283);
}
if (time > 240) {
p.fill(...colors.accent1, p.min((time - 240) * 3, 160));
p.text("2 courses", 315, 270);
p.text("Many projects", 315, 283);
}
time++;
// Loop animation
if (time > 350) {
time = 0;
learningPile = [];
buildingBlocks = [];
p.setup();
}
};
};