labs / static /js /main.js
3v324v23's picture
deploy: unified router + dreamy website (2026-06-16T09:46:52Z)
c1a683f
Raw
History Blame Contribute Delete
5.07 kB
/* =========================================================================
main.js — bubbles, scroll-reveal, copy-endpoint, moon fade
Pure vanilla, no dependencies. Runs on DOMContentLoaded.
========================================================================= */
(function () {
'use strict';
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
/* --- Bubbles ------------------------------------------------------- */
function spawnBubbles() {
const container = document.querySelector('.layer-bubbles');
if (!container) return;
// fewer bubbles on small screens for performance
const isMobile = window.matchMedia('(max-width: 640px)').matches;
const count = isMobile ? 12 : 26;
for (let i = 0; i < count; i++) {
const b = document.createElement('span');
b.className = 'bubble';
const size = rand(16, 82); // bigger, glassier bubbles
b.style.width = size + 'px';
b.style.height = size + 'px';
// space bubbles evenly across the width with a little jitter
const leftPct = Math.max(0, Math.min(100, (i / count) * 100 + rand(-3.5, 3.5)));
b.style.left = leftPct + '%';
const dur = rand(12, 30);
b.style.animationDuration = dur + 's';
// negative delay = already mid-flight, so they float immediately (no waiting)
b.style.animationDelay = '-' + rand(0, dur) + 's';
b.style.setProperty('--sway', rand(-30, 30) + 'px');
b.style.setProperty('--spin', rand(-60, 60) + 'deg');
container.appendChild(b);
}
}
/* --- Moon fade-in on scroll --------------------------------------- */
function initMoon() {
const moon = document.querySelector('.moon');
if (!moon) return;
const reveal = () => {
if (window.scrollY > window.innerHeight * 0.15) {
moon.classList.add('is-visible');
} else {
moon.classList.remove('is-visible');
}
};
reveal();
window.addEventListener('scroll', reveal, { passive: true });
}
/* --- Scroll-reveal moved to js/motion.js (handles [data-reveal] + .reveal) -- */
/* --- Copy endpoint to clipboard (handles hero + CTA buttons) ------ */
function initCopy() {
const ENDPOINT = 'https://apiarium-labs.hf.space/v1/chat/completions';
const toast = document.querySelector('.toast');
// fill every endpoint code element with the URL
document.querySelectorAll('.endpoint code, .endpoint-box code').forEach((el) => {
el.textContent = ENDPOINT;
});
async function doCopy(btn) {
try {
await navigator.clipboard.writeText(ENDPOINT);
} catch (e) {
// fallback for older browsers
const ta = document.createElement('textarea');
ta.value = ENDPOINT;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
}
const orig = btn.dataset.orig || btn.textContent;
btn.dataset.orig = orig;
btn.textContent = 'Copied';
btn.classList.add('copied');
if (toast) {
toast.classList.add('show');
setTimeout(() => toast.classList.remove('show'), 1800);
}
setTimeout(() => {
btn.textContent = orig;
btn.classList.remove('copied');
}, 1800);
}
// wire every copy button on the page
document.querySelectorAll('.copy-btn').forEach((btn) => {
btn.addEventListener('click', () => doCopy(btn));
});
}
/* --- Count-up stat numbers on scroll-in --------------------------- */
function initCountUp() {
const nums = document.querySelectorAll('.num[data-count]');
if (reduceMotion || !('IntersectionObserver' in window)) {
nums.forEach((el) => { el.textContent = formatNum(+el.dataset.count); });
return;
}
const io = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
const el = entry.target;
const target = +el.dataset.count;
const dur = 1400;
const startT = performance.now();
function tick(now) {
const p = Math.min(1, (now - startT) / dur);
const eased = 1 - Math.pow(1 - p, 3); // easeOutCubic
el.textContent = formatNum(Math.round(target * eased));
if (p < 1) requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
io.unobserve(el);
});
}, { threshold: 0.5 });
nums.forEach((el) => io.observe(el));
}
/* --- helpers ------------------------------------------------------ */
function rand(min, max) {
return Math.random() * (max - min) + min;
}
function formatNum(n) {
return n.toLocaleString('en-US');
}
/* --- boot --------------------------------------------------------- */
function init() {
spawnBubbles();
initMoon();
initCopy();
initCountUp();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();