AI Dying Battery - Watch the Panic Unfold

This sketch renders a looping animated battery icon that visually drains from 100% down to 0% over a fixed cycle, changing color from green to yellow to red and swapping in increasingly panicked text messages as the charge falls. Once it hits 0%, it instantly loops back to full and starts again, forever repeating the anxiety spiral.

🧪 Try This!

Experiment with the code by making these changes:

  1. Speed up the drain — Shrinking the duration makes the battery empty and loop much faster, so you see the whole panic cycle in a few seconds.
  2. Make a giant battery — Increasing bw and bh scales up every rectangle in the drawing since they're all calculated from these two values.
  3. Change the danger color to purple — Replacing the low-battery color makes the 'critical' state look eerie purple instead of alarming red.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch draws a cartoon battery in the center of the screen that continuously drains from 100% to 0% and then resets, growing more alarming as it empties. The fill color smoothly blends from green to yellow to red using lerpColor(), and a chain of if/else conditions swaps in funnier, more panicked captions like 'PANIC MODE' and 'goodbye world...' as the percentage drops. The whole animation is driven by nothing but the built-in millis() clock, so there is no state to track between frames - every frame is calculated fresh from elapsed time.

The code is short: a setup() function that creates a full-window canvas, a draw() function that does all the math and drawing every frame, and a windowResized() helper that keeps the canvas matching the browser size. Studying it teaches you how to build looping, time-based animations without any counters or global timers - just modular arithmetic on millis() - plus how to layer multiple rectangles, colors and conditional text into a single cohesive UI-style graphic.

⚙️ How It Works

  1. When the page loads, setup() creates a canvas that fills the entire browser window.
  2. Every frame, draw() clears the background to white, then computes a repeating 0-to-1 progress value t by taking millis() modulo a 30-second duration and dividing by that duration.
  3. That progress value t is inverted and scaled into a whole-number percentage pct that counts down from 100 to 0 as the cycle repeats.
  4. The battery outline and inner tray are drawn as rectangles, and a fill color is calculated by blending between red, yellow and green depending on how high pct is, using lerpColor().
  5. A colored bar is drawn inside the battery whose height is proportional to pct, so it visually shrinks over time, while the percentage number and a matching panic-level caption are drawn as text below it.
  6. Because pct is recalculated purely from millis() % duration, the whole animation loops seamlessly forever with zero extra state variables.

🎓 Concepts You'll Learn

Time-based animation with millis()Modulo arithmetic for looping progresslerpColor for smooth color blendingConditional chains (if/else if) for state-like behaviorRounded rectangles and layered shapesResponsive canvas with windowResized()

📝 Code Breakdown

setup()

setup() runs once when the sketch starts. Here it just sizes the canvas - all the actual animation logic lives entirely in draw(), since there's no state that needs to be initialized once.

function setup() {
  createCanvas(windowWidth, windowHeight);
}
Line-by-line explanation (1 lines)
createCanvas(windowWidth, windowHeight);
Creates a canvas that exactly matches the current browser window's width and height, so the battery is centered on any screen size.

draw()

draw() runs continuously at up to 60 frames per second and here it recalculates the entire animation from scratch each time using only millis() - a great example of stateless, time-driven animation instead of manually incrementing counters.

🔬 This chain decides the battery's color based on pct. What happens if you swap the colors in the first lerpColor() so it goes green-to-blue instead of yellow-to-green when above 50%?

  if (pct > 50) c = lerpColor(color(255, 204, 0), color(0, 200, 0), (pct - 50) / 50);
  else if (pct > 20) c = lerpColor(color(220, 0, 0), color(255, 204, 0), (pct - 20) / 30);
  else c = color(220, 0, 0);

🔬 This chain checks thresholds from highest to lowest. What happens if you add a new 'else if (pct > 95) msg = "Fully charged, relax!";' right after the first line - will it ever actually appear?

  if (pct > 87) msg = "100% All good!";
  else if (pct > 62) msg = "75% Still fine";
  else if (pct > 37) msg = "50% Maybe charge soon?";
  else if (pct > 17) msg = "25% Getting worried...";
  else if (pct > 7) msg = "10% PANIC MODE";
  else if (pct > 3) msg = "5% NOOO!";
  else msg = "1% goodbye world...";
function draw() {
  background(255);
  let t = (millis() % duration) / duration;
  let pct = floor(100 * (1 - t));
  let bw = 120, bh = 200, x = width / 2 - bw / 2, y = height / 2 - bh / 2;
  stroke(0); strokeWeight(4); noFill();
  rect(x, y, bw, bh, 10);
  rect(width / 2 - 20, y - 20, 40, 20, 5);
  let innerH = bh - 40, chargeH = innerH * pct / 100;
  let c;
  if (pct > 50) c = lerpColor(color(255, 204, 0), color(0, 200, 0), (pct - 50) / 50);
  else if (pct > 20) c = lerpColor(color(220, 0, 0), color(255, 204, 0), (pct - 20) / 30);
  else c = color(220, 0, 0);
  noStroke(); fill(230);
  rect(x + 10, y + 10, bw - 20, bh - 20, 6);
  if (pct > 0) {
    fill(c);
    rect(x + 10, y + 10 + (innerH - chargeH), bw - 20, chargeH, 6);
  }
  fill(0); textAlign(CENTER, CENTER);
  textSize(28); text(pct + "%", width / 2, height / 2);
  let msg;
  if (pct > 87) msg = "100% All good!";
  else if (pct > 62) msg = "75% Still fine";
  else if (pct > 37) msg = "50% Maybe charge soon?";
  else if (pct > 17) msg = "25% Getting worried...";
  else if (pct > 7) msg = "10% PANIC MODE";
  else if (pct > 3) msg = "5% NOOO!";
  else msg = "1% goodbye world...";
  textSize(20);
  text(msg, width / 2, height / 2 + bh / 2 + 40);
}
Line-by-line explanation (18 lines)

🔧 Subcomponents:

calculation Looping Progress Value let t = (millis() % duration) / duration;

Uses modulo on the running clock to create a value that repeatedly counts from 0 to 1 every 'duration' milliseconds, without needing a reset flag.

conditional Battery Color Blend if (pct > 50) c = lerpColor(color(255, 204, 0), color(0, 200, 0), (pct - 50) / 50);

Picks and blends between yellow/green or red/yellow depending on the current percentage so the color smoothly shifts as charge drops.

conditional Draw Charge Bar if (pct > 0) { fill(c); rect(x + 10, y + 10 + (innerH - chargeH), bw - 20, chargeH, 6); }

Only draws the colored charge rectangle while there's still charge left, anchored to the bottom of the battery so it drains downward.

switch-case Panic Message Selector if (pct > 87) msg = "100% All good!"; else if (pct > 62) msg = "75% Still fine";

Walks down a chain of percentage thresholds to pick the appropriate escalating panic caption to display.

background(255);
Paints the whole canvas white every frame, erasing the previous frame's drawing before the new one is drawn.
let t = (millis() % duration) / duration;
millis() returns milliseconds since the sketch started; taking it modulo 'duration' makes it wrap back to 0 every cycle, and dividing by duration normalizes it to a 0-1 range.
let pct = floor(100 * (1 - t));
Flips t (so it counts down instead of up) and scales it to a whole-number percentage from 100 down to 0.
let bw = 120, bh = 200, x = width / 2 - bw / 2, y = height / 2 - bh / 2;
Sets the battery's width and height, then computes its top-left x/y so it's centered on the canvas.
stroke(0); strokeWeight(4); noFill();
Sets up a thick black outline with no fill, ready for drawing the battery's outer shell.
rect(x, y, bw, bh, 10);
Draws the main battery body as a rounded rectangle with 10px corner radius.
rect(width / 2 - 20, y - 20, 40, 20, 5);
Draws the small battery 'nub' rectangle above the main body to complete the battery icon shape.
let innerH = bh - 40, chargeH = innerH * pct / 100;
Calculates the height of the inner charge area (leaving a 20px margin on each side) and how tall the colored charge bar should be for the current percentage.
if (pct > 50) c = lerpColor(color(255, 204, 0), color(0, 200, 0), (pct - 50) / 50);
When charge is above 50%, blend from yellow toward green as pct increases from 50 to 100.
else if (pct > 20) c = lerpColor(color(220, 0, 0), color(255, 204, 0), (pct - 20) / 30);
Between 20% and 50%, blend from red toward yellow as pct rises.
else c = color(220, 0, 0);
At 20% or below, the color is locked to solid red for maximum alarm.
noStroke(); fill(230);
Switches to a light gray fill with no outline, used to draw the empty 'tray' behind the charge bar.
rect(x + 10, y + 10, bw - 20, bh - 20, 6);
Draws the gray inner tray slightly inset from the battery's outer edge.
if (pct > 0) { fill(c); rect(x + 10, y + 10 + (innerH - chargeH), bw - 20, chargeH, 6); }
Only if there's charge left, draws the colored bar on top of the gray tray; positioning it at 'innerH - chargeH' from the top makes it fill from the bottom up as pct grows.
fill(0); textAlign(CENTER, CENTER);
Sets text color to black and centers text both horizontally and vertically around the coordinates given to text().
textSize(28); text(pct + "%", width / 2, height / 2);
Draws the large percentage number centered inside the battery.
if (pct > 87) msg = "100% All good!";
Starts a chain of checks that picks a caption based on which percentage range pct currently falls into.
textSize(20); text(msg, width / 2, height / 2 + bh / 2 + 40);
Shrinks the text size and draws the selected panic message below the battery.

windowResized()

windowResized() is a p5.js event function that fires automatically on browser resize, letting you keep responsive, full-window sketches without any extra event listener code.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
}
Line-by-line explanation (1 lines)
resizeCanvas(windowWidth, windowHeight);
p5.js automatically calls this function whenever the browser window is resized; this line makes the canvas match the new window dimensions so the battery stays centered and full-screen.

📦 Key Variables

duration number

The length in milliseconds of one full drain cycle (100% to 0%); used with millis() to compute the looping progress value each frame.

let duration = 30000;

🔧 Potential Improvements (4)

Here are some ways this code could be enhanced:

STYLE draw() message chain

The percentage thresholds (87, 62, 37, 17, 7, 3) and their matching message strings are magic numbers hardcoded inline, making them hard to tweak or extend without carefully recounting ranges.

💡 Store them as an array of {threshold, message} objects and loop through it to find the first match - this makes it trivial to add, remove, or reorder panic levels.

PERFORMANCE draw() color blending

Two color() objects and a lerpColor() call are created from scratch every single frame even though pct only changes in whole-number steps, meaning a lot of redundant color computation over a 60fps loop.

💡 Only recompute the color when pct changes from the previous frame by caching pct in a variable outside draw() and comparing before recalculating c.

BUG draw() text rendering

There's no textFont() or fallback set, so on some systems the default font/rendering of text(pct + "%", ...) may look inconsistent, and rapid font-metric shifts as digits change (100% vs 5%) can cause the text to jitter slightly since textAlign is CENTER but string width varies.

💡 Call textFont() once in setup() to lock in a consistent font, and consider padding the percentage string to a fixed width if jitter is noticeable.

FEATURE overall sketch

The animation is purely automatic and offers no user interaction - there's no way to pause, restart, or manually 'charge' the battery by clicking.

💡 Add a mousePressed() function that resets an offset added to millis(), letting users click to instantly 'recharge' the battery back to 100% for a more interactive experience.

🔄 Code Flow

Code flow showing setup, draw, windowresized

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

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> progresscalc[Progress Calculation] draw --> colorblend[Color Blend] draw --> chargebar[Charge Bar] draw --> messagechain[Panic Message Selector] progresscalc --> draw colorblend --> draw chargebar --> draw messagechain --> draw click setup href "#fn-setup" click draw href "#fn-draw" click progresscalc href "#sub-progress-calc" click colorblend href "#sub-color-blend" click chargebar href "#sub-charge-bar" click messagechain href "#sub-message-chain"

❓ Frequently Asked Questions

What visual experience does the AI Dying Battery sketch provide?

The sketch visually represents a battery draining from 100% to 0%, with a color transition from green to yellow to red, accompanied by increasingly panicked text messages.

Is there any user interaction in the AI Dying Battery sketch?

The sketch is not interactive; it automatically animates the battery depletion and message changes based on a timed duration.

What creative coding concepts are demonstrated in the AI Dying Battery sketch?

This sketch showcases concepts such as animation timing, color interpolation, and dynamic text display based on the battery percentage.

Preview

AI Dying Battery - Watch the Panic Unfold - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of AI Dying Battery - Watch the Panic Unfold - Code flow showing setup, draw, windowresized
Code Flow Diagram