chris 2002d7f35a Add balloon easter egg — long press logo to trigger
Hold the logo for 0.6s (works on mobile touch and desktop mouse) to launch
22 balloon silhouettes floating up from the bottom. Balloons drift with a
slight sway, have a shine highlight, and naturally disappear under the navbar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 21:07:06 -04:00

129 lines
4.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

(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 pressTimer = null;
function startPress() {
pressTimer = setTimeout(function () {
if (!active) launch();
}, 600);
}
function cancelPress() {
clearTimeout(pressTimer);
}
// Long-press on the navbar brand (logo area)
document.addEventListener('mousedown', function (e) {
if (e.target.closest('.navbar-brand')) startPress();
});
document.addEventListener('mouseup', cancelPress);
document.addEventListener('mouseleave', cancelPress);
document.addEventListener('touchstart', function (e) {
if (e.target.closest('.navbar-brand')) startPress();
}, { passive: true });
document.addEventListener('touchend', cancelPress);
document.addEventListener('touchmove', cancelPress);
function launch() {
active = true;
// Inject keyframes once
if (!document.getElementById('bpb-balloon-style')) {
var style = document.createElement('style');
style.id = 'bpb-balloon-style';
style.textContent = [
'@keyframes bpb-float {',
' 0% { transform: translateX(var(--bx)) translateY(0) rotate(var(--br)); opacity: 0; }',
' 8% { opacity: 1; }',
' 85% { opacity: 1; }',
' 100% { transform: translateX(calc(var(--bx) * -1)) translateY(var(--by)) rotate(calc(var(--br) * -0.5)); opacity: 0; }',
'}',
].join('\n');
document.head.appendChild(style);
}
var container = document.createElement('div');
Object.assign(container.style, {
position: 'fixed',
inset: '0',
pointerEvents: 'none',
zIndex: '9', // below Bulma navbar (z-index 30)
overflow: 'hidden',
});
document.body.insertBefore(container, document.body.firstChild);
var count = 22;
for (var i = 0; i < count; i++) {
(function (idx) {
setTimeout(function () {
var color = COLORS[Math.floor(Math.random() * COLORS.length)];
var size = 38 + Math.random() * 32; // 3870 px
var leftPct = 3 + Math.random() * 94; // 397 %
var sway = (Math.random() * 40 - 20) + 'px'; // ±20 px horizontal drift
var tilt = (Math.random() * 24 - 12) + 'deg';
var dur = 4500 + Math.random() * 3500; // 4.58 s
var riseAmt = -(window.innerHeight + size * 2); // rise full viewport height
var wrap = document.createElement('div');
Object.assign(wrap.style, {
position: 'absolute',
bottom: (-size * 1.64 - 10) + 'px',
left: leftPct + '%',
'--bx': sway,
'--by': riseAmt + 'px',
'--br': tilt,
animation: 'bpb-float ' + dur + 'ms cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards',
animationDelay: (idx * 120 + Math.random() * 200) + 'ms',
});
wrap.appendChild(makeBalloon(color, size));
container.appendChild(wrap);
}, 0);
})(i);
}
setTimeout(function () {
container.remove();
active = false;
}, 12000);
}
})();