let documents = []; let currentDocument = null; let documentCards = null; // Initialize card-based viewer if (typeof DocumentCards !== 'undefined') { documentCards = new DocumentCards('document-content'); } // Document categorization const CATEGORIES = { 'start-here': { label: '📚 Start Here', icon: '📚', keywords: ['glossary', 'introduction'], order: 1, color: 'blue', bgColor: 'bg-blue-50', borderColor: 'border-l-4 border-blue-500', textColor: 'text-blue-700' }, 'core-framework': { label: '📖 Core Framework', icon: '📖', keywords: ['core-concepts', 'core-values', 'organizational-theory'], order: 2, color: 'purple', bgColor: 'bg-purple-50', borderColor: 'border-l-4 border-purple-500', textColor: 'text-purple-700' }, 'research': { label: '🔬 Research & Evidence', icon: '🔬', keywords: ['case-studies', 'research-foundations', 'tractatus-based-llm-architecture-for-ai-safety', 'research-topic'], order: 3, color: 'indigo', bgColor: 'bg-indigo-50', borderColor: 'border-l-4 border-indigo-500', textColor: 'text-indigo-700' }, 'implementation': { label: '🛠️ Implementation', icon: '🛠️', keywords: ['implementation-guide', 'implementation-roadmap', 'python-code'], order: 4, color: 'green', bgColor: 'bg-green-50', borderColor: 'border-l-4 border-green-500', textColor: 'text-green-700' }, 'leadership': { label: '💼 Leadership', icon: '💼', keywords: ['executive-brief'], order: 5, color: 'orange', bgColor: 'bg-orange-50', borderColor: 'border-l-4 border-orange-500', textColor: 'text-orange-700' }, 'developer': { label: '🧑‍💻 Developer Tools', icon: '🧑‍💻', keywords: ['claude-code', 'framework-enforcement'], order: 6, color: 'gray', bgColor: 'bg-gray-50', borderColor: 'border-l-4 border-gray-500', textColor: 'text-gray-700' } }; // Documents to hide (internal/confidential) const HIDDEN_DOCS = [ 'security-audit-report', 'koha-production-deployment', 'koha-stripe-payment', 'appendix-e-contact', 'cover-letter' ]; // Categorize a document function categorizeDocument(doc) { const slug = doc.slug.toLowerCase(); // Skip hidden documents if (HIDDEN_DOCS.some(hidden => slug.includes(hidden))) { return null; } // Find matching category for (const [categoryId, category] of Object.entries(CATEGORIES)) { if (category.keywords.some(keyword => slug.includes(keyword))) { return categoryId; } } // Default to core-framework if no match return 'core-framework'; } // Group documents by category function groupDocuments(docs) { const grouped = {}; // Initialize all categories Object.keys(CATEGORIES).forEach(key => { grouped[key] = []; }); // Categorize each document docs.forEach(doc => { const category = categorizeDocument(doc); if (category && grouped[category]) { grouped[category].push(doc); } }); return grouped; } // Render document link with download button function renderDocLink(doc, isHighlighted = false) { const highlightClass = isHighlighted ? 'text-blue-700 bg-blue-50 border border-blue-200' : ''; return `
`; } // Load document list async function loadDocuments() { try { const response = await fetch('/api/documents'); const data = await response.json(); documents = data.documents || []; const listEl = document.getElementById('document-list'); if (documents.length === 0) { listEl.innerHTML = '
No documents available
'; return; } // Group documents by category const grouped = groupDocuments(documents); let html = ''; // Render categories in order const sortedCategories = Object.entries(CATEGORIES) .sort((a, b) => a[1].order - b[1].order); sortedCategories.forEach(([categoryId, category]) => { const docs = grouped[categoryId] || []; if (docs.length === 0) return; // Category header html += `
`; // Render documents in category docs.forEach(doc => { const isHighlighted = categoryId === 'start-here'; html += renderDocLink(doc, isHighlighted); }); html += `
`; }); listEl.innerHTML = html; // Add event delegation for document links listEl.addEventListener('click', function(e) { // Check for download link first (prevent document load when clicking download) const downloadLink = e.target.closest('.doc-download-link'); if (downloadLink) { e.stopPropagation(); return; } const button = e.target.closest('.doc-link'); if (button && button.dataset.slug) { e.preventDefault(); loadDocument(button.dataset.slug); return; } // Category toggle const toggle = e.target.closest('.category-toggle'); if (toggle) { const categoryId = toggle.dataset.category; const docsEl = listEl.querySelector(`.category-docs[data-category="${categoryId}"]`); const arrowEl = toggle.querySelector('.category-arrow'); if (docsEl.style.display === 'none') { docsEl.style.display = 'block'; arrowEl.style.transform = 'rotate(0deg)'; } else { docsEl.style.display = 'none'; arrowEl.style.transform = 'rotate(-90deg)'; } } }); // Auto-load first document in "Start Here" category const startHereDocs = grouped['start-here'] || []; if (startHereDocs.length > 0) { loadDocument(startHereDocs[0].slug); } else if (documents.length > 0) { // Fallback to first available document const firstCategory = sortedCategories.find(([_, cat]) => grouped[cat] && grouped[cat].length > 0); if (firstCategory) { loadDocument(grouped[firstCategory[0]][0].slug); } } } catch (error) { console.error('Error loading documents:', error); document.getElementById('document-list').innerHTML = '
Error loading documents
'; } } // Load specific document let isLoading = false; async function loadDocument(slug) { // Prevent multiple simultaneous loads if (isLoading) return; try { isLoading = true; // Show loading state const contentEl = document.getElementById('document-content'); contentEl.innerHTML = `

Loading document...

`; const response = await fetch(`/api/documents/${slug}`); const data = await response.json(); if (!data.success) { throw new Error(data.error || 'Failed to load document'); } currentDocument = data.document; // Update active state document.querySelectorAll('.doc-link').forEach(el => { if (el.dataset.slug === slug) { el.classList.add('bg-blue-100', 'text-blue-900'); } else { el.classList.remove('bg-blue-100', 'text-blue-900'); } }); // Render with card-based viewer if available and document has sections if (documentCards && currentDocument.sections && currentDocument.sections.length > 0) { documentCards.render(currentDocument); } else { // Fallback to traditional view with header const hasToC = currentDocument.toc && currentDocument.toc.length > 0; let headerHTML = `

${currentDocument.title}

${hasToC ? ` ` : ''}
`; // Remove duplicate title H1 from content (it's already in header) let contentHtml = currentDocument.content_html; const firstH1Match = contentHtml.match(/]*>.*?<\/h1>/); if (firstH1Match) { contentHtml = contentHtml.replace(firstH1Match[0], ''); } contentEl.innerHTML = headerHTML + `
${contentHtml}
`; } // Add ToC button event listener (works for both card and traditional views) setTimeout(() => { const tocButton = document.getElementById('toc-button'); if (tocButton) { tocButton.addEventListener('click', () => openToCModal()); } }, 100); // Scroll to top window.scrollTo({ top: 0, behavior: 'smooth' }); } catch (error) { console.error('Error loading document:', error); document.getElementById('document-content').innerHTML = `

Error loading document

${error.message}

`; } finally { isLoading = false; } } // Open ToC modal function openToCModal() { if (!currentDocument || !currentDocument.toc || currentDocument.toc.length === 0) { return; } const modal = document.getElementById('toc-modal'); if (!modal) return; // Render ToC content const tocContent = document.getElementById('toc-modal-content'); const tocHTML = currentDocument.toc .filter(item => item.level <= 3) // Only show H1, H2, H3 .map(item => { return ` ${item.title} `; }).join(''); tocContent.innerHTML = tocHTML; // Show modal modal.classList.add('show'); // Prevent body scroll and reset modal content scroll document.body.style.overflow = 'hidden'; tocContent.scrollTop = 0; // Add event listeners to ToC links tocContent.querySelectorAll('.toc-link').forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); const targetId = link.getAttribute('href').substring(1); const targetEl = document.getElementById(targetId); if (targetEl) { closeToCModal(); setTimeout(() => { targetEl.scrollIntoView({ behavior: 'smooth', block: 'start' }); }, 200); } }); }); } // Close ToC modal function closeToCModal() { const modal = document.getElementById('toc-modal'); if (modal) { modal.classList.remove('show'); document.body.style.overflow = ''; } } // Initialize loadDocuments(); // Add ESC key listener for closing modal document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { closeToCModal(); } }); // Add close button listener for ToC modal (script loads after DOM, so elements exist) const closeButton = document.getElementById('toc-close-button'); if (closeButton) { closeButton.addEventListener('click', closeToCModal); } // Click outside modal to close const modal = document.getElementById('toc-modal'); if (modal) { modal.addEventListener('click', function(e) { if (e.target === this) { closeToCModal(); } }); }