← typeset typeset.css Download

typeset.css

The CSS implementation. Everything is scoped to .typeset, so it cannot leak into surrounding markup. Drop it in and add the class.

Fonts are not included. The page must load EB Garamond, Source Sans 3 and IBM Plex Mono itself. For a document you send, self-host and embed them rather than fetching from a CDN at print time — and note that Google's build of EB Garamond has no real small caps, so the browser synthesises them. Browse the font files →

1072 lines · 31.6 KB

1/* ==========================================================================
2 typeset.css — a print stylesheet for essays, letters, and reports.
3 Units are in points. Everything is scoped to `.typeset`.
4 ========================================================================== */
5
6/*! @s tokens :: Tokens */
7.typeset {
8 /* Families */
9 --ts-serif: "EB Garamond", "Iowan Old Style", Palatino, Georgia, serif;
10 --ts-sans: "Source Sans 3", "Helvetica Neue", Helvetica, Arial, sans-serif;
11 --ts-mono: "IBM Plex Mono", "SF Mono", Menlo, Consolas, monospace;
12
13 /* Scale — 11pt base, ~1.2 ratio */
14 --ts-xs: 8pt;
15 --ts-sm: 9.5pt;
16 --ts-base: 11pt;
17 --ts-h4: 12pt;
18 --ts-h3: 14pt;
19 --ts-h2: 18pt;
20 --ts-h1: 24pt;
21
22 /* Rhythm */
23 --ts-leading: 1.45;
24 --ts-leading-tight: 1.2;
25 --ts-space: 11pt; /* one line of base leading, near enough */
26 --ts-measure: 33em; /* ~66 characters at 11pt serif */
27
28 /* Ink — never pure black: it blooms on laser and glares in a PDF */
29 --ts-ink: #1a1a1a;
30 --ts-ink-muted: #5a5a5a;
31 --ts-ink-faint: #8a8a8a;
32 --ts-rule: #c9c4bd;
33 --ts-rule-strong: #6f6a64;
34 --ts-wash: #f4f1ec;
35 --ts-accent: #7a1f1f; /* oxblood: reads as dark grey in greyscale */
36}
37/*! @e */
38
39/*! @s foundation :: Foundation */
40.typeset {
41 font-family: var(--ts-serif);
42 font-size: var(--ts-base);
43 font-variant-numeric: oldstyle-nums proportional-nums;
44 line-height: var(--ts-leading);
45 color: var(--ts-ink);
46 text-rendering: optimizeLegibility;
47 font-kerning: normal;
48 font-feature-settings: "liga" 1, "kern" 1;
49}
50
51/* Measure is a property of the text column, not the page. */
52.typeset > * {
53 max-width: var(--ts-measure);
54}
55
56.typeset--wide { --ts-measure: 40em; }
57.typeset--narrow { --ts-measure: 27em; }
58/*! @e */
59
60/*! @s page :: Page setup */
61@page {
62 size: A4; /* or: Letter */
63 margin: 25mm 22mm 25mm 22mm;
64
65 /* Margin boxes need a paged-media engine: Paged.js, WeasyPrint, Prince.
66 Chrome's own print dialog ignores them. */
67 @top-center {
68 content: string(ts-running-head);
69 font: 8pt/1 "Source Sans 3", sans-serif;
70 letter-spacing: 0.08em;
71 text-transform: uppercase;
72 color: #8a8a8a;
73 }
74 @bottom-center {
75 content: counter(page);
76 font: 9pt/1 "EB Garamond", serif;
77 font-variant-numeric: oldstyle-nums;
78 color: #5a5a5a;
79 }
80}
81
82/* No running head or folio on the opening page. */
83@page :first {
84 @top-center { content: none; }
85 @bottom-center { content: none; }
86}
87
88/* Duplex binding: mirror the inner margin so the gutter stays put. */
89@page :left { margin-left: 22mm; margin-right: 28mm; }
90@page :right { margin-left: 28mm; margin-right: 22mm; }
91
92/* The running head follows the current section. */
93.typeset h2 { string-set: ts-running-head content(text); }
94/*! @e */
95
96/*! @s headings :: Headings */
97.typeset h1,
98.typeset h2,
99.typeset h3,
100.typeset h4,
101.typeset h5,
102.typeset h6 {
103 font-family: var(--ts-sans);
104 font-weight: 600;
105 line-height: var(--ts-leading-tight);
106 color: var(--ts-ink);
107
108 /* A heading belongs to what follows it, not to what precedes it. */
109 margin: calc(var(--ts-space) * 2) 0 calc(var(--ts-space) * 0.5);
110
111 /* Never strand a heading at the foot of a page. */
112 break-after: avoid;
113 break-inside: avoid;
114 text-wrap: balance;
115}
116
117.typeset h1 {
118 font-size: var(--ts-h1);
119 font-weight: 300;
120 letter-spacing: -0.015em;
121 margin-top: 0;
122}
123
124.typeset h2 {
125 font-size: var(--ts-h2);
126 letter-spacing: -0.01em;
127}
128
129.typeset h3 { font-size: var(--ts-h3); }
130
131.typeset h4 { font-size: var(--ts-h4); }
132
133/* Below h4, drop the size change and shift register instead. */
134.typeset h5 {
135 font-size: var(--ts-base);
136 font-family: var(--ts-serif);
137 font-weight: 600;
138 font-variant-caps: all-small-caps;
139 letter-spacing: 0.06em;
140}
141
142.typeset h6 {
143 font-size: var(--ts-base);
144 font-family: var(--ts-serif);
145 font-weight: 400;
146 font-style: italic;
147}
148
149/* A run-in heading: the h6 sits on the first line of its paragraph. */
150.typeset .ts-run-in {
151 display: inline;
152 margin: 0;
153}
154.typeset .ts-run-in + p { display: inline; }
155.typeset .ts-run-in::after { content: " · "; color: var(--ts-ink-faint); }
156/*! @e */
157
158/*! @s numbering :: Section numbering */
159.typeset--numbered { counter-reset: ts-h2; }
160
161.typeset--numbered h2 {
162 counter-increment: ts-h2;
163 counter-reset: ts-h3;
164}
165.typeset--numbered h2::before {
166 content: counter(ts-h2) " ";
167 color: var(--ts-ink-faint);
168 font-variant-numeric: lining-nums tabular-nums;
169}
170
171.typeset--numbered h3 { counter-increment: ts-h3; }
172.typeset--numbered h3::before {
173 content: counter(ts-h2) "." counter(ts-h3) " ";
174 color: var(--ts-ink-faint);
175 font-variant-numeric: lining-nums tabular-nums;
176}
177/*! @e */
178
179/*! @s paragraphs :: Paragraphs */
180.typeset {
181 /* Paragraph separation is one decision expressed as two values: a gap, or an
182 indent, never both. A template sets these; nothing else needs to know. */
183 --ts-para-gap: var(--ts-space);
184 --ts-para-indent: 0;
185}
186
187.typeset--indented {
188 --ts-para-gap: 0;
189 --ts-para-indent: 1.5em;
190}
191
192.typeset p {
193 margin: 0 0 var(--ts-para-gap);
194 text-indent: var(--ts-para-indent);
195 orphans: 2; /* no single line left at the foot of a page */
196 widows: 2; /* no single line carried to the head of the next */
197}
198
199/* The first paragraph after a heading, block or break is flush: the thing above
200 it has already marked the start.
201
202 Enumerated rather than written as `:is(...)`. Paged.js rewrites every
203 stylesheet through its own CSS parser, which does not understand `:is()`: it
204 splits the selector on the commas INSIDE the argument list and emits fragments
205 like ` .ts-callout)+p`, which throw on querySelectorAll and abort pagination
206 entirely — a blank document, not a degraded one. Keep this list flat. */
207.typeset h1 + p,
208.typeset h2 + p,
209.typeset h3 + p,
210.typeset h4 + p,
211.typeset h5 + p,
212.typeset h6 + p,
213.typeset blockquote + p,
214.typeset figure + p,
215.typeset pre + p,
216.typeset table + p,
217.typeset .ts-break + p,
218.typeset .ts-callout + p,
219.typeset > p:first-child {
220 text-indent: 0;
221}
222/*! @e */
223
224/*! @s justification :: Justification & hyphenation */
225/* Justification without hyphenation opens rivers of white space.
226 Both, or neither. And `lang` must be set — hyphenation is per-language. */
227.typeset--justified {
228 text-align: justify;
229 text-align-last: left; /* the last line is never stretched */
230 text-justify: inter-word;
231 hyphens: auto;
232 hyphenate-limit-chars: 6 3 3; /* min word, before break, after break */
233 -webkit-hyphenate-limit-before: 3;
234 -webkit-hyphenate-limit-after: 3;
235 hyphenate-limit-lines: 2; /* never stack 3 hyphens in a row */
236}
237
238/* Ragged right. This is the default — the point of naming it is so a document
239 can state the choice rather than express it as the absence of a class, and so
240 the two options read as siblings in the source.
241
242 `text-wrap: pretty` asks the engine to spend more effort on the rag: it
243 avoids a very short last line and evens out the right edge, which is the one
244 thing a ragged setting can get wrong. Hyphenation stays off — a hyphen exists
245 to help justification, and without justification it only breaks a word for
246 no gain. */
247.typeset--ragged {
248 text-align: left;
249 text-align-last: left;
250 hyphens: manual;
251 text-wrap: pretty;
252}
253
254/* `text-align-last` inherits as well, so a block that centres itself must
255 restore it — otherwise its last line, which for a one-line block is its only
256 line, is flushed left. See .ts-pullquote and .ts-break.
257
258 Never justify a heading, a cell, or a listing. Every other block that must
259 stay flush left — subtitle, byline, caption, address, notes — declares
260 `text-align: left` in its own rule, because a direct match always beats an
261 inherited value and its descendants then inherit `left` in turn. Relying on
262 an exception list here instead means every new block silently opts in. */
263.typeset--justified h1,
264.typeset--justified h2,
265.typeset--justified h3,
266.typeset--justified h4,
267.typeset--justified td,
268.typeset--justified pre { text-align: left; hyphens: manual; }
269/*! @e */
270
271/*! @s dropcap :: Drop cap */
272.typeset .ts-dropcap::first-letter {
273 float: left;
274 font-family: var(--ts-serif);
275 font-size: 3.05em; /* tuned to cover exactly three lines */
276 line-height: 0.86;
277 font-weight: 400;
278 color: var(--ts-accent);
279 padding: 0.02em 0.06em 0 0;
280}
281.typeset .ts-dropcap { text-indent: 0; }
282
283/* A drop cap wants the opening words in small caps to bridge the size jump. */
284.typeset .ts-lede { font-variant-caps: all-small-caps; letter-spacing: 0.04em; }
285/*! @e */
286
287/*! @s inline :: Inline emphasis */
288.typeset strong, .typeset b { font-weight: 600; }
289.typeset em, .typeset i { font-style: italic; }
290.typeset em em, .typeset em i { font-style: normal; } /* nested emphasis flips */
291.typeset strong em, .typeset em strong { font-weight: 600; font-style: italic; }
292
293/* Small caps for named entities, acronyms, and stage directions. */
294.typeset .ts-sc {
295 font-variant-caps: all-small-caps;
296 letter-spacing: 0.05em;
297 font-variant-numeric: oldstyle-nums;
298}
299
300.typeset abbr[title] {
301 font-variant-caps: all-small-caps;
302 letter-spacing: 0.05em;
303 text-decoration: none;
304 border-bottom: 0.5pt dotted var(--ts-ink-faint);
305}
306
307.typeset s, .typeset del { text-decoration: line-through; color: var(--ts-ink-muted); }
308.typeset ins { text-decoration: underline; text-underline-offset: 0.15em; }
309.typeset mark { background: var(--ts-wash); box-shadow: 0 0 0 2pt var(--ts-wash); }
310
311/* Real super/subscripts, not shifted full-size digits. */
312.typeset sup, .typeset sub {
313 font-size: 0.72em;
314 line-height: 0;
315 position: relative;
316 vertical-align: baseline;
317}
318.typeset sup { top: -0.45em; }
319.typeset sub { bottom: -0.22em; }
320
321.typeset kbd {
322 font: 0.85em/1 var(--ts-sans);
323 padding: 0.15em 0.35em;
324 border: 0.5pt solid var(--ts-rule);
325 border-bottom-width: 1.5pt;
326 border-radius: 2pt;
327}
328/*! @e */
329
330/*! @s code-inline :: Inline code */
331.typeset code {
332 font-family: var(--ts-mono);
333 font-size: 0.86em; /* mono runs large; pull it back optically */
334 font-variant-ligatures: none;
335 background: var(--ts-wash);
336 padding: 0.1em 0.28em;
337 border-radius: 2pt;
338 overflow-wrap: break-word;
339}
340/*! @e */
341
342/*! @s links :: Links in print */
343.typeset a {
344 color: inherit;
345 text-decoration: underline;
346 text-decoration-thickness: 0.5pt;
347 text-underline-offset: 0.14em;
348 text-decoration-skip-ink: auto;
349}
350
351/* Paper has no hover. Spell out external URLs where they carry information. */
352@media print {
353 .typeset a[href^="http"]::after {
354 content: " (" attr(href) ")";
355 font: 0.82em var(--ts-mono);
356 color: var(--ts-ink-muted);
357 word-break: break-all;
358 }
359 /* Internal and mailto links read fine as-is. */
360 .typeset a[href^="#"]::after,
361 .typeset a[href^="mailto"]::after,
362 .typeset a.ts-bare::after { content: none; }
363}
364/*! @e */
365
366/*! @s figures-numeric :: Figures */
367/* Old-style figures sit in the x-height and belong in running prose.
368 Lining figures are uniform-height and belong in headings and tables.
369 Tabular figures share one advance width so columns align. */
370.typeset .ts-nums-oldstyle { font-variant-numeric: oldstyle-nums proportional-nums; }
371.typeset .ts-nums-lining { font-variant-numeric: lining-nums proportional-nums; }
372.typeset .ts-nums-tabular { font-variant-numeric: lining-nums tabular-nums; }
373.typeset .ts-frac { font-variant-numeric: diagonal-fractions; }
374/*! @e */
375
376/*! @s quotes :: Block quotations */
377.typeset blockquote {
378 margin: calc(var(--ts-space) * 1.25) 0;
379 padding-left: calc(var(--ts-space) * 1.5);
380 border-left: 1pt solid var(--ts-rule);
381 color: var(--ts-ink);
382 font-size: 10.5pt;
383 break-inside: avoid;
384}
385.typeset blockquote > :last-child { margin-bottom: 0; }
386
387/* Attribution: an em dash, and never inside the quote. */
388.typeset blockquote cite,
389.typeset .ts-attribution {
390 display: block;
391 text-align: left;
392 margin-top: calc(var(--ts-space) * 0.5);
393 font-size: var(--ts-sm);
394 font-style: normal;
395 color: var(--ts-ink-muted);
396}
397.typeset blockquote cite::before,
398.typeset .ts-attribution::before { content: "— "; }
399
400/* An epigraph opens a chapter: no rule, indented from the right, italic. */
401.typeset .ts-epigraph {
402 border: 0;
403 text-align: left;
404 margin: 0 0 calc(var(--ts-space) * 2) auto;
405 padding: 0;
406 max-width: 24em;
407 font-style: italic;
408 font-size: 10pt;
409 color: var(--ts-ink-muted);
410}
411.typeset .ts-epigraph cite { font-style: normal; }
412
413/* A pull quote is display type lifted from the body. It repeats — so it must
414 never be the only place a claim appears. */
415.typeset .ts-pullquote {
416 border: 0;
417 text-align: center;
418 text-align-last: center;
419 border-top: 1.5pt solid var(--ts-rule-strong);
420 border-bottom: 0.5pt solid var(--ts-rule);
421 padding: var(--ts-space) 0;
422 margin: calc(var(--ts-space) * 1.5) 0;
423 font-family: var(--ts-sans);
424 font-size: 15pt;
425 font-weight: 300;
426 line-height: 1.3;
427 text-wrap: balance;
428}
429
430/* Verse: preserve the poet's line breaks, indent the runovers. */
431.typeset .ts-verse {
432 border: 0;
433 text-align: left;
434 padding-left: calc(var(--ts-space) * 2);
435 white-space: pre-line;
436 font-size: var(--ts-base);
437 text-indent: -1.5em;
438 padding-inline-start: calc(var(--ts-space) * 2 + 1.5em);
439}
440/*! @e */
441
442/*! @s lists :: Lists */
443.typeset ul,
444.typeset ol {
445 margin: 0 0 var(--ts-space);
446 padding-left: 1.4em;
447}
448.typeset li { margin-bottom: calc(var(--ts-space) * 0.25); }
449.typeset li > ul,
450.typeset li > ol { margin: calc(var(--ts-space) * 0.25) 0 0; }
451.typeset li:last-child { margin-bottom: 0; }
452
453.typeset ul { list-style: none; }
454.typeset ul > li::before {
455 content: "·";
456 float: left;
457 width: 1.4em;
458 margin-left: -1.4em;
459 text-align: left;
460 color: var(--ts-ink-muted);
461 font-weight: 700;
462}
463.typeset ul ul > li::before { content: "–"; font-weight: 400; }
464
465.typeset ol {
466 list-style: none;
467 counter-reset: ts-ol;
468}
469.typeset ol > li { counter-increment: ts-ol; }
470.typeset ol > li::before {
471 content: counter(ts-ol) ".";
472 float: left;
473 width: 1.4em;
474 margin-left: -1.4em;
475 color: var(--ts-ink-muted);
476 font-variant-numeric: lining-nums tabular-nums;
477}
478.typeset ol ol > li::before { content: counter(ts-ol, lower-alpha) "."; }
479
480/* A tight list for enumerations that are not prose. */
481.typeset .ts-list-tight li { margin-bottom: 0; }
482
483/* Definition lists: the right shape for terms, glossaries, and letter fields. */
484.typeset dl { margin: 0 0 var(--ts-space); }
485.typeset dt {
486 text-align: left;
487 font-weight: 600;
488 font-family: var(--ts-sans);
489 font-size: var(--ts-sm);
490 margin-top: calc(var(--ts-space) * 0.6);
491 break-after: avoid;
492}
493.typeset dt:first-child { margin-top: 0; }
494.typeset dd { margin: 0 0 0 calc(var(--ts-space) * 1.5); }
495/*! @e */
496
497/*! @s tables :: Tables */
498.typeset table {
499 width: 100%;
500 border-collapse: collapse;
501 margin: calc(var(--ts-space) * 1.25) 0;
502 font-family: var(--ts-sans);
503 font-size: var(--ts-sm);
504 font-variant-numeric: lining-nums tabular-nums;
505 line-height: 1.35;
506}
507
508/* Rules, not grids. Vertical rules are almost never needed — the columns
509 already read as columns. */
510.typeset th,
511.typeset td {
512 padding: 0.45em 0.7em 0.45em 0;
513 text-align: left;
514 vertical-align: baseline;
515 border-bottom: 0.5pt solid var(--ts-rule);
516}
517.typeset th:last-child,
518.typeset td:last-child { padding-right: 0; }
519
520.typeset thead th {
521 font-weight: 600;
522 font-size: var(--ts-xs);
523 letter-spacing: 0.07em;
524 text-transform: uppercase;
525 color: var(--ts-ink-muted);
526 border-bottom: 1pt solid var(--ts-rule-strong);
527}
528.typeset tbody tr:last-child td { border-bottom: 1pt solid var(--ts-rule-strong); }
529.typeset tfoot td { font-weight: 600; border-bottom: 0; }
530
531/* Numbers align on their right edge. Units go in the header, not every cell. */
532.typeset .ts-num { text-align: right; font-variant-numeric: lining-nums tabular-nums; }
533.typeset th.ts-num { text-align: right; }
534
535/* Repeat the header on every page a long table spans. */
536.typeset thead { display: table-header-group; }
537.typeset tfoot { display: table-footer-group; }
538.typeset tr { break-inside: avoid; }
539
540.typeset caption {
541 caption-side: top;
542 text-align: left;
543 font-family: var(--ts-sans);
544 font-size: var(--ts-sm);
545 color: var(--ts-ink);
546 padding-bottom: 0.5em;
547 max-width: none;
548}
549.typeset caption .ts-label {
550 font-weight: 600;
551 font-variant-caps: all-small-caps;
552 letter-spacing: 0.06em;
553}
554
555/* Optional zebra for wide, dense tables only. */
556.typeset .ts-table-zebra tbody tr:nth-child(even) { background: var(--ts-wash); }
557/*! @e */
558
559/*! @s codeblock :: Code blocks */
560.typeset pre {
561 font-family: var(--ts-mono);
562 font-size: 8.5pt;
563 line-height: 1.45;
564 background: var(--ts-wash);
565 border-left: 2pt solid var(--ts-rule-strong);
566 padding: 0.8em 1em;
567 margin: calc(var(--ts-space) * 1.25) 0;
568 overflow-x: auto;
569
570 /* Paper cannot scroll. Wrap, and mark the wrap. */
571 white-space: pre-wrap;
572 overflow-wrap: break-word;
573 tab-size: 2;
574 break-inside: avoid;
575}
576.typeset pre code {
577 background: none;
578 padding: 0;
579 font-size: inherit;
580}
581
582@media print {
583 .typeset pre { break-inside: auto; } /* long listings must be allowed to split */
584}
585/*! @e */
586
587/*! @s figures :: Figures & captions */
588.typeset figure {
589 margin: calc(var(--ts-space) * 1.5) 0;
590 break-inside: avoid;
591}
592.typeset figure img,
593.typeset figure svg {
594 display: block;
595 max-width: 100%;
596 height: auto;
597}
598.typeset figcaption {
599 text-align: left;
600 font-family: var(--ts-sans);
601 font-size: var(--ts-sm);
602 line-height: 1.4;
603 color: var(--ts-ink-muted);
604 margin-top: 0.5em;
605 padding-top: 0.4em;
606 border-top: 0.5pt solid var(--ts-rule);
607}
608.typeset figcaption .ts-label {
609 font-weight: 600;
610 color: var(--ts-ink);
611 font-variant-caps: all-small-caps;
612 letter-spacing: 0.06em;
613}
614/*! @e */
615
616/*! @s notes :: Footnotes & endnotes */
617/* The reference mark. Superscript, no brackets, and it must not disturb
618 the line's leading. */
619.typeset .ts-noteref {
620 font-size: 0.7em;
621 line-height: 0;
622 position: relative;
623 top: -0.45em;
624 font-variant-numeric: lining-nums;
625 text-decoration: none;
626 color: var(--ts-accent);
627}
628.typeset .ts-noteref::before { content: counter(ts-note); }
629.typeset { counter-reset: ts-note; }
630.typeset .ts-noteref { counter-increment: ts-note; }
631
632/* True bottom-of-page footnotes need an engine that implements the CSS
633 footnote spec — WeasyPrint 53+ and Prince do; browsers do not. Where they
634 are unavailable this degrades to an endnotes block, which is why the notes
635 live in the document rather than in a generated-content property. */
636@supports (float: footnote) {
637 .typeset .ts-note {
638 float: footnote;
639 footnote-display: block;
640 font-size: var(--ts-sm);
641 line-height: 1.35;
642 }
643 .typeset .ts-endnotes { display: none; }
644}
645
646.typeset .ts-endnotes {
647 text-align: left;
648 margin-top: calc(var(--ts-space) * 2);
649 padding-top: var(--ts-space);
650 border-top: 0.5pt solid var(--ts-rule);
651 font-size: var(--ts-sm);
652 line-height: 1.4;
653 counter-reset: ts-endnote;
654}
655.typeset .ts-endnotes ol { padding-left: 1.8em; }
656.typeset .ts-endnotes li { margin-bottom: 0.4em; }
657
658/* Sidenotes: no page-break arithmetic, and the reader never loses their
659 place. They cost you a wide right margin. */
660/* The reserved margin must be wide enough for the gap plus the note: 2em + 11em
661 here, with an em to spare. */
662.typeset--sidenotes { --ts-measure: 27em; padding-right: 14em; }
663.typeset--sidenotes p { position: relative; }
664.typeset .ts-sidenote {
665 position: absolute;
666
667 /* Anchored to the paragraph's own right edge, NOT to `calc(measure + 2em)`.
668 The measure is a max-width, so the paragraph is whichever is narrower —
669 the measure, or what the container's padding leaves it. Offsetting from the
670 measure therefore puts the note in the wrong place whenever the padding is
671 the binding constraint, and it lands on top of the text. */
672 left: 100%;
673 margin-left: 2em;
674 width: 11em;
675 margin-top: -0.3em;
676 font-family: var(--ts-sans);
677 font-size: var(--ts-xs);
678 line-height: 1.4;
679 color: var(--ts-ink-muted);
680 text-align: left;
681 text-indent: 0;
682}
683.typeset .ts-sidenote::before {
684 content: counter(ts-note) " ";
685 color: var(--ts-accent);
686 font-weight: 600;
687}
688/*! @e */
689
690/*! @s callouts :: Callouts */
691.typeset .ts-callout {
692 margin: calc(var(--ts-space) * 1.25) 0;
693 padding: 0.75em 1em;
694 border: 0.5pt solid var(--ts-rule);
695 border-left: 2.5pt solid var(--ts-rule-strong);
696 background: var(--ts-wash);
697 font-size: 10pt;
698 break-inside: avoid;
699}
700.typeset .ts-callout > :last-child { margin-bottom: 0; }
701.typeset .ts-callout-title {
702 font-family: var(--ts-sans);
703 font-size: var(--ts-xs);
704 font-weight: 700;
705 letter-spacing: 0.09em;
706 text-transform: uppercase;
707 color: var(--ts-ink-muted);
708 margin-bottom: 0.35em;
709}
710.typeset .ts-callout--warning { border-left-color: var(--ts-accent); }
711.typeset .ts-callout--warning .ts-callout-title { color: var(--ts-accent); }
712/*! @e */
713
714/*! @s breaks :: Section breaks */
715/* A blank line cannot survive a page break — if the break falls on the gap,
716 the reader never sees it. Use a visible mark. */
717.typeset hr,
718.typeset .ts-break {
719 border: 0;
720 height: auto;
721 margin: calc(var(--ts-space) * 1.5) 0;
722 text-align: center;
723 text-align-last: center; /* a one-line block IS its own last line */
724 color: var(--ts-ink-faint);
725 break-after: avoid;
726}
727.typeset hr::before,
728.typeset .ts-break::before {
729 content: "* * *";
730 letter-spacing: 0.6em;
731 font-size: 10pt;
732}
733
734/* An asterism, for a heavier division. */
735.typeset .ts-break--asterism::before { content: "⁂"; letter-spacing: 0; font-size: 14pt; }
736/* A fleuron, for a decorative one. */
737.typeset .ts-break--fleuron::before { content: "❦"; letter-spacing: 0; font-size: 12pt; color: var(--ts-accent); }
738/* A plain rule, for a report. */
739.typeset .ts-break--rule::before { content: none; }
740.typeset .ts-break--rule { border-top: 0.5pt solid var(--ts-rule); height: 0; }
741/*! @e */
742
743/*! @s frontmatter :: Front matter */
744.typeset .ts-titleblock {
745 margin-bottom: calc(var(--ts-space) * 3);
746 padding-bottom: var(--ts-space);
747 border-bottom: 0.5pt solid var(--ts-rule);
748}
749.typeset .ts-titleblock h1 { margin-bottom: 0.25em; }
750.typeset .ts-subtitle {
751 text-align: left;
752 font-family: var(--ts-sans);
753 font-size: var(--ts-h3);
754 font-weight: 300;
755 line-height: 1.25;
756 color: var(--ts-ink-muted);
757 margin: 0 0 var(--ts-space);
758 text-wrap: balance;
759}
760.typeset .ts-byline {
761 text-align: left;
762 font-variant-caps: all-small-caps;
763 letter-spacing: 0.08em;
764 font-size: var(--ts-base);
765 margin: 0;
766}
767.typeset .ts-dateline {
768 text-align: left;
769 font-family: var(--ts-sans);
770 font-size: var(--ts-sm);
771 color: var(--ts-ink-muted);
772 margin: 0.2em 0 0;
773}
774
775.typeset .ts-abstract {
776 text-align: left;
777 font-size: 10pt;
778 line-height: 1.45;
779 color: var(--ts-ink-muted);
780 max-width: 30em;
781 margin-bottom: calc(var(--ts-space) * 2);
782}
783.typeset .ts-abstract .ts-label {
784 font-family: var(--ts-sans);
785 font-size: var(--ts-xs);
786 font-weight: 700;
787 letter-spacing: 0.1em;
788 text-transform: uppercase;
789 color: var(--ts-ink-faint);
790 display: block;
791 margin-bottom: 0.3em;
792}
793
794/* The colophon closes the document: how it was made, and in what. */
795.typeset .ts-colophon {
796 text-align: left;
797 margin-top: calc(var(--ts-space) * 3);
798 padding-top: var(--ts-space);
799 border-top: 0.5pt solid var(--ts-rule);
800 font-size: var(--ts-sm);
801 font-style: italic;
802 color: var(--ts-ink-muted);
803 max-width: 26em;
804 break-before: avoid;
805}
806/*! @e */
807
808/*! @s toc :: Table of contents */
809/* Dot leaders and real page numbers need `target-counter()` — a paged-media
810 engine only. In a browser the leaders render and the numbers do not. */
811.typeset .ts-toc { text-align: left; font-family: var(--ts-sans); font-size: var(--ts-sm); }
812.typeset .ts-toc ol { padding-left: 0; counter-reset: ts-toc; }
813.typeset .ts-toc li { margin-bottom: 0.35em; }
814.typeset .ts-toc li::before { content: none; }
815.typeset .ts-toc a {
816 display: flex;
817 align-items: baseline;
818 gap: 0.4em;
819 text-decoration: none;
820}
821.typeset .ts-toc a::after {
822 content: target-counter(attr(href url), page);
823 font-variant-numeric: lining-nums tabular-nums;
824 color: var(--ts-ink-muted);
825}
826.typeset .ts-toc .ts-leader {
827 flex: 1;
828 border-bottom: 0.5pt dotted var(--ts-rule);
829 transform: translateY(-0.15em);
830}
831.typeset .ts-toc .ts-toc-2 { padding-left: 1.5em; }
832/*! @e */
833
834/*! @s bibliography :: Bibliography */
835/* Hanging indent: the author's surname is the thing being scanned, so it must
836 be the leftmost thing on the entry. */
837.typeset .ts-bibliography {
838 text-align: left;
839 font-size: var(--ts-sm);
840 line-height: 1.4;
841 padding-left: 0;
842 list-style: none;
843}
844.typeset .ts-bibliography li {
845 padding-left: 1.8em;
846 text-indent: -1.8em;
847 margin-bottom: 0.55em;
848 break-inside: avoid;
849}
850.typeset .ts-bibliography li::before { content: none; }
851.typeset .ts-bibliography cite { font-style: italic; }
852/*! @e */
853
854/*! @s letter :: Letter */
855/* The sender block is address data, not a masthead: one style throughout, at
856 body size, in the reading face. A personal letter does not announce itself —
857 the recipient knows who writes to them. Nothing here is bold, and nothing is
858 set in the sans, so the block recedes and the letter starts sooner. */
859.typeset .ts-letterhead {
860 text-align: left;
861 font-size: var(--ts-base);
862 line-height: 1.35;
863 color: var(--ts-ink);
864 margin: 0 0 calc(var(--ts-space) * 2.5);
865}
866.typeset .ts-letterhead p {
867 margin: 0;
868 text-indent: 0;
869}
870
871/* Address blocks are not prose: they are line-broken data. */
872.typeset .ts-address {
873 text-align: left;
874 font-style: normal;
875 line-height: 1.35;
876 margin-bottom: calc(var(--ts-space) * 1.5);
877}
878.typeset .ts-address .ts-label {
879 display: block;
880 font-family: var(--ts-sans);
881 font-size: var(--ts-xs);
882 letter-spacing: 0.1em;
883 text-transform: uppercase;
884 color: var(--ts-ink-faint);
885 margin-bottom: 0.25em;
886}
887
888.typeset .ts-date {
889 margin-bottom: calc(var(--ts-space) * 1.5);
890 font-variant-numeric: oldstyle-nums;
891 text-align: left;
892}
893
894/* A line under the date, in italic — a dedication, a feast, a devotion. It
895 belongs to the date rather than following it, so it climbs back over the
896 date's gap and carries that gap itself. Written against the same token as the
897 date's margin so the two cannot drift apart.
898
899 Deliberately not `.ts-date:has(+ .ts-date-note)`: Paged.js's CSS parser does
900 not understand `:has()` any more than it understands `:is()`, and a selector
901 it cannot parse aborts pagination outright. */
902.typeset .ts-date-note {
903 margin: calc(var(--ts-space) * -1.5 + 0.1em) 0 calc(var(--ts-space) * 1.5);
904 font-style: italic;
905 text-indent: 0;
906}
907.typeset .ts-salutation { margin-bottom: var(--ts-space); text-align: left; }
908.typeset .ts-closing { margin: calc(var(--ts-space) * 1.5) 0 0; text-align: left; }
909
910/* The typed name, with room above it to sign. No rule: a ruled line is a form
911 to be filled in, and this is a letter. */
912.typeset .ts-signature {
913 text-align: left;
914 margin-top: calc(var(--ts-space) * 3);
915 text-indent: 0;
916 break-inside: avoid;
917}
918.typeset .ts-signature img { display: block; max-height: 16mm; margin-bottom: 0.2em; }
919
920/* Same reasoning as the postscript: "Enc." introduces a sentence, it does not
921 head a section. Left as the only small-caps label in a letter it was the one
922 thing on the page shouting. */
923.typeset .ts-enclosures {
924 text-align: left;
925 margin-top: calc(var(--ts-space) * 2);
926 font-size: var(--ts-base);
927 text-indent: 0;
928}
929/* A postscript is a sentence that happens to begin with "P.S." — the label is
930 not a heading, so it matches the text it introduces exactly. */
931.typeset .ts-ps {
932 text-align: left;
933 margin-top: var(--ts-space);
934 font-size: var(--ts-base);
935 text-indent: 0;
936}
937/*! @e */
938
939/*! @s two-column :: Two column, equal */
940/* Papers, journal articles, newsletters, technical notes — documents scanned
941 and referenced as much as read.
942
943 The base size drops to 9.5pt here, and that is arithmetic rather than taste:
944 a 77mm column carries 40 characters at 11pt, below the 45-character floor
945 this stylesheet sets for a line of prose. 9.5pt restores 47. An
946 implementation that keeps 11pt in two columns breaks the measure rule, which
947 is the rule everything else is downstream of. */
948.typeset--two-column {
949 /* Every step comes down. A 24pt heading in a 77mm column spends three lines
950 saying two words. */
951 --ts-xs: 7pt;
952 --ts-sm: 8.5pt;
953 --ts-base: 9.5pt;
954 --ts-h4: 9.5pt;
955 --ts-h3: 11pt;
956 --ts-h2: 13pt;
957 --ts-h1: 20pt;
958
959 /* Leading tightens with the measure: a shorter line needs less separation to
960 keep the return sweep unambiguous. */
961 --ts-leading: 1.4;
962 --ts-space: 9.5pt;
963
964 /* The column IS the measure, so the character cap comes off. */
965 --ts-measure: none;
966 --ts-column-gap: 6mm;
967
968 /* A blank line costs 3% of a column, so paragraphs indent here by default —
969 not as an option. */
970 --ts-para-gap: 0;
971 --ts-para-indent: 1.25em;
972
973 font-size: var(--ts-base);
974 columns: 2;
975 column-gap: var(--ts-column-gap);
976 column-fill: balance; /* the final page balances its columns */
977
978 /* Justification with hyphenation is MANDATORY at this measure, not optional:
979 at 47 characters a ragged edge serrates the column and the word gaps read
980 as rivers. This is the one place the stylesheet removes a choice. */
981 text-align: justify;
982 text-align-last: left;
983 hyphens: auto;
984 hyphenate-limit-chars: 6 3 3;
985 hyphenate-limit-lines: 2;
986}
987
988/* Spans both columns, always. Each is a direct child of the multicol container,
989 which is what column-span requires. */
990.typeset--two-column > .ts-titleblock,
991.typeset--two-column > .ts-abstract,
992.typeset--two-column > h1,
993.typeset--two-column > .ts-colophon,
994.typeset--two-column > .ts-span {
995 column-span: all;
996}
997
998/* Spanning is opt-in per instance for figures and tables: it costs a break in
999 both columns, so the default stays column-width. A spanning element must sit
1000 at the top or the bottom of the page, never mid-column. */
1001.typeset--two-column > .ts-span {
1002 break-before: avoid-column;
1003 margin-bottom: calc(var(--ts-space) * 1.5);
1004}
1005
1006/* An optional hairline where the columns need separating. Most journals omit
1007 it — the gutter is already doing the work. */
1008.typeset--two-column.typeset--column-rule {
1009 column-rule: 0.5pt solid var(--ts-rule);
1010}
1011
1012/* A sidenote has no margin to live in here. Rather than let it be positioned
1013 off the page and silently lost, it degrades to an inline aside. */
1014.typeset--two-column .ts-sidenote {
1015 position: static;
1016 display: block;
1017 width: auto;
1018 margin: 0.4em 0;
1019 padding-left: 0.6em;
1020 border-left: 0.5pt solid var(--ts-rule);
1021 text-indent: 0;
1022}
1023
1024/* A three-line cap at 3.05em is 29pt — a quarter of the column width for one
1025 letter. The opening small caps carry the signal instead. */
1026.typeset--two-column .ts-dropcap::first-letter {
1027 float: none;
1028 font-size: inherit;
1029 line-height: inherit;
1030 color: inherit;
1031 padding: 0;
1032}
1033
1034/* Denser matter for a narrower column. */
1035.typeset--two-column blockquote { font-size: 9pt; padding-left: var(--ts-space); }
1036.typeset--two-column table { font-size: 8pt; line-height: 1.3; }
1037.typeset--two-column pre { font-size: 7.5pt; }
1038.typeset--two-column figcaption { font-size: 8pt; }
1039.typeset--two-column .ts-endnotes { font-size: 8.5pt; }
1040.typeset--two-column .ts-pullquote { font-size: 12pt; }
1041.typeset--two-column .ts-abstract { font-size: 9pt; max-width: none; }
1042.typeset--two-column .ts-epigraph { max-width: none; }
1043/*! @e */
1044
1045/*! @s utilities :: Pagination utilities */
1046.ts-page-break-before { break-before: page; }
1047.ts-page-break-after { break-after: page; }
1048.ts-page-break-avoid { break-inside: avoid; }
1049.ts-keep-together { break-inside: avoid; page-break-inside: avoid; }
1050.ts-no-hyphens { hyphens: manual; }
1051.ts-nowrap { white-space: nowrap; }
1052
1053/* Non-breaking spaces belong in the markup, but these catch the common cases:
1054 a figure and its unit, an initial and a surname. */
1055.ts-tie { white-space: nowrap; }
1056
1057@media print {
1058 .ts-screen-only { display: none !important; }
1059}
1060@media screen {
1061 .ts-print-only { display: none !important; }
1062}
1063
1064/* Force backgrounds and rules to survive the print dialog's ink-saving. */
1065@media print {
1066 .typeset,
1067 .typeset * {
1068 -webkit-print-color-adjust: exact;
1069 print-color-adjust: exact;
1070 }
1071}
1072/*! @e */