Special Birthday Code Surprise

A heartwarming interactive birthday surprise built with HTML, CSS, and JavaScript. Features an animated blossoming heart tree, code terminal typewriter, live milestone clock, musical chimes, and animated GIF export.

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

Special Birthday Code Surprise

live-sandbox://special-birthday-gift

1. Project Overview & Features

The Special Birthday Code Surprise 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:

  • Procedural organic heart tree blooming with 500+ multi-shade fluttering petals
  • macOS styled code terminal window with realistic line-by-line syntax-highlighted typing
  • Live milestone clock tracking real-time celebration days, hours, minutes, seconds
  • Web Audio API romantic & birthday music-box synthesizer (no external audio required)
  • Interactive 3D gift box with unwrap explosion and floating heart fireworks
  • One-click Animated GIF recorder to save and share the surprise on WhatsApp / Social Media
  • Customization dock to personalize recipient name, poem, music, and background themes

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="special-gift-app" id="specialGiftApp">
  <!-- Top Floating Celebration Bar -->
  <div class="gift-top-bar">
    <div class="brand-pill">
      <span class="sparkle-icon">✨</span>
      <span class="gift-heading">A Special Birthday Gift</span>
      <span class="dedication-tag" id="dedicationTag">To: Someone Very Special πŸ’•</span>
    </div>
    <div class="bar-btn-group">
      <button type="button" class="bar-btn btn-export-gif" id="btnRecordCardGif" title="Download as Animated GIF">
        <i class="fas fa-file-video"></i> <span id="gifExportText">Download GIF</span>
      </button>
      <button type="button" class="bar-btn btn-music-play" id="btnAudioMelody" title="Toggle Musical Chimes">
        <i class="fas fa-music"></i> <span id="audioStatusText">Play Music</span>
      </button>
      <button type="button" class="bar-btn btn-gift-unwrap" id="btnUnwrapGift" title="Unwrap Birthday Gift">
        <i class="fas fa-gift"></i> Surprise
      </button>
      <button type="button" class="bar-btn btn-edit-wish" id="btnOpenWishModal" title="Edit Poem & Name">
        <i class="fas fa-pen"></i> Edit Wish
      </button>
      <button type="button" class="bar-btn btn-replay-tree" id="btnReplaySurprise" title="Replay Animation">
        <i class="fas fa-redo"></i> Replay
      </button>
    </div>
  </div>

  <!-- The Main Stage (#wrap and #main structure) -->
  <div id="main" class="main-canvas-wrapper">
    <div id="wrap" class="heart-stage-layout">
      
      <!-- Left: macOS Styled Code / Poem Window -->
      <div id="text" class="code-terminal-card">
        <div class="terminal-top-bar">
          <div class="terminal-dots">
            <span class="t-dot dot-red"></span>
            <span class="t-dot dot-yellow"></span>
            <span class="t-dot dot-green"></span>
          </div>
          <span class="terminal-file-name"><i class="fab fa-html5 text-danger me-1"></i> index.html</span>
        </div>

        <div id="code" class="code-editor-lines">
          <!-- Typewriter content typed out with syntax highlight spans -->
          <div class="typed-code-content" id="typedCodeBlock"></div>
          <span class="blinking-code-cursor" id="blinkingCursor">_</span>
        </div>

        <!-- Clock / Milestone Box (Exact match from screenshot) -->
        <div id="clock-box" class="milestone-clock-card">
          <div class="clock-badge-icon"><i class="fas fa-hourglass-half"></i></div>
          <div class="clock-text-wrap">
            <div class="clock-title">Days & Time Celebrated:</div>
            <span id="clock" class="clock-display-live">365 days 0 hours 0 minutes 0 seconds</span>
          </div>
        </div>
      </div>

      <!-- Right: Pure HTML5 Tree & Blooming Heart Canopy Canvas -->
      <div class="canvas-wrapper-box">
        <canvas id="canvas" width="680" height="580"></canvas>
        <div class="canvas-interactive-toast" id="interactiveToast">
          <i class="fas fa-heart text-danger"></i> Move cursor for sparkling heart dust β€’ Click to blast fireworks!
        </div>
      </div>

    </div>
  </div>

  <!-- 3D Gift Box Modal (Unwrap Surprise) -->
  <div class="gift-modal-overlay hidden" id="giftModal">
    <div class="gift-surprise-card">
      <div class="gift-box-3d" id="giftBoxAnimation">
        <div class="box-lid">🎁</div>
      </div>
      <h2 class="surprise-title">Happy Birthday! πŸŽ‚βœ¨</h2>
      <p class="surprise-quote">"I turned HTML, CSS, and JavaScript into a special birthday surprise. A special gift, made with time, effort, and a little bit of code."</p>
      <button type="button" class="btn-close-gift" id="btnCloseGiftModal">Love It! ❀️</button>
    </div>
  </div>

  <!-- Customization Modal -->
  <div class="wish-modal-overlay hidden" id="wishModal">
    <div class="wish-card-dialog">
      <div class="wish-dialog-header">
        <h3><i class="fas fa-heart text-danger me-2"></i> Customize Your Birthday Code Gift</h3>
        <button type="button" class="btn-close-x" id="btnCloseWishModal"><i class="fas fa-times"></i></button>
      </div>
      <div class="wish-dialog-body">
        <div class="wish-field">
          <label for="recipientInput">Recipient's Name / Nickname:</label>
          <input type="text" id="recipientInput" value="My Favorite Person" placeholder="e.g. Sarah, Alex, Mom, Bestie">
        </div>
        <div class="wish-field">
          <label for="poemInput">Custom Code Lines (HTML/Poem):</label>
          <textarea id="poemInput" rows="7" placeholder="Write line by line...">Hey you πŸ’•
Happy Birthday πŸŽ‚βœ¨
May God bless you 🌸
And give u many happiness πŸ’•
Just saying... you're pretty awesome πŸ’–
Sending good vibes and maybe a wink πŸ˜‰
Hope u have a great day today πŸ’•
I turned HTML, CSS, and JavaScript into a special birthday surprise.
A special gift, made with time, effort, and a little bit of code. ✨
β€” Forever & Always ❀️</textarea>
        </div>
      </div>
      <div class="wish-dialog-footer">
        <button type="button" class="btn-wish-cancel" id="btnCancelWishModal">Cancel</button>
        <button type="button" class="btn-wish-submit" id="btnSaveWishModal"><i class="fas fa-sparkles me-1"></i> Update & Replay</button>
      </div>
    </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;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}

body {
  background: #0f172a;
  background-image: 
    radial-gradient(rgba(244, 114, 182, 0.15) 1px, transparent 1px),
    radial-gradient(rgba(236, 72, 153, 0.1) 1px, #0f172a 1px);
  background-size: 30px 30px;
  background-position: 0 0, 15px 15px;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  color: #f8fafc;
  overflow-x: hidden;
}

.special-gift-app {
  width: 100%;
  max-width: 1160px;
  background: #1e293b;
  border: 1px solid #334155;
  border-radius: 24px;
  padding: 22px;
  box-shadow: 0 25px 60px -10px rgba(0, 0, 0, 0.7), 0 0 40px rgba(244, 114, 182, 0.15);
  display: flex;
  flex-direction: column;
  gap: 18px;
  position: relative;
}

/* Top Toolbar */
.gift-top-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  background: #0f172a;
  padding: 10px 16px;
  border-radius: 16px;
  border: 1px solid #334155;
}
.brand-pill {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.sparkle-icon {
  font-size: 1.2rem;
  animation: pulseSparkle 1.5s infinite alternate;
}
@keyframes pulseSparkle {
  0% { transform: scale(1) rotate(0deg); }
  100% { transform: scale(1.25) rotate(15deg); }
}
.gift-heading {
  font-size: 1.05rem;
  font-weight: 800;
  background: linear-gradient(90deg, #f43f5e 0%, #ec4899 50%, #fb7185 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}
.dedication-tag {
  background: rgba(244, 63, 94, 0.15);
  border: 1px solid rgba(244, 63, 94, 0.3);
  color: #fb7185;
  font-size: 0.8rem;
  font-weight: 700;
  padding: 3px 10px;
  border-radius: 20px;
}
.bar-btn-group {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.bar-btn {
  background: #1e293b;
  border: 1px solid #334155;
  color: #cbd5e1;
  padding: 7px 14px;
  border-radius: 10px;
  font-size: 0.82rem;
  font-weight: 700;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.bar-btn:hover {
  background: #334155;
  color: #ffffff;
  transform: translateY(-2px);
}
.btn-export-gif {
  background: linear-gradient(135deg, #8b5cf6 0%, #6366f1 100%);
  color: #ffffff;
  border-color: #7c3aed;
  box-shadow: 0 4px 12px rgba(99, 102, 241, 0.35);
}
.btn-export-gif:hover {
  background: linear-gradient(135deg, #7c3aed 0%, #4f46e5 100%);
}
.btn-export-gif.recording {
  background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
  border-color: #b91c1c;
  animation: pulseRec 1s infinite alternate;
}
@keyframes pulseRec {
  0% { transform: scale(1); }
  100% { transform: scale(1.05); }
}
.btn-music-play.active-music {
  background: linear-gradient(135deg, #ec4899 0%, #f43f5e 100%);
  color: #ffffff;
  border-color: #f43f5e;
  box-shadow: 0 4px 12px rgba(244, 63, 94, 0.4);
}
.btn-gift-unwrap {
  background: rgba(245, 158, 11, 0.15);
  border-color: rgba(245, 158, 11, 0.3);
  color: #fbbf24;
}
.btn-gift-unwrap:hover {
  background: rgba(245, 158, 11, 0.25);
  color: #fef08a;
}

/* Stage Layout (Grid: Code Terminal + Canvas Tree) */
.heart-stage-layout {
  display: grid;
  grid-template-columns: 1fr 1.25fr;
  gap: 20px;
  align-items: center;
}

@media (max-width: 900px) {
  .heart-stage-layout {
    grid-template-columns: 1fr;
  }
}

/* Code Terminal Card */
.code-terminal-card {
  background: #0b1329;
  border: 1px solid #1e293b;
  border-radius: 18px;
  padding: 20px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-height: 420px;
}
.terminal-top-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 1px solid #1e293b;
  padding-bottom: 12px;
}
.terminal-dots {
  display: flex;
  gap: 6px;
}
.t-dot {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  display: inline-block;
}
.dot-red { background: #ef4444; }
.dot-yellow { background: #f59e0b; }
.dot-green { background: #10b981; }
.terminal-file-name {
  font-size: 0.8rem;
  font-family: monospace;
  color: #94a3b8;
}

/* Typed Code Lines */
.code-editor-lines {
  font-family: 'Consolas', 'Fira Code', Courier, monospace;
  font-size: 0.92rem;
  line-height: 1.7;
  color: #e2e8f0;
  min-height: 240px;
}
.code-line-item {
  margin-bottom: 4px;
  animation: codeLineFade 0.25s ease-out;
}
@keyframes codeLineFade {
  from { opacity: 0; transform: translateX(-4px); }
  to { opacity: 1; transform: translateX(0); }
}
.c-tag { color: #f43f5e; font-weight: 700; }
.c-attr { color: #38bdf8; }
.c-str { color: #fbbf24; }
.c-text { color: #f1f5f9; }
.c-highlight {
  color: #f472b6;
  font-weight: 700;
  text-shadow: 0 0 8px rgba(244, 114, 182, 0.5);
}
.blinking-code-cursor {
  color: #f43f5e;
  font-weight: 900;
  animation: blinkCursor 0.75s infinite;
}
@keyframes blinkCursor {
  0%, 100% { opacity: 1; }
  50% { opacity: 0; }
}

/* Milestone Clock Box */
.milestone-clock-card {
  background: rgba(15, 23, 42, 0.7);
  border: 1px solid #334155;
  border-radius: 14px;
  padding: 12px 16px;
  display: flex;
  align-items: center;
  gap: 14px;
}
.clock-badge-icon {
  width: 40px;
  height: 40px;
  background: rgba(244, 63, 94, 0.15);
  border: 1px solid rgba(244, 63, 94, 0.3);
  border-radius: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fb7185;
  font-size: 1.15rem;
}
.clock-text-wrap {
  display: flex;
  flex-direction: column;
}
.clock-title {
  font-size: 0.72rem;
  font-weight: 700;
  color: #94a3b8;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.clock-display-live {
  font-family: monospace;
  font-size: 0.92rem;
  font-weight: 800;
  color: #f472b6;
}

/* Canvas Tree Container */
.canvas-wrapper-box {
  background: #0b1329;
  border: 1px solid #1e293b;
  border-radius: 20px;
  padding: 10px;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
  overflow: hidden;
}
#canvas {
  width: 100%;
  height: auto;
  max-width: 680px;
  background: radial-gradient(circle at 50% 30%, #172554 0%, #0b1329 100%);
  border-radius: 14px;
  cursor: crosshair;
}
.canvas-interactive-toast {
  position: absolute;
  bottom: 16px;
  background: rgba(15, 23, 42, 0.85);
  backdrop-filter: blur(8px);
  border: 1px solid rgba(244, 114, 182, 0.3);
  color: #f472b6;
  font-size: 0.75rem;
  font-weight: 700;
  padding: 4px 14px;
  border-radius: 20px;
  pointer-events: none;
  box-shadow: 0 2px 10px rgba(0,0,0,0.5);
}

/* 3D Gift Modal */
.gift-modal-overlay {
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  background: rgba(15, 23, 42, 0.75);
  backdrop-filter: blur(8px);
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.gift-modal-overlay.hidden { display: none; }
.gift-surprise-card {
  background: #1e293b;
  border: 1px solid #475569;
  border-radius: 24px;
  padding: 32px 24px;
  text-align: center;
  max-width: 480px;
  width: 100%;
  box-shadow: 0 25px 60px rgba(0,0,0,0.8), 0 0 30px rgba(244, 63, 94, 0.3);
  animation: giftPop 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes giftPop {
  0% { transform: scale(0.8); opacity: 0; }
  100% { transform: scale(1); opacity: 1; }
}
.gift-box-3d {
  font-size: 4rem;
  margin-bottom: 12px;
  animation: floatGift 2s infinite ease-in-out alternate;
}
@keyframes floatGift {
  0% { transform: translateY(0px) rotate(-5deg); }
  100% { transform: translateY(-10px) rotate(5deg); }
}
.surprise-title {
  font-size: 1.6rem;
  font-weight: 800;
  color: #ffffff;
  margin-bottom: 10px;
}
.surprise-quote {
  font-size: 0.95rem;
  color: #cbd5e1;
  line-height: 1.6;
  font-style: italic;
  margin-bottom: 20px;
}
.btn-close-gift {
  background: linear-gradient(135deg, #f43f5e 0%, #ec4899 100%);
  color: #ffffff;
  border: none;
  padding: 12px 28px;
  border-radius: 50px;
  font-size: 1rem;
  font-weight: 800;
  cursor: pointer;
  box-shadow: 0 4px 15px rgba(244, 63, 94, 0.4);
}

/* Wish Customization Modal */
.wish-modal-overlay {
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  background: rgba(15, 23, 42, 0.75);
  backdrop-filter: blur(8px);
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.wish-modal-overlay.hidden { display: none; }
.wish-card-dialog {
  background: #1e293b;
  border: 1px solid #334155;
  border-radius: 20px;
  padding: 24px;
  max-width: 500px;
  width: 100%;
  box-shadow: 0 20px 50px rgba(0,0,0,0.6);
}
.wish-dialog-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 16px;
}
.wish-dialog-header h3 {
  font-size: 1.15rem;
  font-weight: 800;
  color: #f8fafc;
  margin: 0;
}
.btn-close-x {
  background: transparent;
  border: none;
  color: #94a3b8;
  font-size: 1.1rem;
  cursor: pointer;
}
.wish-dialog-body {
  display: flex;
  flex-direction: column;
  gap: 14px;
  margin-bottom: 20px;
}
.wish-field label {
  display: block;
  font-size: 0.85rem;
  font-weight: 700;
  color: #cbd5e1;
  margin-bottom: 6px;
}
.wish-field input,
.wish-field textarea {
  width: 100%;
  background: #0f172a;
  border: 1px solid #334155;
  border-radius: 12px;
  padding: 10px 14px;
  color: #f8fafc;
  font-family: inherit;
  font-size: 0.92rem;
  outline: none;
}
.wish-field input:focus,
.wish-field textarea:focus {
  border-color: #f43f5e;
  box-shadow: 0 0 0 3px rgba(244, 63, 94, 0.2);
}
.wish-dialog-footer {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}
.btn-wish-cancel {
  background: #334155;
  border: none;
  color: #cbd5e1;
  padding: 10px 18px;
  border-radius: 10px;
  font-weight: 700;
  cursor: pointer;
}
.btn-wish-submit {
  background: linear-gradient(135deg, #f43f5e 0%, #ec4899 100%);
  border: none;
  color: #ffffff;
  padding: 10px 20px;
  border-radius: 10px;
  font-weight: 700;
  cursor: pointer;
  box-shadow: 0 4px 12px rgba(244, 63, 94, 0.4);
}

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)
/**
 * Special Birthday Code Surprise Engine
 * Procedural Fractal Tree, Blooming Heart Canopy, Terminal Typewriter, Music Box & GIF Export
 */

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

// State Variables
let treeBranches = [];
let heartBlossoms = [];
let fallingLeaves = [];
let cursorHearts = [];
let celebrationConfetti = [];
let isTreeComplete = false;
let animFrame = null;
let celebratedSeconds = 365 * 86400 + 14 * 3600 + 28 * 60;
let isRecording = false;

// Typewriter Config
let recipient = "My Favorite Person";
let codeLines = [
  '<span class="c-tag">&lt;div</span> <span class="c-attr">id</span>=<span class="c-str">"code"</span><span class="c-tag">&gt;</span>',
  '  <span class="c-tag">&lt;span</span> <span class="c-attr">class</span>=<span class="c-str">"say"</span><span class="c-tag">&gt;</span>Hey you πŸ’•<span class="c-tag">&lt;/span&gt;&lt;br /&gt;</span>',
  '  <span class="c-tag">&lt;span</span> <span class="c-attr">class</span>=<span class="c-str">"say"</span><span class="c-tag">&gt;</span>Happy Birthday πŸŽ‚βœ¨<span class="c-tag">&lt;/span&gt;&lt;br /&gt;</span>',
  '  <span class="c-tag">&lt;span</span> <span class="c-attr">class</span>=<span class="c-str">"say"</span><span class="c-tag">&gt;</span>May God bless you 🌸<span class="c-tag">&lt;/span&gt;&lt;br /&gt;</span>',
  '  <span class="c-tag">&lt;span</span> <span class="c-attr">class</span>=<span class="c-str">"say"</span><span class="c-tag">&gt;</span>And give u many happiness πŸ’•<span class="c-tag">&lt;/span&gt;&lt;br /&gt;</span>',
  '  <span class="c-tag">&lt;span</span> <span class="c-attr">class</span>=<span class="c-str">"say"</span><span class="c-tag">&gt;</span>Just saying... you\'re pretty awesome πŸ’–<span class="c-tag">&lt;/span&gt;&lt;br /&gt;</span>',
  '  <span class="c-tag">&lt;span</span> <span class="c-attr">class</span>=<span class="c-str">"say"</span><span class="c-tag">&gt;</span>Sending good vibes and maybe a wink πŸ˜‰<span class="c-tag">&lt;/span&gt;&lt;br /&gt;</span>',
  '  <span class="c-tag">&lt;span</span> <span class="c-attr">class</span>=<span class="c-str">"say"</span><span class="c-tag">&gt;</span>Hope u have a great day today πŸ’•<span class="c-tag">&lt;/span&gt;&lt;br /&gt;</span>',
  '  <span class="c-highlight">// I turned HTML, CSS, and JS into a special birthday gift. ✨</span>',
  '<span class="c-tag">&lt;/div&gt;</span>'
];

let activeLineIdx = 0;
let activeCharIdx = 0;
let typewriterTimer = null;

// Palette for Heart Canopy
const PETAL_COLORS = [
  '#ff4d6d', '#f72585', '#ff758f', '#ff85a1', 
  '#ff99c8', '#ffb703', '#fb8500', '#f15bb5', 
  '#fee440', '#e63946', '#ffb3c1', '#f43f5e'
];

/**
 * 1. Parametric Heart Formula
 */
function getHeartCoord(t, scale) {
  const x = 16 * Math.pow(Math.sin(t), 3);
  const y = -(13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t));
  return { x: x * scale, y: y * scale };
}

function drawHeartShape(c, x, y, size, color, angle = 0, alpha = 1) {
  c.save();
  c.translate(x, y);
  c.rotate(angle);
  c.scale(size / 15, size / 15);
  c.globalAlpha = Math.max(0, Math.min(1, alpha));
  c.fillStyle = color;

  c.beginPath();
  c.moveTo(0, 0);
  c.bezierCurveTo(-10, -10, -20, 5, 0, 18);
  c.bezierCurveTo(20, 5, 10, -10, 0, 0);
  c.closePath();
  c.fill();
  c.restore();
}

/**
 * 2. Initialize Tree & Heart Blossom Coordinates
 */
function buildTreeSystem() {
  treeBranches = [];
  heartBlossoms = [];
  fallingLeaves = [];
  isTreeComplete = false;

  const rootX = canvas.width * 0.5;
  const rootY = canvas.height - 20;
  const heartCenterY = canvas.height * 0.38;
  const heartCenterX = canvas.width * 0.5;

  // Build Procedural Branches
  createBranch(rootX, rootY, -Math.PI / 2, 118, 14, 0);

  // Generate 480 Heart-shaped Blossoms
  for (let i = 0; i < 480; i++) {
    const t = Math.random() * Math.PI * 2;
    const r = Math.sqrt(Math.random());
    const pt = getHeartCoord(t, 12.8);

    const targetX = heartCenterX + pt.x * r + (Math.random() * 8 - 4);
    const targetY = heartCenterY + pt.y * r + (Math.random() * 8 - 4);

    heartBlossoms.push({
      x: targetX,
      y: targetY,
      size: Math.random() * 7 + 7,
      color: PETAL_COLORS[Math.floor(Math.random() * PETAL_COLORS.length)],
      angle: (Math.random() - 0.5) * 0.8,
      delay: Math.random() * 80 + 25,
      progress: 0,
      scale: 0
    });
  }
}

function createBranch(x, y, angle, length, width, depth) {
  const endX = x + Math.cos(angle) * length;
  const endY = y + Math.sin(angle) * length;

  treeBranches.push({
    startX: x,
    startY: y,
    endX: endX,
    endY: endY,
    width: width,
    depth: depth,
    current: 0,
    target: 1
  });

  if (depth >= 4 || length < 25) return;

  const subCount = depth === 0 ? 3 : (Math.random() > 0.35 ? 2 : 3);
  for (let i = 0; i < subCount; i++) {
    const spread = (i - (subCount - 1) / 2) * 0.42 + (Math.random() * 0.16 - 0.08);
    const newLen = length * (0.66 + Math.random() * 0.14);
    const newWidth = Math.max(width * 0.65, 2.5);
    createBranch(endX, endY, angle + spread, newLen, newWidth, depth + 1);
  }
}

/**
 * 3. Render Animation Loop
 */
function renderFrame() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // Base Mound / Tree Soil
  ctx.fillStyle = 'rgba(244, 114, 182, 0.2)';
  ctx.beginPath();
  ctx.ellipse(canvas.width * 0.5, canvas.height - 12, 120, 16, 0, 0, Math.PI * 2);
  ctx.fill();

  // Draw Branches
  let allDone = true;
  treeBranches.forEach(b => {
    if (b.current < b.target) {
      b.current += 0.045;
      if (b.current > b.target) b.current = b.target;
      allDone = false;
    }

    const curX = b.startX + (b.endX - b.startX) * b.current;
    const curY = b.startY + (b.endY - b.startY) * b.current;

    ctx.save();
    ctx.strokeStyle = '#c08497';
    ctx.lineWidth = Math.max(b.width * (1 - b.depth * 0.15), 2.2);
    ctx.lineCap = 'round';
    ctx.beginPath();
    ctx.moveTo(b.startX, b.startY);
    ctx.lineTo(curX, curY);
    ctx.stroke();
    ctx.restore();
  });

  if (allDone) {
    isTreeComplete = true;
  }

  // Draw Heart Blossoms
  if (isTreeComplete) {
    heartBlossoms.forEach(b => {
      if (b.delay > 0) {
        b.delay--;
      } else if (b.progress < 1) {
        b.progress += 0.05;
        if (b.progress > 1) b.progress = 1;
        b.scale = (1 - Math.cos(b.progress * Math.PI)) * 0.5;
      }

      if (b.scale > 0) {
        drawHeartShape(ctx, b.x, b.y, b.size * b.scale, b.color, b.angle);
      }
    });

    // Spawn falling leaves
    if (Math.random() < 0.28) {
      const randB = heartBlossoms[Math.floor(Math.random() * heartBlossoms.length)];
      if (randB && randB.scale >= 0.8) {
        fallingLeaves.push({
          x: randB.x,
          y: randB.y,
          size: Math.random() * 5 + 6,
          color: randB.color,
          vx: (Math.random() - 0.5) * 1.6,
          vy: Math.random() * 1.2 + 0.8,
          angle: Math.random() * Math.PI * 2,
          vAngle: (Math.random() - 0.5) * 0.05,
          alpha: 1,
          sway: Math.random() * Math.PI * 2
        });
      }
    }
  }

  // Update Falling Leaves
  for (let i = fallingLeaves.length - 1; i >= 0; i--) {
    const p = fallingLeaves[i];
    p.sway += 0.035;
    p.x += p.vx + Math.sin(p.sway) * 0.85;
    p.y += p.vy;
    p.angle += p.vAngle;

    if (p.y > canvas.height - 20) p.alpha -= 0.02;

    drawHeartShape(ctx, p.x, p.y, p.size, p.color, p.angle, p.alpha);
    if (p.alpha <= 0 || p.y > canvas.height + 20) {
      fallingLeaves.splice(i, 1);
    }
  }

  // Cursor Trail Hearts
  for (let i = cursorHearts.length - 1; i >= 0; i--) {
    const h = cursorHearts[i];
    h.x += h.vx;
    h.y += h.vy;
    h.vy -= 0.06;
    h.alpha -= 0.018;
    h.size *= 0.99;

    drawHeartShape(ctx, h.x, h.y, h.size, h.color, h.angle, h.alpha);
    if (h.alpha <= 0) {
      cursorHearts.splice(i, 1);
    }
  }

  // Celebration Confetti
  for (let i = celebrationConfetti.length - 1; i >= 0; i--) {
    const c = celebrationConfetti[i];
    c.x += c.vx;
    c.y += c.vy;
    c.vy += 0.22;
    c.vx *= 0.98;
    c.rotation += c.vRot;
    c.life--;

    ctx.save();
    ctx.translate(c.x, c.y);
    ctx.rotate(c.rotation);
    ctx.fillStyle = c.color;
    ctx.fillRect(-c.w / 2, -c.h / 2, c.w, c.h);
    ctx.restore();

    if (c.life <= 0 || c.y > canvas.height + 20) {
      celebrationConfetti.splice(i, 1);
    }
  }

  animFrame = requestAnimationFrame(renderFrame);
}

/**
 * 4. Terminal Typewriter
 */
function startTerminalTypewriter() {
  clearTimeout(typewriterTimer);
  const block = document.getElementById('typedCodeBlock');
  block.innerHTML = '';
  activeLineIdx = 0;

  typewriterLineLoop();
}

function typewriterLineLoop() {
  if (activeLineIdx >= codeLines.length) return;

  const block = document.getElementById('typedCodeBlock');
  const div = document.createElement('div');
  div.className = 'code-line-item';
  div.innerHTML = codeLines[activeLineIdx];
  block.appendChild(div);

  activeLineIdx++;
  typewriterTimer = setTimeout(typewriterLineLoop, 280);
}

/**
 * 5. Milestone Clock
 */
function updateClock() {
  celebratedSeconds++;
  const days = Math.floor(celebratedSeconds / 86400);
  const hrs = Math.floor((celebratedSeconds % 86400) / 3600);
  const mins = Math.floor((celebratedSeconds % 3600) / 60);
  const secs = celebratedSeconds % 60;

  const clockEl = document.getElementById('clock');
  if (clockEl) {
    clockEl.textContent = `${days} days ${hrs} hours ${mins} minutes ${secs} seconds`;
  }
}
setInterval(updateClock, 1000);

/**
 * 6. Web Audio API Acoustic Birthday Chime
 */
let audioContext = null;
let isAudioPlaying = false;
let noteTimer = null;
let noteIndex = 0;

function getAudio() {
  if (!audioContext) {
    const Ctor = window.AudioContext || window.webkitAudioContext;
    if (Ctor) audioContext = new Ctor();
  }
  if (audioContext && audioContext.state === 'suspended') {
    audioContext.resume();
  }
  return audioContext;
}

const SONG_NOTES = [
  { f: 261.63, d: 350 }, { f: 261.63, d: 250 }, { f: 293.66, d: 500 }, { f: 261.63, d: 500 },
  { f: 349.23, d: 500 }, { f: 329.63, d: 900 }, { f: 0, d: 250 },
  { f: 261.63, d: 350 }, { f: 261.63, d: 250 }, { f: 293.66, d: 500 }, { f: 261.63, d: 500 },
  { f: 392.00, d: 500 }, { f: 349.23, d: 900 }, { f: 0, d: 250 },
  { f: 261.63, d: 350 }, { f: 261.63, d: 250 }, { f: 523.25, d: 500 }, { f: 440.00, d: 500 },
  { f: 349.23, d: 500 }, { f: 329.63, d: 500 }, { f: 293.66, d: 700 }, { f: 0, d: 250 },
  { f: 466.16, d: 350 }, { f: 466.16, d: 250 }, { f: 440.00, d: 500 }, { f: 349.23, d: 500 },
  { f: 392.00, d: 500 }, { f: 349.23, d: 1200}
];

function playNextNote() {
  if (!isAudioPlaying) return;
  const ctx = getAudio();
  if (!ctx) return;

  const n = SONG_NOTES[noteIndex];
  if (n.f > 0) {
    const osc = ctx.createOscillator();
    const gain = ctx.createGain();
    osc.type = 'triangle';
    osc.frequency.setValueAtTime(n.f, ctx.currentTime);

    gain.gain.setValueAtTime(0.2, ctx.currentTime);
    gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + (n.d / 1000));

    osc.connect(gain);
    gain.connect(ctx.destination);
    osc.start();
    osc.stop(ctx.currentTime + (n.d / 1000));
  }

  noteIndex = (noteIndex + 1) % SONG_NOTES.length;
  noteTimer = setTimeout(playNextNote, n.d + 60);
}

function toggleAudio() {
  const btn = document.getElementById('btnAudioMelody');
  const txt = document.getElementById('audioStatusText');

  if (!isAudioPlaying) {
    isAudioPlaying = true;
    btn.classList.add('active-music');
    txt.textContent = 'Pause Music';
    noteIndex = 0;
    playNextNote();
  } else {
    isAudioPlaying = false;
    clearTimeout(noteTimer);
    btn.classList.remove('active-music');
    txt.textContent = 'Play Music';
  }
}

/**
 * 7. Interactive Cursor Trail & Click Fireworks
 */
canvas.addEventListener('mousemove', (e) => {
  if (Math.random() > 0.4) return;
  const rect = canvas.getBoundingClientRect();
  const scaleX = canvas.width / rect.width;
  const scaleY = canvas.height / rect.height;
  const x = (e.clientX - rect.left) * scaleX;
  const y = (e.clientY - rect.top) * scaleY;

  cursorHearts.push({
    x: x,
    y: y,
    size: Math.random() * 6 + 6,
    color: PETAL_COLORS[Math.floor(Math.random() * PETAL_COLORS.length)],
    vx: (Math.random() - 0.5) * 2,
    vy: -Math.random() * 2,
    angle: (Math.random() - 0.5) * 0.5,
    alpha: 0.9
  });
});

canvas.addEventListener('click', (e) => {
  const rect = canvas.getBoundingClientRect();
  const scaleX = canvas.width / rect.width;
  const scaleY = canvas.height / rect.height;
  const x = (e.clientX - rect.left) * scaleX;
  const y = (e.clientY - rect.top) * scaleY;

  for (let i = 0; i < 16; i++) {
    cursorHearts.push({
      x: x,
      y: y,
      size: Math.random() * 10 + 10,
      color: PETAL_COLORS[Math.floor(Math.random() * PETAL_COLORS.length)],
      vx: (Math.random() - 0.5) * 8,
      vy: (Math.random() - 0.5) * 8 - 3,
      angle: (Math.random() - 0.5) * 0.8,
      alpha: 1
    });
  }
});

/**
 * 8. Confetti Cannon Burst
 */
function fireConfetti() {
  const cols = ['#f43f5e', '#ec4899', '#facc15', '#38bdf8', '#a855f7', '#10b981'];
  for (let i = 0; i < 100; i++) {
    celebrationConfetti.push({
      x: canvas.width * 0.5 + (Math.random() * 100 - 50),
      y: canvas.height * 0.35,
      vx: (Math.random() - 0.5) * 16,
      vy: (Math.random() - 0.7) * 15,
      w: Math.random() * 8 + 6,
      h: Math.random() * 6 + 4,
      color: cols[Math.floor(Math.random() * cols.length)],
      rotation: Math.random() * Math.PI * 2,
      vRot: (Math.random() - 0.5) * 0.25,
      life: Math.random() * 80 + 90
    });
  }
}

/**
 * 9. Record and Download Animated GIF
 */
function recordAndDownloadCardGif() {
  if (isRecording) return;
  const btn = document.getElementById('btnRecordCardGif');
  const txt = document.getElementById('gifExportText');

  isRecording = true;
  btn.classList.add('recording');
  btn.disabled = true;

  const frames = [];
  const total = 28;
  let current = 0;

  const offscreen = document.createElement('canvas');
  offscreen.width = 540;
  offscreen.height = 460;
  const octx = offscreen.getContext('2d');

  const interval = setInterval(() => {
    current++;
    txt.textContent = `Recording ${current}/${total}...`;

    octx.fillStyle = '#0b1329';
    octx.fillRect(0, 0, offscreen.width, offscreen.height);
    octx.drawImage(canvas, 0, 0, offscreen.width, offscreen.height);

    octx.fillStyle = '#f472b6';
    octx.font = 'bold 14px Consolas, monospace';
    octx.textAlign = 'center';
    octx.fillText(`Happy Birthday, ${recipient}! ❀️✨`, offscreen.width / 2, offscreen.height - 14);

    frames.push(offscreen.toDataURL('image/jpeg', 0.85));

    if (current >= total) {
      clearInterval(interval);
      txt.textContent = 'Rendering GIF...';

      if (typeof gifshot !== 'undefined') {
        gifshot.createGIF({
          images: frames,
          gifWidth: 540,
          gifHeight: 460,
          interval: 0.1,
          numFrames: total,
          sampleInterval: 10
        }, function(obj) {
          isRecording = false;
          btn.classList.remove('recording');
          btn.disabled = false;

          if (!obj.error) {
            const link = document.createElement('a');
            link.href = obj.image;
            const safe = recipient.toLowerCase().replace(/[^a-z0-9]/g, '-');
            link.download = `special-birthday-gift-${safe}.gif`;
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);

            txt.textContent = 'GIF Saved! πŸŽ‰';
            setTimeout(() => { txt.textContent = 'Download GIF'; }, 3500);
          } else {
            fallbackDownload();
          }
        });
      } else {
        fallbackDownload();
      }
    }
  }, 100);

  function fallbackDownload() {
    isRecording = false;
    btn.classList.remove('recording');
    btn.disabled = false;

    const link = document.createElement('a');
    link.href = canvas.toDataURL('image/png');
    const safe = recipient.toLowerCase().replace(/[^a-z0-9]/g, '-');
    link.download = `special-birthday-gift-${safe}.png`;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);

    txt.textContent = 'Saved Snapshot!';
    setTimeout(() => { txt.textContent = 'Download GIF'; }, 3500);
  }
}

/**
 * 10. Modals and Triggers
 */
document.getElementById('btnAudioMelody').addEventListener('click', toggleAudio);
document.getElementById('btnRecordCardGif').addEventListener('click', recordAndDownloadCardGif);
document.getElementById('btnReplaySurprise').addEventListener('click', () => {
  buildTreeSystem();
  startTerminalTypewriter();
  fireConfetti();
});

// Gift Modal
const giftModal = document.getElementById('giftModal');
document.getElementById('btnUnwrapGift').addEventListener('click', () => {
  giftModal.classList.remove('hidden');
  fireConfetti();
});
document.getElementById('btnCloseGiftModal').addEventListener('click', () => {
  giftModal.classList.add('hidden');
});

// Wish Edit Modal
const wishModal = document.getElementById('wishModal');
document.getElementById('btnOpenWishModal').addEventListener('click', () => {
  wishModal.classList.remove('hidden');
});
document.getElementById('btnCloseWishModal').addEventListener('click', () => {
  wishModal.classList.add('hidden');
});
document.getElementById('btnCancelWishModal').addEventListener('click', () => {
  wishModal.classList.add('hidden');
});

document.getElementById('btnSaveWishModal').addEventListener('click', () => {
  const name = document.getElementById('recipientInput').value.trim();
  const rawPoem = document.getElementById('poemInput').value.trim();

  if (name) {
    recipient = name;
    document.getElementById('dedicationTag').textContent = `To: ${recipient} πŸ’•`;
  }

  if (rawPoem) {
    const rawLines = rawPoem.split('\n').map(l => l.trim()).filter(l => l.length > 0);
    codeLines = [
      '<span class="c-tag">&lt;div</span> <span class="c-attr">id</span>=<span class="c-str">"code"</span><span class="c-tag">&gt;</span>',
      ...rawLines.map(l => `  <span class="c-tag">&lt;span</span> <span class="c-attr">class</span>=<span class="c-str">"say"</span><span class="c-tag">&gt;</span>${l}<span class="c-tag">&lt;/span&gt;&lt;br /&gt;</span>`),
      '<span class="c-tag">&lt;/div&gt;</span>'
    ];
  }

  wishModal.classList.add('hidden');
  buildTreeSystem();
  startTerminalTypewriter();
  fireConfetti();
});

// Boot Initial
buildTreeSystem();
startTerminalTypewriter();
renderFrame();

3. Core JavaScript Concepts You Learned

Fractal Tree Growth Algorithm

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

Heart Geometry Math

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

Terminal Typewriter Effect

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

Milestone Real-Time Clock

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

Web Audio Melodic Chimes

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

Animated GIF Generation

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 special-birthday-gift-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