The Taste Gap
About This Sketch
A dynamic visualization of the "taste gap" phenomenon - the painful phase where your ability to recognize quality outpaces your ability to produce it. Watch as two curves diverge: taste develops rapidly while skill follows more slowly, creating a gap filled with the particles of frustration that define the learning process.
Algorithm
This sketch visualizes the relationship between taste (the ability to recognize quality) and skill (the ability to produce quality) over time.
Two curves represent the development paths:
- The upper curve (taste) develops more rapidly using a power curve with exponent 0.6
- The lower curve (skill) develops more slowly using a power curve with exponent 1.5
The shaded region between them represents "the gap" - the painful space where you can see quality but can't yet produce it. Particles spawn within this gap, representing the frustration and discomfort of being caught between knowing what good looks like and being able to make it.
An animated indicator moves along the timeline, showing "you are here" with a bracket measuring the current gap size. This accompanies the blog post "The Taste Gap" about the hardest phase of learning creative skills.
Pseudocode
SETUP:
Initialize 400x300 canvas
Create arrays for taste and skill curve points
DRAW (every frame):
Get current theme colors
Clear background
FOR each point along x-axis (0 to 400):
Calculate taste curve using power function (faster development)
Calculate skill curve using power function (slower development)
Store y-coordinates for both curves
FILL and draw the region between curves (the gap)
EVERY 8 frames:
Sample a random x position where gap exists
Spawn particle in the gap with random velocity
Particles represent frustration/discomfort
FOR each particle:
Update position with velocity and gravity
Decrease life (fade out)
Draw particle if still alive
Draw skill curve (lower, slower line)
Draw taste curve (upper, faster line)
Animate indicator moving along timeline
Find gap size at indicator position
Draw bracket showing current gap
Label curves and axes
Source Code
let sketch = function(p) {
let tastePoints = [];
let skillPoints = [];
let time = 0;
let gapParticles = [];
p.setup = function() {
p.createCanvas(400, 300);
p.colorMode(p.RGB);
// Initialize the curves
for (let i = 0; i < 400; i++) {
tastePoints.push({x: i, y: 0});
skillPoints.push({x: i, y: 0});
}
};
p.draw = function() {
const colors = getThemeColors();
p.background(...colors.bg);
time += 0.02;
// Title
p.noStroke();
p.fill(...colors.accent1, 180);
p.textAlign(p.CENTER);
p.textSize(12);
p.text('The Taste Gap', 200, 25);
// Calculate curves - taste develops faster than skill
for (let i = 0; i < 400; i++) {
let x = i;
// Taste curve - faster development, smoother
let tasteProgress = p.map(i, 0, 400, 0, 1);
let tasteLevel = p.pow(tasteProgress, 0.6); // Faster curve (lower exponent)
tastePoints[i].y = p.map(tasteLevel, 0, 1, 260, 80);
// Skill curve - slower development, more gradual
let skillProgress = p.map(i, 0, 400, 0, 1);
let skillLevel = p.pow(skillProgress, 1.5); // Slower curve (higher exponent)
skillPoints[i].y = p.map(skillLevel, 0, 1, 260, 80);
}
// Draw the gap region
p.noStroke();
p.fill(...colors.accent3, 40);
p.beginShape();
for (let i = 0; i < 400; i++) {
p.vertex(tastePoints[i].x, tastePoints[i].y);
}
for (let i = 399; i >= 0; i--) {
p.vertex(skillPoints[i].x, skillPoints[i].y);
}
p.endShape(p.CLOSE);
// Spawn particles in the gap
if (p.frameCount % 8 === 0) {
// Sample a point along the timeline where gap is visible
let sampleX = p.random(80, 320);
let tasteY = 0;
let skillY = 0;
// Find the Y values at this X
for (let i = 0; i < tastePoints.length; i++) {
if (Math.abs(tastePoints[i].x - sampleX) < 1) {
tasteY = tastePoints[i].y;
skillY = skillPoints[i].y;
break;
}
}
// Only spawn if there's a gap
if (tasteY < skillY) {
gapParticles.push({
x: sampleX,
y: p.random(tasteY, skillY),
vx: p.random(-0.3, 0.3),
vy: p.random(-0.4, 0.1),
life: 255,
size: p.random(2, 5)
});
}
}
// Update and draw gap particles (representing frustration/discomfort)
for (let i = gapParticles.length - 1; i >= 0; i--) {
let particle = gapParticles[i];
particle.x += particle.vx;
particle.y += particle.vy;
particle.vy += 0.02; // Slight gravity
particle.life -= 2;
if (particle.life <= 0) {
gapParticles.splice(i, 1);
} else {
p.noStroke();
p.fill(...colors.accent3, particle.life * 0.6);
p.circle(particle.x, particle.y, particle.size);
}
}
// Draw skill curve (lower, slower)
p.noFill();
p.stroke(...colors.accent2, 200);
p.strokeWeight(3);
p.beginShape();
for (let point of skillPoints) {
p.vertex(point.x, point.y);
}
p.endShape();
// Draw taste curve (higher, faster)
p.stroke(...colors.accent1, 200);
p.strokeWeight(3);
p.beginShape();
for (let point of tastePoints) {
p.vertex(point.x, point.y);
}
p.endShape();
// Draw a moving indicator showing "you are here"
let indicatorX = (p.sin(time * 0.5) * 0.35 + 0.5) * 400; // Oscillates through the timeline
indicatorX = p.constrain(indicatorX, 0, 399);
let indicatorTasteY = 0;
let indicatorSkillY = 0;
for (let i = 0; i < tastePoints.length; i++) {
if (Math.abs(tastePoints[i].x - indicatorX) < 1) {
indicatorTasteY = tastePoints[i].y;
indicatorSkillY = skillPoints[i].y;
break;
}
}
// Draw indicator line
p.stroke(...colors.accent1, 100);
p.strokeWeight(1);
p.line(indicatorX, 50, indicatorX, 270);
// Draw gap bracket
if (indicatorTasteY < indicatorSkillY) {
let gapSize = indicatorSkillY - indicatorTasteY;
let midY = (indicatorTasteY + indicatorSkillY) / 2;
// Bracket lines
p.stroke(...colors.accent3, 180);
p.strokeWeight(2);
p.line(indicatorX + 8, indicatorTasteY, indicatorX + 20, indicatorTasteY);
p.line(indicatorX + 8, indicatorSkillY, indicatorX + 20, indicatorSkillY);
p.line(indicatorX + 14, indicatorTasteY, indicatorX + 14, indicatorSkillY);
// Gap label
p.noStroke();
p.fill(...colors.accent3, 180);
p.textSize(9);
p.textAlign(p.LEFT);
p.text('gap', indicatorX + 24, midY + 3);
}
// Labels for curves
p.noStroke();
p.textSize(10);
p.textAlign(p.LEFT);
p.fill(...colors.accent1, 200);
p.text('Taste', 10, 90);
p.fill(...colors.accent2, 200);
p.text('Skill', 10, 250);
// Axis labels
p.fill(...colors.accent1, 140);
p.textSize(9);
p.textAlign(p.CENTER);
p.text('Time / Experience', 200, 285);
p.push();
p.translate(15, 150);
p.rotate(-p.HALF_PI);
p.text('Capability', 0, 0);
p.pop();
// Bottom message
p.textAlign(p.CENTER);
p.fill(...colors.accent1, 140);
p.textSize(9);
p.text('The discomfort is the mechanism', 200, 295);
};
};