my love
2 hours ago in JavaScript
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Click to Surprise 💌</title>
<style>
:root{--bg:#0f1724;--accent:#ff5c8a;--muted:#cbd5e1}
html,body{height:100%;margin:0;font-family:Inter,system-ui,Segoe UI,Roboto,'Helvetica Neue',Arial}
body{background:linear-gradient(180deg,#071021 0%, #0f1724 60%);color:var(--muted);display:grid;place-items:center;padding:24px}
.card{width:min(760px,96%);background:linear-gradient(180deg,rgba(255,255,255,0.03),rgba(255,255,255,0.02));border-radius:18px;box-shadow:0 10px 30px rgba(2,6,23,0.6);padding:34px;position:relative;overflow:hidden}
h1{margin:0 0 8px;color:white;font-size:28px}
p.lead{margin:0 0 18px;color:#a8b3c7}
.big-link{display:inline-block;padding:16px 26px;border-radius:999px;background:linear-gradient(90deg,var(--accent),#ff9ab3);color:white;text-decoration:none;font-weight:700;letter-spacing:0.3px;box-shadow:0 8px 30px rgba(255,92,138,0.18);cursor:pointer;transform:translateY(0);transition:transform .18s ease}
.big-link:active{transform:translateY(2px)}
.sub{margin-top:12px;color:#97a6bd;font-size:13px}
.footer{margin-top:22px;font-size:13px;color:#7f8aa3}
/* surprise panel */
.surprise{position:absolute;inset:0;background:radial-gradient(circle at 10% 10%, rgba(255,92,138,0.06), transparent 6%), linear-gradient(180deg, rgba(10,14,22,0.4), rgba(5,7,11,0.8));display:flex;flex-direction:column;align-items:center;justify-content:center;padding:36px;backdrop-filter: blur(6px);opacity:0;pointer-events:none;transition:opacity .45s ease, transform .45s ease}
.surprise.show{opacity:1;pointer-events:auto}
.message{color:white;text-align:center}
.message h2{font-size:34px;margin:0 0 4px}
.message p{margin:0 0 14px;color:#d6e1f2}
.heart{width:120px;height:120px;margin:12px auto 18px;transform:scale(0.9);animation:pulse 1.6s infinite}
@keyframes pulse{0%{transform:scale(0.95)}50%{transform:scale(1.05)}100%{transform:scale(0.95)}}
/* small floating hearts */
.hearts{position:absolute;inset:0;pointer-events:none}
.heart-emoji{position:absolute;font-size:20px;opacity:0;will-change:transform,opacity}
/* confetti canvas full */
canvas{position:absolute;left:0;top:0;pointer-events:none}
/* personalization tip */
.edit-note{margin-top:18px;font-size:13px;color:#9fb0d1}
.name-input{margin-top:10px;padding:10px 12px;border-radius:10px;border:1px solid rgba(255,255,255,0.06);background:transparent;color:white;width:220px}
/* small screens */
@media (max-width:520px){.message h2{font-size:26px}.big-link{padding:12px 18px}}
</style>
</head>
<body>
<div class="card">
<h1>Send a tiny surprise link</h1>
<p class="lead">A single click reveals a sweet animated surprise hearts, confetti and a short melody. Perfect to impress someone special.</p>
<div>
<label style="display:block;margin-bottom:8px;color:#9fb0d1;font-weight:600">Type a name (optional)</label>
<input id="nameField" class="name-input" placeholder="Type their name — e.g. Aisha" />
</div>
<div style="margin-top:18px">
<a id="surpriseBtn" class="big-link">Click here to open your surprise 💖</a>
<div class="sub">Pro tip: send this file or host it and share the link they just click and enjoy.</div>
</div>
<div class="footer">Made with care No ads, no trackers just a sweet moment.</div>
<!-- surprise overlay -->
<div id="surprise" class="surprise" role="dialog" aria-hidden="true">
<div class="message">
<div class="heart" id="bigHeart">
<svg viewBox="0 0 32 29.6" width="120" height="120" xmlns="http://www.w3.org/2000/svg">
<path id="heartPath" d="M23.6 0c-2.7 0-5 1.6-6.6 3.9C14.4 1.6 12.1 0 9.4 0 4.2 0 0 4.3 0 9.6 0 17.1 9.9 22.9 16 29.6c6.1-6.6 16-12.5 16-20 0-5.3-4.2-9.6-8.4-9.6z" fill="url(#g)"/>
<defs>
<linearGradient id="g" x1="0" x2="1">
<stop offset="0%" stop-color="#ff9ab3"/>
<stop offset="100%" stop-color="#ff5c8a"/>
</linearGradient>
</defs>
</svg>
</div>
<h2 id="titleText">Surprise!</h2>
<p id="subtitle">You make my world brighter thanks for being you.</p>
<div style="margin-top:14px">
<a id="closeBtn" class="big-link" style="background:transparent;border:2px solid rgba(255,255,255,0.08);color:white;padding:12px 18px">Close</a>
</div>
<div class="edit-note">If you'd like, edit the name before sending — they'll see it when they open.</div>
</div>
</div>
<div class="hearts" id="hearts"></div>
<canvas id="confettiCanvas"></canvas>
</div>
<script>
// Elements
const btn = document.getElementById('surpriseBtn');
const surprise = document.getElementById('surprise');
const closeBtn = document.getElementById('closeBtn');
const nameField = document.getElementById('nameField');
const titleText = document.getElementById('titleText');
const subtitle = document.getElementById('subtitle');
const heartsLayer = document.getElementById('hearts');
const confettiCanvas = document.getElementById('confettiCanvas');
const ctx = confettiCanvas.getContext('2d');
// Resize canvas
function resizeCanvas(){
confettiCanvas.width = window.innerWidth; confettiCanvas.height = window.innerHeight;
}
resizeCanvas(); window.addEventListener('resize', resizeCanvas);
// Small melody with Web Audio API (no external audio)
function playMelody(){
try{
const AudioCtx = window.AudioContext || window.webkitAudioContext;
const ctx = new AudioCtx();
const now = ctx.currentTime;
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'sine';
osc.connect(gain); gain.connect(ctx.destination);
const notes = [523.25,659.25,783.99,659.25]; // C5,E5,G5,E5
let t = 0;
gain.gain.setValueAtTime(0, now);
osc.start(now);
notes.forEach((f,i)=>{
osc.frequency.setValueAtTime(f, now + t);
gain.gain.linearRampToValueAtTime(0.12, now + t + 0.01);
gain.gain.linearRampToValueAtTime(0.0, now + t + 0.28);
t += 0.3;
});
osc.stop(now + t + 0.05);
}catch(e){console.warn('Audio not supported', e)}
}
// Confetti particle system (simple)
const confetti = [];
function spawnConfetti(x,y){
for(let i=0;i<40;i++){
confetti.push({x,y, vx:(Math.random()-0.5)*6, vy:(Math.random()*-6)-2, r:Math.random()*6+4, rot:Math.random()*360, vr:(Math.random()-0.5)*10, life:Math.random()*80+60, color: `hsl(${Math.floor(Math.random()*60)+330},80%,60%)`});
}
}
function updateConfetti(){
ctx.clearRect(0,0,confettiCanvas.width,confettiCanvas.height);
for(let i=confetti.length-1;i>=0;i--){
const p=confetti[i]; p.life--; if(p.life<0){confetti.splice(i,1);continue}
p.vy+=0.18; p.x+=p.vx; p.y+=p.vy; p.rot+=p.vr;
ctx.save(); ctx.translate(p.x,p.y); ctx.rotate(p.rot*Math.PI/180);
ctx.fillStyle=p.color; ctx.fillRect(-p.r/2,-p.r/2,p.r,p.r*0.6);
ctx.restore();
}
requestAnimationFrame(updateConfetti);
}
updateConfetti();
// Floating hearts
function spawnHearts(){
for(let i=0;i<12;i++){
const el = document.createElement('div'); el.className='heart-emoji';
el.textContent='❤️';
const startX = Math.random()*window.innerWidth;
el.style.left = (startX)+'px';
el.style.top = (window.innerHeight + Math.random()*40) + 'px';
el.style.fontSize = (14 + Math.random()*22)+'px';
heartsLayer.appendChild(el);
const dur = 4500 + Math.random()*2000;
el.animate([
{transform:`translateY(0) scale(0.7)`, opacity:0},
{transform:`translateY(-${window.innerHeight - 80}px) scale(1)`, opacity:1},
{transform:`translateY(-${window.innerHeight + 140}px) scale(0.6)`, opacity:0}
], {duration:dur, easing:'cubic-bezier(.16,.8,.24,1)', iterations:1});
setTimeout(()=>el.remove(), dur+80);
}
}
// Click handler
btn.addEventListener('click', ()=>{
const name = nameField.value.trim();
if(name) titleText.textContent = `For ${name} — 💖`;
else titleText.textContent = 'A little surprise — just for you 💖';
subtitle.textContent = name ? `Hey ${name}, you make everything better.` : 'You make my world brighter — thanks for being you.';
surprise.classList.add('show');
surprise.setAttribute('aria-hidden','false');
// spawn confetti at center
spawnConfetti(window.innerWidth/2, window.innerHeight/3);
playMelody();
spawnHearts();
// repeat small bursts
setTimeout(()=>{spawnConfetti(window.innerWidth/2 + 120, window.innerHeight/3 + 40); spawnHearts();}, 600);
setTimeout(()=>{spawnConfetti(window.innerWidth/2 - 120, window.innerHeight/3 + 10); spawnHearts();}, 1100);
// subtle page title emoji pulse
const orig = document.title;
let blink = 0; const iv = setInterval(()=>{document.title = blink%2? '💘 Opened!' : orig; blink++; if(blink>6){clearInterval(iv);document.title=orig;}},360);
});
closeBtn.addEventListener('click', ()=>{ surprise.classList.remove('show'); surprise.setAttribute('aria-hidden','true'); });
// Allow opening with Enter when focused on input
nameField.addEventListener('keydown', (e)=>{ if(e.key==='Enter'){ e.preventDefault(); btn.click(); } });
// Small tip: allow spacebar to trigger when focused anywhere
window.addEventListener('keydown', (e)=>{ if(e.code==='Space' && document.activeElement.tagName !== 'INPUT'){ e.preventDefault(); btn.click(); } });
// Make sure confetti originates inside viewport when button moves
btn.addEventListener('mousemove', ()=>{});
// Accessibility: focus management
btn.setAttribute('role','button'); btn.setAttribute('tabindex','0');
// Save as file tip (doesn't actually save files): show a quick hint when loaded
window.addEventListener('load', ()=>{
setTimeout(()=>{
console.log('Tip: Save this page as an .html file to share directly.');
},900);
});
</script>
</body>
</html>