<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Carta con Corazón de Neón</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="stars-bg"></div>
<div class="container">
<div class="letter">
<pre id="letter-content"></pre>
</div>
<div class="heart-container">
<svg class="heart" viewBox="0 0 200 200">
<g id="heart-shape-group">
<path class="heart-glow" d="M 100,60 C 80,20 40,30 40,60 C 40,90 100,140 100,140 C 100,140 160,90 160,60 C 160,30 120,20 100,60 Z" />
<path id="heart-path" class="heart-path" d="M 100,60 C 80,20 40,30 40,60 C 40,90 100,140 100,140 C 100,140 160,90 160,60 C 160,30 120,20 100,60 Z" />
</g>
<path id="arrow-motion-path" d="M 20,-20 L 180,220" fill="none" stroke="none" />
<g id="arrow-group">
<path class="arrow-path" d="M -30,0 L 15,0 M 5,-5 L 15,0 L 5,5" />
</g>
<!-- MODIFICAR: Nombre de la persona -->
<text id="heart-text" x="100" y="95" text-anchor="middle" dominant-baseline="middle">
Su nombre :)
</text>
</svg>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
@import url('https://fonts.googleapis.com/css2?family=Special+Elite&display=swap');
:root {
--neon-color: #ff007f;
--background-color: #1a1a2e;
--text-color: #e0e0e0;
--cursor-color: #ffffff;
}
body {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: var(--background-color);
font-family: 'Special Elite', cursive, sans-serif;
color: var(--text-color);
overflow-x: hidden;
overflow-y: auto;
}
.stars-bg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: transparent;
z-index: -1;
background-image:
radial-gradient(1px 1px at 10% 20%, white, transparent),
radial-gradient(1px 1px at 80% 10%, white, transparent),
radial-gradient(2px 2px at 50% 30%, white, transparent),
radial-gradient(2px 2px at 90% 40%, white, transparent),
radial-gradient(1px 1px at 30% 80%, white, transparent),
radial-gradient(2px 2px at 45% 55%, white, transparent),
radial-gradient(1px 1px at 60% 70%, white, transparent),
radial-gradient(2px 2px at 5% 5%, white, transparent),
radial-gradient(1px 1px at 95% 95%, white, transparent),
radial-gradient(2px 2px at 70% 85%, white, transparent),
radial-gradient(1px 1px at 25% 60%, white, transparent),
radial-gradient(2px 2px at 85% 25%, white, transparent);
animation: move-stars 200s linear infinite;
}
@keyframes move-stars {
from { transform: translateY(0); }
to { transform: translateY(-2000px); }
}
.container {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
gap: 2rem;
padding: 2rem;
max-width: 1200px;
width: 90%;
animation: fade-in 1.5s ease-out;
position: relative;
z-index: 1;
}
@keyframes fade-in {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.letter {
background: rgba(255, 255, 255, 0.05);
border-radius: 15px;
padding: 2rem;
width: 100%;
max-width: 500px;
min-height: 350px;
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
border: 1px solid rgba(255, 255, 255, 0.2);
position: relative;
animation: float-effect 6s ease-in-out infinite;
}
#letter-content {
font-size: 1.1rem;
line-height: 1.8;
margin: 0;
font-family: inherit;
white-space: pre-wrap;
word-wrap: break-word;
}
#letter-content::after {
content: '▋';
display: inline-block;
animation: blink 0.7s infinite;
color: var(--cursor-color);
margin-left: 2px;
}
#letter-content.typing-done::after {
display: none;
}
.heart-container {
width: 100%;
max-width: 550px;
display: flex;
justify-content: center;
align-items: center;
animation: float-effect 6s ease-in-out infinite;
animation-delay: -3s;
}
.heart {
width: 100%;
height: auto;
overflow: visible;
}
.heart.finished {
animation: pulse 1.5s infinite ease-in-out;
}
#heart-shape-group {
transform-origin: center;
}
.heart-glow {
fill: none;
stroke: var(--neon-color);
stroke-width: 5;
filter: drop-shadow(0 0 7px var(--neon-color))
drop-shadow(0 0 20px var(--neon-color))
drop-shadow(0 0 35px rgba(255, 0, 127, 0.5));
opacity: 0;
transition: opacity 1s ease;
}
.heart-path {
fill: transparent;
stroke: var(--neon-color);
stroke-width: 5;
stroke-linecap: round;
stroke-linejoin: round;
transition: stroke-dashoffset 0.1s linear;
}
#heart-text {
font-family: 'Special Elite', cursive, sans-serif;
font-size: 15px;
fill: #fff;
text-shadow: 0 0 4px #fff, 0 0 10px var(--neon-color);
opacity: 0;
transition: opacity 1.5s ease-in 0.5s;
}
.heart.finished .heart-glow,
.heart.finished #heart-text {
opacity: 1;
}
#arrow-group {
offset-path: path('M 20,-20 L 180,220');
offset-rotate: auto;
offset-distance: 0%;
opacity: 0;
}
.arrow-path {
fill: none;
stroke: var(--neon-color);
stroke-width: 4;
stroke-linecap: round;
stroke-linejoin: round;
filter: drop-shadow(0 0 5px var(--neon-color)) drop-shadow(0 0 15px var(--neon-color));
}
.heart.finished #arrow-group {
animation: fly-arrow 1s ease-in-out 0.5s forwards;
}
.heart.finished #heart-shape-group {
animation: heart-hit-scale 0.4s ease-out 0.9s forwards;
}
.heart.finished .heart-glow {
animation: heart-hit-glow 0.4s ease-out 0.9s forwards;
}
@keyframes fly-arrow {
0% { offset-distance: 0%; opacity: 0; }
10% { opacity: 1; }
90% { opacity: 1; }
100% { offset-distance: 100%; opacity: 0; }
}
@keyframes heart-hit-scale {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
}
@keyframes heart-hit-glow {
0%, 100% {
filter: drop-shadow(0 0 7px var(--neon-color))
drop-shadow(0 0 20px var(--neon-color))
drop-shadow(0 0 35px rgba(255, 0, 127, 0.5));
}
50% {
filter: drop-shadow(0 0 15px #fff)
drop-shadow(0 0 30px var(--neon-color))
drop-shadow(0 0 55px var(--neon-color));
}
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
@keyframes float-effect {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
.sparkle {
position: absolute;
background: var(--neon-color);
border-radius: 50%;
pointer-events: none;
z-index: 9999;
animation: sparkle-fade 1s forwards;
box-shadow: 0 0 5px var(--neon-color), 0 0 10px var(--neon-color);
}
@keyframes sparkle-fade {
from {
opacity: 1;
transform: scale(1) translateY(0);
}
to {
opacity: 0;
transform: scale(0) translateY(-50px);
}
}
@media (max-width: 768px) {
body {
align-items: flex-start;
padding-top: 2rem;
}
.container {
flex-direction: column;
padding: 1rem;
gap: 1.5rem;
width: 95%;
}
.letter {
padding: 1.5rem;
min-height: auto;
}
#letter-content {
font-size: 1rem;
}
.heart-container {
max-width: 300px;
}
#heart-text {
font-size: 13px;
}
.letter, .heart-container {
animation: none;
}
}
document.addEventListener('DOMContentLoaded', function() {
const letterContentEl = document.getElementById('letter-content');
const heartEl = document.querySelector('.heart');
const heartPath = document.getElementById('heart-path');
/* MODIFICAR: El mensaje de la carta */
const text = "Querido amor de mi vida,\n\n" +
"Cada día que pasa me doy cuenta de lo afortunado que soy de tenerte a mi lado. Tu sonrisa ilumina mis días más oscuros y tu amor me da la fuerza para enfrentar cualquier desafío.\n\n" +
"Eres mi refugio en las tormentas, mi alegría en los momentos felices y mi compañero en esta hermosa aventura llamada vida.\n\n" +
"No existen palabras suficientes para expresar todo lo que sientes en mi corazón, pero quiero que sepas que mi amor por ti crece cada día más fuerte.\n\n" +
"Gracias por ser exactamente quien eres, por amarme tal como soy y por hacer de cada momento algo especial.\n\n" +
"Con todo mi amor infinito,\n" +
"Tu eterno enamorado ❤️";
/* MODIFICAR: Velocidad de escritura */
const typingSpeed = 30;
const heartPathLength = heartPath.getTotalLength();
heartPath.style.strokeDasharray = heartPathLength;
heartPath.style.strokeDashoffset = heartPathLength;
let i = 0;
function typeWriter() {
if (i < text.length) {
letterContentEl.innerHTML += text.charAt(i);
const progress = i / (text.length - 1);
const newDashoffset = heartPathLength * (1 - progress);
heartPath.style.strokeDashoffset = newDashoffset;
i++;
setTimeout(typeWriter, typingSpeed);
} else {
letterContentEl.classList.add('typing-done');
heartEl.classList.add('finished');
}
}
typeWriter();
function createSparkle(x, y) {
const sparkle = document.createElement('div');
sparkle.className = 'sparkle';
document.body.appendChild(sparkle);
const size = Math.random() * 6 + 2;
sparkle.style.width = `${size}px`;
sparkle.style.height = `${size}px`;
sparkle.style.left = `${x - size / 2}px`;
sparkle.style.top = `${y - size / 2}px`;
const randomX = Math.random() * 40 - 20;
const randomY = Math.random() * 40 - 20;
sparkle.addEventListener('animationend', () => {
sparkle.remove();
});
}
document.addEventListener('mousemove', (e) => {
createSparkle(e.clientX, e.clientY);
});
document.addEventListener('touchmove', (e) => {
if (e.touches.length > 0) {
createSparkle(e.touches[0].clientX, e.touches[0].clientY);
}
});
});