Files
bible/assets/css/app.css
T
2026-10-22 23:48:03 -04:00

536 lines
14 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. */
:root {
--font-body: 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. */
body {
display: flow-root;
margin: 0;
font-family: var(--font-body);
font-size: 1rem;
line-height: 1.5;
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. 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. 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;
}
.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: .06em .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 {
--u: min(1rem, 100cqi / 22);
container-type: inline-size;
display: grid;
place-items: center;
padding: calc(var(--u) * 4.5) calc(var(--u) * 3.5);
margin: 1rem 0 2rem;
}
.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. */
.chapter-number {
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 {
content: "";
flex: none;
width: 3.2em;
height: .8em;
/* Flex centres on the line box, which leaves room below for descenders;
numerals have none, so this lifts the flourish to their middle. */
translate: 0 -.1em;
background: currentColor;
mask: url("../ornaments/group-l-49.svg") center / contain no-repeat;
}
.chapter-number::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: .875rem;
color: var(--text-muted);
}
.footnotes-title { font-size: 1rem; }
.marker { font-weight: 700; }
.noteref,
.marker { font-variant-emoji: text; }
.chapter,
.v { scroll-margin-top: 1rem; }
.index ul {
list-style: none;
padding-left: 0;
}
.index .count { display: none; }
.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) {
.book { line-height: 1.45; }
.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;
}
.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;
}