<html lang="es">
<head>
<meta charset="UTF-8">
<title>Lluvia de Flores Interactiva</title>
<style>
html,
body {
margin: 0;
padding: 0;
overflow: hidden;
background: linear-gradient(180deg, #87CEEB, #98FB98);
}
canvas {
display: block;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
(function() {
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
let width = canvas.width = window.innerWidth,
height = canvas.height = window.innerHeight;
window.addEventListener("resize", () => {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
});
let isPointerDown = false;
const particles = [];
const gather = {
x: width / 2,
y: height / 2,
active: false
};
class Particle {
constructor(x, y) {
this.x = x;
this.y = y;
this.size = Math.random() * 8 + 5;
this.speedX = Math.random() * 4 - 2;
this.speedY = Math.random() * 4 - 2;
this.color = `hsl(${Math.random() * 20 + 40}, 70%, 50%)`;
this.alpha = 1;
}
update() {
this.x += this.speedX;
this.y += this.speedY;
this.alpha -= 0.02;
return this.alpha > 0;
}
draw() {
ctx.save();
ctx.globalAlpha = this.alpha;
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
}
const bigFlower = {
active: false,
x: width / 2,
y: height / 2,
size: 0,
targetSize: 200,
rotation: 0,
petals: [],
pathProgress: 0,
showMessage: false,
travelPath: []
};
class SmallFlower {
constructor() {
this.reset(true);
this.angle = Math.random() * Math.PI * 2;
this.hue = Math.random() * 20 + 40;
}
reset(initial = false) {
this.x = Math.random() * width;
this.y = initial ? Math.random() * height : -50;
this.size = Math.random() * 25 + 15;
this.speed = Math.random() * 0.5 + 0.3;
this.offset = Math.random() * 1000;
this.rotation = Math.random() * Math.PI * 2;
this.rotationSpeed = (Math.random() - 0.5) * 0.02;
}
update() {
// Movimiento de lluvia
this.y += this.speed * 2;
this.x += Math.sin(this.offset + performance.now() * 0.001) * 0.5;
this.rotation += this.rotationSpeed;
if (this.y > height + this.size) this.reset();
// Seguir dedo/ratón
if (gather.active) {
const dx = gather.x - this.x;
const dy = gather.y - this.y;
this.x += dx * 0.05;
this.y += dy * 0.05;
}
}
draw() {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
// Centro
ctx.fillStyle = `hsl(${this.hue}, 70%, 40%)`;
ctx.beginPath();
ctx.arc(0, 0, this.size * 0.4, 0, Math.PI * 2);
ctx.fill();
// Pétalos
for (let i = 0; i < 8; i++) {
ctx.save();
ctx.rotate((Math.PI * 2 * i) / 8);
ctx.fillStyle = `hsl(${this.hue}, 85%, 60%)`;
ctx.beginPath();
ctx.ellipse(this.size * 0.6, 0, this.size * 0.5, this.size * 0.3, 0, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
ctx.restore();
}
}
const flowers = Array.from({
length: 60
}, () => new SmallFlower());
function createTravelPath() {
return [{
x: bigFlower.x,
y: bigFlower.y
},
{
x: width / 2,
y: height / 2
}
];
}
function animateBigFlower() {
if (!bigFlower.active) return;
const progress = bigFlower.pathProgress;
if (progress < 1) {
bigFlower.x = lerp(bigFlower.travelPath[0].x, bigFlower.travelPath[1].x, progress);
bigFlower.y = lerp(bigFlower.travelPath[0].y, bigFlower.travelPath[1].y, progress);
bigFlower.pathProgress += 0.03;
bigFlower.rotation += 0.05;
} else {
bigFlower.showMessage = true;
startPetalCountdown();
}
bigFlower.size = lerp(bigFlower.size, bigFlower.targetSize, 0.1);
drawBigFlower();
}
function lerp(a, b, t) {
return a + (b - a) * t;
}
function startPetalCountdown() {
if (bigFlower.petals.length === 0) {
for (let i = 0; i < 12; i++) {
bigFlower.petals.push({
angle: (Math.PI * 2 * i) / 12,
visible: true
});
}
}
setTimeout(() => {
if (bigFlower.petals.length > 0) {
bigFlower.petals.shift();
particles.push(Array.from({
length: 8
},
() => new Particle(bigFlower.x, bigFlower.y)));
if (bigFlower.petals.length > 0) {
startPetalCountdown();
} else {
bigFlower.active = false;
bigFlower.showMessage = false;
}
}
}, 500);
}
function drawBigFlower() {
ctx.save();
ctx.translate(bigFlower.x, bigFlower.y);
ctx.rotate(bigFlower.rotation);
// Centro
const centerGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, bigFlower.size * 0.4);
centerGradient.addColorStop(0, '#FFD700');
centerGradient.addColorStop(1, '#FFA500');
ctx.fillStyle = centerGradient;
ctx.beginPath();
ctx.arc(0, 0, bigFlower.size * 0.4, 0, Math.PI * 2);
ctx.fill();
// Pétalos
bigFlower.petals.forEach((petal, i) => {
if (!petal.visible) return;
ctx.save();
ctx.rotate(petal.angle);
const petalGradient = ctx.createLinearGradient(
bigFlower.size * 0.5, 0,
bigFlower.size * 0.8, 0
);
petalGradient.addColorStop(0, '#FFD700');
petalGradient.addColorStop(1, '#FFA500');
ctx.fillStyle = petalGradient;
ctx.beginPath();
ctx.ellipse(bigFlower.size * 0.6, 0,
bigFlower.size * 0.4, bigFlower.size * 0.2,
0, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
});
ctx.restore();
// Texto legible
if (bigFlower.showMessage) {
ctx.save();
ctx.fillStyle = "#ffffff";
ctx.font = "bold 32px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.shadowColor = "rgba(0,0,0,0.5)";
ctx.shadowBlur = 5;
ctx.fillText("Eres especial para mí", width / 2, height / 2);
ctx.restore();
}
}
// Eventos
canvas.addEventListener('click', (e) => {
if (bigFlower.active) return;
bigFlower.active = true;
bigFlower.travelPath = createTravelPath();
bigFlower.pathProgress = 0;
bigFlower.x = e.clientX;
bigFlower.y = e.clientY;
bigFlower.size = 50;
bigFlower.petals = [];
bigFlower.showMessage = false;
});
// Seguimiento del dedo/ratón
const handleMove = (x, y) => {
gather.x = x;
gather.y = y;
gather.active = true;
};
canvas.addEventListener('mousedown', (e) => {
isPointerDown = true;
handleMove(e.clientX, e.clientY);
});
canvas.addEventListener('mousemove', (e) => {
if (isPointerDown) handleMove(e.clientX, e.clientY);
});
canvas.addEventListener('mouseup', () => {
isPointerDown = false;
gather.active = false;
});
canvas.addEventListener('touchstart', (e) => {
isPointerDown = true;
handleMove(e.touches[0].clientX, e.touches[0].clientY);
});
canvas.addEventListener('touchmove', (e) => {
if (isPointerDown) handleMove(e.touches[0].clientX, e.touches[0].clientY);
});
canvas.addEventListener('touchend', () => {
isPointerDown = false;
gather.active = false;
});
function animate() {
ctx.clearRect(0, 0, width, height);
// Flores pequeñas (lluvia)
flowers.forEach(flower => {
flower.update();
flower.draw();
});
// Partículas
for (let i = particles.length - 1; i >= 0; i--) {
particles[i].update();
particles[i].draw();
if (!particles[i].update()) particles.splice(i, 1);
}
// Flor grande
if (bigFlower.active) {
animateBigFlower();
}
requestAnimationFrame(animate);
}
animate();
})();
</script>
</body>
</html>
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.