143 lines
5.4 KiB
JavaScript
143 lines
5.4 KiB
JavaScript
(function () {
|
|
var COLORS = ['#11b3be','#ff6b9d','#ffd93d','#6bcb77','#4d96ff','#ff6b35','#c77dff','#ff9f43','#54a0ff','#ff6348'];
|
|
|
|
var SVG_NS = 'http://www.w3.org/2000/svg';
|
|
function makeBalloon(color, size) {
|
|
var svg = document.createElementNS(SVG_NS, 'svg');
|
|
svg.setAttribute('viewBox', '0 0 50 82');
|
|
svg.setAttribute('width', size);
|
|
svg.setAttribute('height', Math.round(size * 1.64));
|
|
|
|
var body = document.createElementNS(SVG_NS, 'ellipse');
|
|
body.setAttribute('cx', '25'); body.setAttribute('cy', '26');
|
|
body.setAttribute('rx', '21'); body.setAttribute('ry', '25');
|
|
body.setAttribute('fill', color); body.setAttribute('opacity', '0.88');
|
|
|
|
// shine
|
|
var shine = document.createElementNS(SVG_NS, 'ellipse');
|
|
shine.setAttribute('cx', '18'); shine.setAttribute('cy', '16');
|
|
shine.setAttribute('rx', '7'); shine.setAttribute('ry', '5');
|
|
shine.setAttribute('fill', 'white'); shine.setAttribute('opacity', '0.25');
|
|
shine.setAttribute('transform', 'rotate(-20 18 16)');
|
|
|
|
var knot = document.createElementNS(SVG_NS, 'path');
|
|
knot.setAttribute('d', 'M25 51 Q27.5 55.5 25 58.5 Q22.5 55.5 25 51Z');
|
|
knot.setAttribute('fill', color); knot.setAttribute('opacity', '0.88');
|
|
|
|
var str = document.createElementNS(SVG_NS, 'path');
|
|
str.setAttribute('d', 'M25 58.5 Q20 68 25 80');
|
|
str.setAttribute('stroke', '#999'); str.setAttribute('stroke-width', '1.2');
|
|
str.setAttribute('fill', 'none'); str.setAttribute('opacity', '0.7');
|
|
|
|
svg.appendChild(body);
|
|
svg.appendChild(shine);
|
|
svg.appendChild(knot);
|
|
svg.appendChild(str);
|
|
return svg;
|
|
}
|
|
|
|
var active = false;
|
|
var tapCount = 0;
|
|
var tapTimer = null;
|
|
var lastTouch = 0;
|
|
|
|
function registerTap(source, target) {
|
|
if (active) { console.log('[🎈 easter egg] already active, ignoring'); return; }
|
|
tapCount++;
|
|
clearTimeout(tapTimer);
|
|
tapTimer = setTimeout(function () {
|
|
console.log('[🎈 easter egg] tap window expired, resetting count');
|
|
tapCount = 0;
|
|
}, 3000);
|
|
console.log('[🎈 easter egg] tap ' + tapCount + '/5 via ' + source + ' on <' + (target.tagName || '?').toLowerCase() + '>');
|
|
if (tapCount >= 5) {
|
|
tapCount = 0;
|
|
console.log('[🎈 easter egg] launching!');
|
|
launch();
|
|
}
|
|
}
|
|
|
|
// touchstart for mobile (fires immediately, not affected by scroll)
|
|
document.addEventListener('touchstart', function (e) {
|
|
if (e.target.closest('input, select, textarea')) {
|
|
console.log('[🎈 easter egg] touchstart ignored — form element');
|
|
return;
|
|
}
|
|
lastTouch = Date.now();
|
|
registerTap('touch', e.target);
|
|
}, { passive: true });
|
|
|
|
// click for desktop (deduplicated from touch events)
|
|
document.addEventListener('click', function (e) {
|
|
if (Date.now() - lastTouch < 500) {
|
|
console.log('[🎈 easter egg] click deduplicated (touch fired recently)');
|
|
return;
|
|
}
|
|
if (e.target.closest('input, select, textarea')) {
|
|
console.log('[🎈 easter egg] click ignored — form element');
|
|
return;
|
|
}
|
|
registerTap('click', e.target);
|
|
});
|
|
|
|
function launch() {
|
|
active = true;
|
|
console.log('[🎈 easter egg] building container');
|
|
|
|
var container = document.createElement('div');
|
|
Object.assign(container.style, {
|
|
position: 'fixed',
|
|
inset: '0',
|
|
pointerEvents: 'none',
|
|
zIndex: '9',
|
|
overflow: 'hidden',
|
|
});
|
|
document.body.insertBefore(container, document.body.firstChild);
|
|
|
|
var count = 22;
|
|
for (var i = 0; i < count; i++) {
|
|
(function (idx) {
|
|
var delay = idx * 130 + Math.random() * 150;
|
|
setTimeout(function () {
|
|
var color = COLORS[Math.floor(Math.random() * COLORS.length)];
|
|
var size = 38 + Math.random() * 32;
|
|
var leftPct = 3 + Math.random() * 94;
|
|
var sway = Math.random() * 40 - 20; // ±20 px
|
|
var tilt = Math.random() * 24 - 12; // ±12 deg
|
|
var dur = 4500 + Math.random() * 3500;
|
|
var rise = window.innerHeight + size * 2;
|
|
|
|
var wrap = document.createElement('div');
|
|
Object.assign(wrap.style, {
|
|
position: 'absolute',
|
|
bottom: (-size * 1.64 - 10) + 'px',
|
|
left: leftPct + '%',
|
|
});
|
|
wrap.appendChild(makeBalloon(color, size));
|
|
container.appendChild(wrap);
|
|
|
|
// Web Animations API — no CSS custom properties needed
|
|
wrap.animate([
|
|
{ transform: 'translateX(0px) translateY(0px) rotate(' + tilt + 'deg)', opacity: 0 },
|
|
{ transform: 'translateX(0px) translateY(0px) rotate(' + tilt + 'deg)', opacity: 1, offset: 0.06 },
|
|
{ transform: 'translateX(' + sway + 'px) translateY(' + (-rise * 0.5) + 'px) rotate(' + (-tilt * 0.3) + 'deg)', opacity: 1, offset: 0.5 },
|
|
{ transform: 'translateX(' + (-sway) + 'px) translateY(' + (-rise) + 'px) rotate(' + (-tilt * 0.6) + 'deg)', opacity: 0 },
|
|
], {
|
|
duration: dur,
|
|
easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
|
|
fill: 'forwards',
|
|
});
|
|
|
|
if (idx === 0) console.log('[🎈 easter egg] first balloon animated, rise=' + rise + 'px dur=' + dur + 'ms');
|
|
}, delay);
|
|
})(i);
|
|
}
|
|
|
|
setTimeout(function () {
|
|
console.log('[🎈 easter egg] cleaning up');
|
|
container.remove();
|
|
active = false;
|
|
}, 14000);
|
|
}
|
|
})();
|