From 240cc7d3a943d7b1dfd98f350162332987b3e0f3 Mon Sep 17 00:00:00 2001 From: TheFlow Date: Fri, 17 Apr 2026 07:15:44 +1200 Subject: [PATCH] feat: reading-mode toggle for long research papers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three density modes: Overview (~3 min), Standard (~12 min), Deep (~25 min). Applied to researcher.html — 7 sections tagged with data-reading-level. Persists user preference in localStorage. Auto-injects toggle UI. Reusable component for architectural-alignment papers. Co-Authored-By: Claude Opus 4.6 (1M context) --- public/css/reading-mode.css | 54 +++++++++++++++++ public/js/reading-mode.js | 114 ++++++++++++++++++++++++++++++++++++ public/researcher.html | 18 +++--- 3 files changed, 179 insertions(+), 7 deletions(-) create mode 100644 public/css/reading-mode.css create mode 100644 public/js/reading-mode.js diff --git a/public/css/reading-mode.css b/public/css/reading-mode.css new file mode 100644 index 00000000..9ded19fc --- /dev/null +++ b/public/css/reading-mode.css @@ -0,0 +1,54 @@ +.reading-mode-toggle { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.75rem 1.5rem; + margin: 1rem auto 2rem; + max-width: 900px; + background: #f8fafc; + border: 1px solid #e2e8f0; + border-radius: 0.5rem; + font-size: 0.875rem; +} + +.reading-mode-label { + color: #64748b; + font-weight: 500; + margin-right: 0.25rem; +} + +.reading-mode-btn { + padding: 0.375rem 0.75rem; + border: 1px solid #cbd5e1; + border-radius: 0.375rem; + background: white; + color: #475569; + cursor: pointer; + font-size: 0.8rem; + font-weight: 500; + transition: all 0.2s; +} + +.reading-mode-btn:hover { + background: #f1f5f9; + border-color: #94a3b8; +} + +.reading-mode-btn.active { + background: #1e40af; + color: white; + border-color: #1e40af; +} + +.reading-mode-estimate { + margin-left: auto; + color: #94a3b8; + font-size: 0.8rem; +} + +@media (max-width: 640px) { + .reading-mode-toggle { + flex-wrap: wrap; + padding: 0.5rem 1rem; + } +} diff --git a/public/js/reading-mode.js b/public/js/reading-mode.js new file mode 100644 index 00000000..e1b01353 --- /dev/null +++ b/public/js/reading-mode.js @@ -0,0 +1,114 @@ +/** + * Reading Mode Toggle + * Provides overview/standard/deep density modes for long research papers. + * + * Usage: Add data-reading-level="overview|standard|deep" to content sections. + * Sections without the attribute are always visible. + * + * The toggle is auto-injected at the top of any page that includes this script + * and has elements with data-reading-level attributes. + */ +(function() { + 'use strict'; + + var STORAGE_KEY = 'tractatus-reading-mode'; + var MODES = ['overview', 'standard', 'deep']; + var MODE_LABELS = { + overview: { label: 'Overview', desc: 'Key points only' }, + standard: { label: 'Standard', desc: 'Main content' }, + deep: { label: 'Deep', desc: 'Full detail' } + }; + + function init() { + // Only activate if page has reading-level content + var leveledContent = document.querySelectorAll('[data-reading-level]'); + if (leveledContent.length === 0) return; + + var savedMode = localStorage.getItem(STORAGE_KEY) || 'standard'; + + // Inject toggle UI after the first h1 or at the top of main content + var heroOrH1 = document.querySelector('.hero-section, .page-hero, h1'); + var insertPoint = (heroOrH1 && heroOrH1.parentElement) + || document.querySelector('main') + || document.body; + + var toggle = document.createElement('div'); + toggle.className = 'reading-mode-toggle'; + toggle.setAttribute('role', 'radiogroup'); + toggle.setAttribute('aria-label', 'Reading depth'); + toggle.innerHTML = 'Reading depth:' + + MODES.map(function(mode) { + return ''; + }).join('') + + ''; + + // Insert after the hero/h1 section + if (insertPoint.nextSibling) { + insertPoint.parentNode.insertBefore(toggle, insertPoint.nextSibling); + } else { + insertPoint.parentNode.appendChild(toggle); + } + + // Apply initial mode + applyMode(savedMode); + + // Handle clicks + toggle.addEventListener('click', function(e) { + var btn = e.target.closest('.reading-mode-btn'); + if (!btn) return; + + var mode = btn.dataset.mode; + localStorage.setItem(STORAGE_KEY, mode); + + // Update button states + var buttons = toggle.querySelectorAll('.reading-mode-btn'); + for (var i = 0; i < buttons.length; i++) { + var b = buttons[i]; + if (b.dataset.mode === mode) { + b.classList.add('active'); + b.setAttribute('aria-checked', 'true'); + } else { + b.classList.remove('active'); + b.setAttribute('aria-checked', 'false'); + } + } + + applyMode(mode); + }); + } + + function applyMode(mode) { + var elements = document.querySelectorAll('[data-reading-level]'); + var modeIndex = MODES.indexOf(mode); + + for (var i = 0; i < elements.length; i++) { + var el = elements[i]; + var level = el.dataset.readingLevel; + var levelIndex = MODES.indexOf(level); + // overview shows only overview; standard shows overview+standard; deep shows all + var visible = levelIndex <= modeIndex; + el.style.display = visible ? '' : 'none'; + el.setAttribute('aria-hidden', !visible); + } + + // Update reading time estimate if present + var counter = document.querySelector('.reading-mode-estimate'); + if (counter) { + var labels = { overview: '~3 min read', standard: '~12 min read', deep: '~25 min read' }; + counter.textContent = labels[mode] || ''; + } + } + + // Init on DOM ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } +})(); diff --git a/public/researcher.html b/public/researcher.html index 6960c903..b25472c0 100644 --- a/public/researcher.html +++ b/public/researcher.html @@ -38,6 +38,7 @@ +