import { initGlassTerminal, renderTerminalLayout, } from "./js/components/glass-terminal.js"; import { initLensEffect } from "./js/components/lens.js"; import { initFrameworkViz } from "./js/components/framework-viz.js"; import { initHeroEffect } from "./js/effects/liquid-canvas.js"; import { initScrollReveal } from "./js/utils/reveal.js"; import { initScrollIndicator, initSmoothScroll, initHashTracking } from "./js/utils/scroll.js"; // ============================================ // STATE // ============================================ let allCommands = []; // ============================================ // CONTENT LOADING // ============================================ async function loadContent() { try { const [commandsRes, patternsRes] = await Promise.all([ fetch("/api/commands"), fetch("/api/patterns"), ]); // 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); // 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 renderPatternsWithTabs(patterns, antipatterns) { const container = document.getElementById("patterns-categories"); if (!container || !patterns || !antipatterns) return; // Create a map of antipatterns by category name const antipatternMap = {}; antipatterns.forEach(cat => { antipatternMap[cat.name] = cat.items; }); // Generate unique IDs for tabs const tabId = (name) => `pattern-tab-${name.toLowerCase().replace(/\s+/g, '-')}`; const panelId = (name) => `pattern-panel-${name.toLowerCase().replace(/\s+/g, '-')}`; // Build tabs with WAI-ARIA attributes const tabsHTML = patterns .map((category, i) => ``) .join(""); // Build panels with WAI-ARIA attributes const panelsHTML = patterns .map((category, i) => { const antiItems = antipatternMap[category.name] || []; return `
Don't
    ${antiItems.map((item) => `
  • ${item}
  • `).join("")}
Do
    ${category.items.map((item) => `
  • ${item}
  • `).join("")}
`; }) .join(""); container.innerHTML = `
${tabsHTML}
${panelsHTML}
`; const tabs = container.querySelectorAll('.pattern-tab'); const panels = container.querySelectorAll('.pattern-panel'); // Function to switch tabs const switchTab = (newTab) => { const tabName = newTab.dataset.tab; // Update ARIA attributes on all tabs tabs.forEach(t => { t.classList.remove('active'); t.setAttribute('aria-selected', 'false'); t.setAttribute('tabindex', '-1'); }); // Activate the new tab newTab.classList.add('active'); newTab.setAttribute('aria-selected', 'true'); newTab.setAttribute('tabindex', '0'); newTab.focus(); // Update panels panels.forEach(p => { p.classList.remove('active'); p.setAttribute('hidden', ''); }); const activePanel = container.querySelector(`[data-panel="${tabName}"]`); activePanel.classList.add('active'); activePanel.removeAttribute('hidden'); }; // Tab click handling tabs.forEach(tab => { tab.addEventListener('click', () => switchTab(tab)); }); // Keyboard navigation (Arrow keys, Home, End) tabs.forEach((tab, index) => { tab.addEventListener('keydown', (e) => { let targetIndex = index; switch (e.key) { case 'ArrowLeft': case 'ArrowUp': e.preventDefault(); targetIndex = index === 0 ? tabs.length - 1 : index - 1; break; case 'ArrowRight': case 'ArrowDown': e.preventDefault(); targetIndex = index === tabs.length - 1 ? 0 : index + 1; break; case 'Home': e.preventDefault(); targetIndex = 0; break; case 'End': e.preventDefault(); targetIndex = tabs.length - 1; break; default: return; } switchTab(tabs[targetIndex]); }); }); } // ============================================ // EVENT HANDLERS // ============================================ // Handle bundle download clicks via event delegation document.addEventListener("click", (e) => { const bundleBtn = e.target.closest("[data-bundle]"); if (bundleBtn) { const { bundle: provider } = bundleBtn.dataset; window.location.href = `/api/download/bundle/${provider}`; } }); // ============================================ // STARTUP // ============================================ function init() { initSmoothScroll(); initScrollIndicator(); initHashTracking(); initHeroEffect(); initLensEffect(); initScrollReveal(); initGlassTerminal(); initFrameworkViz(); loadContent(); document.body.classList.add("loaded"); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); }