p5js.ai is super cool

This sketch displays a spinning 3D cube in WEBGL mode with glowing text wrapped around all six faces. The text "p5js.ai is super cool" shimmers with soft yellow-orange neon light created by layered glow effects, while the cube continuously rotates on three axes. An epilepsy warning overlay appears on load and must be dismissed before the animation begins.

🧪 Try This!

Experiment with the code by making these changes:

  1. Change the spinning cube text — The textString variable holds the words displayed on every face. Change it to your own message to see it spin around the cube.
  2. Make the glow colors cyan and purple — The two color() calls define the glow gradient. Changing RGB values swaps the hue while keeping the glowing effect intact.
  3. Speed up the spinning dramatically — Multiplying each rotation coefficient by 2 doubles the spin speed on all three axes, creating dizzier motion.
  4. Dim the glow to near-darkness — Reducing the background value from 0 to nearly black shows the glow more dramatically; changing to a higher number like 100 makes it blend more into the background.
  5. Make the text enormous — Increasing textSize from 32 to 64 doubles the size of the glowing text on every face and proportionally expands the glow halo.
  6. Add extra glow layers for smoothness — Increasing numGlowLayers from 10 to 25 adds more semi-transparent text layers, creating a silkier, more refined glow gradient.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch creates a stunning 3D neon cube that spins continuously in space, with text glowing on all six faces. What makes it visually striking is the layered glow effect—multiple semi-transparent text layers are drawn with offsets and ADD blend mode to create a soft, diffused luminescence around the crisp text. The sketch demonstrates advanced p5.js techniques including WEBGL 3D rendering, push/pop transformation stacks, color interpolation with lerpColor, and blend mode manipulation to achieve the neon aesthetic.

The code is organized into a preload() function that loads a custom font, a setup() function that initializes the WEBGL canvas and constructs an accessibility warning overlay, and a draw() function that rotates the scene and renders glowing text on each cube face. By studying it, you will learn how to position and orient 3D objects, layer effects for visual depth, and create interactive warnings that pause animation until user interaction.

⚙️ How It Works

  1. When the sketch loads, preload() fetches a custom Roboto font from a CDN, and setup() creates a full-window WEBGL canvas with glow colors (yellow to orange) and a semi-transparent dark overlay with an epilepsy warning. The animation loop is paused with noLoop() until the user dismisses the warning.
  2. When the user clicks 'I Understand, Continue', dismissWarning() hides the overlay and calls loop() to start the draw loop running.
  3. On every frame, draw() clears the background to black, adds ambient and directional lighting for subtle shading, then rotates the entire scene on all three axes using rotateX(), rotateY(), and rotateZ() with frameCount to create continuous spinning.
  4. drawTextOnFaces() defines six cube faces, each with a position and rotation vector. For each face, the code uses push() to save the transformation state, then applies that face's translation and rotation.
  5. For each face, a loop draws 10 layers of the text with decreasing spread and varying alpha, using ADD blend mode so the semi-transparent colors accumulate into a glowing halo effect. The glow colors are interpolated from yellow to orange using lerpColor().
  6. Finally, the main crisp text is drawn on top of the glow layers in NORMAL blend mode, then pop() restores the transformation state and the next face is processed. The windowResized() function keeps the canvas sized to the full viewport.

🎓 Concepts You'll Learn

WEBGL 3D renderingTransformation stacks (push/pop)Rotation matricesBlend modes (ADD)Color interpolation (lerpColor)Glow and diffusion effectsEvent handling and UI overlaysCustom fonts in WEBGL

📝 Code Breakdown

preload()

preload() is a special p5.js function that runs before setup(). It's the correct place to load assets (images, fonts, sounds) that must be ready before your sketch draws. WEBGL mode requires fonts to be loaded from URLs; system fonts won't render in 3D.

function preload() {
  // Load a font for WEBGL text. Using Fontsource Roboto.
  // https://fontsource.org/fonts/roboto
  // This URL is reliable and works well with p5.js loadFont() in WEBGL mode.
  myFont = loadFont('https://cdn.jsdelivr.net/fontsource/fonts/roboto@latest/latin-400-normal.woff');
}
Line-by-line explanation (1 lines)

🔧 Subcomponents:

calculation Font Loading from CDN myFont = loadFont('https://cdn.jsdelivr.net/fontsource/fonts/roboto@latest/latin-400-normal.woff');

Fetches a WOFF font file from a reliable CDN before setup runs, ensuring the font is ready for WEBGL text rendering

myFont = loadFont('https://cdn.jsdelivr.net/fontsource/fonts/roboto@latest/latin-400-normal.woff');
loadFont() loads a font file from a URL asynchronously. preload() is the only place in p5.js where this blocking behavior is safe. The font is stored in myFont and used in setup() with textFont().

setup()

setup() runs once when the sketch starts. It's where you configure the canvas, initialize variables, and set up UI elements. Using noLoop() here is a common accessibility pattern—you can pause animation until a user confirms they understand the risks.

🔬 These three lines define the color palette. What happens if you swap the RGB values of glowColorStart and glowColorEnd to see a different glow gradient? Try glowColorStart = color(255, 100, 255, 100) and glowColorEnd = color(100, 255, 255, 50)—do you get a magenta-to-cyan shift?

  glowColorStart = color(255, 255, 100, 100); 
  glowColorEnd = color(255, 150, 0, 50);   
  textColor = color(255, 255, 200);
function setup() {
  // Create a WEBGL canvas.
  createCanvas(windowWidth, windowHeight, WEBGL);
  
  // Set the loaded font, size, and alignment for text.
  textFont(myFont);
  textSize(32); // Adjust text size as needed for the cube.
  textAlign(CENTER, CENTER);
  
  // Define colors for the glow and the main text.
  glowColorStart = color(255, 255, 100, 100); 
  glowColorEnd = color(255, 150, 0, 50);   
  textColor = color(255, 255, 200);        

  // Initially stop the drawing loop until the warning is dismissed
  noLoop();

  // Create the warning overlay
  warningDiv = createDiv();
  warningDiv.style('position', 'absolute');
  warningDiv.style('top', '0');
  warningDiv.style('left', '0');
  warningDiv.style('width', '100%');
  warningDiv.style('height', '100%');
  warningDiv.style('background-color', 'rgba(0, 0, 0, 0.9)'); // Dark, semi-transparent background
  warningDiv.style('color', 'white');
  warningDiv.style('display', 'flex');
  warningDiv.style('flex-direction', 'column');
  warningDiv.style('justify-content', 'center');
  warningDiv.style('align-items', 'center');
  warningDiv.style('z-index', '1000'); // Ensure it's on top of the canvas
  warningDiv.style('font-family', myFont.font.names.fontFamily.en || 'sans-serif'); // Use loaded font or fallback
  warningDiv.style('font-size', '24px');
  warningDiv.style('text-align', 'center');
  warningDiv.html(`
    <p style="margin-bottom: 20px;">
      <b>EPILEPSY WARNING:</b> This sketch contains rapidly rotating 3D objects and glowing text effects,<br>
      which may potentially trigger seizures for people with photosensitive epilepsy.<br>
      Viewer discretion is advised.
    </p>
  `);

  // Create the continue button
  continueButton = createButton('I Understand, Continue');
  continueButton.style('font-size', '18px');
  continueButton.style('padding', '10px 20px');
  continueButton.style('margin-top', '20px');
  continueButton.style('background-color', 'red');
  continueButton.style('color', 'white');
  continueButton.style('border', 'none');
  continueButton.style('border-radius', '5px');
  continueButton.style('cursor', 'pointer');
  continueButton.style('font-family', myFont.font.names.fontFamily.en || 'sans-serif'); // Use loaded font or fallback
  continueButton.parent(warningDiv); // Add button to the warning div

  // Attach event listeners to the button
  continueButton.mousePressed(dismissWarning);
  continueButton.touchStarted(dismissWarning);
}
Line-by-line explanation (14 lines)

🔧 Subcomponents:

calculation WEBGL Canvas Creation createCanvas(windowWidth, windowHeight, WEBGL);

Creates a full-window canvas in WEBGL mode, enabling 3D rendering with transforms, lights, and depth

calculation Text Rendering Configuration textFont(myFont); textSize(32); textAlign(CENTER, CENTER);

Sets the font, size, and centering for all subsequent text() calls

calculation Glow and Text Colors glowColorStart = color(255, 255, 100, 100); glowColorEnd = color(255, 150, 0, 50); textColor = color(255, 255, 200);

Defines the yellow-to-orange color gradient for the glow effect and the bright yellow for main text

calculation Epilepsy Warning Overlay warningDiv = createDiv(); warningDiv.style('position', 'absolute'); // ... additional styling and HTML content

Constructs an accessible, full-screen warning overlay with flexbox centering and semantic HTML

createCanvas(windowWidth, windowHeight, WEBGL);
Creates a canvas spanning the full window in WEBGL mode. WEBGL enables 3D transforms, lighting, and advanced rendering—essential for the spinning cube effect.
textFont(myFont);
Applies the loaded Roboto font to all text() calls. In WEBGL mode, custom fonts must be pre-loaded from URLs.
textSize(32);
Sets the size of text drawn with text() to 32 pixels. This size is used in the glow calculation to scale the spread effect proportionally.
textAlign(CENTER, CENTER);
Centers text both horizontally and vertically around the coordinates passed to text(). Without this, text would be positioned from its top-left corner.
glowColorStart = color(255, 255, 100, 100);
Creates a yellow color with RGB(255, 255, 100) and alpha 100. This is the outer glow color that will be interpolated in the draw loop.
glowColorEnd = color(255, 150, 0, 50);
Creates an orange color with RGB(255, 150, 0) and alpha 50. This is blended with glowColorStart to create the gradient glow.
textColor = color(255, 255, 200);
Creates a bright pale yellow for the main (non-glowing) text layer. This appears sharp and clear on top of the soft glow.
noLoop();
Stops the draw loop from running automatically. The loop only starts after dismissWarning() is called, allowing time for the user to read and accept the epilepsy warning.
warningDiv = createDiv();
Creates an HTML div element that will hold the warning message and button. This is rendered outside the canvas, as an overlay.
warningDiv.style('position', 'absolute');
Positions the warning absolutely, so it can float over the canvas without pushing other page content.
warningDiv.style('width', '100%'); warningDiv.style('height', '100%');
Makes the warning overlay span the full window, ensuring it covers the entire canvas and demands user attention.
warningDiv.style('display', 'flex'); warningDiv.style('flex-direction', 'column'); warningDiv.style('justify-content', 'center'); warningDiv.style('align-items', 'center');
Uses flexbox CSS to center the warning text and button both horizontally and vertically within the overlay.
continueButton = createButton('I Understand, Continue');
Creates an HTML button element with the label 'I Understand, Continue'. This button will trigger dismissWarning() when clicked.
continueButton.mousePressed(dismissWarning); continueButton.touchStarted(dismissWarning);
Attaches the dismissWarning() function to both mouse and touch events, ensuring the warning works on desktop and mobile devices.

dismissWarning()

This callback function demonstrates event-driven programming in p5.js. It's triggered by user interaction (button click or touch) and changes the application state by hiding the overlay and starting the animation. Removing listeners afterward is a best practice to avoid memory leaks and unintended repeated calls.

function dismissWarning() {
  warningDiv.hide(); // Hide the warning overlay
  loop();            // Start the p5.js drawing loop
  // Remove event listeners from the button to prevent multiple calls
  continueButton.mousePressed(null);
  continueButton.touchStarted(null);
  return false; // Prevent default touch behavior (scrolling/zooming)
}
Line-by-line explanation (5 lines)

🔧 Subcomponents:

calculation Hide Warning Overlay warningDiv.hide();

Hides the warning div from view, revealing the canvas and animation beneath

calculation Resume Drawing Loop loop();

Resumes the draw() function, which was paused by noLoop() in setup()

calculation Cleanup Event Listeners continueButton.mousePressed(null); continueButton.touchStarted(null);

Detaches the event listeners to prevent dismissWarning() from being called multiple times

warningDiv.hide();
The hide() method hides an HTML element via CSS (display: none). The warning overlay disappears, and the canvas is now visible.
loop();
loop() resumes the draw() function, which had been paused by noLoop() in setup(). The cube animation now begins.
continueButton.mousePressed(null);
Passing null removes the dismissWarning callback from the mousePressed event. If the user clicks the button again, nothing happens.
continueButton.touchStarted(null);
Removes the dismissWarning callback from the touchStarted event, preventing accidental re-triggering on mobile devices.
return false;
Returning false from a touch event handler prevents default browser behavior like scrolling or zooming, keeping the user focused on the sketch.

draw()

draw() runs 60 times per second (default frameRate), making it the heart of animation. By applying rotations *before* calling drawTextOnFaces(), all the text is rotated together as a group. This is why push() and pop() inside drawTextOnFaces() are crucial—they save and restore the group transformation so each face can be positioned independently.

🔬 These three lines control how the cube spins. What happens if you change one or more of these coefficients? For example, try making rotateX(frameCount * 0.02) to double the X-axis spin speed. Or set rotateZ(frameCount * 0) to stop the Z rotation entirely—does the cube look less chaotic?

  rotateX(frameCount * 0.01);
  rotateY(frameCount * 0.005);
  rotateZ(frameCount * 0.007);

🔬 These set up 3D lighting. What if you increase the ambientLight to 200? Or change the directionalLight direction from (0, 0, -1) to (1, 1, -1)? Can you predict how the shading on the text will change based on the light direction?

  ambientLight(100);
  directionalLight(255, 255, 255, 0, 0, -1);
function draw() {
  background(0); // Dark background for the glowing effect.
  noStroke();    // No strokes for shapes.

  // Add some ambient and directional light for subtle shading on the cube (if drawn).
  ambientLight(100);
  directionalLight(255, 255, 255, 0, 0, -1); // Light coming from behind the camera.

  // Rotate the entire scene to make the cube spin.
  rotateX(frameCount * 0.01);
  rotateY(frameCount * 0.005);
  rotateZ(frameCount * 0.007);

  // Draw the text on all faces of the cube.
  drawTextOnFaces();
}
Line-by-line explanation (8 lines)

🔧 Subcomponents:

calculation Canvas Clearing background(0);

Fills the entire canvas with black, erasing the previous frame and providing contrast for the glowing text

calculation 3D Lighting Configuration ambientLight(100); directionalLight(255, 255, 255, 0, 0, -1);

Sets up ambient (overall) and directional (from a specific direction) lighting to add subtle depth and shading to 3D objects

calculation Continuous Rotation rotateX(frameCount * 0.01); rotateY(frameCount * 0.005); rotateZ(frameCount * 0.007);

Rotates the entire scene on three axes simultaneously, with different speeds to create a smooth, dynamic spinning effect

background(0);
Clears the canvas with black (RGB 0, 0, 0) at the start of every frame. Without this, previous frames would be visible, creating motion trails instead of clean animation.
noStroke();
Disables outline strokes on all shapes drawn after this line. Text and shapes will have no borders, only fill colors.
ambientLight(100);
Sets up ambient lighting with brightness 100. Ambient light illuminates all surfaces equally from all directions, providing a baseline of visibility for 3D objects.
directionalLight(255, 255, 255, 0, 0, -1);
Adds directional light with white color (255, 255, 255) pointing in direction (0, 0, -1)—toward the camera from behind the scene. This creates subtle shading and depth cues on 3D surfaces.
rotateX(frameCount * 0.01);
Rotates the entire scene around the X-axis (left-right) by an angle proportional to frameCount. Since frameCount increments each frame, the rotation increases continuously, creating animation.
rotateY(frameCount * 0.005);
Rotates the scene around the Y-axis (up-down). The coefficient 0.005 is smaller than the X coefficient, so this rotation is slower, creating complex multi-axis spinning.
rotateZ(frameCount * 0.007);
Rotates the scene around the Z-axis (toward/away from camera). All three rotations compound, making the cube spin in a smooth, three-dimensional tumble.
drawTextOnFaces();
Calls the helper function that does the heavy lifting—positioning text on each of the six cube faces and drawing the glow effect layers.

drawTextOnFaces()

drawTextOnFaces() is the core of the visual effect. It demonstrates several key p5.js 3D techniques: (1) using push/pop to manage transformation stacks, (2) looping through object arrays, (3) using lerpColor() for smooth color gradients, (4) using map() to rescale values, (5) using blendMode() for visual effects, and (6) layering multiple semi-transparent renders to create complex effects. This is a pattern you'll use in many creative sketches—build up a visual effect by layering simpler, semi-transparent elements.

🔬 These four text() calls create the diffusion by drawing text in four diagonal directions. What if you add more offsets—say, also text(textString, spread, 0) and text(textString, 0, spread) to fill in the cardinal directions? Would the glow look rounder or more distributed?

      text(textString, spread, spread);
      text(textString, -spread, -spread);
      text(textString, spread, -spread);
      text(textString, -spread, spread);
function drawTextOnFaces() {
  // Reset font size (in case it was changed elsewhere, though not in this sketch).
  let fontSize = 32;
  textSize(fontSize);
  
  // Define how much the glow should spread relative to the font size.
  let glowSpreadFactor = fontSize * 0.1; 
  // Number of layers to draw for the glow, more layers create a smoother glow.
  let numGlowLayers = 10; 

  // Define the position and rotation for each of the 6 cube faces.
  // The translations include +1 or -1 to slightly offset the text
  // from the cube's surface, preventing z-fighting artifacts.
  let faces = [
    { pos: createVector(0, 0, cubeSize / 2 + 1), rot: createVector(0, 0, 0) },         // Front face
    { pos: createVector(0, 0, -cubeSize / 2 - 1), rot: createVector(0, PI, 0) },       // Back face (rotated 180 degrees around Y)
    { pos: createVector(cubeSize / 2 + 1, 0, 0), rot: createVector(0, HALF_PI, 0) },   // Right face (rotated 90 degrees around Y)
    { pos: createVector(-cubeSize / 2 - 1, 0, 0), rot: createVector(0, -HALF_PI, 0) }, // Left face (rotated -90 degrees around Y)
    { pos: createVector(0, -cubeSize / 2 - 1, 0), rot: createVector(HALF_PI, 0, 0) },  // Top face (rotated 90 degrees around X)
    { pos: createVector(0, cubeSize / 2 + 1, 0), rot: createVector(-HALF_PI, 0, 0) }   // Bottom face (rotated -90 degrees around X)
  ];

  for (let face of faces) {
    // Save the current transformation state.
    push();
    
    // Apply translation and rotation for the current face.
    translate(face.pos.x, face.pos.y, face.pos.z);
    rotateX(face.rot.x);
    rotateY(face.rot.y);
    rotateZ(face.rot.z);

    // Draw multiple layers for the glowing effect.
    // We iterate from numGlowLayers down to 1 to draw the inner glow layers last,
    // making them appear on top of outer, more transparent layers.
    for (let i = numGlowLayers; i > 0; i--) {
      // Lerp (linearly interpolate) between the start and end glow colors.
      let lerpedGlowColor = lerpColor(glowColorStart, glowColorEnd, i / numGlowLayers);
      // Map the alpha value for each layer, making inner layers more opaque.
      let alpha = map(i, 1, numGlowLayers, 10, glowColorStart.levels[3]);
      // Map the spread for each layer, creating a diffused effect.
      let spread = map(i, 1, numGlowLayers, 0, glowSpreadFactor);
      
      // Use ADD blend mode to make colors add up, creating a glowing effect.
      blendMode(ADD);
      // Set the fill color with the lerped glow color and calculated alpha.
      fill(lerpedGlowColor.levels[0], lerpedGlowColor.levels[1], lerpedGlowColor.levels[2], alpha);
      
      // Draw the text multiple times with slight offsets for diffusion.
      // This creates a softer, more distributed glow.
      text(textString, spread, spread);
      text(textString, -spread, -spread);
      text(textString, spread, -spread);
      text(textString, -spread, spread);
    }
    
    // Draw the main crisp text on top of the glow layers.
    // Reset blend mode to NORMAL to draw the text clearly.
    blendMode(NORMAL);
    fill(textColor);
    text(textString, 0, 0); // Text is centered on the current face (0,0).

    // Restore the previous transformation state.
    pop();
  }
}
Line-by-line explanation (26 lines)

🔧 Subcomponents:

calculation Font Size Configuration let fontSize = 32; textSize(fontSize); let glowSpreadFactor = fontSize * 0.1;

Sets up font size and proportionally scales the glow spread factor so glow thickness adapts to text size

calculation Cube Face Definitions let faces = [ { pos: createVector(0, 0, cubeSize / 2 + 1), rot: createVector(0, 0, 0) }, // ... 5 more faces ];

Defines the 3D position and rotation of all six cube faces as an array of objects, each with pos (position) and rot (rotation) vectors

for-loop Face Rendering Loop for (let face of faces) { ... }

Iterates through each of the six faces, applying transformations and drawing glow layers for each one

conditional Transformation Stack Management push(); // ... transformations pop();

Saves and restores the transformation matrix so each face's rotations don't affect subsequent faces

for-loop Glow Layer Rendering for (let i = numGlowLayers; i > 0; i--) { ... }

Draws multiple semi-transparent text layers with varying opacity and offsets to create a soft, diffused glow halo

calculation Color Interpolation let lerpedGlowColor = lerpColor(glowColorStart, glowColorEnd, i / numGlowLayers);

Smoothly blends between yellow and orange across glow layers, creating a gradient effect

calculation Main Text Rendering blendMode(NORMAL); fill(textColor); text(textString, 0, 0);

Draws the crisp, bright text on top of the glow layers in normal blend mode

let fontSize = 32;
Stores the font size in a local variable so it can be reused in the glow spread calculation.
textSize(fontSize);
Applies the font size to all text() calls within this function.
let glowSpreadFactor = fontSize * 0.1;
Calculates the maximum glow spread as 10% of the font size. Larger text gets a proportionally larger glow.
let numGlowLayers = 10;
Sets the number of semi-transparent glow layers. More layers create a smoother gradient; fewer layers make it more discrete.
let faces = [
Initializes an array that will hold six objects, each describing a cube face's position and rotation.
{ pos: createVector(0, 0, cubeSize / 2 + 1), rot: createVector(0, 0, 0) },
Front face: positioned at cubeSize/2 + 1 on the Z-axis (forward from origin) with no rotation. The +1 offset prevents z-fighting (flickering when two surfaces overlap at the same depth).
{ pos: createVector(0, 0, -cubeSize / 2 - 1), rot: createVector(0, PI, 0) },
Back face: positioned at -cubeSize/2 - 1 on the Z-axis (backward) and rotated 180 degrees around the Y-axis so text faces outward.
{ pos: createVector(cubeSize / 2 + 1, 0, 0), rot: createVector(0, HALF_PI, 0) },
Right face: positioned at cubeSize/2 + 1 on the X-axis (right) and rotated 90 degrees around Y-axis to face outward.
{ pos: createVector(-cubeSize / 2 - 1, 0, 0), rot: createVector(0, -HALF_PI, 0) },
Left face: positioned at -cubeSize/2 - 1 on the X-axis (left) and rotated -90 degrees around Y-axis.
{ pos: createVector(0, -cubeSize / 2 - 1, 0), rot: createVector(HALF_PI, 0, 0) },
Top face: positioned at -cubeSize/2 - 1 on the Y-axis (upward) and rotated 90 degrees around X-axis to face outward.
{ pos: createVector(0, cubeSize / 2 + 1, 0), rot: createVector(-HALF_PI, 0, 0) }
Bottom face: positioned at cubeSize/2 + 1 on the Y-axis (downward) and rotated -90 degrees around X-axis.
for (let face of faces) {
Loops through each face object. On each iteration, 'face' holds one face's pos and rot vectors.
push();
Saves the current transformation matrix (position, rotation, scale). This ensures that transformations applied to one face don't affect the next.
translate(face.pos.x, face.pos.y, face.pos.z);
Moves the origin to this face's position. All subsequent drawing happens relative to this new origin.
rotateX(face.rot.x); rotateY(face.rot.y); rotateZ(face.rot.z);
Rotates the coordinate system around each axis by the amounts stored in the face's rotation vector. This orients the text to face outward on the cube.
for (let i = numGlowLayers; i > 0; i--) {
Counts down from numGlowLayers (10) to 1. The loop draws outer, more transparent layers first, then inner, more opaque layers on top.
let lerpedGlowColor = lerpColor(glowColorStart, glowColorEnd, i / numGlowLayers);
lerpColor() interpolates between two colors. When i=10, the result is close to glowColorStart (yellow). When i=1, it's close to glowColorEnd (orange). This creates a smooth color gradient across layers.
let alpha = map(i, 1, numGlowLayers, 10, glowColorStart.levels[3]);
map() rescales i from the range [1, numGlowLayers] to [10, glowColorStart.levels[3]] (the original alpha of yellow, usually 100). Outer layers (low i) are transparent; inner layers are more opaque.
let spread = map(i, 1, numGlowLayers, 0, glowSpreadFactor);
Maps i to a spread distance from 0 to glowSpreadFactor. Outer layers have large spread (drawn far from center); inner layers have small spread, converging toward the text center.
blendMode(ADD);
Sets blend mode to ADD, which adds color values instead of replacing them. Semi-transparent yellow and orange layers stacked with ADD create a bright, glowing effect.
fill(lerpedGlowColor.levels[0], lerpedGlowColor.levels[1], lerpedGlowColor.levels[2], alpha);
Extracts the R, G, B components from the interpolated glow color and combines them with the calculated alpha. This fill color is applied to the next text() calls.
text(textString, spread, spread); text(textString, -spread, -spread); text(textString, spread, -spread); text(textString, -spread, spread);
Draws the text string four times with small offsets: (+spread, +spread), (-spread, -spread), (+spread, -spread), (-spread, +spread). These offsets in the four diagonal directions create a diffused, halo-like glow when layered.
blendMode(NORMAL);
Switches back to normal blend mode (replace) so the crisp main text is drawn clearly without blending.
fill(textColor);
Sets the fill color to the bright pale yellow defined in setup(), ready for the main text.
text(textString, 0, 0);
Draws the text at the face's origin (0, 0) with no offset. This appears sharp and bright on top of the soft glow layers.
pop();
Restores the transformation matrix saved by push(), undoing all translations and rotations. The next face will start with a clean slate.

windowResized()

windowResized() is a special p5.js function that p5.js calls automatically whenever the browser window is resized. Without it, the canvas would stay its original size and look broken if the user resizes their window. It's best practice to include this in any sketch that should be responsive.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  // Re-center the warning div if it's visible, though it's already full-screen
  // No specific re-positioning needed for a full-screen flex container.
}
Line-by-line explanation (1 lines)

🔧 Subcomponents:

calculation Canvas Resize resizeCanvas(windowWidth, windowHeight);

Adjusts the canvas dimensions to match the new window size when the browser is resized

resizeCanvas(windowWidth, windowHeight);
resizeCanvas() changes the canvas width and height. windowWidth and windowHeight are p5.js variables that always hold the current browser window dimensions. Calling this in windowResized() keeps the canvas responsive when the user resizes their browser.

📦 Key Variables

myFont p5.Font

Stores the loaded Roboto font object used for all text rendering in WEBGL mode

let myFont;
// ... loaded in preload()
cubeSize number

Controls the distance of each cube face from the center (250 pixels). Larger values make the cube appear bigger.

let cubeSize = 250;
glowColorStart p5.Color

The outer glow color (yellow with alpha 100), used as the starting point for color interpolation in the glow layers

let glowColorStart;
// ... initialized to color(255, 255, 100, 100) in setup()
glowColorEnd p5.Color

The inner glow color (orange with alpha 50), used as the ending point for color interpolation

let glowColorEnd;
// ... initialized to color(255, 150, 0, 50) in setup()
textColor p5.Color

The color of the main (non-glowing) text layer, a bright pale yellow that stands out sharply

let textColor;
// ... initialized to color(255, 255, 200) in setup()
textString string

The text displayed on each cube face

let textString = "p5js.ai is super cool";
warningDiv p5.Renderer (HTML div)

Stores the HTML div element that contains the epilepsy warning overlay

let warningDiv;
// ... created in setup()
continueButton p5.Renderer (HTML button)

Stores the HTML button element used to dismiss the warning and start the animation

let continueButton;
// ... created in setup()

🔧 Potential Improvements (5)

Here are some ways this code could be enhanced:

PERFORMANCE drawTextOnFaces() - glow layer loop

Drawing text 40+ times per frame (10 layers × 4 offsets per layer) can be expensive on low-end devices. With 25 layers, that's 100+ text renders per face × 6 faces = 600+ text operations per frame.

💡 Consider caching glow layers to an off-screen graphics buffer using createGraphics() and rendering once, then displaying the cached image on each frame. This trades memory for CPU time.

STYLE drawTextOnFaces() - faces array

The faces array uses inline position and rotation vectors, making it hard to modify or reuse. Comments explain each face but the pattern could be more systematic.

💡 Consider factoring the faces array into named constants (e.g., FACE_FRONT, FACE_BACK, etc.) or a helper function that generates the array programmatically based on cubeSize.

BUG setup() - warning overlay on mobile

The warning overlay uses z-index: 1000, but if other page elements (e.g., browser toolbars, address bars) exist, they might layover the warning on some mobile browsers. Additionally, the button styling is basic and may not meet WCAG accessibility standards.

💡 Add focus-visible styles to the button and ensure sufficient color contrast (text: white on red is usually fine, but test with a contrast checker). Consider making the warning modal and adding keyboard navigation support.

FEATURE preload()

Font loading from a CDN is reliable but adds a network dependency. If the CDN is slow or down, the sketch hangs during preload.

💡 Add a fallback: wrap loadFont() in a try/catch and provide a fallback system font, or pre-warn the user that custom fonts are being loaded.

STYLE draw()

Hard-coded lighting values (ambientLight(100), directionalLight with fixed direction) limit customization. Users can't easily change the lighting mood.

💡 Move ambientLight and directionalLight parameters to global variables so they can be tuned interactively (e.g., sliders for light brightness and direction).

🔄 Code Flow

Code flow showing preload, setup, dismisswarning, draw, drawtextonffaces, windowresized

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

graph TD start[Start] --> setup[setup] setup --> preload[preload] preload --> canvas-setup[canvas-setup] canvas-setup --> text-config[text-config] text-config --> color-init[color-init] setup --> warning-overlay[warning-overlay] warning-overlay --> hide-overlay[hide-overlay] hide-overlay --> start-loop[start-loop] start-loop --> draw[draw loop] click setup href "#fn-setup" click preload href "#fn-preload" click canvas-setup href "#sub-canvas-setup" click text-config href "#sub-text-config" click color-init href "#sub-color-init" click warning-overlay href "#sub-warning-overlay" click hide-overlay href "#sub-hide-overlay" click start-loop href "#sub-start-loop" draw --> clear-canvas[clear-canvas] clear-canvas --> lighting-setup[lighting-setup] lighting-setup --> scene-rotation[scene-rotation] scene-rotation --> drawtextonffaces[drawTextOnFaces] drawtextonffaces --> faces-array[faces-array] faces-array --> face-loop[face-loop] face-loop --> push-pop[push-pop] push-pop --> glow-layer-loop[glow-layer-loop] glow-layer-loop --> color-lerp[color-lerp] glow-layer-loop --> main-text[main-text] main-text --> face-loop click draw href "#fn-draw" click clear-canvas href "#sub-clear-canvas" click lighting-setup href "#sub-lighting-setup" click scene-rotation href "#sub-scene-rotation" click drawtextonffaces href "#fn-drawtextonffaces" click faces-array href "#sub-faces-array" click face-loop href "#sub-face-loop" click push-pop href "#sub-push-pop" click glow-layer-loop href "#sub-glow-layer-loop" click color-lerp href "#sub-color-lerp" click main-text href "#sub-main-text" windowresized --> canvas-resize[canvas-resize] click windowresized href "#fn-windowresized" click canvas-resize href "#sub-canvas-resize"

❓ Frequently Asked Questions

What visual experience does the p5.js sketch 'p5js.ai is super cool' provide?

The sketch features a glowing 3D cube that spins in space, with shimmering faces displaying the phrase 'p5js.ai is super cool' wrapped around it.

How can users interact with the 'p5js.ai is super cool' sketch?

Users can dismiss an epilepsy warning overlay to start the continuous rotation of the glowing cube.

What creative coding concept is showcased in the 'p5js.ai is super cool' sketch?

The sketch demonstrates the use of 3D rendering and dynamic text display in a WEBGL environment using p5.js.

Preview

p5js.ai is super cool - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of p5js.ai is super cool - Code flow showing preload, setup, dismisswarning, draw, drawtextonffaces, windowresized
Code Flow Diagram