AMERICAAAAAAAAA

This sketch creates a patriotic fireworks display where colorful rockets launch from the bottom of the screen and explode into particles that assemble into glowing "AMERICA 250" text. After one minute, the text dissolves away in a shimmering fade, creating a celebratory visual effect.

🧪 Try This!

Experiment with the code by making these changes:

  1. Change the text to your name — Modify the text string and watch particles form your name instead of 'AMERICA 250'
  2. Make particles assemble instantly — Increase the steering force so particles snap to their text positions much faster instead of drifting
  3. Double the particle count — More particles create denser, more detailed text
  4. Make the text glow longer — Increase the delay before text dissolves—text stays visible and bright for twice as long
  5. Soften the explosion trails — Lower the trail fade rate so motion streaks persist longer and look more ethereal
  6. Make rockets rise slowly and gently — Reduce the upward force so rockets take longer to reach their targets with a more graceful arc
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch combines multiple advanced p5.js techniques to create a stunning visual celebration: it loads a custom font, renders text to an off-screen graphics buffer, extracts pixel coordinates to create particle target positions, simulates physics with vectors and forces, and animates two layers of particles with different behaviors. The result is a fireworks display where hundreds of colored rockets streak upward and explode into particles that intelligently gather to form text before dissolving.

The code is organized around two particle classes—FireworkRocket and ExplosionParticle—each handling a different phase of the animation. By studying this sketch, you'll learn how to use createGraphics() to extract text geometry, how to apply forces and vectors to create organic motion, how classes manage complex state with multiple phases, and how to layer animations with different timing to create a cohesive visual narrative.

⚙️ How It Works

  1. When the sketch loads, preload() fetches a Google Font, and setup() creates an off-screen graphics buffer where 'AMERICA 250' is rendered in white. The sketch then samples every pixel of that text and stores their coordinates in textPoints, creating a map of where particles should ultimately settle.
  2. For each text coordinate, a FireworkRocket object is created at a random x position on the bottom of the screen with an upward velocity. These rockets are stored in the fireworks array.
  3. Every frame, draw() updates and displays all rockets, which climb upward with applied force and fade in size. When a rocket passes its target height or peaks, it transforms into an ExplosionParticle at that location and is removed from the fireworks array.
  4. ExplosionParticles explode outward in random directions with random speeds, while gravity and a steering force gradually pull them toward their assigned target text coordinates. As they move, their size shrinks and they fade out slightly.
  5. Once an ExplosionParticle gets within 1 pixel of its target, it 'settles'—snaps to the exact position, stops moving, resets its opacity to full brightness, and shrinks to a small fixed size. Now it's part of the visible text.
  6. After 60 seconds of sitting at its text position, each settled particle begins dissolving—its lifespan decreases and it fades out over several frames. Once lifespan drops below zero, the particle is removed from the explosionParticles array.

🎓 Concepts You'll Learn

Particle systemsVector physics and forcesFont loading and text renderingOff-screen graphics buffersPixel sampling and geometry extractionObject-oriented design with classesMulti-phase animation state machinesColor arrays and random selection

📝 Code Breakdown

preload()

preload() is a special p5.js function that runs before setup(). Use it to load fonts, images, and other media that must be ready before your sketch begins drawing.

function preload() {
  myFont = loadFont('https://cdn.jsdelivr.net/fontsource/fonts/roboto@latest/latin-400-normal.woff');
}
Line-by-line explanation (1 lines)
myFont = loadFont('https://cdn.jsdelivr.net/fontsource/fonts/roboto@latest/latin-400-normal.woff');
Loads a Google Font (Roboto) from a CDN and stores it in myFont so we can use it for text rendering. preload() runs before setup() to ensure the font is ready.

setup()

setup() runs once at startup. Here it does the heavy lifting: loading the text geometry, extracting pixel positions, and creating all the rocket objects that will later animate. The trick of using createGraphics() to render text off-screen and then sampling its pixels is a powerful technique for converting any text or image into particle positions.

🔬 These three lines control how the text looks before it becomes particles. What happens if you change 255 (white) to color(255, 0, 0) to make red particles, or change / 8 to / 4 to make bigger text?

  graphics.textSize(min(width, height) / 8); // Adjust text size based on canvas size
  graphics.textAlign(CENTER, CENTER); // Center the text
  graphics.fill(255); // White color for the text
function setup() {
  createCanvas(windowWidth, windowHeight);
  background(0); // Set initial background to black

  // Create an off-screen graphics buffer to draw the text
  let graphics = createGraphics(width, height);
  graphics.textFont(myFont); // Set the font
  graphics.textSize(min(width, height) / 8); // Adjust text size based on canvas size
  graphics.textAlign(CENTER, CENTER); // Center the text
  graphics.fill(255); // White color for the text

  // ⇨ This is the key change: draw "AMERICA 250" instead of "AMERICA"
  graphics.text("AMERICA 250", width / 2, height / 2);

  // Load pixels from the off-screen buffer to find text points
  graphics.loadPixels();
  for (let x = 0; x < graphics.width; x += 2) { // Reduced sampling step for denser text
    for (let y = 0; y < graphics.height; y += 2) { // Reduced sampling step for denser text
      let index = (x + y * graphics.width) * 4;
      let alpha = graphics.pixels[index + 3]; // Get the alpha value
      if (alpha > 0) { // If the pixel is part of the text
        textPoints.push(createVector(x, y));
      }
    }
  }

  // Sample textPoints if there are too many for performance
  const desiredFireworkCount = 2000; // Desired number of sparks to form the text
  if (textPoints.length > desiredFireworkCount) {
    const sampledPoints = [];
    while (sampledPoints.length < desiredFireworkCount) {
      const randomIndex = floor(random(textPoints.length));
      sampledPoints.push(textPoints[randomIndex]);
      textPoints.splice(randomIndex, 1); // Remove to avoid duplicates
    }
    textPoints = sampledPoints;
  }

  // Create a FireworkRocket for each text point, launching from the bottom
  for (let i = 0; i < textPoints.length; i++) {
    fireworks.push(new FireworkRocket(random(width), height, textPoints[i].x, textPoints[i].y));
  }
}
Line-by-line explanation (15 lines)

🔧 Subcomponents:

for-loop Text Pixel Sampling for (let x = 0; x < graphics.width; x += 2) { for (let y = 0; y < graphics.height; y += 2) {

Scans the off-screen text image and extracts all white pixel coordinates as target positions for particles

conditional Performance-Based Point Reduction if (textPoints.length > desiredFireworkCount) { const sampledPoints = []; while (sampledPoints.length < desiredFireworkCount) {

If too many pixels were found, randomly selects a subset to maintain smooth animation performance

for-loop Rocket Instantiation for (let i = 0; i < textPoints.length; i++) { fireworks.push(new FireworkRocket(random(width), height, textPoints[i].x, textPoints[i].y)); }

Creates one FireworkRocket object for each text pixel, each launching from a random x position at the bottom

createCanvas(windowWidth, windowHeight);
Creates a canvas that fills the entire browser window and responds to resizing
background(0); // Set initial background to black
Fills the canvas with black, establishing the night-sky look
let graphics = createGraphics(width, height);
Creates an invisible off-screen drawing surface (same size as the main canvas) where we'll render text without displaying it directly
graphics.textFont(myFont); // Set the font
Tells the off-screen buffer to use the custom Roboto font we loaded in preload()
graphics.textSize(min(width, height) / 8); // Adjust text size based on canvas size
Makes the text size responsive—on small screens it shrinks, on large screens it grows
graphics.text("AMERICA 250", width / 2, height / 2);
Draws the text centered on the off-screen buffer at a huge size—this is invisible to the viewer but we'll use it as a geometry template
graphics.loadPixels();
Copies pixel data from the off-screen buffer into an array so we can read individual pixel colors
let index = (x + y * graphics.width) * 4;
Calculates the position in the pixels array for coordinate (x, y). Multiply by 4 because each pixel stores 4 values: red, green, blue, and alpha (transparency)
let alpha = graphics.pixels[index + 3]; // Get the alpha value
Reads the transparency value at that pixel position. +3 skips past red, green, and blue to grab the alpha channel
if (alpha > 0) { // If the pixel is part of the text
Only processes pixels that are visible (non-transparent). Invisible background pixels are skipped
textPoints.push(createVector(x, y));
Stores this pixel's (x, y) location as a target position where a particle will eventually settle
const desiredFireworkCount = 2000; // Desired number of sparks to form the text
Sets a cap on particle count. If we found more than 2000 text pixels, we'll thin them out to keep animation smooth
const randomIndex = floor(random(textPoints.length));
Picks a random index from the full list of text pixels
sampledPoints.push(textPoints[randomIndex]); textPoints.splice(randomIndex, 1);
Adds that pixel to our final list and removes it from the full list to avoid picking it twice
fireworks.push(new FireworkRocket(random(width), height, textPoints[i].x, textPoints[i].y));
Creates a new rocket that starts at a random x position at the bottom of the canvas and targets the text pixel at textPoints[i]

draw()

draw() runs 60 times per second (the animation loop). Each frame it clears the canvas with a semi-transparent background (creating trails), updates and redraws all rockets, then does the same for explosion particles. The order matters: rockets transform into explosion particles, so rockets are processed first and removed before the explosion array grows.

🔬 This loop processes every rocket every frame. What happens if you comment out the fireworks[i].show() line so rockets update their position but never draw themselves—do you still see the text form?

  // Update and show FireworkRockets
  for (let i = fireworks.length - 1; i >= 0; i--) {
    fireworks[i].update();
    fireworks[i].show();

    // If a rocket is "finished" (transformed into an explosion particle), remove it
    if (fireworks[i].finished()) {
      fireworks.splice(i, 1);
    }
  }
function draw() {
  // Create a semi-transparent background to create trail effects
  colorMode(RGB);
  background(0, 0, 0, 25); // Dark background with some transparency

  // Update and show FireworkRockets
  for (let i = fireworks.length - 1; i >= 0; i--) {
    fireworks[i].update();
    fireworks[i].show();

    // If a rocket is "finished" (transformed into an explosion particle), remove it
    if (fireworks[i].finished()) {
      fireworks.splice(i, 1);
    }
  }

  // Update and show ExplosionParticles
  for (let i = explosionParticles.length - 1; i >= 0; i--) {
    explosionParticles[i].update();
    explosionParticles[i].show();

    // If an explosion particle has dissolved completely, remove it
    if (explosionParticles[i].finished()) {
      explosionParticles.splice(i, 1);
    }
  }
}
Line-by-line explanation (6 lines)

🔧 Subcomponents:

for-loop Rocket Update Loop for (let i = fireworks.length - 1; i >= 0; i--) { fireworks[i].update(); fireworks[i].show(); if (fireworks[i].finished()) { fireworks.splice(i, 1); } }

Loops backwards through all active rockets, updates their physics, draws them, and removes finished ones

for-loop Explosion Particle Update Loop for (let i = explosionParticles.length - 1; i >= 0; i--) { explosionParticles[i].update(); explosionParticles[i].show(); if (explosionParticles[i].finished()) { explosionParticles.splice(i, 1); } }

Loops backwards through all explosion particles, updates their motion and state, draws them, and removes fully dissolved ones

colorMode(RGB);
Ensures colors are specified using RGB (red, green, blue) values rather than HSB (hue, saturation, brightness)
background(0, 0, 0, 25); // Dark background with some transparency
Fills the canvas with near-black but with transparency (alpha = 25). This semi-transparent layer means each frame slightly fades older particles, creating motion trails instead of erasing them completely
for (let i = fireworks.length - 1; i >= 0; i--) {
Loops backwards through the fireworks array (important when removing items). Backwards looping prevents skipped elements when splice() is called
fireworks[i].update();
Calls the update() method on the rocket, which applies forces, updates velocity and position, and checks if it has reached its peak
fireworks[i].show();
Calls the show() method to draw the rocket at its current position
if (fireworks[i].finished()) { fireworks.splice(i, 1); }
If the rocket has finished its journey (transformed into an explosion particle), remove it from the fireworks array to save memory and processing

windowResized()

windowResized() is a special p5.js function that p5 calls automatically whenever the browser window is resized. It's good practice to include it for full-screen sketches so the animation adapts to the screen size.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  background(0); // Clear the canvas and reset background
  // For this sketch, we keep existing particles; regenerating text would be more complex.
}
Line-by-line explanation (2 lines)
resizeCanvas(windowWidth, windowHeight);
Automatically resizes the p5.js canvas to match the new browser window dimensions when the user resizes their window
background(0); // Clear the canvas and reset background
Clears any drawing artifacts from the old canvas size and fills with black

FireworkRocket()

FireworkRocket is a class representing a single rocket particle. It has its own position, velocity, and acceleration, managed using p5.Vector objects and basic physics (force → acceleration → velocity → position). When a rocket reaches its peak or target height, it transforms into an ExplosionParticle by adding one to the explosionParticles array and marking itself as finished so draw() removes it.

🔬 This is the rocket's physics engine. What happens if you change -0.2 to 0.2 (removing the negative sign) so the force pushes downward instead of up? Do rockets still explode at the right height?

    if (!this.finishedMoving) {
      this.applyForce(createVector(0, -0.2)); // Apply force upwards to simulate launch
      this.vel.add(this.acc);
      this.pos.add(this.vel);
      this.acc.mult(0); // Reset acceleration

      // Check if rocket has reached its peak or passed its target height
      if (this.vel.y >= 0 || this.pos.y < this.targetPos.y) {
class FireworkRocket {
  constructor(x, y, targetX, targetY) {
    this.pos = createVector(x, y);
    this.vel = createVector(0, random(-10, -15)); // Launch upwards
    this.acc = createVector(0, 0);
    this.targetPos = createVector(targetX, targetY);
    this.color = this.getRandomColor(); // Get a patriotic color
    this.finishedMoving = false; // Flag to indicate it's done moving and ready to transform
    this.size = 2; // Size of the rocket particle
  }

  // Get a random patriotic color (red, white, blue, or gold)
  getRandomColor() {
    let colors = [
      color(255, 0, 0), // Red
      color(255),       // White
      color(0, 0, 255), // Blue
      color(255, 204, 0) // Gold
    ];
    return random(colors);
  }

  // Apply a force to the particle
  applyForce(force) {
    this.acc.add(force);
  }

  // Update the rocket's position
  update() {
    if (!this.finishedMoving) {
      this.applyForce(createVector(0, -0.2)); // Apply force upwards to simulate launch
      this.vel.add(this.acc);
      this.pos.add(this.vel);
      this.acc.mult(0); // Reset acceleration

      // Check if rocket has reached its peak or passed its target height
      if (this.vel.y >= 0 || this.pos.y < this.targetPos.y) {
        // Transform into an ExplosionParticle at its current position
        explosionParticles.push(
          new ExplosionParticle(this.pos.x, this.pos.y, this.targetPos.x, this.targetPos.y, this.color)
        );
        this.finishedMoving = true;
      }
    }
  }

  // Show the rocket
  show() {
    if (!this.finishedMoving) {
      strokeWeight(2); // Keep stroke for the rocket trail
      stroke(this.color);
      point(this.pos.x, this.pos.y);
    }
  }

  // Check if the rocket has finished its movement and transformed
  finished() {
    return this.finishedMoving;
  }
}
Line-by-line explanation (17 lines)

🔧 Subcomponents:

calculation Patriotic Color Selection return random(colors);

Picks a random patriotic color (red, white, blue, or gold) for the rocket to use during flight

calculation Physics Integration this.applyForce(createVector(0, -0.2)); this.vel.add(this.acc); this.pos.add(this.vel);

Applies upward force, updates velocity based on acceleration, and moves the rocket

conditional Peak Detection and Transformation if (this.vel.y >= 0 || this.pos.y < this.targetPos.y) { explosionParticles.push(new ExplosionParticle(...)); this.finishedMoving = true; }

Detects when the rocket has peaked (velocity stops going up) or reached its target height, then transforms it into an explosion particle

this.pos = createVector(x, y);
Stores the rocket's starting position (a random x at the bottom of the screen) as a p5.Vector
this.vel = createVector(0, random(-10, -15)); // Launch upwards
Creates a velocity vector with zero horizontal movement and a strong upward speed (negative y, since y increases downward in p5.js)
this.acc = createVector(0, 0);
Initializes acceleration to zero; forces will be added to this each frame
this.targetPos = createVector(targetX, targetY);
Stores the text pixel coordinate this rocket should aim for when it explodes
this.color = this.getRandomColor(); // Get a patriotic color
Assigns this rocket a random patriotic color (red, white, blue, or gold) that will be used for the rocket trail and inherited by its explosion particles
this.finishedMoving = false; // Flag to indicate it's done moving and ready to transform
Boolean flag tracking whether this rocket has already peaked and transformed into particles
let colors = [ color(255, 0, 0), color(255), color(0, 0, 255), color(255, 204, 0) ];
Array of four p5.js color objects: red, white, blue, and gold—the patriotic color palette
return random(colors);
Randomly picks one color from the array and returns it
this.acc.add(force);
Adds the force vector to the accumulator; multiple forces can be applied per frame and they add up
this.applyForce(createVector(0, -0.2)); // Apply force upwards to simulate launch
Applies an upward force of 0.2 pixels per frame squared, accelerating the rocket upward against gravity
this.vel.add(this.acc);
Updates velocity by adding the accumulated forces. The rocket speeds up in the direction of the force
this.pos.add(this.vel);
Updates position by adding velocity. The rocket moves to a new location each frame
this.acc.mult(0); // Reset acceleration
Clears acceleration to zero at the end of the frame, ready for new forces to be added next frame
if (this.vel.y >= 0 || this.pos.y < this.targetPos.y) {
Checks two conditions: if velocity has stopped going up (vel.y >= 0, meaning the rocket peaked) OR if the rocket has passed its target height. Either way, it's time to explode.
explosionParticles.push( new ExplosionParticle(this.pos.x, this.pos.y, this.targetPos.x, this.targetPos.y, this.color) );
Creates a new ExplosionParticle at the rocket's current position, targeting the text coordinates, and using the same color
stroke(this.color); point(this.pos.x, this.pos.y);
Draws a single colored pixel at the rocket's position, creating a thin colored trail as it moves upward
return this.finishedMoving;
Returns true if the rocket has already transformed, signaling to draw() that it can be removed from the fireworks array

ExplosionParticle()

ExplosionParticle is the most complex class, managing three distinct phases: explosion (particles burst outward while fading), attraction (gravity and steering pull them toward text targets), and dissolution (settled particles eventually fade away). Each phase has different physics and lifespan behavior. The use of flags (settled, dissolving) creates a state machine that determines which update rules apply each frame. This pattern is powerful for managing complex multi-phase behaviors in animation.

🔬 These lines control how particles fade during the explosion phase. What happens if you change -= 2 to -= 0.5 so particles fade much more slowly? Do they stay visible longer before settling?

      // Reduce lifespan for initial fading
      this.lifespan -= 2;
      // Map lifespan to current size during explosion phase
      this.currentSize = map(this.lifespan, 0, this.initialLifespan, 0.5, this.initialParticleSize);

🔬 This block controls the final fade-out. What happens if you change -= 1 to -= 5 so particles dissolve five times faster? How does this change the visual feel?

    } else if (this.dissolving) {
      // Particle is dissolving
      this.lifespan -= 1; // Reduce lifespan to fade it out
      // Map lifespan to current size during dissolving phase
      this.currentSize = map(this.lifespan, 0, this.initialLifespan, 0, this.settledParticleSize);
    }
class ExplosionParticle {
  constructor(x, y, targetX, targetY, col) {
    this.pos = createVector(x, y);
    this.vel = p5.Vector.random2D(); // Initial random explosion direction
    this.vel.mult(random(2, 8)); // Initial explosion speed
    this.acc = createVector(0, 0);
    this.targetPos = createVector(targetX, targetY);
    this.color = col;
    this.initialLifespan = 255; // Max lifespan
    this.lifespan = this.initialLifespan; // Current lifespan

    this.settled = false; // Flag to indicate it has reached its target
    this.settleTime = 0; // Time when the particle settled
    this.dissolving = false; // Flag to indicate it's currently dissolving

    this.initialParticleSize = random(2, 6); // Size during explosion phase
    this.settledParticleSize = 2; // Size when forming the text
    this.currentSize = this.initialParticleSize; // Dynamic size
  }

  // Apply a force to the particle
  applyForce(force) {
    this.acc.add(force);
  }

  // Update the particle's position and move towards target
  update() {
    if (!this.settled) {
      // Add a small gravity effect
      this.applyForce(createVector(0, 0.05));

      // Calculate force towards target position
      let steer = p5.Vector.sub(this.targetPos, this.pos);
      steer.setMag(0.1); // Adjust steering force
      this.applyForce(steer);

      this.vel.add(this.acc);
      this.pos.add(this.vel);
      this.acc.mult(0); // Reset acceleration

      // Reduce lifespan for initial fading
      this.lifespan -= 2;
      // Map lifespan to current size during explosion phase
      this.currentSize = map(this.lifespan, 0, this.initialLifespan, 0.5, this.initialParticleSize);

      // Stop moving and settle if close enough to target
      if (this.pos.dist(this.targetPos) < 1) {
        this.pos.set(this.targetPos); // Snap to target
        this.vel.mult(0); // Stop movement
        this.acc.mult(0);
        this.settled = true;
        this.lifespan = this.initialLifespan; // Reset lifespan to full visibility for the text
        this.currentSize = this.settledParticleSize; // Fixed size for text particles
        this.settleTime = millis(); // Record the time it settled
      }
    } else if (this.settled && !this.dissolving) {
      // Particle is settled, check if it's time to dissolve
      if (millis() - this.settleTime > DISSOLVE_DELAY) {
        this.dissolving = true;
      }
    } else if (this.dissolving) {
      // Particle is dissolving
      this.lifespan -= 1; // Reduce lifespan to fade it out
      // Map lifespan to current size during dissolving phase
      this.currentSize = map(this.lifespan, 0, this.initialLifespan, 0, this.settledParticleSize);
    }
  }

  // Show the particle
  show() {
    noStroke(); // No stroke for a solid, fluffy look
    fill(this.color.levels[0], this.color.levels[1], this.color.levels[2], this.lifespan);
    circle(this.pos.x, this.pos.y, this.currentSize); // Draw as a circle
  }

  // Text particles are finished if they have dissolved completely
  finished() {
    return this.dissolving && this.lifespan < 0;
  }
}
Line-by-line explanation (30 lines)

🔧 Subcomponents:

conditional Explosion and Attraction Phase if (!this.settled) { this.applyForce(createVector(0, 0.05)); let steer = p5.Vector.sub(this.targetPos, this.pos); steer.setMag(0.1); this.applyForce(steer);

Applies gravity and steering forces so particles burst outward then gradually curve toward their text target positions

conditional Settling Detection if (this.pos.dist(this.targetPos) < 1) { this.pos.set(this.targetPos); this.vel.mult(0); this.acc.mult(0); this.settled = true;

Detects when a particle gets within 1 pixel of its target, then snaps it to place and freezes it as part of the text

conditional Dissolve Trigger else if (this.settled && !this.dissolving) { if (millis() - this.settleTime > DISSOLVE_DELAY) { this.dissolving = true; } }

Waits for DISSOLVE_DELAY milliseconds after settling, then flags the particle to begin fading out

conditional Dissolving Phase else if (this.dissolving) { this.lifespan -= 1; this.currentSize = map(this.lifespan, 0, this.initialLifespan, 0, this.settledParticleSize); }

Reduces lifespan and shrinks the particle until it disappears completely

this.vel = p5.Vector.random2D(); // Initial random explosion direction
Creates a velocity pointing in a random direction (north, northeast, east, etc.) with magnitude 1. This starts the particle moving outward in the explosion
this.vel.mult(random(2, 8)); // Initial explosion speed
Multiplies the velocity by a random number between 2 and 8, making each particle burst outward at a different speed
this.initialLifespan = 255; // Max lifespan
Stores the maximum lifespan (opacity) as 255. This is used later to map size and alpha during different phases
this.settled = false; // Flag to indicate it has reached its target
Boolean flag tracking the particle's phase: false during explosion/attraction, true once it reaches the text position
this.settleTime = 0; // Time when the particle settled
Will store the millisecond timestamp when the particle reaches its target, used to trigger dissolving after DISSOLVE_DELAY
this.initialParticleSize = random(2, 6); // Size during explosion phase
Each particle gets a random starting size. Larger starting sizes create a more varied explosion look
this.currentSize = this.initialParticleSize; // Dynamic size
Tracks the particle's current size, which changes dynamically during explosion, settling, and dissolving phases
if (!this.settled) {
Only update physics and position if the particle hasn't reached its target yet
this.applyForce(createVector(0, 0.05));
Applies a gentle downward gravity force, pulling particles down slightly as they move—makes the motion feel organic
let steer = p5.Vector.sub(this.targetPos, this.pos);
Calculates a vector pointing from the particle to its target position by subtracting current position from target
steer.setMag(0.1); // Adjust steering force
Normalizes the steering vector and scales it to magnitude 0.1, controlling how strongly particles are pulled toward their targets
this.applyForce(steer);
Applies the steering force, causing the particle to curve toward its text position as it's also pushed by gravity
this.lifespan -= 2;
Reduces lifespan by 2 each frame during the explosion phase, making particles fade as they move (before settling)
this.currentSize = map(this.lifespan, 0, this.initialLifespan, 0.5, this.initialParticleSize);
Maps the declining lifespan to a size range: as lifespan goes from 255 down to 0, size scales from initialParticleSize down to 0.5
if (this.pos.dist(this.targetPos) < 1) {
Checks if the particle is within 1 pixel of its target using the dist() method, indicating it has arrived
this.pos.set(this.targetPos); // Snap to target
Forces the particle to the exact target position, eliminating any floating-point drift
this.vel.mult(0); // Stop movement
Zeroes out velocity so the particle stops moving
this.settled = true;
Marks the particle as settled so the update loop skips physics on future frames and checks the dissolve timer instead
this.lifespan = this.initialLifespan; // Reset lifespan to full visibility for the text
Resets lifespan to 255 so the particle is fully opaque once it reaches the text, erasing the fading that happened during the explosion
this.currentSize = this.settledParticleSize; // Fixed size for text particles
Sets the particle to a small fixed size (2 pixels), making the text sharp and uniform
this.settleTime = millis(); // Record the time it settled
Captures the current time in milliseconds so the dissolve timer can measure how long the particle has been settled
} else if (this.settled && !this.dissolving) {
If the particle is settled but not yet dissolving, check the dissolve timer
if (millis() - this.settleTime > DISSOLVE_DELAY) {
Compares the elapsed time (now minus settleTime) to DISSOLVE_DELAY (60000 ms). If more than 60 seconds have passed, start dissolving
this.dissolving = true;
Flags the particle to enter dissolving mode on the next update
} else if (this.dissolving) {
If the particle is in dissolving mode, fade it out
this.lifespan -= 1; // Reduce lifespan to fade it out
Reduces lifespan slowly (by 1 each frame instead of 2) to create a slow, elegant fade
this.currentSize = map(this.lifespan, 0, this.initialLifespan, 0, this.settledParticleSize);
Maps declining lifespan to size: as lifespan goes from 255 to 0, size shrinks from settledParticleSize (2) to 0
fill(this.color.levels[0], this.color.levels[1], this.color.levels[2], this.lifespan);
Sets the fill color to the particle's color using its RGB channels and alpha set to lifespan (255 = opaque, 0 = transparent)
circle(this.pos.x, this.pos.y, this.currentSize); // Draw as a circle
Draws a circle at the particle's position with its current size, using the fill color with current opacity
return this.dissolving && this.lifespan < 0;
Returns true only if the particle is dissolving AND its lifespan has dropped below 0, meaning it's completely invisible and can be removed

📦 Key Variables

fireworks array

Stores all active FireworkRocket objects. Rockets are removed when they transform into explosion particles.

let fireworks = [];
explosionParticles array

Stores all active ExplosionParticle objects. Particles are removed when fully dissolved.

let explosionParticles = [];
textPoints array

Stores the (x, y) coordinates of every white pixel in the rendered text. Each point becomes a target for one particle.

let textPoints = [];
myFont p5.Font object

Holds the loaded Google Font (Roboto) that is used to render the text in the off-screen graphics buffer.

let myFont;
DISSOLVE_DELAY number

Constant defining how many milliseconds a settled particle waits before it begins dissolving (60000 ms = 60 seconds).

const DISSOLVE_DELAY = 60000;

🔧 Potential Improvements (6)

Here are some ways this code could be enhanced:

PERFORMANCE setup() - textPoints sampling

The sampling loop manually builds a `sampledPoints` array by splicing from `textPoints` repeatedly, which is O(n²) and slow for large arrays.

💡 Use Fisher-Yates shuffle or simply use `textPoints = textPoints.slice(0, desiredFireworkCount)` after shuffling, or push a random subset without manual removal.

BUG draw() - backwards looping

While backwards looping prevents skipped elements when splicing, it's easy for future maintainers to accidentally change to forward iteration and create bugs.

💡 Add a comment explaining why backwards looping is critical, or refactor to filter arrays using `.filter()` instead of `.splice()` during the loop.

STYLE ExplosionParticle.show()

Accessing color values via `.levels[0], .levels[1], .levels[2]` is fragile; if color format changes, the sketch breaks.

💡 Create a helper method or use p5.js color functions like `red()`, `green()`, `blue()` to extract channels in a more robust way.

FEATURE setup() - text rendering

If the canvas is resized, the text geometry is not regenerated, so new particles are never created and the animation ends prematurely.

💡 Refactor text point generation into a separate function and call it from both setup() and windowResized() to support full-screen responsive behavior.

BUG FireworkRocket.update()

Rockets that start at y = height might not explode correctly if their target is very high; the comparison `this.pos.y < this.targetPos.y` can miss edge cases when target equals current position.

💡 Check `dist(this.pos, this.targetPos) < 50` (or similar threshold) instead of only comparing y, to ensure rockets explode when close to their target in any direction.

PERFORMANCE setup() - graphics.loadPixels()

The nested loop samples pixels with a step of 2 in both dimensions, but uses `+=` which iterates 250,000+ pixels for a typical screen. For high-resolution displays, this is wasteful.

💡 Increase the sampling step to `+=4` or `+=8` for larger displays, or use `desiredFireworkCount` to calculate an adaptive step size that maintains roughly 2000 points regardless of canvas size.

🔄 Code Flow

Code flow showing preload, setup, draw, windowresized, fireworkrocket, explosionparticle

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

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> pixelloop[pixel-sampling-loop] pixelloop --> performance[performance-sampling] performance --> rocketloop[rocket-creation-loop] rocketloop --> rocketupdate[rocket-update-loop] rocketupdate --> physics[physics-update] physics --> peak[peak-detection] peak -->|if peaked| explosion[explosion-phase] explosion --> settling[settling-check] settling -->|if settled| dissolve[dissolve-timer] dissolve --> dissolving[dissolving-phase] rocketupdate --> draw draw --> explosionupdate[explosion-update-loop] explosionupdate --> explosionphysics[explosion-phase] explosionphysics --> draw click setup href "#fn-setup" click draw href "#fn-draw" click pixelloop href "#sub-pixel-sampling-loop" click performance href "#sub-performance-sampling" click rocketloop href "#sub-rocket-creation-loop" click rocketupdate href "#sub-rocket-update-loop" click physics href "#sub-physics-update" click peak href "#sub-peak-detection" click explosion href "#sub-explosion-phase" click settling href "#sub-settling-check" click dissolve href "#sub-dissolve-timer" click dissolving href "#sub-dissolving-phase" click explosionupdate href "#sub-explosion-update-loop"

❓ Frequently Asked Questions

What visual effects are featured in the AMERICAAAAAAAAA sketch?

The sketch creates a stunning display of colorful fireworks that burst into glowing particles, which then gather to form the text 'AMERICA 250' against a dark night-sky background.

Is there any user interaction available in this creative coding sketch?

The current version of the sketch does not include user interaction; it automatically displays the fireworks and text without requiring user input.

What creative coding techniques are highlighted in the AMERICAAAAAAAAA sketch?

This sketch demonstrates techniques such as particle systems for fireworks and off-screen graphics buffers to create dynamic text effects.

Preview

AMERICAAAAAAAAA - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of AMERICAAAAAAAAA - Code flow showing preload, setup, draw, windowresized, fireworkrocket, explosionparticle
Code Flow Diagram