Serene Pebble Pond é uma experiência interativa projetada para promover calma e relaxamento. Simule o ato nostálgico de jogar pedrinhas num lago tranquilo.
Arraste e solte para lançar pedras
Observe as ondas hipnóticas se espalharem
Ative o Modo Zen para relaxamento automático
Encontre sua paz. 🌿
// 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();