diff --git a/kjvstudy_org/static/base.js b/kjvstudy_org/static/base.js index 519a5fa..e5fad6d 100644 --- a/kjvstudy_org/static/base.js +++ b/kjvstudy_org/static/base.js @@ -12,6 +12,31 @@ function toggleDarkMode() { localStorage.setItem('theme', newTheme); } +// Font size functionality +(function() { + const sizes = ['small', 'normal', 'large', 'x-large']; + const savedSize = localStorage.getItem('fontSize') || 'normal'; + if (savedSize !== 'normal') { + document.documentElement.setAttribute('data-font-size', savedSize); + } +})(); + +function changeFontSize(direction) { + const sizes = ['small', 'normal', 'large', 'x-large']; + const currentSize = document.documentElement.getAttribute('data-font-size') || 'normal'; + const currentIndex = sizes.indexOf(currentSize); + const newIndex = Math.max(0, Math.min(sizes.length - 1, currentIndex + direction)); + const newSize = sizes[newIndex]; + + if (newSize === 'normal') { + document.documentElement.removeAttribute('data-font-size'); + localStorage.removeItem('fontSize'); + } else { + document.documentElement.setAttribute('data-font-size', newSize); + localStorage.setItem('fontSize', newSize); + } +} + // Red letter toggle functionality (function() { // Check for saved red letter preference or default to enabled @@ -34,6 +59,41 @@ function toggleRedLetters() { } } +// Sticky header on scroll +(function() { + var stickyHeader = document.getElementById('sticky-header'); + var breadcrumb = document.querySelector('.breadcrumb'); + if (!stickyHeader || !breadcrumb) return; + + var lastScrollY = 0; + var ticking = false; + var threshold = 150; // Show after scrolling 150px past breadcrumb + + function updateStickyHeader() { + var breadcrumbBottom = breadcrumb.getBoundingClientRect().bottom; + var scrollY = window.scrollY; + + // Show sticky header when breadcrumb is scrolled out of view + if (breadcrumbBottom < -threshold) { + stickyHeader.classList.add('visible'); + stickyHeader.setAttribute('aria-hidden', 'false'); + } else { + stickyHeader.classList.remove('visible'); + stickyHeader.setAttribute('aria-hidden', 'true'); + } + + ticking = false; + } + + window.addEventListener('scroll', function() { + lastScrollY = window.scrollY; + if (!ticking) { + window.requestAnimationFrame(updateStickyHeader); + ticking = true; + } + }, { passive: true }); +})(); + // Sidebar collapse state persistence (mobile only) (function() { var toggle = document.getElementById('sidebar-toggle'); @@ -1410,3 +1470,45 @@ function showKeyboardHelp() { }); })(); +// Prefetch next/prev pages for faster navigation +(function() { + // Wait for page to be fully loaded and idle + if ('requestIdleCallback' in window) { + requestIdleCallback(prefetchNavigationLinks, { timeout: 2000 }); + } else { + setTimeout(prefetchNavigationLinks, 1000); + } + + function prefetchNavigationLinks() { + // Find navigation links (next/prev chapter, verse, book) + var prefetchSelectors = [ + 'a[rel="next"]', + 'a[rel="prev"]', + '.chapter-nav a', + '.verse-nav a', + '.book-nav a', + '.nav-btn[href*="/chapter/"]', + '.nav-btn[href*="/verse/"]' + ]; + + var links = document.querySelectorAll(prefetchSelectors.join(', ')); + var prefetched = new Set(); + + links.forEach(function(link) { + var href = link.href; + if (!href || prefetched.has(href)) return; + if (href.includes('#')) return; // Skip anchor links + if (!href.startsWith(window.location.origin)) return; // Skip external links + + prefetched.add(href); + + // Use link prefetch hint + var prefetchLink = document.createElement('link'); + prefetchLink.rel = 'prefetch'; + prefetchLink.href = href; + prefetchLink.as = 'document'; + document.head.appendChild(prefetchLink); + }); + } +})(); + diff --git a/kjvstudy_org/templates/base.html b/kjvstudy_org/templates/base.html index 801ca0b..a7c60b7 100644 --- a/kjvstudy_org/templates/base.html +++ b/kjvstudy_org/templates/base.html @@ -346,6 +346,184 @@ padding-bottom: 1rem; } + /* Sticky header on scroll */ + .sticky-header { + position: fixed; + top: 0; + left: 140px; + right: 0; + background: var(--bg-color); + border-bottom: 1px solid var(--border-color); + padding: 0.6rem 2rem; + z-index: 99; + transform: translateY(-100%); + transition: transform 0.2s ease; + box-shadow: 0 2px 8px rgba(0,0,0,0.05); + } + + .sticky-header.visible { + transform: translateY(0); + } + + .sticky-header-content { + display: flex; + align-items: center; + justify-content: space-between; + max-width: 800px; + gap: 1rem; + } + + .sticky-header-title { + font-size: 0.95rem; + font-weight: 500; + color: var(--text-color); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .sticky-header-breadcrumb { + font-size: 0.8rem; + color: var(--text-secondary); + display: flex; + align-items: center; + gap: 0.5rem; + } + + .sticky-header-breadcrumb a { + color: var(--text-secondary); + text-decoration: none; + border-bottom: none; + } + + .sticky-header-breadcrumb a:hover { + color: var(--link-color); + } + + .sticky-header-breadcrumb .sep { + color: var(--text-quaternary); + } + + [data-theme="dark"] .sticky-header { + box-shadow: 0 2px 8px rgba(0,0,0,0.2); + } + + @media (max-width: 1200px) { + .sticky-header { + left: 0; + } + } + + /* Font size controls */ + .font-size-controls { + position: fixed; + top: 2rem; + right: 4rem; + z-index: 101; + margin: -25px 0 0 0; + display: flex; + gap: 0.25rem; + } + + .font-size-btn { + width: 24px; + height: 24px; + cursor: pointer; + background: transparent; + border: 1px solid var(--border-color-darker); + border-radius: 3px; + display: flex; + align-items: center; + justify-content: center; + font-size: 0.75rem; + font-weight: 600; + color: var(--text-secondary); + transition: all 0.2s; + font-family: inherit; + } + + .font-size-btn:hover { + border-color: var(--text-tertiary); + color: var(--link-hover); + } + + .font-size-btn.decrease { + font-size: 0.65rem; + } + + .font-size-btn.increase { + font-size: 0.85rem; + } + + /* Font size scale classes */ + [data-font-size="small"] article { + font-size: 0.9rem; + } + + [data-font-size="small"] article p, + [data-font-size="small"] article li { + font-size: 1.2rem; + } + + [data-font-size="small"] article h1 { + font-size: 2.4rem; + } + + [data-font-size="small"] article h2 { + font-size: 1.7rem; + } + + [data-font-size="large"] article { + font-size: 1.1rem; + } + + [data-font-size="large"] article p, + [data-font-size="large"] article li { + font-size: 1.6rem; + line-height: 2.2; + } + + [data-font-size="large"] article h1 { + font-size: 3.2rem; + } + + [data-font-size="large"] article h2 { + font-size: 2.3rem; + } + + [data-font-size="x-large"] article { + font-size: 1.2rem; + } + + [data-font-size="x-large"] article p, + [data-font-size="x-large"] article li { + font-size: 1.8rem; + line-height: 2.4; + } + + [data-font-size="x-large"] article h1 { + font-size: 3.5rem; + } + + [data-font-size="x-large"] article h2 { + font-size: 2.6rem; + } + + @media (max-width: 768px) { + .font-size-controls { + top: 1rem; + right: 3.5rem; + margin: 0; + } + } + + @media (max-width: 480px) { + .font-size-controls { + top: 0.5rem; + right: 3rem; + } + } + .breadcrumb a { color: var(--link-color); text-decoration: none; @@ -1306,9 +1484,29 @@
++ + + {% if breadcrumbs %} + + {% endif %} +