Files
2026-10-25 23:49:06 -04:00

788 lines
22 KiB
CSS

/* Colours and type for the whole app. The values are the ones Bootstrap
gave the page before it was taken out, so nothing shifts. */
/* Variable fonts, one file per style covering weights 400 to 800. They are
cut down to Latin, Greek and punctuation; Hebrew falls back to the next
font in the stack. */
@font-face {
font-family: "EB Garamond";
src: url("../fonts/EBGaramond.woff2") format("woff2");
font-weight: 400 800;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "EB Garamond";
src: url("../fonts/EBGaramond-Italic.woff2") format("woff2");
font-weight: 400 800;
font-style: italic;
font-display: swap;
}
:root {
--font-body: "EB Garamond", Georgia, "Iowan Old Style", "Palatino Linotype",
"Times New Roman", serif;
--text: #212529;
--text-muted: rgb(33 37 41 / .75);
--background: #f8f9fa;
--link: #0d6efd;
--link-hover: #0a58ca;
--focus: #0d6efd;
-webkit-text-size-adjust: 100%;
text-size-adjust: 100%;
}
@media (prefers-reduced-motion: no-preference) {
:root { scroll-behavior: smooth; }
}
*,
*::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. EB Garamond's x-height is a fifth smaller than
Georgia's, so it is set a size up to read the same. */
body {
display: flow-root;
margin: 0;
font-family: var(--font-body);
font-size: 1.1875rem;
line-height: 1.55;
color: var(--text);
background: var(--background);
-webkit-tap-highlight-color: transparent;
}
h1, h2, h3, h4, h5, h6 {
margin: 0 0 .5rem;
font-weight: 500;
line-height: 1.2;
}
/* Headings grow with the viewport up to their full size on a wide screen. */
h1 { font-size: calc(1.375rem + 1.5vw); }
h2 { font-size: calc(1.325rem + .9vw); }
h3 { font-size: calc(1.3rem + .6vw); }
h4 { font-size: calc(1.275rem + .3vw); }
h5 { font-size: 1.25rem; }
h6 { font-size: 1rem; }
@media (min-width: 1200px) {
h1 { font-size: 2.5rem; }
h2 { font-size: 2rem; }
h3 { font-size: 1.75rem; }
h4 { font-size: 1.5rem; }
}
p,
ul,
ol { margin: 0 0 1rem; }
ul,
ol { padding-left: 2rem; }
:is(ul, ol) :is(ul, ol) { margin-bottom: 0; }
/* Footnote callers sit above the line without pushing it apart. */
sup {
position: relative;
top: -.5em;
font-size: .75em;
line-height: 0;
vertical-align: baseline;
}
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, in the Schuil tiles' units as on the index. A schuil-10
sits in each corner, and brass runs along each side between them, its two
strokes in line with the corner's arm, 8 and 34 units in from the edge,
and standing off from the tiles as the index's brass does. The unit is
the index's at its full size, on every screen. */
.sidebar-shell {
--u: .22px;
--corner: calc(var(--u) * 150);
--stroke: calc(var(--u) * 8);
--outer: calc(var(--u) * 8);
--inner: calc(var(--u) * 34);
--run: calc(100% - var(--corner) * 2);
--corners:
url("../ornaments/schuil-10-tl.svg") left top / var(--corner) no-repeat,
url("../ornaments/schuil-10-tr.svg") right top / var(--corner) no-repeat,
url("../ornaments/schuil-10-bl.svg") left bottom / var(--corner) no-repeat,
url("../ornaments/schuil-10-br.svg") right bottom / var(--corner) no-repeat;
--margin: 1.5rem;
--fab: 4rem;
}
/* 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:
var(--corners),
linear-gradient(#000 0 0) var(--corner) var(--outer) / var(--run) var(--stroke) no-repeat,
linear-gradient(#000 0 0) var(--corner) var(--inner) / var(--run) var(--stroke) no-repeat,
linear-gradient(#000 0 0) var(--corner) calc(100% - var(--outer)) / var(--run) var(--stroke) no-repeat,
linear-gradient(#000 0 0) var(--corner) calc(100% - var(--inner)) / var(--run) var(--stroke) no-repeat,
linear-gradient(#000 0 0) var(--outer) var(--corner) / var(--stroke) var(--run) no-repeat,
linear-gradient(#000 0 0) var(--inner) var(--corner) / var(--stroke) var(--run) no-repeat,
linear-gradient(#000 0 0) calc(100% - var(--outer)) var(--corner) / var(--stroke) var(--run) no-repeat,
linear-gradient(#000 0 0) calc(100% - var(--inner)) var(--corner) / var(--stroke) 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);
}
}
/* The title over a rule, then the selects, then the link back to the index
pushed down to the foot of the frame by the row between that takes up the
rest. */
.sidebar-body {
display: grid;
grid-template-rows: auto auto auto 1fr auto;
height: 100%;
padding: calc(var(--corner) + 1rem);
}
.sidebar-title {
font-size: 1.5rem;
text-align: center;
}
/* A hairline, like the selects' boxes. */
.sidebar-rule {
width: 100%;
margin: 0 0 1rem;
border: 0;
border-top: 1px solid #000;
}
.sidebar-index {
grid-row: 5;
justify-self: center;
}
.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
marked out by the frame's four corners. 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: var(--fab);
height: var(--fab);
padding: 0;
color: #000;
border: 0;
background: var(--corners), var(--background);
cursor: pointer;
}
.sidebar-fab::before {
content: "";
display: block;
width: 60%;
height: 60%;
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;
}
/* Heads that lead the brass into the button's box: schuil-07 along the
foot, its curl against the box, and schuil-08 turned to run up the side
from the box's head. Each stands off the brass as schuil-13's heads
stand off its bar: its foot stroke, 10 units up the tile, clears the
brass's inner stroke by about 17 units. */
.sidebar:modal::before,
.sidebar:modal::after {
content: "";
position: absolute;
width: calc(var(--u) * 300);
height: calc(var(--u) * 100);
background: #000;
mask-size: 100% 100%;
mask-repeat: no-repeat;
pointer-events: none;
}
.sidebar:modal::before {
right: var(--fab);
bottom: calc(var(--u) * 50);
mask-image: url("../ornaments/schuil-07.svg");
}
/* Turned a quarter left about its foot's left end, which comes to sit at
the box's head, its foot to the right. */
.sidebar:modal::after {
right: calc(var(--u) * (50 - 300));
bottom: var(--fab);
transform-origin: 0 100%;
transform: rotate(-90deg);
mask-image: url("../ornaments/schuil-08.svg");
}
}
/* Each label on the left with its select set flush to the right. */
.reference {
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
gap: .5rem 1rem;
}
/* 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;
}
/* 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. The
sidebar sizes itself from the same two values. */
:root { --gutter: .75rem; }
main {
margin: 1.5rem auto;
padding: 0 var(--gutter);
}
@media (min-width: 1200px) {
:root { --measure: 760px; }
main { max-width: var(--measure); }
}
@media (min-width: 1400px) {
:root { --measure: 880px; }
}
/* Only A-Z and a-z: the compiler puts a drop cap on a chapter only when it
opens on a letter. */
@font-face {
font-family: "Day Leaf Initials";
src: url("../fonts/DayLeafInitials.woff2") format("woff2");
font-display: swap;
}
/* Both of these are links to their own id. They keep the text colour they
had as plain numbers, since an underlined blue verse number in the middle
of a sentence reads as part of the text. */
.v,
.chapter-number a {
color: var(--text);
text-decoration: none;
}
/* Garamond's numbers are old-style by default; in a superscript or beside
the flourishes they sink below the line, so these use lining ones. */
.v,
.chapter-number { font-variant-numeric: lining-nums; }
.v {
font-size: .7em;
font-weight: 700;
vertical-align: super;
line-height: 0;
margin-right: .15em;
user-select: none;
}
.v:focus-visible,
.chapter-number a:focus-visible {
outline: 2px solid var(--focus);
outline-offset: 2px;
border-radius: 2px;
}
/* Three lines deep. The verse number it replaces is still there for its id
and the keyboard, as the chapter number above already says where verse 1
is -- the way a printed Bible drops it. */
.dropcap {
float: left;
font-family: "Day Leaf Initials", var(--font-body);
font-size: 5em;
line-height: .8;
margin: .1em .08em 0 0;
}
/* The first line pulled in closer to the letter than the lines below it, so
the rest of the first word reads as part of it. .chapter makes this beat
the list's and the poetry's own first-line indents. */
.chapter :is(p, li):has(.dropcap) { text-indent: -.4em; }
/* The rest of the first line in small caps, as a lead-in from the initial.
The letter can sit inside a span, such as .wj. */
:is(p, li):has(.dropcap)::first-line {
font-variant: small-caps;
letter-spacing: .05em;
}
.v:has(+ .dropcap):not(:focus-visible) {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
/* The book's title, centred with room around it. --u shrinks with the header
on a phone. */
.book-header {
container-type: inline-size;
display: grid;
place-items: center;
margin: 10rem 0;
}
.book-header h1 {
max-width: 100%;
margin: 0;
padding: .6em 1.2em;
text-align: center;
font-weight: 400;
line-height: 1.15;
text-wrap: balance;
}
/* The lines above and below the name itself, such as "The First Book of
Moses, Commonly Called", on lines of their own in small caps. */
.book-header :is(.mt1, .mt2, .mt3, .mt4) {
display: block;
font-size: .45em;
font-variant: small-caps;
letter-spacing: .08em;
margin: .3em 0;
}
.book-header .mt { display: block; }
/* Centred between two flourishes (Group L 49), the left one mirrored so
their curls point in at the number. The index's testaments are set the
same way. */
.chapter-number,
.index h2 {
display: flex;
align-items: center;
justify-content: center;
gap: .4em;
font-size: 2rem;
font-weight: 400;
color: var(--text);
margin: 5rem 0 .75rem;
}
.chapter-number::before,
.chapter-number::after,
.index h2::before,
.index h2::after {
content: "";
flex: none;
width: 3.2em;
height: .8em;
/* Flex centres on the line box. In EB Garamond its middle is .35em above
the baseline and a lining numeral's is .33em, so this lowers the
flourish the difference to sit level with the number. */
translate: 0 .05em;
background: currentColor;
mask: url("../ornaments/group-l-49.svg") center / contain no-repeat;
}
.chapter-number::before,
.index h2::before { transform: scaleX(-1); }
/* The export marks these up as list items but hides the marker, indenting the
first line instead, which is how a printed Bible sets them -- Ezra 10:18-44
and the like are prose, not a bulleted list. Only the glossary and front
matter items it actually marks, with a square. The list markup is worth
keeping for what it means; the bullet is not. */
.list {
list-style: none;
padding-left: 0;
}
.list .li { text-indent: 2em; }
.list .ili {
list-style: square;
margin-left: 2em;
}
.poetry { margin-bottom: 1rem; }
.poetry .line {
margin: 0;
padding-left: 1.5em;
text-indent: -1.5em;
}
.poetry .line[data-indent="2"] { padding-left: 3em; }
.poetry .line[data-indent="3"] { padding-left: 4.5em; }
/* A poetry line's indent is padding, which a float covers. Each line after
the drop cap becomes its own block beside the letter, so its indent counts
from the letter's edge; the drop cap's own line has no indent of its own. */
.poetry .line:has(.dropcap) { padding-left: 0; }
.poetry .line:not(:has(.dropcap)) { display: flow-root; }
.d {
font-style: italic;
color: var(--text-muted);
}
.footnotes {
font-size: .875em;
color: var(--text-muted);
}
.footnotes-title { font-size: 1em; }
.marker { font-weight: 700; }
.noteref,
.marker { font-variant-emoji: text; }
.chapter,
.v { scroll-margin-top: 1rem; }
.index ul {
list-style: none;
padding-left: 0;
}
/* A testament's name is longer than a chapter number, so on a phone it
keeps to one line and the flourishes narrow to make room. */
.index h2 { white-space: nowrap; }
.index h2::before,
.index h2::after { flex-shrink: 1; }
/* Each book's chapter count set to the right, led to by a row of periods.
The count has a column three figures wide, and the periods run from its
side, so they line up from row to row. */
.index li {
display: flex;
align-items: baseline;
}
.index .count {
display: grid;
grid-template-columns: 1fr 3ch;
justify-items: end;
align-items: baseline;
flex: 1;
min-width: 0;
font-variant-numeric: tabular-nums;
}
.index .count::before {
content: ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .";
justify-self: stretch;
min-width: 0;
margin: 0 .4em;
overflow: hidden;
white-space: nowrap;
direction: rtl;
color: var(--text-muted);
}
.book .p,
.book .m,
.book .pi,
.book .ip,
.book .li,
.book .ili,
.footnotes .note {
text-align: justify;
text-justify: inter-word;
-webkit-hyphens: auto;
hyphens: auto;
text-wrap: pretty;
}
@media (max-width: 767.98px) {
body { font-size: 1.0625rem; }
.book { line-height: 1.5; }
.book .p,
.book .m,
.book .pi,
.book .ip,
.book .li,
.book .ili,
.footnotes .note { word-spacing: -.04em; }
}
.poetry .line,
.book .sp,
.book .d {
text-align: left;
-webkit-hyphens: manual;
hyphens: manual;
}
/* The index's frame, set in the Schuil tiles' own units, a pica to 100. A
unit is the specimen's size where the reading column is at its narrowest
on a laptop, and smaller below that, so the band of heads under the title
always fits with a little brass either side. */
.home { container-type: inline-size; }
.schuil {
--u: min(.22px, 100cqw / 2700);
position: relative;
min-height: calc(var(--u) * 2600);
padding: calc(var(--u) * 1600) calc(var(--u) * 300) calc(var(--u) * 450);
}
.schuil-ornaments {
position: absolute;
inset: 0;
color: #000;
pointer-events: none;
}
.schuil-half {
position: absolute;
inset: 0 50% 0 0;
}
.schuil-right {
inset: 0 0 0 50%;
transform: scaleX(-1);
}
/* A tile goes where the specimen put it: the point (--x, --y) from the
corner it hangs from, then turned by --m about its own corner, as an SVG
transform would. */
.schuil-tile {
position: absolute;
left: calc(var(--ax, 0) * 100%);
top: calc(var(--ay, 0) * 100%);
width: calc(var(--w) * var(--u));
height: calc(var(--h) * var(--u));
transform-origin: 0 0;
transform: translate(calc(var(--x) * var(--u)), calc(var(--y) * var(--u)))
matrix(var(--m, 1, 0, 0, 1, 0, 0));
background: currentColor;
mask-position: center;
mask-size: 100% 100%;
mask-repeat: no-repeat;
}
.schuil-01 { --w: 50; --h: 50; mask-image: url("../ornaments/schuil-01.svg"); }
.schuil-06 { --w: 100; --h: 100; mask-image: url("../ornaments/schuil-06.svg"); }
.schuil-10 { --w: 150; --h: 150; mask-image: url("../ornaments/schuil-10.svg"); }
.schuil-11 { --w: 150; --h: 125; mask-image: url("../ornaments/schuil-11.svg"); }
.schuil-12 { --w: 150; --h: 125; mask-image: url("../ornaments/schuil-12.svg"); }
.schuil-13 { --w: 250; --h: 300; mask-image: url("../ornaments/schuil-13.svg"); }
.schuil-14 { --w: 300; --h: 600; mask-image: url("../ornaments/schuil-14.svg"); }
.schuil-15 { --w: 350; --h: 350; mask-image: url("../ornaments/schuil-15.svg"); }
.schuil-16 { --w: 350; --h: 700; mask-image: url("../ornaments/schuil-16.svg"); }
.schuil-07 { --w: 300; --h: 100; mask-image: url("../ornaments/schuil-07.svg"); }
.schuil-08 { --w: 300; --h: 100; mask-image: url("../ornaments/schuil-08.svg"); }
.schuil-brass-v { --w: 50; --h: 100; mask-image: url("../ornaments/schuil-brass-v.svg"); }
/* Brass rule that stretches: two 8 unit strokes in a six point body, drawn
as gradients so they run any length without a seam. A horizontal run
stands in from either side by --x, or by --r on the right; a vertical one
from the head by --y and the foot by --b. */
.schuil-rule {
position: absolute;
left: calc(var(--x) * var(--u));
right: calc(var(--r, var(--x)) * var(--u));
top: calc(var(--ay, 0) * 100% + var(--y) * var(--u));
height: calc(var(--u) * 50);
background:
linear-gradient(currentColor 0 0) 0 calc(var(--u) * 8) / 100% calc(var(--u) * 8) no-repeat,
linear-gradient(currentColor 0 0) 0 calc(var(--u) * 34) / 100% calc(var(--u) * 8) no-repeat;
}
.schuil-rule-v {
position: absolute;
left: calc(var(--x) * var(--u));
top: calc(var(--y) * var(--u));
bottom: calc(var(--b) * var(--u));
width: calc(var(--u) * 50);
background:
linear-gradient(currentColor 0 0) calc(var(--u) * 8) 0 / calc(var(--u) * 8) 100% no-repeat,
linear-gradient(currentColor 0 0) calc(var(--u) * 34) 0 / calc(var(--u) * 8) 100% no-repeat;
}
/* Set in the panel at the head, the panel's inside 2100 units by 400 at
the specimen's size. */
.schuil-title {
position: absolute;
inset: calc(var(--u) * 475) calc(var(--u) * 475) auto;
height: calc(var(--u) * 400);
display: flex;
align-items: center;
justify-content: center;
margin: 0;
font-size: calc(var(--u) * 220);
font-weight: 400;
line-height: 1;
letter-spacing: .06em;
text-transform: uppercase;
}
/* On a phone the frame is only the panel round the title, moved up to the
head, at full width and full size. A pair of heads stands on it and
another hangs from it, each clear of the brass by about 17 units as the
sidebar's heads are. The title keeps to one line, smaller if the panel is
narrower than it. The space over the frame, with main's margin, matches
the space under it to the first testament: that one's 5rem margin and
the leading over its capitals. */
.schuil-crest { display: none; }
@media (max-width: 767.98px) {
.schuil {
--u: .22px;
min-height: 0;
margin-top: 4rem;
padding: calc(var(--u) * 700) 0 0;
}
.schuil-ornaments :is(.schuil-tile, .schuil-rule, .schuil-rule-v):not(.schuil-panel, .schuil-crest) {
display: none;
}
.schuil-crest { display: block; }
.schuil-panel { margin: calc(var(--u) * -325) 0 0 calc(var(--u) * -425); }
.schuil-rule.schuil-panel { margin-right: calc(var(--u) * -425); }
.schuil-title {
inset: calc(var(--u) * 150) calc(var(--u) * 50) auto;
font-size: min(var(--u) * 220, (100cqw - var(--u) * 100) / 7);
white-space: nowrap;
}
}
.app-version {
margin: 3rem 0 .5rem;
text-align: center;
}
.app-version p {
margin: .25rem 0 0;
font-size: .875em;
color: var(--text-muted);
}
/* The page shown while the service worker installs. */
.boot {
margin: 0 auto;
padding: 3rem .75rem;
text-align: center;
}
.boot p { color: var(--text-muted); }
.spinner {
display: inline-block;
width: 2rem;
height: 2rem;
margin-bottom: 1rem;
border: .25em solid #6c757d;
border-right-color: transparent;
border-radius: 50%;
animation: spin .75s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.boot .boot-warning {
display: inline-block;
padding: 1rem;
color: #664d03;
background: #fff3cd;
border: 1px solid #ffe69c;
border-radius: .375rem;
}