{# The shell every page is wrapped in. Rendered by sw.js, not by the build, so editing this file changes every page without regenerating any of them. Context: title, content (safe HTML fragment), mark (bookmark or null), reference (the navbar picker's model, or null). Bootstrap is the whole stylesheet. The fragments carry semantic USFM classes and no Bootstrap ones, so the body text is styled by Reboot; the chrome here is what uses the framework. #} {# No viewport-fit=cover, deliberately. It extends the page into the notch and the home indicator, which only makes sense paired with safe-area-inset padding in the stylesheet -- and without that padding on iOS the first line of scripture sits under the clock. Paired with the status bar style below: change one and the other has to change too. #} {% block title %}{{ title | safe }}{% endblock %} {# Vendored rather than linked from a CDN: sw.js only caches same-origin responses, so a CDN stylesheet would leave the app unstyled offline. #} {# app.css comes second so its variable overrides win. It styles the scripture; Bootstrap styles the chrome. #} {# History entries are whole books, and htmx keeps its history cache in localStorage -- a couple of them would blow the quota. Turned off, so every back or forward is a cache miss, and refreshOnHistoryMiss decides what a miss does. False on purpose. True makes htmx call location.reload(true) -- a forced reload, which is exactly the kind of navigation a browser takes past the service worker, so it reaches server.py and comes back as boot.html: the spinner flashes by and then reloads into the real page. False makes htmx fetch the page over XHR instead, which the worker answers itself, from its own cache when offline. No navigation, no boot page, nothing to flash. That request carries HX-History-Restore-Request, and the worker answers it with a whole composed document; htmx lifts the element marked hx-history-elt out of the response and puts it back in the page. #} {# The bundle carries Popper, which the navbar collapse and the modal do not need -- but a dropdown or tooltip added later would, and this is one file in the precache either way. Deferred scripts run in order, so `bootstrap` is defined by the time app.js runs. #} {# htmx is not deferred: hx-boost has to be in place before the first click. #} {# black, not black-translucent. Translucent makes iOS draw the status bar over the web view and reserve no space for it, so the navbar starts at the very top of the screen with the clock and battery on top of it. black keeps the status bar opaque and the view below it, which is what this app wants: there is no full-bleed artwork here to show through. #} {# Boosted links swap only the fragment, so the navbar, the modal and the scripts above survive navigation and never reload. No show: modifier, deliberately. htmx adds show:top itself for a boosted link, but skips it when the href carries a fragment identifier and scrolls to that anchor after the swap instead. Spelling show:top out here overrides that decision for every link, which leaves a cross-reference like ISA#v61_6 sitting at the top of Isaiah. #} {# col-xl-8 keeps a line of verse near a readable measure instead of running the full width of a desktop window. #}
{# The swap target. hx-target on names this id, so every boosted link lands here -- remove it and a click does nothing at all while a full navigation still works. It is also where a verse click becomes a request. htmx's bootstrap-modal example puts hx-get on the element that opens the dialog; the element here is a verse number in a generated book, and there are 38,000 of them -- an attribute apiece would cost more than the whole shell, and a listener apiece more than the reading. So one handler sits on the container they all swap into, and names the verse it found. Delegated, so it survives every swap: this element is never replaced, only filled. hx-vals cannot do this. htmx evaluates it as Function("return (...)")() with nothing passed in, so an expression there can read the document but not the click -- and which verse was clicked is the one thing that has to travel. Enter and space go through the same door rather than a second one: compile_bible.py writes role=button and tabindex on every verse, and a button that answers the keyboard by being clicked is a button with one behaviour instead of two. #}
{% block content %}{% endblock %}
{% include "version.njk" %}
{# The verse dialog: empty in the page, filled per request. Bootstrap owns showing it, dismissing it, the backdrop and Escape; sw.js owns everything inside, and templates/verse.njk is what lands in the modal-content below. Opened by the arrival of the content rather than by data-bs-toggle on whatever was clicked. The example puts that attribute on its trigger, which is not available here for the same reason hx-get is not -- see #content above -- and the swap is the honest signal anyway: there is nothing worth showing until it is here. A verse the corpus does not have is answered 204, which swaps nothing, which opens nothing. after-swap and not after-settle: settling is the 20ms attribute pass and this dialog has nothing to settle. Only the modal-content swap can reach here -- the bookmark write targets the navbar slot, and the toast it sends back rides in out of band, which fires its own event and not this one. verse-done is the dialog saying it has finished its job, and it arrives from both of the things in it: sw.js sends it as a trigger header on a bookmark write that committed, and the copy button dispatches it when the clipboard has actually taken the link. Neither sends it when the action failed -- the field in the dialog is the fallback for a copy that was refused, and there is nothing to take down the dialog for when there is nothing to celebrate. Closed at once rather than after a pause. The pause was there to let the status line inside be read before the dialog took it away; the line is a toast now, outside and above all of this, so it outlives the dialog and there is nothing left to wait for. #} {# Where confirmations land. Empty in the page and filled a toast at a time: api/toast appends one when the copy button asks for it, and a bookmark write sends one out of band on its own answer. templates/toast.njk is what lands here, and each one shows and then removes itself, so this is empty again a few seconds later. Fixed, and outside #content, for the same reason the dialog is: it has to survive the swap under it. It also has to survive the dialog -- a copy confirmed by a toast inside the modal would go when the modal did. Bottom right, and last in the document, so it covers the text rather than moving it, and so nothing in the reading order comes after it. It sits above the modal without being asked to: Bootstrap gives .toast a z-index of 1090 against the modal's 1055, and this container takes none of its own, which is what leaves that comparison to the toast. #}