Unclickable NO & Birthday Love Surprise (3D Interactive)

The viral "Do you love me?" love proposal card with an unclickable runaway "No" button, interactive 3D kitty mascot, 3D card tilt, confetti explosion, and customized birthday surprise dedication.

Difficulty: Intermediate Est. Time: 30 mins Vanilla JS (ES6+) Includes HTML, CSS, JS & README (.ZIP)
Live UI Runner

Unclickable NO & Birthday Love Surprise (3D Interactive)

live-sandbox://unclickable-no-love-surprise

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.

HTML (index.html)
<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">
            &ldquo;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. ✨&rdquo;
          </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">&times;</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 &amp; 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 &amp; 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.

CSS (style.css)
* {
  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:

JavaScript (script.js)
(() => {
  // 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

Evasive Cursor Physics

Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.

3D Card Tilt & Parallax

Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.

Confetti Particle System

Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.

Dynamic DOM Scaling

Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.

Web Audio API

Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.

State Transitions

Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.

4. How to Run Locally on Your Computer

  1. 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.
  2. Extract the archive: Unzip the downloaded file. Inside you will find separate index.html, style.css, script.js, index-standalone.html, and a comprehensive README.md.
  3. Open in any browser: Simply double-click index.html (or index-standalone.html) to open and test immediately in Chrome, Firefox, Safari, or Edge.
  4. Edit in Visual Studio Code: Open the extracted folder in VS Code. Right-click index.html and 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.

Other Beginner Projects You Might Like

AnimatedBeginner20 mins
UI & Animations

Interactive Animated Login Form

A playful animated login form with an interactive mascot avatar that tracks your typing and covers its eyes on password input.

DOM Focus & Blur EventsCSS Transform & TransitionsPassword Peek Toggle+2
AnimatedBeginner25 mins
UI & Animations

Light Switch & Lamp Turn On Login Form

A dramatic interactive login page with a 3D hanging lamp and pull cord that turns on the spotlight to illuminate the dark room.

Dynamic CSS Conical Gradients & GlowWeb Audio Synthesis (Click SFX)DOM State & Class Transitions+2
AnimatedBeginner20 mins
UI & Animations

Folding Paper Plane Subscribe Animation

A creative subscribe button animation where clicking Send folds the button into an origami paper airplane that flies away.

CSS 3D Transforms & KeyframesSVG Origami 3D Facet FoldsMulti-Stage State Machine (Input βž” Folding βž” Sending βž” Subscribed)+2