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 { initScrollReveal } from "./js/utils/reveal.js"; import { initAnchorScroll, initHashTracking } from "./js/utils/scroll.js"; // ============================================ // STATE // ============================================ let allCommands = []; // ============================================ // CONTENT LOADING // ============================================ function escapeHtml(value) { if (typeof value !== "string") return ""; return value .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } function copyTextFallback(text) { const textArea = document.createElement("textarea"); textArea.value = text; textArea.setAttribute("readonly", ""); textArea.style.position = "absolute"; textArea.style.left = "-9999px"; document.body.appendChild(textArea); textArea.select(); try { return document.execCommand("copy"); } catch { return false; } finally { document.body.removeChild(textArea); } } 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) => { const categoryName = String(category.name ?? ""); return ``; }) .join(""); // Build panels with WAI-ARIA attributes const panelsHTML = patterns .map((category, i) => { const categoryName = String(category.name ?? ""); const antiItems = antipatternMap[category.name] || []; return `
Don't
    ${antiItems.map((item) => `
  • ${escapeHtml(item)}
  • `).join("")}
Do
    ${category.items.map((item) => `
  • ${escapeHtml(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 escapedTabName = typeof CSS !== "undefined" && CSS.escape ? CSS.escape(tabName) : tabName.replaceAll('"', '\\"'); const activePanel = container.querySelector( `[data-panel="${escapedTabName}"]`, ); if (!activePanel) return; 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 provider = bundleBtn.dataset.bundle; const prefixToggle = document.getElementById("prefix-toggle"); const usePrefixed = prefixToggle && prefixToggle.checked; const bundleName = usePrefixed ? `${provider}-prefixed` : provider; window.location.href = `/api/download/bundle/${bundleName}`; } // Handle copy button clicks const copyBtn = e.target.closest("[data-copy]"); if (copyBtn) { const textToCopy = copyBtn.dataset.copy; const onCopySuccess = () => { copyBtn.classList.add("copied"); setTimeout(() => copyBtn.classList.remove("copied"), 1500); }; if (navigator.clipboard?.writeText) { navigator.clipboard .writeText(textToCopy) .then(onCopySuccess) .catch(() => { if (copyTextFallback(textToCopy)) { onCopySuccess(); } }); } else if (copyTextFallback(textToCopy)) { onCopySuccess(); } } }); // ============================================ // STARTUP // ============================================ function init() { initAnchorScroll(); initHashTracking(); initLensEffect(); initScrollReveal(); initGlassTerminal(); initFrameworkViz(); loadContent(); document.body.classList.add("loaded"); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); }