From 4dd606e4a0cc2ad88ef11e4ff9d1efa336106fc6 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 3 Dec 2025 13:52:16 -0500 Subject: [PATCH] Add PWA manifest and improve Strong's keyboard navigation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add manifest.json for PWA support (stops 404 requests) - Add [ and ] keys to navigate between Strong's numbers - Add info-card selection styles for keyboard nav - Update accessibility page with Strong's shortcuts 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- kjvstudy_org/static/manifest.json | 9 +++ kjvstudy_org/templates/accessibility.html | 32 ++++++++++ kjvstudy_org/templates/strongs_entry.html | 76 ++++++++++++++++------- 3 files changed, 95 insertions(+), 22 deletions(-) create mode 100644 kjvstudy_org/static/manifest.json diff --git a/kjvstudy_org/static/manifest.json b/kjvstudy_org/static/manifest.json new file mode 100644 index 0000000..4948f25 --- /dev/null +++ b/kjvstudy_org/static/manifest.json @@ -0,0 +1,9 @@ +{ + "name": "KJV Study", + "short_name": "KJV Study", + "description": "Study the King James Bible with AI-powered commentary, Strong's Concordance, and interlinear analysis", + "start_url": "/", + "display": "standalone", + "background_color": "#fffff8", + "theme_color": "#4a7c59" +} diff --git a/kjvstudy_org/templates/accessibility.html b/kjvstudy_org/templates/accessibility.html index 4ed924e..96aaf4e 100644 --- a/kjvstudy_org/templates/accessibility.html +++ b/kjvstudy_org/templates/accessibility.html @@ -289,6 +289,38 @@ +

Strong's Concordance Pages

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyAction
[Previous Strong's number
]Next Strong's number
j or ↓Select next card (info, related, occurrence)
k or ↑Select previous card
EnterFollow link on selected card
+

Navigation keys are inspired by Vim conventions (hjkl) for users familiar with that paradigm, while also supporting standard arrow keys for everyone else.

A Note on Keyboard Navigation

diff --git a/kjvstudy_org/templates/strongs_entry.html b/kjvstudy_org/templates/strongs_entry.html index 5feb5dd..32326ff 100644 --- a/kjvstudy_org/templates/strongs_entry.html +++ b/kjvstudy_org/templates/strongs_entry.html @@ -121,6 +121,13 @@ border: 1px solid var(--border-color); border-radius: 10px; padding: 1.5rem; + transition: all 0.15s; +} + +.info-card.selected { + border-color: var(--link-color); + box-shadow: 0 0 0 2px rgba(74, 124, 89, 0.3); + background: #f8fff8; } .info-card h2 { @@ -230,6 +237,12 @@ box-shadow: 0 1px 4px rgba(0,0,0,0.05); } +.related-entry-card.selected { + border-color: var(--link-color); + box-shadow: 0 0 0 2px rgba(74, 124, 89, 0.3); + background: #f8fff8; +} + .related-entry-header { display: flex; align-items: baseline; @@ -270,6 +283,11 @@ border-color: #333; } +[data-theme="dark"] .related-entry-card.selected { + background: #1a2a1a; + box-shadow: 0 0 0 2px rgba(107, 155, 122, 0.3); +} + [data-theme="dark"] .related-entry-header a { color: #6495ED; } @@ -480,6 +498,11 @@ border-color: #333; } +[data-theme="dark"] .info-card.selected { + background: #1a2a1a; + box-shadow: 0 0 0 2px rgba(107, 155, 122, 0.3); +} + [data-theme="dark"] .kjv-tag { background: #2a2a2a; border-color: #444; @@ -551,9 +574,9 @@ {{ entry.strongs }} {{ entry.language }} -

{{ entry.word }}

-

{{ entry.transliteration }}{% if entry.pronunciation %} ({{ entry.pronunciation }}){% endif %}

-

Definition: {{ entry.definition }}

+
{{ entry.word }}
+
{{ entry.transliteration }}{% if entry.pronunciation %} ({{ entry.pronunciation }}){% endif %}
+
{{ entry.definition }}
@@ -661,24 +684,23 @@ function showMoreOccurrences() { } (function() { - const allCards = document.querySelectorAll('.occurrence-card'); - const visibleCards = document.querySelectorAll('.occurrence-card:not(.hidden)'); - if (visibleCards.length === 0) return; + // Include info cards, related entries, and occurrence cards in keyboard nav + function getAllNavigableCards() { + const infoCards = document.querySelectorAll('.info-card'); + const related = document.querySelectorAll('.related-entry-card'); + const occurrences = document.querySelectorAll('.occurrence-card:not(.hidden)'); + return [...infoCards, ...related, ...occurrences]; + } let selectedIndex = -1; - function getVisibleCards() { - return document.querySelectorAll('.occurrence-card:not(.hidden)'); - } - function selectCard(index) { - const cards = getVisibleCards(); + const cards = getAllNavigableCards(); + if (cards.length === 0) return; // Remove previous selection - if (selectedIndex >= 0) { - const prevCard = document.querySelector('.occurrence-card.selected'); - if (prevCard) prevCard.classList.remove('selected'); - } + const prevCard = document.querySelector('.info-card.selected, .related-entry-card.selected, .occurrence-card.selected'); + if (prevCard) prevCard.classList.remove('selected'); // Update index with bounds checking selectedIndex = Math.max(0, Math.min(index, cards.length - 1)); @@ -698,7 +720,8 @@ function showMoreOccurrences() { if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return; if (typeof KJVNav !== 'undefined' && KJVNav.sidebarActive) return; - const cards = getVisibleCards(); + const cards = getAllNavigableCards(); + if (cards.length === 0) return; if (e.key === 'ArrowDown' || e.key === 'j') { e.preventDefault(); @@ -708,18 +731,27 @@ function showMoreOccurrences() { selectCard(selectedIndex - 1); } else if (e.key === 'Enter' && selectedIndex >= 0 && selectedIndex < cards.length) { e.preventDefault(); - const link = cards[selectedIndex].querySelector('.occ-reference'); + // Check for link in any card type + const link = cards[selectedIndex].querySelector('.occ-reference, .related-entry-header a, .strongs-ref'); if (link) window.location.href = link.href; + } else if (e.key === '[') { + // Navigate to previous Strong's number + const prevLink = document.querySelector('.nav-btn[rel="prev"]'); + if (prevLink) window.location.href = prevLink.href; + } else if (e.key === ']') { + // Navigate to next Strong's number + const nextLink = document.querySelector('.nav-btn[rel="next"]'); + if (nextLink) window.location.href = nextLink.href; } }); - // Click to select - allCards.forEach((card, index) => { + // Click to select - attach to all card types + document.querySelectorAll('.info-card, .related-entry-card, .occurrence-card').forEach((card) => { card.addEventListener('click', function(e) { if (e.target.tagName !== 'A') { - const visCards = getVisibleCards(); - const visIndex = Array.from(visCards).indexOf(card); - if (visIndex >= 0) selectCard(visIndex); + const allCards = getAllNavigableCards(); + const cardIndex = allCards.indexOf(card); + if (cardIndex >= 0) selectCard(cardIndex); } }); });