let documents = []; let currentDocument = null; let documentCards = null; let currentLanguage = 'en'; // Default language // Active sidebar filters const activeFilters = { document_type: '', audience: '' }; // UI translations for full page i18n const UI_TRANSLATIONS = { en: { pageTitle: 'Framework Documentation', pageSubtitle: 'Technical specifications, guides, and reference materials', documentsHeading: 'Documents', searchButton: 'Search', backToDocuments: 'Back to Documents', selectDocument: 'Select a Document', selectDocumentDesc: 'Choose a document from the sidebar to begin reading', loadingDocument: 'Loading document...', errorLoadingDoc: 'Error loading document', tableOfContents: 'Table of Contents', downloadPdf: 'Download PDF', sourceCode: 'Source Code', sovereignRepo: 'Sovereign Repository', sovereignRepoDesc: 'Source code hosted on sovereign infrastructure', implGuide: 'Implementation Guide', implGuideDesc: 'Architecture, deployment & integration', categories: { 'getting-started': 'Getting Started', 'resources': 'Resources', 'research-theory': 'Research & Theory', 'technical-reference': 'Technical Reference', 'advanced-topics': 'Advanced Topics', 'business-leadership': 'Business & Leadership' }, filters: { allTypes: 'All types', allAudiences: 'All audiences', clear: 'Clear', showingDocs: '{count} documents', documentTypes: { 'working-paper': 'Working paper', 'case-study': 'Case study', 'technical-report': 'Technical report', 'guide': 'Guide', 'reference': 'Reference', 'brief': 'Brief' }, audiences: { 'researcher': 'Researcher', 'implementer': 'Implementer', 'leader': 'Leader', 'general': 'General' } } }, de: { pageTitle: 'Framework-Dokumentation', pageSubtitle: 'Technische Spezifikationen, Leitfäden und Referenzmaterialien', documentsHeading: 'Dokumente', searchButton: 'Suchen', backToDocuments: 'Zurück zu Dokumenten', selectDocument: 'Dokument auswählen', selectDocumentDesc: 'Wählen Sie ein Dokument aus der Seitenleiste, um mit dem Lesen zu beginnen', loadingDocument: 'Dokument wird geladen...', errorLoadingDoc: 'Fehler beim Laden des Dokuments', tableOfContents: 'Inhaltsverzeichnis', downloadPdf: 'PDF herunterladen', sourceCode: 'Source Code', publicRepository: 'Öffentliches Repository', publicRepositoryDesc: 'Quellcode, Beispiele und Beiträge', readmeQuickStart: 'README & Schnellstart', readmeQuickStartDesc: 'Installation und Einstiegsanleitung', categories: { 'getting-started': 'Erste Schritte', 'resources': 'Ressourcen', 'research-theory': 'Forschung & Theorie', 'technical-reference': 'Technische Referenz', 'advanced-topics': 'Fortgeschrittene Themen', 'business-leadership': 'Business & Führung' }, filters: { allTypes: 'Alle Typen', allAudiences: 'Alle Zielgruppen', clear: 'Zurücksetzen', showingDocs: '{count} Dokumente', documentTypes: { 'working-paper': 'Arbeitspapier', 'case-study': 'Fallstudie', 'technical-report': 'Technischer Bericht', 'guide': 'Leitfaden', 'reference': 'Referenz', 'brief': 'Kurzfassung' }, audiences: { 'researcher': 'Forscher', 'implementer': 'Umsetzer', 'leader': 'Führungskraft', 'general': 'Allgemein' } } }, fr: { pageTitle: 'Documentation du Framework', pageSubtitle: 'Spécifications techniques, guides et matériels de référence', documentsHeading: 'Documents', searchButton: 'Rechercher', backToDocuments: 'Retour aux documents', selectDocument: 'Sélectionner un document', selectDocumentDesc: 'Choisissez un document dans la barre latérale pour commencer la lecture', loadingDocument: 'Chargement du document...', errorLoadingDoc: 'Erreur lors du chargement du document', tableOfContents: 'Table des matières', downloadPdf: 'Télécharger PDF', sourceCode: 'Source Code', publicRepository: 'Dépôt public', publicRepositoryDesc: 'Code source, exemples et contributions', readmeQuickStart: 'README & Démarrage rapide', readmeQuickStartDesc: 'Guide d\'installation et de démarrage', categories: { 'getting-started': 'Premiers pas', 'resources': 'Ressources', 'research-theory': 'Recherche & Théorie', 'technical-reference': 'Référence technique', 'advanced-topics': 'Sujets avancés', 'business-leadership': 'Business & Leadership' }, filters: { allTypes: 'Tous les types', allAudiences: 'Tous les publics', clear: 'Effacer', showingDocs: '{count} documents', documentTypes: { 'working-paper': 'Document de travail', 'case-study': 'Étude de cas', 'technical-report': 'Rapport technique', 'guide': 'Guide', 'reference': 'Référence', 'brief': 'Résumé' }, audiences: { 'researcher': 'Chercheur', 'implementer': 'Implémenteur', 'leader': 'Dirigeant', 'general': 'Général' } } }, mi: { pageTitle: 'Ngā Tuhinga Anga', pageSubtitle: 'Ngā tautukunga hangarau, ngā aratohu, me ngā rauemi tohutoro', documentsHeading: 'Ngā Tuhinga', searchButton: 'Rapu', backToDocuments: 'Hoki ki ngā Tuhinga', selectDocument: 'Kōwhiria he Tuhinga', selectDocumentDesc: 'Kōwhiria he tuhinga mai i te taha ki te tīmata pānui', loadingDocument: 'Kei te uta te tuhinga...', errorLoadingDoc: 'He hapa i te utanga o te tuhinga', tableOfContents: 'Rārangi Kaupapa', downloadPdf: 'Tikiake PDF', sourceCode: 'Source Code', publicRepository: 'Pūtahi Tūmatanui', publicRepositoryDesc: 'Waehere pūtake, tauira me ngā koha', readmeQuickStart: 'README & Tīmata Tere', readmeQuickStartDesc: 'Aratohu whakauru me te tīmata', categories: { 'getting-started': 'Tīmata', 'resources': 'Ngā Rauemi', 'research-theory': 'Rangahau & Ariā', 'technical-reference': 'Tohutoro Hangarau', 'advanced-topics': 'Kaupapa Matatau', 'business-leadership': 'Pakihi & Hautūtanga' }, filters: { allTypes: 'Ngā momo katoa', allAudiences: 'Ngā hunga katoa', clear: 'Ūkui', showingDocs: '{count} tuhinga', documentTypes: { 'working-paper': 'Pepa mahi', 'case-study': 'Rangahau āhua', 'technical-report': 'Pūrongo hangarau', 'guide': 'Aratohu', 'reference': 'Tohutoro', 'brief': 'Whakarāpopototanga' }, audiences: { 'researcher': 'Kairangahau', 'implementer': 'Kaiwhakatinana', 'leader': 'Kaihautū', 'general': 'Whānui' } } } }; // Get current UI translations function getUITranslations(lang = currentLanguage) { return UI_TRANSLATIONS[lang] || UI_TRANSLATIONS.en; } // Update all page UI elements with current language translations function updatePageUI(lang = currentLanguage) { const t = getUITranslations(lang); // Update page header const allPageTitles = Object.values(UI_TRANSLATIONS).map(t => t.pageTitle); const pageTitle = document.querySelector('h1'); if (pageTitle && allPageTitles.includes(pageTitle.textContent)) { pageTitle.textContent = t.pageTitle; } const allPageSubtitles = Object.values(UI_TRANSLATIONS).map(t => t.pageSubtitle); const pageSubtitle = document.querySelector('.text-gray-600.mt-2'); if (pageSubtitle && allPageSubtitles.includes(pageSubtitle.textContent)) { pageSubtitle.textContent = t.pageSubtitle; } // Update search button const searchBtn = document.querySelector('#open-search-modal-btn span'); if (searchBtn) { searchBtn.textContent = t.searchButton; } // Update sidebar Documents heading const allDocsHeadings = Object.values(UI_TRANSLATIONS).map(t => t.documentsHeading); const docsHeading = document.querySelector('aside h3'); if (docsHeading && allDocsHeadings.includes(docsHeading.textContent)) { docsHeading.textContent = t.documentsHeading; } // Update Source Code section heading const allSourceLabels = Object.values(UI_TRANSLATIONS).map(t => t.sourceCode); const sourceHeadings = document.querySelectorAll('aside h3'); sourceHeadings.forEach(heading => { if (allSourceLabels.includes(heading.textContent.trim())) { const textNode = Array.from(heading.childNodes).find(node => node.nodeType === Node.TEXT_NODE); if (textNode) { textNode.textContent = t.sourceCode; } } }); // Update Source Code links const allRepoTitles = Object.values(UI_TRANSLATIONS).map(t => t.sovereignRepo); const allReadmeTitles = Object.values(UI_TRANSLATIONS).map(t => t.implGuide); const allRepoDescs = Object.values(UI_TRANSLATIONS).map(t => t.sovereignRepoDesc); const allReadmeDescs = Object.values(UI_TRANSLATIONS).map(t => t.implGuideDesc); const sourceLinks = document.querySelectorAll('aside a[href*="source-code"]'); sourceLinks.forEach(link => { const titleDiv = link.querySelector('.text-sm.font-medium'); const descDiv = link.querySelector('.text-xs.text-gray-500'); if (titleDiv) { if (allRepoTitles.includes(titleDiv.textContent)) { titleDiv.textContent = t.sovereignRepo; } else if (allReadmeTitles.includes(titleDiv.textContent)) { titleDiv.textContent = t.implGuide; } } if (descDiv) { if (allRepoDescs.includes(descDiv.textContent)) { descDiv.textContent = t.sovereignRepoDesc; } else if (allReadmeDescs.includes(descDiv.textContent)) { descDiv.textContent = t.implGuideDesc; } } }); // Update Back to Documents bar label const backLabel = document.querySelector('#back-to-docs-btn [data-back-label]'); if (backLabel) { backLabel.textContent = t.documentsHeading; } // Update Select Document placeholder (if visible) const selectDocHeading = document.getElementById('select-document-heading'); if (selectDocHeading) { selectDocHeading.textContent = t.selectDocument; } const selectDocDesc = document.getElementById('select-document-desc'); if (selectDocDesc) { selectDocDesc.textContent = t.selectDocumentDesc; } // Update page title tag document.title = `${t.pageTitle} | Tractatus AI Safety`; // Update filter dropdown labels populateFilters(); updateFilterBar(); } // Initialize card-based viewer if (typeof DocumentCards !== 'undefined') { documentCards = new DocumentCards('document-content'); } // Detect language from URL, localStorage, or i18n system function detectLanguage() { // Priority 1: URL parameter const urlParams = new URLSearchParams(window.location.search); const urlLang = urlParams.get('lang'); if (urlLang) { return urlLang; } // Priority 2: localStorage const storedLang = localStorage.getItem('tractatus-lang'); if (storedLang) { return storedLang; } // Priority 3: i18n system if (window.I18n && window.I18n.currentLang) { return window.I18n.currentLang; } // Default: English return 'en'; } // Update URL with language parameter function updateURL(slug, lang) { const url = new URL(window.location); url.searchParams.set('doc', slug); if (lang && lang !== 'en') { url.searchParams.set('lang', lang); } else { url.searchParams.delete('lang'); } window.history.pushState({}, '', url); } // Listen for language changes from i18n system if (typeof window !== 'undefined') { window.addEventListener('languageChanged', async (e) => { const newLang = e.detail.language; currentLanguage = newLang; // Update page UI (hero section, sidebar headings, etc.) updatePageUI(newLang); // Remember current document slug before reloading list const currentSlug = currentDocument ? currentDocument.slug : null; // Update URL lang parameter BEFORE reloading documents // This ensures detectLanguage() reads the correct language from URL if (currentSlug) { updateURL(currentSlug, newLang); } // Reload document list to show translated category labels and document titles await loadDocuments(); // Explicitly reload current document to ensure it updates // (loadDocuments auto-loads from URL, but explicit call ensures it happens) if (currentSlug) { await loadDocument(currentSlug, newLang); } }); // Initialize language on i18n ready window.addEventListener('i18nInitialized', (e) => { currentLanguage = e.detail.language; }); } // Document categorization - Final 5 categories (curated for public docs) const CATEGORIES = { 'getting-started': { icon: '📚', description: 'Introduction, core concepts, and glossary', order: 1, color: 'blue', bgColor: 'bg-blue-50', borderColor: 'border-l-4 border-blue-500', textColor: 'text-blue-700', collapsed: false }, 'resources': { icon: '📖', description: 'Implementation guides and reference materials', order: 2, color: 'amber', bgColor: 'bg-amber-50', borderColor: 'border-l-4 border-amber-500', textColor: 'text-amber-700', collapsed: false }, 'research-theory': { icon: '🔬', description: 'Research papers, case studies, theoretical foundations', order: 3, color: 'purple', bgColor: 'bg-purple-50', borderColor: 'border-l-4 border-purple-500', textColor: 'text-purple-700', collapsed: true }, 'technical-reference': { icon: '🔌', description: 'API documentation, code examples, architecture', order: 4, color: 'green', bgColor: 'bg-green-50', borderColor: 'border-l-4 border-green-500', textColor: 'text-green-700', collapsed: true }, 'advanced-topics': { icon: '🎓', description: 'Value pluralism, organizational theory, advanced concepts', order: 5, color: 'teal', bgColor: 'bg-teal-50', borderColor: 'border-l-4 border-teal-500', textColor: 'text-teal-700', collapsed: true }, 'business-leadership': { icon: '💼', description: 'Business cases, ROI analysis, executive briefs', order: 6, color: 'pink', bgColor: 'bg-pink-50', borderColor: 'border-l-4 border-pink-500', textColor: 'text-pink-700', collapsed: true } }; // 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 using database category field // New granular category system for better document organization function categorizeDocument(doc) { const slug = doc.slug.toLowerCase(); // Skip hidden documents if (HIDDEN_DOCS.some(hidden => slug.includes(hidden))) { return null; } // Use category from database const category = doc.category || 'resources'; // Validate category exists in CATEGORIES constant if (CATEGORIES[category]) { return category; } // Fallback to resources for uncategorized console.warn(`Document "${doc.title}" has invalid category "${category}", using fallback to resources`); return 'resources'; } // Group documents by category function groupDocuments(docs) { const grouped = {}; // Initialize all categories Object.keys(CATEGORIES).forEach(key => { grouped[key] = []; }); // Categorize each document (already sorted by order from API) 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' : ''; // Get translated title if available and language is not English let displayTitle = doc.title; if (currentLanguage !== 'en' && doc.translations && doc.translations[currentLanguage]) { displayTitle = doc.translations[currentLanguage].title || doc.title; } // Determine if PDF download is available and get PDF path // First check if document has explicit download_formats.pdf let pdfPath = null; let hasPDF = false; if (doc.download_formats && doc.download_formats.pdf) { pdfPath = doc.download_formats.pdf; hasPDF = true; } // Add download button styling const paddingClass = hasPDF ? 'pr-10' : 'pr-3'; return `
`; } // Populate filter dropdowns with translated labels from loaded documents function populateFilters() { const t = getUITranslations(currentLanguage); const f = t.filters || {}; // Document type dropdown const typeSelect = document.getElementById('filter-doc-type'); if (typeSelect) { const types = [...new Set(documents.map(d => d.document_type).filter(Boolean))].sort(); const currentVal = typeSelect.value; typeSelect.innerHTML = ``; types.forEach(type => { const label = (f.documentTypes && f.documentTypes[type]) || type; typeSelect.innerHTML += ``; }); typeSelect.value = currentVal || activeFilters.document_type; } // Audience dropdown const audSelect = document.getElementById('filter-doc-audience'); if (audSelect) { const audiences = [...new Set(documents.map(d => d.audience).filter(Boolean))].sort(); const currentVal = audSelect.value; audSelect.innerHTML = ``; audiences.forEach(aud => { const label = (f.audiences && f.audiences[aud]) || aud; audSelect.innerHTML += ``; }); audSelect.value = currentVal || activeFilters.audience; } // Clear button label const clearBtn = document.getElementById('clear-sidebar-filters'); if (clearBtn) { clearBtn.textContent = f.clear || 'Clear'; } } // Apply active filters to documents array function getFilteredDocuments() { return documents.filter(doc => { if (activeFilters.document_type && doc.document_type !== activeFilters.document_type) return false; if (activeFilters.audience && doc.audience !== activeFilters.audience) return false; return true; }); } // Update the active filters bar (count + clear button visibility) function updateFilterBar() { const bar = document.getElementById('active-filters-bar'); const countEl = document.getElementById('filter-result-count'); if (!bar || !countEl) return; const hasFilters = activeFilters.document_type || activeFilters.audience; if (hasFilters) { const filtered = getFilteredDocuments(); const t = getUITranslations(currentLanguage); const f = t.filters || {}; const template = f.showingDocs || '{count} documents'; countEl.textContent = template.replace('{count}', filtered.length); bar.classList.remove('hidden'); bar.classList.add('flex'); } else { bar.classList.add('hidden'); bar.classList.remove('flex'); } } // Re-render the document list with current filters applied function renderFilteredDocuments() { const filtered = getFilteredDocuments(); const grouped = groupDocuments(filtered); const listEl = document.getElementById('document-list'); if (!listEl) return; let html = ''; const sortedCategories = Object.entries(CATEGORIES).sort((a, b) => a[1].order - b[1].order); const t = getUITranslations(currentLanguage); sortedCategories.forEach(([categoryId, category]) => { const docs = grouped[categoryId] || []; if (docs.length === 0) return; const categoryLabel = t.categories[categoryId] || categoryId; html += `${t.loadingDocument}
${error.message}