@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.
- package/.eslintignore +1 -0
- package/LICENSE +21 -0
- package/README.md +121 -0
- package/components/ad.marko +81 -0
- package/components/ad.marko.js +102 -0
- package/components/article-body.marko +134 -0
- package/components/article-body.marko.js +186 -0
- package/components/author.marko +30 -0
- package/components/author.marko.js +59 -0
- package/components/gate.marko +158 -0
- package/components/gate.marko.js +147 -0
- package/components/ld-json.marko +5 -0
- package/components/ld-json.marko.js +29 -0
- package/components/marko.json +78 -0
- package/components/reflow.marko +67 -0
- package/components/reflow.marko.js +106 -0
- package/components/replica.marko +86 -0
- package/components/replica.marko.js +128 -0
- package/components/scroll.marko +134 -0
- package/components/scroll.marko.js +23 -0
- package/components/theme.marko +73 -0
- package/components/theme.marko.js +30 -0
- package/components/toc.marko +28 -0
- package/components/toc.marko.js +53 -0
- package/components/toolbar.marko +47 -0
- package/components/toolbar.marko.js +54 -0
- package/components/tracking.marko +211 -0
- package/components/tracking.marko.js +60 -0
- package/lib/authors.js +125 -0
- package/lib/entities.js +97 -0
- package/lib/index.js +453 -0
- package/marko.json +6 -0
- package/package.json +33 -0
- package/routes/index.js +129 -0
- package/scss/index.scss +679 -0
- package/templates/article.marko +108 -0
- package/templates/article.marko.js +238 -0
- package/templates/hub.marko +94 -0
- package/templates/hub.marko.js +219 -0
- package/templates/partial.marko +17 -0
- package/templates/partial.marko.js +44 -0
package/scss/index.scss
ADDED
|
@@ -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
|
+
}
|