@plus234feed/ds 0.3.0 → 0.5.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 (3) hide show
  1. package/README.md +69 -138
  2. package/package.json +3 -2
  3. package/src/components.css +258 -45
package/README.md CHANGED
@@ -1,90 +1,44 @@
1
- # Plus234Feed Design System
1
+ # @plus234feed/ds
2
2
 
3
- Editorial tokens, a named type scale and the components a news intelligence
4
- platform needs. Plain CSS on top of design tokens, no framework, no build step
5
- for consumers.
3
+ The Plus234Feed design system. Editorial tokens, a named type scale and the
4
+ components a news site needs: newspaper hierarchy, accessible colour in both
5
+ themes, plain CSS.
6
6
 
7
- Documentation site: `docs/index.html`, generated from `site/pages/`. Deploy the
8
- `docs/` folder; `vercel.json` already points at it.
7
+ No framework, no JavaScript, no build step for consumers. Four stylesheets and
8
+ four font files.
9
9
 
10
- 27 pages: get started, foundations, one page per component, patterns and the
11
- rules. Each component page says what it is, when to use it, when not to, live
12
- examples with copyable HTML, and the tokens it reads.
13
-
14
- ## Why this exists
15
-
16
- The frontend had 252 component files and 40,638 lines of TSX, but only 25
17
- shared components. Measured on 2026-11-04:
18
-
19
- | Symptom | Count |
20
- | --- | --- |
21
- | `page.tsx` files not using `PageLayout` | 92 (16 do) |
22
- | Files re-implementing an article card | 47 |
23
- | `rounded-*` variants in use | 11 |
24
- | Ad-hoc text sizes in use | 9, dominated by `text-sm` (1,019) and `text-xs` (720) |
25
- | Hardcoded uses of a single green | 27 |
26
- | Variables used but defined nowhere | 5 |
27
-
28
- That last row was a live bug, not a matter of taste. `--text-muted` (42 uses),
29
- `--border` (36), `--bg-elevated` (13), `--bg-secondary` and `--bg-hover` were
30
- referenced with no fallback and declared in no stylesheet, concentrated in
31
- `components/prices/` and the cost of living pages.
32
-
33
- Reader behaviour pointed the same way. Almost every visitor is new, and
34
- almost nobody opens a second page. Readers arriving from search spend real
35
- time on the article, finish it, and leave: the problem is not that they are
36
- bored, it is that the page offers them nothing to do next.
10
+ Documentation: [ds.plus234feed.com](https://ds.plus234feed.com)
37
11
 
38
12
  ## Install
39
13
 
40
14
  ```bash
41
- npm install
42
- npm run build # tokens + gallery
43
- npm test # 13 checks
15
+ npm install @plus234feed/ds
44
16
  ```
45
17
 
18
+ ```css
19
+ @import "@plus234feed/ds/tokens.css"; /* custom properties, both themes */
20
+ @import "@plus234feed/ds/type.css"; /* .p234-headline and friends */
21
+ @import "@plus234feed/ds/components.css"; /* .p234-story and friends */
22
+ @import "@plus234feed/ds/fonts.css"; /* Charter, 64KB, four faces */
46
23
  ```
47
- npm run build:tokens tokens/*.json -> build/*.css, build/tokens.json
48
- npm run build:docs site/pages/** -> docs/**
49
- npm run check build + test, what CI runs
50
- ```
51
-
52
- ### Authoring a docs page
53
24
 
54
- A page is an HTML fragment with a JSON comment at the top, wrapped by
55
- `site/layout.html`. Four authoring elements expand at build time:
25
+ `fonts.css` resolves `./fonts/*.woff2` relative to itself. If your bundler does
26
+ not follow that, copy the faces to a public path and rewrite the four `url()`
27
+ values:
56
28
 
57
- ```html
58
- <!-- { "title": "Story", "section": "components", "order": 10, "description": "..." } -->
59
-
60
- <docs-example label="...">markup</docs-example> preview plus copyable HTML
61
- <docs-tokens names="ink,ink-muted"> generated swatches, both themes
62
- <docs-type roles="display-1,headline"> generated specimens
63
- <docs-scale group="space"> generated scale table
64
- <docs-uses>--p234-color-ink, ...</docs-uses> the tokens a component reads
29
+ ```bash
30
+ cp node_modules/@plus234feed/ds/src/fonts/*.woff2 public/fonts/
31
+ sed 's|url("./fonts/|url("/fonts/|g' \
32
+ node_modules/@plus234feed/ds/src/fonts.css > app/fonts.css
65
33
  ```
66
34
 
67
- Navigation and the section sidebar are derived from the front matter, so a new
68
- page appears in both by existing.
69
-
70
35
  ## Use it
71
36
 
72
- ```css
73
- @import "@plus234feed/ds/fonts.css"; /* Charter, self-hosted, 64KB */
74
- @import "@plus234feed/ds/tokens.css"; /* theme driven by a .dark class */
75
- @import "@plus234feed/ds/type.css"; /* .p234-headline and friends */
76
- @import "@plus234feed/ds/components.css";
77
- ```
78
-
79
- Use `tokens-auto.css` instead of `tokens.css` only on standalone pages that
80
- have no theme toggle. It adds an OS-preference block. The app must not use it:
81
- a reader who picked light has to get light on a dark laptop.
82
-
83
37
  ```html
84
- <a class="p234-story p234-story--lead" href="...">
38
+ <a class="p234-story p234-story--lead" href="/article/...">
85
39
  <span class="p234-kicker" data-section="markets">Market intelligence</span>
86
- <h3 class="p234-story__headline">Pump prices are running 25 per cent above the last official figure</h3>
87
- <p class="p234-story__standfirst">NBS puts Lagos petrol at 1,077 naira in May.</p>
40
+ <h3 class="p234-story__headline">Pump prices are running above the last official figure</h3>
41
+ <p class="p234-story__standfirst">The gap between the two is the story.</p>
88
42
  <div class="p234-meta">
89
43
  <span class="p234-meta__source">Plus234Feed</span>
90
44
  <span class="p234-meta__dot">&middot;</span>
@@ -94,76 +48,53 @@ a reader who picked light has to get light on a dark laptop.
94
48
  </a>
95
49
  ```
96
50
 
97
- ## How it is built
51
+ ## Themes
98
52
 
53
+ `tokens.css` is class-driven: add `dark` or `data-theme="dark"` to the root
54
+ element. It never reads the operating system preference, because a reader who
55
+ chose light should get light on a dark laptop.
56
+
57
+ Use `tokens-auto.css` instead only on a standalone page with no theme control.
58
+
59
+ ## What is in it
60
+
61
+ | | |
62
+ | --- | --- |
63
+ | Tokens | colour, type, space, shape, charts, forms, layering, motion |
64
+ | Type | 17 named roles, serif for reading, tabular sans for figures |
65
+ | Components | story, masthead, footer, prose, chart, map, table, field, and more |
66
+
67
+ Colour is verified rather than eyeballed. Every pair that has to be readable is
68
+ checked in both themes at build time and the build fails on any that is not.
69
+ The chart palette is checked for colour-blind separation using OKLab and the
70
+ Machado simulation, which is also why it stops at the number of series it can
71
+ actually keep distinct instead of inventing more.
72
+
73
+ ## Typeface
74
+
75
+ Charter, self-hosted. Bitstream contributed it to the X Consortium in 1992 with
76
+ permission to use, copy, modify, sublicense, sell and redistribute, provided
77
+ the notice stays intact. That notice ships at `src/fonts/LICENSE.charter.txt`.
78
+ BITSTREAM CHARTER is a registered trademark of Bitstream Inc.
79
+
80
+ ## Developing
81
+
82
+ ```bash
83
+ npm install
84
+ npm run build # tokens and documentation
85
+ npm test # 33 checks
86
+ npm run check # both, which is what CI runs
99
87
  ```
100
- tokens/core.tokens.json light theme, type, space, radius, layout
101
- tokens/themes/dark.tokens.json colour overrides only, enforced
102
- scripts/color.mjs WCAG luminance and contrast maths
103
- scripts/tokens.mjs loading, flattening, alias resolution, the contrast table
104
- scripts/build-tokens.mjs writes build/, fails on any unreadable pair
105
- scripts/build-site.mjs writes the gallery from the built tokens
106
- src/components.css editorial components, every rule reads a token
88
+
89
+ Releases publish from CI through an npm trusted publisher, so there is no token
90
+ in the repository:
91
+
92
+ ```bash
93
+ npm version patch # rebuilds the docs, commits, tags
94
+ git push --follow-tags
107
95
  ```
108
96
 
109
- Token format follows the W3C Design Tokens Community Group draft. Values become
110
- CSS custom properties prefixed `--p234-`.
111
-
112
- ### The contrast gate
113
-
114
- 44 colour pairs are checked in every theme, 88 checks in all, and the build
115
- exits non-zero on any failure. This is the point of the pipeline: a combination
116
- nobody can read cannot reach the site. The first run caught three real
117
- problems, two of which were dark-theme only.
118
-
119
- The focus ring is checked differently. It is two colours, `focus` with
120
- `focus-outer` drawn around it, and neither half clears 3:1 alone. The amber is
121
- 1.64:1 on white and relies on the dark outer ring there; on a dark page the
122
- outer ring recedes and the amber carries it. So the rule is that at least one
123
- half must clear 3:1 on every surface. See `checkFocus`.
124
-
125
- ## Rules the tokens cannot enforce
126
-
127
- 1. **Two ad slots on an article, maximum, and no mobile sticky.** The current
128
- template carries six plus a sticky around a 147-word summary. Mobile is 64
129
- per cent of search traffic.
130
- 2. **Every article ends with a next step that matches why the reader came.** A
131
- visa reader gets visa stories, not the general feed. This is the module the
132
- near-total absence of second pages is asking for.
133
- 3. **No raw hex.** If a colour is missing, add a token.
134
- 4. **No undefined tokens.** A prebuild check fails on any `--p234-` variable
135
- this package does not declare.
136
- 5. **Pick a type role, never a font size.** Seventeen roles cover the site.
137
- Reaching past them means a role is missing, which is a conversation.
138
- 6. **Direction is never colour alone.** Gainers and decliners carry an arrow.
139
- 7. **One lead per page.** If two stories both look like the lead, neither is.
140
-
141
- ## Type
142
-
143
- Roles, not t-shirt sizes. Serif for what a reader reads, sans for what a reader
144
- operates, tabular sans for every figure.
145
-
146
- The serif is **Charter**, self-hosted from `src/fonts/`, 64KB for four faces,
147
- `font-display: swap`. Charter is the face macOS and iOS already carry, which is
148
- why the gallery looked right before a single font was loaded. Shipping it is
149
- what gives Android readers, who are most of the audience, the same page
150
- instead of a Noto Serif fallback.
151
-
152
- Bitstream contributed Charter to the X Consortium in 1992 with permission to
153
- use, copy, modify, sublicense, sell and redistribute, provided the notice stays
154
- intact. That notice is `src/fonts/LICENSE.charter.txt` and ships with the
155
- files. BITSTREAM CHARTER is a registered trademark of Bitstream Inc.
156
-
157
- The sans is still the system stack headed by Inter, which the Next app already
158
- loads. On a machine without Inter it resolves to San Francisco or Roboto. If
159
- that drift matters it should be self-hosted the same way.
160
-
161
- Running text never drops below 16px. `caption` and `kicker` are the only roles
162
- allowed at 12px, and the tests enforce both floors.
163
-
164
- ## Scope
165
-
166
- Phase 1 is this package plus the site chrome and the article template:
167
- almost every session starts on an article, and the masthead and footer appear
168
- on every page, so those are what make the site stop looking like two sites.
169
- Fronts, data pages, account and forms follow.
97
+ ## Licence
98
+
99
+ UNLICENSED. Published so Plus234Feed applications can install it; not offered
100
+ for reuse.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plus234feed/ds",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Plus234Feed design system. Editorial tokens, type scale and components for a news intelligence platform: newspaper hierarchy, accessible colour in both themes, one component per job.",
5
5
  "keywords": [
6
6
  "design-system",
@@ -44,7 +44,8 @@
44
44
  "build:docs": "node scripts/build-docs.mjs",
45
45
  "build": "npm run build:tokens && npm run build:docs",
46
46
  "test": "node --test test/*.test.js",
47
- "check": "npm run build && npm test"
47
+ "check": "npm run build && npm test",
48
+ "version": "npm run build && git add build docs"
48
49
  },
49
50
  "publishConfig": {
50
51
  "access": "public"
@@ -328,6 +328,91 @@
328
328
  color: var(--p234-color-ink);
329
329
  }
330
330
 
331
+ /* Headings, lists and small print, styled on the element itself.
332
+ *
333
+ * An article body arrives as markup from the pipeline and a policy page is
334
+ * written by hand, but both are the same thing: a document. Styling the
335
+ * element rather than a class is what lets a page body be plain semantic
336
+ * HTML, which is also the only version of these pages anyone will maintain.
337
+ *
338
+ * Section headings carry a hairline above instead of sitting in a bordered
339
+ * box. A page of eight boxes reads as eight unrelated panels; a page of eight
340
+ * ruled sections reads as one document, which is what it is. */
341
+ .p234-prose h2 {
342
+ font-family: var(--p234-type-headline-sm-family);
343
+ font-size: var(--p234-type-headline-sm-size);
344
+ line-height: var(--p234-type-headline-sm-leading);
345
+ letter-spacing: var(--p234-type-headline-sm-tracking);
346
+ font-weight: var(--p234-type-headline-sm-weight);
347
+ color: var(--p234-color-ink);
348
+ border-top: var(--p234-border-hairline) solid var(--p234-color-rule);
349
+ padding-top: var(--p234-space-4);
350
+ }
351
+ .p234-prose > * + h2 { margin-top: var(--p234-space-7); }
352
+
353
+ .p234-prose h3 {
354
+ font-family: var(--p234-type-headline-xs-family);
355
+ font-size: var(--p234-type-headline-xs-size);
356
+ line-height: var(--p234-type-headline-xs-leading);
357
+ letter-spacing: var(--p234-type-headline-xs-tracking);
358
+ font-weight: var(--p234-type-headline-xs-weight);
359
+ color: var(--p234-color-ink);
360
+ }
361
+ .p234-prose > * + h3 { margin-top: var(--p234-space-6); }
362
+
363
+ /* A heading followed straight by its own text should not carry a full gap:
364
+ the two belong together. */
365
+ .p234-prose h2 + *,
366
+ .p234-prose h3 + * { margin-top: var(--p234-space-3); }
367
+
368
+ .p234-prose ul,
369
+ .p234-prose ol { padding-left: var(--p234-space-6); }
370
+ .p234-prose ul { list-style: disc; }
371
+ .p234-prose ol { list-style: decimal; }
372
+ .p234-prose li + li { margin-top: var(--p234-space-3); }
373
+ .p234-prose li::marker { color: var(--p234-color-ink-subtle); }
374
+
375
+ .p234-prose strong { font-weight: 700; }
376
+ .p234-prose em { font-style: italic; }
377
+ .p234-prose hr {
378
+ border: 0;
379
+ border-top: var(--p234-border-hairline) solid var(--p234-color-rule);
380
+ }
381
+ .p234-prose code {
382
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
383
+ font-size: 0.9em;
384
+ background: var(--p234-color-paper-sunk);
385
+ padding: 0.1em 0.3em;
386
+ border-radius: var(--p234-radius-sm);
387
+ }
388
+ .p234-prose pre {
389
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
390
+ font-size: var(--p234-type-ui-sm-size);
391
+ line-height: 1.55;
392
+ background: var(--p234-color-paper-sunk);
393
+ border: var(--p234-border-hairline) solid var(--p234-color-rule);
394
+ border-radius: var(--p234-radius-sm);
395
+ padding: var(--p234-space-4);
396
+ overflow-x: auto;
397
+ }
398
+ .p234-prose pre code { background: none; padding: 0; }
399
+
400
+ /* The line under a document: last updated, a version, a notice. */
401
+ .p234-prose small {
402
+ display: block;
403
+ font-family: var(--p234-type-meta-family);
404
+ font-size: var(--p234-type-meta-size);
405
+ line-height: var(--p234-type-meta-leading);
406
+ color: var(--p234-color-ink-muted);
407
+ }
408
+
409
+ /* A document page is a column of sections, not a reading column of 66
410
+ characters: its lists and definition rows want the wider measure. */
411
+ .p234-prose--document { max-width: var(--p234-measure-wide); }
412
+ .p234-prose--document > p,
413
+ .p234-prose--document > ul,
414
+ .p234-prose--document > ol { max-width: var(--p234-measure-text); }
415
+
331
416
  /* --- ad slot -----------------------------------------------------------
332
417
  Declared, labelled and bounded. The article template allows two of these,
333
418
  total. The count is a design decision, not an inventory decision: at six
@@ -590,76 +675,88 @@
590
675
  }
591
676
 
592
677
  /* --- masthead ----------------------------------------------------------
593
- The 727 line Header replaced by a shell. The dropdown and overlay
594
- behaviour belongs to the app; what lives here is the shape, the rules
595
- and the layering. */
678
+ A newspaper masthead, not an app bar. Three rows:
679
+
680
+ utility search, edition links, account
681
+ brand the wordmark, centred, with date and figures flanking it
682
+ sections the section nav, with a full width panel beneath it
683
+
684
+ The centred wordmark is the whole point: it is what makes the top of the
685
+ page read as a paper rather than a product. */
596
686
  .p234-masthead {
597
687
  position: sticky;
598
688
  top: 0;
599
689
  z-index: var(--p234-z-sticky);
600
690
  background: var(--p234-color-paper);
601
- border-bottom: var(--p234-border-heavy) solid var(--p234-color-ink);
691
+ border-bottom: var(--p234-border-hairline) solid var(--p234-color-rule);
602
692
  }
603
- .p234-masthead__bar {
693
+
694
+ /* Row 1: utility. Hidden on phones, where the bar collapses to one row. */
695
+ .p234-masthead__utility {
696
+ display: none;
697
+ border-bottom: var(--p234-border-hairline) solid var(--p234-color-rule);
698
+ }
699
+ @media (min-width: 62rem) { .p234-masthead__utility { display: block; } }
700
+ .p234-masthead__utility-in {
604
701
  max-width: var(--p234-layout-container);
605
702
  margin: 0 auto;
606
- padding: var(--p234-space-4) var(--p234-layout-gutter);
607
- display: flex;
703
+ padding: var(--p234-space-3) var(--p234-layout-gutter-lg);
704
+ display: grid;
705
+ grid-template-columns: 1fr auto 1fr;
608
706
  align-items: center;
609
707
  gap: var(--p234-space-5);
610
708
  }
611
- @media (min-width: 48rem) {
612
- .p234-masthead__bar { padding-inline: var(--p234-layout-gutter-lg); }
613
- }
614
- .p234-masthead__brand {
709
+ .p234-masthead__editions {
615
710
  display: flex;
616
- align-items: baseline;
617
- gap: var(--p234-space-3);
711
+ justify-content: center;
712
+ gap: var(--p234-space-5);
713
+ }
714
+ .p234-masthead__editions a {
715
+ font-family: var(--p234-type-ui-sm-family);
716
+ font-size: var(--p234-type-ui-sm-size);
717
+ font-weight: 600;
718
+ letter-spacing: 0.04em;
719
+ text-transform: uppercase;
720
+ color: var(--p234-color-ink-muted);
618
721
  text-decoration: none;
619
- color: inherit;
620
- flex-shrink: 0;
722
+ white-space: nowrap;
723
+ }
724
+ .p234-masthead__editions a:hover,
725
+ .p234-masthead__editions a[aria-current] { color: var(--p234-color-ink); }
726
+
727
+ /* Row 2: the wordmark, centred. */
728
+ .p234-masthead__bar {
729
+ max-width: var(--p234-layout-container);
730
+ margin: 0 auto;
731
+ padding: var(--p234-space-4) var(--p234-layout-gutter);
732
+ display: grid;
733
+ grid-template-columns: 1fr auto 1fr;
734
+ align-items: center;
735
+ gap: var(--p234-space-4);
736
+ }
737
+ @media (min-width: 62rem) {
738
+ .p234-masthead__bar { padding: var(--p234-space-5) var(--p234-layout-gutter-lg); }
621
739
  }
622
- /* The wordmark. Two files, not one recoloured image: the E is dark on light
623
- pages and white on dark, so neither a filter nor an opacity trick gets it
624
- right. The pair swaps with the theme. */
740
+ .p234-masthead__brand { justify-self: center; display: block; text-decoration: none; }
625
741
  .p234-masthead__logo { display: block; height: 1.75rem; width: auto; }
742
+ @media (min-width: 62rem) { .p234-masthead__logo { height: 2.5rem; } }
626
743
  .p234-masthead__logo--dark { display: none; }
627
744
  .dark .p234-masthead__logo--light,
628
745
  [data-theme="dark"] .p234-masthead__logo--light { display: none; }
629
746
  .dark .p234-masthead__logo--dark,
630
747
  [data-theme="dark"] .p234-masthead__logo--dark { display: block; }
631
748
 
632
- /* Text fallback, for anywhere the image cannot be used. */
633
- .p234-masthead__mark {
634
- font-family: var(--p234-type-ui-family);
635
- font-weight: 900;
636
- font-size: 1.25rem;
637
- letter-spacing: -0.03em;
638
- color: var(--p234-color-brand);
639
- }
640
- .p234-masthead__tagline {
641
- font-family: var(--p234-type-caption-family);
642
- font-size: var(--p234-type-caption-size);
643
- color: var(--p234-color-ink-muted);
749
+ .p234-masthead__left { display: flex; align-items: center; gap: var(--p234-space-3); }
750
+ .p234-masthead__right { display: flex; align-items: center; justify-content: flex-end; gap: var(--p234-space-3); }
751
+ .p234-masthead__dateline {
644
752
  display: none;
645
- }
646
- @media (min-width: 62rem) { .p234-masthead__tagline { display: inline; } }
647
-
648
- .p234-masthead__nav { display: none; gap: var(--p234-space-5); margin-left: auto; }
649
- @media (min-width: 62rem) { .p234-masthead__nav { display: flex; } }
650
- .p234-masthead__nav a {
651
- font-family: var(--p234-type-ui-family);
652
- font-size: var(--p234-type-ui-size);
653
- font-weight: 600;
753
+ font-family: var(--p234-type-meta-family);
754
+ font-size: var(--p234-type-meta-size);
654
755
  color: var(--p234-color-ink-muted);
655
- text-decoration: none;
656
- white-space: nowrap;
756
+ line-height: 1.3;
657
757
  }
658
- .p234-masthead__nav a:hover { color: var(--p234-color-ink); }
659
- .p234-masthead__nav a[aria-current] { color: var(--p234-color-ink); }
758
+ @media (min-width: 62rem) { .p234-masthead__dateline { display: block; } }
660
759
 
661
- .p234-masthead__actions { display: flex; align-items: center; gap: var(--p234-space-3); margin-left: auto; }
662
- @media (min-width: 62rem) { .p234-masthead__actions { margin-left: 0; } }
663
760
  .p234-masthead__icon {
664
761
  display: inline-flex;
665
762
  align-items: center;
@@ -674,6 +771,122 @@
674
771
  }
675
772
  .p234-masthead__icon:hover { background: var(--p234-color-paper-sunk); }
676
773
 
774
+ /* Phone only: the hamburger and the compact sign in. On desktop the utility
775
+ row carries these, so showing them twice is just clutter. */
776
+ @media (min-width: 62rem) {
777
+ .p234-masthead__menubtn,
778
+ .p234-masthead__compact { display: none !important; }
779
+ }
780
+
781
+ /* Row 3: the sections. Centred on desktop, scrolls sideways on a phone. */
782
+ .p234-masthead__sections {
783
+ border-top: var(--p234-border-hairline) solid var(--p234-color-rule);
784
+ overflow-x: auto;
785
+ scrollbar-width: none;
786
+ }
787
+ .p234-masthead__sections::-webkit-scrollbar { display: none; }
788
+ .p234-masthead__sections-in {
789
+ max-width: var(--p234-layout-container);
790
+ margin: 0 auto;
791
+ padding: 0 var(--p234-layout-gutter);
792
+ display: flex;
793
+ gap: var(--p234-space-6);
794
+ }
795
+ @media (min-width: 62rem) {
796
+ .p234-masthead__sections-in { justify-content: center; padding-inline: var(--p234-layout-gutter-lg); }
797
+ }
798
+ .p234-masthead__section {
799
+ display: inline-flex;
800
+ align-items: center;
801
+ gap: var(--p234-space-2);
802
+ flex-shrink: 0;
803
+ background: none;
804
+ border: 0;
805
+ cursor: pointer;
806
+ padding: var(--p234-space-4) 0;
807
+ font-family: var(--p234-type-ui-family);
808
+ font-size: var(--p234-type-ui-size);
809
+ font-weight: 500;
810
+ color: var(--p234-color-ink);
811
+ text-decoration: none;
812
+ white-space: nowrap;
813
+ border-bottom: var(--p234-border-thick) solid transparent;
814
+ margin-bottom: -1px;
815
+ }
816
+ .p234-masthead__section:hover { border-bottom-color: var(--p234-color-rule-strong); }
817
+ .p234-masthead__section[aria-expanded="true"],
818
+ .p234-masthead__section[aria-current] { border-bottom-color: var(--p234-color-ink); }
819
+
820
+ /* The panel. Full width of the viewport, not a box anchored to a button:
821
+ a dropdown pinned under the last nav item runs off the right edge. */
822
+ .p234-megamenu {
823
+ position: absolute;
824
+ left: 0;
825
+ right: 0;
826
+ top: 100%;
827
+ z-index: var(--p234-z-dropdown);
828
+ background: var(--p234-color-paper);
829
+ border-top: var(--p234-border-hairline) solid var(--p234-color-rule);
830
+ border-bottom: var(--p234-border-hairline) solid var(--p234-color-rule);
831
+ box-shadow: var(--p234-shadow-overlay);
832
+ }
833
+ .p234-megamenu__in {
834
+ max-width: var(--p234-layout-container);
835
+ margin: 0 auto;
836
+ padding: var(--p234-space-7) var(--p234-layout-gutter-lg);
837
+ display: grid;
838
+ gap: var(--p234-space-7);
839
+ grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
840
+ }
841
+ .p234-megamenu ul { list-style: none; margin: 0; padding: 0; }
842
+ .p234-megamenu li { margin-bottom: var(--p234-space-4); }
843
+ .p234-megamenu a {
844
+ font-family: var(--p234-type-ui-family);
845
+ font-size: var(--p234-type-ui-size);
846
+ color: var(--p234-color-ink);
847
+ text-decoration: none;
848
+ }
849
+ .p234-megamenu a:hover { text-decoration: underline; text-underline-offset: 2px; }
850
+
851
+ /* The phone drawer: a full screen panel, search first, then the sections as
852
+ accordions. <details> so it works with no JavaScript. */
853
+ .p234-drawer {
854
+ position: fixed;
855
+ inset: 0;
856
+ z-index: var(--p234-z-overlay);
857
+ background: var(--p234-color-paper);
858
+ overflow-y: auto;
859
+ padding: var(--p234-space-5) var(--p234-layout-gutter) var(--p234-space-9);
860
+ }
861
+ .p234-drawer__head { display: flex; justify-content: flex-start; margin-bottom: var(--p234-space-6); }
862
+ .p234-drawer__search { display: flex; gap: var(--p234-space-3); margin-bottom: var(--p234-space-6); }
863
+ .p234-drawer__group { border-top: var(--p234-border-hairline) solid var(--p234-color-rule); }
864
+ .p234-drawer__group > summary {
865
+ list-style: none;
866
+ display: flex;
867
+ align-items: center;
868
+ justify-content: space-between;
869
+ padding: var(--p234-space-5) 0;
870
+ cursor: pointer;
871
+ font-family: var(--p234-type-headline-xs-family);
872
+ font-size: var(--p234-type-headline-xs-size);
873
+ font-weight: var(--p234-type-headline-xs-weight);
874
+ color: var(--p234-color-ink);
875
+ }
876
+ .p234-drawer__group > summary::-webkit-details-marker { display: none; }
877
+ .p234-drawer__links {
878
+ display: grid;
879
+ grid-template-columns: 1fr 1fr;
880
+ gap: var(--p234-space-4);
881
+ padding-bottom: var(--p234-space-5);
882
+ }
883
+ .p234-drawer__links a {
884
+ font-family: var(--p234-type-ui-family);
885
+ font-size: var(--p234-type-ui-size);
886
+ color: var(--p234-color-ink);
887
+ text-decoration: none;
888
+ }
889
+
677
890
  /* --- subnav ------------------------------------------------------------
678
891
  Section level navigation: the price pages, the account panes. Scrolls
679
892
  sideways on a phone rather than wrapping into two rows. */