@plus234feed/ds 0.0.0-stage → 0.4.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.
@@ -0,0 +1,1519 @@
1
+ /* Plus234Feed design system: editorial components.
2
+ Plain CSS on top of tokens.css and type.css. No framework, no build step.
3
+ Every rule here reads a token. A raw colour or font-size in this file is a bug. */
4
+
5
+ /* --- focus -------------------------------------------------------------
6
+ One focus style for the whole site. The amber needs the outer ring on a
7
+ light page; on a dark page the outer ring recedes and the amber carries it. */
8
+ .p234 :focus-visible,
9
+ .p234-focusable:focus-visible {
10
+ outline: 3px solid var(--p234-color-focus);
11
+ outline-offset: 0;
12
+ box-shadow: 0 0 0 6px var(--p234-color-focus-outer);
13
+ border-radius: var(--p234-radius-sm);
14
+ }
15
+
16
+ /* --- rules -------------------------------------------------------------
17
+ An editorial page separates with rules, not cards and shadows. */
18
+ .p234-rule {
19
+ border: 0;
20
+ border-top: var(--p234-border-hairline) solid var(--p234-color-rule);
21
+ margin: var(--p234-space-6) 0;
22
+ }
23
+ .p234-rule--section {
24
+ border-top-width: var(--p234-border-thick);
25
+ border-top-color: var(--p234-color-rule-strong);
26
+ }
27
+ .p234-rule--masthead {
28
+ border-top-width: var(--p234-border-heavy);
29
+ border-top-color: var(--p234-color-ink);
30
+ }
31
+
32
+ /* --- kicker ------------------------------------------------------------
33
+ The section label above a headline. Carries the only section colour on the
34
+ card, which is why section colours are never backgrounds. */
35
+ .p234-kicker {
36
+ font-family: var(--p234-type-kicker-family);
37
+ font-size: var(--p234-type-kicker-size);
38
+ line-height: var(--p234-type-kicker-leading);
39
+ font-weight: var(--p234-type-kicker-weight);
40
+ letter-spacing: var(--p234-type-kicker-tracking);
41
+ text-transform: var(--p234-type-kicker-case);
42
+ color: var(--p234-color-brand);
43
+ display: block;
44
+ margin-bottom: var(--p234-space-3);
45
+ }
46
+ .p234-kicker[data-section="politics"] { color: var(--p234-color-section-politics); }
47
+ .p234-kicker[data-section="business"] { color: var(--p234-color-section-business); }
48
+ .p234-kicker[data-section="markets"] { color: var(--p234-color-section-markets); }
49
+ .p234-kicker[data-section="world"] { color: var(--p234-color-section-world); }
50
+ .p234-kicker[data-section="sport"] { color: var(--p234-color-section-sport); }
51
+
52
+ /* --- story -------------------------------------------------------------
53
+ One component, four densities. This replaces the 47 files that each
54
+ re-implement an article card. */
55
+ .p234-story {
56
+ display: block;
57
+ color: inherit;
58
+ text-decoration: none;
59
+ padding-block: var(--p234-space-5);
60
+ border-top: var(--p234-border-hairline) solid var(--p234-color-rule);
61
+ }
62
+ .p234-story:first-child { border-top: 0; padding-top: 0; }
63
+
64
+ .p234-story__headline {
65
+ font-family: var(--p234-type-headline-sm-family);
66
+ font-size: var(--p234-type-headline-sm-size);
67
+ line-height: var(--p234-type-headline-sm-leading);
68
+ font-weight: var(--p234-type-headline-sm-weight);
69
+ letter-spacing: var(--p234-type-headline-sm-tracking);
70
+ color: var(--p234-color-ink);
71
+ margin: 0;
72
+ text-wrap: balance;
73
+ }
74
+ .p234-story:hover .p234-story__headline { text-decoration: underline; text-underline-offset: 2px; }
75
+ .p234-story:visited .p234-story__headline { color: var(--p234-color-link-visited); }
76
+
77
+ .p234-story__standfirst {
78
+ font-family: var(--p234-type-body-sm-family);
79
+ font-size: var(--p234-type-body-sm-size);
80
+ line-height: var(--p234-type-body-sm-leading);
81
+ color: var(--p234-color-ink-muted);
82
+ max-width: var(--p234-measure-narrow);
83
+ margin: var(--p234-space-3) 0 0;
84
+ }
85
+
86
+ .p234-story--lead .p234-story__headline {
87
+ font-size: var(--p234-type-display-1-size);
88
+ line-height: var(--p234-type-display-1-leading);
89
+ letter-spacing: var(--p234-type-display-1-tracking);
90
+ }
91
+ .p234-story--lead .p234-story__standfirst {
92
+ font-family: var(--p234-type-standfirst-family);
93
+ font-size: var(--p234-type-standfirst-size);
94
+ line-height: var(--p234-type-standfirst-leading);
95
+ color: var(--p234-color-ink);
96
+ max-width: var(--p234-measure-text);
97
+ }
98
+ .p234-story--secondary .p234-story__headline {
99
+ font-size: var(--p234-type-headline-size);
100
+ line-height: var(--p234-type-headline-leading);
101
+ letter-spacing: var(--p234-type-headline-tracking);
102
+ }
103
+ .p234-story--compact {
104
+ padding-block: var(--p234-space-4);
105
+ }
106
+ .p234-story--compact .p234-story__headline {
107
+ font-size: var(--p234-type-headline-xs-size);
108
+ line-height: var(--p234-type-headline-xs-leading);
109
+ }
110
+ .p234-story--compact .p234-story__standfirst { display: none; }
111
+
112
+ /* --- story media -------------------------------------------------------
113
+ Most story cards on the site carry a picture. The image is a child of the
114
+ story, not a separate component, so the two cannot drift apart. */
115
+ .p234-story__media {
116
+ display: block;
117
+ width: 100%;
118
+ aspect-ratio: var(--p234-figure-ratio);
119
+ object-fit: cover;
120
+ background: var(--p234-color-paper-sunk);
121
+ border-radius: var(--p234-radius-sm);
122
+ margin-bottom: var(--p234-space-4);
123
+ }
124
+
125
+ /* Card: picture on top, headline under it. The homepage grid. */
126
+ .p234-story--card { display: flex; flex-direction: column; }
127
+
128
+ /* Thumb: picture beside the headline, for dense lists and rails. The image
129
+ comes second in the markup so the headline is read first, and is pulled
130
+ back into place visually. */
131
+ .p234-story--thumb { display: grid; grid-template-columns: 1fr 5.5rem; gap: var(--p234-space-4); align-items: start; }
132
+ .p234-story--thumb .p234-story__media { grid-column: 2; grid-row: 1 / span 3; margin-bottom: 0; }
133
+ .p234-story--thumb .p234-story__standfirst { display: none; }
134
+
135
+ /* Lead with a picture: the image sits above the headline at full width. */
136
+ .p234-story--lead .p234-story__media { margin-bottom: var(--p234-space-5); }
137
+
138
+ /* --- avatar ------------------------------------------------------------
139
+ A person: a legislator, a candidate, an author. */
140
+ .p234-avatar {
141
+ display: block;
142
+ width: 2.75rem;
143
+ height: 2.75rem;
144
+ border-radius: var(--p234-radius-pill);
145
+ object-fit: cover;
146
+ background: var(--p234-color-paper-sunk);
147
+ flex-shrink: 0;
148
+ }
149
+ .p234-avatar--sm { width: 1.75rem; height: 1.75rem; }
150
+ .p234-avatar--lg { width: 5rem; height: 5rem; }
151
+ .p234-avatar--xl { width: 8rem; height: 8rem; }
152
+ /* Initials when there is no photograph, which is common for newer members. */
153
+ .p234-avatar--initials {
154
+ display: inline-flex;
155
+ align-items: center;
156
+ justify-content: center;
157
+ font-family: var(--p234-type-ui-family);
158
+ font-weight: 700;
159
+ color: var(--p234-color-ink-muted);
160
+ background: var(--p234-color-paper-sunk);
161
+ }
162
+
163
+ /* --- logo --------------------------------------------------------------
164
+ A company, a team, a publisher. Square, contained, never cropped: a
165
+ cropped logo is a different logo. */
166
+ .p234-logo {
167
+ display: block;
168
+ width: 2rem;
169
+ height: 2rem;
170
+ object-fit: contain;
171
+ background: var(--p234-color-paper-sunk);
172
+ border-radius: var(--p234-radius-sm);
173
+ padding: 2px;
174
+ flex-shrink: 0;
175
+ }
176
+ .p234-logo--lg { width: 3rem; height: 3rem; }
177
+
178
+ /* --- meta and coverage -------------------------------------------------
179
+ Source, timestamp, and the number of outlets carrying the story. The
180
+ coverage count is the one thing an aggregator can say that a single
181
+ publisher cannot, so it is a first-class part of the card. */
182
+ .p234-meta {
183
+ font-family: var(--p234-type-meta-family);
184
+ font-size: var(--p234-type-meta-size);
185
+ line-height: var(--p234-type-meta-leading);
186
+ color: var(--p234-color-ink-muted);
187
+ display: flex;
188
+ flex-wrap: wrap;
189
+ align-items: center;
190
+ gap: var(--p234-space-3);
191
+ margin-top: var(--p234-space-3);
192
+ }
193
+ .p234-meta__dot { color: var(--p234-color-rule-strong); }
194
+ .p234-meta__source { font-weight: 600; color: var(--p234-color-ink); }
195
+
196
+ .p234-coverage {
197
+ font-family: var(--p234-type-ui-sm-family);
198
+ font-size: var(--p234-type-ui-sm-size);
199
+ font-weight: 600;
200
+ color: var(--p234-color-brand);
201
+ border: var(--p234-border-hairline) solid var(--p234-color-brand);
202
+ border-radius: var(--p234-radius-pill);
203
+ padding: 1px var(--p234-space-3);
204
+ white-space: nowrap;
205
+ }
206
+
207
+ /* --- tag ---------------------------------------------------------------
208
+ The only pill-shaped thing in the system. */
209
+ .p234-tag {
210
+ display: inline-block;
211
+ font-family: var(--p234-type-ui-sm-family);
212
+ font-size: var(--p234-type-ui-sm-size);
213
+ font-weight: var(--p234-type-ui-sm-weight);
214
+ color: var(--p234-color-ink);
215
+ background: var(--p234-color-paper-sunk);
216
+ border-radius: var(--p234-radius-pill);
217
+ padding: var(--p234-space-2) var(--p234-space-4);
218
+ text-decoration: none;
219
+ }
220
+ .p234-tag[aria-pressed="true"] {
221
+ background: var(--p234-color-brand-tint);
222
+ box-shadow: inset 0 0 0 1px var(--p234-color-brand);
223
+ }
224
+
225
+ /* --- button ------------------------------------------------------------ */
226
+ .p234-btn {
227
+ font-family: var(--p234-type-ui-family);
228
+ font-size: var(--p234-type-ui-size);
229
+ font-weight: 600;
230
+ line-height: 1;
231
+ padding: var(--p234-space-4) var(--p234-space-5);
232
+ border-radius: var(--p234-radius-md);
233
+ border: var(--p234-border-hairline) solid transparent;
234
+ background: var(--p234-color-brand);
235
+ color: var(--p234-color-on-brand);
236
+ cursor: pointer;
237
+ transition: background var(--p234-motion-duration-fast) var(--p234-motion-easing-standard);
238
+ }
239
+ .p234-btn:hover { background: var(--p234-color-brand-hover); }
240
+ .p234-btn--secondary {
241
+ background: transparent;
242
+ color: var(--p234-color-ink);
243
+ border-color: var(--p234-color-rule-strong);
244
+ }
245
+ .p234-btn--secondary:hover { background: var(--p234-color-paper-sunk); }
246
+
247
+ /* --- data --------------------------------------------------------------
248
+ Figures are sans and tabular so columns line up. Direction is never colour
249
+ alone: the arrow glyph carries it for anyone who cannot separate the hues. */
250
+ .p234-tile {
251
+ border-top: var(--p234-border-thick) solid var(--p234-color-ink);
252
+ padding-top: var(--p234-space-4);
253
+ }
254
+ .p234-tile__label {
255
+ font-family: var(--p234-type-meta-family);
256
+ font-size: var(--p234-type-meta-size);
257
+ color: var(--p234-color-ink-muted);
258
+ display: block;
259
+ }
260
+ .p234-tile__figure {
261
+ font-family: var(--p234-type-data-lg-family);
262
+ font-size: var(--p234-type-data-lg-size);
263
+ line-height: var(--p234-type-data-lg-leading);
264
+ font-weight: var(--p234-type-data-lg-weight);
265
+ letter-spacing: var(--p234-type-data-lg-tracking);
266
+ font-variant-numeric: var(--p234-type-data-lg-numeric);
267
+ color: var(--p234-color-ink);
268
+ display: block;
269
+ margin-top: var(--p234-space-2);
270
+ }
271
+ .p234-delta {
272
+ font-family: var(--p234-type-data-sm-family);
273
+ font-size: var(--p234-type-data-sm-size);
274
+ font-weight: var(--p234-type-data-sm-weight);
275
+ font-variant-numeric: var(--p234-type-data-sm-numeric);
276
+ color: var(--p234-color-data-flat);
277
+ }
278
+ .p234-delta[data-direction="up"] { color: var(--p234-color-data-up); }
279
+ .p234-delta[data-direction="down"] { color: var(--p234-color-data-down); }
280
+ .p234-delta[data-direction="up"]::before { content: "\25B2 "; }
281
+ .p234-delta[data-direction="down"]::before { content: "\25BC "; }
282
+
283
+ .p234-table {
284
+ width: 100%;
285
+ border-collapse: collapse;
286
+ font-family: var(--p234-type-data-family);
287
+ font-size: var(--p234-type-data-sm-size);
288
+ font-variant-numeric: tabular-nums;
289
+ }
290
+ .p234-table th {
291
+ font-family: var(--p234-type-ui-sm-family);
292
+ font-size: var(--p234-type-ui-sm-size);
293
+ font-weight: 600;
294
+ text-align: left;
295
+ color: var(--p234-color-ink-muted);
296
+ padding: var(--p234-space-3) var(--p234-space-4);
297
+ border-bottom: var(--p234-border-thick) solid var(--p234-color-ink);
298
+ }
299
+ .p234-table td {
300
+ padding: var(--p234-space-3) var(--p234-space-4);
301
+ border-bottom: var(--p234-border-hairline) solid var(--p234-color-rule);
302
+ color: var(--p234-color-ink);
303
+ }
304
+ .p234-table td:not(:first-child),
305
+ .p234-table th:not(:first-child) { text-align: right; }
306
+
307
+ /* --- prose -------------------------------------------------------------
308
+ The article body. Capped at a measure because a full-width line of 18px
309
+ serif is unreadable, which is most of what "looks like a dashboard" means. */
310
+ .p234-prose {
311
+ font-family: var(--p234-type-body-family);
312
+ font-size: var(--p234-type-body-size);
313
+ line-height: var(--p234-type-body-leading);
314
+ color: var(--p234-color-ink);
315
+ max-width: var(--p234-measure-text);
316
+ }
317
+ .p234-prose > * + * { margin-top: var(--p234-space-5); }
318
+ .p234-prose a { color: var(--p234-color-link); text-underline-offset: 2px; }
319
+ .p234-prose a:visited { color: var(--p234-color-link-visited); }
320
+ .p234-prose blockquote {
321
+ font-family: var(--p234-type-quote-family);
322
+ font-size: var(--p234-type-quote-size);
323
+ line-height: var(--p234-type-quote-leading);
324
+ letter-spacing: var(--p234-type-quote-tracking);
325
+ border-left: var(--p234-border-heavy) solid var(--p234-color-brand);
326
+ padding-left: var(--p234-space-5);
327
+ margin-inline: 0;
328
+ color: var(--p234-color-ink);
329
+ }
330
+
331
+ /* --- ad slot -----------------------------------------------------------
332
+ Declared, labelled and bounded. The article template allows two of these,
333
+ total. The count is a design decision, not an inventory decision: at six
334
+ plus a sticky, the page reads as an ad wrapper and the reader leaves. */
335
+ .p234-adslot {
336
+ background: var(--p234-color-paper-sunk);
337
+ border: var(--p234-border-hairline) dashed var(--p234-color-rule-strong);
338
+ border-radius: var(--p234-radius-sm);
339
+ padding: var(--p234-space-5);
340
+ text-align: center;
341
+ max-width: var(--p234-measure-text);
342
+ }
343
+ .p234-adslot::before {
344
+ content: "Advertisement";
345
+ display: block;
346
+ font-family: var(--p234-type-caption-family);
347
+ font-size: var(--p234-type-caption-size);
348
+ letter-spacing: 0.06em;
349
+ text-transform: uppercase;
350
+ color: var(--p234-color-ink-subtle);
351
+ margin-bottom: var(--p234-space-3);
352
+ }
353
+
354
+ /* --- next step ---------------------------------------------------------
355
+ The module the GA4 numbers say is missing: 114 seconds on the page, then
356
+ 1.18 pages per session. A reader who arrived on a visa story wants the next
357
+ visa story, not the general feed. */
358
+ .p234-nextstep {
359
+ border-top: var(--p234-border-thick) solid var(--p234-color-ink);
360
+ padding-top: var(--p234-space-4);
361
+ max-width: var(--p234-measure-text);
362
+ }
363
+ .p234-nextstep__title {
364
+ font-family: var(--p234-type-kicker-family);
365
+ font-size: var(--p234-type-kicker-size);
366
+ font-weight: var(--p234-type-kicker-weight);
367
+ letter-spacing: var(--p234-type-kicker-tracking);
368
+ text-transform: var(--p234-type-kicker-case);
369
+ color: var(--p234-color-ink-muted);
370
+ margin: 0 0 var(--p234-space-4);
371
+ }
372
+ .p234-nextstep ol { margin: 0; padding-left: var(--p234-space-6); }
373
+ .p234-nextstep li { margin-bottom: var(--p234-space-4); }
374
+ .p234-nextstep li::marker {
375
+ font-family: var(--p234-type-ui-sm-family);
376
+ font-weight: 700;
377
+ color: var(--p234-color-brand);
378
+ }
379
+ .p234-nextstep a {
380
+ font-family: var(--p234-type-headline-xs-family);
381
+ font-size: var(--p234-type-headline-xs-size);
382
+ line-height: var(--p234-type-headline-xs-leading);
383
+ font-weight: var(--p234-type-headline-xs-weight);
384
+ color: var(--p234-color-ink);
385
+ text-decoration: none;
386
+ }
387
+ .p234-nextstep a:hover { text-decoration: underline; text-underline-offset: 2px; }
388
+
389
+ /* --- breadcrumb --------------------------------------------------------
390
+ 34 files hand-roll this today, each with its own separator and spacing. */
391
+ .p234-breadcrumb {
392
+ font-family: var(--p234-type-meta-family);
393
+ font-size: var(--p234-type-meta-size);
394
+ color: var(--p234-color-ink-muted);
395
+ margin-bottom: var(--p234-space-5);
396
+ }
397
+ .p234-breadcrumb ol {
398
+ list-style: none;
399
+ margin: 0;
400
+ padding: 0;
401
+ display: flex;
402
+ flex-wrap: wrap;
403
+ align-items: center;
404
+ gap: var(--p234-space-3);
405
+ }
406
+ .p234-breadcrumb li { display: flex; align-items: center; gap: var(--p234-space-3); }
407
+ .p234-breadcrumb li + li::before {
408
+ content: "/";
409
+ color: var(--p234-color-rule-strong);
410
+ }
411
+ .p234-breadcrumb a { color: var(--p234-color-ink-muted); text-decoration: none; }
412
+ .p234-breadcrumb a:hover { color: var(--p234-color-ink); text-decoration: underline; text-underline-offset: 2px; }
413
+ .p234-breadcrumb [aria-current="page"] { color: var(--p234-color-ink); }
414
+
415
+ /* --- figure ------------------------------------------------------------
416
+ An image with its credit. The credit is not optional: the archive
417
+ summarises other outlets, and their photographs belong to them. */
418
+ .p234-figure { margin: 0 0 var(--p234-space-6); }
419
+ .p234-figure img {
420
+ display: block;
421
+ width: 100%;
422
+ height: auto;
423
+ /* The space is reserved before the image lands, so the text below does not
424
+ jump under the reader. This is why the ratio is a token and not a guess. */
425
+ aspect-ratio: var(--p234-figure-ratio);
426
+ object-fit: cover;
427
+ border-radius: var(--p234-radius-sm);
428
+ background: var(--p234-color-paper-sunk);
429
+ }
430
+
431
+ /* Hero: the lead image on an article. Capped in height so the first paragraph
432
+ is still on screen on a laptop. */
433
+ .p234-figure--hero img { max-height: var(--p234-figure-hero-max-height); }
434
+
435
+ /* Portrait, for people. A face cropped to 16:9 is a forehead and a chin. */
436
+ .p234-figure--portrait img { aspect-ratio: var(--p234-figure-ratio-portrait); }
437
+ .p234-figure--square img { aspect-ratio: var(--p234-figure-ratio-square); }
438
+
439
+ /* Inset: beside the text once there is room for it, full width before that. */
440
+ @media (min-width: 48rem) {
441
+ .p234-figure--inset {
442
+ float: right;
443
+ width: var(--p234-figure-inset-width);
444
+ margin: 0 0 var(--p234-space-5) var(--p234-space-6);
445
+ }
446
+ }
447
+
448
+ /* An image whose host the resizer cannot touch arrives at its original size and
449
+ shape. Letterbox it rather than crop blind: for a third of the archive this
450
+ is the normal case, not the edge case. */
451
+ .p234-figure--unsized img {
452
+ aspect-ratio: auto;
453
+ object-fit: contain;
454
+ max-height: var(--p234-figure-hero-max-height);
455
+ }
456
+ .p234-figure figcaption {
457
+ font-family: var(--p234-type-caption-family);
458
+ font-size: var(--p234-type-caption-size);
459
+ line-height: var(--p234-type-caption-leading);
460
+ color: var(--p234-color-ink-muted);
461
+ margin-top: var(--p234-space-3);
462
+ max-width: var(--p234-measure-text);
463
+ }
464
+ .p234-figure__credit { color: var(--p234-color-ink-subtle); }
465
+
466
+ /* --- toggle ------------------------------------------------------------
467
+ Save, follow topic, follow source. One control, three labels. The state
468
+ lives in aria-pressed, so it is announced rather than merely coloured. */
469
+ .p234-toggle {
470
+ display: inline-flex;
471
+ align-items: center;
472
+ gap: var(--p234-space-3);
473
+ font-family: var(--p234-type-ui-family);
474
+ font-size: var(--p234-type-ui-size);
475
+ font-weight: 600;
476
+ line-height: 1;
477
+ min-height: var(--p234-field-height);
478
+ padding: 0 var(--p234-space-5);
479
+ background: transparent;
480
+ color: var(--p234-color-ink);
481
+ border: var(--p234-border-hairline) solid var(--p234-color-rule-strong);
482
+ border-radius: var(--p234-radius-md);
483
+ cursor: pointer;
484
+ transition: background var(--p234-motion-duration-fast) var(--p234-motion-easing-standard);
485
+ }
486
+ .p234-toggle:hover { background: var(--p234-color-paper-sunk); }
487
+ .p234-toggle[aria-pressed="true"] {
488
+ background: var(--p234-color-brand-tint);
489
+ border-color: var(--p234-color-brand);
490
+ color: var(--p234-color-brand);
491
+ }
492
+ /* The icon changes with the state too, so the change is not colour alone. */
493
+ .p234-toggle__on, .p234-toggle[aria-pressed="true"] .p234-toggle__off { display: none; }
494
+ .p234-toggle[aria-pressed="true"] .p234-toggle__on { display: inline; }
495
+
496
+ /* --- share -------------------------------------------------------------- */
497
+ .p234-share {
498
+ display: flex;
499
+ flex-wrap: wrap;
500
+ align-items: center;
501
+ gap: var(--p234-space-3);
502
+ border-top: var(--p234-border-hairline) solid var(--p234-color-rule);
503
+ border-bottom: var(--p234-border-hairline) solid var(--p234-color-rule);
504
+ padding: var(--p234-space-4) 0;
505
+ margin: var(--p234-space-6) 0;
506
+ max-width: var(--p234-measure-text);
507
+ }
508
+ .p234-share__label {
509
+ font-family: var(--p234-type-kicker-family);
510
+ font-size: var(--p234-type-kicker-size);
511
+ font-weight: var(--p234-type-kicker-weight);
512
+ letter-spacing: var(--p234-type-kicker-tracking);
513
+ text-transform: var(--p234-type-kicker-case);
514
+ color: var(--p234-color-ink-muted);
515
+ margin-right: var(--p234-space-3);
516
+ }
517
+ .p234-share__btn {
518
+ display: inline-flex;
519
+ align-items: center;
520
+ justify-content: center;
521
+ gap: var(--p234-space-3);
522
+ min-width: var(--p234-field-height);
523
+ min-height: var(--p234-field-height);
524
+ padding: 0 var(--p234-space-4);
525
+ font-family: var(--p234-type-ui-sm-family);
526
+ font-size: var(--p234-type-ui-sm-size);
527
+ font-weight: 600;
528
+ color: var(--p234-color-ink);
529
+ background: transparent;
530
+ border: var(--p234-border-hairline) solid var(--p234-color-rule);
531
+ border-radius: var(--p234-radius-md);
532
+ cursor: pointer;
533
+ text-decoration: none;
534
+ }
535
+ .p234-share__btn:hover { background: var(--p234-color-paper-sunk); border-color: var(--p234-color-rule-strong); }
536
+
537
+ /* --- subscribe ---------------------------------------------------------
538
+ The newsletter block. One field, one button, one line of what you get. */
539
+ .p234-subscribe {
540
+ border-top: var(--p234-border-thick) solid var(--p234-color-ink);
541
+ padding-top: var(--p234-space-5);
542
+ max-width: var(--p234-measure-text);
543
+ }
544
+ .p234-subscribe__title {
545
+ font-family: var(--p234-type-headline-sm-family);
546
+ font-size: var(--p234-type-headline-sm-size);
547
+ line-height: var(--p234-type-headline-sm-leading);
548
+ font-weight: var(--p234-type-headline-sm-weight);
549
+ letter-spacing: var(--p234-type-headline-sm-tracking);
550
+ color: var(--p234-color-ink);
551
+ margin: 0 0 var(--p234-space-3);
552
+ }
553
+ .p234-subscribe__what {
554
+ font-family: var(--p234-type-body-sm-family);
555
+ font-size: var(--p234-type-body-sm-size);
556
+ line-height: var(--p234-type-body-sm-leading);
557
+ color: var(--p234-color-ink-muted);
558
+ margin: 0 0 var(--p234-space-5);
559
+ }
560
+ .p234-subscribe__row { display: flex; flex-wrap: wrap; gap: var(--p234-space-3); }
561
+ .p234-subscribe__field {
562
+ flex: 1 1 14rem;
563
+ min-width: 0;
564
+ height: var(--p234-field-height);
565
+ padding: 0 var(--p234-field-padding-x);
566
+ background: var(--p234-color-field-bg);
567
+ color: var(--p234-color-ink);
568
+ border: var(--p234-field-border-width) solid var(--p234-color-field-border);
569
+ border-radius: var(--p234-field-radius);
570
+ font-family: var(--p234-type-ui-family);
571
+ font-size: var(--p234-type-ui-size);
572
+ }
573
+ .p234-subscribe__field::placeholder { color: var(--p234-color-field-ink-placeholder); }
574
+ .p234-subscribe__field:hover { border-color: var(--p234-color-field-border-hover); }
575
+ .p234-subscribe__field[aria-invalid="true"] {
576
+ border-width: var(--p234-field-border-width-invalid);
577
+ border-color: var(--p234-color-error);
578
+ }
579
+ .p234-subscribe__note {
580
+ font-family: var(--p234-type-caption-family);
581
+ font-size: var(--p234-type-caption-size);
582
+ color: var(--p234-color-ink-subtle);
583
+ margin: var(--p234-space-4) 0 0;
584
+ }
585
+ .p234-subscribe__error {
586
+ font-family: var(--p234-type-meta-family);
587
+ font-size: var(--p234-type-meta-size);
588
+ color: var(--p234-color-error);
589
+ margin: var(--p234-space-3) 0 0;
590
+ }
591
+
592
+ /* --- masthead ----------------------------------------------------------
593
+ A newspaper masthead, not an app bar. Three rows:
594
+
595
+ utility search, edition links, account
596
+ brand the wordmark, centred, with date and figures flanking it
597
+ sections the section nav, with a full width panel beneath it
598
+
599
+ The centred wordmark is the whole point: it is what makes the top of the
600
+ page read as a paper rather than a product. */
601
+ .p234-masthead {
602
+ position: sticky;
603
+ top: 0;
604
+ z-index: var(--p234-z-sticky);
605
+ background: var(--p234-color-paper);
606
+ border-bottom: var(--p234-border-hairline) solid var(--p234-color-rule);
607
+ }
608
+
609
+ /* Row 1: utility. Hidden on phones, where the bar collapses to one row. */
610
+ .p234-masthead__utility {
611
+ display: none;
612
+ border-bottom: var(--p234-border-hairline) solid var(--p234-color-rule);
613
+ }
614
+ @media (min-width: 62rem) { .p234-masthead__utility { display: block; } }
615
+ .p234-masthead__utility-in {
616
+ max-width: var(--p234-layout-container);
617
+ margin: 0 auto;
618
+ padding: var(--p234-space-3) var(--p234-layout-gutter-lg);
619
+ display: grid;
620
+ grid-template-columns: 1fr auto 1fr;
621
+ align-items: center;
622
+ gap: var(--p234-space-5);
623
+ }
624
+ .p234-masthead__editions {
625
+ display: flex;
626
+ justify-content: center;
627
+ gap: var(--p234-space-5);
628
+ }
629
+ .p234-masthead__editions a {
630
+ font-family: var(--p234-type-ui-sm-family);
631
+ font-size: var(--p234-type-ui-sm-size);
632
+ font-weight: 600;
633
+ letter-spacing: 0.04em;
634
+ text-transform: uppercase;
635
+ color: var(--p234-color-ink-muted);
636
+ text-decoration: none;
637
+ white-space: nowrap;
638
+ }
639
+ .p234-masthead__editions a:hover,
640
+ .p234-masthead__editions a[aria-current] { color: var(--p234-color-ink); }
641
+
642
+ /* Row 2: the wordmark, centred. */
643
+ .p234-masthead__bar {
644
+ max-width: var(--p234-layout-container);
645
+ margin: 0 auto;
646
+ padding: var(--p234-space-4) var(--p234-layout-gutter);
647
+ display: grid;
648
+ grid-template-columns: 1fr auto 1fr;
649
+ align-items: center;
650
+ gap: var(--p234-space-4);
651
+ }
652
+ @media (min-width: 62rem) {
653
+ .p234-masthead__bar { padding: var(--p234-space-5) var(--p234-layout-gutter-lg); }
654
+ }
655
+ .p234-masthead__brand { justify-self: center; display: block; text-decoration: none; }
656
+ .p234-masthead__logo { display: block; height: 1.75rem; width: auto; }
657
+ @media (min-width: 62rem) { .p234-masthead__logo { height: 2.5rem; } }
658
+ .p234-masthead__logo--dark { display: none; }
659
+ .dark .p234-masthead__logo--light,
660
+ [data-theme="dark"] .p234-masthead__logo--light { display: none; }
661
+ .dark .p234-masthead__logo--dark,
662
+ [data-theme="dark"] .p234-masthead__logo--dark { display: block; }
663
+
664
+ .p234-masthead__left { display: flex; align-items: center; gap: var(--p234-space-3); }
665
+ .p234-masthead__right { display: flex; align-items: center; justify-content: flex-end; gap: var(--p234-space-3); }
666
+ .p234-masthead__dateline {
667
+ display: none;
668
+ font-family: var(--p234-type-meta-family);
669
+ font-size: var(--p234-type-meta-size);
670
+ color: var(--p234-color-ink-muted);
671
+ line-height: 1.3;
672
+ }
673
+ @media (min-width: 62rem) { .p234-masthead__dateline { display: block; } }
674
+
675
+ .p234-masthead__icon {
676
+ display: inline-flex;
677
+ align-items: center;
678
+ justify-content: center;
679
+ width: var(--p234-field-height);
680
+ height: var(--p234-field-height);
681
+ background: transparent;
682
+ color: var(--p234-color-ink);
683
+ border: 0;
684
+ border-radius: var(--p234-radius-md);
685
+ cursor: pointer;
686
+ }
687
+ .p234-masthead__icon:hover { background: var(--p234-color-paper-sunk); }
688
+
689
+ /* Phone only: the hamburger and the compact sign in. On desktop the utility
690
+ row carries these, so showing them twice is just clutter. */
691
+ @media (min-width: 62rem) {
692
+ .p234-masthead__menubtn,
693
+ .p234-masthead__compact { display: none !important; }
694
+ }
695
+
696
+ /* Row 3: the sections. Centred on desktop, scrolls sideways on a phone. */
697
+ .p234-masthead__sections {
698
+ border-top: var(--p234-border-hairline) solid var(--p234-color-rule);
699
+ overflow-x: auto;
700
+ scrollbar-width: none;
701
+ }
702
+ .p234-masthead__sections::-webkit-scrollbar { display: none; }
703
+ .p234-masthead__sections-in {
704
+ max-width: var(--p234-layout-container);
705
+ margin: 0 auto;
706
+ padding: 0 var(--p234-layout-gutter);
707
+ display: flex;
708
+ gap: var(--p234-space-6);
709
+ }
710
+ @media (min-width: 62rem) {
711
+ .p234-masthead__sections-in { justify-content: center; padding-inline: var(--p234-layout-gutter-lg); }
712
+ }
713
+ .p234-masthead__section {
714
+ display: inline-flex;
715
+ align-items: center;
716
+ gap: var(--p234-space-2);
717
+ flex-shrink: 0;
718
+ background: none;
719
+ border: 0;
720
+ cursor: pointer;
721
+ padding: var(--p234-space-4) 0;
722
+ font-family: var(--p234-type-ui-family);
723
+ font-size: var(--p234-type-ui-size);
724
+ font-weight: 500;
725
+ color: var(--p234-color-ink);
726
+ text-decoration: none;
727
+ white-space: nowrap;
728
+ border-bottom: var(--p234-border-thick) solid transparent;
729
+ margin-bottom: -1px;
730
+ }
731
+ .p234-masthead__section:hover { border-bottom-color: var(--p234-color-rule-strong); }
732
+ .p234-masthead__section[aria-expanded="true"],
733
+ .p234-masthead__section[aria-current] { border-bottom-color: var(--p234-color-ink); }
734
+
735
+ /* The panel. Full width of the viewport, not a box anchored to a button:
736
+ a dropdown pinned under the last nav item runs off the right edge. */
737
+ .p234-megamenu {
738
+ position: absolute;
739
+ left: 0;
740
+ right: 0;
741
+ top: 100%;
742
+ z-index: var(--p234-z-dropdown);
743
+ background: var(--p234-color-paper);
744
+ border-top: var(--p234-border-hairline) solid var(--p234-color-rule);
745
+ border-bottom: var(--p234-border-hairline) solid var(--p234-color-rule);
746
+ box-shadow: var(--p234-shadow-overlay);
747
+ }
748
+ .p234-megamenu__in {
749
+ max-width: var(--p234-layout-container);
750
+ margin: 0 auto;
751
+ padding: var(--p234-space-7) var(--p234-layout-gutter-lg);
752
+ display: grid;
753
+ gap: var(--p234-space-7);
754
+ grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
755
+ }
756
+ .p234-megamenu ul { list-style: none; margin: 0; padding: 0; }
757
+ .p234-megamenu li { margin-bottom: var(--p234-space-4); }
758
+ .p234-megamenu a {
759
+ font-family: var(--p234-type-ui-family);
760
+ font-size: var(--p234-type-ui-size);
761
+ color: var(--p234-color-ink);
762
+ text-decoration: none;
763
+ }
764
+ .p234-megamenu a:hover { text-decoration: underline; text-underline-offset: 2px; }
765
+
766
+ /* The phone drawer: a full screen panel, search first, then the sections as
767
+ accordions. <details> so it works with no JavaScript. */
768
+ .p234-drawer {
769
+ position: fixed;
770
+ inset: 0;
771
+ z-index: var(--p234-z-overlay);
772
+ background: var(--p234-color-paper);
773
+ overflow-y: auto;
774
+ padding: var(--p234-space-5) var(--p234-layout-gutter) var(--p234-space-9);
775
+ }
776
+ .p234-drawer__head { display: flex; justify-content: flex-start; margin-bottom: var(--p234-space-6); }
777
+ .p234-drawer__search { display: flex; gap: var(--p234-space-3); margin-bottom: var(--p234-space-6); }
778
+ .p234-drawer__group { border-top: var(--p234-border-hairline) solid var(--p234-color-rule); }
779
+ .p234-drawer__group > summary {
780
+ list-style: none;
781
+ display: flex;
782
+ align-items: center;
783
+ justify-content: space-between;
784
+ padding: var(--p234-space-5) 0;
785
+ cursor: pointer;
786
+ font-family: var(--p234-type-headline-xs-family);
787
+ font-size: var(--p234-type-headline-xs-size);
788
+ font-weight: var(--p234-type-headline-xs-weight);
789
+ color: var(--p234-color-ink);
790
+ }
791
+ .p234-drawer__group > summary::-webkit-details-marker { display: none; }
792
+ .p234-drawer__links {
793
+ display: grid;
794
+ grid-template-columns: 1fr 1fr;
795
+ gap: var(--p234-space-4);
796
+ padding-bottom: var(--p234-space-5);
797
+ }
798
+ .p234-drawer__links a {
799
+ font-family: var(--p234-type-ui-family);
800
+ font-size: var(--p234-type-ui-size);
801
+ color: var(--p234-color-ink);
802
+ text-decoration: none;
803
+ }
804
+
805
+ /* --- subnav ------------------------------------------------------------
806
+ Section level navigation: the price pages, the account panes. Scrolls
807
+ sideways on a phone rather than wrapping into two rows. */
808
+ .p234-subnav {
809
+ border-bottom: var(--p234-border-hairline) solid var(--p234-color-rule);
810
+ overflow-x: auto;
811
+ scrollbar-width: none;
812
+ }
813
+ .p234-subnav::-webkit-scrollbar { display: none; }
814
+ .p234-subnav ul {
815
+ display: flex;
816
+ gap: var(--p234-space-5);
817
+ list-style: none;
818
+ margin: 0;
819
+ padding: 0 var(--p234-layout-gutter);
820
+ max-width: var(--p234-layout-container);
821
+ margin-inline: auto;
822
+ }
823
+ .p234-subnav a {
824
+ display: block;
825
+ font-family: var(--p234-type-ui-sm-family);
826
+ font-size: var(--p234-type-ui-sm-size);
827
+ font-weight: 600;
828
+ color: var(--p234-color-ink-muted);
829
+ text-decoration: none;
830
+ white-space: nowrap;
831
+ padding: var(--p234-space-4) 0;
832
+ border-bottom: var(--p234-border-thick) solid transparent;
833
+ margin-bottom: -1px;
834
+ }
835
+ .p234-subnav a:hover { color: var(--p234-color-ink); }
836
+ .p234-subnav a[aria-current] {
837
+ color: var(--p234-color-ink);
838
+ border-bottom-color: var(--p234-color-brand);
839
+ }
840
+
841
+ /* --- page hero ---------------------------------------------------------
842
+ The header for a page that is not an article: a section front, a price
843
+ page, a state page. */
844
+ .p234-pagehero {
845
+ border-bottom: var(--p234-border-thick) solid var(--p234-color-ink);
846
+ padding-bottom: var(--p234-space-5);
847
+ margin-bottom: var(--p234-space-7);
848
+ }
849
+ .p234-pagehero__title {
850
+ font-family: var(--p234-type-display-2-family);
851
+ font-size: var(--p234-type-display-2-size);
852
+ line-height: var(--p234-type-display-2-leading);
853
+ font-weight: var(--p234-type-display-2-weight);
854
+ letter-spacing: var(--p234-type-display-2-tracking);
855
+ color: var(--p234-color-ink);
856
+ margin: 0;
857
+ text-wrap: balance;
858
+ }
859
+ .p234-pagehero__standfirst {
860
+ font-family: var(--p234-type-standfirst-family);
861
+ font-size: var(--p234-type-standfirst-size);
862
+ line-height: var(--p234-type-standfirst-leading);
863
+ color: var(--p234-color-ink-muted);
864
+ max-width: var(--p234-measure-text);
865
+ margin: var(--p234-space-4) 0 0;
866
+ }
867
+ .p234-pagehero__meta {
868
+ font-family: var(--p234-type-meta-family);
869
+ font-size: var(--p234-type-meta-size);
870
+ color: var(--p234-color-ink-muted);
871
+ margin-top: var(--p234-space-4);
872
+ }
873
+
874
+ /* --- footer ------------------------------------------------------------- */
875
+ .p234-footer {
876
+ border-top: var(--p234-border-heavy) solid var(--p234-color-ink);
877
+ margin-top: var(--p234-space-9);
878
+ padding: var(--p234-space-8) 0;
879
+ }
880
+ .p234-footer__in {
881
+ max-width: var(--p234-layout-container);
882
+ margin: 0 auto;
883
+ padding-inline: var(--p234-layout-gutter);
884
+ }
885
+ .p234-footer__groups {
886
+ display: grid;
887
+ gap: var(--p234-space-7);
888
+ grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
889
+ margin-bottom: var(--p234-space-8);
890
+ }
891
+ .p234-footer__title {
892
+ font-family: var(--p234-type-kicker-family);
893
+ font-size: var(--p234-type-kicker-size);
894
+ font-weight: var(--p234-type-kicker-weight);
895
+ letter-spacing: var(--p234-type-kicker-tracking);
896
+ text-transform: var(--p234-type-kicker-case);
897
+ color: var(--p234-color-ink-muted);
898
+ margin: 0 0 var(--p234-space-4);
899
+ }
900
+ .p234-footer ul { list-style: none; margin: 0; padding: 0; }
901
+ .p234-footer li { margin-bottom: var(--p234-space-3); }
902
+ .p234-footer a {
903
+ font-family: var(--p234-type-ui-sm-family);
904
+ font-size: var(--p234-type-ui-sm-size);
905
+ color: var(--p234-color-ink);
906
+ text-decoration: none;
907
+ }
908
+ .p234-footer a:hover { text-decoration: underline; text-underline-offset: 2px; }
909
+ .p234-footer__legal {
910
+ border-top: var(--p234-border-hairline) solid var(--p234-color-rule);
911
+ padding-top: var(--p234-space-5);
912
+ font-family: var(--p234-type-caption-family);
913
+ font-size: var(--p234-type-caption-size);
914
+ line-height: 1.6;
915
+ color: var(--p234-color-ink-muted);
916
+ }
917
+
918
+ /* --- route progress ----------------------------------------------------
919
+ A thin bar while the next page is fetched. It sits above everything
920
+ except a toast, because it is about the navigation itself. */
921
+ .p234-progress {
922
+ position: fixed;
923
+ inset: 0 0 auto 0;
924
+ height: 2px;
925
+ z-index: var(--p234-z-overlay);
926
+ background: var(--p234-color-brand);
927
+ transform-origin: 0 50%;
928
+ transition: transform var(--p234-motion-duration-base) var(--p234-motion-easing-standard);
929
+ }
930
+
931
+ /* --- field -------------------------------------------------------------
932
+ One control for input, select and textarea. 34 files currently style
933
+ their own. The label is a real label, never a placeholder. */
934
+ .p234-field { display: block; margin-bottom: var(--p234-space-5); }
935
+ .p234-field__label {
936
+ display: block;
937
+ font-family: var(--p234-type-ui-sm-family);
938
+ font-size: var(--p234-type-ui-sm-size);
939
+ font-weight: 600;
940
+ color: var(--p234-color-ink);
941
+ margin-bottom: var(--p234-space-3);
942
+ }
943
+ .p234-field__optional { font-weight: 400; color: var(--p234-color-ink-muted); }
944
+ .p234-field__hint {
945
+ font-family: var(--p234-type-meta-family);
946
+ font-size: var(--p234-type-meta-size);
947
+ color: var(--p234-color-ink-muted);
948
+ margin: 0 0 var(--p234-space-3);
949
+ max-width: var(--p234-measure-narrow);
950
+ }
951
+ .p234-field__control {
952
+ display: block;
953
+ width: 100%;
954
+ min-height: var(--p234-field-height);
955
+ padding: 0 var(--p234-field-padding-x);
956
+ background: var(--p234-color-field-bg);
957
+ color: var(--p234-color-ink);
958
+ border: var(--p234-field-border-width) solid var(--p234-color-field-border);
959
+ border-radius: var(--p234-field-radius);
960
+ font-family: var(--p234-type-ui-family);
961
+ font-size: var(--p234-type-ui-size);
962
+ }
963
+ .p234-field__control::placeholder { color: var(--p234-color-field-ink-placeholder); }
964
+ .p234-field__control:hover { border-color: var(--p234-color-field-border-hover); }
965
+ .p234-field__control:disabled {
966
+ background: var(--p234-color-field-bg-disabled);
967
+ color: var(--p234-color-ink-muted);
968
+ border-color: var(--p234-color-rule);
969
+ cursor: not-allowed;
970
+ }
971
+ textarea.p234-field__control {
972
+ padding: var(--p234-space-4) var(--p234-field-padding-x);
973
+ min-height: 7rem;
974
+ line-height: 1.5;
975
+ resize: vertical;
976
+ }
977
+ /* Invalid thickens the border as well as changing its colour, so the state
978
+ does not depend on seeing the hue. */
979
+ .p234-field__control[aria-invalid="true"] {
980
+ border-width: var(--p234-field-border-width-invalid);
981
+ border-color: var(--p234-color-error);
982
+ }
983
+ .p234-field__error {
984
+ font-family: var(--p234-type-meta-family);
985
+ font-size: var(--p234-type-meta-size);
986
+ font-weight: 600;
987
+ color: var(--p234-color-error);
988
+ margin: var(--p234-space-3) 0 0;
989
+ max-width: var(--p234-measure-narrow);
990
+ }
991
+
992
+ /* Checkbox and radio: the control keeps its native shape and the whole row
993
+ is the target. */
994
+ .p234-check {
995
+ display: flex;
996
+ align-items: flex-start;
997
+ gap: var(--p234-space-4);
998
+ padding: var(--p234-space-3) 0;
999
+ font-family: var(--p234-type-ui-family);
1000
+ font-size: var(--p234-type-ui-size);
1001
+ color: var(--p234-color-ink);
1002
+ cursor: pointer;
1003
+ }
1004
+ .p234-check input {
1005
+ flex-shrink: 0;
1006
+ width: 1.25rem;
1007
+ height: 1.25rem;
1008
+ margin: 0;
1009
+ accent-color: var(--p234-color-brand);
1010
+ }
1011
+ .p234-check__text { line-height: 1.45; }
1012
+ .p234-check__hint {
1013
+ display: block;
1014
+ font-family: var(--p234-type-meta-family);
1015
+ font-size: var(--p234-type-meta-size);
1016
+ color: var(--p234-color-ink-muted);
1017
+ margin-top: 2px;
1018
+ }
1019
+
1020
+ /* --- toast -------------------------------------------------------------
1021
+ A short message about something that just happened. Lives in a live
1022
+ region so it is announced, not only seen. */
1023
+ .p234-toast-region {
1024
+ position: fixed;
1025
+ left: 50%;
1026
+ bottom: var(--p234-space-6);
1027
+ transform: translateX(-50%);
1028
+ z-index: var(--p234-z-toast);
1029
+ display: flex;
1030
+ flex-direction: column;
1031
+ gap: var(--p234-space-3);
1032
+ width: calc(100% - (2 * var(--p234-layout-gutter)));
1033
+ max-width: 26rem;
1034
+ pointer-events: none;
1035
+ }
1036
+ .p234-toast {
1037
+ display: flex;
1038
+ align-items: flex-start;
1039
+ gap: var(--p234-space-4);
1040
+ pointer-events: auto;
1041
+ background: var(--p234-color-paper-dark);
1042
+ color: var(--p234-color-ink-inverse);
1043
+ border-radius: var(--p234-radius-md);
1044
+ box-shadow: var(--p234-shadow-overlay);
1045
+ padding: var(--p234-space-4) var(--p234-space-5);
1046
+ font-family: var(--p234-type-ui-family);
1047
+ font-size: var(--p234-type-ui-size);
1048
+ line-height: 1.45;
1049
+ }
1050
+ .p234-toast__mark { flex-shrink: 0; font-weight: 700; }
1051
+ .p234-toast--success .p234-toast__mark { color: var(--p234-color-data-up); }
1052
+ .p234-toast--error .p234-toast__mark { color: var(--p234-color-data-down); }
1053
+ .p234-toast__action {
1054
+ margin-left: auto;
1055
+ flex-shrink: 0;
1056
+ background: none;
1057
+ border: 0;
1058
+ color: inherit;
1059
+ font: inherit;
1060
+ font-weight: 600;
1061
+ text-decoration: underline;
1062
+ text-underline-offset: 2px;
1063
+ cursor: pointer;
1064
+ }
1065
+
1066
+ /* --- empty state -------------------------------------------------------
1067
+ 30 files write their own "no results" copy. An empty state says what
1068
+ happened and what to do next. */
1069
+ .p234-empty {
1070
+ border-top: var(--p234-border-thick) solid var(--p234-color-ink);
1071
+ padding: var(--p234-space-7) 0;
1072
+ max-width: var(--p234-measure-text);
1073
+ }
1074
+ .p234-empty__title {
1075
+ font-family: var(--p234-type-headline-sm-family);
1076
+ font-size: var(--p234-type-headline-sm-size);
1077
+ line-height: var(--p234-type-headline-sm-leading);
1078
+ font-weight: var(--p234-type-headline-sm-weight);
1079
+ color: var(--p234-color-ink);
1080
+ margin: 0 0 var(--p234-space-3);
1081
+ }
1082
+ .p234-empty__body {
1083
+ font-family: var(--p234-type-body-sm-family);
1084
+ font-size: var(--p234-type-body-sm-size);
1085
+ line-height: var(--p234-type-body-sm-leading);
1086
+ color: var(--p234-color-ink-muted);
1087
+ margin: 0 0 var(--p234-space-5);
1088
+ }
1089
+ .p234-empty__actions { display: flex; flex-wrap: wrap; gap: var(--p234-space-3); }
1090
+
1091
+ /* --- skeleton ----------------------------------------------------------
1092
+ A placeholder shaped like the thing that is coming. Never a spinner:
1093
+ a spinner says "wait", a skeleton says "here is what is arriving". */
1094
+ .p234-skeleton {
1095
+ display: block;
1096
+ background: var(--p234-color-paper-sunk);
1097
+ border-radius: var(--p234-radius-sm);
1098
+ position: relative;
1099
+ overflow: hidden;
1100
+ }
1101
+ .p234-skeleton::after {
1102
+ content: "";
1103
+ position: absolute;
1104
+ inset: 0;
1105
+ background: linear-gradient(90deg, transparent, var(--p234-color-rule), transparent);
1106
+ transform: translateX(-100%);
1107
+ animation: p234-shimmer 1.4s infinite;
1108
+ }
1109
+ @keyframes p234-shimmer { to { transform: translateX(100%); } }
1110
+
1111
+ .p234-skeleton--line { height: 0.75rem; margin-bottom: var(--p234-space-3); }
1112
+ .p234-skeleton--headline { height: 1.5rem; margin-bottom: var(--p234-space-4); }
1113
+ .p234-skeleton--figure { aspect-ratio: var(--p234-figure-ratio); height: auto; width: 100%; }
1114
+ .p234-skeleton--short { width: 40%; }
1115
+ .p234-skeleton--medium { width: 70%; }
1116
+
1117
+ /* --- tabs --------------------------------------------------------------
1118
+ Switches a panel in place. If it navigates, it is a sub-nav. */
1119
+ .p234-tabs {
1120
+ display: flex;
1121
+ gap: var(--p234-space-5);
1122
+ border-bottom: var(--p234-border-hairline) solid var(--p234-color-rule);
1123
+ overflow-x: auto;
1124
+ scrollbar-width: none;
1125
+ }
1126
+ .p234-tabs::-webkit-scrollbar { display: none; }
1127
+ .p234-tabs__tab {
1128
+ flex-shrink: 0;
1129
+ background: none;
1130
+ border: 0;
1131
+ border-bottom: var(--p234-border-thick) solid transparent;
1132
+ margin-bottom: -1px;
1133
+ padding: var(--p234-space-4) 0;
1134
+ font-family: var(--p234-type-ui-family);
1135
+ font-size: var(--p234-type-ui-size);
1136
+ font-weight: 600;
1137
+ color: var(--p234-color-ink-muted);
1138
+ white-space: nowrap;
1139
+ cursor: pointer;
1140
+ }
1141
+ .p234-tabs__tab:hover { color: var(--p234-color-ink); }
1142
+ .p234-tabs__tab[aria-selected="true"] {
1143
+ color: var(--p234-color-ink);
1144
+ border-bottom-color: var(--p234-color-brand);
1145
+ }
1146
+ .p234-tabs__count { color: var(--p234-color-ink-subtle); font-weight: 400; }
1147
+ .p234-tabpanel { padding-top: var(--p234-space-6); }
1148
+
1149
+ /* --- pagination --------------------------------------------------------
1150
+ One button and a count. The count is the part people leave out, and it
1151
+ is the part that tells a reader whether to keep going. */
1152
+ .p234-pagination {
1153
+ display: flex;
1154
+ flex-direction: column;
1155
+ align-items: center;
1156
+ gap: var(--p234-space-4);
1157
+ border-top: var(--p234-border-hairline) solid var(--p234-color-rule);
1158
+ padding-top: var(--p234-space-6);
1159
+ margin-top: var(--p234-space-6);
1160
+ }
1161
+ .p234-pagination__status {
1162
+ font-family: var(--p234-type-meta-family);
1163
+ font-size: var(--p234-type-meta-size);
1164
+ color: var(--p234-color-ink-muted);
1165
+ margin: 0;
1166
+ }
1167
+
1168
+ /* --- strip -------------------------------------------------------------
1169
+ A sideways row of cards. 22 files roll their own overflow container. */
1170
+ .p234-strip { position: relative; }
1171
+ .p234-strip__track {
1172
+ display: flex;
1173
+ gap: var(--p234-space-5);
1174
+ overflow-x: auto;
1175
+ scroll-snap-type: x proximity;
1176
+ scrollbar-width: none;
1177
+ padding-bottom: var(--p234-space-3);
1178
+ }
1179
+ .p234-strip__track::-webkit-scrollbar { display: none; }
1180
+ .p234-strip__item {
1181
+ flex: 0 0 15rem;
1182
+ scroll-snap-align: start;
1183
+ border-top: var(--p234-border-thick) solid var(--p234-color-ink);
1184
+ padding-top: var(--p234-space-4);
1185
+ color: inherit;
1186
+ text-decoration: none;
1187
+ }
1188
+ .p234-strip__item:hover .p234-strip__title { text-decoration: underline; text-underline-offset: 2px; }
1189
+ .p234-strip__title {
1190
+ font-family: var(--p234-type-headline-xs-family);
1191
+ font-size: var(--p234-type-headline-xs-size);
1192
+ line-height: var(--p234-type-headline-xs-leading);
1193
+ font-weight: var(--p234-type-headline-xs-weight);
1194
+ color: var(--p234-color-ink);
1195
+ margin: var(--p234-space-3) 0 0;
1196
+ }
1197
+ /* The right edge fades, which is the only hint that the row continues once
1198
+ the scrollbar is hidden. */
1199
+ .p234-strip::after {
1200
+ content: "";
1201
+ position: absolute;
1202
+ inset: 0 0 var(--p234-space-3) auto;
1203
+ width: var(--p234-space-8);
1204
+ pointer-events: none;
1205
+ background: linear-gradient(90deg, transparent, var(--p234-color-paper));
1206
+ }
1207
+
1208
+ /* --- directory ---------------------------------------------------------
1209
+ A long index: every equity, every state, every seat. Grouped, with the
1210
+ group letter sticking as the reader scrolls past it. */
1211
+ .p234-directory { border-top: var(--p234-border-thick) solid var(--p234-color-ink); }
1212
+ .p234-directory__letter {
1213
+ position: sticky;
1214
+ top: 0;
1215
+ background: var(--p234-color-paper);
1216
+ font-family: var(--p234-type-kicker-family);
1217
+ font-size: var(--p234-type-kicker-size);
1218
+ font-weight: var(--p234-type-kicker-weight);
1219
+ letter-spacing: var(--p234-type-kicker-tracking);
1220
+ text-transform: var(--p234-type-kicker-case);
1221
+ color: var(--p234-color-ink-muted);
1222
+ padding: var(--p234-space-4) 0 var(--p234-space-3);
1223
+ margin: 0;
1224
+ border-bottom: var(--p234-border-hairline) solid var(--p234-color-rule);
1225
+ }
1226
+ .p234-directory__row {
1227
+ display: flex;
1228
+ align-items: baseline;
1229
+ gap: var(--p234-space-4);
1230
+ padding: var(--p234-space-4) 0;
1231
+ border-bottom: var(--p234-border-hairline) solid var(--p234-color-rule);
1232
+ color: inherit;
1233
+ text-decoration: none;
1234
+ }
1235
+ .p234-directory__row:hover { background: var(--p234-color-paper-alt); }
1236
+ .p234-directory__name {
1237
+ font-family: var(--p234-type-ui-family);
1238
+ font-size: var(--p234-type-ui-size);
1239
+ font-weight: 600;
1240
+ color: var(--p234-color-ink);
1241
+ }
1242
+ .p234-directory__detail {
1243
+ font-family: var(--p234-type-meta-family);
1244
+ font-size: var(--p234-type-meta-size);
1245
+ color: var(--p234-color-ink-muted);
1246
+ }
1247
+ .p234-directory__value {
1248
+ margin-left: auto;
1249
+ font-family: var(--p234-type-data-sm-family);
1250
+ font-size: var(--p234-type-data-sm-size);
1251
+ font-weight: var(--p234-type-data-sm-weight);
1252
+ font-variant-numeric: var(--p234-type-data-sm-numeric);
1253
+ color: var(--p234-color-ink);
1254
+ }
1255
+
1256
+ /* --- chart -------------------------------------------------------------
1257
+ The frame around a plot: title, legend, the plot itself, and the line
1258
+ saying where the numbers came from and as of when. */
1259
+ .p234-chart { margin: 0 0 var(--p234-space-7); max-width: var(--p234-measure-wide); }
1260
+ .p234-chart__head { margin-bottom: var(--p234-space-5); }
1261
+ .p234-chart__title {
1262
+ font-family: var(--p234-type-headline-sm-family);
1263
+ font-size: var(--p234-type-headline-sm-size);
1264
+ line-height: var(--p234-type-headline-sm-leading);
1265
+ font-weight: var(--p234-type-headline-sm-weight);
1266
+ color: var(--p234-color-ink);
1267
+ margin: 0;
1268
+ }
1269
+ .p234-chart__legend {
1270
+ display: flex;
1271
+ flex-wrap: wrap;
1272
+ gap: var(--p234-space-3) var(--p234-space-5);
1273
+ margin-top: var(--p234-space-4);
1274
+ list-style: none;
1275
+ padding: 0;
1276
+ }
1277
+ .p234-chart__key {
1278
+ display: flex;
1279
+ align-items: center;
1280
+ gap: var(--p234-space-3);
1281
+ font-family: var(--p234-type-ui-sm-family);
1282
+ font-size: var(--p234-type-ui-sm-size);
1283
+ color: var(--p234-color-ink);
1284
+ }
1285
+ .p234-chart__swatch { width: 0.875rem; height: 0.875rem; border-radius: 2px; flex-shrink: 0; }
1286
+ .p234-chart__plot { display: block; width: 100%; height: auto; }
1287
+ .p234-chart__source {
1288
+ font-family: var(--p234-type-caption-family);
1289
+ font-size: var(--p234-type-caption-size);
1290
+ line-height: var(--p234-type-caption-leading);
1291
+ color: var(--p234-color-ink-subtle);
1292
+ margin: var(--p234-space-4) 0 0;
1293
+ }
1294
+
1295
+ /* --- map ---------------------------------------------------------------
1296
+ A choropleth of the 36 states and the FCT, with the ramp as its key. */
1297
+ .p234-map { max-width: var(--p234-measure-wide); }
1298
+ .p234-map__svg { display: block; width: 100%; height: auto; }
1299
+ .p234-map__shape { stroke: var(--p234-color-paper); stroke-width: 1; }
1300
+ .p234-map__shape:hover { stroke: var(--p234-color-ink); stroke-width: 2; }
1301
+ .p234-map__shape--nodata { fill: var(--p234-color-paper-sunk); }
1302
+ .p234-map__key { display: flex; align-items: center; gap: var(--p234-space-3); margin-top: var(--p234-space-5); flex-wrap: wrap; }
1303
+ .p234-map__steps { display: flex; }
1304
+ .p234-map__step { width: 2.25rem; height: 0.75rem; }
1305
+ .p234-map__bound {
1306
+ font-family: var(--p234-type-data-sm-family);
1307
+ font-size: var(--p234-type-data-sm-size);
1308
+ font-variant-numeric: var(--p234-type-data-sm-numeric);
1309
+ color: var(--p234-color-ink-muted);
1310
+ }
1311
+
1312
+ /* --- hemicycle ---------------------------------------------------------- */
1313
+ .p234-hemicycle { max-width: var(--p234-measure-wide); }
1314
+ .p234-hemicycle__svg { display: block; width: 100%; height: auto; }
1315
+ .p234-hemicycle__seat { stroke: var(--p234-color-paper); stroke-width: 0.5; }
1316
+ .p234-hemicycle__seat--vacant { fill: var(--p234-color-paper-sunk); }
1317
+
1318
+ /* --- results -----------------------------------------------------------
1319
+ Declared results and pending seats. Party identity is carried by the
1320
+ abbreviation, not by the colour: party colours are data, they are not
1321
+ tokens, and three of them are close to each other. */
1322
+ .p234-results { border-top: var(--p234-border-thick) solid var(--p234-color-ink); }
1323
+ .p234-results__row {
1324
+ display: grid;
1325
+ grid-template-columns: auto 1fr auto;
1326
+ align-items: center;
1327
+ gap: var(--p234-space-3) var(--p234-space-4);
1328
+ padding: var(--p234-space-4) 0;
1329
+ border-bottom: var(--p234-border-hairline) solid var(--p234-color-rule);
1330
+ }
1331
+ .p234-results__party {
1332
+ font-family: var(--p234-type-ui-sm-family);
1333
+ font-size: var(--p234-type-ui-sm-size);
1334
+ font-weight: 700;
1335
+ color: var(--p234-color-ink);
1336
+ min-width: 3.5rem;
1337
+ }
1338
+ .p234-results__name {
1339
+ font-family: var(--p234-type-ui-family);
1340
+ font-size: var(--p234-type-ui-size);
1341
+ color: var(--p234-color-ink);
1342
+ }
1343
+ .p234-results__votes {
1344
+ font-family: var(--p234-type-data-family);
1345
+ font-size: var(--p234-type-data-size);
1346
+ font-weight: var(--p234-type-data-weight);
1347
+ font-variant-numeric: var(--p234-type-data-numeric);
1348
+ color: var(--p234-color-ink);
1349
+ }
1350
+ .p234-results__bar {
1351
+ grid-column: 1 / -1;
1352
+ height: 0.5rem;
1353
+ background: var(--p234-color-paper-sunk);
1354
+ border-radius: 1px;
1355
+ overflow: hidden;
1356
+ }
1357
+ .p234-results__fill { display: block; height: 100%; background: var(--p234-color-ink-muted); }
1358
+ .p234-results__fill--lead { background: var(--p234-color-brand); }
1359
+ .p234-results__status {
1360
+ display: inline-block;
1361
+ font-family: var(--p234-type-ui-sm-family);
1362
+ font-size: var(--p234-type-ui-sm-size);
1363
+ font-weight: 600;
1364
+ padding: 1px var(--p234-space-3);
1365
+ border-radius: var(--p234-radius-pill);
1366
+ }
1367
+ .p234-results__status--declared { color: var(--p234-color-success); border: 1px solid var(--p234-color-success); }
1368
+ .p234-results__status--pending { color: var(--p234-color-ink-muted); border: 1px solid var(--p234-color-rule-strong); }
1369
+
1370
+ /* --- banner ------------------------------------------------------------
1371
+ One band for the four that exist today: consent, push prompt, daily brief
1372
+ and quick access. A banner interrupts, so it earns its place or it goes. */
1373
+ .p234-banner {
1374
+ display: flex;
1375
+ align-items: flex-start;
1376
+ gap: var(--p234-space-4);
1377
+ padding: var(--p234-space-5);
1378
+ border: var(--p234-border-hairline) solid var(--p234-color-rule);
1379
+ border-left: var(--p234-border-heavy) solid var(--p234-color-brand);
1380
+ border-radius: var(--p234-radius-sm);
1381
+ background: var(--p234-color-paper-alt);
1382
+ margin-bottom: var(--p234-space-6);
1383
+ }
1384
+ .p234-banner__body { flex: 1; min-width: 0; }
1385
+ .p234-banner__title {
1386
+ font-family: var(--p234-type-ui-family);
1387
+ font-size: var(--p234-type-ui-size);
1388
+ font-weight: 700;
1389
+ color: var(--p234-color-ink);
1390
+ margin: 0 0 var(--p234-space-2);
1391
+ }
1392
+ .p234-banner__text {
1393
+ font-family: var(--p234-type-body-sm-family);
1394
+ font-size: var(--p234-type-body-sm-size);
1395
+ line-height: var(--p234-type-body-sm-leading);
1396
+ color: var(--p234-color-ink-muted);
1397
+ margin: 0;
1398
+ }
1399
+ .p234-banner__actions { display: flex; flex-wrap: wrap; gap: var(--p234-space-3); margin-top: var(--p234-space-4); }
1400
+ .p234-banner__dismiss {
1401
+ flex-shrink: 0;
1402
+ background: none;
1403
+ border: 0;
1404
+ color: var(--p234-color-ink-muted);
1405
+ font: inherit;
1406
+ font-size: var(--p234-type-ui-sm-size);
1407
+ cursor: pointer;
1408
+ min-width: var(--p234-field-height);
1409
+ min-height: var(--p234-field-height);
1410
+ }
1411
+ .p234-banner--info { border-left-color: var(--p234-color-info); }
1412
+ .p234-banner--warning { border-left-color: var(--p234-color-warning); }
1413
+ /* Consent is the one banner that may sit over the page. */
1414
+ .p234-banner--sticky {
1415
+ position: fixed;
1416
+ inset: auto var(--p234-layout-gutter) var(--p234-layout-gutter);
1417
+ z-index: var(--p234-z-overlay);
1418
+ max-width: 34rem;
1419
+ margin: 0 auto;
1420
+ background: var(--p234-color-paper);
1421
+ box-shadow: var(--p234-shadow-overlay);
1422
+ }
1423
+
1424
+ /* --- ticker ------------------------------------------------------------
1425
+ A row of figures across the top of a markets page. Scrolls by hand; it
1426
+ never animates on its own, because a moving number cannot be read. */
1427
+ .p234-ticker {
1428
+ display: flex;
1429
+ gap: var(--p234-space-6);
1430
+ overflow-x: auto;
1431
+ scrollbar-width: none;
1432
+ border-block: var(--p234-border-hairline) solid var(--p234-color-rule);
1433
+ padding: var(--p234-space-4) 0;
1434
+ }
1435
+ .p234-ticker::-webkit-scrollbar { display: none; }
1436
+ .p234-ticker__item {
1437
+ display: flex;
1438
+ align-items: baseline;
1439
+ gap: var(--p234-space-3);
1440
+ flex-shrink: 0;
1441
+ text-decoration: none;
1442
+ color: inherit;
1443
+ }
1444
+ .p234-ticker__name {
1445
+ font-family: var(--p234-type-ui-sm-family);
1446
+ font-size: var(--p234-type-ui-sm-size);
1447
+ font-weight: 600;
1448
+ color: var(--p234-color-ink-muted);
1449
+ }
1450
+ .p234-ticker__value {
1451
+ font-family: var(--p234-type-data-sm-family);
1452
+ font-size: var(--p234-type-data-sm-size);
1453
+ font-weight: 600;
1454
+ font-variant-numeric: var(--p234-type-data-sm-numeric);
1455
+ color: var(--p234-color-ink);
1456
+ }
1457
+
1458
+ /* --- link grid ---------------------------------------------------------
1459
+ Destinations: categories, states, tools. Text, not icons in boxes. */
1460
+ .p234-linkgrid {
1461
+ display: grid;
1462
+ gap: var(--p234-space-4);
1463
+ grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
1464
+ list-style: none;
1465
+ margin: 0;
1466
+ padding: 0;
1467
+ }
1468
+ .p234-linkgrid a {
1469
+ display: block;
1470
+ border-top: var(--p234-border-thick) solid var(--p234-color-ink);
1471
+ padding-top: var(--p234-space-3);
1472
+ font-family: var(--p234-type-ui-family);
1473
+ font-size: var(--p234-type-ui-size);
1474
+ font-weight: 600;
1475
+ color: var(--p234-color-ink);
1476
+ text-decoration: none;
1477
+ }
1478
+ .p234-linkgrid a:hover { color: var(--p234-color-brand); }
1479
+ .p234-linkgrid__count {
1480
+ display: block;
1481
+ font-family: var(--p234-type-meta-family);
1482
+ font-size: var(--p234-type-meta-size);
1483
+ font-weight: 400;
1484
+ color: var(--p234-color-ink-muted);
1485
+ margin-top: 2px;
1486
+ }
1487
+
1488
+ /* --- poster ------------------------------------------------------------
1489
+ Portrait media: films, shows, book and product covers. */
1490
+ .p234-poster { display: block; text-decoration: none; color: inherit; }
1491
+ .p234-poster__img {
1492
+ display: block;
1493
+ width: 100%;
1494
+ aspect-ratio: 2 / 3;
1495
+ object-fit: cover;
1496
+ background: var(--p234-color-paper-sunk);
1497
+ border-radius: var(--p234-radius-sm);
1498
+ }
1499
+ .p234-poster__title {
1500
+ font-family: var(--p234-type-ui-sm-family);
1501
+ font-size: var(--p234-type-ui-sm-size);
1502
+ font-weight: 600;
1503
+ color: var(--p234-color-ink);
1504
+ margin: var(--p234-space-3) 0 0;
1505
+ }
1506
+ .p234-poster__meta {
1507
+ font-family: var(--p234-type-meta-family);
1508
+ font-size: var(--p234-type-meta-size);
1509
+ color: var(--p234-color-ink-muted);
1510
+ }
1511
+
1512
+ @media (prefers-reduced-motion: reduce) {
1513
+ .p234-btn, .p234-toggle { transition: none; }
1514
+ /* The bar still appears and still fills, it just stops sliding. */
1515
+ .p234-progress { transition: none; }
1516
+ /* The placeholder stays, the shimmer goes. */
1517
+ .p234-skeleton::after { animation: none; }
1518
+ .p234-strip__track { scroll-behavior: auto; }
1519
+ }