<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Happy Valentine's Day | A Digital Love Letter</title>
<!-- Import Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Dancing+Script:wght@600;700&family=Nunito:wght@300;400;700&display=swap" rel="stylesheet">
<!-- Font Awesome for Icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* --- CSS Variables & Reset --- */
:root {
--primary: #e91e63;
--primary-dark: #c2185b;
--secondary: #ff80ab;
--bg-color: #fce4ec;
--text-color: #4a4a4a;
--white: #ffffff;
--glass: rgba(255, 255, 255, 0.7);
--shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.15);
--transition: all 0.3s ease-in-out;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
font-family: 'Nunito', sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
overflow-x: hidden;
line-height: 1.6;
}
h1,
h2,
h3 {
font-family: 'Dancing Script', cursive;
color: var(--primary);
}
/* --- Canvas Background --- */
#heart-canvas {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
pointer-events: none;
}
/* --- Navigation --- */
nav {
position: fixed;
top: 0;
width: 100%;
padding: 1.5rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
z-index: 1000;
background: rgba(252, 228, 236, 0.8);
backdrop-filter: blur(10px);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}
.logo {
font-size: 1.8rem;
font-weight: 700;
color: var(--primary);
}
.nav-links button {
background: transparent;
border: 2px solid var(--primary);
padding: 0.5rem 1.2rem;
border-radius: 50px;
cursor: pointer;
font-family: 'Nunito', sans-serif;
font-weight: 600;
color: var(--primary);
transition: var(--transition);
}
.nav-links button:hover {
background: var(--primary);
color: var(--white);
}
/* --- Hero Section --- */
.hero {
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
padding: 2rem;
position: relative;
}
.hero h1 {
font-size: 5rem;
margin-bottom: 1rem;
opacity: 0;
animation: fadeUp 1s ease forwards 0.5s;
}
.hero p {
font-size: 1.5rem;
max-width: 600px;
margin-bottom: 2.5rem;
opacity: 0;
animation: fadeUp 1s ease forwards 1s;
}
.pulse-heart {
font-size: 4rem;
color: var(--primary);
cursor: pointer;
animation: heartbeat 1.5s infinite;
margin-bottom: 1rem;
opacity: 0;
animation: heartbeat 1.5s infinite, fadeUp 1s ease forwards 1.5s;
}
.scroll-down {
position: absolute;
bottom: 2rem;
font-size: 2rem;
color: var(--primary);
animation: bounce 2s infinite;
}
/* --- Memories Timeline Section --- */
.section-title {
text-align: center;
font-size: 3.5rem;
margin: 4rem 0 3rem;
position: relative;
display: inline-block;
left: 50%;
transform: translateX(-50%);
}
.section-title::after {
content: '';
display: block;
width: 60%;
height: 3px;
background: var(--secondary);
margin: 0.5rem auto 0;
border-radius: 2px;
}
.timeline {
max-width: 800px;
margin: 0 auto;
padding: 2rem;
position: relative;
}
.timeline::before {
content: '';
position: absolute;
left: 50%;
transform: translateX(-50%);
width: 2px;
height: 100%;
background: var(--secondary);
}
.timeline-item {
display: flex;
justify-content: flex-end;
padding-right: 3rem;
position: relative;
margin-bottom: 2rem;
width: 50%;
opacity: 0;
/* Hidden initially for JS Observer */
transform: translateY(30px);
transition: var(--transition);
}
.timeline-item:nth-child(even) {
align-self: flex-end;
justify-content: flex-start;
padding-right: 0;
padding-left: 3rem;
left: 50%;
}
.timeline-dot {
position: absolute;
right: -8px;
top: 0;
width: 16px;
height: 16px;
background: var(--primary);
border-radius: 50%;
border: 3px solid var(--white);
z-index: 2;
}
.timeline-item:nth-child(even) .timeline-dot {
right: auto;
left: -8px;
}
.timeline-content {
background: var(--white);
padding: 1.5rem;
border-radius: 15px;
box-shadow: var(--shadow);
width: 100%;
position: relative;
}
.timeline-content h3 {
font-family: 'Nunito', sans-serif;
font-weight: 700;
color: var(--primary-dark);
margin-bottom: 0.5rem;
}
.timeline-content span {
font-size: 0.85rem;
color: #888;
font-style: italic;
}
/* --- Reasons (Flip Cards) --- */
.reasons-section {
padding: 4rem 2rem;
background: rgba(255, 255, 255, 0.4);
}
.cards-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
max-width: 1200px;
margin: 0 auto;
}
.flip-card {
background-color: transparent;
height: 300px;
perspective: 1000px;
/* Enable 3D effect */
opacity: 0;
transform: translateY(30px);
transition: var(--transition);
}
.flip-card-inner {
position: relative;
width: 100%;
height: 100%;
text-align: center;
transition: transform 0.8s;
transform-style: preserve-3d;
border-radius: 20px;
box-shadow: var(--shadow);
cursor: pointer;
}
.flip-card:hover .flip-card-inner {
transform: rotateY(180deg);
}
.flip-card-front,
.flip-card-back {
position: absolute;
width: 100%;
height: 100%;
-webkit-backface-visibility: hidden;
/* Safari */
backface-visibility: hidden;
border-radius: 20px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 2rem;
}
.flip-card-front {
background: var(--white);
border: 2px solid var(--secondary);
}
.flip-card-front i {
font-size: 3rem;
color: var(--primary);
margin-bottom: 1rem;
}
.flip-card-back {
background: var(--primary);
color: var(--white);
transform: rotateY(180deg);
}
/* --- Gallery Section --- */
.gallery {
padding: 4rem 2rem;
max-width: 1000px;
margin: 0 auto;
}
.photo-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
}
.photo-item {
border-radius: 15px;
overflow: hidden;
height: 250px;
box-shadow: var(--shadow);
opacity: 0;
transform: scale(0.9);
transition: var(--transition);
}
.photo-item img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.5s ease;
}
.photo-item:hover img {
transform: scale(1.1);
}
/* --- Interactive Surprise Section --- */
.surprise-section {
min-height: 50vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
padding: 2rem;
}
.open-gift-btn {
background: var(--primary);
color: var(--white);
border: none;
padding: 1rem 3rem;
font-size: 1.2rem;
border-radius: 50px;
cursor: pointer;
box-shadow: 0 4px 15px rgba(233, 30, 99, 0.4);
transition: var(--transition);
font-family: 'Nunito', sans-serif;
font-weight: 700;
display: flex;
align-items: center;
gap: 10px;
}
.open-gift-btn:hover {
transform: translateY(-3px) scale(1.05);
box-shadow: 0 6px 20px rgba(233, 30, 99, 0.6);
}
/* --- Modal --- */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(5px);
display: flex;
justify-content: center;
align-items: center;
z-index: 2000;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease;
}
.modal-overlay.active {
opacity: 1;
pointer-events: auto;
}
.modal-content {
background: var(--white);
padding: 3rem;
border-radius: 20px;
max-width: 500px;
width: 90%;
text-align: center;
position: relative;
transform: scale(0.7);
transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
}
.modal-overlay.active .modal-content {
transform: scale(1);
}
.close-modal {
position: absolute;
top: 15px;
right: 20px;
font-size: 1.5rem;
cursor: pointer;
color: #aaa;
transition: color 0.3s;
}
.close-modal:hover {
color: var(--primary);
}
/* --- Footer --- */
footer {
text-align: center;
padding: 2rem;
background: var(--primary);
color: var(--white);
font-size: 0.9rem;
}
/* --- Keyframes --- */
@keyframes fadeUp {
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes bounce {
0%,
20%,
50%,
80%,
100% {
transform: translateY(0);
}
40% {
transform: translateY(-10px);
}
60% {
transform: translateY(-5px);
}
}
@keyframes heartbeat {
0% {
transform: scale(1);
}
14% {
transform: scale(1.1);
}
28% {
transform: scale(1);
}
42% {
transform: scale(1.1);
}
70% {
transform: scale(1);
}
}
/* --- Utility Class for Observer --- */
.visible {
opacity: 1 !important;
transform: translateY(0) scale(1) !important;
}
/* Mobile Responsive */
@media (max-width: 768px) {
.hero h1 {
font-size: 3.5rem;
}
.timeline::before {
left: 20px;
}
.timeline-item {
width: 100%;
padding-left: 45px;
padding-right: 0;
}
.timeline-item:nth-child(even) {
left: 0;
padding-left: 45px;
}
.timeline-dot {
left: 12px;
right: auto;
}
.timeline-item:nth-child(even) .timeline-dot {
left: 12px;
}
}
</style>
</head>
<body>
<!-- Background Canvas -->
<canvas id="heart-canvas"></canvas>
<!-- Navigation -->
<nav>
<div class="logo"><i class="fas fa-heart"></i> Love</div>
<div class="nav-links">
<button onclick="scrollToSection('surprise')">Open Gift</button>
</div>
</nav>
<!-- Hero Section -->
<section class="hero">
<div class="pulse-heart"><i class="fas fa-heart"></i></div>
<h1>Happy Valentine's Day</h1>
<p>Every love story is beautiful, but ours is my favorite. This is a tiny digital corner to celebrate us.</p>
<div class="scroll-down" onclick="scrollToSection('timeline')">
<i class="fas fa-chevron-down"></i>
</div>
</section>
<!-- Timeline / Journey -->
<section id="timeline" class="timeline-section">
<h2 class="section-title">Our Journey</h2>
<div class="timeline">
<div class="timeline-item">
<div class="timeline-dot"></div>
<div class="timeline-content">
<h3>First Met</h3>
<span>The Beginning</span>
<p>I remember the day we met. The world seemed a little brighter, and your smile caught me off guard.</p>
</div>
</div>
<div class="timeline-item">
<div class="timeline-dot"></div>
<div class="timeline-content">
<h3>First Date</h3>
<span>Butterflies</span>
<p>Coffee turned into hours of conversation. I knew right then that I wanted to know everything about you.</p>
</div>
</div>
<div class="timeline-item">
<div class="timeline-dot"></div>
<div class="timeline-content">
<h3>Falling in Love</h3>
<span>The Magic</span>
<p>It wasn't a single moment, but a collection of small moments that made me realize I was in love.</p>
</div>
</div>
<div class="timeline-item">
<div class="timeline-dot"></div>
<div class="timeline-content">
<h3>Today & Forever</h3>
<span>Still Falling</span>
<p>Every day with you is a gift. Here's to creating a million more memories together.</p>
</div>
</div>
</div>
</section>
<!-- Reasons Flip Cards -->
<section class="reasons-section">
<h2 class="section-title">Why I Love You</h2>
<div class="cards-grid">
<div class="flip-card">
<div class="flip-card-inner">
<div class="flip-card-front">
<i class="fas fa-smile-beam"></i>
<h3>Your Smile</h3>
</div>
<div class="flip-card-back">
<h3>Your Smile</h3>
<p>It lights up the darkest rooms and makes my worst days instantly better.</p>
</div>
</div>
</div>
<div class="flip-card">
<div class="flip-card-inner">
<div class="flip-card-front">
<i class="fas fa-brain"></i>
<h3>Your Mind</h3>
</div>
<div class="flip-card-back">
<h3>Your Mind</h3>
<p>I love how you think, your creativity, and the way you solve problems.</p>
</div>
</div>
</div>
<div class="flip-card">
<div class="flip-card-inner">
<div class="flip-card-front">
<i class="fas fa-mug-hot"></i>
<h3>Kindness</h3>
</div>
<div class="flip-card-back">
<h3>Your Kindness</h3>
<p>The way you care for others, even strangers, shows the beautiful soul you have.</p>
</div>
</div>
</div>
</div>
</section>
<!-- Photo Gallery (Placeholders) -->
<section class="gallery">
<h2 class="section-title">Memories</h2>
<div class="photo-grid">
<div class="photo-item">
<img src="../images/pexels-pixabay-326612.jpg" alt="Memory 1">
</div>
<div class="photo-item">
<img src="../images/pexels-fmaderebner-340566.jpg" alt="Memory 2">
</div>
<div class="photo-item">
<img src="../images/pexels-asadphoto-1024984.jpg" alt="Memory 3">
</div>
<div class="photo-item">
<img src="../images/pexels-nurseryart-348520.jpg" alt="Memory 4">
</div>
</div>
</section>
<!-- Surprise CTA -->
<section id="surprise" class="surprise-section">
<h2>I have one more thing to say...</h2>
<br>
<button class="open-gift-btn" onclick="openModal()">
<i class="fas fa-gift"></i> Click Here
</button>
</section>
<!-- Footer -->
<footer>
<p>Made with <i class="fas fa-heart"></i> just for you. Happy Valentine's Day.</p>
</footer>
<!-- Modal -->
<div class="modal-overlay" id="modal">
<div class="modal-content">
<span class="close-modal" onclick="closeModal()">×</span>
<i class="fas fa-heart" style="font-size: 3rem; color: var(--primary); margin-bottom: 1rem;"></i>
<h2 style="font-family: 'Dancing Script'; margin-bottom: 1rem;">I Love You!</h2>
<p>Thank you for being you, for being my rock, and for making life so incredibly beautiful. Today and every day, you are my valentine.</p>
<button onclick="closeModal()"
style="margin-top: 1.5rem; background: var(--secondary); border: none; padding: 0.5rem 1.5rem; border-radius: 20px; color: white; cursor: pointer;">Close</button>
</div>
</div>
<script>
// --- 1. Canvas Falling Hearts Animation ---
const canvas = document.getElementById('heart-canvas');
const ctx = canvas.getContext('2d');
let width, height;
let hearts = [];
function resize() {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
}
window.addEventListener('resize', resize);
resize();
class Heart {
constructor() {
this.x = Math.random() * width;
this.y = Math.random() * height - height; // Start above screen
this.size = Math.random() * 10 + 5;
this.speedY = Math.random() * 1 + 0.5;
this.speedX = Math.random() * 1 - 0.5;
this.opacity = Math.random() * 0.5 + 0.1;
this.color = `rgba(233, 30, 99, ${this.opacity})`; // Primary pink
}
update() {
this.y += this.speedY;
this.x += Math.sin(this.y * 0.01) * 0.5; // Gentle sway
if (this.y > height) {
this.y = -20;
this.x = Math.random() * width;
}
}
draw() {
ctx.fillStyle = this.color;
ctx.beginPath();
// Draw heart shape
let topCurveHeight = this.size * 0.3;
ctx.moveTo(this.x, this.y + topCurveHeight);
// Top left curve
ctx.bezierCurveTo(
this.x, this.y,
this.x - this.size / 2, this.y,
this.x - this.size / 2, this.y + topCurveHeight
);
// Bottom left curve
ctx.bezierCurveTo(
this.x - this.size / 2, this.y + (this.size + topCurveHeight) / 2,
this.x, this.y + (this.size + topCurveHeight) / 2,
this.x, this.y + this.size
);
// Bottom right curve
ctx.bezierCurveTo(
this.x, this.y + (this.size + topCurveHeight) / 2,
this.x + this.size / 2, this.y + (this.size + topCurveHeight) / 2,
this.x + this.size / 2, this.y + topCurveHeight
);
// Top right curve
ctx.bezierCurveTo(
this.x + this.size / 2, this.y,
this.x, this.y,
this.x, this.y + topCurveHeight
);
ctx.fill();
}
}
function initHearts() {
hearts = [];
for (let i = 0; i < 50; i++) {
hearts.push(new Heart());
}
}
function animateHearts() {
ctx.clearRect(0, 0, width, height);
hearts.forEach(heart => {
heart.update();
heart.draw();
});
requestAnimationFrame(animateHearts);
}
initHearts();
animateHearts();
// --- 2. Scroll Animations (Intersection Observer) ---
const observerOptions = {
threshold: 0.2,
rootMargin: "0px 0px -50px 0px"
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
// Optional: Stop observing once visible
// observer.unobserve(entry.target);
}
});
}, observerOptions);
document.querySelectorAll('.timeline-item, .flip-card, .photo-item').forEach(el => {
observer.observe(el);
});
// --- 3. Interaction Functions ---
function scrollToSection(id) {
document.getElementById(id).scrollIntoView({behavior: 'smooth'});
}
function openModal() {
document.getElementById('modal').classList.add('active');
launchConfetti();
}
function closeModal() {
document.getElementById('modal').classList.remove('active');
}
// Close modal if clicking outside content
document.getElementById('modal').addEventListener('click', function(e) {
if (e.target === this) {
closeModal();
}
});
// --- 4. Simple Confetti Effect (JS) ---
function launchConfetti() {
const colors = ['#e91e63', '#ff80ab', '#ffc107', '#ffffff'];
for (let i = 0; i < 100; i++) {
createConfettiPiece(colors);
}
}
function createConfettiPiece(colors) {
const confetti = document.createElement('div');
const bg = colors[Math.floor(Math.random() * colors.length)];
confetti.style.position = 'fixed';
confetti.style.width = '10px';
confetti.style.height = '10px';
confetti.style.backgroundColor = bg;
confetti.style.top = '50%';
confetti.style.left = '50%';
confetti.style.zIndex = '2001';
confetti.style.pointerEvents = 'none';
document.body.appendChild(confetti);
// Random destination
const x = (Math.random() - 0.5) * window.innerWidth * 0.8;
const y = (Math.random() - 0.5) * window.innerHeight * 0.8;
const rotation = Math.random() * 360;
const animation = confetti.animate([
{transform: `translate(0, 0) rotate(0deg)`, opacity: 1},
{transform: `translate(${x}px, ${y}px) rotate(${rotation}deg)`, opacity: 0}
], {
duration: 1000 + Math.random() * 1000,
easing: 'cubic-bezier(0.25, 1, 0.5, 1)'
});
animation.onfinish = () => confetti.remove();
}
</script>
</body>
</html>