update borders

This commit is contained in:
mark committed 2026-10-22 23:48:03 -04:00
1 parent 531a32e394
commit c66f9a1e2a
12 files changed
+378 -9

No files matched your search

+190 -5
View File
@@ -22,7 +22,11 @@
*::before,
*::after { box-sizing: border-box; }
/* flow-root keeps main's top margin inside the body. Let through, it moves
the body's top edge down the page, and htmx scrolls a boosted page to that
edge, not to the very top. */
body {
display: flow-root;
margin: 0;
font-family: var(--font-body);
font-size: 1rem;
@@ -74,19 +78,200 @@ sup {
a { color: var(--link); }
a:hover { color: var(--link-hover); }
/* The frame's measures, shared by the sidebar and the button that opens it
below a laptop. A corner is six rules square, set turned half round from how
the piece is drawn, so its small square sits outermost. The frame runs two
rules along each side, one on the edge and one 2.5 rules in, both meeting
the corner's far side. */
.sidebar-shell {
--rule: 3px;
--corner: calc(var(--rule) * 6);
--inner: calc(var(--rule) * 2.5);
--run: calc(100% - var(--corner) * 2);
--margin: 1.5rem;
}
/* Over the text along the left edge, so the reading column stays centred on
the screen, with the same margin on every side. The dialog's own box has
no padding: a click that lands on it rather than on what is inside is a
click on the backdrop. */
.sidebar {
position: fixed;
inset: var(--margin) auto var(--margin) var(--margin);
z-index: 1;
width: auto;
height: auto;
max-height: none;
margin: 0;
padding: 0;
color: var(--text);
border: 0;
background:
url("../ornaments/uniform-rule-corner-br.svg") left top / var(--corner) no-repeat,
url("../ornaments/uniform-rule-corner-bl.svg") right top / var(--corner) no-repeat,
url("../ornaments/uniform-rule-corner-tr.svg") left bottom / var(--corner) no-repeat,
url("../ornaments/uniform-rule-corner-tl.svg") right bottom / var(--corner) no-repeat,
linear-gradient(#000 0 0) var(--corner) var(--inner) / var(--run) var(--rule) no-repeat,
linear-gradient(#000 0 0) var(--corner) 0 / var(--run) var(--rule) no-repeat,
linear-gradient(#000 0 0) var(--corner) calc(100% - var(--inner)) / var(--run) var(--rule) no-repeat,
linear-gradient(#000 0 0) var(--corner) 100% / var(--run) var(--rule) no-repeat,
linear-gradient(#000 0 0) var(--inner) var(--corner) / var(--rule) var(--run) no-repeat,
linear-gradient(#000 0 0) 0 var(--corner) / var(--rule) var(--run) no-repeat,
linear-gradient(#000 0 0) calc(100% - var(--inner)) var(--corner) / var(--rule) var(--run) no-repeat,
linear-gradient(#000 0 0) 100% var(--corner) / var(--rule) var(--run) no-repeat,
var(--background);
transition: opacity .15s ease-out;
}
/* Where the reading column has a margin beside it, the sidebar fills it, the
gap to the text the same as the margin round the rest. */
@media (min-width: 1200px) {
.sidebar {
width: calc((100% - var(--measure)) / 2 + var(--gutter) - var(--margin) * 2);
}
}
.sidebar-body { padding: calc(var(--corner) + 1rem); }
.sidebar::backdrop { background: rgb(0 0 0 / .3); }
/* Away from the top it fades out unless it is under the pointer. Focus does
not hold it: a select keeps focus after it is used, which would leave the
sidebar over the text. */
.scrolled .sidebar:not(:modal, :hover) { opacity: 0; }
/* Below a laptop the sidebar waits behind a button in the corner, a box in the
frame's rule. Opened, the frame grows out of it and a copy of it, which
closes the dialog, stands in for the frame's bottom right corner. */
.sidebar-fab {
display: none;
width: 4rem;
height: 4rem;
padding: 0;
color: #000;
border: var(--rule) solid #000;
background: var(--background);
cursor: pointer;
}
.sidebar-fab::before {
content: "";
display: block;
width: 90%;
height: 90%;
margin: auto;
background: currentColor;
mask: url("../ornaments/mural-311a.svg") center / contain no-repeat;
}
.sidebar-fab:focus-visible {
outline: 2px solid var(--focus);
outline-offset: 2px;
}
.sidebar-open {
position: fixed;
right: max(var(--margin), env(safe-area-inset-right));
bottom: max(var(--margin), env(safe-area-inset-bottom));
z-index: 1;
transition: opacity .15s ease-out;
}
/* It fades out away from the top of the page but can still be tapped. Only a
real pointer brings it back on hover: a touch screen keeps :hover on
whatever was last tapped, which would leave it showing. */
.scrolled .sidebar-open:not(:focus-visible) { opacity: 0; }
@media (hover: hover) {
.scrolled .sidebar-open:hover { opacity: 1; }
}
.sidebar-close {
position: absolute;
right: 0;
bottom: 0;
}
@media (max-width: 767.98px) {
.sidebar-shell { --margin: 1rem; }
}
/* Without a margin beside the reading column to hold the sidebar, it waits
behind the button. Opened, it fills the screen less the margin all round,
clear of a notch or home indicator. */
@media (max-width: 1199.98px) {
.sidebar:not(:modal) { display: none; }
.sidebar-open,
.sidebar:modal .sidebar-close { display: block; }
.sidebar:modal {
inset: max(var(--margin), env(safe-area-inset-top))
max(var(--margin), env(safe-area-inset-right))
max(var(--margin), env(safe-area-inset-bottom))
max(var(--margin), env(safe-area-inset-left));
width: auto;
max-width: none;
}
}
.reference {
display: flex;
align-items: center;
gap: .25rem;
}
.reference-book {
font-weight: 700;
margin-right: .25rem;
}
/* A plain box in a hairline, square like the frame, with a small black
triangle in place of the system's arrow. */
.reference select {
appearance: none;
padding: .25rem 1.75rem .25rem .5rem;
font: inherit;
color: #000;
border: 1px solid #000;
border-radius: 0;
background:
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M0 0h10L5 6z'/%3E%3C/svg%3E")
right .5rem center / .625rem no-repeat,
var(--background);
cursor: pointer;
}
.reference select:focus-visible {
outline: 2px solid var(--focus);
outline-offset: 2px;
}
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
/* One reading column: the full width less a gutter on anything narrower than
a laptop, and a measure that grows a step with the screen beyond that. */
a laptop, and a measure that grows a step with the screen beyond that. The
sidebar sizes itself from the same two values. */
:root { --gutter: .75rem; }
main {
margin: 1.5rem auto;
padding: 0 .75rem;
padding: 0 var(--gutter);
}
@media (min-width: 1200px) {
main { max-width: 760px; }
:root { --measure: 760px; }
main { max-width: var(--measure); }
}
@media (min-width: 1400px) {
main { max-width: 880px; }
:root { --measure: 880px; }
}
/* Only A-Z and a-z: the compiler puts a drop cap on a chapter only when it
@@ -196,7 +381,7 @@ main {
gap: .4em;
font-size: 2rem;
font-weight: 400;
color: var(--text-muted);
color: var(--text);
margin: 5rem 0 .75rem;
}
+10 -2
View File
@@ -11,6 +11,9 @@
'page.njk',
'book.njk',
'index.njk',
'sidebar.njk',
'reference.njk',
'verses.njk',
'version.njk',
'notice.njk'
];
@@ -57,11 +60,16 @@
return {
render: render,
compose: function (fragment) {
compose: function (fragment, chrome) {
var title = (TITLE_RE.exec(fragment) || ['', ''])[1].trim();
var content = fragment.replace(TITLE_RE, '');
var name = (TEMPLATE_RE.exec(content) || ['', 'page'])[1] + '.njk';
return render(name, { title: title, content: content });
return render(name, {
title: title,
content: content,
sidebar: chrome.sidebar,
reference: chrome.reference
});
},
notice: function (heading, text) {
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 450 450" width="450" height="450" fill="currentColor"><path d="M222.2 28.1C219.6 28.8 218.4 30.3 216.5 35.7C216.2 36.5 215.3 38 214.5 39C213.8 40.1 212.9 41.4 212.5 42.1C212.2 42.8 210.9 44.3 209.7 45.5C205.1 49.8 202.6 53 201.8 55.4C201.4 56.7 200.7 58.4 200.2 59.3C199.3 61.5 199.2 63.9 200.1 66.8C201.7 71.9 200.1 75.4 194.9 77.7C193.6 78.2 192.1 79.1 191.5 79.5C190.4 80.3 186.8 84.9 186 86.5C185.7 87 184.4 88.5 183 90C180.5 92.5 180.2 92.7 176.3 94.4C174.1 95.3 171.3 96.3 170.2 96.4C167.9 96.8 167.1 97.3 166.5 98.9C165.8 100.6 166.4 102 168.7 104.6C169.9 105.9 170.9 107.1 170.9 107.2C170.9 107.4 172 108.3 173.5 109.2C176.6 111.4 178.9 111.9 184.4 111.9C187.5 111.9 188.8 112.1 190.3 112.6C191.4 112.9 193.5 113.4 195.1 113.6C199.2 114.1 200.4 115.1 200 117.9C199.7 120.5 198.2 121.7 194.7 122C192.2 122.2 191.6 122.4 189.9 123.5C188.8 124.2 187 125 186 125.3C182.9 126.1 179.2 128.3 175 131.7C172.3 133.8 169.6 133.5 169.6 131.1C169.6 128.5 172.5 121.7 175 118.7C176.5 116.9 176.6 115.9 175.4 114.1C174.3 112.4 174.3 112.4 171.1 111.4C168.5 110.5 168 110.5 162.2 110.5C154.9 110.4 150.6 111.2 147.4 113.1C143.7 115.3 142.7 115 138.8 111C136.9 109 135.8 108.1 134.6 107.7C133.8 107.4 131.9 106.6 130.4 106C128.2 105.2 127.1 104.9 124.4 104.8C121.8 104.6 120.2 104.3 117.3 103.4C109.9 101 108.1 102.3 109 109.2C109.3 111.4 109.3 112.9 109 115.4C108.5 119.7 108.8 128 109.5 130.3C110.1 132 110.6 132.9 112.6 135.3C114.2 137.3 114.1 141 112.4 144.7C109.2 151.7 108.5 155.7 109.4 163.6C109.6 166 109.8 166.6 110.6 167.4C112.9 169.8 114.2 169.9 119.6 167.7C123.2 166.2 127.6 165.4 132.8 165.3C136.3 165.2 136.7 165.6 135.2 168.5C133.4 172.1 129.2 181.9 128.3 184.5C122.8 201.5 123.9 200 117.2 200C109.9 200 104.3 199.2 102.4 198.1C99.9 196.5 98.9 194.2 99.1 190.6C99.4 186.3 99.9 183.4 101 178.6C102.3 173.3 102.3 171.3 100.7 166.4C98.4 159.3 94 155.1 90.5 156.8C89.5 157.3 87.2 160 85.2 163.1C84.2 164.7 80.9 167.8 79.5 168.5C79 168.8 77.6 169.3 76.3 169.6C75 169.9 73.4 170.5 72.6 170.9C71.8 171.3 70.4 172 69.4 172.4C67.4 173.2 64.2 175.9 62 179C61.3 180 60.6 180.8 60.3 181C59.5 181.5 58.9 183.5 58.6 186.4C58.3 190.9 57.7 195.3 57.3 196.2C56.8 197.4 54.2 198.4 52 198.2C49.6 197.8 46.5 198.7 42 200.8C38.3 202.6 38.3 202.6 33.4 207.9C28.3 213.3 25.6 215.5 21.2 217.7C18.4 219.2 17.7 220.1 17.5 222.3C17 226 19.3 229 23.9 230.6C27.3 231.9 29.8 234.3 32.3 238.5C34.2 241.7 35.8 243.3 38.7 245C39.6 245.5 40.9 246.5 41.5 247.2C42.7 248.4 47.7 251.5 49.8 252.3C50.3 252.6 52.4 253.1 54.4 253.6C60.1 254.9 60.8 255.4 61.2 258.9C61.4 260.5 61.7 261.1 63 262.8C63.8 263.9 64.8 265.2 65.1 265.6C65.7 266.4 69.2 269 70.8 269.9C71.2 270.1 72.5 270.5 73.8 270.9C78.6 272.2 83.8 276.5 87.1 281.8C88.1 283.4 89.3 285.1 89.7 285.6C91.1 287.1 93.4 286.8 95 284.6C95.4 284.1 96.4 282.9 97.2 282C98.6 280.3 99.6 278.2 100.5 274.5C101.1 272.3 101.1 271 100.5 260.2C100.2 256 100.5 254.7 102.1 252.9C103.7 251.1 107.4 250.4 115.9 250.4C120.1 250.4 120.1 250.4 121.3 251.4C122.8 252.7 123.9 254.5 125.4 258.3C126 259.9 127.3 262.4 128.1 263.8C128.9 265.2 129.7 267 129.9 267.7C130.3 269.2 133.7 274.7 135.5 276.7C136.8 278.1 136.9 278.9 136 279.7C134.2 281.3 121 278.6 114.4 275.2C112.9 274.4 111.7 274.4 110.4 275.1C108.5 276 108.4 276.5 108.4 283.3C108.4 290 108.8 293.9 110.2 299.7C111.6 305.2 111.5 305.8 107.8 310.1C105.2 313.2 105 314.4 105.2 321.6C105.4 327.5 105.4 327.5 104.4 330.5C103 334.4 103.1 335.7 104.4 337.3C106.4 339.5 107.5 339.8 115.9 340.1C120 340.3 124.2 340.5 125.3 340.6C127.8 340.9 130.2 340.2 132.6 338.5C136.5 335.7 137.4 335.6 142.1 337.1C147.1 338.6 148 338.8 151.6 338.4C153.5 338.2 157.4 338 160.3 337.9C168.4 337.7 174.2 336.4 175 334.5C175.4 333.6 174.4 330.3 172.3 325.9C170.3 321.7 169.2 314.8 170.3 313.6C171.2 312.7 173.7 313.1 176 314.6C178.9 316.5 183.5 318.5 187.4 319.5C189.1 319.9 191.1 320.5 191.8 320.8C192.5 321 193.6 321.3 194.2 321.3C195.5 321.3 197.7 322.3 198.7 323.3C201.4 326.3 202.5 338.2 200.3 342.6C198.8 345.8 197.2 347.1 195.2 347.1C192.3 347.1 188.6 346.3 185 344.8C180.9 343.1 177.3 342.7 172.6 343.4C167.8 344.2 165.9 345.4 165.6 348C165.4 349.5 165.5 349.6 167.3 351.3C170.6 354.4 172.4 357.7 175.3 365.8C176 367.8 177 370.1 177.5 370.9C178.4 372.5 182.4 376.3 184.8 377.9C186.8 379.2 196.4 383.4 197.4 383.4C199.4 383.4 201 386.4 201.5 390.8C202 395.4 202.6 396.8 205.2 400.1C206.5 401.8 208.1 403.5 208.8 404.1C209.5 404.6 211.4 406.4 213.1 408C214.8 409.5 216.9 411.4 217.8 412.1C218.7 412.8 220.2 414.1 220.9 415.2C221.7 416.2 223.5 418.2 224.9 419.7C227.3 422.1 227.7 422.4 228.8 422.4C231.3 422.4 232.8 421.1 233.8 418.1C234.2 416.9 234.9 415.3 235.4 414.5C235.9 413.8 236.6 412.5 236.9 411.7C237.8 409.4 238.2 408.8 240.3 407.5C241.3 406.8 242.3 405.9 242.6 405.6C242.8 405.3 244.5 403.6 246.3 401.9C248.2 400.2 250 398.3 250.5 397.6C251.3 396.5 252.6 391.7 253.1 388.6C253.5 385.6 254 385 259 382.6C264.2 380.1 267.9 377.1 268.7 374.5C268.9 373.8 269.6 372.3 270.1 371.2C270.7 370.1 271.4 Line truncated

After

Width:  |  Height:  |  Size: 22 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 600" fill="currentColor" fill-rule="evenodd"><path d="M0 0H100V250H250V0H600V350H350V500H600V600H0Z M350 100V250H500V100Z M100 350V500H250V350Z"/></svg>

After

Width:  |  Height:  |  Size: 210 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 600" fill="currentColor" fill-rule="evenodd"><g transform="translate(600,0) scale(-1,1)"><path d="M0 0H100V250H250V0H600V350H350V500H600V600H0Z M350 100V250H500V100Z M100 350V500H250V350Z"/></g></svg>

After

Width:  |  Height:  |  Size: 258 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 600" fill="currentColor" fill-rule="evenodd"><g transform="translate(0,600) scale(1,-1)"><path d="M0 0H100V250H250V0H600V350H350V500H600V600H0Z M350 100V250H500V100Z M100 350V500H250V350Z"/></g></svg>

After

Width:  |  Height:  |  Size: 258 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 600" fill="currentColor" fill-rule="evenodd"><g transform="translate(600,600) scale(-1,-1)"><path d="M0 0H100V250H250V0H600V350H350V500H600V600H0Z M350 100V250H500V100Z M100 350V500H250V350Z"/></g></svg>

After

Width:  |  Height:  |  Size: 261 B

+102 -2
View File
@@ -24,6 +24,11 @@ const PRECACHE = [
'./css/app.css',
'./fonts/DayLeafInitials.woff2',
'./ornaments/group-l-49.svg',
'./ornaments/mural-311a.svg',
'./ornaments/uniform-rule-corner-tl.svg',
'./ornaments/uniform-rule-corner-tr.svg',
'./ornaments/uniform-rule-corner-bl.svg',
'./ornaments/uniform-rule-corner-br.svg',
'./js/htmx.min.js'
].concat(TEMPLATE_URLS);
@@ -123,6 +128,71 @@ const view = createRenderer(readTemplate);
const router = new Router(new URL('./', location.href).pathname);
const VERSES_URL = './verses.json';
let verses = null;
function readVerses() {
if (!verses) {
verses = readText(pageRequest(VERSES_URL))
.then(JSON.parse)
.catch((err) => { verses = null; throw err; });
}
return verses;
}
function parseAnchor(id) {
const chapter = /^ch(\d+)$/.exec(id || '');
if (chapter) return { chapter: +chapter[1], verse: null };
const verse = /^v(\d+)_[0-9-]+$/.exec(id || '');
if (verse) return { chapter: +verse[1], verse: id };
return null;
}
function verseLabels(chapters, number) {
const entry = chapters[number - 1];
if (typeof entry !== 'number') return entry || [];
const out = [];
for (let i = 1; i <= entry; i++) out.push(String(i));
return out;
}
function referenceFor(map, code, anchor) {
const chapters = map && Object.prototype.hasOwnProperty.call(map, code)
? map[code] : null;
if (!chapters || !chapters.length) return null;
if (!chapters.some((entry) => (typeof entry === 'number' ? entry : entry.length))) {
return null;
}
const at = parseAnchor(anchor) || { chapter: 1, verse: null };
const number = at.chapter >= 1 && at.chapter <= chapters.length ? at.chapter : 1;
const verse = at.chapter === number ? at.verse : null;
return {
book: code,
chapter: 'ch' + number,
chapters: chapters.map((_, i) => ({ id: 'ch' + (i + 1), label: String(i + 1) })),
verse: verse,
verses: verseLabels(chapters, number)
.map((label) => ({ id: 'v' + number + '_' + label, label: label }))
};
}
function bookOf(url) {
return url ? router.relative(url).slice(1) : '';
}
async function referenceOf(code, anchor) {
if (!code) return null;
try {
return referenceFor(await readVerses(), code, anchor);
} catch (err) {
return null;
}
}
const OFFLINE_NOTICE = ['Not available offline',
'This page has not been opened on this device, so there is no copy stored ' +
'here. Reconnect and try again, or open one you have read before.'];
@@ -143,9 +213,13 @@ async function servePage(path, code, call) {
if (!code) {
fragment += '\n' + await view.render('version.njk', { version: VERSIONS });
}
if (!call.navigate && !call.restore) return fragment;
const chrome = { sidebar: !!code, reference: await referenceOf(code, null) };
if (!call.navigate && !call.restore) {
return fragment + '\n'
+ await view.render('sidebar.njk', Object.assign({ oob: true }, chrome));
}
try {
return await view.compose(fragment);
return await view.compose(fragment, chrome);
} catch (err) {
console.error('compose failed: ' + err.message);
return fragment;
@@ -156,6 +230,32 @@ router.get('/', (call) => servePage('./', '', call));
router.get('/:page', (call) =>
servePage('./' + call.params.page, call.params.page, call));
router.get('/api/reference', async (call) => {
const params = call.url.searchParams;
const page = call.currentUrl();
const moved = params.has('at') ? params.get('at') : null;
const code = bookOf(page);
const reference = await referenceOf(
code, moved === null ? (page && page.hash.slice(1)) : moved);
if (moved === null) {
return view.render('sidebar.njk',
{ sidebar: !!code, reference: reference, settled: true });
}
if (!reference) return new Response('', { status: 204 });
const target = reference.verse || reference.chapter;
return new Response(await view.render('verses.njk', { reference: reference }), {
headers: {
'Content-Type': 'text/html; charset=utf-8',
'Cache-Control': 'no-store',
'HX-Reswap': 'innerHTML settle:0 show:#' + target + ':top',
'HX-Replace-Url': page.pathname + page.search + '#' + target
}
});
});
let filling = null;
let complete = false;
+14
View File
@@ -21,9 +21,23 @@
<body class="{% block body_class %}{% endblock %}"
hx-boost="true" hx-target="#content" hx-swap="innerHTML show:window:top">
{% include "sidebar.njk" %}
<main id="content" hx-history-elt>
{% block content %}{% endblock %}
</main>
<script>
// The sidebar, and on a phone its button, fade out once the page leaves the
// top, so they are out of the way while reading; the stylesheet brings the
// sidebar back under the pointer.
(function () {
var root = document.documentElement;
function update() { root.classList.toggle('scrolled', window.scrollY > 0); }
addEventListener('scroll', update, { passive: true });
update();
})();
</script>
</body>
</html>
+18
View File
@@ -0,0 +1,18 @@
<div class="reference" id="reference">
<span class="reference-book" id="reference-book">{{ reference.book }}</span>
<label class="visually-hidden" for="reference-chapter">Chapter</label>
<select class="reference-chapter" id="reference-chapter" name="at"
hx-get="api/reference" hx-target="#reference-verse"
hx-swap="innerHTML">
{%- for chapter in reference.chapters %}
<option value="{{ chapter.id }}"
{%- if chapter.id == reference.chapter %} selected{% endif %}>{{ chapter.label }}</option>
{%- endfor %}
</select>
<span class="reference-sep">:</span>
<label class="visually-hidden" for="reference-verse">Verse</label>
<select class="reference-verse" id="reference-verse" name="at"
hx-get="api/reference" hx-target="this" hx-swap="innerHTML">
{% include "verses.njk" %}
</select>
</div>
+35
View File
@@ -0,0 +1,35 @@
{# Only the /:page routes have one. The element is always there, hidden on the
others, so that a boosted link in either direction has something to swap
out of band, and so a history restore can ask for the right one. #}
<div class="sidebar-shell" id="sidebar"
{%- if not sidebar %} hidden{% endif %}
{%- if oob %} hx-swap-oob="true"{% endif %}
hx-get="api/reference"
{# htmx keeps an event only when the filter returns true exactly, so a
string is discarded. #}
hx-trigger="{% if reference and not settled %}load[!!location.hash], {% endif %}htmx:historyRestore from:body"
hx-target="this" hx-swap="outerHTML">
{# Always open: beside the text on a wide screen, and hidden by the
stylesheet on a phone until the button below opens it as a modal. Closing
the modal opens it again the plain way, so it is there after a resize.
Its box has no padding, so a click on the dialog itself is the backdrop.
autofocus has opening focus the dialog itself; otherwise it goes to the
first select, and a phone opens the select's picker with it. #}
<dialog class="sidebar" open autofocus aria-label="Contents"
hx-on:click="if (event.target === this && this.matches(':modal')) this.close();"
hx-on:close="if (!this.open) this.show();">
<div class="sidebar-body">
{%- if reference %}
{% include "reference.njk" %}
{%- endif %}
</div>
{# Takes the place of the frame's last corner on a phone, right where the
button that opened it was. #}
<button class="sidebar-fab sidebar-close" type="button" aria-label="Close"
hx-on:click="this.closest('dialog').close();"></button>
</dialog>
<button class="sidebar-fab sidebar-open" type="button" aria-label="Contents"
hx-on:click="var dialog = this.previousElementSibling;
dialog.close();
dialog.showModal();"></button>
</div>
+4
View File
@@ -0,0 +1,4 @@
{% for verse in reference.verses -%}
<option value="{{ verse.id }}"
{%- if verse.id == reference.verse %} selected{% endif %}>{{ verse.label }}</option>
{% endfor -%}