Skip to main content
Glama
app.js3.77 kB
document.documentElement.classList.add("js"); window.requestAnimationFrame(() => { document.documentElement.classList.add("isReady"); }); function setupReveals() { const els = Array.from(document.querySelectorAll("[data-reveal]")); let i = 0; for (const el of els) { if (!(el instanceof HTMLElement)) continue; el.style.transitionDelay = `${70 + i * 70}ms`; i += 1; } } const commands = { brew: "brew install steipete/tap/peekaboo", npm: "npx -y @steipete/peekaboo", }; function toast(message) { let node = document.querySelector(".toast"); if (!(node instanceof HTMLElement)) { node = document.createElement("div"); node.className = "toast"; node.setAttribute("role", "status"); node.setAttribute("aria-live", "polite"); document.body.appendChild(node); } node.textContent = message; node.classList.remove("isOn"); // force reflow so animation re-triggers void node.offsetWidth; node.classList.add("isOn"); } function setActiveTab(root, tabName) { const code = root.querySelector("[data-code]"); const tabs = Array.from(root.querySelectorAll("[data-tab]")); for (const tab of tabs) { const isActive = tab.dataset.tab === tabName; tab.classList.toggle("isActive", isActive); tab.setAttribute("aria-selected", isActive ? "true" : "false"); } if (code) code.textContent = commands[tabName] ?? commands.brew; } function setupCommandCard() { const root = document.querySelector("[data-cmd]"); if (!root) return; const initial = localStorage.getItem("peekabooInstallTab") || "brew"; setActiveTab(root, initial); root.addEventListener("click", async (event) => { const target = event.target; if (!(target instanceof HTMLElement)) return; const tabName = target.dataset.tab; if (tabName) { setActiveTab(root, tabName); localStorage.setItem("peekabooInstallTab", tabName); return; } if (!target.hasAttribute("data-copy")) return; let text = ""; const selector = target.getAttribute("data-copy"); if (selector && selector !== "true") { const el = document.querySelector(selector); if (el) text = el.textContent ?? ""; } else { const code = root.querySelector("[data-code]"); if (code) text = code.textContent ?? ""; } if (!text.trim()) return; try { await navigator.clipboard.writeText(text); toast("Copied"); } catch { toast("Copy failed"); } }); } function setupEye() { const eyes = Array.from(document.querySelectorAll(".eye")); if (eyes.length === 0) return; let lastX = 0; let lastY = 0; let raf = 0; const tick = () => { raf = 0; for (const eye of eyes) { const rect = eye.getBoundingClientRect(); const cx = rect.left + rect.width / 2; const cy = rect.top + rect.height / 2; const dx = lastX - cx; const dy = lastY - cy; const max = 3.2; const len = Math.hypot(dx, dy) || 1; const ux = (dx / len) * max; const uy = (dy / len) * max; eye.style.transform = `translate(calc(-50% + ${ux}px), calc(-50% + ${uy}px))`; } }; window.addEventListener( "pointermove", (event) => { lastX = event.clientX; lastY = event.clientY; if (!raf) raf = window.requestAnimationFrame(tick); }, { passive: true }, ); // blink window.setInterval(() => { for (const eye of eyes) { eye.animate( [{ transform: eye.style.transform, filter: "saturate(1)" }, { transform: `${eye.style.transform} scaleY(0.1)`, filter: "saturate(0.8)" }, { transform: eye.style.transform, filter: "saturate(1)" }], { duration: 170, easing: "ease-out" }, ); } }, 5200); } setupCommandCard(); setupEye(); setupReveals();

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/steipete/Peekaboo'

If you have feedback or need assistance with the MCP directory API, please join our Discord server