tractatus/public/js/components/footer.js
TheFlow 7a4603b3a6 feat(researcher): WCAG compliance, Berlin/Weil foundations, fixed footer i18n
- Full WCAG accessibility: ARIA attributes (aria-expanded, aria-controls), keyboard navigation (Enter/Space)
- Reframed research context: Berlin/Weil as primary intellectual foundation (moral pluralism, categorical imperative)
- Bibliography with proper academic citations: Weil (The Need for Roots, Gravity and Grace), Berlin (Four Essays on Liberty)
- Fixed footer i18n: Implemented recursive deepMerge() to preserve nested translation objects
- Root cause: Shallow merge {...obj1, ...obj2} was overwriting entire footer object from common.json
- Consolidated all footer translations in common.json, removed from page-specific files
- Mobile optimization: 44px/48px touch targets, touch-action: manipulation, responsive design
- Progressive enhancement: <noscript> fallback for JavaScript-disabled users
- Version 1.3.0

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-22 23:56:37 +13:00

181 lines
8.2 KiB
JavaScript

/**
* Footer Component - i18n-enabled
* Shared footer for all Tractatus pages with language persistence
*/
(function() {
'use strict';
class TractatusFooter {
constructor() {
this.init();
}
init() {
console.log('[Footer] Initializing...');
// Listen for i18n initialization event (fired by i18n-simple.js)
if (window.I18n && window.I18n.translations && window.I18n.translations.footer) {
// i18n already loaded
console.log('[Footer] i18n already loaded, rendering immediately');
this.render();
this.attachEventListeners();
} else {
// Wait for i18nInitialized event
console.log('[Footer] Waiting for i18nInitialized event...');
window.addEventListener('i18nInitialized', () => {
console.log('[Footer] i18n initialized event received');
// Double-check translations loaded
if (window.I18n && window.I18n.translations && window.I18n.translations.footer) {
console.log('[Footer] Footer translations confirmed, rendering');
this.render();
this.attachEventListeners();
} else {
console.error('[Footer] Event fired but no footer translations:', window.I18n?.translations);
// Render anyway
this.render();
this.attachEventListeners();
}
}, { once: true });
}
}
render() {
const currentYear = new Date().getFullYear();
// Create footer HTML with data-i18n attributes
const footerHTML = `
<footer class="bg-gray-900 text-gray-300 mt-16" role="contentinfo">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<!-- Main Footer Content -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-8 mb-8">
<!-- About -->
<div>
<h3 class="text-white font-semibold mb-4" data-i18n="footer.about_heading">Tractatus Framework</h3>
<p class="text-sm text-gray-400" data-i18n="footer.about_text">
Architectural constraints for AI safety that preserve human agency through structural, not aspirational, enforcement.
</p>
</div>
<!-- Documentation -->
<div>
<h3 class="text-white font-semibold mb-4" data-i18n="footer.documentation_heading">Documentation</h3>
<ul class="space-y-2 text-sm">
<li><a href="/docs.html" class="hover:text-white transition" data-i18n="footer.documentation_links.framework_docs">Framework Docs</a></li>
<li><a href="/about.html" class="hover:text-white transition" data-i18n="footer.documentation_links.about">About</a></li>
<li><a href="/about/values.html" class="hover:text-white transition" data-i18n="footer.documentation_links.core_values">Core Values</a></li>
<li><a href="/demos/27027-demo.html" class="hover:text-white transition" data-i18n="footer.documentation_links.interactive_demo">Interactive Demo</a></li>
</ul>
</div>
<!-- Support -->
<div>
<h3 class="text-white font-semibold mb-4" data-i18n="footer.support_heading">Support</h3>
<ul class="space-y-2 text-sm">
<li><a href="/koha.html" class="hover:text-white transition" data-i18n="footer.support_links.koha">Support (Koha)</a></li>
<li><a href="/koha/transparency.html" class="hover:text-white transition" data-i18n="footer.support_links.transparency">Transparency</a></li>
<li><a href="/media-inquiry.html" class="hover:text-white transition" data-i18n="footer.support_links.media_inquiries">Media Inquiries</a></li>
<li><a href="/case-submission.html" class="hover:text-white transition" data-i18n="footer.support_links.submit_case">Submit Case Study</a></li>
</ul>
</div>
<!-- Legal & Contact -->
<div>
<h3 class="text-white font-semibold mb-4" data-i18n="footer.legal_heading">Legal</h3>
<ul class="space-y-2 text-sm">
<li><a href="/privacy.html" class="hover:text-white transition" data-i18n="footer.legal_links.privacy">Privacy Policy</a></li>
<li><a href="mailto:hello@agenticgovernance.digital" class="hover:text-white transition" data-i18n="footer.legal_links.contact">Contact Us</a></li>
<li><a href="https://github.com/AgenticGovernance/tractatus-framework" class="hover:text-white transition" target="_blank" rel="noopener">GitHub</a></li>
</ul>
</div>
</div>
<!-- Divider -->
<div class="border-t border-gray-800 pt-8">
<!-- Te Tiriti Acknowledgement -->
<div class="mb-6">
<p class="text-sm text-gray-400">
<strong class="text-gray-300" data-i18n="footer.te_tiriti_label">Te Tiriti o Waitangi:</strong>
<span data-i18n="footer.te_tiriti_text">We acknowledge Te Tiriti o Waitangi and our commitment to partnership, protection, and participation. This project respects Māori data sovereignty (rangatiratanga) and collective guardianship (kaitiakitanga).</span>
</p>
</div>
<!-- Bottom Row -->
<div class="flex flex-col md:flex-row justify-between items-center gap-4 text-sm">
<p class="text-gray-400">
© ${currentYear} <span data-i18n="footer.copyright">John G Stroh. Licensed under</span> <a href="https://www.apache.org/licenses/LICENSE-2.0" class="text-blue-400 hover:text-blue-300 transition" target="_blank" rel="noopener"><span data-i18n="footer.license">Apache 2.0</span></a>.
</p>
<p class="text-gray-400" data-i18n="footer.location">
Made in Aotearoa New Zealand 🇳🇿
</p>
</div>
</div>
</div>
</footer>
`;
// Insert footer at end of body
const existingFooter = document.querySelector('footer[role="contentinfo"]');
if (existingFooter) {
existingFooter.outerHTML = footerHTML;
} else if (document.body) {
document.body.insertAdjacentHTML('beforeend', footerHTML);
} else {
// If body not ready, wait for DOM
document.addEventListener('DOMContentLoaded', () => {
document.body.insertAdjacentHTML('beforeend', footerHTML);
this.applyFooterTranslations();
});
return; // Exit early if DOM not ready
}
// Apply translations after DOM update
this.applyFooterTranslations();
}
applyFooterTranslations() {
// Use double requestAnimationFrame to ensure DOM is fully painted
requestAnimationFrame(() => {
requestAnimationFrame(() => {
if (window.I18n && window.I18n.applyTranslations) {
console.log('[Footer] Applying translations...');
console.log('[Footer] Footer exists:', !!document.querySelector('footer[role="contentinfo"]'));
console.log('[Footer] Elements with data-i18n:', document.querySelectorAll('footer[role="contentinfo"] [data-i18n]').length);
window.I18n.applyTranslations();
console.log('[Footer] Translations applied');
// Verify a sample translation
const aboutHeading = document.querySelector('footer [data-i18n="footer.about_heading"]');
if (aboutHeading) {
console.log('[Footer] about_heading element text:', aboutHeading.innerHTML);
}
} else {
console.warn('[Footer] I18n not available for translation');
}
});
});
}
attachEventListeners() {
// Listen for language changes and re-render footer
window.addEventListener('languageChanged', (event) => {
console.log('[Footer] Language changed to:', event.detail.language);
this.render();
});
}
}
// Auto-initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => new TractatusFooter());
} else {
new TractatusFooter();
}
})();