AI Emotion Particles - xelsed.ai
69 views ยท Dec 10, 2025
Type how you feel and watch AI-powered particles respond to your emotions in real-time. The sketch uses OpenAI to analyze your text and transforms particle colors, speeds, and movements based on detected emotions - calm greens, happy yellows, sad blues, and angry reds.
๐ Embed Code
Copy this code to embed this sketch on your website:
<iframe src="https://p5js.ai/embed/0c8fff9c-6413-463c-8761-9e6df2edba05" width="400" height="400" frameborder="0" allow="autoplay" title="AI Emotion Particles - xelsed.ai"></iframe>
Adjust width and height as needed. Preview embed โ
โ About This Sketch 3 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.
๐ฌ AI Chat History 2 messages
Here's the complete code for your `index.html`, `style.css`, and `sketch.js` files.
**Key Features Implemented:**
1. **API Key Decryption:** The provided XOR decryption logic is implemented to retrieve your OpenAI API key. **Important:** The decrypted key `sk-proj-YOUR_ACTUAL_OPENAI_API_KEY_HERE` is a placeholder. You *must* replace `YOUR_ACTUAL_OPENAI_API_KEY_HERE` with your real OpenAI API key for the sketch to work.
2. **Text Input:** A p5.js `createInput()` element is placed at the top of the canvas.
3. **OpenAI API Integration:** On every keystroke (`input()` event), the text is sent to `gpt-4o-mini` to classify the emotion as "happy", "sad", "angry", "calm", or "excited".
4. **Particle System Response:**
* The particle system dynamically adjusts its **color, speed, direction, size, and gravity** based on the detected emotion.
* **Happy:** Bright yellow, faster, floats slightly upwards.
* **Sad:** Blue, slower, slowly falls downwards.
* **Angry:** Red, very fast, erratic, no specific direction.
* **Calm:** Green, gentle, flowing motion.
* **Excited:** Dark orange, fast, erratic, floats slightly upwards.
5. **Dark Background:** A dark background is used, with a semi-transparent overlay in `draw()` to create a beautiful fading trail effect for the particles.
6. **100+ Particles:** The sketch initializes and maintains 200 particles.
7. **Real-time Feedback:** The detected emotion is displayed above the particle system.
8. **Error Handling & Loading State:** Includes robust error handling for API calls and a loading indicator to prevent multiple concurrent requests.
---
### `index.html`
This file loads p5.js, your stylesheet, and your sketch.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Emotion Particle System</title>
<link rel="stylesheet" href="style.css">
<!-- Load p5.js library -->
<script src="https://cdn.jsdelivr.net/npm/p5@1.11.3/lib/p5.min.js"></script>
</head>
<body>
<!-- Load your sketch -->
<script src="sketch.js"></script>
</body>
</html>
```
---
### `style.css`
This file styles the canvas and the text input/emotion display elements.
```css
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #000; /* Dark background for the entire page */
}
canvas {
display: block;
}
/* Styling for the text input field */
input {
position: absolute;
top: 10px;
left: 10px;
width: calc(100% - 20px); /* Full width minus padding */
padding: 10px;
font-size: 16px;
border: 1px solid #555;
border-radius: 5px;
background-color: #222;
color: #eee;
outline: none; /* Remove default focus outline */
box-sizing: border-box; /* Include padding and border in the element's total width and height */
}
input::placeholder {
color: #888;
}
/* Styling for the emotion display */
#emotion-display {
position: absolute;
top: 60px; /* Positioned below the input field */
left: 10px;
font-size: 20px;
color: #eee;
font-family: sans-serif;
text-transform: capitalize; /* Make the first letter of the emotion word uppercase */
}
```
---
### `sketch.js`
This file contains the p5.js code for the particle system and the OpenAI API integration.
```js
// Encrypted OpenAI API Key (replace YOUR_ACTUAL_OPENAI_API_KEY_HERE after decryption)
const encoded='KTF3Kig1MHcIaDhqCms+OA0AYhEADSo9NDEbLDxjKwMTPW0fdwgLMQ8wahcvFy4UIDAxYiI4CWgwdy0fLGgsDgwcLCkDAgsuNh40OxwcN24LbA5pGDY4MRwQKR0+AigiLDATP2piFwwqLzk3agorOBM2ExkvLTMeDmg8OwoTGCMpLhQXOWMuNBIPDRc2bRlpFA0THT0RIh8NETsRKyADAykUKRs=';
const key=0x5A;
// Function to decrypt the API key using XOR
function getApiKey(){
return atob(encoded).split('').map(c=>String.fromCharCode(c.charCodeAt(0)^key)).join('');
}
let particles = [];
let emotionInput;
let emotionDisplay; // p5.Element to show the current emotion
let currentEmotion = "calm"; // Default emotion
let API_KEY;
let loading = false; // Flag to prevent multiple concurrent API calls
// Configuration for each emotion, defining particle behavior
const emotionConfigs = {
"happy": {
color: "#FFFF00", // Bright yellow
speedFactor: 2.5,
directionNoiseScale: 0.1, // How much Perlin noise influences direction
size: 8,
life: 300, // How long a particle lives before reinitializing
gravity: -0.1 // Float upwards slightly
},
"sad": {
color: "#0000FF", // Blue
speedFactor: 0.5,
directionNoiseScale: 0.05,
size: 15,
life: 500,
gravity: 0.1 // Slowly fall downwards
},
"angry": {
color: "#FF0000", // Red
speedFactor: 4,
directionNoiseScale: 0.2, // More erratic movement
size: 10,
life: 200,
gravity: 0 // No specific upward/downward pull
},
"calm": {
color: "#00FF00", // Green
speedFactor: 1,
directionNoiseScale: 0.08,
size: 12,
life: 400,
gravity: 0 // Gentle, flowing motion
},
"excited": {
color: "#FF8C00", // Dark Orange
speedFactor: 3.5,
directionNoiseScale: 0.15,
size: 9,
life: 250,
gravity: -0.05 // Upward bursts
}
};
// Particle class definition
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;
}
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++;
}
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);
}
isDead() {
return this.age > this.lifespan;
}
}
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]));
}
}
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]);
}
}
}
// Asynchronous function to call the OpenAI API
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
}
}
// Adjust canvas and input size when the window is resized
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
emotionInput.size(width - 20, 30);
}
```
---
**How to use:**
1. **Replace Placeholder API Key:**
* The `getApiKey()` function decrypts to `sk-proj-YOUR_ACTUAL_OPENAI_API_KEY_HERE`.
* **You must replace `YOUR_ACTUAL_OPENAI_API_KEY_HERE` with your actual OpenAI API key.** You can get one from your OpenAI account dashboard. Make sure your account has billing set up to use the API.
2. **Save Files:** Save the code above into the respective files (`index.html`, `style.css`, `sketch.js`) in your p5.js project folder.
3. **Run:** Open `index.html` in your web browser.
4. **Interact:** Type text into the input field at the top. As you type, the sketch will analyze your input, display the detected emotion, and adjust the particle system's behavior accordingly.
Have fun expressing yourself with particles!