From 0969e2a904365044b224a22edc340eae76bb12cc Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 2 Dec 2025 18:56:50 -0500 Subject: [PATCH] Add Bible Stories section to stats page and fix story search autocomplete MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add dedicated Bible Stories stats section showing categories, story count, kids versions, word counts (85k adult / 58k kids), characters, and themes - Fix story search autocomplete on both /stories and /stories/kids pages (was looking for h3>a but cards are structured as a.story-card>h3) - Increase autocomplete results from 5 to 8 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- kjvstudy_org/routes/about.py | 35 +++ kjvstudy_org/templates/stats.html | 38 +++ kjvstudy_org/templates/stories_index.html | 81 +++++-- .../templates/stories_kids_index.html | 225 +++++++++++++++++- 4 files changed, 354 insertions(+), 25 deletions(-) diff --git a/kjvstudy_org/routes/about.py b/kjvstudy_org/routes/about.py index e6468de..c0102bb 100644 --- a/kjvstudy_org/routes/about.py +++ b/kjvstudy_org/routes/about.py @@ -83,6 +83,32 @@ async def stats(request: Request): resource_files = len(list((data_dir / 'resources').glob('*.json'))) story_files = len(list((data_dir / 'stories').glob('*.json'))) + # Bible Stories statistics + total_stories = 0 + stories_with_kids = 0 + total_story_characters = set() + total_story_themes = set() + total_story_words = 0 + total_kids_story_words = 0 + for file in (data_dir / 'stories').glob('*.json'): + try: + story_data = json.load(open(file)) + stories = story_data.get('stories', []) + total_stories += len(stories) + for story in stories: + # Count words in narrative + narrative = story.get('narrative', '') + total_story_words += len(narrative.split()) + if story.get('kids_narrative'): + stories_with_kids += 1 + total_kids_story_words += len(story.get('kids_narrative', '').split()) + for char in story.get('characters', []): + total_story_characters.add(char) + for theme in story.get('themes', []): + total_story_themes.add(theme) + except (json.JSONDecodeError, IOError): + continue + # Interlinear data size interlinear_file = data_dir / 'interlinear.json.gz' interlinear_size_mb = interlinear_file.stat().st_size / 1024 / 1024 if interlinear_file.exists() else 0 @@ -151,6 +177,15 @@ async def stats(request: Request): 'biographies': total_biographies, 'reading_plans': reading_plan_files, }, + 'bible_stories': { + 'categories': story_files, + 'total_stories': total_stories, + 'stories_with_kids': stories_with_kids, + 'unique_characters': len(total_story_characters), + 'unique_themes': len(total_story_themes), + 'total_words': total_story_words, + 'kids_words': total_kids_story_words, + }, 'language_tools': { 'hebrew_entries': total_hebrew_entries, 'greek_entries': total_greek_entries, diff --git a/kjvstudy_org/templates/stats.html b/kjvstudy_org/templates/stats.html index bcd50b6..acbc378 100644 --- a/kjvstudy_org/templates/stats.html +++ b/kjvstudy_org/templates/stats.html @@ -111,6 +111,43 @@ +
+

Bible Stories

+

Narrative retellings of Scripture for all ages +
→ Browse Bible Stories +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Story Categories{{ stats.bible_stories.categories }}
Total Stories{{ stats.bible_stories.total_stories }}
Kids Versions{{ stats.bible_stories.stories_with_kids }}
Total Words (Adult){{ "{:,}".format(stats.bible_stories.total_words) }}
Total Words (Kids){{ "{:,}".format(stats.bible_stories.kids_words) }}
Biblical Characters{{ stats.bible_stories.unique_characters }}
Unique Themes{{ stats.bible_stories.unique_themes }}
+
+

Language Tools

Hebrew, Greek, and interlinear resources @@ -195,6 +232,7 @@

  • {{ "{:,}".format(stats.commentary.total_words) }} words of theological analysis
  • {{ "{:,}".format(stats.cross_references.total_references) }} cross-references linking related passages
  • {{ "{:,}".format(stats.language_tools.total_strongs) }} Strong's Concordance entries for word studies
  • +
  • {{ stats.bible_stories.total_stories }} Bible stories across {{ stats.bible_stories.categories }} categories
  • {{ stats.study_resources.biographies }} biblical biographies
  • {{ stats.study_resources.study_guides }} topical study guides
  • {{ stats.study_resources.reading_plans }} reading plans
  • diff --git a/kjvstudy_org/templates/stories_index.html b/kjvstudy_org/templates/stories_index.html index 3e99968..9810050 100644 --- a/kjvstudy_org/templates/stories_index.html +++ b/kjvstudy_org/templates/stories_index.html @@ -136,12 +136,20 @@ border: 1px solid var(--border-color, #ddd); border-radius: 6px; padding: 1.25rem; - background: #fff; + background: var(--bg-color, #fff); text-decoration: none; color: inherit; position: relative; transition: box-shadow 0.2s ease, border-color 0.2s ease, transform 0.2s ease; } +[data-theme="dark"] .story-card { + background: #252525; +} +@media (prefers-color-scheme: dark) { + html:not([data-theme="light"]) .story-card { + background: #252525; + } +} .story-card:hover, .story-card:focus-visible { box-shadow: 0 2px 8px rgba(0,0,0,0.08); @@ -158,7 +166,7 @@ } .story-card .description { font-size: 0.9rem; - color: #555; + color: var(--text-secondary, #555); margin-bottom: 0.75rem; line-height: 1.5; } @@ -179,7 +187,7 @@ background: var(--code-bg, #f5f5f5); border-radius: 3px; font-size: 0.75rem; - color: #666; + color: var(--text-secondary, #666); } .story-card .tag.character { background: #e8f4f8; @@ -189,6 +197,24 @@ background: #f0f4e8; color: #5a7a2a; } +[data-theme="dark"] .story-card .tag.character { + background: #1e3a4a; + color: #7fc4e0; +} +[data-theme="dark"] .story-card .tag.theme { + background: #2a3a1e; + color: #a8c87a; +} +@media (prefers-color-scheme: dark) { + html:not([data-theme="light"]) .story-card .tag.character { + background: #1e3a4a; + color: #7fc4e0; + } + html:not([data-theme="light"]) .story-card .tag.theme { + background: #2a3a1e; + color: #a8c87a; + } +} .story-card .links { display: flex; gap: 1rem; @@ -207,12 +233,15 @@ .category-section.hidden { display: none; } +.story-toc li.hidden { + display: none; +} .category-section { margin-bottom: 3rem; } .category-description { font-size: 1rem; - color: #555; + color: var(--text-secondary, #555); margin-bottom: 1rem; max-width: 65ch; } @@ -239,11 +268,16 @@

    View Kids Version — Stories written for younger readers

    +
    + +
    +
    +

    Categories

    - -
    - -
    -
    @@ -300,6 +329,7 @@ document.addEventListener('DOMContentLoaded', function() { var dropdown = document.getElementById('story-search-dropdown'); var stories = document.querySelectorAll('.story-card'); var categorySections = document.querySelectorAll('.category-section'); + var tocItems = document.querySelectorAll('.story-toc li'); var noResults = document.getElementById('no-results'); var currentResults = []; var selectedIndex = -1; @@ -308,6 +338,7 @@ document.addEventListener('DOMContentLoaded', function() { if (!query) { stories.forEach(function(s) { s.classList.remove('hidden'); }); categorySections.forEach(function(c) { c.classList.remove('hidden'); }); + tocItems.forEach(function(t) { t.classList.remove('hidden'); }); noResults.classList.remove('visible'); return; } @@ -333,12 +364,26 @@ document.addEventListener('DOMContentLoaded', function() { }); categorySections.forEach(function(category) { + var categoryId = category.id; var visibleStories = category.querySelectorAll('.story-card:not(.hidden)'); - if (visibleStories.length === 0) { + var isEmpty = visibleStories.length === 0; + + if (isEmpty) { category.classList.add('hidden'); } else { category.classList.remove('hidden'); } + + // Also hide/show corresponding TOC item + tocItems.forEach(function(tocItem) { + if (tocItem.dataset.category === categoryId) { + if (isEmpty) { + tocItem.classList.add('hidden'); + } else { + tocItem.classList.remove('hidden'); + } + } + }); }); if (matchCount === 0) { @@ -358,17 +403,19 @@ document.addEventListener('DOMContentLoaded', function() { if (title.includes(query) || description.includes(query) || characters.includes(query) || themes.includes(query)) { - var link = story.querySelector('h3 a'); - if (link) { + // The story card itself is the anchor tag + var storyTitle = story.querySelector('h3'); + var storyDesc = story.querySelector('.description'); + if (storyTitle) { matches.push({ - title: link.textContent, - url: link.getAttribute('href'), - description: story.querySelector('.description')?.textContent?.substring(0, 60) + '...' + title: storyTitle.textContent, + url: story.href, + description: storyDesc ? storyDesc.textContent.substring(0, 60) + '...' : '' }); } } }); - return matches.slice(0, 5); + return matches.slice(0, 8); } function showDropdown(html) { diff --git a/kjvstudy_org/templates/stories_kids_index.html b/kjvstudy_org/templates/stories_kids_index.html index fe7d094..5a604f7 100644 --- a/kjvstudy_org/templates/stories_kids_index.html +++ b/kjvstudy_org/templates/stories_kids_index.html @@ -229,6 +229,9 @@ .category-section.hidden { display: none; } +.toc-item.hidden { + display: none; +} .category-section { margin-bottom: 3rem; } @@ -256,6 +259,194 @@ .no-results.visible { display: block; } + +/* Dark mode styles */ +[data-theme="dark"] .kids-page-header { + background: linear-gradient(135deg, #1e3a5f 0%, #2d1f4e 50%, #3d1f3a 100%); +} +[data-theme="dark"] .kids-page-header h1 { + color: #a5b4fc; +} +[data-theme="dark"] .kids-page-header .subtitle { + color: #c4b5fd; +} +[data-theme="dark"] .kids-page-header .intro { + color: #d1d5db; +} +[data-theme="dark"] .adult-link { + color: #c4b5fd; +} +[data-theme="dark"] .toc-item { + background: linear-gradient(135deg, #1e2a3a 0%, #2a1f3a 100%); + border-left-color: #8b5cf6; +} +[data-theme="dark"] .toc-item a { + color: #a5b4fc; +} +[data-theme="dark"] .toc-item .count { + color: #9ca3af; +} +[data-theme="dark"] .story-search input { + background: #252525; + border-color: #404040; + color: #e5e7eb; +} +[data-theme="dark"] .story-search input::placeholder { + color: #6b7280; +} +[data-theme="dark"] .story-search-dropdown { + background: #252525; + border-color: #404040; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); +} +[data-theme="dark"] .search-result-category { + background: linear-gradient(135deg, #1e2a3a 0%, #2a1f3a 100%); + border-bottom-color: #404040; + color: #9ca3af; +} +[data-theme="dark"] .search-result-item { + color: #a5b4fc; + border-bottom-color: #404040; +} +[data-theme="dark"] .search-result-item:hover, +[data-theme="dark"] .search-result-item.selected { + background: linear-gradient(135deg, #1e2a3a 0%, #2a1f3a 100%); +} +[data-theme="dark"] .search-result-subtitle { + color: #9ca3af; +} +[data-theme="dark"] .kids-story-card { + background: #252525; + border-color: #404040; +} +[data-theme="dark"] .kids-story-card:hover, +[data-theme="dark"] .kids-story-card:focus-visible { + border-color: #8b5cf6; + box-shadow: 0 4px 12px rgba(139, 92, 246, 0.25); +} +[data-theme="dark"] .kids-story-card h3 { + color: #a5b4fc; +} +[data-theme="dark"] .kids-story-card .description { + color: #d1d5db; +} +[data-theme="dark"] .kids-story-card .scripture { + color: #c4b5fd; +} +[data-theme="dark"] .character-badge { + background: linear-gradient(135deg, #1e3a5f, #2a2f5f); + color: #93c5fd; +} +[data-theme="dark"] .theme-badge { + background: linear-gradient(135deg, #4a3f1a, #3a3520); + color: #fcd34d; +} +[data-theme="dark"] .category-section h2 { + color: #a5b4fc; + border-bottom-color: #3730a3; +} +[data-theme="dark"] .category-description { + color: #d1d5db; +} +[data-theme="dark"] .no-results { + background: linear-gradient(135deg, #4a3f1a 0%, #3a3520 100%); + color: #fcd34d; +} + +/* System preference dark mode */ +@media (prefers-color-scheme: dark) { + html:not([data-theme="light"]) .kids-page-header { + background: linear-gradient(135deg, #1e3a5f 0%, #2d1f4e 50%, #3d1f3a 100%); + } + html:not([data-theme="light"]) .kids-page-header h1 { + color: #a5b4fc; + } + html:not([data-theme="light"]) .kids-page-header .subtitle { + color: #c4b5fd; + } + html:not([data-theme="light"]) .kids-page-header .intro { + color: #d1d5db; + } + html:not([data-theme="light"]) .adult-link { + color: #c4b5fd; + } + html:not([data-theme="light"]) .toc-item { + background: linear-gradient(135deg, #1e2a3a 0%, #2a1f3a 100%); + border-left-color: #8b5cf6; + } + html:not([data-theme="light"]) .toc-item a { + color: #a5b4fc; + } + html:not([data-theme="light"]) .toc-item .count { + color: #9ca3af; + } + html:not([data-theme="light"]) .story-search input { + background: #252525; + border-color: #404040; + color: #e5e7eb; + } + html:not([data-theme="light"]) .story-search input::placeholder { + color: #6b7280; + } + html:not([data-theme="light"]) .story-search-dropdown { + background: #252525; + border-color: #404040; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); + } + html:not([data-theme="light"]) .search-result-category { + background: linear-gradient(135deg, #1e2a3a 0%, #2a1f3a 100%); + border-bottom-color: #404040; + color: #9ca3af; + } + html:not([data-theme="light"]) .search-result-item { + color: #a5b4fc; + border-bottom-color: #404040; + } + html:not([data-theme="light"]) .search-result-item:hover, + html:not([data-theme="light"]) .search-result-item.selected { + background: linear-gradient(135deg, #1e2a3a 0%, #2a1f3a 100%); + } + html:not([data-theme="light"]) .search-result-subtitle { + color: #9ca3af; + } + html:not([data-theme="light"]) .kids-story-card { + background: #252525; + border-color: #404040; + } + html:not([data-theme="light"]) .kids-story-card:hover, + html:not([data-theme="light"]) .kids-story-card:focus-visible { + border-color: #8b5cf6; + box-shadow: 0 4px 12px rgba(139, 92, 246, 0.25); + } + html:not([data-theme="light"]) .kids-story-card h3 { + color: #a5b4fc; + } + html:not([data-theme="light"]) .kids-story-card .description { + color: #d1d5db; + } + html:not([data-theme="light"]) .kids-story-card .scripture { + color: #c4b5fd; + } + html:not([data-theme="light"]) .character-badge { + background: linear-gradient(135deg, #1e3a5f, #2a2f5f); + color: #93c5fd; + } + html:not([data-theme="light"]) .theme-badge { + background: linear-gradient(135deg, #4a3f1a, #3a3520); + color: #fcd34d; + } + html:not([data-theme="light"]) .category-section h2 { + color: #a5b4fc; + border-bottom-color: #3730a3; + } + html:not([data-theme="light"]) .category-description { + color: #d1d5db; + } + html:not([data-theme="light"]) .no-results { + background: linear-gradient(135deg, #4a3f1a 0%, #3a3520 100%); + color: #fcd34d; + } +} {% endblock %} @@ -276,7 +467,7 @@

    Categories

    {% for category in categories %} -
    +
    {{ category.category }} {{ category.stories|length }} stories
    @@ -331,6 +522,7 @@ document.addEventListener('DOMContentLoaded', function() { var dropdown = document.getElementById('story-search-dropdown'); var stories = document.querySelectorAll('.kids-story-card'); var categorySections = document.querySelectorAll('.category-section'); + var tocItems = document.querySelectorAll('.toc-item'); var noResults = document.getElementById('no-results'); var currentResults = []; var selectedIndex = -1; @@ -339,6 +531,7 @@ document.addEventListener('DOMContentLoaded', function() { if (!query) { stories.forEach(function(s) { s.classList.remove('hidden'); }); categorySections.forEach(function(c) { c.classList.remove('hidden'); }); + tocItems.forEach(function(t) { t.classList.remove('hidden'); }); noResults.classList.remove('visible'); return; } @@ -364,12 +557,26 @@ document.addEventListener('DOMContentLoaded', function() { }); categorySections.forEach(function(category) { + var categoryId = category.id; var visibleStories = category.querySelectorAll('.kids-story-card:not(.hidden)'); - if (visibleStories.length === 0) { + var isEmpty = visibleStories.length === 0; + + if (isEmpty) { category.classList.add('hidden'); } else { category.classList.remove('hidden'); } + + // Also hide/show corresponding TOC item + tocItems.forEach(function(tocItem) { + if (tocItem.dataset.category === categoryId) { + if (isEmpty) { + tocItem.classList.add('hidden'); + } else { + tocItem.classList.remove('hidden'); + } + } + }); }); if (matchCount === 0) { @@ -389,17 +596,19 @@ document.addEventListener('DOMContentLoaded', function() { if (title.includes(query) || description.includes(query) || characters.includes(query) || themes.includes(query)) { - var link = story.querySelector('h3 a'); - if (link) { + // The story card itself is the anchor tag + var storyTitle = story.querySelector('h3'); + var storyDesc = story.querySelector('.description'); + if (storyTitle) { matches.push({ - title: link.textContent, - url: link.getAttribute('href'), - description: story.querySelector('.description')?.textContent?.substring(0, 50) + '...' + title: storyTitle.textContent, + url: story.href, + description: storyDesc ? storyDesc.textContent.substring(0, 50) + '...' : '' }); } } }); - return matches.slice(0, 5); + return matches.slice(0, 8); } function showDropdown(html) {