<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fuegos Artificiales - Feliz Cumpleaños</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="message-container">
</div>
<div class="fireworks-container">
</div>
</body>
<script src="script.js"></script>
</html>
body {
margin: 0;
overflow: hidden;
/* MODIFICAR: Color de fondo. Es un degradado radial. */
background: radial-gradient(ellipse at bottom, #1b2735 0%, #090a0f 100%);
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
position: relative;
/* MODIFICAR: Fuente principal para el texto del mensaje. */
font-family: 'Arial', cursive, sans-serif;
}
.fireworks-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.particle {
position: absolute;
border-radius: 50%;
opacity: 1;
transform-origin: center center;
}
.rocket {
position: absolute;
bottom: 0;
/* MODIFICAR: Ancho y alto del cohete que sube. */
width: 4px;
height: 20px;
/* MODIFICAR: Color del cohete (es un degradado). */
background: linear-gradient(to top, #ffeb3b, #ff5722);
border-radius: 2px 2px 0 0;
opacity: 0;
animation: launch 1.5s ease-in forwards;
}
@keyframes launch {
0% {
transform: translateY(0);
opacity: 1;
}
80% {
opacity: 1;
}
100% {
/* MODIFICAR: Altura a la que explota el cohete. Un valor más alto (ej: -90vh) lo hará subir más. */
transform: translateY(-80vh);
opacity: 0;
}
}
.message-container {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
/* MODIFICAR: Tamaño del texto. clamp(mínimo, preferido, máximo). */
font-size: clamp(2rem, 8vw, 6rem);
/* MODIFICAR: Color principal de las letras del mensaje. */
color: #fff;
/* MODIFICAR: Efecto de brillo del texto. Puedes añadir o cambiar colores y desenfoque. */
text-shadow: 0 0 10px #ffd700, 0 0 20px #ffd700, 0 0 30px #ff8c00;
z-index: 10;
white-space: nowrap;
text-align: center;
padding: 0 10px;
}
.message-container span {
opacity: 0;
display: inline-block;
transition: opacity 0.2s ease-out, transform 0.2s ease-out;
margin: 0 0.05em;
}
.message-container span.visible {
opacity: 1;
transform: translateY(0) scale(1);
}
.message-container span.initial {
transform: translateY(20px) scale(0.8);
}
.star {
position: absolute;
/* MODIFICAR: Tamaño de las estrellas de fondo. */
width: 2px;
height: 2px;
/* MODIFICAR: Color y brillo de las estrellas. */
background-color: white;
box-shadow: 0 0 5px white, 0 0 10px white;
border-radius: 50%;
animation: twinkle 2s infinite ease-in-out;
}
@keyframes twinkle {
0%, 100% {
/* MODIFICAR: Opacidad mínima de las estrellas (qué tan tenues se ponen). */
opacity: 0.3;
}
50% {
/* MODIFICAR: Opacidad máxima de las estrellas (qué tan brillantes se ponen). */
opacity: 1;
}
}
const fireworksContainer = document.querySelector('.fireworks-container');
const messageContainer = document.querySelector('.message-container');
let currentLetterIndex = 0;
const letters = [];
// --- ZONA DE MODIFICACIÓN PRINCIPAL ---
// MODIFICAR: El mensaje que se mostrará letra por letra.
const fullMessage = "¡Feliz Cumpleaños!";
// MODIFICAR: Paleta de colores para las explosiones de los fuegos artificiales.
const fireworkColors = [
'#FF5252', '#FF4081', '#E040FB', '#7C4DFF', '#536DFE',
'#448AFF', '#40C4FF', '#18FFFF', '#64FFDA', '#69F0AE',
'#B2FF59', '#EEFF41', '#FFFF00', '#FFD740', '#FFAB40'
];
// MODIFICAR: Cantidad de estrellas en el fondo.
const STAR_COUNT = 150;
// --- FIN DE LA ZONA DE MODIFICACIÓN ---
function createMessageLetters() {
messageContainer.innerHTML = '';
fullMessage.split('').forEach(char => {
const span = document.createElement('span');
span.textContent = char === ' ' ? ' ' : char; // Espacio no rompible
span.classList.add('initial');
messageContainer.appendChild(span);
letters.push(span);
});
}
function revealNextLetter() {
if (currentLetterIndex < letters.length) {
const letterSpan = letters[currentLetterIndex];
letterSpan.classList.add('visible');
letterSpan.classList.remove('initial');
// Efecto de "pop" al aparecer la letra
letterSpan.style.transform = 'translateY(-5px) scale(1.1)';
setTimeout(() => {
letterSpan.style.transform = 'translateY(0) scale(1)';
}, 100);
currentLetterIndex++;
}
}
function createParticle(x, y, color) {
const particle = document.createElement('div');
particle.classList.add('particle');
particle.style.backgroundColor = color;
// MODIFICAR: Tamaño de las partículas de la explosión. `* 5` es el rango, `+ 2` es el tamaño mínimo.
const size = Math.random() * 5 + 2;
particle.style.width = `${size}px`;
particle.style.height = `${size}px`;
particle.style.left = `${x}px`;
particle.style.top = `${y}px`;
const angle = Math.random() * Math.PI * 2;
// MODIFICAR: Velocidad de la explosión. `* 80` es el rango, `+ 50` es la velocidad base.
const speed = Math.random() * 80 + 50;
const deltaX = Math.cos(angle) * speed;
const deltaY = Math.sin(angle) * speed;
let currentX = x;
let currentY = y;
let vx = deltaX / 20;
let vy = deltaY / 20;
// MODIFICAR: Gravedad que afecta a las partículas (cómo caen).
const gravity = 0.1;
let opacity = 1;
// MODIFICAR: Velocidad a la que las partículas se desvanecen. Un número más alto las hace desaparecer más rápido.
const fadeSpeed = 0.04;
fireworksContainer.appendChild(particle);
function updateParticle() {
currentX += vx;
currentY += vy;
vy += gravity;
opacity -= fadeSpeed;
if (opacity <= 0) {
particle.remove();
return;
}
particle.style.transform = `translate(${currentX - x}px, ${currentY - y}px)`;
particle.style.opacity = opacity;
requestAnimationFrame(updateParticle);
}
requestAnimationFrame(updateParticle);
}
function createExplosion(x, y) {
// MODIFICAR: Cantidad de partículas por explosión. `* 50` es el rango, `+ 50` es la cantidad base.
const particleCount = 50 + Math.floor(Math.random() * 50);
const baseColor = fireworkColors[Math.floor(Math.random() * fireworkColors.length)];
for (let i = 0; i < particleCount; i++) {
// El 0.7 significa que el 70% de las partículas tendrán el color base.
const colorVariation = Math.random() < 0.7 ? baseColor : fireworkColors[Math.floor(Math.random() * fireworkColors.length)];
createParticle(x, y, colorVariation);
}
if (currentLetterIndex < fullMessage.length) {
revealNextLetter();
}
}
function launchFirework() {
const rocket = document.createElement('div');
rocket.classList.add('rocket');
const startX = Math.random() * (window.innerWidth * 0.6) + (window.innerWidth * 0.2);
rocket.style.left = `${startX}px`;
fireworksContainer.appendChild(rocket);
// MODIFICAR: Duración (en milisegundos) del ascenso del cohete.
const launchDuration = 1000;
rocket.style.animationDuration = `${launchDuration / 1000}s`;
setTimeout(() => {
rocket.remove();
const explosionY = window.innerHeight * (0.2 + Math.random() * 0.1);
createExplosion(startX, explosionY);
}, launchDuration * 0.95);
if (currentLetterIndex < letters.length) {
// MODIFICAR: Frecuencia de lanzamiento (en milisegundos). `* 400` es rango, `+ 300` es base.
setTimeout(launchFirework, Math.random() * 400 + 300);
} else {
// MODIFICAR: Gran final. Lanza `5` fuegos artificiales más, cada `400`ms.
setTimeout(() => launchFireworkSeries(5, 400), 1000);
}
}
function launchFireworkSeries(count, interval) {
if (count <= 0) return;
const rocket = document.createElement('div');
rocket.classList.add('rocket');
const startX = Math.random() * (window.innerWidth * 0.6) + (window.innerWidth * 0.2);
rocket.style.left = `${startX}px`;
fireworksContainer.appendChild(rocket);
const launchDuration = 1000;
rocket.style.animationDuration = `${launchDuration / 1000}s`;
setTimeout(() => {
rocket.remove();
const explosionY = window.innerHeight * (0.2 + Math.random() * 0.1);
createExplosion(startX, explosionY);
}, launchDuration * 0.95);
setTimeout(() => launchFireworkSeries(count - 1, interval), interval);
}
function createStars() {
const body = document.body;
document.querySelectorAll('.star').forEach(star => star.remove());
for (let i = 0; i < STAR_COUNT; i++) {
const star = document.createElement('div');
star.classList.add('star');
star.style.left = `${Math.random() * 100}%`;
star.style.top = `${Math.random() * 100}%`;
star.style.animationDelay = `${Math.random() * 2}s`;
const size = Math.random() * 2 + 1;
star.style.width = `${size}px`;
star.style.height = `${size}px`;
body.insertBefore(star, body.firstChild);
}
}
document.addEventListener('DOMContentLoaded', () => {
createStars();
createMessageLetters();
// MODIFICAR: Retraso inicial (en milisegundos) antes de que se lance el primer fuego artificial.
setTimeout(launchFirework, 300);
});
window.addEventListener('resize', () => {
currentLetterIndex = 0;
createMessageLetters();
createStars(STAR_COUNT);
});