import { initGlassTerminal, renderTerminalLayout, } from "./components/glass-terminal.js"; import { initFrameworkViz } from "./components/framework-viz.js"; import { initScrollReveal } from "./utils/reveal.js"; import { initAnchorScroll, initHashTracking } from "./utils/scroll.js"; import { initCopyFeedback } from "./utils/copy-feedback.js"; import { initSectionNav } from "./components/section-nav.js"; import { initFoundationGrid } from "./components/foundation-grid.js"; import { initLiveDemo, initGbarPageChat } from "./components/live-demo.js"; // ============================================ // STATE // ============================================ let allCommands = []; // ============================================ // CONTENT LOADING // ============================================ function escapeHtml(value) { if (typeof value !== "string") return ""; return value .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } async function loadContent() { try { const [commandsRes, patternsRes] = await Promise.all([ fetch("/_data/api/commands.json"), fetch("/_data/api/patterns.json"), ]); // Check for HTTP errors if (!commandsRes.ok) { throw new Error(`Commands API failed: ${commandsRes.status}`); } if (!patternsRes.ok) { throw new Error(`Patterns API failed: ${patternsRes.status}`); } allCommands = await commandsRes.json(); const patternsData = await patternsRes.json(); // Render commands (Glass Terminal) renderTerminalLayout(allCommands); // Initialize gallery card stack initGalleryStack(); // Render patterns with tabbed navigation renderPatternsWithTabs(patternsData.patterns, patternsData.antipatterns); } catch (error) { console.error("Failed to load content:", error); showLoadError(error); } } function showLoadError(error) { // Show error in commands section const commandsGallery = document.querySelector('.commands-gallery'); if (commandsGallery) { commandsGallery.innerHTML = ` `; } // Show error in patterns section const patternsContainer = document.getElementById("patterns-categories"); if (patternsContainer) { patternsContainer.innerHTML = ` `; } } function initGalleryStack() { const container = document.querySelector('.gallery-stack-container'); const stack = document.getElementById('gallery-stack'); if (!stack || !container) return; const cards = stack.querySelectorAll('.gallery-stack-card'); const counter = container.querySelector('.gallery-stack-counter'); const total = cards.length; let current = 0; let lastScroll = 0; function update() { cards.forEach((card, i) => { const offset = (i - current + total) % total; card.dataset.offset = offset; }); } function next() { current = (current + 1) % total; update(); } function prev() { current = (current - 1 + total) % total; update(); } container.querySelector('.gallery-stack-prev').addEventListener('click', prev); container.querySelector('.gallery-stack-next').addEventListener('click', next); stack.addEventListener('wheel', (e) => { e.preventDefault(); const now = Date.now(); if (now - lastScroll < 350) return; lastScroll = now; if (e.deltaY > 0) next(); else prev(); }, { passive: false }); update(); } function renderPatternsWithTabs(patterns, antipatterns) { const container = document.getElementById("patterns-categories"); if (!container || !patterns || !antipatterns) return; const antipatternMap = {}; antipatterns.forEach(cat => { antipatternMap[cat.name] = cat.items; }); const tabsHTML = patterns.map((cat, i) => `` ).join(''); const panelsHTML = patterns.map((cat, i) => { const antiItems = antipatternMap[cat.name] || []; return `
`; }).join(''); container.innerHTML = `
${tabsHTML}
${panelsHTML}`; const tabsEl = container.querySelector('.patterns-tabs'); const tabsWrap = container.querySelector('.patterns-tabs-wrap'); container.addEventListener('click', (e) => { const tab = e.target.closest('.patterns-tab'); if (!tab) return; const index = tab.dataset.index; container.querySelectorAll('.patterns-tab').forEach(t => t.classList.remove('is-active')); container.querySelectorAll('.patterns-content').forEach(p => p.classList.remove('is-active')); tab.classList.add('is-active'); container.querySelector(`.patterns-content[data-index="${index}"]`).classList.add('is-active'); // Center the clicked tab inside the tabs strip (not the page). Using // scrollBy on the container keeps the page scroll untouched. if (tabsEl) { const tabRect = tab.getBoundingClientRect(); const stripRect = tabsEl.getBoundingClientRect(); const offset = (tabRect.left + tabRect.width / 2) - (stripRect.left + stripRect.width / 2); tabsEl.scrollBy({ left: offset, behavior: 'smooth' }); } }); // Track scroll position so the edge-fade mask only appears on sides where // there's actually more content. At the start, no left fade; at the end, // no right fade; if no overflow, no fade at all. const updateScrollState = () => { if (!tabsEl) return; const { scrollLeft, scrollWidth, clientWidth } = tabsEl; const max = scrollWidth - clientWidth; let state; if (max <= 1) state = 'none'; else if (scrollLeft <= 1) state = 'start'; else if (scrollLeft >= max - 1) state = 'end'; else state = 'middle'; tabsEl.dataset.scroll = state; if (tabsWrap) tabsWrap.dataset.scroll = state; }; tabsEl?.addEventListener('scroll', updateScrollState, { passive: true }); window.addEventListener('resize', updateScrollState); updateScrollState(); } // ============================================ // EVENT HANDLERS // ============================================ // Handle bundle download clicks via event delegation. // Each download button carries the full bundle name in data-bundle // (currently just "universal") so the handler is just a redirect. document.addEventListener("click", (e) => { const bundleBtn = e.target.closest("[data-bundle]"); if (bundleBtn) { const bundleName = bundleBtn.dataset.bundle; window.location.href = `/api/download/bundle/${bundleName}`; } }); // ============================================ // STARTUP // ============================================ // Fade the header's glass background in px-by-px as the user scrolls off the // hero, by writing scroll progress (0 → 1 over RANGE px) to a --hp custom // property the CSS interpolates against. function initHeaderScroll() { const header = document.querySelector("[data-site-header]"); if (!header) return; const RANGE = 200; let ticking = false; const apply = () => { const p = Math.min(1, window.scrollY / RANGE); header.style.setProperty("--hp", p.toFixed(4)); ticking = false; }; const onScroll = () => { if (!ticking) { ticking = true; requestAnimationFrame(apply); } }; apply(); window.addEventListener("scroll", onScroll, { passive: true }); } function initHeroProof() { const root = document.querySelector("[data-hero-proof]"); if (!root) return; const tabs = Array.from(root.querySelectorAll("[data-proof-tab]")); const panels = Array.from(root.querySelectorAll("[data-proof-panel]")); const result = root.querySelector("[data-hero-proof-result]"); const positionUpdates = new Map(); if (!tabs.length || !panels.length) return; panels.forEach((panel) => { const range = panel.querySelector("[data-proof-range]"); if (!range) return; const updatePosition = () => { const position = Number(range.value); panel.style.setProperty("--proof-position", `${position}%`); if (panel.hidden) return; const panelRect = panel.getBoundingClientRect(); const seamX = panelRect.left + panelRect.width * position / 100; panel.querySelectorAll(".hero-proof-layer--before .proof-annotation").forEach((annotation) => { const target = annotation.parentElement; const targetX = target?.getBoundingClientRect().left ?? panelRect.left; annotation.classList.toggle("is-concealed", position <= 0 || targetX >= seamX); }); }; positionUpdates.set(panel, updatePosition); range.addEventListener("input", updatePosition); updatePosition(); }); const activate = (index, moveFocus = false) => { const nextTab = tabs[index]; const nextPanel = panels.find((panel) => panel.dataset.proofPanel === nextTab?.dataset.proofTab); if (!nextTab || !nextPanel) return; tabs.forEach((tab) => { const active = tab === nextTab; tab.classList.toggle("is-active", active); tab.setAttribute("aria-selected", active ? "true" : "false"); tab.tabIndex = active ? 0 : -1; }); panels.forEach((panel) => { const active = panel === nextPanel; panel.classList.toggle("is-active", active); panel.hidden = !active; }); positionUpdates.get(nextPanel)?.(); if (result) result.textContent = nextPanel.dataset.summary || "Example updated"; if (moveFocus) nextTab.focus(); }; window.addEventListener("resize", () => { const activePanel = panels.find((panel) => !panel.hidden); if (activePanel) positionUpdates.get(activePanel)?.(); }, { passive: true }); tabs.forEach((tab, index) => { tab.addEventListener("click", () => activate(index)); tab.addEventListener("keydown", (event) => { let next = index; if (event.key === "ArrowRight") next = (index + 1) % tabs.length; else if (event.key === "ArrowLeft") next = (index - 1 + tabs.length) % tabs.length; else if (event.key === "Home") next = 0; else if (event.key === "End") next = tabs.length - 1; else return; event.preventDefault(); activate(next, true); }); }); } function init() { initAnchorScroll(); initHashTracking(); initCopyFeedback(); initHeaderScroll(); initHeroProof(); initScrollReveal(); initGlassTerminal(); initFrameworkViz(); initFoundationGrid(); initSectionNav(); initWhyTabs(); initLanguageTabs(); initSteeringDesktop(); initLiveDemo(); initGbarPageChat(); loadContent(); document.body.classList.add("loaded"); } function initSteeringDesktop() { const desktop = document.querySelector('[data-steering-desktop]'); if (!desktop) return; const mobileTabs = Array.from(desktop.querySelectorAll('[data-steering-mobile-tab]')); const commandButtons = Array.from(desktop.querySelectorAll('[data-steering-command]')); const activeCommands = Array.from(desktop.querySelectorAll('[data-steering-active-command]')); const agentCopy = desktop.querySelector('[data-steering-agent-copy]'); const agentThread = desktop.querySelector('[data-steering-thread]'); const agentPane = desktop.querySelector('[data-steering-pane="agent"]'); const browserPane = desktop.querySelector('[data-steering-pane="browser"]'); const liveDemo = desktop.querySelector('.language-live-demo'); const polishScene = desktop.querySelector('[data-steering-thread-scene="polish"]'); const liveScene = desktop.querySelector('[data-steering-thread-scene="live"]'); const composerCommand = desktop.querySelector('[data-steering-composer-command]'); const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)'); const mobileLayout = window.matchMedia('(max-width: 760px)'); const selectedCommand = () => commandButtons.find((button) => button.classList.contains('is-active'))?.dataset.steeringCommand || 'polish'; const setBrowserUiQuiet = (quiet) => { desktop.dataset.steeringBrowserUi = quiet ? 'quiet' : 'live'; if (!liveDemo) return; liveDemo.toggleAttribute('inert', quiet); if (quiet) liveDemo.setAttribute('aria-hidden', 'true'); else liveDemo.removeAttribute('aria-hidden'); liveDemo.dispatchEvent(new CustomEvent('steering-demo-quiet', { detail: { quiet } })); }; const showThreadScene = (scene) => { if (!agentThread || !polishScene || !liveScene) return; const target = scene === 'live' ? liveScene : polishScene; agentThread.scrollTo({ top: Math.max(0, target.offsetTop - polishScene.offsetTop), behavior: reducedMotion.matches ? 'auto' : 'smooth', }); if (composerCommand) composerCommand.textContent = scene === 'live' ? 'live' : selectedCommand(); }; mobileTabs.forEach((tab) => { tab.addEventListener('click', () => { const pane = tab.dataset.steeringMobileTab; desktop.dataset.mobilePane = pane; showThreadScene(pane === 'browser' ? 'live' : 'polish'); setBrowserUiQuiet(pane === 'agent'); mobileTabs.forEach((item) => { const selected = item === tab; item.classList.toggle('is-active', selected); item.setAttribute('aria-selected', selected ? 'true' : 'false'); }); }); }); if (agentPane) { agentPane.addEventListener('pointerenter', () => { if (!mobileLayout.matches) setBrowserUiQuiet(true); }); agentPane.addEventListener('pointerleave', () => { if (!mobileLayout.matches) setBrowserUiQuiet(false); }); agentPane.addEventListener('focusin', () => setBrowserUiQuiet(true)); agentPane.addEventListener('focusout', (event) => { if (!agentPane.contains(event.relatedTarget)) setBrowserUiQuiet(false); }); } if (browserPane) { browserPane.addEventListener('pointerenter', () => { if (mobileLayout.matches) return; setBrowserUiQuiet(false); showThreadScene('live'); }); browserPane.addEventListener('pointerleave', () => { if (!mobileLayout.matches) showThreadScene('polish'); }); browserPane.addEventListener('focusin', () => { setBrowserUiQuiet(false); showThreadScene('live'); }); browserPane.addEventListener('focusout', (event) => { if (!browserPane.contains(event.relatedTarget)) showThreadScene('polish'); }); } if (mobileLayout.matches && desktop.dataset.mobilePane === 'agent') { setBrowserUiQuiet(true); } commandButtons.forEach((button) => { button.addEventListener('click', () => { const command = button.dataset.steeringCommand; commandButtons.forEach((item) => { const selected = item === button; item.classList.toggle('is-active', selected); item.setAttribute('aria-pressed', selected ? 'true' : 'false'); }); activeCommands.forEach((item) => { item.textContent = command; }); if (agentCopy && button.dataset.agentCopy) agentCopy.textContent = button.dataset.agentCopy; }); }); } function initLanguageTabs() { const toggle = document.querySelector('.language-view-toggle'); if (!toggle) return; const tabs = Array.from(toggle.querySelectorAll('.language-view-tab')); const panels = Array.from(document.querySelectorAll('.language-view[data-view-panel]')); if (!tabs.length || !panels.length) return; tabs.forEach((tab) => { tab.addEventListener('click', () => { const view = tab.dataset.view; tabs.forEach((t) => { const on = t === tab; t.classList.toggle('is-active', on); t.setAttribute('aria-selected', on ? 'true' : 'false'); }); panels.forEach((p) => { const on = p.dataset.viewPanel === view; p.classList.toggle('is-active', on); if (on) p.removeAttribute('hidden'); else p.setAttribute('hidden', ''); }); }); }); } function initWhyTabs() { const container = document.querySelector('.why-layout'); if (!container) return; const tabs = Array.from(container.querySelectorAll('.why-tab')); const panels = Array.from(container.querySelectorAll('.why-panel')); if (!tabs.length || !panels.length) return; const CYCLE_MS = 7000; const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; let current = Math.max(tabs.findIndex((tab) => tab.classList.contains('is-active')), 0); let timer = null; let autoRotate = !reducedMotion; let visible = false; const tabStrip = container.querySelector('.why-tabs'); const getPanelForTab = (tab) => { const panelId = tab?.getAttribute('aria-controls'); return panelId ? container.querySelector(`#${CSS.escape(panelId)}`) : null; }; const centerActiveInStrip = (active) => { // On mobile the tab list is a horizontal scroll strip. Keep the // active pill visible without touching the page scroll. Using // scrollTo with behavior:auto + direct scrollLeft assignment, // because smooth-scroll on this container is disabled by the // parent's mask-image compositing and silently no-ops. if (!tabStrip || tabStrip.scrollWidth <= tabStrip.clientWidth + 1) return; const tabRect = active.getBoundingClientRect(); const stripRect = tabStrip.getBoundingClientRect(); const offset = (tabRect.left + tabRect.width / 2) - (stripRect.left + stripRect.width / 2); if (Math.abs(offset) < 2) return; tabStrip.scrollLeft += offset; }; const activate = (index, fromAuto = false) => { const targetTab = tabs[index]; const targetPanel = getPanelForTab(targetTab); if (!targetTab || !targetPanel) return; current = index; tabs.forEach((tab, i) => { const on = i === index; tab.classList.toggle('is-active', on); tab.setAttribute('aria-selected', on ? 'true' : 'false'); // Reset cycling class, re-add on the new active tab so the // progress indicator restarts cleanly. tab.classList.remove('is-cycling'); }); panels.forEach((panel) => { const on = panel === targetPanel; panel.classList.toggle('is-active', on); if (on) panel.removeAttribute('hidden'); else panel.setAttribute('hidden', ''); }); if (autoRotate && visible) { // Force reflow so the animation restart is picked up. void targetTab.offsetWidth; targetTab.classList.add('is-cycling'); } centerActiveInStrip(targetTab); }; const scheduleNext = () => { clearTimeout(timer); if (!autoRotate || !visible) return; timer = setTimeout(() => { const next = (current + 1) % tabs.length; activate(next, true); scheduleNext(); }, CYCLE_MS); }; const stopAuto = () => { autoRotate = false; clearTimeout(timer); tabs.forEach((t) => t.classList.remove('is-cycling')); }; tabs.forEach((tab, index) => { tab.addEventListener('click', () => { stopAuto(); activate(index); }); tab.addEventListener('keydown', (e) => { if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp') return; e.preventDefault(); stopAuto(); const dir = e.key === 'ArrowDown' ? 1 : -1; const next = (index + dir + tabs.length) % tabs.length; tabs[next].focus(); activate(next); }); }); container.addEventListener('mouseenter', () => { // Pause auto-rotation on hover. Resume only if still allowed and // user hasn't interacted (stopAuto flips autoRotate off). clearTimeout(timer); tabs.forEach((t) => t.classList.remove('is-cycling')); }); container.addEventListener('mouseleave', () => { if (autoRotate && visible) { // Re-apply cycling class to current tab and resume the timer. const active = tabs[current]; void active.offsetWidth; active.classList.add('is-cycling'); scheduleNext(); } }); // Observe visibility so we only rotate while the user can see it. const io = new IntersectionObserver((entries) => { entries.forEach((e) => { visible = e.isIntersecting; if (visible) { if (autoRotate) { const active = tabs[current]; void active.offsetWidth; active.classList.add('is-cycling'); scheduleNext(); } } else { clearTimeout(timer); tabs.forEach((t) => t.classList.remove('is-cycling')); } }); }, { threshold: 0.35 }); io.observe(container); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); }