<!DOCTYPE html>

<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>

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.