/* ========================================================================= 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(); } })();