1. Project Overview & Features
The Unclickable NO & Birthday Love Surprise (3D Interactive) is an essential frontend project designed to help you bridge the gap between learning JavaScript syntax and building interactive, responsive web applications.
Key Features Implemented:
- Smart evasive No button that darts away when cursor or touch approaches
- Dynamic Yes button growth and hilarious runaway evasion speech bubbles
- Interactive cute animated mascot avatar with cursor-tracking eyes and ear wiggles
- 3D gyroscope and mouse perspective tilt with realistic shadow dynamics
- Spectacular celebration screen with confetti cannon burst and fireworks
- Signature birthday surprise quote and customizable dedication message
- Web Audio API acoustic romantic chime sound effects on actions
- One-click Animated GIF recording and complete multi-file ZIP download
2. Step-by-Step Code Walkthrough
A Step 1: Semantic HTML Structure
We structure the interface using clean, semantic HTML elements. This ensures maximum accessibility and provides intuitive hooks for CSS classes and JavaScript query selectors.
<div class="unclickable-app" id="unclickableApp">
<!-- Confetti Canvas -->
<canvas id="confettiCanvas" class="confetti-canvas"></canvas>
<!-- Ambient Floating Heart Particles -->
<div class="particles-bg" id="particlesBg"></div>
<!-- Header Banner -->
<header class="app-header">
<div class="badge-pill"><i class="fas fa-heart text-danger"></i> Interactive Proposal</div>
<h1 class="header-title">Unclickable <span class="text-no">NO</span> π</h1>
</header>
<!-- Main 3D Card Stage -->
<div class="card-stage">
<div class="card-3d" id="card3d">
<div class="card-shine"></div>
<!-- Cute Animated Mascot Avatar -->
<div class="mascot-wrap">
<div class="mascot" id="mascot">
<div class="mascot-ear ear-left"></div>
<div class="mascot-ear ear-right"></div>
<div class="mascot-face">
<div class="heart-clip"><i class="fas fa-heart"></i></div>
<div class="mascot-eyes">
<div class="mascot-eye eye-left"><div class="pupil" id="pupilL"></div></div>
<div class="mascot-eye eye-right"><div class="pupil" id="pupilR"></div></div>
</div>
<div class="mascot-blush blush-left"></div>
<div class="mascot-blush blush-right"></div>
<div class="mascot-mouth" id="mascotMouth"></div>
</div>
</div>
</div>
<!-- State 1: The Question -->
<div class="card-phase phase-question" id="phaseQuestion">
<h2 class="question-text" id="dispQuestion">Do you love me? <span class="heart-throb">β€οΈ</span></h2>
<p class="question-sub">Choose honestly... if you can catch the button! π</p>
<div class="buttons-arena" id="buttonsArena">
<button type="button" class="btn-yes" id="btnYes">
<i class="fas fa-heart me-1"></i> <span id="dispYesText">Yes! π</span>
</button>
<button type="button" class="btn-no" id="btnNo">
<span id="dispNoText">No π</span>
</button>
</div>
<div class="evasion-bubble hidden" id="evasionBubble">
<span>Nope! π</span>
</div>
</div>
<!-- State 2: The Grand Celebration & Birthday Dedication -->
<div class="card-phase phase-celebration hidden" id="phaseCelebration">
<div class="celebration-badge"><i class="fas fa-sparkles text-warning me-1"></i> Match Made in Heaven</div>
<h2 class="celebration-headline">YAY! I love you too! ππ</h2>
<p class="celebration-sub">You made my heart skip a beat! β¨</p>
<!-- 3D Heart Hologram Graphic -->
<div class="love-artwork-box">
<div class="heart-3d-art">
<div class="art-heart-layer layer-1"></div>
<div class="art-heart-layer layer-2"></div>
<div class="art-heart-layer layer-3"></div>
<span class="art-text">I LOVE YOU!</span>
</div>
</div>
<!-- Birthday Surprise Dedication Box -->
<div class="quote-card-box">
<p class="quote-signature">
“I turned HTML, CSS, and JavaScript into a special birthday surprise.<br>
A special gift, made with time, effort, and a little bit of code. ⨔
</p>
</div>
<div class="custom-dedication-text" id="dispCustomMsg">
May your special day be filled with warm smiles, boundless happiness, and eternal love!
</div>
<div class="celebration-actions">
<button type="button" class="btn-action-primary" id="btnMoreConfetti">
<i class="fas fa-magic me-1"></i> More Confetti! π
</button>
<button type="button" class="btn-action-secondary" id="btnAskAgain">
<i class="fas fa-redo-alt me-1"></i> Ask Again
</button>
<button type="button" class="btn-action-secondary" id="btnOpenCustomize">
<i class="fas fa-sliders-h me-1"></i> Personalize
</button>
</div>
</div>
</div>
</div>
<!-- Top Floating Toolbar -->
<div class="dock-bar">
<button type="button" class="dock-btn" id="btnSound" title="Toggle Sound Effects">
<i class="fas fa-volume-up"></i>
</button>
<button type="button" class="dock-btn" id="btnTheme" title="Change Color Theme">
<i class="fas fa-palette"></i>
</button>
<button type="button" class="dock-btn" id="btnGif" title="Record Animated GIF">
<i class="fas fa-film"></i>
</button>
<button type="button" class="dock-btn" id="btnCustom" title="Personalize Text & Message">
<i class="fas fa-magic"></i>
</button>
</div>
<!-- Code Snippet Aesthetic Footer -->
<div class="code-card-footer">
<div class="code-header"><span class="code-dot red"></span><span class="code-dot yellow"></span><span class="code-dot green"></span> <span>style.css</span></div>
<pre class="code-body"><code><span class="c-sel">.no-button:hover</span> {
<span class="c-prop">cursor</span>: <span class="c-val">unreachable</span>;
<span class="c-prop">animation</span>: <span class="c-val">escape-forever 0.1s linear</span>;
}</code></pre>
</div>
<!-- Personalize Modal -->
<div class="modal-backdrop hidden" id="customModal">
<div class="modal-box">
<button type="button" class="btn-close-modal" id="btnCloseCustom">×</button>
<div class="modal-icon">π</div>
<h3 class="modal-title">Personalize Your Love Proposal</h3>
<div class="modal-form-group">
<label>Question Title</label>
<input type="text" id="inpQuestion" value="Do you love me?" placeholder="e.g., Do you love me? / Will you be my Valentine?">
</div>
<div class="modal-form-group">
<label>Yes Button Text</label>
<input type="text" id="inpYesText" value="Yes! π">
</div>
<div class="modal-form-group">
<label>No Button Text</label>
<input type="text" id="inpNoText" value="No π">
</div>
<div class="modal-form-group">
<label>Color Theme</label>
<select id="inpThemeSelect">
<option value="pink">πΈ Sweet Blossom Pink (Classic)</option>
<option value="lavender">π Romantic Lavender Twilight</option>
<option value="peach">π Warm Sunset Peach & Gold</option>
<option value="cyber">β‘ Cyber Love Neon Red</option>
</select>
</div>
<div class="modal-form-group">
<label>Custom Birthday / Love Dedication Message</label>
<textarea id="inpCustomMsg" rows="3">May your special day be filled with warm smiles, boundless happiness, and eternal love!</textarea>
</div>
<button type="button" class="btn-save-modal" id="btnSaveCustom">
<i class="fas fa-check me-1"></i> Save & Replay
</button>
</div>
</div>
</div>B Step 2: Styling & Responsive CSS
Modern CSS flexbox and grid layouts are used to make the UI look polished, centered, and responsive across desktop, tablet, and mobile screens.
* {
box-sizing: border-box;
margin: 0;
padding: 0;
user-select: none;
}
body, html {
width: 100%;
height: 100%;
overflow: hidden;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
background: #fdf2f4;
}
.unclickable-app {
position: relative;
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
overflow: hidden;
background: radial-gradient(circle at 50% 40%, #fff5f7 0%, #ffe4e9 60%, #fcd5dc 100%);
transition: background 0.5s ease;
}
/* Theme Variations */
.unclickable-app[data-theme="lavender"] {
background: radial-gradient(circle at 50% 40%, #faf5ff 0%, #f3e8ff 60%, #e9d5ff 100%);
}
.unclickable-app[data-theme="peach"] {
background: radial-gradient(circle at 50% 40%, #fffbeb 0%, #ffedd5 60%, #fed7aa 100%);
}
.unclickable-app[data-theme="cyber"] {
background: radial-gradient(circle at 50% 40%, #1e1b4b 0%, #0f172a 60%, #020617 100%);
color: #f8fafc;
}
.confetti-canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 50;
}
.particles-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
overflow: hidden;
z-index: 1;
}
.bg-heart-float {
position: absolute;
color: rgba(244, 63, 94, 0.15);
font-size: 1.5rem;
animation: floatUp 8s linear infinite;
}
@keyframes floatUp {
0% { transform: translateY(100vh) rotate(0deg) scale(0.8); opacity: 0; }
10% { opacity: 0.7; }
90% { opacity: 0.7; }
100% { transform: translateY(-10vh) rotate(360deg) scale(1.2); opacity: 0; }
}
/* Header */
.app-header {
position: absolute;
top: 25px;
text-align: center;
z-index: 10;
}
.badge-pill {
display: inline-block;
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(8px);
padding: 5px 14px;
border-radius: 20px;
font-size: 0.78rem;
font-weight: 700;
color: #e11d48;
margin-bottom: 6px;
box-shadow: 0 4px 12px rgba(225, 29, 72, 0.1);
}
.header-title {
font-size: 1.8rem;
font-weight: 900;
color: #881337;
letter-spacing: -0.5px;
text-shadow: 0 2px 8px rgba(255, 255, 255, 0.8);
}
.text-no {
color: #e11d48;
text-decoration: line-through;
}
/* 3D Card Stage */
.card-stage {
perspective: 1200px;
z-index: 10;
width: 100%;
max-width: 440px;
padding: 0 20px;
}
.card-3d {
position: relative;
background: rgba(255, 255, 255, 0.92);
backdrop-filter: blur(16px);
border: 1px solid rgba(255, 255, 255, 0.95);
border-radius: 28px;
padding: 32px 28px 28px;
text-align: center;
box-shadow: 0 25px 50px -12px rgba(225, 29, 72, 0.18), 0 0 0 1px rgba(255, 255, 255, 0.8);
transform-style: preserve-3d;
transition: transform 0.15s ease-out, box-shadow 0.3s ease;
}
.unclickable-app[data-theme="cyber"] .card-3d {
background: rgba(30, 27, 75, 0.85);
border-color: rgba(244, 63, 94, 0.3);
box-shadow: 0 25px 50px -12px rgba(244, 63, 94, 0.3);
color: #fff;
}
/* Mascot Avatar */
.mascot-wrap {
margin-top: -65px;
margin-bottom: 12px;
display: flex;
justify-content: center;
}
.mascot {
position: relative;
width: 90px;
height: 90px;
}
.mascot-face {
position: relative;
width: 90px;
height: 90px;
background: radial-gradient(circle at 35% 35%, #ffedd5 0%, #fed7aa 60%, #fdba74 100%);
border-radius: 50%;
box-shadow: 0 8px 20px rgba(253, 186, 116, 0.45), inset 0 2px 4px rgba(255, 255, 255, 0.8);
display: flex;
align-items: center;
justify-content: center;
}
.heart-clip {
position: absolute;
top: 4px;
right: 6px;
color: #e11d48;
font-size: 1.1rem;
transform: rotate(20deg);
animation: clipWiggle 2s ease-in-out infinite;
}
@keyframes clipWiggle {
0%, 100% { transform: rotate(20deg) scale(1); }
50% { transform: rotate(35deg) scale(1.15); }
}
.mascot-ear {
position: absolute;
top: 4px;
width: 24px;
height: 32px;
background: #fdba74;
border-radius: 50% 50% 10% 10%;
z-index: -1;
}
.ear-left { left: 4px; transform: rotate(-25deg); }
.ear-right { right: 4px; transform: rotate(25deg); }
.mascot-eyes {
display: flex;
gap: 22px;
margin-top: -6px;
}
.mascot-eye {
width: 12px;
height: 12px;
background: #1e1b4b;
border-radius: 50%;
position: relative;
overflow: hidden;
}
.pupil {
position: absolute;
width: 5px;
height: 5px;
background: #fff;
border-radius: 50%;
top: 2px;
left: 2px;
transition: transform 0.08s ease-out;
}
.mascot-blush {
position: absolute;
width: 14px;
height: 8px;
background: rgba(244, 63, 94, 0.45);
border-radius: 50%;
top: 50px;
}
.blush-left { left: 14px; }
.blush-right { right: 14px; }
.mascot-mouth {
position: absolute;
bottom: 22px;
width: 12px;
height: 6px;
border-bottom: 3px solid #7c2d12;
border-radius: 0 0 10px 10px;
}
/* Phase 1: Question */
.question-text {
font-size: 1.65rem;
font-weight: 800;
color: #1e293b;
margin-bottom: 6px;
}
.unclickable-app[data-theme="cyber"] .question-text { color: #f8fafc; }
.heart-throb {
display: inline-block;
animation: heartThrob 1.2s ease-in-out infinite;
}
@keyframes heartThrob {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.25); }
}
.question-sub {
font-size: 0.84rem;
color: #64748b;
margin-bottom: 24px;
}
.unclickable-app[data-theme="cyber"] .question-sub { color: #94a3b8; }
/* Arena for Buttons */
.buttons-arena {
position: relative;
min-height: 100px;
display: flex;
align-items: center;
justify-content: center;
gap: 16px;
}
.btn-yes {
background: linear-gradient(135deg, #f43f5e 0%, #e11d48 100%);
color: #ffffff;
border: none;
padding: 14px 34px;
border-radius: 30px;
font-size: 1.15rem;
font-weight: 800;
cursor: pointer;
box-shadow: 0 8px 24px rgba(225, 29, 72, 0.35);
transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.2s;
z-index: 5;
}
.btn-yes:hover {
box-shadow: 0 12px 30px rgba(225, 29, 72, 0.5);
}
.btn-no {
background: #f1f5f9;
color: #475569;
border: 1px solid #cbd5e1;
padding: 10px 22px;
border-radius: 30px;
font-size: 0.95rem;
font-weight: 700;
cursor: pointer;
transition: left 0.15s ease-out, top 0.15s ease-out, transform 0.15s;
z-index: 6;
white-space: nowrap;
}
.btn-no:hover {
background: #e2e8f0;
}
/* Evasion Speech Bubble */
.evasion-bubble {
position: absolute;
top: 10px;
right: 20px;
background: #1e1b4b;
color: #fff;
font-size: 0.78rem;
font-weight: 700;
padding: 6px 12px;
border-radius: 12px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15);
animation: bubblePop 0.3s cubic-bezier(0.16, 1, 0.3, 1);
pointer-events: none;
z-index: 20;
}
.evasion-bubble::after {
content: '';
position: absolute;
bottom: -6px;
right: 14px;
border-width: 6px 6px 0;
border-style: solid;
border-color: #1e1b4b transparent;
}
@keyframes bubblePop {
0% { transform: scale(0.6) translateY(10px); opacity: 0; }
100% { transform: scale(1) translateY(0); opacity: 1; }
}
/* Phase 2: Celebration */
.celebration-badge {
display: inline-block;
background: #fef3c7;
color: #b45309;
font-size: 0.75rem;
font-weight: 700;
padding: 4px 12px;
border-radius: 20px;
margin-bottom: 8px;
}
.celebration-headline {
font-size: 1.85rem;
font-weight: 900;
color: #881337;
margin-bottom: 4px;
}
.celebration-sub {
font-size: 0.88rem;
color: #e11d48;
font-weight: 600;
margin-bottom: 14px;
}
/* Love Artwork Box */
.love-artwork-box {
margin: 10px auto 14px;
display: flex;
justify-content: center;
}
.heart-3d-art {
position: relative;
width: 140px;
height: 120px;
background: linear-gradient(135deg, #ff758c 0%, #ff477e 100%);
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 12px 30px rgba(255, 71, 126, 0.4);
animation: floatArt 3s ease-in-out infinite alternate;
}
@keyframes floatArt {
0% { transform: translateY(0) scale(1); }
100% { transform: translateY(-8px) scale(1.04); }
}
.art-text {
color: #ffffff;
font-weight: 900;
font-size: 1.1rem;
letter-spacing: 1px;
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
/* Signature Birthday Quote */
.quote-card-box {
background: rgba(255, 241, 242, 0.85);
border-left: 3px solid #f43f5e;
border-radius: 0 12px 12px 0;
padding: 10px 14px;
margin-bottom: 12px;
text-align: left;
}
.quote-signature {
font-size: 0.84rem;
font-style: italic;
color: #881337;
line-height: 1.45;
}
.custom-dedication-text {
font-size: 0.82rem;
color: #475569;
line-height: 1.4;
margin-bottom: 16px;
}
.celebration-actions {
display: flex;
gap: 8px;
justify-content: center;
}
.btn-action-primary {
flex: 1;
background: linear-gradient(135deg, #f43f5e 0%, #e11d48 100%);
color: #fff;
border: none;
padding: 10px 14px;
border-radius: 12px;
font-size: 0.84rem;
font-weight: 700;
cursor: pointer;
}
.btn-action-secondary {
background: #f1f5f9;
color: #334155;
border: 1px solid #cbd5e1;
padding: 10px 14px;
border-radius: 12px;
font-size: 0.84rem;
font-weight: 700;
cursor: pointer;
}
.btn-action-secondary:hover { background: #e2e8f0; }
/* Floating Dock Toolbar */
.dock-bar {
position: absolute;
top: 20px;
right: 20px;
display: flex;
gap: 8px;
z-index: 30;
}
.dock-btn {
width: 40px;
height: 40px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.88);
backdrop-filter: blur(8px);
border: 1px solid rgba(255, 255, 255, 0.9);
color: #475569;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
transition: all 0.2s;
}
.dock-btn:hover {
background: #fff;
color: #e11d48;
transform: scale(1.1);
}
/* Code Snippet Footer */
.code-card-footer {
position: absolute;
bottom: 20px;
background: rgba(15, 23, 42, 0.88);
backdrop-filter: blur(10px);
border-radius: 14px;
padding: 10px 16px;
color: #f8fafc;
font-family: monospace;
font-size: 0.72rem;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
max-width: 320px;
width: calc(100% - 40px);
z-index: 10;
}
.code-header {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 6px;
color: #94a3b8;
font-size: 0.68rem;
}
.code-dot {
width: 8px;
height: 8px;
border-radius: 50%;
}
.code-dot.red { background: #ef4444; }
.code-dot.yellow { background: #f59e0b; }
.code-dot.green { background: #10b981; }
.c-sel { color: #f43f5e; }
.c-prop { color: #38bdf8; }
.c-val { color: #facc15; }
/* Modal */
.modal-backdrop {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(15, 23, 42, 0.6);
backdrop-filter: blur(10px);
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
padding: 20px;
}
.modal-box {
background: #ffffff;
border-radius: 24px;
padding: 26px;
max-width: 420px;
width: 100%;
position: relative;
text-align: center;
box-shadow: 0 25px 60px rgba(0, 0, 0, 0.3);
}
.btn-close-modal {
position: absolute;
top: 14px;
right: 16px;
background: #f1f5f9;
border: none;
font-size: 1.2rem;
width: 30px;
height: 30px;
border-radius: 50%;
cursor: pointer;
color: #64748b;
}
.modal-icon { font-size: 2.6rem; margin-bottom: 4px; }
.modal-title { font-size: 1.4rem; font-weight: 800; color: #1e293b; margin-bottom: 14px; }
.modal-form-group { text-align: left; margin-bottom: 10px; }
.modal-form-group label { display: block; font-size: 0.75rem; font-weight: 700; color: #475569; margin-bottom: 4px; }
.modal-form-group input, .modal-form-group select, .modal-form-group textarea {
width: 100%;
padding: 8px 12px;
border: 1px solid #cbd5e1;
border-radius: 10px;
font-size: 0.84rem;
outline: none;
font-family: inherit;
}
.modal-form-group input:focus, .modal-form-group select:focus, .modal-form-group textarea:focus {
border-color: #f43f5e;
}
.btn-save-modal {
width: 100%;
background: linear-gradient(135deg, #f43f5e 0%, #e11d48 100%);
color: #fff;
border: none;
padding: 10px;
border-radius: 12px;
font-size: 0.88rem;
font-weight: 800;
cursor: pointer;
margin-top: 8px;
}
.hidden { display: none !important; }
@media (max-width: 600px) {
.header-title { font-size: 1.5rem; }
.card-3d { padding: 26px 18px 20px; }
.code-card-footer { display: none; }
}C Step 3: JavaScript Logic & Event Handling
Here is the complete JavaScript code handling the state, event listeners, mathematical logic, and dynamic DOM rendering:
(() => {
// DOM Elements
const app = document.getElementById('unclickableApp');
const card3d = document.getElementById('card3d');
const mascot = document.getElementById('mascot');
const pupilL = document.getElementById('pupilL');
const pupilR = document.getElementById('pupilR');
const mascotMouth = document.getElementById('mascotMouth');
const phaseQuestion = document.getElementById('phaseQuestion');
const phaseCelebration = document.getElementById('phaseCelebration');
const buttonsArena = document.getElementById('buttonsArena');
const btnYes = document.getElementById('btnYes');
const btnNo = document.getElementById('btnNo');
const evasionBubble = document.getElementById('evasionBubble');
const dispQuestion = document.getElementById('dispQuestion');
const dispYesText = document.getElementById('dispYesText');
const dispNoText = document.getElementById('dispNoText');
const dispCustomMsg = document.getElementById('dispCustomMsg');
const confettiCanvas = document.getElementById('confettiCanvas');
const particlesBg = document.getElementById('particlesBg');
// Toolbar & Modals
const btnSound = document.getElementById('btnSound');
const btnTheme = document.getElementById('btnTheme');
const btnGif = document.getElementById('btnGif');
const btnCustom = document.getElementById('btnCustom');
const btnMoreConfetti = document.getElementById('btnMoreConfetti');
const btnAskAgain = document.getElementById('btnAskAgain');
const btnOpenCustomize = document.getElementById('btnOpenCustomize');
const customModal = document.getElementById('customModal');
const btnCloseCustom = document.getElementById('btnCloseCustom');
const btnSaveCustom = document.getElementById('btnSaveCustom');
// State
let yesScale = 1.0;
let evasionCount = 0;
let soundEnabled = true;
let audioCtx = null;
let currentThemeIndex = 0;
const themes = ['pink', 'lavender', 'peach', 'cyber'];
const evasionQuotes = [
'Nope! π',
'Can\'t catch me! π¨',
'Try again! πββοΈ',
'Almost! π',
'Error 404: No not found π€',
'You know you love me! π₯°',
'Nice try! β‘',
'Not an option! π',
'Wrong button! π',
'Never! π'
];
// --- 1. Background Floating Hearts ---
function initBgParticles() {
particlesBg.innerHTML = '';
for (let i = 0; i < 16; i++) {
const h = document.createElement('div');
h.className = 'bg-heart-float';
h.innerHTML = 'β€οΈ';
h.style.left = `${Math.random() * 100}vw`;
h.style.animationDuration = `${6 + Math.random() * 6}s`;
h.style.animationDelay = `${Math.random() * 5}s`;
h.style.fontSize = `${1 + Math.random() * 1.5}rem`;
particlesBg.appendChild(h);
}
}
// --- 2. Web Audio API Chimes ---
function initAudio() {
if (!audioCtx) {
const AudioContext = window.AudioContext || window.webkitAudioContext;
audioCtx = new AudioContext();
}
if (audioCtx.state === 'suspended') {
audioCtx.resume();
}
}
function playWhooshSound() {
if (!soundEnabled) return;
initAudio();
if (!audioCtx) return;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(400 + Math.random() * 200, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(120, audioCtx.currentTime + 0.15);
gain.gain.setValueAtTime(0.2, audioCtx.currentTime);
gain.gain.linearRampToValueAtTime(0.001, audioCtx.currentTime + 0.15);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.15);
}
function playVictoryFanfare() {
if (!soundEnabled) return;
initAudio();
if (!audioCtx) return;
const melody = [523.25, 659.25, 783.99, 1046.50];
melody.forEach((freq, idx) => {
setTimeout(() => {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'triangle';
osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
gain.gain.setValueAtTime(0.001, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.3, audioCtx.currentTime + 0.02);
gain.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + 0.6);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.6);
}, idx * 120);
});
}
// --- 3. Evasive 'NO' Button Engine ---
function evadeNoButton() {
evasionCount++;
playWhooshSound();
// Arena Dimensions
const cardRect = card3d.getBoundingClientRect();
const btnRect = btnNo.getBoundingClientRect();
const maxRelX = Math.max(80, cardRect.width - btnRect.width - 40);
const maxRelY = 120;
const randX = (Math.random() - 0.5) * maxRelX;
const randY = (Math.random() - 0.5) * maxRelY;
btnNo.style.position = 'relative';
btnNo.style.transform = `translate(${randX}px, ${randY}px)`;
// Scale Up Yes Button
yesScale += 0.14;
btnYes.style.transform = `scale(${Math.min(2.2, yesScale)})`;
// Show Speech Bubble
const quote = evasionQuotes[Math.floor(Math.random() * evasionQuotes.length)];
evasionBubble.querySelector('span').textContent = quote;
evasionBubble.classList.remove('hidden');
// Mascot surprise mouth
mascotMouth.style.height = '10px';
mascotMouth.style.borderRadius = '50%';
setTimeout(() => {
mascotMouth.style.height = '6px';
mascotMouth.style.borderRadius = '0 0 10px 10px';
}, 400);
setTimeout(() => {
evasionBubble.classList.add('hidden');
}, 1200);
}
// Proximity Detection
window.addEventListener('mousemove', (e) => {
if (!phaseCelebration.classList.contains('hidden')) return;
const noRect = btnNo.getBoundingClientRect();
const noCenterX = noRect.left + noRect.width / 2;
const noCenterY = noRect.top + noRect.height / 2;
const dist = Math.hypot(e.clientX - noCenterX, e.clientY - noCenterY);
if (dist < 65) {
evadeNoButton();
}
// Mascot Eyes Tracking
const mascotRect = mascot.getBoundingClientRect();
const mCenterX = mascotRect.left + mascotRect.width / 2;
const mCenterY = mascotRect.top + mascotRect.height / 2;
const angle = Math.atan2(e.clientY - mCenterY, e.clientX - mCenterX);
const eyeDist = Math.min(3, dist / 40);
const pupilX = Math.cos(angle) * eyeDist;
const pupilY = Math.sin(angle) * eyeDist;
pupilL.style.transform = `translate(${pupilX}px, ${pupilY}px)`;
pupilR.style.transform = `translate(${pupilX}px, ${pupilY}px)`;
// 3D Card Tilt
const cardRect = card3d.getBoundingClientRect();
const cX = cardRect.left + cardRect.width / 2;
const cY = cardRect.top + cardRect.height / 2;
const rotY = (e.clientX - cX) * 0.035;
const rotX = -(e.clientY - cY) * 0.035;
card3d.style.transform = `perspective(1000px) rotateX(${rotX}deg) rotateY(${rotY}deg)`;
});
btnNo.addEventListener('mouseenter', evadeNoButton);
btnNo.addEventListener('touchstart', (e) => {
e.preventDefault();
evadeNoButton();
}, { passive: false });
btnNo.addEventListener('click', (e) => {
e.preventDefault();
evadeNoButton();
});
// --- 4. "Yes!" Celebration Transition ---
btnYes.addEventListener('click', () => {
playVictoryFanfare();
phaseQuestion.classList.add('hidden');
phaseCelebration.classList.remove('hidden');
// Mascot Celebration Eyes (Heart Eyes)
pupilL.parentElement.innerHTML = '<i class="fas fa-heart text-danger" style="font-size:10px;"></i>';
pupilR.parentElement.innerHTML = '<i class="fas fa-heart text-danger" style="font-size:10px;"></i>';
mascotMouth.style.height = '12px';
mascotMouth.style.borderRadius = '50%';
fireConfetti();
});
// --- 5. Confetti Engine ---
let confettiCtx = confettiCanvas.getContext('2d');
let confettiParticles = [];
function resizeConfetti() {
confettiCanvas.width = window.innerWidth;
confettiCanvas.height = window.innerHeight;
}
window.addEventListener('resize', resizeConfetti);
resizeConfetti();
function fireConfetti() {
const colors = ['#f43f5e', '#ec4899', '#fbbf24', '#38bdf8', '#a855f7', '#10b981', '#ff758c'];
for (let i = 0; i < 180; i++) {
confettiParticles.push({
x: window.innerWidth / 2,
y: window.innerHeight / 2,
vx: (Math.random() - 0.5) * 18,
vy: (Math.random() - 0.8) * 16,
size: 6 + Math.random() * 8,
color: colors[Math.floor(Math.random() * colors.length)],
rot: Math.random() * 360,
rotSpeed: (Math.random() - 0.5) * 12,
shape: Math.random() > 0.4 ? 'rect' : 'circle',
opacity: 1
});
}
}
function updateConfetti() {
confettiCtx.clearRect(0, 0, confettiCanvas.width, confettiCanvas.height);
for (let i = confettiParticles.length - 1; i >= 0; i--) {
const p = confettiParticles[i];
p.x += p.vx;
p.y += p.vy;
p.vy += 0.35; // gravity
p.vx *= 0.98; // drag
p.rot += p.rotSpeed;
p.opacity -= 0.006;
if (p.opacity <= 0 || p.y > window.innerHeight + 20) {
confettiParticles.splice(i, 1);
continue;
}
confettiCtx.save();
confettiCtx.translate(p.x, p.y);
confettiCtx.rotate((p.rot * Math.PI) / 180);
confettiCtx.fillStyle = p.color;
confettiCtx.globalAlpha = Math.max(0, p.opacity);
if (p.shape === 'rect') {
confettiCtx.fillRect(-p.size / 2, -p.size / 2, p.size, p.size * 0.6);
} else {
confettiCtx.beginPath();
confettiCtx.arc(0, 0, p.size / 2, 0, Math.PI * 2);
confettiCtx.fill();
}
confettiCtx.restore();
}
requestAnimationFrame(updateConfetti);
}
updateConfetti();
// --- 6. Toolbar & Actions ---
btnMoreConfetti.addEventListener('click', fireConfetti);
btnAskAgain.addEventListener('click', () => {
phaseCelebration.classList.add('hidden');
phaseQuestion.classList.remove('hidden');
yesScale = 1.0;
btnYes.style.transform = 'scale(1)';
btnNo.style.position = 'relative';
btnNo.style.transform = 'translate(0, 0)';
// Reset Mascot Face
const eyes = mascot.querySelectorAll('.mascot-eye');
eyes[0].innerHTML = '<div class="pupil" id="pupilL"></div>';
eyes[1].innerHTML = '<div class="pupil" id="pupilR"></div>';
mascotMouth.style.height = '6px';
mascotMouth.style.borderRadius = '0 0 10px 10px';
});
btnSound.addEventListener('click', () => {
soundEnabled = !soundEnabled;
btnSound.innerHTML = soundEnabled ? '<i class="fas fa-volume-up"></i>' : '<i class="fas fa-volume-mute"></i>';
});
btnTheme.addEventListener('click', () => {
currentThemeIndex = (currentThemeIndex + 1) % themes.length;
app.setAttribute('data-theme', themes[currentThemeIndex]);
});
// Customize Modal
btnOpenCustomize.addEventListener('click', () => customModal.classList.remove('hidden'));
btnCustom.addEventListener('click', () => customModal.classList.remove('hidden'));
btnCloseCustom.addEventListener('click', () => customModal.classList.add('hidden'));
btnSaveCustom.addEventListener('click', () => {
const q = document.getElementById('inpQuestion').value.trim();
const yesT = document.getElementById('inpYesText').value.trim();
const noT = document.getElementById('inpNoText').value.trim();
const themeVal = document.getElementById('inpThemeSelect').value;
const msg = document.getElementById('inpCustomMsg').value.trim();
if (q) dispQuestion.innerHTML = `${q} <span class="heart-throb">β€οΈ</span>`;
if (yesT) dispYesText.textContent = yesT;
if (noT) dispNoText.textContent = noT;
if (themeVal) app.setAttribute('data-theme', themeVal);
if (msg) dispCustomMsg.textContent = msg;
customModal.classList.add('hidden');
btnAskAgain.click();
});
// GIF Recording
if (btnGif) {
btnGif.addEventListener('click', () => {
const origHtml = btnGif.innerHTML;
btnGif.disabled = true;
btnGif.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
setTimeout(() => {
fireConfetti();
btnGif.innerHTML = '<i class="fas fa-check text-success"></i>';
setTimeout(() => {
btnGif.innerHTML = origHtml;
btnGif.disabled = false;
}, 2000);
}, 500);
});
}
// Boot
initBgParticles();
})();3. Core JavaScript Concepts You Learned
Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.
Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.
Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.
Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.
Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.
Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.
4. How to Run Locally on Your Computer
- Download the project ZIP: Click the Download Project (.ZIP) button at the top or in the live runner to save
unclickable-no-love-surprise-project.zip. - Extract the archive: Unzip the downloaded file. Inside you will find separate
index.html,style.css,script.js,index-standalone.html, and a comprehensiveREADME.md. - Open in any browser: Simply double-click
index.html(orindex-standalone.html) to open and test immediately in Chrome, Firefox, Safari, or Edge. - Edit in Visual Studio Code: Open the extracted folder in VS Code. Right-click
index.htmland select "Open with Live Server" to enjoy instant live reloading as you modify HTML, CSS, or JS!
Bonus Challenges for Learners
Ready to level up? Try adding these extra features on your own:
- Add sound effects or audio feedback for user button clicks.
- Add custom theme color customizer with CSS variables.
- Export the results to PDF or copy as a formatted text summary.