@bitbaum/design-tokens 1.1.0 → 1.2.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @bitbaum/design-tokens
2
2
 
3
- The design **single source of truth** for OrangeCat, FleetCrown and Solon.
3
+ The design **single source of truth** for OrangeCat, Loki and Solon.
4
4
 
5
5
  One file — [`tokens.css`](./tokens.css) — defines every colour, typeface, weight,
6
6
  tracking, radius and shared primitive the three products use. They import it.
@@ -15,9 +15,13 @@ three different companies. Copies drift; imports cannot.
15
15
 
16
16
  ## Retheming the entire stack
17
17
 
18
- 1. Edit the block marked `▼▼▼ THE KNOBS ▼▼▼` in `tokens.css`. Nothing else.
19
- 2. Tag a release: `git tag v1.2.0 && git push --tags`
20
- 3. Bump `@bitbaum/design-tokens` in the three apps. Their CI auto-merges.
18
+ 1. Edit the block marked `▼▼▼ THE KNOBS ▼▼▼` in `tokens.css` for a retheme, or update the optional `composition.css` contract for interface improvements.
19
+ 2. Bump `package.json`'s version **and** set the matching explicit `.github/release-request.txt`; submit a PR and pass package CI.
20
+ 3. A successful **main-branch push CI** triggers `tag-approved-release.yml`. It tags only the exact latest verified SHA and never rewrites an existing version tag.
21
+ 4. The tag workflow explicitly dispatches `publish.yml`; npm publishing is still guarded by `NPM_PUBLISHING` and requires trusted-publishing bootstrap. If that variable is off, a Git tag exists but **no npm release** is claimed.
22
+ 5. Upgrade consumers to the confirmed Git tag, published npm version, or an exact source-commit SHA; run their frozen-lockfile CI.
23
+
24
+ The one-time release request does not automatically approve future version bumps: a request must match the manifest version.
21
25
 
22
26
  Changing the display typeface is **one line**:
23
27
 
@@ -51,6 +55,68 @@ module.exports = {
51
55
  Tailwind v4 apps consume `tokens.css` through `@theme`; the variable names are
52
56
  identical, so both paths stay in sync.
53
57
 
58
+ ## Optional cross-product composition grammar
59
+
60
+ The **brand tokens** above intentionally belong to OrangeCat/Loki/Solon.
61
+ Their shared palette should never be imposed on unrelated products or
62
+ customer websites.
63
+
64
+ For spacing, control sizing, visual rhythm and interaction *decisions*,
65
+ `composition.css` is an opt-in, namespaced layer. It defines no fonts, brand
66
+ colours, body style or global reset.
67
+
68
+ ```tsx
69
+ // App Router — load AFTER the app's CSS reset and BEFORE product CSS modules.
70
+ // The shared classes intentionally use :where() specificity, so importing
71
+ // them before a later universal '*' padding reset would erase their padding.
72
+ import './globals.css';
73
+ import '@bitbaum/design-tokens/composition.css';
74
+ ```
75
+
76
+ ```tsx
77
+ <main className="bb-frame">
78
+ <div className="bb-stack" style={{ '--bb-stack-gap': '1.5rem' }}>
79
+ <div className="bb-measure">A product-specific headline and description</div>
80
+ <div className="bb-actions">
81
+ <a className="bb-button bb-button--primary" href="/start">Get started</a>
82
+ <a className="bb-button bb-button--secondary" href="/method">Read the method</a>
83
+ </div>
84
+ </div>
85
+ </main>
86
+ ```
87
+
88
+ Map only the small seam of **semantic** CSS roles once in each app:
89
+
90
+ ```css
91
+ :root {
92
+ --bb-action-bg: var(--color-action);
93
+ --bb-action-fg: var(--color-on-action);
94
+ --bb-control-bg: var(--color-surface);
95
+ --bb-control-fg: var(--color-text);
96
+ --bb-line: var(--color-border);
97
+ --bb-muted: var(--color-text-muted);
98
+ --bb-focus: var(--color-focus);
99
+ }
100
+ ```
101
+
102
+ The exported grammar includes `bb-frame`, `bb-measure`, `bb-section`,
103
+ `bb-stack`, `bb-cluster`, `bb-grid`, `bb-actions`,
104
+ `bb-choice-row`, `bb-list`, `bb-keyline`, `bb-tabular`,
105
+ `bb-control`, `bb-button`, and `bb-skip`.
106
+
107
+ It guarantees 44px controls, visible focus, responsive equal-cell choices,
108
+ and reduced-motion support. Apps own typography, colours, spacing adjustments,
109
+ components and their content. Do **not** render all information as cards: the
110
+ grammar deliberately offers whitespace and hairline lists.
111
+
112
+ **Release contract:** the new composition subpath first appears in
113
+ source version `1.2.0`; `v1.1.0` does **not** include it. The verified
114
+ tagging pipeline only tags a version explicitly requested in
115
+ `.github/release-request.txt` after main CI is green. Verify the
116
+ `v1.2.0` Git ref and the registry separately before claiming either is
117
+ published. Until then, use a verified exact commit SHA. Existing
118
+ `tokens.css` consumers do not change appearance by opting out.
119
+
54
120
  ## The contract
55
121
 
56
122
  Components declare **intent**, never mechanics.
@@ -88,3 +154,20 @@ grotesque convention, where the winner is whoever has the largest design budget.
88
154
  A high-contrast serif exits that comparison: it reads institutional rather than
89
155
  startup, which is exactly right for a stack whose product is *legitimacy* —
90
156
  signed votes, verifiable treasuries, an append-only audit trail.
157
+
158
+ ---
159
+
160
+ Part of **[bitbaum](https://bitbaum.orangecat.ch)** — AI-native products on open
161
+ infrastructure, built in Zürich. Every package here lists the apps that use it:
162
+ **[which apps use design-tokens](https://bitbaum.orangecat.ch/packages/#design-tokens)**.
163
+
164
+ ## Data maps
165
+
166
+ `--map-scale-1` … `--map-scale-7` are the one sequential scale for every
167
+ choropleth (viridis, dark to light; hex, so an SVG `fill` takes them as is).
168
+ Luminance rises at every step, so the scale survives colour-vision deficiency
169
+ and greyscale; `test/contrast.test.js` keeps it that way. `--map-no-data` and
170
+ `--map-boundary` follow the theme. An area more than one authority claims is
171
+ **hatched**, never given a colour of its own: lines of `--map-hatch-width`
172
+ every `--map-hatch-gap` at `--map-hatch-angle`, in the text colour. Colour is
173
+ never the only carrier: every map has a list and a legend.
@@ -0,0 +1,196 @@
1
+ /**
2
+ * @bitbaum/design-tokens/composition.css
3
+ *
4
+ * Opt-in cross-product layout and interaction decisions, not a brand skin.
5
+ * No :root, html, body, element selectors or color values are set.
6
+ * Every rule is namespaced; apps provide semantic values through --bb-*.
7
+ *
8
+ * A site remains responsible for its own palette, fonts and personality.
9
+ * This layer owns only spatial rhythm, consistent controls, accessible
10
+ * interactions and density that behaves at 390px.
11
+ */
12
+
13
+ :where(.bb-frame) {
14
+ width: min(100%, var(--bb-frame-max, 72rem));
15
+ margin-inline: auto;
16
+ padding-inline: var(--bb-gutter, clamp(1rem, 4vw, 3rem));
17
+ }
18
+
19
+ :where(.bb-measure) {
20
+ max-width: var(--bb-measure, 68ch);
21
+ }
22
+
23
+ :where(.bb-stack) {
24
+ display: grid;
25
+ gap: var(--bb-stack-gap, 1rem);
26
+ min-width: 0;
27
+ }
28
+
29
+ :where(.bb-cluster) {
30
+ display: flex;
31
+ align-items: center;
32
+ flex-wrap: wrap;
33
+ gap: var(--bb-cluster-gap, 0.75rem);
34
+ }
35
+
36
+ :where(.bb-grid) {
37
+ display: grid;
38
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--bb-grid-min, 16rem)), 1fr));
39
+ gap: var(--bb-grid-gap, 1.5rem);
40
+ }
41
+
42
+ :where(.bb-section) {
43
+ padding-block: var(--bb-section-space, clamp(3.5rem, 7vw, 7rem));
44
+ }
45
+
46
+ :where(.bb-keyline) {
47
+ border-block-start: 1px solid var(--bb-line, var(--color-border, currentColor));
48
+ }
49
+
50
+ :where(.bb-tabular) {
51
+ font-variant-numeric: tabular-nums;
52
+ }
53
+
54
+ :where(.bb-control) {
55
+ display: block;
56
+ inline-size: 100%;
57
+ min-block-size: 44px;
58
+ border: 1px solid var(--bb-line, var(--color-border, currentColor));
59
+ border-radius: var(--bb-radius-control, 0.5rem);
60
+ padding: 0.75rem 0.9rem;
61
+ background: var(--bb-control-bg, Canvas);
62
+ color: var(--bb-control-fg, CanvasText);
63
+ font: inherit;
64
+ font-size: 1rem;
65
+ line-height: 1.4;
66
+ }
67
+
68
+ :where(.bb-control)::placeholder {
69
+ color: var(--bb-muted, GrayText);
70
+ opacity: 1;
71
+ }
72
+
73
+ :where(.bb-control:focus-visible, .bb-button:focus-visible, .bb-skip:focus-visible) {
74
+ outline: 3px solid var(--bb-focus, var(--bb-action-bg, Highlight));
75
+ outline-offset: 3px;
76
+ }
77
+
78
+ /* A textarea needs room to write, but not a fixed desktop-height on a phone. */
79
+ :where(textarea.bb-control) {
80
+ resize: vertical;
81
+ min-block-size: 10rem;
82
+ }
83
+
84
+ :where(.bb-button) {
85
+ display: inline-flex;
86
+ align-items: center;
87
+ justify-content: center;
88
+ gap: 0.5rem;
89
+ min-block-size: 44px;
90
+ padding: 0.75rem 1.2rem;
91
+ border: 1px solid transparent;
92
+ border-radius: var(--bb-radius-control, 0.5rem);
93
+ font: inherit;
94
+ font-size: 0.95rem;
95
+ font-weight: 700;
96
+ line-height: 1.2;
97
+ text-align: center;
98
+ text-decoration: none;
99
+ cursor: pointer;
100
+ white-space: normal;
101
+ }
102
+
103
+ :where(.bb-button--primary) {
104
+ color: var(--bb-action-fg, Canvas);
105
+ background: var(--bb-action-bg, CanvasText);
106
+ }
107
+
108
+ :where(.bb-button--secondary) {
109
+ color: var(--bb-action-bg, CanvasText);
110
+ background: var(--bb-control-bg, Canvas);
111
+ border-color: var(--bb-line, var(--color-border, currentColor));
112
+ }
113
+
114
+ :where(.bb-button:disabled, .bb-button[aria-disabled='true']) {
115
+ cursor: not-allowed;
116
+ opacity: 0.5;
117
+ }
118
+
119
+ /* Choose once, keep the choices in one row. Equal cells, never a 4+1 wrap. */
120
+ :where(.bb-choice-row) {
121
+ display: grid;
122
+ grid-template-columns: repeat(var(--bb-choices, 2), minmax(0, 1fr));
123
+ gap: 0;
124
+ overflow: hidden;
125
+ border: 1px solid var(--bb-line, var(--color-border, currentColor));
126
+ border-radius: var(--bb-radius-control, 0.5rem);
127
+ }
128
+
129
+ :where(.bb-choice-row > *) {
130
+ min-width: 0;
131
+ min-height: 44px;
132
+ }
133
+
134
+ :where(.bb-choice-row > * + *) {
135
+ border-inline-start: 1px solid var(--bb-line, var(--color-border, currentColor));
136
+ }
137
+
138
+ /* Same shape and predictable vertical rhythm for sibling rows. */
139
+ :where(.bb-list) {
140
+ list-style: none;
141
+ margin: 0;
142
+ padding: 0;
143
+ }
144
+
145
+ :where(.bb-list > * + *) {
146
+ border-block-start: 1px solid var(--bb-line, var(--color-border, currentColor));
147
+ }
148
+
149
+ :where(.bb-list > *) {
150
+ padding-block: var(--bb-list-row-y, 1rem);
151
+ min-width: 0;
152
+ }
153
+
154
+ :where(.bb-skip) {
155
+ position: absolute;
156
+ inset-inline-start: var(--bb-gutter, 1rem);
157
+ inset-block-start: -8rem;
158
+ z-index: 100;
159
+ padding: 0.8rem 1rem;
160
+ background: var(--bb-control-bg, Canvas);
161
+ color: var(--bb-control-fg, CanvasText);
162
+ border: 2px solid var(--bb-focus, Highlight);
163
+ border-radius: var(--bb-radius-control, 0.5rem);
164
+ }
165
+
166
+ :where(.bb-skip:focus) {
167
+ inset-block-start: 1rem;
168
+ }
169
+
170
+ @media (max-width: 640px) {
171
+ :where(.bb-actions) {
172
+ display: grid;
173
+ grid-template-columns: minmax(0, 1fr);
174
+ gap: 0.75rem;
175
+ }
176
+
177
+ :where(.bb-actions > .bb-button) {
178
+ inline-size: 100%;
179
+ }
180
+ }
181
+
182
+ @media (min-width: 641px) {
183
+ :where(.bb-actions) {
184
+ display: flex;
185
+ flex-wrap: wrap;
186
+ gap: 0.75rem;
187
+ align-items: center;
188
+ }
189
+ }
190
+
191
+ @media (prefers-reduced-motion: reduce) {
192
+ :where(.bb-button, .bb-control) {
193
+ transition: none;
194
+ animation: none;
195
+ }
196
+ }
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@bitbaum/design-tokens",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
+ "packageManager": "pnpm@11.25.0",
4
5
  "engines": {
5
6
  "node": ">=20"
6
7
  },
@@ -8,7 +9,7 @@
8
9
  "access": "public",
9
10
  "provenance": true
10
11
  },
11
- "description": "Design SSOT for OrangeCat, FleetCrown and Solon — tokens, self-hosted faces, and the Tailwind preset that maps them.",
12
+ "description": "Design SSOT for OrangeCat, Loki and Solon — tokens, self-hosted faces, and the Tailwind preset that maps them.",
12
13
  "license": "MIT",
13
14
  "repository": {
14
15
  "type": "git",
@@ -16,25 +17,19 @@
16
17
  },
17
18
  "files": [
18
19
  "tokens.css",
20
+ "composition.css",
19
21
  "tailwind-preset.js",
20
22
  "fonts"
21
23
  ],
22
24
  "exports": {
23
25
  "./tokens.css": "./tokens.css",
26
+ "./composition.css": "./composition.css",
24
27
  "./tailwind-preset": "./tailwind-preset.js",
25
28
  "./fonts/*": "./fonts/*"
26
29
  },
27
30
  "sideEffects": [
28
31
  "*.css"
29
32
  ],
30
- "devDependencies": {
31
- "@eslint/js": "^10.0.1",
32
- "@types/node": "^26.4.0",
33
- "eslint": "^10.9.1",
34
- "globals": "^17.12.0",
35
- "prettier": "3.9.6",
36
- "typescript": "^6.0.3"
37
- },
38
33
  "scripts": {
39
34
  "lint": "eslint .",
40
35
  "typecheck": "tsc --noEmit",
@@ -42,5 +37,13 @@
42
37
  "verify": "pnpm run format:check && pnpm run lint && pnpm run typecheck && pnpm test",
43
38
  "format": "prettier --write .",
44
39
  "format:check": "prettier --check ."
40
+ },
41
+ "devDependencies": {
42
+ "@eslint/js": "^10.0.1",
43
+ "@types/node": "^26.6.5",
44
+ "eslint": "^10.12.0",
45
+ "globals": "^17.13.0",
46
+ "prettier": "3.9.10",
47
+ "typescript": "^6.0.3"
45
48
  }
46
- }
49
+ }
@@ -11,7 +11,7 @@
11
11
  * content: [...],
12
12
  * }
13
13
  *
14
- * Tailwind v4 apps (FleetCrown) consume tokens.css directly via @theme instead;
14
+ * Tailwind v4 apps (Loki) consume tokens.css directly via @theme instead;
15
15
  * the variable names are identical, so the two paths stay in sync.
16
16
  */
17
17
 
package/tokens.css CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @bitbaum/design-tokens — the design SSOT for OrangeCat, FleetCrown and Solon.
2
+ * @bitbaum/design-tokens — the design SSOT for OrangeCat, Loki and Solon.
3
3
  *
4
4
  * ┌──────────────────────────────────────────────────────────────────────────┐
5
5
  * │ TO RETHEME THE ENTIRE STACK, EDIT THE "KNOBS" BLOCK BELOW AND NOTHING │
@@ -131,7 +131,7 @@
131
131
  *
132
132
  * White on #ff5c00 is 3.10:1 — below the 4.5 AA floor — so every primary
133
133
  * CTA in the fleet was failing at once: Solon's "Open the Dashboard",
134
- * OrangeCat's "Get Started", FleetCrown's "Launch agent". Measured, not
134
+ * OrangeCat's "Get Started", Loki's "Launch agent". Measured, not
135
135
  * guessed. Near-black on the same orange is 6.10:1.
136
136
  *
137
137
  * The orange is deliberately NOT darkened to rescue white text: it would
@@ -155,7 +155,7 @@
155
155
  /* ── LIGHT THEME ────────────────────────────────────────────────────────
156
156
  :root is light and `.dark` overrides it — the same mechanism OrangeCat
157
157
  uses, so an app can toggle themes without translating anything. An app
158
- that is dark-only (Solon, FleetCrown's public surface) pins it by putting
158
+ that is dark-only (Solon, Loki's public surface) pins it by putting
159
159
  `class="dark"` on <html> and never removing it. */
160
160
 
161
161
  /* Surfaces — page < base < raised < overlay < modal < drawer */
@@ -242,6 +242,32 @@
242
242
  /* Bitcoin's own orange. Not a brand colour — it means "this is Bitcoin", so
243
243
  it is deliberately NOT --public-accent and must not be used for actions. */
244
244
  --bitcoin-orange: #f7931a;
245
+
246
+ /**
247
+ * — Data maps ——————————————————————————————————————————————————————————
248
+ * One sequential scale for every choropleth (Solon's Places map first):
249
+ * viridis, dark to light, so it survives every colour-vision deficiency and
250
+ * reads in greyscale — its luminance rises at every step (pinned in
251
+ * test/contrast.test.js). Colour is never the only carrier: each map has a
252
+ * list with the same numbers, and a legend.
253
+ *
254
+ * Hatching marks an area more than one authority claims. A map takes no
255
+ * side, so a contested area gets the pattern, never a colour of its own:
256
+ * lines of --map-hatch-width every --map-hatch-gap at --map-hatch-angle, in
257
+ * the text colour, over whatever the scale says.
258
+ */
259
+ --map-scale-1: #46327e;
260
+ --map-scale-2: #365c8d;
261
+ --map-scale-3: #277f8e;
262
+ --map-scale-4: #1fa187;
263
+ --map-scale-5: #4ac16d;
264
+ --map-scale-6: #a0da39;
265
+ --map-scale-7: #fde725;
266
+ --map-no-data: 0 0% 85%;
267
+ --map-boundary: 0 0% 45%;
268
+ --map-hatch-angle: 45deg;
269
+ --map-hatch-gap: 6px;
270
+ --map-hatch-width: 1.5px;
245
271
  }
246
272
 
247
273
  /* ══════════════════════════════════════════════════════════════════════════
@@ -273,6 +299,10 @@
273
299
  --border-subtle: 0 0% 13%;
274
300
  --border-default: 0 0% 20%;
275
301
  --border-strong: 0 0% 24%;
302
+
303
+ /* Data maps: no-data and boundaries sit between the page and the scale */
304
+ --map-no-data: 0 0% 20%;
305
+ --map-boundary: 0 0% 40%;
276
306
  --border-interactive: 0 0% 60%;
277
307
 
278
308
  /* Accent */