diff --git a/public/app.js b/public/app.js
index 6ae7f41c0..7ab784756 100644
--- a/public/app.js
+++ b/public/app.js
@@ -1,6 +1,6 @@
import {
- initGlassTerminal,
- renderTerminalLayout,
+ initGlassTerminal,
+ renderTerminalLayout,
} from "./js/components/glass-terminal.js";
import { initLensEffect } from "./js/components/lens.js";
import { initFrameworkViz } from "./js/components/framework-viz.js";
@@ -18,66 +18,49 @@ let allCommands = [];
// ============================================
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);
- }
+ if (typeof value !== "string") return "";
+ return value
+ .replaceAll("&", "&")
+ .replaceAll("<", "<")
+ .replaceAll(">", ">")
+ .replaceAll('"', """)
+ .replaceAll("'", "'");
}
async function loadContent() {
- try {
- const [commandsRes, patternsRes] = await Promise.all([
- fetch("/api/commands"),
- fetch("/api/patterns"),
- ]);
+ 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}`);
- }
+ // 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();
+ allCommands = await commandsRes.json();
+ const patternsData = await patternsRes.json();
- // Render commands (Glass Terminal)
- renderTerminalLayout(allCommands);
+ // 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);
- }
+ // 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 commands section
+ const commandsGallery = document.querySelector('.commands-gallery');
+ if (commandsGallery) {
+ commandsGallery.innerHTML = `
⚠
Failed to load commands
@@ -87,12 +70,12 @@ function showLoadError(error) {
`;
- }
+ }
- // Show error in patterns section
- const patternsContainer = document.getElementById("patterns-categories");
- if (patternsContainer) {
- patternsContainer.innerHTML = `
+ // Show error in patterns section
+ const patternsContainer = document.getElementById("patterns-categories");
+ if (patternsContainer) {
+ patternsContainer.innerHTML = `
⚠
Failed to load patterns
@@ -102,54 +85,48 @@ function showLoadError(error) {
`;
- }
+ }
}
function renderPatternsWithTabs(patterns, antipatterns) {
- const container = document.getElementById("patterns-categories");
- if (!container || !patterns || !antipatterns) return;
+ 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;
- });
+ // 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, "-")}`;
+ // 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 `
+ // Build panels with WAI-ARIA attributes
+ const panelsHTML = patterns
+ .map((category, i) => {
+ const antiItems = antipatternMap[category.name] || [];
+ return `
@@ -167,87 +144,82 @@ function renderPatternsWithTabs(patterns, antipatterns) {
`;
- })
- .join("");
+ })
+ .join("");
- container.innerHTML = `
+ container.innerHTML = `
${tabsHTML}
${panelsHTML}
`;
- const tabs = container.querySelectorAll(".pattern-tab");
- const panels = container.querySelectorAll(".pattern-panel");
+ const tabs = container.querySelectorAll('.pattern-tab');
+ const panels = container.querySelectorAll('.pattern-panel');
- // Function to switch tabs
- const switchTab = (newTab) => {
- const tabName = newTab.dataset.tab;
+ // 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");
- });
+ // 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();
+ // 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");
- };
+ // Update panels
+ panels.forEach(p => {
+ p.classList.remove('active');
+ p.setAttribute('hidden', '');
+ });
+ const escapedName = CSS.escape(tabName);
+ const activePanel = container.querySelector(`[data-panel="${escapedName}"]`);
+ if (!activePanel) return;
+ activePanel.classList.add('active');
+ activePanel.removeAttribute('hidden');
+ };
- // Tab click handling
- tabs.forEach((tab) => {
- tab.addEventListener("click", () => switchTab(tab));
- });
+ // 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;
+ // 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;
- }
+ 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]);
- });
- });
+ switchTab(tabs[targetIndex]);
+ });
+ });
}
// ============================================
@@ -256,57 +228,55 @@ function renderPatternsWithTabs(patterns, antipatterns) {
// 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}`;
- }
+ 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();
- }
- }
+ // Handle copy button clicks
+ const copyBtn = e.target.closest("[data-copy]");
+ if (copyBtn) {
+ const textToCopy = copyBtn.dataset.copy;
+ const onCopied = () => {
+ copyBtn.classList.add('copied');
+ setTimeout(() => copyBtn.classList.remove('copied'), 1500);
+ };
+ if (navigator.clipboard?.writeText) {
+ navigator.clipboard.writeText(textToCopy).then(onCopied).catch(() => {});
+ } else {
+ // Fallback for non-HTTPS or older browsers
+ const ta = Object.assign(document.createElement('textarea'), { value: textToCopy, style: 'position:fixed;left:-9999px' });
+ document.body.appendChild(ta);
+ ta.select();
+ try { document.execCommand('copy'); onCopied(); } catch {}
+ ta.remove();
+ }
+ }
});
+
// ============================================
// STARTUP
// ============================================
function init() {
- initAnchorScroll();
- initHashTracking();
- initLensEffect();
- initScrollReveal();
- initGlassTerminal();
- initFrameworkViz();
- loadContent();
+ initAnchorScroll();
+ initHashTracking();
+ initLensEffect();
+ initScrollReveal();
+ initGlassTerminal();
+ initFrameworkViz();
+ loadContent();
- document.body.classList.add("loaded");
+ document.body.classList.add("loaded");
}
if (document.readyState === "loading") {
- document.addEventListener("DOMContentLoaded", init);
+ document.addEventListener("DOMContentLoaded", init);
} else {
- init();
+ init();
}