AI Emotion Particles - xelsed.ai

This sketch fills the screen with 200 Perlin-noise-driven particles whose color, speed, size, and gravity are controlled by an emotion word - happy, sad, angry, calm, or excited. As you type into a text box, the sketch sends your words to the OpenAI API, which returns a single emotion, and every particle on screen instantly adopts that emotion's visual personality.

🧪 Try This!

Experiment with the code by making these changes:

  1. Supersize the particle swarm — Increasing the loop count in setup() fills the canvas with far more particles, making the effect feel denser and busier.
  2. Stretch out the fading trails — Lowering the background alpha value makes each frame clear more slowly, leaving longer glowing streaks behind every particle.
  3. Recolor the happy emotion — Swapping the hex color for the happy state changes what color the particles turn whenever happiness is detected.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch turns raw text input into a living particle swarm: whatever you type is analyzed by the OpenAI chat completions API for its emotional tone, and the result - happy, sad, angry, calm, or excited - instantly reshapes 200 particles drawn with p5.js's circle() function. Each emotion maps to its own color, speed, particle size, lifespan, and gravity, and the particles themselves drift using Perlin noise (the noise() function) layered with frameCount so their motion feels organic rather than random. A semi-transparent background() call every frame leaves soft fading trails behind the particles, giving the whole scene a glowing, smoke-like quality.

Structurally, the code combines a classic p5.js particle system (a Particle class with constructor, update, and display methods) with a DOM text input created via createInput(), and an async function that talks to a real web API using fetch(). Studying this sketch teaches you how to build object-oriented particles driven by Perlin noise, how to bridge external asynchronous data (an API response) into a running animation loop, and how to swap an entire visual 'mood' by mutating one shared configuration object that every particle reads from on every frame.

⚙️ How It Works

  1. When the page loads, setup() creates a full-window canvas, decrypts a stored API key, builds the text input and emotion-label DOM elements, and spawns 200 Particle objects using the default 'calm' configuration.
  2. Every frame, draw() paints a nearly-transparent black rectangle over the whole canvas (instead of fully clearing it) so older particle positions fade out slowly, creating soft motion trails, then loops through every particle calling update() and display().
  3. Each particle's update() method pulls the current emotion's settings from emotionConfigs every frame, applies gravity, nudges its velocity using layered Perlin noise so it drifts rather than jitters randomly, applies friction, moves, and respawns itself at a random position if it drifts off-screen.
  4. As soon as you type into the input box, the input event fires analyzeEmotion(), which sends your text to OpenAI's gpt-4o-mini model and asks it to reply with a single emotion word.
  5. When the API responds, the returned word is validated against the five known emotions and stored in the global currentEmotion variable, which every particle reads on its very next update() call - instantly changing the color, speed, size, and gravity of the entire swarm.
  6. If the input is empty, the API call fails, or an unexpected word comes back, the sketch safely falls back to the 'calm' configuration so the animation never breaks.

🎓 Concepts You'll Learn

Perlin noise-driven motionAsync/await and fetch() API callsObject-oriented particle systemsp5.Element DOM inputs (createInput, createDiv)Color, alpha, and fading trailsEvent-driven global state

📝 Code Breakdown

getApiKey()

This function reverses a lightweight XOR 'encryption' so the raw API key never appears in plain text in the source file. It's called once in setup() to reconstruct API_KEY.

function getApiKey(){
  return atob(encoded).split('').map(c=>String.fromCharCode(c.charCodeAt(0)^key)).join('');
}
Line-by-line explanation (3 lines)

🔧 Subcomponents:

calculation XOR Decryption .map(c=>String.fromCharCode(c.charCodeAt(0)^key))

Reverses a simple XOR obfuscation applied to each character of the encoded key

return atob(encoded).split('')
atob() decodes the base64 string `encoded` back into raw text, then split('') turns it into an array of individual characters
.map(c=>String.fromCharCode(c.charCodeAt(0)^key))
For every character, this gets its numeric character code, XORs it with the constant `key` (0x5A), and converts the result back into a character - this undoes the same XOR operation used to scramble the key originally
.join('')
Joins the array of decrypted characters back into a single string, producing the real OpenAI API key

Particle (constructor)

The constructor runs once whenever `new Particle(...)` is called - either at startup or whenever a particle dies and needs to respawn. It sets up every property the particle needs before update() and display() take over.

class Particle {
  constructor(x, y, emotionConfig) {
    this.x = x;
    this.y = y;
    this.baseSpeed = emotionConfig.speedFactor;
    this.directionNoiseScale = emotionConfig.directionNoiseScale;
    this.color = emotionConfig.color;
    this.size = emotionConfig.size;
    this.lifespan = emotionConfig.life;
    this.age = 0;
    this.gravity = emotionConfig.gravity;

    // Initial velocity based on Perlin noise for organic starting movement
    this.vx = (noise(x * this.directionNoiseScale, y * this.directionNoiseScale) - 0.5) * this.baseSpeed * 2;
    this.vy = (noise(y * this.directionNoiseScale, x * this.directionNoiseScale) - 0.5) * this.baseSpeed * 2;
  }
Line-by-line explanation (6 lines)

🔧 Subcomponents:

calculation Noise-Based Initial Velocity this.vx = (noise(x * this.directionNoiseScale, y * this.directionNoiseScale) - 0.5) * this.baseSpeed * 2;

Uses each particle's own x/y position as noise coordinates so starting directions vary smoothly across the canvas instead of looking random

this.x = x; this.y = y;
Stores the particle's starting position, passed in when it's created
this.baseSpeed = emotionConfig.speedFactor;
Copies the current emotion's speed setting onto this particle
this.color = emotionConfig.color;
Copies the emotion's hex color string, which display() will use to fill the particle
this.lifespan = emotionConfig.life;
How many frames this particle can live before isDead() reports true and it gets replaced
this.age = 0;
Tracks how many frames this particle has existed - starts at zero
this.vx = (noise(x * this.directionNoiseScale, y * this.directionNoiseScale) - 0.5) * this.baseSpeed * 2;
Uses Perlin noise (instead of random()) to pick a starting horizontal velocity - noise() returns a smooth value between 0 and 1, so subtracting 0.5 centers it around zero before scaling by speed

update()

update() runs once per particle per frame, and is where the emotion's config is applied live, physics (gravity, noise force, drag) is simulated, and boundary wrapping keeps particles on screen forever.

🔬 This 0.1 multiplier tames how much the noise field steers each particle. What happens if you raise it to 0.5 or even 1 - does the motion still feel organic, or does it turn erratic?

    this.vx += noiseForceX * 0.1; // Small influence to avoid overly wild movement
    this.vy += noiseForceY * 0.1;
  update() {
    // Dynamically adjust particle properties based on the current emotion
    const config = emotionConfigs[currentEmotion];
    if (config) {
      this.baseSpeed = config.speedFactor;
      this.directionNoiseScale = config.directionNoiseScale;
      this.color = config.color;
      this.size = config.size;
      this.gravity = config.gravity;
    }

    // Apply gravity
    this.vy += this.gravity;

    // Apply Perlin noise-based forces for organic, swirling movement
    const noiseForceX = (noise(this.x * this.directionNoiseScale, this.y * this.directionNoiseScale, frameCount * 0.01) - 0.5) * this.baseSpeed;
    const noiseForceY = (noise(this.y * this.directionNoiseScale, this.x * this.directionNoiseScale, frameCount * 0.01) - 0.5) * this.baseSpeed;

    this.vx += noiseForceX * 0.1; // Small influence to avoid overly wild movement
    this.vy += noiseForceY * 0.1;

    // Add some drag/friction
    this.vx *= 0.98;
    this.vy *= 0.98;

    // Update position
    this.x += this.vx;
    this.y += this.vy;

    // Reinitialize particle if it goes off-screen
    if (this.x > width || this.x < 0 || this.y > height || this.y < 0) {
      this.x = random(width);
      this.y = random(height);
      this.age = 0;
      // Re-calculate initial velocity with current emotion config
      const currentConfig = emotionConfigs[currentEmotion];
      this.vx = (noise(this.x * currentConfig.directionNoiseScale, this.y * currentConfig.directionNoiseScale) - 0.5) * currentConfig.speedFactor * 2;
      this.vy = (noise(this.y * currentConfig.directionNoiseScale, this.x * currentConfig.directionNoiseScale) - 0.5) * currentConfig.speedFactor * 2;
    }

    this.age++;
  }
Line-by-line explanation (9 lines)

🔧 Subcomponents:

conditional Live Emotion Sync const config = emotionConfigs[currentEmotion]; if (config) { this.baseSpeed = config.speedFactor;

Every frame, pulls the latest emotion settings so ALL particles switch look and behavior together the instant currentEmotion changes

calculation Perlin Noise Steering Force const noiseForceX = (noise(this.x * this.directionNoiseScale, this.y * this.directionNoiseScale, frameCount * 0.01) - 0.5) * this.baseSpeed;

Adds a smoothly-changing 3D noise force (using position and time) so particles swirl organically instead of moving in straight lines

conditional Off-Screen Respawn if (this.x > width || this.x < 0 || this.y > height || this.y < 0) {

Detects when a particle has drifted past any canvas edge and teleports it back to a new random position

const config = emotionConfigs[currentEmotion];
Looks up the settings object for whatever emotion is currently active (e.g. emotionConfigs['happy'])
this.vy += this.gravity;
Gravity is added directly to vertical velocity each frame - negative gravity makes particles drift upward, positive pulls them down
const noiseForceX = (noise(this.x * this.directionNoiseScale, this.y * this.directionNoiseScale, frameCount * 0.01) - 0.5) * this.baseSpeed;
Samples 3D Perlin noise using the particle's position AND the current frame count as inputs, so the force field itself slowly evolves over time instead of staying static
this.vx += noiseForceX * 0.1;
Only 10% of the noise force is applied per frame, keeping motion smooth rather than jittery
this.vx *= 0.98;
Multiplying velocity by a number slightly less than 1 simulates friction/drag, preventing particles from accelerating forever
this.x += this.vx; this.y += this.vy;
Applies the final velocity to move the particle's position this frame
if (this.x > width || this.x < 0 || this.y > height || this.y < 0) {
Checks if the particle has left any edge of the canvas
this.x = random(width); this.y = random(height);
Teleports the particle to a brand new random spot instead of letting it disappear forever
this.age++;
Increments the particle's age counter every frame, which display() and isDead() both use

display()

display() is purely about visuals - it never changes physics, only how each particle is drawn based on state computed elsewhere (age, color, size).

  display() {
    // Fade out particles as they age
    const alpha = map(this.age, 0, this.lifespan, 255, 0);
    const col = color(this.color);
    col.setAlpha(alpha); // Set alpha for fading effect
    fill(col);
    noStroke();
    circle(this.x, this.y, this.size);
  }
Line-by-line explanation (4 lines)

🔧 Subcomponents:

calculation Age-Based Fade const alpha = map(this.age, 0, this.lifespan, 255, 0);

Converts the particle's age into a transparency value, so it becomes fully transparent right as it reaches the end of its lifespan

const alpha = map(this.age, 0, this.lifespan, 255, 0);
map() rescales this.age from the range [0, lifespan] into the range [255, 0] - so a brand new particle (age 0) gets alpha 255 (fully opaque) and an old particle (age = lifespan) gets alpha 0 (invisible)
const col = color(this.color);
Converts the hex string (like '#FFFF00') into a p5.Color object that can be modified
col.setAlpha(alpha);
Applies the calculated fade-out transparency to that color
circle(this.x, this.y, this.size);
Draws the particle as a filled circle at its current position, using its emotion-defined size

isDead()

This simple boolean check is what allows draw() to continuously recycle particles, keeping the total particle count constant while individual particles come and go.

  isDead() {
    return this.age > this.lifespan;
  }
Line-by-line explanation (1 lines)
return this.age > this.lifespan;
Returns true once a particle has existed longer than its emotion-defined lifespan, signaling draw() to replace it with a fresh particle

setup()

setup() runs exactly once when the sketch starts, and is responsible for building the canvas, the DOM controls (input + label), and the initial particle population.

function setup() {
  createCanvas(windowWidth, windowHeight);
  API_KEY = getApiKey(); // Decrypt the API key

  // Create text input field
  emotionInput = createInput('');
  emotionInput.attribute('placeholder', 'Type something to express your emotion...');
  emotionInput.input(analyzeEmotion); // Call analyzeEmotion on every keystroke

  // Create emotion display element (div)
  emotionDisplay = createDiv(currentEmotion);
  emotionDisplay.id('emotion-display');

  // Initialize 200 particles with the default emotion's configuration
  for (let i = 0; i < 200; i++) {
    particles.push(new Particle(random(width), random(height), emotionConfigs[currentEmotion]));
  }
}
Line-by-line explanation (7 lines)

🔧 Subcomponents:

for-loop Initial Particle Creation for (let i = 0; i < 200; i++) { particles.push(new Particle(random(width), random(height), emotionConfigs[currentEmotion])); }

Creates 200 particles at random positions, all starting with the default 'calm' configuration

createCanvas(windowWidth, windowHeight);
Makes the canvas fill the entire browser window rather than a fixed size
API_KEY = getApiKey();
Decrypts the obfuscated OpenAI key once at startup and stores it for later fetch() calls
emotionInput = createInput('');
Creates a real HTML text input element on the page using p5's DOM API, starting empty
emotionInput.input(analyzeEmotion);
Registers analyzeEmotion() to run automatically every time the user types a character in the input box (the 'input' DOM event)
emotionDisplay = createDiv(currentEmotion);
Creates a div showing the current emotion word, styled by #emotion-display in the CSS
for (let i = 0; i < 200; i++) {
Loops 200 times to build the initial batch of particles
particles.push(new Particle(random(width), random(height), emotionConfigs[currentEmotion]));
Creates a new Particle at a random position using the current emotion's config, and adds it to the particles array

draw()

draw() is the animation heartbeat, running ~60 times per second. It never touches the emotion-detection logic directly - it just trusts that currentEmotion is always up to date, which is what makes the emotion-driven behavior feel instantaneous.

🔬 This loop updates every particle every frame. What happens visually if you comment out the particles[i].display() line but keep update() running - can you guess before trying?

  for (let i = particles.length - 1; i >= 0; i--) {
    particles[i].update();
    particles[i].display();
    if (particles[i].isDead()) {
function draw() {
  // Semi-transparent background for a fading trail effect
  background(0, 0, 0, 20);

  // Update and display particles
  for (let i = particles.length - 1; i >= 0; i--) {
    particles[i].update();
    particles[i].display();
    if (particles[i].isDead()) {
      // Reinitialize dead particles based on the current emotion
      particles[i] = new Particle(random(width), random(height), emotionConfigs[currentEmotion]);
    }
  }
}
Line-by-line explanation (5 lines)

🔧 Subcomponents:

for-loop Reverse Particle Update Loop for (let i = particles.length - 1; i >= 0; i--) {

Iterates through every particle backwards so particles can be safely replaced mid-loop without skipping indices

conditional Dead Particle Replacement if (particles[i].isDead()) { particles[i] = new Particle(random(width), random(height), emotionConfigs[currentEmotion]); }

Swaps out any particle that has exceeded its lifespan for a brand new one using the current emotion's settings

background(0, 0, 0, 20);
Draws a mostly-transparent black rectangle over the whole canvas instead of fully clearing it, which is what creates the soft trailing effect behind moving particles
for (let i = particles.length - 1; i >= 0; i--) {
Loops through the particles array from the last element to the first
particles[i].update();
Runs the physics/movement logic for this particle for the current frame
particles[i].display();
Draws this particle at its updated position
particles[i] = new Particle(random(width), random(height), emotionConfigs[currentEmotion]);
If the particle has aged past its lifespan, it's entirely replaced with a fresh one using the currently active emotion's configuration

analyzeEmotion()

This is the bridge between user input and the visual system: it's the only function in the sketch that talks to the outside world (via fetch), and the only place currentEmotion is ever reassigned based on real analysis rather than defaults.

🔬 This validation only accepts the five configured emotions. What would happen if you added a brand new emotion (like 'surprised') to emotionConfigs but forgot to add matching config values - what error might appear?

    const validEmotions = Object.keys(emotionConfigs);
    if (validEmotions.includes(detectedEmotion)) {
      currentEmotion = detectedEmotion;
    } else {
async function analyzeEmotion() {
  const text = emotionInput.value();

  // If input is empty, default to calm
  if (text.trim() === '') {
    currentEmotion = "calm";
    emotionDisplay.html(currentEmotion);
    return;
  }

  // Prevent multiple API calls if one is already in progress
  if (loading) {
    console.log("Still loading, please wait...");
    return;
  }

  loading = true;
  emotionDisplay.html(`Loading...`); // Indicate loading state to the user

  try {
    const response = await fetch('https://api.openai.com/v1/chat/completions', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + API_KEY // Use the decrypted API key
      },
      body: JSON.stringify({
        model: 'gpt-4o-mini', // Cost-effective and capable model
        messages: [{
          role: 'user',
          content: 'Return ONLY one word: happy, sad, angry, calm, or excited based on this text: ' + text
        }],
        max_tokens: 10 // Strongly encourages a single-word response
      })
    });

    // Check if the API request was successful
    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(`OpenAI API error: ${response.status} - ${errorData.error ? errorData.error.message : 'Unknown error'}`);
    }

    const data = await response.json();
    let detectedEmotion = data.choices[0].message.content.toLowerCase().trim();

    // Validate and normalize the detected emotion
    const validEmotions = Object.keys(emotionConfigs);
    if (validEmotions.includes(detectedEmotion)) {
      currentEmotion = detectedEmotion;
    } else {
      console.warn(`Detected emotion "${detectedEmotion}" is not one of the expected values. Defaulting to calm.`);
      currentEmotion = "calm";
    }

    emotionDisplay.html(currentEmotion); // Update the displayed emotion

  } catch (error) {
    console.error("Error analyzing emotion:", error);
    currentEmotion = "calm"; // Default to calm on API error
    emotionDisplay.html(`Error: ${error.message || 'Could not analyze'}`);
  } finally {
    loading = false; // Reset loading flag
  }
}
Line-by-line explanation (11 lines)

🔧 Subcomponents:

conditional Empty Input Guard if (text.trim() === '') { currentEmotion = "calm"; emotionDisplay.html(currentEmotion); return; }

Skips the API call entirely and resets to calm if the text box is empty

conditional Concurrent Call Guard if (loading) { console.log("Still loading, please wait..."); return; }

Prevents overlapping API requests by ignoring new calls while one is already in flight

conditional Emotion Validation if (validEmotions.includes(detectedEmotion)) { currentEmotion = detectedEmotion; } else {

Makes sure only one of the five known emotion words is ever assigned to currentEmotion, falling back to calm otherwise

conditional Error Handling try { const response = await fetch(...

Wraps the network request so any failure (bad key, network error, rate limit) gracefully falls back to calm instead of crashing the sketch

const text = emotionInput.value();
Reads whatever the user has currently typed into the input box
if (text.trim() === '') {
Checks if the input is blank (ignoring whitespace) - if so, defaults to calm without calling the API
if (loading) {
Guards against firing a second API request before the first one finishes, since this function runs on every keystroke
loading = true;
Sets a flag so subsequent keystrokes are ignored until this request completes
const response = await fetch('https://api.openai.com/v1/chat/completions', {...})
Sends an HTTP POST request to OpenAI's chat completion endpoint, awaiting the response before continuing (this pauses this async function but not the rest of the sketch)
content: 'Return ONLY one word: happy, sad, angry, calm, or excited based on this text: ' + text
The prompt instructs the AI model to classify the user's text into exactly one of five emotion words
if (!response.ok) {
Checks the HTTP status - if it's not a success code, an error is thrown with details from the response body
let detectedEmotion = data.choices[0].message.content.toLowerCase().trim();
Extracts the AI's text reply from the response JSON structure and normalizes it to lowercase with no extra whitespace
const validEmotions = Object.keys(emotionConfigs);
Builds a list of allowed emotion names directly from the emotionConfigs object's keys, so it always matches whatever emotions are configured
currentEmotion = detectedEmotion;
Updates the global variable that every particle's update() reads from - this is the single line that actually changes the whole sketch's behavior
} finally { loading = false;
No matter whether the request succeeded or failed, the loading flag is reset so future keystrokes can trigger new requests

windowResized()

windowResized() is a special p5.js callback that fires automatically on browser resize events, keeping the canvas and DOM elements in sync with the viewport.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  emotionInput.size(width - 20, 30);
}
Line-by-line explanation (2 lines)
resizeCanvas(windowWidth, windowHeight);
A built-in p5.js function called automatically whenever the browser window changes size, so this resizes the canvas to match
emotionInput.size(width - 20, 30);
Resizes the text input element to stay proportional to the new canvas width, leaving a 20 pixel margin

📦 Key Variables

encoded string

The base64-encoded, XOR-scrambled OpenAI API key stored directly in the source file

const encoded='KTF3Kig1MHcIaDhqCms+OA0A...';
key number

The XOR key (0x5A) used to scramble and unscramble the API key string

const key=0x5A;
particles array

Holds every Particle object currently on screen; draw() loops over this array every frame to update and render them

let particles = [];
emotionInput object

A p5.Element reference to the HTML text input where users type how they feel

let emotionInput;
emotionDisplay object

A p5.Element reference to the div that shows the currently detected emotion word

let emotionDisplay;
currentEmotion string

The single source of truth for which emotion is active right now; every particle reads this to know how to look and move

let currentEmotion = "calm";
API_KEY string

Stores the decrypted OpenAI API key used to authorize fetch() requests

let API_KEY;
loading boolean

Prevents multiple overlapping API calls from firing while a previous request is still in progress

let loading = false;
emotionConfigs object

A lookup table mapping each emotion name to its color, speed, noise scale, size, lifespan, and gravity settings

const emotionConfigs = { "happy": { color: "#FFFF00", speedFactor: 2.5, ... } };

🔧 Potential Improvements (4)

Here are some ways this code could be enhanced:

BUG top of file, encoded/key + getApiKey()

The OpenAI API key is only lightly obfuscated with base64 and a single-byte XOR, both of which are trivially reversible in the browser console or by inspecting network requests, exposing a real, billable API key to anyone who opens dev tools.

💡 Move the API call behind a small backend/serverless proxy that holds the real key server-side, and have the client fetch from your own endpoint instead of api.openai.com directly.

BUG setup() / analyzeEmotion() event binding

emotionInput.input(analyzeEmotion) fires a brand new API request on every single keystroke, which is wasteful, costs money per character typed, and can create a rapid-fire queue of requests even with the `loading` guard (a fast typer can still trigger many calls in a row).

💡 Debounce the input handler (e.g. only call analyzeEmotion after the user has paused typing for ~500ms using setTimeout/clearTimeout) so only one request fires per pause in typing.

PERFORMANCE Particle.update()

Three separate noise() calls are made per particle per frame (one in the constructor's initial velocity, two in update() for noiseForceX/Y), each of which is a moderately expensive Perlin noise lookup; with hundreds of particles this adds up every frame.

💡 Consider caching a single noise value per particle per frame and deriving both x/y forces from it with a rotation offset, or reduce particle count/noise octaves if frame rate drops on low-power devices.

FEATURE emotionDisplay / analyzeEmotion()

There's no visual transition between emotions - the particle swarm instantly snaps to new colors and speeds, which can feel abrupt compared to the otherwise organic noise-based motion.

💡 Use lerpColor() to smoothly blend from the old emotion's color to the new one over a few seconds, and gradually interpolate speedFactor/gravity instead of assigning them directly in update().

🔄 Code Flow

Code flow showing getapikey, particle, update, display, isdead, setup, draw, analyzeemotion, windowresized

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

graph TD start[Start] --> setup[setup] setup --> particle-init-loop[Initial Particle Creation] particle-init-loop --> draw[draw loop] click setup href "#fn-setup" click particle-init-loop href "#sub-particle-init-loop" click draw href "#fn-draw" draw --> config-sync[Live Emotion Sync] draw --> particle-loop[Reverse Particle Update Loop] particle-loop --> dead-check[Dead Particle Replacement] dead-check --> isdead[isdead] isdead -->|yes| particle-loop isdead -->|no| update[update] update --> noise-forces[Perlin Noise Steering Force] update --> offscreen-reset[Off-Screen Respawn] offscreen-reset --> display[display] display --> age-fade[Age-Based Fade] age-fade --> draw click config-sync href "#sub-config-sync" click particle-loop href "#sub-particle-loop" click dead-check href "#sub-dead-check" click isdead href "#fn-isdead" click update href "#fn-update" click noise-forces href "#sub-noise-forces" click offscreen-reset href "#sub-offscreen-reset" click display href "#fn-display" click age-fade href "#sub-age-fade" analyzeemotion --> empty-check[Empty Input Guard] empty-check -->|yes| draw empty-check -->|no| loading-guard[Concurrent Call Guard] loading-guard -->|yes| draw loading-guard -->|no| try-catch[Error Handling] try-catch --> validation[Emotion Validation] validation -->|valid| config-sync validation -->|invalid| draw click analyzeemotion href "#fn-analyzeemotion" click empty-check href "#sub-empty-check" click loading-guard href "#sub-loading-guard" click try-catch href "#sub-try-catch" click validation href "#sub-validation" windowresized --> setup click windowresized href "#fn-windowresized" getapikey --> setup click getapikey href "#fn-getapikey"

❓ Frequently Asked Questions

What visual effects can I expect from the AI Emotion Particles sketch?

The sketch creates a dynamic visual display of particles that change color, speed, and movement based on the user's expressed emotions, with calming greens, cheerful yellows, somber blues, and intense reds.

How can I interact with the AI Emotion Particles sketch?

Users can type in their feelings, and the sketch will analyze the text in real-time, adjusting the particle behavior to reflect the detected emotions.

What coding concepts does the AI Emotion Particles sketch illustrate?

This sketch showcases the use of AI for emotion recognition, as well as particle systems and dynamic visual effects in creative coding.

Preview

AI Emotion Particles - xelsed.ai - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of AI Emotion Particles - xelsed.ai - Code flow showing getapikey, particle, update, display, isdead, setup, draw, analyzeemotion, windowresized
Code Flow Diagram