<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>A Special Gift For You Darling β‘</title>
<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=Caveat:wght@600;700&family=Great+Vibes&family=Montserrat:wght@400;500;600;700&family=Playfair+Display:ital,wght@0,700;1,400&display=swap" rel="stylesheet">
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: radial-gradient(circle at center, #1a0b1a 0%, #0d050e 100%);
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-family: 'Montserrat', sans-serif;
overflow: hidden;
color: #333;
transition: background 1.5s ease-in-out;
position: relative;
}
body.rose-theme {
background: radial-gradient(circle at center, #fce4ec 0%, #f8bbd0 50%, #f48fb1 100%);
}
#confetti-canvas, #magical-canvas, #stars-canvas {
position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; pointer-events: none;
}
#confetti-canvas { z-index: 300; }
#stars-canvas { z-index: 2; opacity: 1; transition: opacity 1.5s ease; }
#magical-canvas { z-index: 1; opacity: 1; transition: opacity 1.5s ease; }
body.rose-theme #magical-canvas { opacity: 1; }
.top-bar {
position: fixed; top: 20px; right: 20px;
display: flex; align-items: center; gap: 10px; z-index: 400;
}
.top-btn {
background: #ffffff; border: 2px solid #d4af37; color: #701c2e;
padding: 8px 16px; border-radius: 25px; font-size: 0.85rem; font-weight: 600;
cursor: pointer; box-shadow: 0 4px 15px rgba(0,0,0,0.12);
transition: all 0.3s ease;
}
.top-btn:hover { transform: scale(1.05); }
.music-input-box {
display: flex; align-items: center; background: #ffffff;
border: 2px solid #d4af37; border-radius: 25px; padding: 3px 6px 3px 14px;
box-shadow: 0 4px 15px rgba(0,0,0,0.12);
}
.music-input-box input { border: none; outline: none; font-size: 0.8rem; width: 170px; color: #333; }
.music-input-box button {
background: #701c2e; color: #fff; border: none; padding: 6px 12px;
border-radius: 18px; font-size: 0.75rem; font-weight: 600; cursor: pointer;
}
.instruction-text {
position: absolute; top: 8%; font-size: 1.25rem; letter-spacing: 2px;
color: #ffe57f; text-transform: uppercase; font-weight: 700;
z-index: 20; text-align: center; padding: 0 20px; transition: opacity 0.4s ease, color 1s ease;
text-shadow: 0 0 12px rgba(255, 229, 127, 0.6);
}
body.rose-theme .instruction-text { color: #701c2e; text-shadow: none; }
.scene-container {
width: 100vw; height: 100vh; display: flex;
justify-content: center; align-items: center; position: relative;
z-index: 10;
}
/* ENHANCED DECORATED GIFT AREA & BOX */
.gift-area-decor {
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 15;
}
.gift-aura-ring {
position: absolute;
width: 540px; height: 540px;
border-radius: 50%;
background: radial-gradient(circle, rgba(212,175,55,0.25) 0%, rgba(212,175,55,0.05) 50%, transparent 70%);
animation: pulseAura 3s ease-in-out infinite;
pointer-events: none;
}
@keyframes pulseAura {
0%, 100% { transform: scale(0.95); opacity: 0.6; }
50% { transform: scale(1.1); opacity: 1; }
}
.gift-sparkles-around {
position: absolute; width: 500px; height: 500px;
pointer-events: none;
animation: rotateDecor 20s linear infinite;
}
@keyframes rotateDecor {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.gift-sparkles-around::before, .gift-sparkles-around::after {
content: 'β¨ π β¨ π';
position: absolute; font-size: 1.5rem;
width: 100%; text-align: center; color: #ffe57f;
text-shadow: 0 0 10px #ffe57f;
}
.gift-sparkles-around::before { top: -20px; }
.gift-sparkles-around::after { bottom: -20px; transform: rotate(180deg); }
.gift-box {
width: 400px; height: 400px;
background: linear-gradient(135deg, #821b30, #4a0c18);
border: 4px solid #d4af37; border-radius: 20px;
box-shadow: 0 25px 60px rgba(0,0,0,0.6), inset 0 0 25px rgba(212,175,55,0.3);
position: relative; display: flex; justify-content: center; align-items: center;
cursor: pointer; transition: all 0.8s cubic-bezier(0.175, 0.885, 0.32, 1.275);
z-index: 20; transform-style: preserve-3d;
}
.gift-box:hover {
transform: scale(1.03);
box-shadow: 0 30px 70px rgba(212,175,55,0.4), inset 0 0 35px rgba(212,175,55,0.5);
}
.ribbon {
position: absolute; background: linear-gradient(135deg, #fff1b8, #d4af37, #aa7c11);
transition: all 0.8s ease; z-index: 5;
box-shadow: 0 0 10px rgba(0,0,0,0.3);
}
.ribbon-v { width: 56px; height: 100%; left: 172px; top: 0; }
.ribbon-h { width: 100%; height: 56px; top: 172px; left: 0; }
.unwrapped .ribbon-v, .unwrapped .ribbon-h { opacity: 0; transform: scale(0); }
.gold-bow {
position: absolute; top: 145px; left: 145px; width: 110px; height: 110px;
font-size: 5.5rem; z-index: 6; line-height: 100px; text-align: center;
filter: drop-shadow(0 6px 12px rgba(0,0,0,0.5));
transition: all 0.8s ease; pointer-events: none;
}
.unwrapped .gold-bow { transform: scale(0) rotate(180deg); opacity: 0; }
.sticky-note {
position: absolute; width: 290px; height: 205px; top: 98px; left: 55px; background: #fffdf5;
padding: 22px; transform: rotate(-4deg); box-shadow: 0 10px 25px rgba(0,0,0,0.3);
cursor: pointer; z-index: 30; transition: all 0.4s ease; display: flex;
justify-content: center; align-items: center; text-align: center;
border-radius: 4px; border: 1px solid #f0e6d2;
}
.sticky-note:hover { transform: rotate(0deg) scale(1.08); }
.sticky-note p { font-family: 'Caveat', cursive; font-size: 1.62rem; line-height: 1.18; color: #222; font-weight: 700; }
.sticky-note.fade-out { opacity: 0; pointer-events: none; transform: scale(0.5) rotate(45deg); }
.gift-area-decor.hide-box { opacity: 0; transform: scale(0.8); pointer-events: none; display: none !important; }
/* BOUQUET & SPECIAL NOTE CARD SCENE */
.bouquet-scene-wrapper {
position: absolute; left: 50%; top: 50%; width: 92vw; max-width: 1050px; height: 82vh; max-height: 660px;
background: rgba(255, 253, 250, 0.96); border: 4px solid #d4af37; border-radius: 20px;
box-shadow: 0 25px 60px rgba(0,0,0,0.25); display: flex; flex-direction: row;
overflow: hidden; opacity: 0; transform: translate(-50%, -50%) scale(0.9); pointer-events: none;
transition: all 0.8s cubic-bezier(0.175, 0.885, 0.32, 1.275); z-index: 40;
}
.bouquet-scene-wrapper.active { opacity: 1; transform: translate(-50%, -50%) scale(1); pointer-events: auto; }
.bouquet-left-col {
width: 45%; height: 100%; background: linear-gradient(135deg, #fff0f3 0%, #fbe4e8 100%);
border-right: 2px dashed #f48fb1; padding: 25px; display: flex; flex-direction: column;
align-items: center; justify-content: space-between; position: relative; overflow-y: auto;
}
.animated-bouquet-box {
width: 100%; display: flex; flex-direction: column; align-items: center; position: relative;
}
.flower-bouquet-img {
width: min(100%, 330px); height: 245px; display:flex; align-items:center; justify-content:center;
filter: drop-shadow(0 10px 14px rgba(112,28,46,0.18));
animation: floatBouquet 3s ease-in-out infinite;
}
.bouquet-art { width: 100%; height: 100%; overflow: visible; }
@keyframes floatBouquet {
0%, 100% { transform: translateY(0) rotate(0deg); }
50% { transform: translateY(-10px) rotate(3deg); }
}
.bouquet-left-col .media-container { width: 100%; flex: 1; min-height: 185px; margin-top: 4px; display:flex; align-items:stretch; }
.bouquet-left-col .removable-block { width:100%; display:flex; }
.bouquet-left-col .video-box { width:100%; height:100% !important; min-height:185px; }
.bouquet-right-col {
width: 55%; height: 100%; padding: 35px 30px 25px 30px; display: flex;
flex-direction: column; justify-content: space-between; align-items: center;
text-align: center; position: relative; background: #fffdfa; overflow-y: auto;
}
.bouquet-right-col h2 {
font-family: 'Great Vibes', cursive; font-size: 3.5rem; color: #701c2e; margin-bottom: 10px; font-weight: normal;
}
.card-space-text {
font-size: 1.1rem; line-height: 1.7; color: #444; width: 100%; min-height: 100px;
margin-bottom: 15px; word-wrap: break-word; text-align: center;
}
.proceed-btn {
background: linear-gradient(135deg, #701c2e, #821b30); color: #ffffff;
border: 2px solid #d4af37; padding: 12px 28px; border-radius: 30px;
font-size: 1rem; font-weight: 700; cursor: pointer;
box-shadow: 0 6px 20px rgba(112, 28, 46, 0.3); transition: all 0.3s ease;
display: flex; align-items: center; gap: 8px; margin-top: auto;
}
.proceed-btn:hover { transform: translateY(-3px) scale(1.03); box-shadow: 0 10px 25px rgba(112, 28, 46, 0.4); }
/* SCRAPBOOK STYLES */
.scrapbook-wrapper {
position: absolute; left: 50%; top: 50%; width: 94vw; max-width: 1100px; height: 82vh; max-height: 680px;
perspective: 2200px; opacity: 0; pointer-events: none; transition: all 0.8s ease; z-index: 50; display: none; transform: translate(-50%, -50%);
}
.scrapbook-wrapper.center-screen { opacity: 1; pointer-events: auto; display: block; transform: translate(-50%, -50%); }
.closed-book {
width: 50%; height: 100%; margin: 0 auto;
background: linear-gradient(135deg, #42464c, #2b2e33);
border: 4px solid #d4af37; border-radius: 14px; display: flex;
flex-direction: column; justify-content: center; align-items: center;
text-align: center; padding: 40px; box-shadow: 0 25px 50px rgba(0,0,0,0.35);
cursor: pointer; transition: all 0.7s cubic-bezier(0.4, 0, 0.2, 1);
position: relative; transform-style: preserve-3d; transform-origin: left center;
}
.closed-book::before {
content: 'β¦ β β‘ β β¦'; position: absolute; top: 15px; left: 15px; right: 15px; bottom: 15px;
border: 2px solid rgba(212, 175, 55, 0.65); border-radius: 10px; pointer-events: none;
color:#f3d375; font-size:1.5rem; padding:18px; letter-spacing:12px;
text-shadow:0 0 12px rgba(243,211,117,0.55);
}
.closed-book::after {
content: 'β¦ β¦ β¦'; position:absolute; bottom:24px; color:#f3d375; font-size:1.5rem;
letter-spacing:16px; opacity:.85; pointer-events:none;
}
/* Leather-style magnetic closure β matched to the burgundy book cover */
.magnetic-latch {
position: absolute; right: -29px; top: 50%; transform: translateY(-50%);
width: 58px; height: 108px;
background: linear-gradient(145deg, #7a2337 0%, #5a1728 48%, #3f0e1c 100%);
border: 2px solid #3a0d19; border-left: 3px solid #8d3046;
border-radius: 0 14px 14px 0;
box-shadow: 5px 8px 18px rgba(0,0,0,0.42), inset 2px 0 4px rgba(255,255,255,.08);
display: flex; justify-content: center; align-items: center;
cursor: pointer; transition: transform 0.6s ease, opacity 0.4s ease; z-index: 100;
}
.magnetic-latch::before, .magnetic-latch::after {
content: ''; position: absolute; left: 9px; right: 9px; height: 2px;
background: rgba(184,92,112,.35); border-radius: 2px;
}
.magnetic-latch::before { top: 15px; }
.magnetic-latch::after { bottom: 15px; }
.magnetic-clasp {
width: 31px; height: 31px;
background: radial-gradient(circle at 35% 30%, #9a3a52 0%, #671b2d 55%, #3a0d19 100%);
border-radius: 50%; border: 2px solid #2f0914;
box-shadow: inset 0 2px 3px rgba(255,255,255,.12), 0 3px 8px rgba(0,0,0,.35);
position: relative;
}
.magnetic-clasp::after {
content: 'β₯'; position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
color: #c76a82; font-size: 13px; text-shadow: 0 1px 2px rgba(0,0,0,.45);
}
.closed-book.unlatched .magnetic-latch { transform: translateY(-50%) rotateY(-120deg) scale(0.9); opacity: 0.2; }
.closed-book.opening-anim { transform: rotateY(-90deg); opacity: 0; }
.closed-book .bow-icon { font-size: 3.5rem; margin-bottom: 20px; }
.closed-book h2 { font-family: 'Great Vibes', cursive; font-size: 3.8rem; color: #f3d375; font-weight: normal; }
.closed-book p.tap-hint { font-size: 0.9rem; color: #d7d9dd; margin-top: 30px; letter-spacing: 1px; font-style: italic; }
.open-book {
display: none; width: 100%; height: 100%; background: #fdfbf7;
border-radius: 16px; border: 4px solid #d4af37; box-shadow: 0 25px 60px rgba(0,0,0,0.3);
position: relative; overflow: hidden; transform-style: preserve-3d; transition: transform 0.8s ease, opacity 0.6s ease;
}
.open-book.active { display: flex; }
/* PAGE STYLES */
.page {
width: 50%; height: 100%; padding: 35px 30px 50px 30px; display: flex;
flex-direction: column; justify-content: flex-start; align-items: center;
text-align: center; position: relative; background: #fffdfa; overflow-y: auto;
}
.page-left { border-right: 1px solid #eae2d6; }
.page-right { border-left: 1px solid #eae2d6; }
.page::before, .bouquet-right-col::before {
content: 'πΈ πΊ πΈ'; position: absolute; top: 8px; left: 12px; right: 12px;
font-size: 0.9rem; opacity: 0.6; letter-spacing: 12px; pointer-events: none;
}
.floral-border-box {
position: absolute; top: 12px; bottom: 12px; left: 12px; right: 12px;
border: 2px dashed rgba(212, 175, 55, 0.4); border-radius: 10px; pointer-events: none;
}
.washi-tape {
position: absolute; top: 12px; width: 80px; height: 20px;
background: rgba(248, 187, 208, 0.55); border-left: 2px dashed rgba(212, 175, 55, 0.6);
border-right: 2px dashed rgba(212, 175, 55, 0.6); transform: rotate(-2deg); pointer-events: none; z-index: 5;
}
.page-left .washi-tape { right: 35px; }
.page-right .washi-tape { left: 35px; }
.page h3 { font-family: 'Great Vibes', cursive; font-size: 3.2rem; color: #701c2e; margin-bottom: 8px; font-weight: normal; z-index: 2; width: 100%; }
.page-text {
font-size: 1.05rem; line-height: 1.6; color: #444; z-index: 2; margin-bottom: 10px; width: 100%;
min-height: 40px; transition: all 0.3s ease; text-align: center; word-wrap: break-word;
}
.media-container {
width: 100%; display: flex; flex-direction: column; gap: 12px; margin-top: 5px; max-width: 420px; z-index: 2; flex-grow: 1;
}
/* REMOVABLE BLOCK WRAPPER */
.removable-block { position: relative; width: 100%; transition: all 0.3s ease; }
.remove-btn {
display: none; position: absolute; top: -8px; right: -8px; width: 24px; height: 24px;
background: #701c2e; color: #fff; border: 2px solid #fff; border-radius: 50%;
font-size: 0.75rem; font-weight: bold; cursor: pointer; z-index: 30;
box-shadow: 0 2px 6px rgba(0,0,0,0.2); align-items: center; justify-content: center; transition: transform 0.2s ease;
}
.remove-btn:hover { transform: scale(1.2); background: #a3223f; }
body.is-editing .remove-btn { display: flex; }
.photo-box {
width: 100%; height: 135px; background: #fffaf0; border: 2px dashed #d4af37;
border-radius: 8px; display: flex; justify-content: center; align-items: center;
color: #887a65; font-size: 0.85rem; overflow: hidden;
}
.video-box {
width: 100%; height: 135px; background: #111; border: 2px solid #701c2e;
border-radius: 8px; overflow: hidden; display: flex; justify-content: center; align-items: center; color: #fff;
}
.shayari-box {
width: 100%; padding: 12px 14px;
background: linear-gradient(135deg, #fff5f8 0%, #ffedf2 100%);
border: 2px solid #f48fb1; border-radius: 12px; box-shadow: 0 4px 12px rgba(244, 143, 177, 0.15);
position: relative; text-align: center;
}
.shayari-box p {
font-family: 'Caveat', cursive; font-size: 1.4rem; color: #701c2e; line-height: 1.3; margin: 0; font-weight: 600;
}
.shayari-box span.author {
display: block; font-size: 0.78rem; font-family: 'Montserrat', sans-serif;
color: #ad1457; margin-top: 4px; font-style: italic; font-weight: 500;
}
.voice-box {
width: 100%; background: #ffffff; border: 2px solid #d4af37; border-radius: 12px;
padding: 12px; margin-top: 5px; box-shadow: 0 4px 10px rgba(0,0,0,0.05); text-align: left;
}
.voice-header {
display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;
}
.voice-title { font-size: 0.85rem; font-weight: 700; color: #701c2e; }
.voice-limit-badge { font-size: 0.7rem; color: #888; background: #f0f0f0; padding: 2px 6px; border-radius: 10px; }
.voice-player { width: 100%; height: 38px; outline: none; }
.page-num { position: absolute; bottom: 15px; font-size: 0.85rem; color: #999; z-index: 2; }
.page-left .page-num { left: 20px; }
.page-right .page-num { right: 20px; }
.editable-active { outline: 2px dashed #701c2e; background: rgba(255, 253, 245, 0.85); border-radius: 4px; padding: 4px; }
.nav-controls {
position: absolute; bottom: 15px; left: 0; right: 0; display: flex;
justify-content: space-between; align-items: center; padding: 0 30px; z-index: 20; pointer-events: none;
}
.page-counter-badge {
background: rgba(255, 255, 255, 0.95); color: #701c2e; border: 1px solid #d4af37;
padding: 4px 14px; border-radius: 15px; font-size: 0.85rem; font-weight: 600; pointer-events: auto;
}
.nav-btn {
background: #701c2e; color: #ffffff; border: 2px solid #d4af37;
padding: 8px 18px; border-radius: 20px; font-size: 0.88rem; font-weight: 600;
cursor: pointer; pointer-events: auto; transition: all 0.3s ease;
}
.nav-btn:hover { background: #821b30; transform: scale(1.05); }
.nav-btn:disabled { opacity: 0.4; cursor: not-allowed; transform: none; }
/* CHANGE 4 β proposal interlude */
.proposal-scene-wrapper {
position:absolute; left:50%; top:50%; transform:translate(-50%,-50%) scale(.94);
width:min(92vw,760px); min-height:min(78vh,650px); padding:28px 24px 30px;
display:flex; flex-direction:column; align-items:center; justify-content:center; gap:16px;
text-align:center; border:3px solid #d4af37; border-radius:28px;
background:radial-gradient(circle at 50% 18%,#fff7f3 0%,#fffaf7 45%,#f7e5e5 100%);
box-shadow:0 25px 70px rgba(39,5,18,.38),inset 0 0 0 7px rgba(112,28,46,.08);
opacity:0; pointer-events:none; z-index:65; transition:opacity .6s ease,transform .6s ease;
overflow:hidden;
}
.proposal-scene-wrapper.active { opacity:1; pointer-events:auto; transform:translate(-50%,-50%) scale(1); }
.proposal-eyebrow { color:#a36a24; font-size:.85rem; letter-spacing:3px; text-transform:uppercase; font-weight:800; }
.proposal-title { font-family:'Playfair Display',serif; color:#701c2e; font-size:clamp(1.65rem,4vw,2.65rem); line-height:1.2; margin:0; }
.proposal-subtitle { color:#805461; font-size:1rem; margin:0; }
.ring-box-button { border:0; background:transparent; cursor:pointer; padding:0; position:relative; margin:4px 0; -webkit-tap-highlight-color:transparent; }
.ring-box { width:130px; height:100px; position:relative; filter:drop-shadow(0 12px 10px rgba(50,0,20,.2)); transition:transform .4s ease; }
.ring-box-button:hover .ring-box { transform:translateY(-5px) rotate(-2deg); }
.ring-box-base { position:absolute; left:9px; right:9px; bottom:0; height:55px; border:3px solid #d4af37; border-radius:9px 9px 18px 18px; background:linear-gradient(135deg,#8e2942,#5a1026 65%,#3e0918); }
.ring-box-lid { position:absolute; left:4px; right:4px; top:20px; height:29px; border:3px solid #e0bd61; border-radius:9px 9px 5px 5px; background:linear-gradient(135deg,#b84b66,#7a1832 70%,#4a0d20); transform-origin:bottom center; transition:transform .65s cubic-bezier(.2,.8,.2,1); z-index:2; }
.ring-box-lid::after { content:'β₯'; color:#f3d375; position:absolute; left:50%; top:2px; transform:translateX(-50%); font-size:17px; }
.ring-box.open .ring-box-lid { transform:rotateX(135deg) translateY(-8px); }
.ring-pop { position:absolute; z-index:3; left:50%; bottom:28px; transform:translate(-50%,15px) scale(.3); opacity:0; font-size:3.3rem; transition:all .65s cubic-bezier(.2,.8,.2,1); pointer-events:none; }
.ring-box.open .ring-pop { opacity:1; transform:translate(-50%,-35px) scale(1); }
.ring-hint { color:#9a6873; font-size:.88rem; }
.proposal-question { display:none; color:#701c2e; font-family:'Playfair Display',serif; font-size:clamp(1.45rem,3.5vw,2.2rem); font-weight:700; margin:4px 0; animation:fadeInContents .7s ease both; }
.proposal-question.visible { display:block; }
.proposal-buttons { display:none; align-items:center; justify-content:center; gap:22px; position:relative; width:min(100%,440px); min-height:100px; margin-top:2px; }
.proposal-buttons.visible { display:flex; }
.proposal-choice { border:2px solid #d4af37; border-radius:999px; padding:12px 30px; font-size:1.08rem; font-weight:800; cursor:pointer; box-shadow:0 6px 16px rgba(112,28,46,.16); transition:transform .2s ease,font-size .2s ease; touch-action:manipulation; }
#proposalYes { color:white; background:linear-gradient(135deg,#8b203c,#5c1026); position:relative; z-index:2; transform:scale(1); }
#proposalNo { color:#701c2e; background:#fff; position:relative; z-index:1; }
.proposal-after { display:none; font-family:'Playfair Display',serif; color:#701c2e; font-size:clamp(1.3rem,3.4vw,2rem); font-weight:700; animation:fadeInContents .8s ease both; }
.proposal-after.visible { display:block; }
.celebration-fall { position:fixed; top:-45px; z-index:350; pointer-events:none; animation:petalShower linear forwards; }
@keyframes petalShower { 0%{transform:translate3d(0,-30px,0) rotate(0deg);opacity:0} 10%{opacity:1} 100%{transform:translate3d(var(--drift),110vh,0) rotate(620deg);opacity:0} }
@media (max-width:600px) {
.proposal-scene-wrapper { width:92vw; min-height:76vh; padding:22px 15px; gap:12px; }
.ring-box { transform:scale(.9); }
.proposal-buttons { gap:12px; min-height:95px; }
.proposal-choice { padding:11px 23px; }
}
/* CERTIFICATE / REWARD CHEST SCENE */
.award-scene-wrapper {
position: absolute; left: 50%; top: 50%; width: 92vw; max-width: 1050px; height: 82vh; max-height: 660px;
background: #fffdf8; border: 4px solid #d4af37; border-radius: 20px;
box-shadow: 0 25px 60px rgba(0,0,0,0.3); display: flex; flex-direction: column;
align-items: center; justify-content: space-between; padding: 25px;
opacity: 0; transform: translate(-50%, -50%) scale(0.9); pointer-events: none; transition: all 0.8s ease; z-index: 60;
}
.award-scene-wrapper.active { opacity: 1; transform: translate(-50%, -50%) scale(1); pointer-events: auto; }
.award-header-title {
font-family: 'Playfair Display', serif; font-size: 2rem; color: #701c2e;
text-align: center; text-transform: uppercase; letter-spacing: 1px; font-weight: 700;
border-bottom: 2px double #d4af37; padding-bottom: 8px; margin-bottom: 10px; width: 100%;
}
.chest-container {
display:flex; flex-direction:column; align-items:center; justify-content:center; cursor:pointer;
transition:transform .35s ease; margin:4px 0 8px;
}
.chest-container:hover { transform:scale(1.04) translateY(-3px); }
.chest-icon { width:170px; height:105px; position:relative; filter:drop-shadow(0 12px 18px rgba(0,0,0,.28)); }
.treasure-chest-body { position:absolute; left:10px; right:10px; bottom:8px; height:60px; border:3px solid #c9a64a; border-radius:8px 8px 14px 14px; background:linear-gradient(135deg,#8f969e,#555d66 58%,#343941); box-shadow:inset 0 0 18px rgba(255,255,255,.12); }
.treasure-chest-lid { position:absolute; left:8px; right:8px; top:18px; height:48px; border:3px solid #c9a64a; border-radius:70px 70px 8px 8px; background:linear-gradient(135deg,#b9bec4,#69717a 55%,#3d4249); transform-origin:bottom center; transition:transform .65s cubic-bezier(.175,.885,.32,1.275); z-index:2; }
.treasure-chest-lock { position:absolute; left:50%; top:53px; transform:translateX(-50%); width:25px; height:31px; border:3px solid #d4af37; border-radius:5px; background:linear-gradient(#ffe57f,#b98920); z-index:4; box-shadow:0 3px 8px rgba(0,0,0,.3); }
.treasure-chest-lock::after { content:'β‘'; color:#701c2e; font-weight:900; position:absolute; left:50%; top:1px; transform:translateX(-50%); font-size:15px; }
.treasure-chest-glow { position:absolute; width:120px; height:55px; left:25px; top:15px; background:radial-gradient(ellipse,rgba(255,229,127,.45),transparent 70%); opacity:0; transition:opacity .5s ease; z-index:1; pointer-events:none; }
.chest-icon.opened .treasure-chest-lid { transform:rotateX(-115deg) translateY(-4px); }
.chest-icon.opened .treasure-chest-glow { opacity:1; }
.chest-prompt { font-size:0.9rem; font-weight:700; color:#8b6b22; letter-spacing:1px; margin-top:2px; font-family:'Playfair Display',serif; font-style:italic; }
.award-contents-grid {
display: none; width: 100%; height: 68%; flex-direction: row; gap: 20px;
}
.award-contents-grid.revealed { display: flex; animation: fadeInContents 0.8s ease forwards; }
@keyframes fadeInContents { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } }
.award-left-box, .award-right-box {
width: 50%; height: 100%; border: 2px solid #e0c068; border-radius: 12px;
background: #ffffff; padding: 20px; display: flex; flex-direction: column;
align-items: center; justify-content: center; text-align: center; position: relative;
box-shadow: inset 0 0 15px rgba(212, 175, 55, 0.1);
}
.award-right-box {
padding: 0;
overflow: hidden;
flex-direction: row;
}
.certificate-action-half {
width: 50%; height: 100%; padding: 16px 12px;
display: flex; flex-direction: column; align-items: center; justify-content: center;
text-align: center; position: relative; cursor: pointer;
transition: background 0.3s ease, transform 0.3s ease;
}
.certificate-action-half + .certificate-action-half {
}
.certificate-action-half:hover { background: rgba(212,175,55,0.07); }
.certificate-action-half:active { transform: scale(0.98); }
.certificate-action-title {
font-family: 'Playfair Display', serif; color: #701c2e; font-size: 0.9rem;
font-weight: 700; margin-bottom: 12px; text-transform: uppercase; letter-spacing: 0.8px;
}
.maker-label { font-size: 0.72rem; color: #777; margin-top: 10px; line-height: 1.35; }
.winner-label { font-size: 0.72rem; color: #777; margin-top: 10px; line-height: 1.35; }
.winner-signature {
width: 150px; height: 80px; display: flex; align-items: center; justify-content: center;
font-family: 'Great Vibes', cursive; font-size: 3rem; color: #701c2e;
transform: rotate(-7deg) scale(1.7); opacity: 0;
transition: all 0.45s cubic-bezier(0.175, 0.885, 0.32, 1.275);
pointer-events: none;
}
.winner-signature.signed { opacity: 1; transform: rotate(-7deg) scale(1); }
.signature-hint { font-size: 0.75rem; color: #701c2e; font-weight: 600; margin-top: 8px; }
.medal-animation-box {
font-size: 4rem; margin-bottom: 10px;
animation: medalBounce 2s ease-in-out infinite;
}
@keyframes medalBounce {
0%, 100% { transform: translateY(0) scale(1); }
50% { transform: translateY(-12px) scale(1.1); }
}
.stamp-container {
position: relative; width: 100%; height: 100%; display: flex;
flex-direction: column; justify-content: center; align-items: center; cursor: pointer;
}
.official-stamp {
width: 140px; height: 140px; border: 4px double #701c2e; border-radius: 50%;
color: #701c2e; display: flex; flex-direction: column; justify-content: center;
align-items: center; font-weight: 700; transform: rotate(-12deg) scale(2);
opacity: 0; transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
background: rgba(112, 28, 46, 0.05); text-transform: uppercase;
}
.official-stamp.stamped { opacity: 1; transform: rotate(-12deg) scale(1); }
.stamp-hint { font-size: 0.85rem; color: #701c2e; font-weight: 600; margin-top: 10px; }
/* FINAL LOVING CLOSING LETTER SCENE */
.final-letter-wrapper {
position: absolute; left: 50%; top: 50%; width: 92vw; max-width: 900px; height: 84vh; max-height: 660px;
background: #fcf9f2; border: 5px double #d4af37; border-radius: 20px;
box-shadow: 0 25px 60px rgba(0,0,0,0.3); display: flex; flex-direction: column;
align-items: center; justify-content: space-between; padding: 25px 25px 20px 25px;
opacity: 0; transform: translate(-50%, -50%) scale(0.9); pointer-events: none; transition: all 0.8s ease; z-index: 70;
position: absolute; overflow: hidden;
}
.final-letter-wrapper.active { opacity: 1; transform: translate(-50%, -50%) scale(1); pointer-events: auto; }
.decorated-letter-page {
width: 100%; height: 86%; background: radial-gradient(circle at center, #ffffff 0%, #fffbf2 100%);
border: 2px dashed #f48fb1; border-radius: 15px; padding: 20px; display: flex;
flex-direction: column; align-items: center; justify-content: center; position: relative;
box-shadow: inset 0 0 20px rgba(212, 175, 55, 0.15); overflow-y: auto;
}
.corner-decor { position: absolute; font-size: 1.5rem; opacity: 0.7; pointer-events: none; }
.corner-tl { top: 10px; left: 15px; }
.corner-tr { top: 10px; right: 15px; }
.corner-bl { bottom: 10px; left: 15px; }
.corner-br { bottom: 10px; right: 15px; }
.page-heading {
font-family: 'Great Vibes', cursive; font-size: 3rem; color: #701c2e;
margin-bottom: 5px; text-align: center;
}
.envelope-container {
position: relative; width: 320px; height: 210px; cursor: pointer;
perspective: 1000px; transition: transform 0.4s ease, opacity 0.5s ease;
margin: 15px 0; z-index: 10;
}
.envelope-container:hover { transform: scale(1.03); }
.envelope-body {
width: 100%; height: 100%; background: linear-gradient(135deg, #701c2e 0%, #4a101d 100%);
border: 2px solid #d4af37; border-radius: 12px; box-shadow: 0 15px 35px rgba(112, 28, 46, 0.4);
position: relative; overflow: hidden; display: flex; justify-content: center; align-items: center;
}
.envelope-flap {
position: absolute; top: 0; left: 0; width: 100%; height: 115px;
background: linear-gradient(145deg, #8b2137 0%, #701c2e 58%, #541322 100%);
clip-path: polygon(0 0, 100% 0, 50% 100%);
transform-origin: top center;
transition: transform 0.6s ease, z-index 0.2s ease; z-index: 5;
filter: drop-shadow(0 4px 8px rgba(0,0,0,0.3));
}
.wax-seal {
position: absolute; width: 58px; height: 58px;
left: 50%; top: 88px; transform: translate(-50%, -50%);
background: radial-gradient(circle, #fff3a5 0%, #f2cf55 62%, #b78720 100%);
border-radius: 50%; border: 3px solid #701c2e; box-shadow: 0 5px 12px rgba(0,0,0,0.32), inset 0 0 0 2px rgba(255,255,255,.35);
display: flex; justify-content: center; align-items: center; font-size: 1.5rem;
z-index: 8; transition: opacity 0.55s ease, transform 0.55s cubic-bezier(.175,.885,.32,1.275);
animation: sealPulse 2s ease-in-out infinite;
transform-origin: center;
}
.envelope-prompt {
position:absolute; bottom:15px; font-size:.78rem; font-weight:600; color:#ffe57f;
letter-spacing:1px; z-index:4; text-align:center; opacity:.85; font-family:'Caveat',cursive;
}
@keyframes sealPulse { 0%,100%{transform:scale(1)} 50%{transform:scale(1.06)} }
.envelope-container.open .wax-seal { opacity:0; transform:translate(-50%, -50%) scale(1.55) rotate(18deg); animation:none; }
.unfolded-note-card {
display: none; width: 92%; max-width: 650px; background: #ffffff;
border: 3px double #d4af37; border-radius: 14px; padding: 25px 20px;
box-shadow: 0 15px 40px rgba(0,0,0,0.15); position: relative; text-align: center;
animation: unfoldLetter 0.8s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
}
@keyframes unfoldLetter {
0% { opacity: 0; transform: scale(0.3) translateY(50px); }
100% { opacity: 1; transform: scale(1) translateY(0); }
}
.unfolded-note-card.active { display: flex; flex-direction: column; align-items: center; }
.envelope-container.open .envelope-flap { transform: rotateX(180deg); z-index: 1; }
.letter-title { font-family: 'Great Vibes', cursive; font-size: 3.2rem; color: #701c2e; margin-bottom: 8px; }
.letter-body-text {
font-family: 'Caveat', cursive; font-size: 1.55rem; line-height: 1.5; color: #333;
text-align: center; width: 100%; outline: none; margin-bottom: 10px; word-wrap: break-word;
}
.word-counter {
font-size: 0.78rem; color: #888; font-family: 'Montserrat', sans-serif;
position: absolute; bottom: 8px; right: 15px; font-weight: 600;
}
.word-counter.exceeded { color: #d32f2f; font-weight: 700; }
#ytPlayerContainer { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
</style>
</head>
<body>
<audio id="bgMusic" loop preload="auto">
<source id="bgMusicSource" src="https://cdn.pixabay.com/download/audio/2022/05/27/audio_1808fbf07a.mp3?filename=romantic-piano-112188.mp3" type="audio/mpeg">
</audio>
<div id="ytPlayerContainer"></div>
<div class="top-bar">
<div class="music-input-box">
<input type="text" id="ytMusicUrl" placeholder="Paste YT link / Audio URL..." />
<button id="setMusicBtn">Set Music</button>
</div>
<button class="top-btn" id="editBtn">βοΈ Edit Mode: OFF</button>
<button class="top-btn" id="musicBtn">π΅ Music On</button>
</div>
<canvas id="stars-canvas"></canvas>
<canvas id="magical-canvas"></canvas>
<canvas id="confetti-canvas"></canvas>
<div class="scene-container">
<div class="instruction-text" id="statusText">Tap the golden ribbon to untie π</div>
<!-- CENTERED & DECORATED GIFT BOX AREA -->
<div class="gift-area-decor" id="giftAreaDecor">
<div class="gift-aura-ring"></div>
<div class="gift-sparkles-around"></div>
<div class="gift-box" id="giftBox">
<div class="ribbon ribbon-v" id="ribbonV"></div>
<div class="ribbon ribbon-h" id="ribbonH"></div>
<div class="gold-bow" id="goldBow">π</div>
<div class="sticky-note" id="stickyNote">
<p id="stickyNoteText" contenteditable="false">Dear om, youπ«΅π» received a giftπ from Malkin [Prachi ,(^_^)]</p>
</div>
</div>
</div>
<!-- STEP 1: BOUQUET & NOTE CARD SCENE -->
<div class="bouquet-scene-wrapper" id="bouquetScene">
<div class="bouquet-left-col">
<div class="animated-bouquet-box">
<div class="flower-bouquet-img" aria-label="Illustrated bouquet of roses">
<svg class="bouquet-art" viewBox="0 0 360 260" role="img" aria-label="A detailed illustrated bouquet of roses">
<defs>
<linearGradient id="wrap3" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#fff0f5"/><stop offset="0.45" stop-color="#eaa4bb"/><stop offset="1" stop-color="#9c3d60"/></linearGradient>
<linearGradient id="ribbon3" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#f7d77c"/><stop offset="1" stop-color="#b7831f"/></linearGradient>
<radialGradient id="rose3"><stop offset="0" stop-color="#ffd2df"/><stop offset="0.42" stop-color="#e96a91"/><stop offset="0.78" stop-color="#b62f59"/><stop offset="1" stop-color="#65132e"/></radialGradient>
<filter id="soft3"><feGaussianBlur stdDeviation="2"/></filter>
</defs>
<ellipse cx="180" cy="232" rx="105" ry="13" fill="#701c2e" opacity=".12" filter="url(#soft3)"/>
<path d="M72 135 L288 135 L247 242 L113 242 Z" fill="url(#wrap3)" stroke="#8b3655" stroke-width="3"/>
<path d="M72 135 Q180 175 288 135 L246 188 Q180 215 114 188Z" fill="#f6b7ca" opacity=".65"/>
<path d="M102 143 L180 184 L258 143" fill="none" stroke="#fff4f7" stroke-width="4" opacity=".75"/>
<g stroke="#39734c" stroke-width="7" stroke-linecap="round" fill="none">
<path d="M180 156 Q177 92 116 48"/><path d="M180 156 Q190 82 181 32"/><path d="M181 156 Q210 84 260 58"/>
<path d="M171 156 Q137 91 76 78"/><path d="M190 156 Q231 105 302 101"/><path d="M174 155 Q151 104 143 62"/>
</g>
<g fill="#4f985f">
<ellipse cx="116" cy="88" rx="25" ry="10" transform="rotate(28 116 88)"/><ellipse cx="213" cy="78" rx="25" ry="10" transform="rotate(-25 213 78)"/>
<ellipse cx="252" cy="108" rx="25" ry="10" transform="rotate(-15 252 108)"/><ellipse cx="89" cy="105" rx="25" ry="10" transform="rotate(25 89 105)"/>
<ellipse cx="154" cy="87" rx="24" ry="9" transform="rotate(15 154 87)"/>
</g>
<g stroke="#6c1735" stroke-width="2">
<g fill="url(#rose3)" transform="translate(116 53)"><circle r="32"/><circle r="23" fill="none" stroke="#ffbfd0" stroke-width="7"/><circle r="13" fill="none" stroke="#ffd9e3" stroke-width="6"/><path d="M-17 3 Q0 -15 17 3 Q3 22 -17 3Z" fill="none" stroke="#f8a7bd" stroke-width="4"/></g>
<g fill="url(#rose3)" transform="translate(181 38)"><circle r="35"/><circle r="25" fill="none" stroke="#ffbfd0" stroke-width="7"/><circle r="14" fill="none" stroke="#ffd9e3" stroke-width="6"/><path d="M-18 4 Q0 -17 18 4 Q3 24 -18 4Z" fill="none" stroke="#f8a7bd" stroke-width="4"/></g>
<g fill="url(#rose3)" transform="translate(251 66)"><circle r="30"/><circle r="21" fill="none" stroke="#ffbfd0" stroke-width="6"/><circle r="11" fill="none" stroke="#ffd9e3" stroke-width="5"/></g>
<g fill="url(#rose3)" transform="translate(78 81)"><circle r="28"/><circle r="19" fill="none" stroke="#ffbfd0" stroke-width="6"/><circle r="10" fill="none" stroke="#ffd9e3" stroke-width="5"/></g>
<g fill="url(#rose3)" transform="translate(148 76)"><circle r="34"/><circle r="24" fill="none" stroke="#ffd0dc" stroke-width="7"/><circle r="13" fill="none" stroke="#ffe1e8" stroke-width="6"/></g>
<g fill="url(#rose3)" transform="translate(217 91)"><circle r="29"/><circle r="20" fill="none" stroke="#ffbfd0" stroke-width="6"/><circle r="10" fill="none" stroke="#ffd9e3" stroke-width="5"/></g>
</g>
<g fill="#fffafc" opacity=".95">
<circle cx="55" cy="57" r="5"/><circle cx="285" cy="49" r="5"/><circle cx="145" cy="22" r="4"/><circle cx="235" cy="25" r="4"/>
<circle cx="64" cy="38" r="2.5"/><circle cx="303" cy="75" r="2.5"/>
</g>
<path d="M151 177 Q180 198 209 177" fill="none" stroke="url(#ribbon3)" stroke-width="10" stroke-linecap="round"/>
<path d="M178 189 Q150 215 130 195 Q150 184 178 189Z" fill="#d4af37" opacity=".9"/><path d="M182 189 Q211 215 231 195 Q210 184 182 189Z" fill="#d4af37" opacity=".9"/>
<circle cx="180" cy="189" r="7" fill="#ffe57f" stroke="#997a15" stroke-width="2"/>
</svg>
</div>
<p style="font-family:'Caveat', cursive; font-size:1.6rem; color:#701c2e; font-weight:600; margin-top:5px;">A Bouquet of Roses For You πΉ</p>
</div>
<div class="media-container" id="bouquetMediaContainer">
<div class="removable-block">
<button class="remove-btn" onclick="removeBouquetMedia()">β</button>
<div class="video-box" style="height:140px;">
<p style="font-size:0.8rem; padding:10px; text-align:center;">π¬ Special Card Video / Memory<br>(Replace src or upload video here)</p>
</div>
</div>
</div>
</div>
<div class="bouquet-right-col">
<div class="floral-border-box"></div>
<h2 id="bouquetTitle">A Cute Gift For You Darling β‘</h2>
<p class="card-space-text" id="bouquetText" contenteditable="false">
My dearest, before you open our scrapbook of memories, I wanted to send you this little bouquet and note to remind you how much you mean to me. Every single day with you feels like a blessing!
</p>
<button class="proceed-btn" id="openScrapbookBtn">
Open Our Scrapbook β¨ β
</button>
</div>
</div>
<!-- STEP 2: SCRAPBOOK -->
<div class="scrapbook-wrapper" id="scrapbook">
<!-- FRONT COVER -->
<div class="closed-book" id="closedBook">
<div class="bow-icon">π</div>
<h2>Our Love Story Across Distance β‘</h2>
<p class="tap-hint">A little collection of memories, smiles & forever moments β‘</p>
</div>
<!-- OPEN BOOK -->
<div class="open-book" id="openBook">
<div class="page page-left" id="pageLeft"></div>
<div class="page page-right" id="pageRight"></div>
<div class="nav-controls">
<button class="nav-btn" id="prevBtn" disabled>β Previous</button>
<div class="page-counter-badge" id="pageBadge">Page 1-2 of 50</div>
<button class="nav-btn" id="nextBtn">Next β</button>
</div>
</div>
</div>
<!-- STEP 3: BURGUNDY RING-BOX PROPOSAL -->
<div class="proposal-scene-wrapper" id="proposalScene" aria-live="polite">
<div class="proposal-eyebrow">A tiny question, a forever feeling</div>
<h2 class="proposal-title">One last little surpriseβ¦ β‘</h2>
<p class="proposal-subtitle" id="proposalSubtitle">Tap the burgundy box to open your surprise.</p>
<button class="ring-box-button" id="ringBoxButton" aria-label="Open the ring box">
<div class="ring-box" id="ringBox">
<div class="ring-box-base"></div><div class="ring-box-lid"></div>
<div class="ring-pop" aria-hidden="true">π</div>
</div>
</button>
<div class="ring-hint" id="ringHint">A little sparkle is waiting inside β¨</div>
<div class="proposal-question" id="proposalQuestion">Om, will you marry me? πβ€οΈ</div>
<div class="proposal-buttons" id="proposalButtons">
<button class="proposal-choice" id="proposalYes">YES! π</button>
<button class="proposal-choice" id="proposalNo">No π</button>
</div>
<div class="proposal-after" id="proposalAfter">YAAAAAY! Forever starts with us! ππβ¨</div>
</div>
<!-- STEP 4: CERTIFICATE & TREASURE CHEST AWARD SCENE -->
<div class="award-scene-wrapper" id="awardScene">
<div class="award-header-title" id="awardHeader">
π Official Certificate of the Most Handsome & Cutest Boyfriend π
</div>
<div class="chest-container" id="chestBox">
<div class="chest-icon" id="chestIcon">
<div class="treasure-chest-glow"></div>
<div class="treasure-chest-lid"></div>
<div class="treasure-chest-body"></div>
<div class="treasure-chest-lock"></div>
</div>
<div class="chest-prompt" id="chestPrompt">A little treasure awaits Om β‘</div>
</div>
<div class="award-contents-grid" id="awardGrid">
<div class="award-left-box">
<div class="medal-animation-box">π₯</div>
<h3 style="font-family:'Great Vibes', cursive; font-size:2.4rem; color:#701c2e; margin-bottom:5px;">Highest Award</h3>
<p style="font-size:0.95rem; color:#444; line-height:1.5;">
Presented for being the <strong>bestest blessing in my life</strong>, the cutest boyfriend, and having the most precious heart ever!
</p>
</div>
<div class="award-right-box" id="stampBox">
<div class="certificate-action-half maker-half" id="makerStampHalf">
<div class="certificate-action-title">Stamp of the Maker</div>
<div class="official-stamp stamped" id="officialStamp">
<span style="font-size:0.62rem;">STAMP OF</span>
<span style="font-size:0.88rem; margin:2px 0;">MALKIN</span>
<span style="font-size:0.58rem;">PRACHI β‘</span>
</div>
<div class="maker-label">Officially stamped by<br><strong>Malkin [Prachi, (^_^)]</strong></div>
</div>
<div class="certificate-action-half winner-half" id="winnerSignatureHalf">
<div class="certificate-action-title">Signature of the Winner</div>
<div class="winner-signature" id="winnerSignature">Om β‘</div>
<div class="winner-label" id="winnerLabel">Winner: <strong>Om</strong></div>
<div class="signature-hint" id="signatureHint">Tap here to reveal your signature βοΈ</div>
</div>
</div>
</div>
<button class="proceed-btn" id="proceedToLetterBtn" style="margin-top:15px; display:none;">
Read Final Loving Letter βοΈ β
</button>
</div>
<!-- STEP 4: FINAL LOVING CLOSING LETTER -->
<div class="final-letter-wrapper" id="finalLetterScene">
<div class="decorated-letter-page">
<div class="corner-decor corner-tl">πΈ πΏ</div>
<div class="corner-decor corner-tr">πΏ πΈ</div>
<div class="corner-decor corner-bl">πΈ πΏ</div>
<div class="corner-decor corner-br">πΏ πΈ</div>
<h2 class="page-heading" id="pageHeadingTitle">A Secret Message For You β‘</h2>
<div class="envelope-container" id="envelopeBox">
<div class="envelope-flap" id="envelopeFlap">
<div class="wax-seal" id="waxSeal">π</div>
</div>
<div class="envelope-body">
<div class="envelope-prompt" id="envelopePrompt">β‘</div>
</div>
</div>
<div class="unfolded-note-card" id="unfoldedNoteCard">
<div class="washi-tape" style="top:-10px; left:50%; transform:translateX(-50%);"></div>
<h2 class="letter-title">My Last Words For You β‘</h2>
<div class="letter-body-text" id="finalLetterText" contenteditable="false">
My dearest darling, thank you for walking through this virtual scrapbook with me. Distance may keep us apart in miles, but you are in every thought, every prayer, and every beat of my heart. You are my greatest award, my safest home, and my favorite person forever. I love you so much!
</div>
<div class="word-counter" id="wordCounter">Words: 57 / 200 Max</div>
</div>
</div>
<button class="proceed-btn" id="finishBtn" style="margin-top:10px;">
Finish & Replay Gift π
</button>
</div>
</div>
<script>
const giftAreaDecor = document.getElementById('giftAreaDecor');
const giftBox = document.getElementById('giftBox');
const ribbonV = document.getElementById('ribbonV');
const ribbonH = document.getElementById('ribbonH');
const stickyNote = document.getElementById('stickyNote');
const stickyNoteText = document.getElementById('stickyNoteText');
const bouquetScene = document.getElementById('bouquetScene');
const openScrapbookBtn = document.getElementById('openScrapbookBtn');
const scrapbook = document.getElementById('scrapbook');
const proposalScene = document.getElementById('proposalScene');
const ringBoxButton = document.getElementById('ringBoxButton');
const ringBox = document.getElementById('ringBox');
const proposalSubtitle = document.getElementById('proposalSubtitle');
const ringHint = document.getElementById('ringHint');
const proposalQuestion = document.getElementById('proposalQuestion');
const proposalButtons = document.getElementById('proposalButtons');
const proposalYes = document.getElementById('proposalYes');
const proposalNo = document.getElementById('proposalNo');
const proposalAfter = document.getElementById('proposalAfter');
let proposalOpened = false;
let proposalAccepted = false;
let noAttemptCount = 0;
const closedBook = document.getElementById('closedBook');
const openBook = document.getElementById('openBook');
const pageLeft = document.getElementById('pageLeft');
const pageRight = document.getElementById('pageRight');
const prevBtn = document.getElementById('prevBtn');
const nextBtn = document.getElementById('nextBtn');
const pageBadge = document.getElementById('pageBadge');
const statusText = document.getElementById('statusText');
const bgMusic = document.getElementById('bgMusic');
const bgMusicSource = document.getElementById('bgMusicSource');
const musicBtn = document.getElementById('musicBtn');
const editBtn = document.getElementById('editBtn');
const setMusicBtn = document.getElementById('setMusicBtn');
const ytMusicUrl = document.getElementById('ytMusicUrl');
const ytPlayerContainer = document.getElementById('ytPlayerContainer');
const awardScene = document.getElementById('awardScene');
const chestBox = document.getElementById('chestBox');
const chestIcon = document.getElementById('chestIcon');
const chestPrompt = document.getElementById('chestPrompt');
const awardGrid = document.getElementById('awardGrid');
const makerStampHalf = document.getElementById('makerStampHalf');
const winnerSignatureHalf = document.getElementById('winnerSignatureHalf');
const officialStamp = document.getElementById('officialStamp');
const winnerSignature = document.getElementById('winnerSignature');
const signatureHint = document.getElementById('signatureHint');
const proceedToLetterBtn = document.getElementById('proceedToLetterBtn');
const finalLetterScene = document.getElementById('finalLetterScene');
const envelopeBox = document.getElementById('envelopeBox');
const unfoldedNoteCard = document.getElementById('unfoldedNoteCard');
const finalLetterText = document.getElementById('finalLetterText');
const wordCounter = document.getElementById('wordCounter');
const finishBtn = document.getElementById('finishBtn');
let isRibbonUntied = false;
let isNoteRemoved = false;
let isBoxOpened = false;
let musicPlaying = false;
let isEditMode = false;
let currentSpreadIndex = 0;
let isYtEmbed = false;
let ytPlayer = null;
let isChestOpened = false;
let isStamped = false;
let isWinnerSigned = false;
let isEnvelopeOpened = false;
const TOTAL_SPREADS = 25;
const spreads = [];
const sampleShayaris = [
{ text: "βFasle mita kar har ek doori ko tumse jod dungi, tum bas mere ban ke rehna!β", author: "β‘ Love across distance β‘" },
{ text: "βTu paas ho ya door, har lamha tera hi khayal aata hai!β", author: "β‘ Forever Yours β‘" },
{ text: "βSuno, tum meri wo chahat ho jo lafzon mein bayaan nahi hoti!β", author: "β‘ Just For You β‘" },
{ text: "βThousands of miles apart, but you are still my favorite notification.β", author: "β‘ Sweet Memories β‘" },
{ text: "βDil ka rishta ho toh faasle koi maani nahi rakhte!β", author: "β‘ LDR Love Note β‘" }
];
for (let i = 0; i < TOTAL_SPREADS; i++) {
const leftPageNum = (i * 2) + 1;
const rightPageNum = (i * 2) + 2;
const shayari1 = sampleShayaris[i % sampleShayaris.length];
const shayari2 = sampleShayaris[(i + 1) % sampleShayaris.length];
if (i === 0) {
spreads.push({
left: {
title: "Welcome My Love β‘",
text: "Even though miles separate us, you are always right here in my heart.",
blocks: [
{ id: 'photo', html: `<div class="photo-box">πΌ Optional Photo (Add image URL)</div>` },
{ id: 'shayari', html: `<div class="shayari-box"><p>${shayari1.text}</p><span class="author">${shayari1.author}</span></div>` }
],
num: leftPageNum
},
right: {
title: "A Video Moment π¬",
text: "Here is a cute video clip for you!",
blocks: [
{ id: 'video', html: `<div class="video-box"><p style="font-size:0.8rem; padding:10px; text-align:center;">π¬ Video Player Placeholder<br>(Replace src="your-video.mp4")</p></div>` }
],
num: rightPageNum
}
});
} else if (i === TOTAL_SPREADS - 1) {
spreads.push({
left: {
title: "Until We Meet Again π",
text: "Counting down every day until we get to hold each other for real.",
blocks: [
{ id: 'video', html: `<div class="video-box"><p style="font-size:0.8rem; padding:10px; text-align:center;">π¬ Final Memory Video</p></div>` },
{ id: 'shayari', html: `<div class="shayari-box"><p>βTumse door hoke bhi tumhare paas lagta hai, yeh ishq mera kuch khas lagta hai.β</p></div>` }
],
num: leftPageNum
},
right: {
title: "My Heartbeat For You ποΈ",
text: "Listen to my voice message below before we turn to your special reward!",
blocks: [
{
id: 'voice',
html: `
<div class="voice-box">
<div class="voice-header">
<span class="voice-title">ποΈ Long Voice Message:</span>
<span class="voice-limit-badge">Max 5 Mins Capacity</span>
</div>
<audio controls class="voice-player">
<source src="your-voice-message.mp3" type="audio/mpeg">
Your browser does not support audio playback.
</audio>
</div>
`
}
],
num: rightPageNum
}
});
} else {
spreads.push({
left: {
title: `Chapter ${i + 1} β¨`,
text: `This is page ${leftPageNum}. Write a sweet romantic message here!`,
blocks: [
{ id: 'photo', html: `<div class="photo-box">πΌοΈ Optional Photo Box</div>` },
{ id: 'shayari', html: `<div class="shayari-box"><p>${shayari1.text}</p><span class="author">${shayari1.author}</span></div>` }
],
num: leftPageNum
},
right: {
title: `Memories Page ${rightPageNum} π`,
text: `Cherishing all our memories together on page ${rightPageNum}.`,
blocks: [
{ id: 'photo', html: `<div class="photo-box">πΌοΈ Memory Photo Container</div>` },
{ id: 'shayari', html: `<div class="shayari-box"><p>${shayari2.text}</p><span class="author">${shayari2.author}</span></div>` }
],
num: rightPageNum
}
});
}
}
// Gift Box Interactive Unwrapping
giftBox.addEventListener('click', (event) => {
if (event.target === stickyNoteText && isEditMode) return;
if (!isRibbonUntied) {
giftBox.classList.add('unwrapped');
isRibbonUntied = true;
statusText.innerText = "Now tap the sticky note to open the box βοΈ";
spawnShootingStarBurst();
triggerConfetti();
} else if (!isNoteRemoved) {
stickyNote.classList.add('fade-out');
isNoteRemoved = true;
statusText.innerText = "Tap the gift box to open! π";
spawnShootingStarBurst();
} else if (!isBoxOpened) {
giftAreaDecor.classList.add('hide-box');
isBoxOpened = true;
document.body.classList.add('rose-theme');
statusText.innerText = "";
bouquetScene.classList.add('active');
triggerConfetti();
spawnShootingStarBurst();
}
});
function removeBouquetMedia() {
const el = document.getElementById('bouquetMediaContainer');
if (el) el.style.display = 'none';
}
openScrapbookBtn.addEventListener('click', () => {
bouquetScene.classList.remove('active');
setTimeout(() => {
bouquetScene.style.display = 'none';
scrapbook.classList.add('center-screen');
}, 500);
});
closedBook.addEventListener('click', () => {
closedBook.classList.add('unlatched');
setTimeout(() => {
closedBook.classList.add('opening-anim');
setTimeout(() => {
closedBook.style.display = 'none';
openBook.classList.add('active');
renderSpread(currentSpreadIndex);
}, 600);
}, 400);
});
function removeBlock(spreadIdx, pageSide, blockIdx) {
spreads[spreadIdx][pageSide].blocks.splice(blockIdx, 1);
renderSpread(currentSpreadIndex);
}
function renderSpread(idx) {
const spread = spreads[idx];
let leftBlocksHTML = spread.left.blocks.map((b, bIdx) => `
<div class="removable-block">
<button class="remove-btn" onclick="removeBlock(${idx}, 'left', ${bIdx})">β</button>
${b.html}
</div>
`).join('');
pageLeft.innerHTML = `
<div class="floral-border-box"></div>
<div class="washi-tape"></div>
<h3 contenteditable="${isEditMode}">${spread.left.title}</h3>
<p class="page-text ${isEditMode ? 'editable-active' : ''}" contenteditable="${isEditMode}">${spread.left.text}</p>
<div class="media-container">${leftBlocksHTML}</div>
<div class="page-num">${spread.left.num}</div>
`;
let rightBlocksHTML = spread.right.blocks.map((b, bIdx) => `
<div class="removable-block">
<button class="remove-btn" onclick="removeBlock(${idx}, 'right', ${bIdx})">β</button>
${b.html}
</div>
`).join('');
pageRight.innerHTML = `
<div class="floral-border-box"></div>
<div class="washi-tape"></div>
<h3 contenteditable="${isEditMode}">${spread.right.title}</h3>
<p class="page-text ${isEditMode ? 'editable-active' : ''}" contenteditable="${isEditMode}">${spread.right.text}</p>
<div class="media-container">${rightBlocksHTML}</div>
<div class="page-num">${spread.right.num}</div>
`;
pageBadge.innerText = `Page ${spread.left.num}-${spread.right.num} of ${TOTAL_SPREADS * 2}`;
prevBtn.disabled = (idx === 0);
nextBtn.innerText = (idx === TOTAL_SPREADS - 1) ? "Claim Reward π β" : "Next β";
}
prevBtn.addEventListener('click', () => {
if (currentSpreadIndex > 0) {
currentSpreadIndex--;
renderSpread(currentSpreadIndex);
}
});
nextBtn.addEventListener('click', () => {
if (currentSpreadIndex < TOTAL_SPREADS - 1) {
currentSpreadIndex++;
renderSpread(currentSpreadIndex);
} else {
scrapbook.classList.remove('center-screen');
setTimeout(() => {
scrapbook.style.display = 'none';
proposalScene.classList.add('active');
}, 600);
}
});
ringBoxButton.addEventListener('click', () => {
if (proposalOpened) return;
proposalOpened = true;
ringBox.classList.add('open');
proposalSubtitle.innerText = 'For the person who makes my heart feel at homeβ¦';
ringHint.style.display = 'none';
setTimeout(() => {
proposalQuestion.classList.add('visible');
proposalButtons.classList.add('visible');
}, 500);
});
proposalNo.addEventListener('pointerenter', dodgeNoButton);
proposalNo.addEventListener('pointerdown', (event) => { event.preventDefault(); dodgeNoButton(); });
proposalNo.addEventListener('click', (event) => { event.preventDefault(); dodgeNoButton(); });
function dodgeNoButton() {
if (proposalAccepted || !proposalButtons.classList.contains('visible')) return;
noAttemptCount++;
const area = proposalButtons.getBoundingClientRect();
const button = proposalNo.getBoundingClientRect();
const maxX = Math.max(0, area.width - button.width - 12);
const maxY = Math.max(0, area.height - button.height - 8);
proposalNo.style.position = 'absolute';
proposalNo.style.left = (Math.random() * maxX) + 'px';
proposalNo.style.top = (Math.random() * maxY) + 'px';
proposalNo.style.transform = 'rotate(' + (Math.random() * 18 - 9) + 'deg)';
const scale = Math.min(1.75, 1 + noAttemptCount * 0.1);
proposalYes.style.transform = 'scale(' + scale + ')';
proposalYes.style.fontSize = (1.08 + Math.min(noAttemptCount * 0.035, 0.35)) + 'rem';
proposalSubtitle.innerText = ['Hehe, that button is shy π', 'Nope, try the sparkly one! π', 'The YES is getting biggerβ¦ π'][noAttemptCount % 3];
}
proposalYes.addEventListener('click', () => {
if (proposalAccepted) return;
proposalAccepted = true;
proposalButtons.classList.remove('visible');
proposalQuestion.classList.remove('visible');
proposalSubtitle.style.display = 'none';
ringBoxButton.style.display = 'none';
proposalAfter.classList.add('visible');
triggerConfetti();
showerCelebration();
setTimeout(() => {
proposalScene.classList.remove('active');
setTimeout(() => {
proposalScene.style.display = 'none';
awardScene.classList.add('active');
triggerConfetti();
}, 550);
}, 3300);
});
function showerCelebration() {
const symbols = ['πΈ','πΉ','β¨','π','π·','β','π','π'];
for (let i = 0; i < 85; i++) {
const particle = document.createElement('span');
particle.className = 'celebration-fall';
particle.textContent = symbols[Math.floor(Math.random() * symbols.length)];
particle.style.left = Math.random() * 100 + 'vw';
particle.style.fontSize = (16 + Math.random() * 22) + 'px';
particle.style.animationDuration = (2.5 + Math.random() * 3.2) + 's';
particle.style.animationDelay = (Math.random() * 1.3) + 's';
particle.style.setProperty('--drift', (Math.random() * 180 - 90) + 'px');
document.body.appendChild(particle);
particle.addEventListener('animationend', () => particle.remove(), { once: true });
}
}
chestBox.addEventListener('click', () => {
if (!isChestOpened) {
isChestOpened = true;
chestIcon.classList.add('opened');
chestPrompt.innerText = "Your treasure has been revealed β‘";
awardGrid.classList.add('revealed');
triggerConfetti();
}
});
makerStampHalf.addEventListener('click', () => {
if (isChestOpened) {
isStamped = true;
officialStamp.classList.add('stamped');
}
});
winnerSignatureHalf.addEventListener('click', () => {
if (!isChestOpened || isWinnerSigned) return;
isWinnerSigned = true;
winnerSignature.classList.add('signed');
signatureHint.innerText = "Signed by Om with love β€οΈ";
proceedToLetterBtn.style.display = 'flex';
triggerConfetti();
});
proceedToLetterBtn.addEventListener('click', () => {
awardScene.classList.remove('active');
setTimeout(() => {
awardScene.style.display = 'none';
finalLetterScene.classList.add('active');
updateWordCount();
}, 600);
});
envelopeBox.addEventListener('click', () => {
if (!isEnvelopeOpened) {
isEnvelopeOpened = true;
envelopeBox.classList.add('open');
triggerConfetti();
setTimeout(() => {
envelopeBox.style.display = 'none';
unfoldedNoteCard.classList.add('active');
}, 700);
}
});
finalLetterText.addEventListener('input', updateWordCount);
function updateWordCount() {
const text = finalLetterText.innerText.trim();
const words = text ? text.split(/\s+/).length : 0;
wordCounter.innerText = `Words: ${words} / 200 Max`;
if (words > 200) {
wordCounter.classList.add('exceeded');
} else {
wordCounter.classList.remove('exceeded');
}
}
finishBtn.addEventListener('click', () => {
alert("Replaying your gift experience! β‘");
location.reload();
});
editBtn.addEventListener('click', () => {
isEditMode = !isEditMode;
editBtn.innerText = isEditMode ? "βοΈ Edit Mode: ON" : "βοΈ Edit Mode: OFF";
editBtn.style.background = isEditMode ? "#d4af37" : "#ffffff";
editBtn.style.color = isEditMode ? "#ffffff" : "#701c2e";
document.body.classList.toggle('is-editing', isEditMode);
document.getElementById('bouquetText').contentEditable = isEditMode;
document.getElementById('bouquetText').classList.toggle('editable-active', isEditMode);
stickyNoteText.contentEditable = isEditMode;
stickyNoteText.classList.toggle('editable-active', isEditMode);
finalLetterText.contentEditable = isEditMode;
finalLetterText.classList.toggle('editable-active', isEditMode);
if (openBook.classList.contains('active')) {
renderSpread(currentSpreadIndex);
}
});
musicBtn.addEventListener('click', toggleMusic);
function toggleMusic() {
if (isYtEmbed && ytPlayer) {
if (musicPlaying) {
ytPlayer.pauseVideo();
musicBtn.innerText = "π΅ Music Off";
} else {
ytPlayer.playVideo();
musicBtn.innerText = "π΅ Music On";
}
musicPlaying = !musicPlaying;
} else {
if (musicPlaying) {
bgMusic.pause();
musicBtn.innerText = "π΅ Music Off";
} else {
bgMusic.play().catch(e => console.log(e));
musicBtn.innerText = "π΅ Music On";
}
musicPlaying = !musicPlaying;
}
}
setMusicBtn.addEventListener('click', () => {
const url = ytMusicUrl.value.trim();
if (!url) return;
const ytMatch = url.match(/(?:youtu\.be\/|youtube\.com\/(?:watch\?v=|embed\/))([a-zA-Z0-9_-]+)/);
if (ytMatch && ytMatch[1]) {
const videoId = ytMatch[1];
isYtEmbed = true;
bgMusic.pause();
ytPlayerContainer.innerHTML = `<iframe id="ytFrame" src="https://www.youtube.com/embed/${videoId}?enablejsapi=1&autoplay=1&loop=1&playlist=${videoId}" allow="autoplay"></iframe>`;
musicBtn.innerText = "π΅ Music On";
musicPlaying = true;
} else {
isYtEmbed = false;
ytPlayerContainer.innerHTML = "";
bgMusicSource.src = url;
bgMusic.load();
bgMusic.play().then(() => {
musicBtn.innerText = "π΅ Music On";
musicPlaying = true;
}).catch(err => {
alert("Could not load audio URL. Please check link format.");
});
}
});
/* CONFETTI ANIMATION */
function triggerConfetti() {
const canvas = document.getElementById('confetti-canvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const particles = [];
const colors = ['#f48fb1', '#d4af37', '#701c2e', '#ffffff', '#ffe57f'];
for (let i = 0; i < 80; i++) {
particles.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height - canvas.height,
size: Math.random() * 8 + 4,
color: colors[Math.floor(Math.random() * colors.length)],
speedY: Math.random() * 3 + 2,
speedX: Math.random() * 2 - 1
});
}
let animationFrame;
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
particles.forEach(p => {
p.y += p.speedY;
p.x += p.speedX;
ctx.fillStyle = p.color;
ctx.beginPath();
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx.fill();
});
if (particles.some(p => p.y < canvas.height)) {
animationFrame = requestAnimationFrame(animate);
} else {
ctx.clearRect(0, 0, canvas.width, canvas.height);
cancelAnimationFrame(animationFrame);
}
}
animate();
}
/* STARS & SHOOTING STARS ANIMATION ENGINE */
const starsCanvas = document.getElementById('stars-canvas');
const sCtx = starsCanvas.getContext('2d');
starsCanvas.width = window.innerWidth;
starsCanvas.height = window.innerHeight;
let staticStars = [];
let shootingStars = [];
function initStars() {
staticStars = [];
for (let i = 0; i < 240; i++) {
staticStars.push({
x: Math.random() * starsCanvas.width,
y: Math.random() * starsCanvas.height,
radius: Math.random() * 2.4 + 0.6,
alpha: Math.random(),
twinkleSpeed: Math.random() * 0.035 + 0.008
});
}
}
initStars();
function spawnShootingStarBurst() {
for (let i = 0; i < 5; i++) {
setTimeout(() => {
shootingStars.push({
x: Math.random() * starsCanvas.width * 0.8,
y: Math.random() * starsCanvas.height * 0.4,
length: Math.random() * 120 + 80,
speed: Math.random() * 10 + 12,
angle: Math.PI / 4,
alpha: 1
});
}, i * 200);
}
}
function animateStars() {
sCtx.clearRect(0, 0, starsCanvas.width, starsCanvas.height);
// Render static twinkling stars
staticStars.forEach(s => {
s.alpha += s.twinkleSpeed;
if (s.alpha > 1 || s.alpha < 0.1) s.twinkleSpeed = -s.twinkleSpeed;
sCtx.fillStyle = `rgba(255, 229, 127, ${s.alpha})`;
sCtx.beginPath();
sCtx.arc(s.x, s.y, s.radius, 0, Math.PI * 2);
sCtx.fill();
});
// Render shooting stars
for (let i = shootingStars.length - 1; i >= 0; i--) {
const star = shootingStars[i];
const endX = star.x + Math.cos(star.angle) * star.length;
const endY = star.y + Math.sin(star.angle) * star.length;
const grad = sCtx.createLinearGradient(star.x, star.y, endX, endY);
grad.addColorStop(0, `rgba(255, 255, 255, ${star.alpha})`);
grad.addColorStop(0.5, `rgba(212, 175, 55, ${star.alpha * 0.6})`);
grad.addColorStop(1, 'rgba(255, 255, 255, 0)');
sCtx.strokeStyle = grad;
sCtx.lineWidth = 2.5;
sCtx.beginPath();
sCtx.moveTo(star.x, star.y);
sCtx.lineTo(endX, endY);
sCtx.stroke();
star.x += Math.cos(star.angle) * star.speed;
star.y += Math.sin(star.angle) * star.speed;
star.alpha -= 0.015;
if (star.alpha <= 0 || star.y > starsCanvas.height) {
shootingStars.splice(i, 1);
}
}
requestAnimationFrame(animateStars);
}
animateStars();
// Periodically generate a ambient shooting star in the gift opening phase
setInterval(() => {
if (Math.random() > 0.15) {
shootingStars.push({
x: Math.random() * starsCanvas.width * 0.6,
y: Math.random() * starsCanvas.height * 0.3,
length: Math.random() * 100 + 60,
speed: Math.random() * 8 + 10,
angle: Math.PI / 4,
alpha: 1
});
}
}, 1400);
/* MAGICAL PARTICLES + CONTINUOUS HEART / FLOWER FALL */
const magicalCanvas = document.getElementById('magical-canvas');
const mCtx = magicalCanvas.getContext('2d');
magicalCanvas.width = window.innerWidth;
magicalCanvas.height = window.innerHeight;
const magicalParticles = Array.from({ length: 70 }, () => ({
x: Math.random() * magicalCanvas.width,
y: Math.random() * magicalCanvas.height,
radius: Math.random() * 3 + 1,
alpha: Math.random() * 0.65 + 0.2,
speed: Math.random() * 0.6 + 0.2
}));
const fallingDecor = Array.from({ length: 34 }, (_, i) => ({
x: Math.random() * magicalCanvas.width,
y: Math.random() * magicalCanvas.height,
speed: Math.random() * 0.8 + 0.35,
drift: Math.random() * 0.5 - 0.25,
size: Math.random() * 10 + 9,
rotation: Math.random() * Math.PI * 2,
rotationSpeed: Math.random() * 0.015 - 0.0075,
type: i % 3 === 0 ? 'flower' : 'heart',
alpha: Math.random() * 0.4 + 0.35
}));
function drawHeart(ctx, x, y, size, rotation, alpha) {
ctx.save();
ctx.translate(x, y);
ctx.rotate(rotation);
ctx.globalAlpha = alpha;
ctx.fillStyle = '#f48fb1';
ctx.beginPath();
const s = size;
ctx.moveTo(0, s * 0.35);
ctx.bezierCurveTo(-s * 0.9, -s * 0.2, -s * 0.65, -s, 0, -s * 0.45);
ctx.bezierCurveTo(s * 0.65, -s, s * 0.9, -s * 0.2, 0, s * 0.35);
ctx.fill();
ctx.restore();
}
function drawFlower(ctx, x, y, size, rotation, alpha) {
ctx.save();
ctx.translate(x, y);
ctx.rotate(rotation);
ctx.globalAlpha = alpha;
ctx.fillStyle = '#ffd1e1';
for (let i = 0; i < 5; i++) {
ctx.save();
ctx.rotate((Math.PI * 2 * i) / 5);
ctx.beginPath();
ctx.ellipse(0, -size * 0.42, size * 0.27, size * 0.48, 0, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
ctx.fillStyle = '#ffe57f';
ctx.beginPath();
ctx.arc(0, 0, size * 0.18, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
function animateMagical() {
mCtx.clearRect(0, 0, magicalCanvas.width, magicalCanvas.height);
magicalParticles.forEach(p => {
p.y -= p.speed;
if (p.y < 0) p.y = magicalCanvas.height;
mCtx.fillStyle = `rgba(212, 175, 55, ${p.alpha})`;
mCtx.beginPath();
mCtx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
mCtx.fill();
});
fallingDecor.forEach(p => {
p.y += p.speed;
p.x += p.drift + Math.sin(p.y * 0.01) * 0.12;
p.rotation += p.rotationSpeed;
if (p.y > magicalCanvas.height + 30) {
p.y = -30;
p.x = Math.random() * magicalCanvas.width;
}
if (p.x < -30) p.x = magicalCanvas.width + 30;
if (p.x > magicalCanvas.width + 30) p.x = -30;
if (p.type === 'heart') {
drawHeart(mCtx, p.x, p.y, p.size, p.rotation, p.alpha);
} else {
drawFlower(mCtx, p.x, p.y, p.size, p.rotation, p.alpha);
}
});
requestAnimationFrame(animateMagical);
}
animateMagical();
window.addEventListener('resize', () => {
starsCanvas.width = window.innerWidth;
starsCanvas.height = window.innerHeight;
magicalCanvas.width = window.innerWidth;
magicalCanvas.height = window.innerHeight;
initStars();
fallingDecor.forEach(p => {
p.x = Math.random() * magicalCanvas.width;
p.y = Math.random() * magicalCanvas.height;
});
});
</script>
</body>
</html>