@brignano/design 0.3.0 → 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.
- package/DECISIONS.md +6 -1
- package/DESIGN.md +68 -1
- package/README.md +1 -0
- package/package.json +5 -3
- package/product-page.css +172 -0
- package/tailwind.css +1 -0
- package/tokens.css +5 -0
package/DECISIONS.md
CHANGED
|
@@ -299,7 +299,12 @@ other theme.
|
|
|
299
299
|
needs a colour resolves it through `getComputedStyle` on an element
|
|
300
300
|
(DESIGN.md §11). ADR-0002's advice that `chart-colors.ts` read the custom
|
|
301
301
|
properties still stands, but by that route.
|
|
302
|
-
- Consumers shouldn't wrap tokens in `color-mix()`; washes are tokens.
|
|
302
|
+
- Consumers shouldn't wrap tokens in `color-mix()`; washes are tokens. The
|
|
303
|
+
first consumer that needed one (`life`'s sticky bars) got `--surface-sticky`
|
|
304
|
+
in 0.3.1 rather than a local mix.
|
|
305
|
+
- A build that lowers `light-dark()` for older browsers (Lightning CSS, inside
|
|
306
|
+
Next.js and Vite) fixes the theme at `:root`, losing scoped theming. Found in
|
|
307
|
+
`brignano.io`; consumers set `browserslist` to the floor (DESIGN.md §11).
|
|
303
308
|
- Not a token rename and not a visual change, so a patch release.
|
|
304
309
|
|
|
305
310
|
|
package/DESIGN.md
CHANGED
|
@@ -135,6 +135,10 @@ for cards, `--radius-lg` for hero surfaces.
|
|
|
135
135
|
a shadow (`--shadow-1/2/3`). Dark mode does not use borders — surfaces get
|
|
136
136
|
*lighter* with elevation. This is why `--line` can stay nearly invisible in dark.
|
|
137
137
|
|
|
138
|
+
**Sticky chrome** — a header or toolbar that content scrolls under — is
|
|
139
|
+
`--surface-sticky` with `backdrop-filter: blur(8px)`: 92% of the card in light,
|
|
140
|
+
of the page ground in dark, so it reads as the surface it sits on, not a new layer.
|
|
141
|
+
|
|
138
142
|
## 7. Motion
|
|
139
143
|
|
|
140
144
|
**Content is visible by default. Motion is progressive enhancement layered on top
|
|
@@ -241,11 +245,30 @@ Two consequences for code that touches tokens directly:
|
|
|
241
245
|
Re-read it when the theme changes.
|
|
242
246
|
- **Don't wrap a token in `color-mix()` or `light-dark()`.** Nesting is valid CSS,
|
|
243
247
|
but not every engine has been verified to resolve it. If you need a wash, it's
|
|
244
|
-
a token (`--*-surface`, `--*-line`); if it isn't, add it here.
|
|
248
|
+
a token (`--*-surface`, `--*-line`, `--surface-sticky`); if it isn't, add it here.
|
|
245
249
|
|
|
246
250
|
Needs Chrome/Edge 123, Firefox 120, Safari 17.5 or later (all shipped by May 2024).
|
|
247
251
|
An older browser drops every colour token and renders unstyled rather than wrong.
|
|
248
252
|
|
|
253
|
+
### Build tools that lower `light-dark()`
|
|
254
|
+
|
|
255
|
+
A CSS pipeline that targets browsers older than that floor rewrites
|
|
256
|
+
`light-dark()` into a fallback. Lightning CSS (inside Next.js, Vite, Parcel)
|
|
257
|
+
turns each token into `var(--lightningcss-light, …) var(--lightningcss-dark, …)`
|
|
258
|
+
and flips those two variables wherever `color-scheme` changes. The tokens
|
|
259
|
+
resolve on `:root`, so the side is fixed there. Page-wide theming still works,
|
|
260
|
+
but **`data-theme` on an inner element stops re-theming that section.**
|
|
261
|
+
|
|
262
|
+
The fix is to target the same floor the tokens need, which leaves `light-dark()`
|
|
263
|
+
alone. In the consumer's `package.json`:
|
|
264
|
+
|
|
265
|
+
```json
|
|
266
|
+
"browserslist": ["chrome >= 123", "edge >= 123", "firefox >= 120", "safari >= 17.5"]
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Verified with Lightning CSS: older targets lower the tokens; these targets
|
|
270
|
+
pass `light-dark(#076bd3, #6ba5f3)` through untouched.
|
|
271
|
+
|
|
249
272
|
### Tailwind
|
|
250
273
|
|
|
251
274
|
Tailwind's dark variant keys off a `.dark` class; these tokens key off
|
|
@@ -283,3 +306,47 @@ Hardcoded values silently survive a theme change. `life`'s status pills carried
|
|
|
283
306
|
literal greys for months and would have stayed light grey in dark mode.
|
|
284
307
|
`brignano.io` carried four literal violet hexes plus ~15 `violet-*` utilities,
|
|
285
308
|
which is why changing the accent was a migration rather than a one-line edit.
|
|
309
|
+
|
|
310
|
+
## 13. Product pages
|
|
311
|
+
|
|
312
|
+
A product's marketing page is one argument and one ask. `product-page.css` is its
|
|
313
|
+
structure; `previews/product-page.html` is the skeleton to copy. Import it after
|
|
314
|
+
the tokens, put `tier-marketing` on `<html>` and `pp` on `<body>` (or a wrapper).
|
|
315
|
+
|
|
316
|
+
```css
|
|
317
|
+
@import '@brignano/design/tokens.css';
|
|
318
|
+
@import '@brignano/design/product-page.css';
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
The order is fixed, because each band answers the question the last one raised:
|
|
322
|
+
|
|
323
|
+
1. **Header** (`.pp-header`) — brand, two or three in-page links, the status, the
|
|
324
|
+
primary action. Sticky, on `--surface-sticky`.
|
|
325
|
+
2. **Hero** (`.pp-hero`) — the status, a one-line **thesis** (`.pp-thesis`), a
|
|
326
|
+
subhead of one or two sentences, the primary action and one secondary, and a
|
|
327
|
+
**proof slot** (`.pp-media`). The proof is the product itself where it can be
|
|
328
|
+
(a live demo), else a real screenshot. Never stock imagery.
|
|
329
|
+
3. **Story** (`ol.pp-story` of `.pp-chapter`) — three or four numbered chapters,
|
|
330
|
+
usually *the problem*, *how it works*, *who it's for*, *why now*. Each is a mono
|
|
331
|
+
label (`.pp-label`, numbered `01 /` by the list, so reordering renumbers), a
|
|
332
|
+
headline that is a sentence, a short body, and optionally `.pp-points` (two to
|
|
333
|
+
four ruled columns) or a `.pp-figure`.
|
|
334
|
+
4. **Close** (`.pp-close`) — the primary action again, with the room a form needs
|
|
335
|
+
(`.pp-panel`).
|
|
336
|
+
5. **Footer** (`.pp-footer`).
|
|
337
|
+
|
|
338
|
+
Rules:
|
|
339
|
+
|
|
340
|
+
- **One primary action per page.** `.pp-button` is that action, everywhere it
|
|
341
|
+
appears. Anything else is `.pp-button.is-secondary` or a link.
|
|
342
|
+
- **The status is a word with a dot** (`.pp-status`): "Pilot open", "In
|
|
343
|
+
development". `data-state="open"` takes `--success` (you can join now),
|
|
344
|
+
`"soon"` takes `--attention`; no attribute stays neutral. The word carries the
|
|
345
|
+
meaning on its own (§3, never colour alone).
|
|
346
|
+
- **Headlines are sentences.** Calm and declarative, no exclamation marks, no
|
|
347
|
+
claims the page can't back up.
|
|
348
|
+
- **Restraint carries the style.** Hairlines between chapters, whitespace, the
|
|
349
|
+
type scale. No gradients, no tinted section backgrounds, no decorative colour.
|
|
350
|
+
|
|
351
|
+
Every class starts with `.pp-`, so the file sits beside a site's own stylesheet.
|
|
352
|
+
A page can change `--pp-width` (default 1160px) and `--pp-gutter` on `.pp`.
|
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ The shared design system for every brignano surface — `brignano.io`, `life`,
|
|
|
9
9
|
| [`DECISIONS.md`](./DECISIONS.md) | Why the values are what they are. ADRs. |
|
|
10
10
|
| [`tokens.css`](./tokens.css) | Single source of truth for colour, type, space, shape, motion. |
|
|
11
11
|
| [`tokens.chart.css`](./tokens.chart.css) | Validated categorical palette for data viz. |
|
|
12
|
+
| [`product-page.css`](./product-page.css) | The structure of a product's marketing page: header, thesis hero, numbered story, closing ask. DESIGN.md §13; skeleton in `previews/product-page.html`. |
|
|
12
13
|
| [`previews/`](./previews) | Live swatches. Open one in a browser to see the tokens render. |
|
|
13
14
|
|
|
14
15
|
## The shape of it
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brignano/design",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Shared design system for brignano surfaces
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Shared design system for brignano surfaces \u2014 colour, type, space, shape, motion.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"files": [
|
|
18
18
|
"tokens.css",
|
|
19
19
|
"tokens.chart.css",
|
|
20
|
+
"product-page.css",
|
|
20
21
|
"tailwind.css",
|
|
21
22
|
"DESIGN.md",
|
|
22
23
|
"DECISIONS.md"
|
|
@@ -24,7 +25,8 @@
|
|
|
24
25
|
"exports": {
|
|
25
26
|
"./tokens.css": "./tokens.css",
|
|
26
27
|
"./tokens.chart.css": "./tokens.chart.css",
|
|
27
|
-
"./tailwind.css": "./tailwind.css"
|
|
28
|
+
"./tailwind.css": "./tailwind.css",
|
|
29
|
+
"./product-page.css": "./product-page.css"
|
|
28
30
|
},
|
|
29
31
|
"sideEffects": [
|
|
30
32
|
"*.css"
|
package/product-page.css
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/* product-page.css — the page structure for a product's marketing site.
|
|
2
|
+
*
|
|
3
|
+
* @import '@brignano/design/tokens.css';
|
|
4
|
+
* @import '@brignano/design/product-page.css';
|
|
5
|
+
* <html class="tier-marketing">
|
|
6
|
+
*
|
|
7
|
+
* One page, read top to bottom, that makes one argument and asks for one thing:
|
|
8
|
+
*
|
|
9
|
+
* .pp-header brand, a few in-page links, the status, the primary action
|
|
10
|
+
* .pp-hero status, a one-line thesis, a subhead, two actions, a proof slot
|
|
11
|
+
* .pp-story 3–4 numbered chapters: problem, how it works, who it's for, why now
|
|
12
|
+
* .pp-close the primary action again, with whatever it needs (a form)
|
|
13
|
+
* .pp-footer
|
|
14
|
+
*
|
|
15
|
+
* The skeleton is previews/product-page.html: copy it, keep the order, replace
|
|
16
|
+
* the words. Rules (DESIGN.md §13):
|
|
17
|
+
*
|
|
18
|
+
* - One primary action per page (.pp-button). Everything else is secondary.
|
|
19
|
+
* - The status is a word with a dot, never a coloured badge on its own.
|
|
20
|
+
* - The proof slot is the product itself where possible (a live demo), else a
|
|
21
|
+
* real screenshot. No stock imagery, no illustrations of people.
|
|
22
|
+
* - Chapters are numbered by the list, so reordering them renumbers them.
|
|
23
|
+
*
|
|
24
|
+
* Every class starts with .pp- so it can sit beside a site's own stylesheet.
|
|
25
|
+
* Colours are tokens only; widths are --pp-width / --pp-gutter, which a page
|
|
26
|
+
* may override on .pp. */
|
|
27
|
+
|
|
28
|
+
.pp {
|
|
29
|
+
--pp-width: 1160px;
|
|
30
|
+
--pp-gutter: var(--s5);
|
|
31
|
+
background: var(--bg);
|
|
32
|
+
color: var(--ink);
|
|
33
|
+
font: var(--text-base) / 1.6 var(--sans);
|
|
34
|
+
font-variant-numeric: tabular-nums;
|
|
35
|
+
-webkit-font-smoothing: antialiased;
|
|
36
|
+
}
|
|
37
|
+
@media (max-width: 560px) { .pp { --pp-gutter: var(--s4); } }
|
|
38
|
+
|
|
39
|
+
/* The content column every band lines up on. */
|
|
40
|
+
.pp-wrap { max-width: var(--pp-width); margin: 0 auto; padding: 0 var(--pp-gutter); }
|
|
41
|
+
|
|
42
|
+
/* ── Skip link ─────────────────────────────────────────────────────────── */
|
|
43
|
+
.pp-skip { position: absolute; left: var(--s4); top: -100px; z-index: 20; padding: var(--s2) var(--s3);
|
|
44
|
+
background: var(--card); color: var(--ink); border-radius: var(--radius-sm); box-shadow: var(--shadow-2); }
|
|
45
|
+
.pp-skip:focus { top: var(--s3); }
|
|
46
|
+
|
|
47
|
+
/* ── Header ────────────────────────────────────────────────────────────── */
|
|
48
|
+
.pp-header {
|
|
49
|
+
position: sticky; top: 0; z-index: 10;
|
|
50
|
+
background: var(--surface-sticky); -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
|
|
51
|
+
border-bottom: 1px solid var(--line);
|
|
52
|
+
}
|
|
53
|
+
.pp-header > .pp-wrap { display: flex; align-items: center; gap: var(--s5); min-height: 64px; }
|
|
54
|
+
.pp-brand {
|
|
55
|
+
display: inline-flex; align-items: center; gap: 10px; color: var(--ink); text-decoration: none;
|
|
56
|
+
font-weight: 600; font-size: var(--text-lg); letter-spacing: -0.01em;
|
|
57
|
+
}
|
|
58
|
+
.pp-brand img, .pp-brand svg { width: 26px; height: 26px; }
|
|
59
|
+
.pp-nav { display: flex; align-items: center; gap: var(--s5); margin-left: auto; }
|
|
60
|
+
.pp-nav a { color: var(--ink-soft); text-decoration: none; font-size: var(--text-sm); font-weight: 500; }
|
|
61
|
+
.pp-nav a:hover { color: var(--ink); }
|
|
62
|
+
.pp-header-actions { display: flex; align-items: center; gap: var(--s3); }
|
|
63
|
+
.pp-nav + .pp-header-actions { margin-left: 0; }
|
|
64
|
+
.pp-brand + .pp-header-actions { margin-left: auto; }
|
|
65
|
+
@media (max-width: 860px) { .pp-nav { display: none; } .pp-header-actions { margin-left: auto; } }
|
|
66
|
+
@media (max-width: 480px) { .pp-header .pp-status { display: none; } }
|
|
67
|
+
|
|
68
|
+
/* ── Status ────────────────────────────────────────────────────────────
|
|
69
|
+
Where the product stands, as a word with a dot: "Pilot open", "In
|
|
70
|
+
development", "Closed beta". The dot takes a state hue only when the state
|
|
71
|
+
asks something of the reader (DESIGN.md §3): open = success (you can join),
|
|
72
|
+
soon = attention (it is coming, watch for it). Without data-state it stays
|
|
73
|
+
neutral. The word always carries the meaning on its own. */
|
|
74
|
+
.pp-status {
|
|
75
|
+
display: inline-flex; align-items: center; gap: var(--s2); white-space: nowrap;
|
|
76
|
+
font: 500 var(--text-xs) / 1 var(--mono); letter-spacing: 0.08em; text-transform: uppercase;
|
|
77
|
+
color: var(--ink-soft); padding: 6px 10px; border: 1px solid var(--line-strong); border-radius: 999px;
|
|
78
|
+
}
|
|
79
|
+
.pp-status::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--slate); flex: none; }
|
|
80
|
+
.pp-status[data-state="open"]::before { background: var(--success); box-shadow: 0 0 0 3px var(--success-surface); }
|
|
81
|
+
.pp-status[data-state="soon"]::before { background: var(--attention); box-shadow: 0 0 0 3px var(--attention-surface); }
|
|
82
|
+
|
|
83
|
+
/* ── Buttons ───────────────────────────────────────────────────────────
|
|
84
|
+
.pp-button is the page's one primary action. .is-secondary is everything
|
|
85
|
+
else. Works on <a> and <button>. */
|
|
86
|
+
.pp-button {
|
|
87
|
+
display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
|
|
88
|
+
font: 600 var(--text-base) / 1.2 var(--sans); text-decoration: none; white-space: nowrap; cursor: pointer;
|
|
89
|
+
padding: 13px 22px; border-radius: 8px; border: 1px solid var(--i);
|
|
90
|
+
background: var(--i); color: var(--on-i);
|
|
91
|
+
transition: background-color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
|
|
92
|
+
}
|
|
93
|
+
.pp-button:hover { background: var(--i-hover); border-color: var(--i-hover); color: var(--on-i); }
|
|
94
|
+
.pp-button:active { background: var(--i-pressed); border-color: var(--i-pressed); }
|
|
95
|
+
.pp-button.is-secondary { background: transparent; color: var(--ink); border-color: var(--line-control); font-weight: 500; }
|
|
96
|
+
.pp-button.is-secondary:hover { background: var(--surface-hover); color: var(--ink); }
|
|
97
|
+
.pp-button.is-secondary:active { background: var(--surface-active); }
|
|
98
|
+
.pp-button.is-small { font-size: var(--text-sm); padding: 8px 14px; }
|
|
99
|
+
.pp-button:focus-visible { outline: 2px solid var(--i-ink); outline-offset: 2px; }
|
|
100
|
+
.pp-button:disabled { background: var(--i-disabled-bg); border-color: transparent; color: var(--i-disabled-ink); cursor: not-allowed; }
|
|
101
|
+
@media (pointer: coarse) { .pp-button { min-height: 44px; } }
|
|
102
|
+
|
|
103
|
+
.pp-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3); margin: 0; }
|
|
104
|
+
.pp-note { margin: var(--s3) 0 0; font-size: var(--text-sm); color: var(--ink-soft); }
|
|
105
|
+
|
|
106
|
+
/* ── Hero ──────────────────────────────────────────────────────────────
|
|
107
|
+
Copy on the left, proof on the right; stacked below 960px with the copy
|
|
108
|
+
first. The thesis is one line of argument, not a tagline. */
|
|
109
|
+
.pp-hero { padding: var(--s8) 0 var(--s8); }
|
|
110
|
+
.pp-hero > .pp-wrap { display: grid; gap: var(--s7); align-items: center; grid-template-columns: minmax(0, 1fr); }
|
|
111
|
+
@media (min-width: 960px) { .pp-hero > .pp-wrap { grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); } }
|
|
112
|
+
.pp-hero .pp-status { margin-bottom: var(--s5); }
|
|
113
|
+
.pp-thesis {
|
|
114
|
+
font-size: var(--text-4xl); font-weight: 600; line-height: 1.04; letter-spacing: -0.035em;
|
|
115
|
+
margin: 0 0 var(--s4); max-width: 14ch; text-wrap: balance;
|
|
116
|
+
}
|
|
117
|
+
.pp-subhead { font-size: var(--text-lg); line-height: 1.5; color: var(--ink-soft); margin: 0 0 var(--s6); max-width: 40ch; }
|
|
118
|
+
.pp-media { margin: 0; min-width: 0; }
|
|
119
|
+
|
|
120
|
+
/* ── Story ─────────────────────────────────────────────────────────────
|
|
121
|
+
An <ol> of chapters. Each opens on a hairline with "01 / Label" in mono,
|
|
122
|
+
then a headline that is a sentence, then a short body and an optional
|
|
123
|
+
figure. Wide screens put the label in its own column. */
|
|
124
|
+
.pp-story { list-style: none; margin: 0; padding: 0; counter-reset: pp-chapter; }
|
|
125
|
+
.pp-chapter { counter-increment: pp-chapter; padding: var(--s8) 0; border-top: 1px solid var(--line); scroll-margin-top: 64px; }
|
|
126
|
+
.pp-chapter > .pp-wrap { display: grid; gap: var(--s4) var(--s7); grid-template-columns: minmax(0, 1fr); }
|
|
127
|
+
@media (min-width: 960px) {
|
|
128
|
+
.pp-chapter > .pp-wrap { grid-template-columns: minmax(0, 3fr) minmax(0, 9fr); }
|
|
129
|
+
.pp-chapter-body { grid-column: 2; }
|
|
130
|
+
}
|
|
131
|
+
.pp-label { margin: 0; padding-top: 0.4em; font: 500 var(--text-xs) / 1.4 var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-soft); }
|
|
132
|
+
.pp-label::before { content: counter(pp-chapter, decimal-leading-zero) " / "; color: var(--ink); }
|
|
133
|
+
.pp-chapter h2 {
|
|
134
|
+
font-size: clamp(var(--text-xl), 1rem + 2vw, var(--text-3xl)); font-weight: 600; line-height: 1.15;
|
|
135
|
+
letter-spacing: -0.025em; margin: 0 0 var(--s4); max-width: 22ch; text-wrap: balance;
|
|
136
|
+
}
|
|
137
|
+
.pp-chapter-body > p { margin: 0 0 var(--s4); max-width: 62ch; color: var(--ink-soft); font-size: var(--text-lg); line-height: 1.55; }
|
|
138
|
+
.pp-chapter-body > p:last-child { margin-bottom: 0; }
|
|
139
|
+
|
|
140
|
+
/* Two to four short points under a chapter: a ruled column each. */
|
|
141
|
+
.pp-points { list-style: none; margin: var(--s6) 0 0; padding: 0; display: grid; gap: var(--s5); grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
|
|
142
|
+
.pp-points > li { border-top: 2px solid var(--ink); padding-top: var(--s3); }
|
|
143
|
+
.pp-points h3 { margin: 0 0 6px; font-size: var(--text-lg); font-weight: 600; letter-spacing: -0.01em; }
|
|
144
|
+
.pp-points p { margin: 0; color: var(--ink-soft); }
|
|
145
|
+
|
|
146
|
+
/* A chapter's figure: a terminal, a screenshot, a small mock of the product. */
|
|
147
|
+
.pp-figure { margin: var(--s6) 0 0; }
|
|
148
|
+
.pp-figure > img { display: block; width: 100%; height: auto; border-radius: var(--radius-lg); border: 1px solid var(--line); box-shadow: var(--shadow-2); }
|
|
149
|
+
.pp-figure figcaption { margin-top: var(--s2); font-size: var(--text-sm); color: var(--ink-soft); }
|
|
150
|
+
.pp-terminal { margin: 0; overflow-x: auto; background: var(--card); color: var(--ink); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow-1);
|
|
151
|
+
padding: var(--s5); font: var(--text-sm) / 1.7 var(--mono); }
|
|
152
|
+
.pp-terminal code { font: inherit; }
|
|
153
|
+
|
|
154
|
+
/* ── Close ─────────────────────────────────────────────────────────────
|
|
155
|
+
The primary action once more, with the room a form needs. */
|
|
156
|
+
.pp-close { padding: var(--s8) 0 var(--s9); border-top: 1px solid var(--line); scroll-margin-top: 64px; }
|
|
157
|
+
.pp-close > .pp-wrap { display: grid; gap: var(--s6); grid-template-columns: minmax(0, 1fr); }
|
|
158
|
+
@media (min-width: 960px) { .pp-close > .pp-wrap { grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); } }
|
|
159
|
+
.pp-close h2 { font-size: clamp(var(--text-xl), 1rem + 2vw, var(--text-3xl)); font-weight: 600; line-height: 1.15; letter-spacing: -0.025em; margin: var(--s4) 0 var(--s3); text-wrap: balance; }
|
|
160
|
+
.pp-close .pp-subhead { margin-bottom: 0; }
|
|
161
|
+
.pp-panel { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius-lg); box-shadow: var(--shadow-2); padding: var(--s6); }
|
|
162
|
+
@media (max-width: 560px) { .pp-panel { padding: var(--s5) var(--s4); } }
|
|
163
|
+
|
|
164
|
+
/* ── Footer ────────────────────────────────────────────────────────────── */
|
|
165
|
+
.pp-footer { border-top: 1px solid var(--line); padding: var(--s6) 0 var(--s7); color: var(--ink-soft); font-size: var(--text-sm); }
|
|
166
|
+
.pp-footer > .pp-wrap { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--s4) var(--s6); }
|
|
167
|
+
.pp-footer p { margin: 0; display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3); }
|
|
168
|
+
.pp-footer .pp-brand { font-size: var(--text-base); }
|
|
169
|
+
.pp-footer .pp-brand img, .pp-footer .pp-brand svg { width: 20px; height: 20px; }
|
|
170
|
+
.pp-footer nav { display: flex; flex-wrap: wrap; gap: var(--s4); }
|
|
171
|
+
.pp-footer nav a { color: var(--ink-soft); text-decoration: none; }
|
|
172
|
+
.pp-footer nav a:hover { color: var(--ink); }
|
package/tailwind.css
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
--color-line-control: var(--line-control);
|
|
24
24
|
--color-surface-hover: var(--surface-hover);
|
|
25
25
|
--color-surface-active: var(--surface-active);
|
|
26
|
+
--color-surface-sticky: var(--surface-sticky);
|
|
26
27
|
--color-on-dark: var(--on-dark);
|
|
27
28
|
|
|
28
29
|
--color-interactive: var(--i);
|
package/tokens.css
CHANGED
|
@@ -119,6 +119,11 @@
|
|
|
119
119
|
--card: light-dark(#ffffff, #17171a); /* n-0 / n-50 */
|
|
120
120
|
--surface-hover: var(--n-100);
|
|
121
121
|
--surface-active: var(--n-200);
|
|
122
|
+
/* Sticky chrome — a header or toolbar that content scrolls under, usually
|
|
123
|
+
with `backdrop-filter: blur()`. 92% of the surface it sits on: the card
|
|
124
|
+
in light, the page ground in dark, where cards are lighter than the
|
|
125
|
+
page. A token so consumers never color-mix() one (DESIGN.md §11). */
|
|
126
|
+
--surface-sticky: light-dark(color-mix(in srgb, #ffffff 92%, transparent), color-mix(in srgb, #0d0d0f 92%, transparent)); /* card / bg */
|
|
122
127
|
--ink: var(--n-900);
|
|
123
128
|
--ink-soft: var(--n-700);
|
|
124
129
|
--slate: light-dark(#6b6b73, #8b8b93); /* n-600 / n-500 — text-safe: eyebrows, axis labels, captions */
|