一荤一素
毛不易 · 原唱
🎵
毛不易 - 一荤一素
📍
8月25日 · 远程生日祝福
老妈,生日快乐!
今年生日在萍乡没能赶回兖州,特地做了个小程序给你
📍
江西萍乡
儿子在这
✈️
1000+ km
🏡
山东兖州
老妈家中
✦
点这里开始
➔
👈 支持左右滑动翻页 👉
📊
步骤 2 / 5 · 状态打卡
今日状态打卡
连线一下兖州,看看老妈今天状态怎么样:
点击下方按钮开始...
▶
开始打卡测定
👈 滑动或点击按钮继续 👉
🎁
步骤 3 / 5 · 祝福打包
生日心愿打包
点击卡片或直接点下方按钮,打包送给老妈:
💪
身体倍儿棒
点我点亮
🍵
少操点心
点我点亮
😄
天天开心
点我点亮
✨
一键点亮并打包
➔
👈 点击或滑动进入下一步 👉
💬
步骤 4 / 5 · 儿子请示
老妈表个态
这次生日没在身边,老妈能原谅我吗?
不原谅,扣零花钱 😤
准了,原谅你这次 😄
(提示:左边按钮和我一样调皮哦~)
{ isMusicPlaying = true; musicToast.classList.remove('paused'); }).catch(() => { isMusicPlaying = false; musicToast.classList.add('paused'); }); } function toggleBGM() { if (isMusicPlaying) { audio.pause(); isMusicPlaying = false; musicToast.classList.add('paused'); } else { playBGM(); } } function handleFirstTouch() { if (!hasInteracted) { hasInteracted = true; playBGM(); } } document.body.addEventListener('touchstart', handleFirstTouch, { once: true }); document.body.addEventListener('click', handleFirstTouch, { once: true }); document.addEventListener("WeixinJSBridgeReady", function () { playBGM(); }, false); // 歌词实时同步监听 audio.ontimeupdate = () => { const cur = audio.currentTime; let currentLyric = "🎵 毛不易 - 一荤一素"; for (let i = 0; i < lrcList.length; i++) { if (cur >= lrcList[i].t) { currentLyric = lrcList[i].text; } else { break; } } if (lyricText.innerText !== currentLyric) { lyricText.style.opacity = '0'; setTimeout(() => { lyricText.innerText = currentLyric; lyricText.style.opacity = '1'; }, 140); } }; /* 2. 🎆 宏大热烈烟花粒子引擎 */ const canvas = document.getElementById('particle-canvas'); const ctx = canvas.getContext('2d'); let width = canvas.width = window.innerWidth; let height = canvas.height = window.innerHeight; window.addEventListener('resize', () => { width = canvas.width = window.innerWidth; height = canvas.height = window.innerHeight; }); class Dust { constructor() { this.reset(); } reset() { this.x = Math.random() * width; this.y = Math.random() * height; this.size = Math.random() * 2 + 0.5; this.speedX = (Math.random() - 0.5) * 0.3; this.speedY = -Math.random() * 0.35 - 0.1; this.opacity = Math.random() * 0.6 + 0.2; } update() { this.x += this.speedX; this.y += this.speedY; if (this.y < 0 || this.x < 0 || this.x > width) this.reset(); } draw() { ctx.fillStyle = `rgba(212, 175, 55, ${this.opacity})`; ctx.beginPath(); ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); ctx.fill(); } } // 烟花爆炸粒子(带尾迹、光晕和物理阻尼) class FireworkParticle { constructor(x, y, color) { this.x = x; this.y = y; this.color = color; this.size = Math.random() * 3.5 + 1.5; const angle = Math.random() * Math.PI * 2; const speed = Math.random() * 9 + 2; this.vx = Math.cos(angle) * speed; this.vy = Math.sin(angle) * speed; this.friction = 0.95; this.gravity = 0.12; this.alpha = 1; this.decay = Math.random() * 0.015 + 0.012; } update() { this.vx *= this.friction; this.vy *= this.friction; this.vy += this.gravity; this.x += this.vx; this.y += this.vy; this.alpha -= this.decay; } draw() { ctx.save(); ctx.globalAlpha = Math.max(0, this.alpha); ctx.fillStyle = this.color; ctx.shadowBlur = 8; ctx.shadowColor = this.color; ctx.beginPath(); ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); ctx.fill(); ctx.restore(); } } const dustArray = Array.from({ length: 24 }, () => new Dust()); let fireworkParticles = []; let grandFireworkTimer = null; function animateParticles() { ctx.clearRect(0, 0, width, height); dustArray.forEach(d => { d.update(); d.draw(); }); for (let i = fireworkParticles.length - 1; i >= 0; i--) { fireworkParticles[i].update(); fireworkParticles[i].draw(); if (fireworkParticles[i].alpha <= 0) fireworkParticles.splice(i, 1); } requestAnimationFrame(animateParticles); } animateParticles(); const fireworkColors = [ '#ff3b30', '#ff9500', '#ffd60a', '#34c759', '#00c7be', '#30b0c7', '#32ade6', '#af52de', '#ff2d55', '#d4af37', '#ff758c', '#ffffff' ]; function launchGrandFirework(x, y, particleCount = 75) { const color = fireworkColors[Math.floor(Math.random() * fireworkColors.length)]; const color2 = fireworkColors[Math.floor(Math.random() * fireworkColors.length)]; for (let i = 0; i < particleCount; i++) { fireworkParticles.push(new FireworkParticle(x, y, i % 2 === 0 ? color : color2)); } } // 连续热烈大烟花发射器(最后一页专用) function startGrandFireworks() { if (grandFireworkTimer) clearInterval(grandFireworkTimer); // 立即连发3响 launchGrandFirework(width * 0.25, height * 0.3, 80); launchGrandFirework(width * 0.75, height * 0.25, 80); launchGrandFirework(width * 0.5, height * 0.4, 100); // 每 600ms 持续在屏幕随机高处绚烂绽放 grandFireworkTimer = setInterval(() => { const rx = Math.random() * (width * 0.8) + width * 0.1; const ry = Math.random() * (height * 0.45) + height * 0.15; launchGrandFirework(rx, ry, Math.floor(Math.random() * 30 + 60)); }, 600); } function stopGrandFireworks() { if (grandFireworkTimer) { clearInterval(grandFireworkTimer); grandFireworkTimer = null; } } /* 3. 步骤控制器与滑动手势 */ let currentStep = 0; const totalSteps = 5; function updateStepUI() { for (let i = 0; i < totalSteps; i++) { const card = document.getElementById(`card-${i}`); const dot = document.getElementById(`dot-${i}`); if (i === currentStep) { card.className = 'step-card active'; dot.className = 'nav-dot active'; } else if (i < currentStep) { card.className = 'step-card prev'; dot.className = 'nav-dot completed'; } else { card.className = 'step-card'; dot.className = 'nav-dot'; } } // 最后一页(第5步)自动开启热烈绚烂烟花! if (currentStep === 4) { startGrandFireworks(); } else { stopGrandFireworks(); } } function nextStep(burst = false) { if (currentStep < totalSteps - 1) { currentStep++; updateStepUI(); if (burst && currentStep !== 4) { launchGrandFirework(width / 2, height * 0.4, 50); } } } function prevStep() { if (currentStep > 0) { currentStep--; updateStepUI(); } } let touchStartX = 0; let touchStartY = 0; const touchArea = document.getElementById('touch-area'); touchArea.addEventListener('touchstart', (e) => { touchStartX = e.changedTouches[0].screenX; touchStartY = e.changedTouches[0].screenY; }, { passive: true }); touchArea.addEventListener('touchend', (e) => { const diffX = e.changedTouches[0].screenX - touchStartX; const diffY = e.changedTouches[0].screenY - touchStartY; if (Math.abs(diffX) > Math.abs(diffY) && Math.abs(diffX) > 50) { if (diffX < 0) { nextStep(); } else { prevStep(); } } }, { passive: true }); function resetWizard() { stopGrandFireworks(); currentStep = 0; updateStepUI(); document.getElementById('scan-btn').style.display = 'flex'; document.getElementById('scan-btn').innerHTML = `
▶
开始打卡测定`; document.getElementById('scan-text').innerText = '点击下方按钮开始...'; gemsLitCount = 0; for (let i = 1; i <= 3; i++) { document.getElementById(`gem-${i}`).classList.remove('lit'); document.getElementById(`gs-${i}`).innerText = '点我点亮'; } const gBtn = document.getElementById('gem-btn'); gBtn.innerHTML = `
✨
一键点亮并打包
➔
`; gBtn.style.background = 'var(--gold-gradient)'; gBtn.style.color = '#4a3800'; } /* 步骤 2 扫描 */ let isScanning = false; function triggerScan() { if (isScanning) return; isScanning = true; const btn = document.getElementById('scan-btn'); const laser = document.getElementById('laser'); const readout = document.getElementById('scan-text'); btn.style.opacity = '0.5'; btn.style.pointerEvents = 'none'; laser.style.display = 'block'; let progress = 0; const timer = setInterval(() => { progress += 4; if (progress < 40) { readout.innerHTML = `正在连线兖州...
${progress}%
`; } else if (progress < 85) { readout.innerHTML = `正在打卡状态...
${progress}%
`; } else if (progress >= 100) { clearInterval(timer); laser.style.display = 'none'; readout.innerHTML = `
打卡完毕:精神饱满,心情 100分!
`; btn.innerHTML = `
✓
进入下一步
➔
`; btn.style.background = 'var(--gold-gradient)'; btn.style.color = '#4a3800'; btn.style.opacity = '1'; btn.style.pointerEvents = 'auto'; btn.onclick = () => { nextStep(true); }; isScanning = false; launchGrandFirework(width / 2, height * 0.4, 40); } }, 35); } /* 步骤 3 宝石互动 */ let gemsLitCount = 0; function lightGem(index) { const card = document.getElementById(`gem-${index}`); if (!card.classList.contains('lit')) { card.classList.add('lit'); document.getElementById(`gs-${index}`).innerText = '已点亮 ✔️'; gemsLitCount++; const rect = card.getBoundingClientRect(); launchGrandFirework(rect.left + rect.width / 2, rect.top, 25); if (gemsLitCount === 3) { const btn = document.getElementById('gem-btn'); btn.innerHTML = `
✓
心愿已全部集齐 · 继续
➔
`; btn.style.background = 'linear-gradient(135deg, #10b981 0%, #059669 100%)'; btn.style.color = '#fff'; } } } function finishGems() { if (gemsLitCount < 3) { for (let i = 1; i <= 3; i++) { const card = document.getElementById(`gem-${i}`); if (!card.classList.contains('lit')) { card.classList.add('lit'); document.getElementById(`gs-${i}`).innerText = '已点亮 ✔️'; } } gemsLitCount = 3; launchGrandFirework(width / 2, height * 0.45, 50); setTimeout(() => { nextStep(true); }, 300); } else { nextStep(true); } } /* 步骤 4 闪避按钮 */ function dodge(e) { if (e) e.preventDefault(); const btn = document.getElementById('run-btn'); const maxX = 120; const maxY = 35; const offsetX = (Math.random() - 0.5) * maxX * 2; const offsetY = (Math.random() - 0.5) * maxY * 2; btn.style.transform = `translate(${offsetX}px, ${offsetY}px)`; }