- 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>
181 lines
8.2 KiB
JavaScript
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();
|
|
}
|
|
|
|
})();
|