Serene Pebble Pond

Arraste e solte para lançar uma pedrinha

Força e direção controlam o arremesso

Pedras lançadas: 0

Respire. Relaxe. Aproveite o momento.

// p5.js Sketch let pebbles = []; let ripples = []; let shells = []; let fishes = []; let zenMode = false; let pebbleCount = 0; let dragStart = null; let dragVector = null; function setup() { let canvas = createCanvas(windowWidth, windowHeight); canvas.parent('canvas-container'); frameRate(60); // Add some initial decorative elements for (let i = 0; i < 5; i++) { addRandomShell(); } // Add some initial fish for (let i = 0; i < 3; i++) { addRandomFish(); } } function drawBeachBackground() { // Sky gradient for (let y = 0; y < height * 0.6; y++) { let inter = map(y, 0, height * 0.6, 0, 1); let c = lerpColor(color('#87CEEB'), color('#E0F7FA'), inter); stroke(c); line(0, y, width, y); } // Beach sand area for (let y = height * 0.6; y < height * 0.75; y++) { let inter = map(y, height * 0.6, height * 0.75, 0, 1); let c = lerpColor(color('#F4A460'), color('#CD853F'), inter); stroke(c); line(0, y, width, y); } // Ocean water area for (let y = height * 0.75; y < height; y++) { let inter = map(y, height * 0.75, height, 0, 1); let c = lerpColor(color('#4A90E2'), color('#1E3F66'), inter); stroke(c); line(0, y, width, y); } // Add some sand texture stroke(255, 255, 255, 50); for (let i = 0; i < 200; i++) { let x = random(width); let y = random(height * 0.6, height * 0.75); let size = random(1, 3); point(x, y); } // Add wave foam at water edge stroke(255, 255, 255, 80); for (let x = 0; x < width; x += 20) { let waveHeight = sin(x * 0.01 + frameCount * 0.05) * 5 + 10; arc(x, height * 0.75, waveHeight, waveHeight, 0, PI); } } function draw() { // Clear with semi-transparent for trail effect background(255, 255, 255, 3); // Redraw beach background drawBeachBackground(); // Update and draw pebbles for (let i = pebbles.length - 1; i >= 0; i--) { pebbles[i].update(); pebbles[i].display(); if (pebbles[i].shouldRemove()) { pebbles.splice(i, 1); } } // Update and draw ripples for (let i = ripples.length - 1; i >= 0; i--) { ripples[i].update(); ripples[i].display(); if (ripples[i].shouldRemove()) { ripples.splice(i, 1); } } // Draw shells on beach for (let shell of shells) { shell.display(); } // Update and draw fish for (let i = fishes.length - 1; i >= 0; i--) { fishes[i].update(); fishes[i].display(); } // Draw drag vector if dragging if (dragVector) { drawDragVector(); } // Zen mode automatic ripples if (zenMode && frameCount % 120 === 0) { createZenRipple(); } } function mousePressed() { if (mouseY > height * 0.75) { // Only in water area dragStart = createVector(mouseX, mouseY); } return false; } function mouseDragged() { if (dragStart) { // Reverse the direction - drag towards beach to throw into ocean dragVector = createVector(dragStart.x - mouseX, dragStart.y - mouseY); // Limit vector length for better control dragVector.limit(150); } return false; } function mouseReleased() { if (dragStart && dragVector) { throwPebble(dragStart.x, dragStart.y, dragVector); dragStart = null; dragVector = null; } return false; } function throwPebble(x, y, vector) { // Create pebble let pebble = new Pebble(x, y, vector); pebbles.push(pebble); pebbleCount++; document.getElementById('pebbleCount').textContent = pebbleCount; // Play splash sound in real implementation // if (splashSound) { // splashSound.play(); // splashSound.setVolume(map(vector.mag(), 0, 150, 0.3, 1.0)); // } // Chance to spawn surprise shells if (random() < 0.15) { addRandomShell(); } // Chance to spawn fish if (random() < 0.1) { addRandomFish(); } } function drawDragVector() { stroke(255, 255, 255, 200); strokeWeight(2); drawingContext.setLineDash([5, 5]); line(dragStart.x, dragStart.y, dragStart.x + dragVector.x, dragStart.y + dragVector.y); // Draw arrow head push(); translate(dragStart.x + dragVector.x, dragStart.y + dragVector.y); rotate(dragVector.heading()); fill(255, 255, 255, 200); triangle(0, 0, -10, -5, -10, 5); pop(); drawingContext.setLineDash([]); } function createZenRipple() { let x = random(width * 0.2, width * 0.8); let y = random(height * 0.8, height * 0.95); let ripple = new Ripple(x, y, random(5, 15)); ripples.push(ripple); } function addRandomShell() { if (shells.length < 10) { // Limit number of shells let shell = new Shell(); shells.push(shell); } } function addRandomFish() { if (fishes.length < 8) { // Limit number of fish let fish = new Fish(); fishes.push(fish); } } function windowResized() { resizeCanvas(windowWidth, windowHeight); drawBeachBackground(); } // Pebble Class class Pebble { constructor(x, y, velocity) { this.pos = createVector(x, y); this.vel = velocity.copy().mult(0.2); // Scale down velocity this.acc = createVector(0, 0.3); // Gravity this.size = random(8, 15); this.color = color(random(180, 220), random(180, 220), random(180, 220)); this.splashCreated = false; } update() { this.vel.add(this.acc); this.pos.add(this.vel); // Check if hit water surface (ocean) if (!this.splashCreated && this.pos.y > height * 0.75) { this.createSplash(); this.splashCreated = true; } } display() { fill(this.color); noStroke(); ellipse(this.pos.x, this.pos.y, this.size * 2, this.size * 2); } createSplash() { let ripple = new Ripple(this.pos.x, this.pos.y, this.vel.mag() * 0.5); ripples.push(ripple); // Create HTML ripple effect createHTMLRipple(this.pos.x, this.pos.y); // Attract nearby fish to the pebble attractFishToPebble(this.pos.x, this.pos.y); } shouldRemove() { return this.pos.y > height + 50; } } // Ripple Class class Ripple { constructor(x, y, strength) { this.x = x; this.y = y; this.radius = 5; this.maxRadius = strength * 15 + 50; this.speed = map(strength, 0, 75, 1, 3); this.alpha = 1; } update() { this.radius += this.speed; this.alpha = 1 - (this.radius / this.maxRadius); } display() { if (this.alpha <= 0) return; stroke(255, 255, 255, this.alpha * 150); strokeWeight(2); noFill(); ellipse(this.x, this.y, this.radius * 2, this.radius * 2); } shouldRemove() { return this.radius >= this.maxRadius; } } // Fish Class class Fish { constructor() { this.x = random(width); this.y = random(height * 0.7, height * 0.95); this.speed = random(0.5, 2); this.direction = random() < 0.5 ? 1 : -1; this.size = random(15, 25); this.wiggle = 0; this.wiggleSpeed = random(0.1, 0.3); this.color = [255, random(100, 150), random(50, 100)]; // Orange tones this.targetX = null; this.targetY = null; this.attractionTime = 0; this.maxAttractionTime = 180; // 3 seconds at 60fps } update() { // If attracted to a pebble if (this.targetX !== null && this.targetY !== null) { let dx = this.targetX - this.x; let dy = this.targetY - this.y; let distance = sqrt(dx * dx + dy * dy); if (distance > 10 && this.attractionTime < this.maxAttractionTime) { // Move towards target this.x += (dx / distance) * this.speed * 2; this.y += (dy / distance) * this.speed * 2; this.direction = dx > 0 ? 1 : -1; this.attractionTime++; } else { // Stop attraction this.targetX = null; this.targetY = null; this.attractionTime = 0; } } else { // Normal swimming behavior this.x += this.speed * this.direction; this.wiggle += this.wiggleSpeed; // Reverse direction at edges if (this.x < -50 || this.x > width + 50) { this.direction *= -1; } } } display() { drawingContext.save(); drawingContext.translate(this.x, this.y + sin(this.wiggle) * 3); drawingContext.scale(this.direction, 1); // Fish body drawingContext.fillStyle = `rgb(${this.color[0]}, ${this.color[1]}, ${this.color[2]})`; drawingContext.beginPath(); drawingContext.ellipse(0, 0, this.size, this.size/2, 0, 0, TWO_PI); drawingContext.fill(); // Tail drawingContext.beginPath(); drawingContext.moveTo(-this.size, 0); drawingContext.lineTo(-this.size - 10, -this.size/2); drawingContext.lineTo(-this.size - 10, this.size/2); drawingContext.closePath(); drawingContext.fill(); // Eye drawingContext.fillStyle = 'white'; drawingContext.beginPath(); drawingContext.arc(this.size * 0.6, -this.size/4, this.size/5, 0, TWO_PI); drawingContext.fill(); drawingContext.fillStyle = 'black'; drawingContext.beginPath(); drawingContext.arc(this.size * 0.6, -this.size/4, this.size/10, 0, TWO_PI); drawingContext.fill(); drawingContext.restore(); } // Method to attract fish to pebble attractTo(x, y) { this.targetX = x; this.targetY = y; this.attractionTime = 0; } } // Function to attract fish to pebble function attractFishToPebble(x, y) { for (let fish of fishes) { // Check if fish is within attraction radius (200 pixels) let dx = fish.x - x; let dy = fish.y - y; let distance = sqrt(dx * dx + dy * dy); if (distance < 200 && random() < 0.7) { // 70% chance to attract fish.attractTo(x, y); } } } // HTML Ripple Effect function createHTMLRipple(x, y) { const ripple = document.createElement('div'); ripple.className = 'ripple'; ripple.style.left = x + 'px'; ripple.style.top = y + 'px'; ripple.style.width = '0px'; ripple.style.height = '0px'; document.getElementById('ripple-container').appendChild(ripple); setTimeout(() => { ripple.style.transition = 'all 1s ease-out'; ripple.style.width = '100px'; ripple.style.height = '100px'; ripple.style.opacity = '0'; }, 10); setTimeout(() => { ripple.remove(); }, 1100); } // UI Event Listeners document.getElementById('zenToggle').addEventListener('click', function() { zenMode = !zenMode; const zenText = document.getElementById('zenText'); const icon = this.querySelector('i'); if (zenMode) { zenText.textContent = 'Modo Zen: ON'; this.classList.add('bg-green-200/50'); feather.icons['sun'].replace(icon); } else { zenText.textContent = 'Modo Zen: OFF'; this.classList.remove('bg-green-200/50'); feather.icons['moon'].replace(icon); } }); document.getElementById('infoBtn').addEventListener('click', function() { document.getElementById('infoModal').classList.remove('hidden'); }); document.getElementById('closeModal').addEventListener('click', function() { document.getElementById('infoModal').classList.add('hidden'); }); // Close modal on background click document.getElementById('infoModal').addEventListener('click', function(e) { if (e.target === this) { this.classList.add('hidden'); } }); // Initialize feather icons feather.replace();