Where ideas percolate and thoughts brew

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