@mindful-web/marko-web-digital-edition 1.95.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/.eslintignore +1 -0
  2. package/LICENSE +21 -0
  3. package/README.md +121 -0
  4. package/components/ad.marko +81 -0
  5. package/components/ad.marko.js +102 -0
  6. package/components/article-body.marko +134 -0
  7. package/components/article-body.marko.js +186 -0
  8. package/components/author.marko +30 -0
  9. package/components/author.marko.js +59 -0
  10. package/components/gate.marko +158 -0
  11. package/components/gate.marko.js +147 -0
  12. package/components/ld-json.marko +5 -0
  13. package/components/ld-json.marko.js +29 -0
  14. package/components/marko.json +78 -0
  15. package/components/reflow.marko +67 -0
  16. package/components/reflow.marko.js +106 -0
  17. package/components/replica.marko +86 -0
  18. package/components/replica.marko.js +128 -0
  19. package/components/scroll.marko +134 -0
  20. package/components/scroll.marko.js +23 -0
  21. package/components/theme.marko +73 -0
  22. package/components/theme.marko.js +30 -0
  23. package/components/toc.marko +28 -0
  24. package/components/toc.marko.js +53 -0
  25. package/components/toolbar.marko +47 -0
  26. package/components/toolbar.marko.js +54 -0
  27. package/components/tracking.marko +211 -0
  28. package/components/tracking.marko.js +60 -0
  29. package/lib/authors.js +125 -0
  30. package/lib/entities.js +97 -0
  31. package/lib/index.js +453 -0
  32. package/marko.json +6 -0
  33. package/package.json +33 -0
  34. package/routes/index.js +129 -0
  35. package/scss/index.scss +679 -0
  36. package/templates/article.marko +108 -0
  37. package/templates/article.marko.js +238 -0
  38. package/templates/hub.marko +94 -0
  39. package/templates/hub.marko.js +219 -0
  40. package/templates/partial.marko +17 -0
  41. package/templates/partial.marko.js +44 -0
@@ -0,0 +1,679 @@
1
+ // Digital edition reader (spike).
2
+ //
3
+ // Two views off one manifest: `read` (reflow) and `print` (replica page images).
4
+ // Everything is fluid — the replica's ad hotspots are positioned in percentages so
5
+ // they track the page image at every breakpoint with no JS.
6
+ //
7
+ // THEMING
8
+ // Colours are tokens on `.edition`, redefined under `[data-edition-theme="dark"]`.
9
+ // The attribute is set on <html> by an inline head script (components/edition/theme.marko)
10
+ // so there is no flash of light before the reader paints; the same script stamps
11
+ // `data-edition-view` so these rules can tell the views apart.
12
+ //
13
+ // The reflow view themes wholesale. The PRINT view does not: its pages are
14
+ // facsimiles of paper, so dark mode there darkens only the canvas they float on
15
+ // (--edition-canvas) and the chrome keeps the light treatment — PDF-reader behaviour.
16
+ // Never give a colour its only definition inside the dark block.
17
+
18
+ .edition {
19
+ --edition-bg: #ffffff;
20
+ --edition-fg: #16181a;
21
+ --edition-muted: #{rgba(#16181a, 0.62)};
22
+ --edition-faint: #{rgba(#16181a, 0.45)};
23
+ --edition-rule: #{rgba(#000, 0.1)};
24
+ --edition-surface: #ffffff;
25
+ --edition-pill-bg: #111111;
26
+ --edition-pill-fg: #ffffff;
27
+ --edition-shadow: #{rgba(#000, 0.15)};
28
+ --edition-ad-frame: #{rgba(#000, 0.1)};
29
+ --edition-link: #0b5ea8;
30
+ --edition-link-hover: #073f73;
31
+ --edition-page-placeholder: #f2f2f2;
32
+ --edition-max: 44rem;
33
+ // Replica canvas: what the page images float on. The one thing dark mode
34
+ // changes in the print view.
35
+ --edition-canvas: transparent;
36
+ --edition-canvas-fg: var(--edition-faint);
37
+ // The in-article ad breakout measures the reader's OWN width (container query
38
+ // units), not the viewport — the site theme decides how wide the wrapper is.
39
+ container-type: inline-size;
40
+
41
+ background: var(--edition-bg);
42
+ color: var(--edition-fg);
43
+ }
44
+
45
+ // Wholesale dark tokens — reflow only; the print view keeps its light chrome.
46
+ :root[data-edition-theme="dark"]:not([data-edition-view="print"]) .edition {
47
+ --edition-bg: #14161a;
48
+ --edition-fg: #e6e8ea;
49
+ --edition-muted: #{rgba(#e6e8ea, 0.66)};
50
+ --edition-faint: #{rgba(#e6e8ea, 0.42)};
51
+ --edition-rule: #{rgba(#fff, 0.14)};
52
+ --edition-surface: #1b1e24;
53
+ --edition-pill-bg: #e6e8ea;
54
+ --edition-pill-fg: #14161a;
55
+ --edition-shadow: #{rgba(#000, 0.5)};
56
+ // Print ads are designed on white. A light frame keeps them from looking like
57
+ // holes punched in the page.
58
+ --edition-ad-frame: #{rgba(#fff, 0.22)};
59
+ --edition-link: #7fb8f2;
60
+ --edition-link-hover: #a9d0f7;
61
+ // Shows through before a page image loads; a light flash on dark is jarring.
62
+ --edition-page-placeholder: #2a2e35;
63
+ }
64
+
65
+ // Dark mode in the PRINT view: only the canvas around the pages darkens.
66
+ :root[data-edition-theme="dark"] .edition {
67
+ --edition-canvas: #14161a;
68
+ --edition-canvas-fg: #{rgba(#e6e8ea, 0.5)};
69
+ }
70
+
71
+ // Paint the page gutters too, or the dark reader sits in a white window.
72
+ // Not in the print view — there the window stays light around the dark canvas.
73
+ :root[data-edition-theme="dark"]:not([data-edition-view="print"]) {
74
+ background: #14161a;
75
+
76
+ body { background: #14161a; }
77
+ }
78
+
79
+ // ---------------------------------------------------------------- toolbar
80
+ .edition-toolbar {
81
+ position: sticky;
82
+ top: 0;
83
+ z-index: 20;
84
+ display: flex;
85
+ flex-wrap: wrap;
86
+ gap: 0.5rem 1rem;
87
+ align-items: center;
88
+ justify-content: space-between;
89
+ padding: 0.75rem 1rem;
90
+ background: var(--edition-surface);
91
+ color: var(--edition-fg);
92
+ border-bottom: 1px solid var(--edition-rule);
93
+
94
+ &__title { display: flex; flex-direction: column; line-height: 1.2; }
95
+ &__pub { font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; font-size: 0.8rem; }
96
+ &__issue { font-size: 0.95rem; color: var(--edition-muted); }
97
+
98
+ &__actions { display: flex; align-items: center; gap: 0.75rem; }
99
+ &__views { display: flex; gap: 0.25rem; }
100
+
101
+ &__view {
102
+ padding: 0.35rem 0.85rem;
103
+ border: 1px solid var(--edition-rule);
104
+ border-radius: 999px;
105
+ font-size: 0.85rem;
106
+ text-decoration: none;
107
+ color: inherit;
108
+ transition: border-color 0.15s ease;
109
+
110
+ // The site sets a near-black `a:hover` colour globally, which is unreadable on
111
+ // the dark surface. Hover is signalled by brightening the border instead of
112
+ // recolouring the text — the text colour is pinned in every interactive state.
113
+ &:hover,
114
+ &:focus-visible {
115
+ color: inherit;
116
+ border-color: var(--edition-muted);
117
+ text-decoration: none;
118
+ }
119
+
120
+ &:focus-visible { outline: 2px solid var(--edition-muted); outline-offset: 2px; }
121
+
122
+ &.is-active {
123
+ background: var(--edition-pill-bg);
124
+ color: var(--edition-pill-fg);
125
+ border-color: var(--edition-pill-bg);
126
+
127
+ &:hover,
128
+ &:focus-visible {
129
+ color: var(--edition-pill-fg);
130
+ border-color: var(--edition-pill-bg);
131
+ }
132
+ }
133
+ }
134
+
135
+ &__theme {
136
+ display: inline-flex;
137
+ align-items: center;
138
+ gap: 0.4rem;
139
+ padding: 0.35rem 0.7rem;
140
+ background: transparent;
141
+ border: 1px solid var(--edition-rule);
142
+ border-radius: 999px;
143
+ color: inherit;
144
+ font: inherit;
145
+ font-size: 0.85rem;
146
+ cursor: pointer;
147
+
148
+ &:hover { border-color: var(--edition-muted); }
149
+ &:focus-visible { outline: 2px solid var(--edition-muted); outline-offset: 2px; }
150
+
151
+ // One button, two labels — the script only flips a class, so there is no
152
+ // flash of the wrong label and no text measured on the client.
153
+ &__icon { font-size: 1rem; line-height: 1; }
154
+ &__label { white-space: nowrap; }
155
+ }
156
+ }
157
+
158
+ // Show the label matching the active theme.
159
+ .edition-toolbar__theme [data-when="dark"] { display: none; }
160
+ .edition-toolbar__theme [data-when="light"] { display: inline; }
161
+ :root[data-edition-theme="dark"] .edition-toolbar__theme [data-when="dark"] { display: inline; }
162
+ :root[data-edition-theme="dark"] .edition-toolbar__theme [data-when="light"] { display: none; }
163
+
164
+ // ---------------------------------------------------------------- reflow
165
+ .edition-reflow {
166
+ max-width: var(--edition-max);
167
+ margin: 0 auto;
168
+ padding: 1.5rem 1rem 4rem;
169
+
170
+ &__masthead {
171
+ display: flex;
172
+ gap: 1.25rem;
173
+ align-items: flex-end;
174
+ padding-bottom: 1.5rem;
175
+ border-bottom: 2px solid currentColor;
176
+ }
177
+
178
+ &__cover {
179
+ width: 8rem;
180
+ height: auto;
181
+ flex: 0 0 auto;
182
+ box-shadow: 0 2px 12px var(--edition-shadow);
183
+ }
184
+
185
+ &__pub { margin: 0; font-size: 0.8rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; }
186
+ &__issue { margin: 0.15rem 0; font-size: 1.75rem; line-height: 1.1; }
187
+ &__count { margin: 0; font-size: 0.85rem; color: var(--edition-faint); }
188
+ }
189
+
190
+ .edition-toc {
191
+ margin: 2rem 0;
192
+
193
+ &__title { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--edition-muted); }
194
+ &__list { list-style: none; margin: 0; padding: 0; }
195
+ &__item + &__item { border-top: 1px solid var(--edition-rule); }
196
+
197
+ &__link {
198
+ display: flex;
199
+ justify-content: space-between;
200
+ gap: 1rem;
201
+ padding: 0.6rem 0;
202
+ text-decoration: none;
203
+ color: inherit;
204
+
205
+ // Pin the colour against the global `a:hover`; underline carries the affordance.
206
+ &:hover,
207
+ &:focus-visible { color: inherit; }
208
+
209
+ &:hover .edition-toc__headline,
210
+ &:focus-visible .edition-toc__headline { text-decoration: underline; }
211
+ }
212
+
213
+ &__page { flex: 0 0 auto; color: var(--edition-faint); font-variant-numeric: tabular-nums; }
214
+ }
215
+
216
+ .edition-article {
217
+ padding: 2.5rem 0;
218
+ border-top: 1px solid var(--edition-rule);
219
+
220
+ &__page { margin: 0 0 0.5rem; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--edition-faint); }
221
+ &__headline { margin: 0 0 0.5rem; font-size: 1.6rem; line-height: 1.2; }
222
+ &__deck { margin: 0 0 0.75rem; font-size: 1.05rem; color: var(--edition-muted); }
223
+ &__byline { margin: 0 0 1.25rem; font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--edition-muted); }
224
+ &__also {
225
+ margin: 0 0 1.25rem;
226
+ font-size: 0.85rem;
227
+ color: var(--edition-muted);
228
+
229
+ a {
230
+ color: var(--edition-link);
231
+ text-decoration: underline;
232
+
233
+ // Explicit hover colour, or the global near-black `a:hover` wins in dark mode.
234
+ &:hover,
235
+ &:focus-visible { color: var(--edition-link-hover); }
236
+ }
237
+ }
238
+
239
+ &__lead {
240
+ margin: 0 0 1.5rem;
241
+
242
+ img {
243
+ display: block;
244
+ width: 100%;
245
+ height: auto;
246
+ border-radius: 2px;
247
+ }
248
+ }
249
+
250
+ &__body {
251
+ font-size: 1.05rem;
252
+ line-height: 1.65;
253
+ // Contain the floated ads: a last-slot float must not spill into the credits
254
+ // or the next article's border rule.
255
+ display: flow-root;
256
+
257
+ p { margin: 0 0 1.1em; }
258
+
259
+ // Matched articles may render CMS bodies containing links.
260
+ a {
261
+ color: var(--edition-link);
262
+
263
+ &:hover,
264
+ &:focus-visible { color: var(--edition-link-hover); }
265
+ }
266
+ }
267
+
268
+ &__body--teaser {
269
+ position: relative;
270
+ max-height: 9rem;
271
+ overflow: hidden;
272
+
273
+ &::after {
274
+ content: "";
275
+ position: absolute;
276
+ inset: auto 0 0;
277
+ height: 5rem;
278
+ pointer-events: none;
279
+ // Fades to the reader background in either theme.
280
+ background: linear-gradient(to bottom, transparent, var(--edition-bg));
281
+ }
282
+ }
283
+
284
+ // The gated block (teaser + link) is one click target: the whole faded band
285
+ // opens the sign-in gate, and the link is the visible cue. Kept small and muted
286
+ // so eighteen of them read as a convention, not a nag.
287
+ &__gated {
288
+ cursor: pointer;
289
+ }
290
+
291
+ &__continue {
292
+ position: relative;
293
+ z-index: 1;
294
+ display: inline-block;
295
+ margin-top: -1.25rem;
296
+ font-size: 0.875rem;
297
+ font-weight: 600;
298
+ letter-spacing: 0.01em;
299
+ color: var(--edition-muted);
300
+ text-decoration: none;
301
+
302
+ &:hover,
303
+ &:focus-visible {
304
+ color: var(--edition-fg);
305
+ text-decoration: underline;
306
+ }
307
+ }
308
+
309
+ &__continue-arrow {
310
+ display: inline-block;
311
+ margin-left: 0.3em;
312
+ transition: transform 0.15s;
313
+ }
314
+ &__gated:hover &__continue-arrow { transform: translateX(0.2em); }
315
+ }
316
+
317
+ // ---------------------------------------------------------------- ads
318
+ .edition-ad {
319
+ position: relative;
320
+ margin: 2.5rem auto;
321
+ text-align: center;
322
+
323
+ &__label {
324
+ display: block;
325
+ margin-bottom: 0.4rem;
326
+ font-size: 0.65rem;
327
+ text-transform: uppercase;
328
+ letter-spacing: 0.12em;
329
+ color: var(--edition-faint);
330
+ }
331
+
332
+ &__img {
333
+ max-width: 100%;
334
+ height: auto;
335
+ border: 1px solid var(--edition-ad-frame);
336
+ }
337
+
338
+ &__link { display: inline-block; text-decoration: none; }
339
+
340
+ &__advertiser {
341
+ display: block;
342
+ margin-top: 0.35rem;
343
+ font-size: 0.75rem;
344
+ color: var(--edition-faint);
345
+ }
346
+
347
+ // Showcase/advertorial listings are small directory-style units, not full pages.
348
+ &--advertorial .edition-ad__img { max-width: 22rem; }
349
+
350
+ // ---- floated in-article treatment -----------------------------------------
351
+ // The ad sits in the copy at the depth it held in print; the text wraps it, the
352
+ // way a fractional unit shares a page. Below 34rem there is no room to wrap a
353
+ // 1.05rem column, so it stays a normal centered block. Once the READER (not the
354
+ // viewport) is 12rem wider than the 44rem article column it breaks out to the
355
+ // right by however much free space the site's wrapper actually leaves beside
356
+ // the column (less a 2rem gutter, at most 13rem): AB's 1300px container gives
357
+ // the full 13rem, PMMI's ~1060px wrapper about 9rem — a fixed -13rem crossed
358
+ // PMMI's page-wrapper edge.
359
+ &--inline {
360
+ @media (min-width: 34rem) {
361
+ float: right;
362
+ clear: right;
363
+ width: 46%;
364
+ max-width: 19rem;
365
+ margin: 0.35rem 0 1.25rem 1.5rem;
366
+ text-align: right;
367
+ }
368
+
369
+ @media (min-width: 48rem) { width: 18rem; }
370
+
371
+ @container (min-width: 56rem) {
372
+ --edition-breakout: clamp(0rem, (100cqw - var(--edition-max)) / 2 - 2rem, 13rem);
373
+ width: calc(18rem + var(--edition-breakout) * 0.3);
374
+ max-width: none;
375
+ margin-right: calc(-1 * var(--edition-breakout));
376
+ }
377
+ }
378
+ }
379
+
380
+ // ---------------------------------------------------------------- replica
381
+ .edition-replica {
382
+ padding: 1.5rem 1rem 4rem;
383
+ // The canvas the pages float on — transparent in light mode, dark in dark mode.
384
+ background: var(--edition-canvas);
385
+
386
+ &__pages {
387
+ display: flex;
388
+ flex-direction: column;
389
+ align-items: center;
390
+ gap: 2rem;
391
+ }
392
+
393
+ &__page { margin: 0; width: 100%; max-width: 62rem; }
394
+
395
+ // aspect-ratio comes from the manifest so layout space is reserved before load.
396
+ &__frame {
397
+ position: relative;
398
+ width: 100%;
399
+ background: var(--edition-page-placeholder);
400
+ box-shadow: 0 2px 16px var(--edition-shadow);
401
+ }
402
+
403
+ &__img { display: block; width: 100%; height: auto; }
404
+
405
+ // Transparent tap target over an identified ad region.
406
+ &__hotspot {
407
+ position: absolute;
408
+ display: block;
409
+ border: 2px solid transparent;
410
+ border-radius: 2px;
411
+ transition: background-color 0.15s ease, border-color 0.15s ease;
412
+
413
+ &:hover,
414
+ &:focus-visible {
415
+ background: rgba(#ffd400, 0.18);
416
+ border-color: rgba(#ffd400, 0.9);
417
+ outline: none;
418
+ }
419
+ }
420
+
421
+ // Folio + preview note sit ON the canvas, so they follow it, not the chrome.
422
+ &__more {
423
+ margin: 0;
424
+ padding: 1rem;
425
+ text-align: center;
426
+ font-size: 0.85rem;
427
+ color: var(--edition-canvas-fg);
428
+ }
429
+
430
+ &__folio {
431
+ padding-top: 0.5rem;
432
+ text-align: center;
433
+ font-size: 0.75rem;
434
+ color: var(--edition-canvas-fg);
435
+ font-variant-numeric: tabular-nums;
436
+ }
437
+ }
438
+
439
+ // ---------------------------------------------------------------- gate
440
+ // The sign-in gate. Mirrors the content meter bar (fixed bottom, collapsible,
441
+ // email field visible) but styled with the edition's own tokens so it follows the
442
+ // reader's theme. The IdentityX login form inside is the same Vue component the
443
+ // content meter and hard-gated articles use.
444
+ .edition-gated {
445
+ // Room for the fixed bar so the teaser's last lines are never hidden under it.
446
+ padding-bottom: 14rem;
447
+ }
448
+
449
+ .edition-gate {
450
+ $self: &;
451
+ text-align: center;
452
+ color: var(--edition-fg);
453
+ background: var(--edition-surface);
454
+
455
+ &__panel {
456
+ max-width: 44rem;
457
+ margin: 0 auto;
458
+ }
459
+
460
+ &__title {
461
+ margin: 0;
462
+ font-size: 1.375rem;
463
+ font-weight: 700;
464
+ line-height: 1.2;
465
+ }
466
+
467
+ &__call-to-action {
468
+ margin: 0.5rem 0 0;
469
+ font-size: 1.0625rem;
470
+ }
471
+
472
+ &__site-name { font-style: italic; }
473
+
474
+ &__body {
475
+ display: flex;
476
+ justify-content: center;
477
+ margin-top: 1rem;
478
+ }
479
+
480
+ // The login form: email + button on one row where there is room; the
481
+ // required-create-fields step (new users) stacks beneath it.
482
+ &__form {
483
+ width: 100%;
484
+ max-width: 40rem;
485
+
486
+ .form-group label { color: var(--edition-fg); }
487
+ form > .form-group:first-child label { display: none; }
488
+ form > .form-group:first-child input { max-width: 100%; }
489
+ fieldset {
490
+ display: flex;
491
+ flex-direction: column;
492
+ text-align: left;
493
+ padding-bottom: 0.75rem;
494
+ .form-group { margin: 0.5rem 0 0; }
495
+ }
496
+ p { margin-bottom: 0; }
497
+
498
+ @media (min-width: 37.5rem) {
499
+ form:has(> :nth-child(-n+2):last-child) {
500
+ display: flex;
501
+ align-items: flex-end;
502
+ gap: 0.75rem;
503
+ .form-group { flex: 1 1 auto; margin-bottom: 0; }
504
+ .btn { white-space: nowrap; }
505
+ }
506
+ }
507
+ }
508
+
509
+ // --- fixed bottom bar (hub / continuous reader)
510
+ &--bar {
511
+ position: fixed;
512
+ left: 0;
513
+ right: 0;
514
+ bottom: 0;
515
+ z-index: 1501;
516
+ padding: 1rem 1rem 1.5rem;
517
+ max-height: 66vh;
518
+ overflow: auto;
519
+ border-top: 1px solid var(--edition-rule);
520
+ box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.25);
521
+
522
+ #{ $self }__toggle {
523
+ display: flex;
524
+ align-items: center;
525
+ justify-content: center;
526
+ gap: 0.5rem;
527
+ width: 100%;
528
+ padding: 0;
529
+ border: 0;
530
+ background: none;
531
+ color: inherit;
532
+ font: inherit;
533
+ cursor: pointer;
534
+ &:focus-visible { outline: 2px solid var(--edition-fg); outline-offset: 2px; }
535
+ }
536
+ #{ $self }__title--collapsed { display: none; }
537
+ #{ $self }__chevron {
538
+ width: 0.6rem;
539
+ height: 0.6rem;
540
+ border-right: 2px solid currentColor;
541
+ border-bottom: 2px solid currentColor;
542
+ transform: rotate(45deg);
543
+ transition: transform 0.2s;
544
+ }
545
+
546
+ &:not(#{ $self }--open) {
547
+ padding-bottom: 1rem;
548
+ #{ $self }__title--expanded,
549
+ #{ $self }__call-to-action,
550
+ #{ $self }__body { display: none; }
551
+ #{ $self }__title--collapsed { display: inline; }
552
+ #{ $self }__chevron { transform: rotate(-135deg); }
553
+ }
554
+ }
555
+
556
+ // --- inline (single article page)
557
+ &--inline {
558
+ margin: 1.5rem 0 2rem;
559
+ padding: 1.5rem 1rem;
560
+ border: 1px solid var(--edition-rule);
561
+ border-radius: 0.5rem;
562
+ }
563
+ }
564
+
565
+ // ---------------------------------------------------------------- article page
566
+ .edition-article-page {
567
+ max-width: var(--edition-max);
568
+ margin: 0 auto;
569
+ padding: 1.5rem 1rem 4rem;
570
+
571
+ &__breadcrumb {
572
+ display: flex;
573
+ flex-wrap: wrap;
574
+ gap: 0.5rem 1rem;
575
+ justify-content: space-between;
576
+ padding-bottom: 1rem;
577
+ font-size: 0.8rem;
578
+ border-bottom: 1px solid var(--edition-rule);
579
+
580
+ a {
581
+ color: inherit;
582
+ text-decoration: none;
583
+
584
+ // Pinned against the site's global near-black `a:hover`.
585
+ &:hover,
586
+ &:focus-visible { color: inherit; text-decoration: underline; }
587
+ }
588
+ }
589
+
590
+ &__position { color: var(--edition-faint); font-variant-numeric: tabular-nums; }
591
+
592
+ // On its own page the article owns the column, so drop the divider rule the
593
+ // stacked reader needs.
594
+ .edition-article { border-top: 0; padding-top: 1.25rem; }
595
+ }
596
+
597
+ // ---------------------------------------------------------------- byline / credits
598
+ .edition-article {
599
+ &__author { margin: 0 0 1.25rem; }
600
+
601
+ // `theme-author-node` takes its text colours from the monorail skin ($gray-800,
602
+ // #3f4043), which is blind to the reader's dark theme and unreadable on it.
603
+ // Re-point name + title at the edition tokens; hover keeps the skin's primary.
604
+ &__author .author-node__content-name,
605
+ &__author .author-node__content-name a,
606
+ &__author .author-node__content-title {
607
+ color: var(--edition-muted);
608
+ }
609
+
610
+ &__spread { color: var(--edition-faint); }
611
+
612
+ &__credits {
613
+ margin: 1.5rem 0 0;
614
+ padding-top: 0.75rem;
615
+ border-top: 1px solid var(--edition-rule);
616
+ font-size: 0.75rem;
617
+ color: var(--edition-faint);
618
+ }
619
+ }
620
+
621
+ // ---------------------------------------------------------------- pager
622
+ .edition-pager {
623
+ display: grid;
624
+ gap: 0.75rem;
625
+ grid-template-columns: 1fr;
626
+ margin: 2.5rem 0;
627
+
628
+ @media (min-width: 34rem) { grid-template-columns: 1fr 1fr; }
629
+
630
+ &__link {
631
+ display: flex;
632
+ flex-direction: column;
633
+ gap: 0.25rem;
634
+ padding: 0.9rem 1rem;
635
+ border: 1px solid var(--edition-rule);
636
+ border-radius: 4px;
637
+ text-decoration: none;
638
+ color: inherit;
639
+ transition: border-color 0.15s ease;
640
+
641
+ &:hover,
642
+ &:focus-visible { color: inherit; border-color: var(--edition-muted); text-decoration: none; }
643
+ &:focus-visible { outline: 2px solid var(--edition-muted); outline-offset: 2px; }
644
+ }
645
+
646
+ // Keep "next" on the right even when there is no "previous".
647
+ &__link--next { grid-column: 2; text-align: right; }
648
+
649
+ @media (max-width: 33.99rem) {
650
+ &__link--next { grid-column: 1; text-align: left; }
651
+ }
652
+
653
+ &__dir { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--edition-faint); }
654
+ &__title { font-size: 0.95rem; line-height: 1.3; }
655
+ }
656
+
657
+ // ---------------------------------------------------------------- hub extras
658
+ .edition-reflow {
659
+ &__more { margin: 3rem 0 0; }
660
+ &__more-title { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--edition-muted); }
661
+ &__more-list { list-style: none; margin: 0; padding: 0; }
662
+
663
+ &__more-link {
664
+ display: block;
665
+ padding: 0.6rem 0;
666
+ border-top: 1px solid var(--edition-rule);
667
+ color: inherit;
668
+ text-decoration: none;
669
+
670
+ &:hover,
671
+ &:focus-visible { color: inherit; text-decoration: underline; }
672
+ }
673
+
674
+ &__sentinel { height: 1px; }
675
+ }
676
+
677
+ .edition-toc__link.is-current {
678
+ .edition-toc__headline { font-weight: 700; }
679
+ }