@brignano/design 0.3.1 → 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/DESIGN.md CHANGED
@@ -306,3 +306,47 @@ Hardcoded values silently survive a theme change. `life`'s status pills carried
306
306
  literal greys for months and would have stayed light grey in dark mode.
307
307
  `brignano.io` carried four literal violet hexes plus ~15 `violet-*` utilities,
308
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.3.1",
4
- "description": "Shared design system for brignano surfaces — colour, type, space, shape, motion.",
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"
@@ -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); }