/* 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; }