<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>Buenas noches, Lulita ✨</title>
<link href="https://fonts.googleapis.com/css2?family=Dancing+Script:wght@400;600;700&family=Quicksand:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
min-height: 100vh;
background: radial-gradient(ellipse at center, #0a0f2a 0%, #030617 100%);
display: flex;
align-items: center;
justify-content: center;
font-family: 'Quicksand', sans-serif;
padding: 1.5rem;
position: relative;
overflow-x: hidden;
cursor: pointer;
}
/* === ESTRELLAS FIJAS (fondo) === */
.stars {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 0;
}
.star {
position: absolute;
background-color: #fff;
border-radius: 50%;
opacity: 0.7;
animation: twinkle 2s infinite alternate;
}
@keyframes twinkle {
0% { opacity: 0.2; transform: scale(1);}
100% { opacity: 1; transform: scale(1.2);}
}
/* === ESTRELLAS FUGAZES (canvas) === */
#shootingStarsCanvas {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 1;
}
/* === SOBRE Y CARTA === */
.container {
position: relative;
z-index: 20;
max-width: 700px;
width: 100%;
perspective: 1500px;
}
/* Sobre cerrado */
.envelope {
background: linear-gradient(145deg, #1e2a3a, #0f1724);
border-radius: 32px;
padding: 2rem 1.5rem 2rem;
text-align: center;
box-shadow: 0 30px 40px rgba(0,0,0,0.5), 0 0 0 4px #f9e0a0, 0 0 0 8px #b87c4f;
cursor: pointer;
transition: transform 0.4s cubic-bezier(0.2, 0.9, 0.4, 1.1), box-shadow 0.3s;
backdrop-filter: blur(2px);
border: 1px solid rgba(255,215,150,0.6);
}
.envelope:hover {
transform: scale(1.02);
box-shadow: 0 35px 50px rgba(0,0,0,0.6), 0 0 0 4px #ffefb0, 0 0 0 8px #d4945a;
}
.moon-icon {
font-size: 4rem;
color: #f9e0a0;
filter: drop-shadow(0 0 8px #ffd966);
margin-bottom: 0.8rem;
}
.envelope h2 {
font-family: 'Dancing Script', cursive;
font-size: 2.2rem;
color: #ffefcf;
text-shadow: 0 2px 5px #2c1a0e;
margin: 0.5rem 0;
}
.envelope p {
color: #cbd5e6;
font-weight: 500;
margin-bottom: 1.5rem;
}
.open-btn {
background: linear-gradient(135deg, #f3b33d, #e07c1f);
border: none;
padding: 12px 28px;
font-size: 1.2rem;
font-weight: bold;
border-radius: 50px;
color: #1e1a0c;
cursor: pointer;
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
transition: 0.2s;
display: inline-flex;
align-items: center;
gap: 12px;
font-family: 'Quicksand', sans-serif;
}
.open-btn:active {
transform: scale(0.96);
}
.star-deco {
margin-top: 1.8rem;
font-size: 1.4rem;
color: #ffda88;
display: flex;
justify-content: center;
gap: 1rem;
}
/* CARTA (contenido) - inicialmente oculta */
.letter-card {
display: none;
background: rgba(20, 15, 35, 0.85);
backdrop-filter: blur(12px);
border-radius: 48px;
padding: 2rem 1.8rem;
box-shadow: 0 25px 45px rgba(0,0,0,0.5), 0 0 0 2px #ffecb3, 0 0 0 5px #b46f3a;
animation: floatIn 0.7s ease-out forwards;
border: 1px solid rgba(255,225,140,0.6);
}
@keyframes floatIn {
0% {
opacity: 0;
transform: translateY(40px) rotateX(-10deg);
}
100% {
opacity: 1;
transform: translateY(0) rotateX(0);
}
}
.letter-header {
text-align: center;
font-size: 2.5rem;
color: #ffdfa5;
margin-bottom: 0.5rem;
}
.letter-header i {
margin: 0 0.5rem;
filter: drop-shadow(0 0 4px gold);
}
.message-content {
background: rgba(0,0,0,0.4);
border-radius: 32px;
padding: 1.8rem;
margin: 1.2rem 0;
font-family: 'Quicksand', sans-serif;
font-size: 1.15rem;
line-height: 1.6;
color: #fef7e0;
text-align: center;
font-weight: 500;
border: 1px solid rgba(255,215,120,0.4);
box-shadow: inset 0 0 12px rgba(0,0,0,0.3), 0 6px 12px rgba(0,0,0,0.2);
}
.message-content p {
margin: 0.6rem 0;
}
.signature {
text-align: right;
font-family: 'Dancing Script', cursive;
font-size: 1.5rem;
margin-top: 1rem;
color: #ffda88;
border-top: 1px dashed #ffcf7a;
padding-top: 1rem;
}
.night-flowers {
display: flex;
justify-content: center;
gap: 1rem;
font-size: 1.5rem;
color: #b9d0ff;
margin-top: 1rem;
}
/* Responsive */
@media (max-width: 550px) {
.envelope h2 { font-size: 1.6rem; }
.message-content { font-size: 1rem; padding: 1.2rem; }
.letter-card { padding: 1.5rem; }
}
/* ocultar elemento al abrir */
.hidden-envelope {
display: none;
}
</style>
</head>
<body>
<!-- Estrellas fijas de fondo (estáticas) -->
<div class="stars" id="staticStars"></div>
<!-- Canvas para estrellas fugaces -->
<canvas id="shootingStarsCanvas"></canvas>
<div class="container">
<!-- SOBRE CERRADO -->
<div id="envelopeClosed" class="envelope">
<div class="moon-icon">
<i class="fas fa-moon"></i> <i class="fas fa-star"></i>
</div>
<h2>🌙 Para mi Lulita 🌙</h2>
<p>Una última carta antes de dormir...</p>
<button id="openEnvelopeBtn" class="open-btn"><i class="fas fa-envelope-open"></i> Abrir con el corazón</button>
<div class="star-deco">
<i class="fas fa-star"></i> <i class="fas fa-star"></i> <i class="fas fa-star"></i>
<i class="fas fa-cloud-moon"></i> <i class="fas fa-star"></i>
</div>
<div style="margin-top: 1rem; font-size: 0.8rem; color:#b9b3e0;">
<i class="fas fa-crown"></i> Toca para abrir <i class="fas fa-gem"></i>
</div>
</div>
<!-- CARTA ABIERTA (contenido) -->
<div id="letterOpened" class="letter-card">
<div class="letter-header">
<i class="fas fa-star-of-life"></i> ✨ Buenas noches, mi amor ✨ <i class="fas fa-star-of-life"></i>
</div>
<div class="message-content">
<p>🌠 hasta mas enseguida lulita,</p>
<p>espero que me ames siempre, estar siempre juntos y pasar esta mala etapa adelante, por q yo juro q te amo y quiero estar con vs por siempre.</p>
<p>que tengas lindos sueños y q amanezcas con todas las pilas y espero algun dia proponerte q seas mi novia de vuelta y de una manera mejor,</p>
<p>nos vemos mañana te amo. 💖🌙</p>
</div>
<div class="signature">
<i class="fas fa-feather-alt"></i> Con todo mi corazón
</div>
<div class="night-flowers">
<i class="fas fa-moon"></i> <i class="fas fa-star"></i> <i class="fas fa-cloud-moon"></i> <i class="fas fa-star"></i>
</div>
</div>
</div>
<script>
// ========== ESTRELLAS FUGAZES CON CANVAS (animación suave) ==========
const canvas = document.getElementById('shootingStarsCanvas');
let ctx = canvas.getContext('2d');
let width, height;
let meteors = [];
const MAX_METEORS = 5;
let lastTimestamp = 0;
function resizeCanvas() {
width = window.innerWidth;
height = window.innerHeight;
canvas.width = width;
canvas.height = height;
}
window.addEventListener('resize', () => {
resizeCanvas();
initStaticStars(); // regenerar estrellas estáticas también
});
// Clase para estrella fugaz
class ShootingStar {
constructor() {
this.reset();
}
reset() {
this.x = Math.random() * width;
this.y = Math.random() * (height * 0.5); // empieza en mitad superior
this.length = Math.random() * 80 + 40;
this.speed = Math.random() * 8 + 6;
this.angle = Math.random() * (Math.PI / 3) - Math.PI / 6; // entre -30° y 30° (inclinación)
this.opacity = Math.random() * 0.6 + 0.4;
this.active = true;
this.trail = [];
this.maxTrail = 12;
}
update() {
if (!this.active) return false;
// mover
this.x += Math.cos(this.angle) * this.speed;
this.y += Math.sin(this.angle) * this.speed;
// guardar rastro
this.trail.push({ x: this.x, y: this.y });
if (this.trail.length > this.maxTrail) this.trail.shift();
// si sale de pantalla o se aleja mucho, reiniciar
if (this.x > width + 200 || this.x < -200 || this.y > height + 200 || this.y < -200) {
this.reset();
this.x = Math.random() * width;
this.y = Math.random() * (height * 0.4);
return true;
}
return true;
}
draw(ctx) {
if (!this.active) return;
ctx.save();
ctx.beginPath();
// dibujar rastro (estela)
for (let i = 0; i < this.trail.length; i++) {
const point = this.trail[i];
const alpha = (i / this.trail.length) * this.opacity * 0.7;
ctx.beginPath();
ctx.arc(point.x, point.y, 2.2 * (i / this.trail.length), 0, Math.PI * 2);
ctx.fillStyle = `rgba(255, 240, 170, ${alpha * 0.8})`;
ctx.fill();
}
// cabeza brillante
ctx.shadowBlur = 12;
ctx.shadowColor = "#ffd966";
ctx.beginPath();
ctx.arc(this.x, this.y, 3.5, 0, Math.PI * 2);
ctx.fillStyle = `rgba(255, 235, 140, ${this.opacity + 0.3})`;
ctx.fill();
ctx.beginPath();
ctx.arc(this.x, this.y, 2, 0, Math.PI * 2);
ctx.fillStyle = `white`;
ctx.fill();
ctx.restore();
}
}
function initMeteors() {
meteors = [];
for (let i = 0; i < MAX_METEORS; i++) {
let star = new ShootingStar();
star.reset();
// aleatorizar posiciones iniciales distribuidas
star.x = Math.random() * width;
star.y = Math.random() * (height * 0.5);
star.trail = [];
meteors.push(star);
}
}
let animationFrame;
function animateShootingStars() {
if (!ctx) return;
ctx.clearRect(0, 0, width, height);
for (let meteor of meteors) {
meteor.update();
meteor.draw(ctx);
}
animationFrame = requestAnimationFrame(animateShootingStars);
}
// ========== ESTRELLAS ESTÁTICAS (fondo) ==========
function initStaticStars() {
const starsContainer = document.getElementById('staticStars');
starsContainer.innerHTML = '';
const starCount = 220;
for (let i = 0; i < starCount; i++) {
const star = document.createElement('div');
star.classList.add('star');
const size = Math.random() * 3 + 1.5;
star.style.width = size + 'px';
star.style.height = size + 'px';
star.style.left = Math.random() * 100 + '%';
star.style.top = Math.random() * 100 + '%';
star.style.animationDelay = Math.random() * 3 + 's';
star.style.animationDuration = Math.random() * 2 + 1.5 + 's';
starsContainer.appendChild(star);
}
}
// ========== APERTURA DEL SOBRE CON MOVIMIENTO ==========
const envelopeDiv = document.getElementById('envelopeClosed');
const letterDiv = document.getElementById('letterOpened');
const openButton = document.getElementById('openEnvelopeBtn');
function openEnvelopeWithAnimation() {
// Animación de movimiento: rotación, desvanecimiento y "vuelo"
envelopeDiv.style.transition = 'transform 0.5s cubic-bezier(0.34, 1.2, 0.64, 1), opacity 0.4s';
envelopeDiv.style.transform = 'rotateX(90deg) scale(0.7)';
envelopeDiv.style.opacity = '0';
setTimeout(() => {
envelopeDiv.classList.add('hidden-envelope');
envelopeDiv.style.display = 'none';
// Mostrar carta con efecto
letterDiv.style.display = 'block';
// Disparar confeti de estrellas (estrellitas brillantes)
shootStarConfetti();
// también generar una ráfaga de estrellas fugaces extra
for(let i=0; i<3; i++) {
setTimeout(() => {
createExtraShootingStar();
}, i * 200);
}
}, 500);
}
// confeti estilo estrellas y lunas
function shootStarConfetti() {
// Pequeña lluvia de confeti con forma de estrella (usamos canvas-confetti)
if (typeof canvasConfetti !== 'undefined') {
canvasConfetti({ particleCount: 120, spread: 70, origin: { y: 0.6 }, colors: ['#ffec99', '#ffd966', '#f3c26b', '#ffe0a3'], shapes: ['star'] });
canvasConfetti({ particleCount: 60, spread: 45, origin: { y: 0.3, x: 0.2 }, startVelocity: 18, colors: ['#ffdfa5'] });
canvasConfetti({ particleCount: 60, spread: 45, origin: { y: 0.3, x: 0.8 }, startVelocity: 18, colors: ['#f5e3b0'] });
} else {
// fallback simple
console.log("✨ Estrellas mágicas ✨");
}
}
function createExtraShootingStar() {
// generar una estrella fugaz nueva visible de inmediato
if (meteors.length > 0) {
let newMeteor = new ShootingStar();
newMeteor.x = Math.random() * width;
newMeteor.y = Math.random() * (height * 0.3);
newMeteor.active = true;
newMeteor.speed = Math.random() * 12 + 8;
meteors.push(newMeteor);
setTimeout(() => {
// eliminar después de su vida (opcional)
const index = meteors.indexOf(newMeteor);
if (index !== -1) meteors.splice(index, 1);
}, 4000);
}
}
// Eventos para abrir el sobre (click en el botón o en el sobre)
openButton.addEventListener('click', (e) => {
e.stopPropagation();
openEnvelopeWithAnimation();
});
envelopeDiv.addEventListener('click', (e) => {
if (e.target === openButton || openButton.contains(e.target)) return;
openEnvelopeWithAnimation();
});
// Inicialización completa
window.addEventListener('load', () => {
resizeCanvas();
initStaticStars();
initMeteors();
animateShootingStars();
// Cargar canvas-confetti desde CDN si no existe (asegurar)
if (typeof canvasConfetti === 'undefined') {
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/canvas-confetti@1';
script.onload = () => {
console.log('Confetti listo');
};
document.head.appendChild(script);
}
// Pequeño detalle: cada 6 segundos aparece una estrella fugaz nueva aleatoria
setInterval(() => {
if (meteors.length < MAX_METEORS + 2) {
let extra = new ShootingStar();
extra.x = Math.random() * width;
extra.y = Math.random() * (height * 0.4);
extra.speed = Math.random() * 10 + 7;
meteors.push(extra);
setTimeout(() => {
const idx = meteors.indexOf(extra);
if (idx !== -1) meteors.splice(idx, 1);
}, 5000);
}
}, 8000);
});
</script>
<!-- Aseguramos que el canvas-confetti esté cargado -->
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1"></script>
</body>
</html>0 views