Files
bible-app/index.html
T
christianandClaude Opus 5 0db5e9b07d Bible App 1.4.34 — full source
First commit on the local Gitea server, made after moving the tree off the
Windows machine. Build outputs, node_modules and the AI models stay out
(see .gitignore); everything the installer build needs is tracked.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-24 13:05:20 -04:00

214 lines
15 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bible App</title>
<link rel="stylesheet" href="styles.css?v=__BUILD__">
<!-- Removed remote Google Fonts to ensure consistent offline rendering -->
<!-- Font Awesome 6.0.0 (free) vendored locally so toolbar icons render with zero internet dependency -->
<link rel="stylesheet" href="vendor/fontawesome/css/all.min.css?v=__BUILD__">
<script>
// Apply the saved Bible reading font size before first paint (set in Settings > Bible Reading).
// Defaults to the built-in 60px when unset, so existing behavior is unchanged.
try {
var __bfs = parseInt(localStorage.getItem('bibleFontSize'), 10);
if (__bfs && !isNaN(__bfs)) {
__bfs = Math.max(40, Math.min(120, __bfs));
document.documentElement.style.setProperty('--bible-font-size', __bfs + 'px');
}
} catch (e) {}
</script>
<script>
/*
* Follower marker — this window is the BOOTH screen (?follower=1), not the stage TV.
*
* Set before every other script for the same reason as the admin marker below: remote-state.js
* and follower-publish.js decide on their very first tick whether they are the stage renderer,
* and a follower that published its own state would fight the real TV for the feed.
*
* The follower renders the identical page so live pen strokes land on the identical words. It
* is driven entirely by follower-view.js from the state the stage TV publishes.
*/
try {
if (/(^|[?&])follower=1(&|$)/.test(window.location.search)) {
window.__followerMode = true;
document.documentElement.style.background = '#000';
}
} catch (e) {}
</script>
<script>
/*
* Offstage (admin) copy marker.
*
* `?admin=1` means this window is the admin console's PRIVATE copy of the app, running in
* the admin's own browser — not the renderer on the TV. Set before any other script so
* remote-state.js sees it on its first tick and never publishes this window's state as the
* stage state.
*
* The badge is not decoration: this copy looks pixel-identical to the live stage, and an
* admin who forgets which one they are driving will make changes believing the congregation
* can see them (or worse, believing they cannot).
*/
try {
if (/(^|[?&])admin=1(&|$)/.test(window.location.search)) {
window.__adminOffstage = true;
document.addEventListener('DOMContentLoaded', function () {
try {
var b = document.createElement('div');
b.id = 'offstageBadge';
b.textContent = 'ADMIN COPY — NOT ON TV';
b.setAttribute('role', 'status');
b.style.cssText = [
'position:fixed', 'top:0', 'left:0', 'right:0', 'z-index:2147483647',
'background:#b26a00', 'color:#fff', 'font:600 12px/1.6 system-ui,sans-serif',
'letter-spacing:.08em', 'text-align:center', 'padding:3px 8px',
'pointer-events:none', 'text-transform:uppercase'
].join(';');
document.body.appendChild(b);
} catch (_) {}
});
}
} catch (e) {}
</script>
<link rel="icon" type="image/x-icon" href="data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAA==">
</head>
<body>
<div id="app">
<!-- Upper Toolbar -->
<div class="toolbar">
<div class="toolbar-left">
<div class="browser-tabs">
<button class="reset-app-btn" id="resetAppBtn" title="Refresh app (Ctrl+R)"><i class="fas fa-rotate-right"></i></button>
<div class="tab-bar">
<!-- Tabs will be created dynamically by JavaScript -->
</div>
<button class="new-tab-btn" id="newTabBtn">+</button>
</div>
</div>
<div class="toolbar-right">
<div class="tool-indicator" id="toolIndicator" aria-live="polite" title="Current tool"><i class="fas"></i></div>
<div class="tool-rail-edge" id="toolRailEdge" aria-hidden="true"></div>
<div class="tool-rail" id="toolRail">
<div class="tool-slider" id="toolSlider">
<div class="qd-rail-grip" id="railGrip" title="Drag to float the toolbar"><i class="fas fa-grip-vertical"></i></div>
<div class="tool-wrapper">
<button class="tool-icon selected" data-tool="pen" id="penToolBtn"><i class="fas fa-pen"></i></button>
<div class="pen-color-dropdown" id="penColorDropdown">
<button class="pen-color-option" style="--pen-color: #fefefe;" data-color="#fefefe" title="White">&nbsp;</button>
<button class="pen-color-option" style="--pen-color: #ff3333;" data-color="#ff3333" title="Red">&nbsp;</button>
<button class="pen-color-option" style="--pen-color: #0090ff;" data-color="#0090ff" title="Blue">&nbsp;</button>
<button class="pen-color-option" style="--pen-color: #00ff00;" data-color="#00ff00" title="Green">&nbsp;</button>
<button class="pen-color-option" style="--pen-color: #ffff00;" data-color="#ffff00" title="Yellow">&nbsp;</button>
</div>
<div class="size-slider-popup" id="penSizeSliderPopup">
<div class="size-slider-wrap">
<div class="size-slider-ticks" id="penSizeTicks" aria-hidden="true"></div>
<input type="range" min="0" max="6" step="1" value="1" class="size-slider" id="penSizeSlider" aria-label="Pen size">
</div>
</div>
</div>
<div class="tool-wrapper">
<button class="tool-icon" data-tool="highlight" id="highlightToolBtn"><i class="fas fa-highlighter"></i></button>
<div class="highlighter-color-dropdown" id="highlighterColorDropdown">
<button class="highlighter-color-option" style="--highlighter-color: #fefefe;" data-color="#fefefe" title="White">&nbsp;</button>
<button class="highlighter-color-option" style="--highlighter-color: #ff3333;" data-color="#ff3333" title="Red">&nbsp;</button>
<button class="highlighter-color-option" style="--highlighter-color: #0090ff;" data-color="#0090ff" title="Blue">&nbsp;</button>
<button class="highlighter-color-option" style="--highlighter-color: #00ff00;" data-color="#00ff00" title="Green">&nbsp;</button>
<button class="highlighter-color-option" style="--highlighter-color: #ffff00;" data-color="#ffff00" title="Yellow">&nbsp;</button>
</div>
</div>
<button class="tool-icon" data-tool="erase"><i class="fas fa-eraser"></i></button>
<div class="tool-wrapper">
<button class="tool-icon" data-tool="laser" id="laserToolBtn" title="Laser pointer" style="--laser-color: #ffff00;"><i class="fas fa-circle-dot"></i></button>
<div class="laser-color-dropdown" id="laserColorDropdown">
<button class="laser-color-option" style="background: #fefefe;" data-color="#fefefe" title="White">&nbsp;</button>
<button class="laser-color-option" style="background: #ff3333;" data-color="#ff3333" title="Red">&nbsp;</button>
<button class="laser-color-option" style="background: #0090ff;" data-color="#0090ff" title="Blue">&nbsp;</button>
<button class="laser-color-option" style="background: #00ff00;" data-color="#00ff00" title="Green">&nbsp;</button>
<button class="laser-color-option selected" style="background: #ffff00;" data-color="#ffff00" title="Yellow">&nbsp;</button>
</div>
</div>
<button class="tool-icon" data-tool="spotlight" id="spotlightToolBtn" title="Spotlight"><i class="fas fa-bullseye"></i></button>
<button class="tool-icon" id="touchMenuBtn" data-tool="menu" title="Menu (Settings & Content Manager)"><i class="fas fa-bars"></i></button>
</div>
<button class="toolbar-btn" id="undoBtn" tabindex="-1">
<i class="fas fa-undo"></i>
</button>
<button class="toolbar-btn" id="redoBtn" tabindex="-1">
<i class="fas fa-redo"></i>
</button>
<button class="toolbar-btn" id="resetBtn" title="Clear current tab (tap) or all annotations (hold 1.5s)" tabindex="-1">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
</div>
<!-- Tab Content Panels -->
<div class="tab-content">
<!-- Tab panels will be created dynamically by JavaScript -->
</div>
</div>
<!-- Touch Menu Overlay -->
<div id="touchMenuOverlay" class="touch-menu-overlay hidden">
<div class="touch-menu-content">
<button class="touch-menu-close" aria-label="Close menu">
<i class="fas fa-times"></i>
</button>
<div class="touch-menu-items">
<button class="touch-menu-item" id="touchMenuSettings">
<i class="fas fa-cog"></i>
<span>Settings</span>
</button>
<button class="touch-menu-item" id="touchMenuContentManager">
<i class="fas fa-folder-open"></i>
<span>Content Manager</span>
</button>
<button class="touch-menu-item" id="touchMenuAbout">
<i class="fas fa-info-circle"></i>
<span>About</span>
</button>
<button class="touch-menu-item" id="touchMenuExit">
<i class="fas fa-sign-out-alt"></i>
<span>Exit</span>
</button>
</div>
</div>
</div>
<!-- Strong's dictionaries are served via /api/dict endpoints; avoid loading Node-formatted JS directly in browser -->
<!-- Removed direct script tags to prevent "module is not defined" errors in browser -->
<script type="module" src="init.js?v=__BUILD__"></script>
<script type="module" src="wordnet-init.js?v=__BUILD__"></script>
<!-- Presentation overlays (laser / spotlight) — isolated, additive -->
<script src="presentation-tools.js?v=__BUILD__"></script>
<!-- Optional draggable quick-tool dock (toggled in Settings) — isolated, additive -->
<script src="quick-dock.js?v=__BUILD__"></script>
<!-- Two-finger tap → radial tool selector under the fingers (Settings › Presentation) — isolated, additive -->
<script src="tool-wheel.js?v=__BUILD__"></script>
<!-- Red-letter (words of Christ) — isolated, additive -->
<script src="red-letter.js?v=__BUILD__"></script>
<!-- Watchdog: re-render a Bible tab that came up blank — isolated, additive -->
<script src="text-watchdog.js?v=__BUILD__"></script>
<!-- Draw on the word-definition popup cards — isolated, additive -->
<script src="popup-draw.js?v=__BUILD__"></script>
<!-- Live UI-state feed for the phone remote — isolated, additive -->
<script src="remote-state.js?v=__BUILD__"></script>
<!-- Fast frame source for the phone remote's mirror (renderer self-capture) — isolated, additive -->
<script src="mirror-stream.js?v=__BUILD__"></script>
<!-- An X may only close: stops a closing overlay's tap falling through to what was behind it -->
<script src="overlay-close-guard.js?v=__BUILD__"></script>
<!-- Book/abbreviation reference resolver, shared with the phone remote -->
<script src="book-aliases.js?v=__BUILD__"></script>
<!-- Follower link: the stage TV publishes its state so the booth screen can re-render this exact
picture for ProPresenter / the ATEM without a video cable — isolated, additive -->
<script src="follower-publish.js?v=__BUILD__"></script>
<script src="follower-view.js?v=__BUILD__"></script>
<!-- ProPresenter Sync tab: slide-reference scanner + the live link client — isolated, additive -->
<script src="scripture-scan.js?v=__BUILD__"></script>
<script src="propresenter-sync.js?v=__BUILD__"></script>
</body>
</html>