tractatus/public/js/components/research-papers-modal.js
TheFlow 00b9121ba6 fix: Use correct document slug for taonga paper in research modal
The taonga paper's migrated slug includes the full subtitle. Update the
modal href to match the actual documents collection slug so the
docs-viewer resolves it correctly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 16:33:43 +13:00

171 lines
7.1 KiB
JavaScript

/**
* Research Papers Modal Component
* Displays curated research papers grouped by recency when triggered
* from the homepage hero "Read the Research" button.
*/
class ResearchPapersModal {
constructor() {
this.isOpen = false;
this.papers = [
{
group: 'Recent Papers',
items: [
{
code: 'STO-RES-0010',
title: 'Taonga-Centred Steering Governance',
subtitle: 'Polycentric Authority for Sovereign Small Language Models',
description: 'Proposes polycentric governance replacing platform hierarchy with co-equal steering authorities, drawing on te ao M\u0101ori concepts.',
date: 'February 2026',
status: 'Draft',
href: '/docs-viewer.html?slug=taonga-centred-steering-governance-polycentric-authority-for-sovereign-small-language-models'
},
{
code: 'STO-RES-0009',
title: 'Steering Vectors and Mechanical Bias',
subtitle: 'Inference-Time Debiasing for Sovereign Small Language Models',
description: 'Investigates whether LLM biases operate at a sub-reasoning level and how sovereign SLM deployments enable inference-time debiasing.',
date: 'February 2026',
status: 'v1.1',
href: '/docs-viewer.html?slug=steering-vectors-mechanical-bias-sovereign-ai'
}
]
},
{
group: 'Foundational Papers',
items: [
{
code: 'STO-INN-0003',
title: 'Architectural Alignment',
subtitle: 'Interrupting Neural Reasoning Through Constitutional Inference Gating',
description: 'The core Tractatus paper presenting architectural alignment as a formal specification for interrupted neural reasoning. Available in three editions.',
date: 'January 2026',
status: 'v2.1 \u2014 3 editions',
href: '/architectural-alignment.html'
}
]
}
];
this.init();
}
init() {
this.renderModal();
this.attachEventListeners();
}
renderModal() {
const papersHTML = this.papers.map(group => `
<div class="mb-6 last:mb-0">
<h3 class="text-sm font-semibold text-gray-500 uppercase tracking-wider mb-3">${group.group}</h3>
<div class="space-y-3">
${group.items.map(paper => `
<a href="${paper.href}"
class="block border border-gray-200 rounded-lg p-4 hover:border-blue-300 hover:bg-blue-50/50 transition-colors group">
<div class="flex items-start justify-between gap-3 mb-1">
<h4 class="font-semibold text-gray-900 group-hover:text-blue-700 transition-colors">${paper.title}</h4>
<span class="shrink-0 text-xs font-mono bg-gray-100 text-gray-600 px-2 py-0.5 rounded">${paper.code}</span>
</div>
<p class="text-sm text-gray-600 mb-2">${paper.subtitle}</p>
<p class="text-sm text-gray-500 mb-2">${paper.description}</p>
<div class="flex items-center gap-3 text-xs text-gray-400">
<span>${paper.date}</span>
<span class="inline-block bg-gray-100 text-gray-600 px-2 py-0.5 rounded">${paper.status}</span>
</div>
</a>
`).join('')}
</div>
</div>
`).join('');
const modalHTML = `
<div id="research-papers-modal" class="hidden fixed inset-0 z-50" role="dialog" aria-modal="true" aria-labelledby="research-papers-title">
<div id="research-papers-backdrop" class="absolute inset-0 bg-gray-900/60 backdrop-blur-sm"></div>
<div class="absolute inset-0 flex items-center justify-center p-4">
<div class="bg-white rounded-xl shadow-2xl w-full max-w-2xl relative flex flex-col max-h-[90vh]">
<button id="research-papers-close"
class="absolute top-4 right-4 text-gray-400 hover:text-gray-600 transition-colors z-10"
aria-label="Close research papers modal">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
<div class="p-6 pb-0">
<div class="flex items-center gap-3 mb-1">
<div class="inline-flex items-center justify-center w-10 h-10 bg-blue-100 rounded-lg">
<svg class="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
</svg>
</div>
<h2 id="research-papers-title" class="text-xl font-bold text-gray-900">Research Papers</h2>
</div>
<p class="text-sm text-gray-500 mb-4 ml-13">Published research from the Tractatus project</p>
</div>
<div class="overflow-y-auto px-6 pb-2 flex-1">
${papersHTML}
</div>
<div class="border-t border-gray-100 px-6 py-4">
<a href="/docs.html"
class="flex items-center justify-center gap-2 text-sm font-medium text-blue-600 hover:text-blue-700 transition-colors">
Browse All Documentation
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</a>
</div>
</div>
</div>
</div>
`;
document.body.insertAdjacentHTML('beforeend', modalHTML);
}
attachEventListeners() {
const backdrop = document.getElementById('research-papers-backdrop');
const closeBtn = document.getElementById('research-papers-close');
backdrop.addEventListener('click', () => this.close());
closeBtn.addEventListener('click', () => this.close());
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && this.isOpen) {
this.close();
}
});
document.addEventListener('click', (e) => {
if (e.target.closest('[data-research-papers-trigger]')) {
e.preventDefault();
this.open();
}
});
}
open() {
const modal = document.getElementById('research-papers-modal');
modal.classList.remove('hidden');
this.isOpen = true;
document.body.classList.add('overflow-hidden');
}
close() {
const modal = document.getElementById('research-papers-modal');
modal.classList.add('hidden');
this.isOpen = false;
document.body.classList.remove('overflow-hidden');
}
}
// Auto-initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
window.researchPapersModal = new ResearchPapersModal();
});
} else {
window.researchPapersModal = new ResearchPapersModal();
}