serene-pebble-pond / index.html
emroberto's picture
CORRIJA O ERRO
75b8c84 verified
<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Serene Pebble Pond</title>
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/feather-icons"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.7.0/p5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.7.0/addons/p5.sound.min.js"></script>
<style>
body, html {
margin: 0;
padding: 0;
overflow: hidden;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
#defaultCanvas0 {
position: absolute;
top: 0;
left: 0;
z-index: 1;
}
.ui-overlay {
position: absolute;
z-index: 10;
pointer-events: none;
width: 100%;
height: 100%;
}
.ui-element {
pointer-events: auto;
}
.ripple {
position: absolute;
border-radius: 50%;
border: 2px solid rgba(255, 255, 255, 0.6);
transform: translate(-50%, -50%);
pointer-events: none;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.fade-in {
animation: fadeIn 0.8s ease-out forwards;
}
</style>
</head>
<body class="bg-gradient-to-br from-blue-50 to-green-50 flex items-center justify-center min-h-screen">
<!-- Main Canvas for the Pond -->
<div id="canvas-container" class="w-full h-full absolute"></div>
<!-- UI Overlay -->
<div class="ui-overlay flex flex-col items-center justify-between p-6">
<!-- Top Bar -->
<div class="w-full flex justify-between items-center fade-in">
<h1 class="text-3xl font-light text-slate-700 drop-shadow-lg">Serene Pebble Pond</h1>
<div class="flex space-x-4">
<button id="zenToggle" class="ui-element bg-white/30 backdrop-blur-sm text-slate-700 px-4 py-2 rounded-full shadow-lg hover:bg-white/50 transition-all duration-300 flex items-center space-x-2">
<i data-feather="moon"></i>
<span id="zenText">Modo Zen: OFF</span>
</button>
<button id="infoBtn" class="ui-element bg-white/30 backdrop-blur-sm text-slate-700 w-10 h-10 rounded-full shadow-lg hover:bg-white/50 transition-all duration-300 flex items-center justify-center">
<i data-feather="info"></i>
</button>
</div>
</div>
<!-- Center Instructions -->
<div id="instructions" class="text-center bg-black/20 backdrop-blur-sm text-white p-4 rounded-lg shadow-xl fade-in">
<p class="text-lg mb-2">Arraste e solte para lançar uma pedrinha</p>
<p class="text-sm opacity-80">Força e direção controlam o arremesso</p>
</div>
<!-- Bottom Stats -->
<div class="w-full flex justify-between items-end fade-in">
<div class="bg-white/30 backdrop-blur-sm text-slate-700 px-4 py-2 rounded-lg shadow-lg">
<p class="text-sm">Pedras lançadas: <span id="pebbleCount">0</span></p>
</div>
<div class="text-slate-700 text-sm bg-white/30 backdrop-blur-sm px-4 py-2 rounded-lg shadow-lg">
<p>Respire. Relaxe. Aproveite o momento.</p>
</div>
</div>
</div>
<!-- Info Modal -->
<div id="infoModal" class="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-20 hidden">
<div class="bg-white rounded-xl shadow-2xl p-6 max-w-md mx-4 fade-in">
<div class="flex justify-between items-center mb-4">
<h2 class="text-2xl font-light text-slate-700">Sobre o Jogo</h2>
<button id="closeModal" class="text-slate-500 hover:text-slate-700">
<i data-feather="x"></i>
</button>
</div>
<p class="text-slate-600 mb-4">
Serene Pebble Pond é uma experiência interativa projetada para promover calma e relaxamento. Simule o ato nostálgico de jogar pedrinhas num lago tranquilo.
</p>
<ul class="text-slate-600 space-y-2 mb-4">
<li class="flex items-start">
<i data-feather="check-circle" class="text-green-500 mr-2 mt-1 flex-shrink-0"></i>
<span>Arraste e solte para lançar pedras</span>
</li>
<li class="flex items-start">
<i data-feather="check-circle" class="text-green-500 mr-2 mt-1 flex-shrink-0"></i>
<span>Observe as ondas hipnóticas se espalharem</span>
</li>
<li class="flex items-start">
<i data-feather="check-circle" class="text-green-500 mr-2 mt-1 flex-shrink-0"></i>
<span>Ative o Modo Zen para relaxamento automático</span>
</li>
</ul>
<p class="text-sm text-slate-500">Encontre sua paz. 🌿</p>
</div>
</div>
<!-- Custom Ripple Container -->
<div id="ripple-container"></div>
// 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();
</script>
</body>
</html>