AI Excuse Generator - Why I Can't Work Today

This sketch generates a stream of increasingly ridiculous work excuses in floating speech-bubble shapes every time you click. Bubbles drift upward and fade out as they rise, and the pool of excuses escalates from mild to absurd the more you click, tracked by an on-screen counter.

🧪 Try This!

Experiment with the code by making these changes:

  1. Make bubbles fade out much faster — Increasing how much alpha drops each frame makes every excuse bubble disappear almost instantly after appearing.
  2. Give the canvas a dark mode background — Changing the background() color instantly shifts the whole sketch's mood from soft pastel to dramatic dark.
  3. Escalate to absurd excuses immediately — Lowering the click thresholds makes the hardest, most ridiculous excuses appear after just a couple of clicks instead of ten.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch creates a playful 'excuse generator' where every mouse click spawns a rounded speech-bubble containing a randomly chosen, increasingly absurd excuse for skipping work. The bubbles float upward and dissolve using an alpha fade, and the humor escalates in tiers - mild excuses for the first few clicks, then mid-tier, then full absurdity - all powered by simple array selection logic. Key p5.js techniques on display include managing an array of live objects each frame, using push()/pop() with translate() to position independent shapes, fading elements out with alpha transparency, and picking random items from arrays with random().

The code is organized around a global bubbles array that holds every active excuse bubble as a plain JavaScript object with position, text, opacity, and speed. setup() prepares the canvas and font once, draw() loops through bubbles every frame to animate, fade, and remove them, and mousePressed() is the only place new bubbles get created. By studying it you'll learn a common pattern in creative coding: spawn-update-remove object lifecycles driven by a single event handler and a continuously running draw loop.

⚙️ How It Works

  1. When the sketch loads, setup() creates a full-window canvas and sets a sans-serif font at size 16 for all future text.
  2. Every frame, draw() paints a solid lavender background, then loops backwards through the bubbles array, moving each bubble upward and lowering its opacity slightly.
  3. If a bubble's opacity drops to zero or below, it's deleted from the array with splice() so it stops being drawn and doesn't waste memory.
  4. Each remaining bubble is drawn as a rounded white rectangle with dark purple text inside, positioned using translate() so the shape math stays simple regardless of the bubble's location.
  5. When the mouse is pressed, mousePressed() picks an excuse pool based on how many times you've already clicked (easy, then mid, then hard), grabs a random excuse from that pool, and pushes a brand new bubble object into the bubbles array starting just below the screen.
  6. The click counter increments every press, which both escalates the excuse difficulty over time and is displayed live in the top-left corner of the canvas.

🎓 Concepts You'll Learn

Array of live objects (spawn/update/remove pattern)Alpha transparency and fade-out animationpush()/translate()/pop() for local coordinate systemsTernary conditionals for tiered difficultyrandom() for picking array elementsmousePressed event handlingResponsive canvas with windowResized()

📝 Code Breakdown

setup()

setup() runs exactly once when the sketch starts. It's the right place to configure the canvas and any global drawing defaults like font and text size before draw() begins looping.

function setup() {
  createCanvas(windowWidth, windowHeight);
  textFont("sans-serif"); textSize(16);
}
Line-by-line explanation (3 lines)
createCanvas(windowWidth, windowHeight);
Creates a canvas that fills the entire browser window, so the sketch is always full-screen.
textFont("sans-serif");
Sets the default font used by every text() call afterward.
textSize(16);
Sets the default font size in pixels for all text drawn in the sketch.

draw()

draw() runs about 60 times per second and is where all animation happens. Looping backwards through an array while removing items is a key pattern to know any time you manage a changing list of objects, like particles, bullets, or - in this case - excuse bubbles.

🔬 This is what makes bubbles disappear. What happens if you change b.a -= 2 to b.a -= 0.3 so bubbles linger onscreen much longer before vanishing?

    b.y -= b.vy; b.a -= 2;
    if (b.a <= 0) { bubbles.splice(i, 1); continue; }

🔬 The last number in rect() is the corner radius. What happens if you change 20 to 0 for sharp square bubbles, or 40 for pill-shaped ones?

    let pad = 12, w = textWidth(b.txt) + pad * 2, h = 50;
    rect(0, 0, w, h, 20);
function draw() {
  background(245, 234, 255);
  for (let i = bubbles.length - 1; i >= 0; i--) {
    let b = bubbles[i];
    b.y -= b.vy; b.a -= 2;
    if (b.a <= 0) { bubbles.splice(i, 1); continue; }
    push();
    translate(b.x, b.y); rectMode(CENTER); noStroke();
    fill(255, 255, 255, b.a);
    let pad = 12, w = textWidth(b.txt) + pad * 2, h = 50;
    rect(0, 0, w, h, 20);
    fill(70, 60, 90, b.a); textAlign(CENTER, CENTER);
    text(b.txt, 0, 0, w - 10, h - 10);
    pop();
  }
  fill(70); textAlign(LEFT, TOP);
  text("Excuses generated: " + count, 10, 10);
}
Line-by-line explanation (15 lines)

🔧 Subcomponents:

for-loop Bubble Update And Draw Loop for (let i = bubbles.length - 1; i >= 0; i--) {

Iterates through the bubbles array backwards so bubbles can be safely removed mid-loop without skipping the next item.

conditional Remove Faded Bubble if (b.a <= 0) { bubbles.splice(i, 1); continue; }

Deletes a bubble from the array once it has fully faded out, so invisible bubbles don't keep being processed.

background(245, 234, 255);
Repaints the whole canvas with a soft lavender color every frame, erasing the previous frame so bubbles appear to move smoothly instead of leaving trails.
for (let i = bubbles.length - 1; i >= 0; i--) {
Loops through every active bubble from last to first - going backwards avoids skipping elements when one gets removed with splice().
let b = bubbles[i];
Grabs the current bubble object so its properties (x, y, txt, a, vy) can be read and changed.
b.y -= b.vy; b.a -= 2;
Moves the bubble upward by its own speed and reduces its alpha (opacity) by 2 every frame, which is what creates the rising-and-fading effect.
if (b.a <= 0) { bubbles.splice(i, 1); continue; }
Once a bubble is fully transparent, removes it from the array and skips the rest of this loop iteration since there's nothing left to draw.
push();
Saves the current drawing style and coordinate system so changes made for this bubble don't affect anything else.
translate(b.x, b.y); rectMode(CENTER); noStroke();
Shifts the drawing origin to the bubble's position, sets rectangles to be drawn centered on that origin, and disables outlines for a clean look.
fill(255, 255, 255, b.a);
Sets the fill color to white using the bubble's current alpha value, so the bubble visually fades as it rises.
let pad = 12, w = textWidth(b.txt) + pad * 2, h = 50;
Calculates the bubble's width based on how wide the excuse text is (plus padding), and uses a fixed height of 50 pixels.
rect(0, 0, w, h, 20);
Draws the rounded rectangle bubble background at the translated origin, with 20-pixel rounded corners.
fill(70, 60, 90, b.a); textAlign(CENTER, CENTER);
Sets the text color to a dark purple with the same fading alpha, and centers text both horizontally and vertically.
text(b.txt, 0, 0, w - 10, h - 10);
Draws the excuse text wrapped inside a box slightly smaller than the bubble so it doesn't touch the edges.
pop();
Restores the saved drawing state, so the next bubble (or the UI text below) starts with a clean slate.
fill(70); textAlign(LEFT, TOP);
Switches to a plain gray color and top-left alignment for drawing the click counter.
text("Excuses generated: " + count, 10, 10);
Displays the running total of excuses generated in the top-left corner of the canvas.

mousePressed()

mousePressed() is a p5.js event function that automatically runs once every time the mouse is clicked. It's the perfect place to trigger one-time actions like spawning a new object, as opposed to draw()'s continuous per-frame updates.

🔬 The vy value controls how fast each bubble floats upward. What happens if you change random(0.5, 1.2) to random(3, 6) so bubbles zoom off the top of the screen quickly?

  bubbles.push({ x: random(120, width - 120), y: height + 40, txt: msg, a: 255, vy: random(0.5, 1.2) });
  count++;
function mousePressed() {
  let pool = count < 5 ? excusesEasy : count < 10 ? excusesMid : excusesHard;
  let msg = random(pool);
  bubbles.push({ x: random(120, width - 120), y: height + 40, txt: msg, a: 255, vy: random(0.5, 1.2) });
  count++;
}
Line-by-line explanation (4 lines)

🔧 Subcomponents:

conditional Difficulty Ramp Ternary let pool = count < 5 ? excusesEasy : count < 10 ? excusesMid : excusesHard;

Selects which excuse array to draw from based on the current click count, escalating from mild to absurd excuses over time.

let pool = count < 5 ? excusesEasy : count < 10 ? excusesMid : excusesHard;
Chooses which array of excuse strings to use: the first 5 clicks pull from excusesEasy, the next 5 from excusesMid, and every click after that from excusesHard.
let msg = random(pool);
p5's random() function, when given an array, returns one randomly chosen item from it - here, a random excuse string.
bubbles.push({ x: random(120, width - 120), y: height + 40, txt: msg, a: 255, vy: random(0.5, 1.2) });
Creates a brand-new bubble object starting just below the bottom edge of the canvas at a random horizontal spot, fully opaque (alpha 255), with a random upward speed, and adds it to the bubbles array so draw() will start animating it.
count++;
Increases the click counter by one, which both escalates which excuse pool gets used next and updates the on-screen counter.

windowResized()

windowResized() is a p5.js event function that fires automatically whenever the browser window changes size. Pairing it with resizeCanvas() is the standard way to build sketches that adapt to any screen size.

function windowResized() { resizeCanvas(windowWidth, windowHeight); }
Line-by-line explanation (1 lines)
resizeCanvas(windowWidth, windowHeight);
Resizes the canvas to match the browser window's new dimensions whenever the user resizes their window, keeping the sketch full-screen and responsive.

📦 Key Variables

bubbles array

Holds every currently active excuse bubble as an object with position (x, y), text, opacity (a), and rising speed (vy). Objects are added in mousePressed() and removed in draw() once fully faded.

let bubbles = [];
count number

Tracks the total number of excuses generated so far. Used both to escalate which excuse pool is used and to display a running total in the corner of the canvas.

let count = 0;
excusesEasy array

A list of mild excuse strings shown for the first 5 clicks.

let excusesEasy = ["Mercury is in retrograde", ...];
excusesMid array

A list of medium-absurdity excuse strings shown for clicks 6 through 10.

let excusesMid = ["I'm emotionally buffering", ...];
excusesHard array

A list of the most absurd excuse strings shown after 10 clicks, for maximum comedic escalation.

let excusesHard = ["My last brain cell called in sick", ...];

🔧 Potential Improvements (3)

Here are some ways this code could be enhanced:

BUG draw() - text(b.txt, 0, 0, w - 10, h - 10);

The bubble's height is always a fixed 50 pixels, but the width is calculated from single-line textWidth(). If an excuse is long enough that p5's automatic wrapping breaks it into multiple lines within the given width box, the text can overflow vertically past the bubble's edges since the height never adjusts.

💡 Estimate the number of wrapped lines (e.g. based on textWidth and available width) and scale h accordingly, or increase h to a safer fixed value like 70-80 to give wrapped text more room.

PERFORMANCE mousePressed()

Every click pushes a new bubble into the bubbles array with no upper limit. If a user clicks very rapidly, dozens of bubbles could exist simultaneously before any fade out, since each only loses alpha slowly (2 per frame from a starting value of 255).

💡 Cap the maximum number of simultaneous bubbles (e.g. if (bubbles.length < 30) bubbles.push(...)) or add a short cooldown between clicks to prevent performance issues on rapid clicking.

STYLE draw() and mousePressed()

Magic numbers like 5, 10, 2, 50, 20, and 0.5/1.2 are scattered directly in the code with no explanation of what they represent or why those values were chosen.

💡 Extract these into named constants at the top of the file, such as const FADE_SPEED = 2; const BUBBLE_HEIGHT = 50; const EASY_THRESHOLD = 5; const MID_THRESHOLD = 10; to make the code more self-documenting and easier to tune.

🔄 Code Flow

Code flow showing setup, draw, mousepressed, windowresized

💡 Click on function names in the diagram to jump to their code

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> bubbleloop[Bubble Update And Draw Loop] bubbleloop --> faderemoval[Remove Faded Bubble] bubbleloop --> difficultyternary[Difficulty Ramp Ternary] faderemoval --> draw difficultyternary --> draw click setup href "#fn-setup" click draw href "#fn-draw" click bubbleloop href "#sub-bubble-loop" click faderemoval href "#sub-fade-removal" click difficultyternary href "#sub-difficulty-ternary"

❓ Frequently Asked Questions

What visual experience does the AI Excuse Generator create?

The sketch generates floating excuse bubbles that fade away as they rise, set against a soft pastel background, providing a whimsical and humorous visual experience.

How can users engage with the AI Excuse Generator sketch?

Users can interact with the sketch by clicking the canvas, which generates increasingly absurd excuses that appear as floating bubbles.

What creative coding concepts are illustrated in this p5.js sketch?

This sketch demonstrates the use of arrays for categorizing excuses, dynamic bubble animations, and user interaction through mouse events.

Preview

AI Excuse Generator - Why I Can't Work Today - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of AI Excuse Generator - Why I Can't Work Today - Code flow showing setup, draw, mousepressed, windowresized
Code Flow Diagram