@humanforest/tokens 0.3.7 → 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/CHANGELOG.md CHANGED
@@ -2,6 +2,77 @@
2
2
 
3
3
  Every @humanforest package shares one version, so one entry covers all seven packages. Newest first.
4
4
 
5
+ ## 0.4.0 — 2026-09-15
6
+
7
+ ### Features
8
+
9
+ - **docs**: the llms mirror prints a section kicker under its heading
10
+ - **frames**: FFitFrame fits a slot that sizes itself
11
+ - **charts**: a sparkline's area fades from its line to the floor
12
+ - **charts**: the calendar month rule boxes each month in a toned line
13
+ - **docs**: the llms mirror heads a non-prop table by its own column
14
+ - **docs**: the llms mirror names the module a page destructures
15
+ - **docs**: the llms mirror renders section loops
16
+ - **docs**: the llms mirror resolves plain strings a page imports
17
+ - **docs**: DemoStage puts a demo's controls above the specimen
18
+ - **docs**: the llms mirror names each code tab in a ProseCodeGroup
19
+ - **docs**: the llms mirror renders guidance cards and imported arrays
20
+
21
+ ### Fixes
22
+
23
+ - **ui**: the scroll fade follows content that changes after mount
24
+ - **ui**: map drawing places a corner only for a press on the map
25
+ - **ui**: the scale bar measures at the centre and hides off the globe
26
+ - **ui**: geolocate stays active through a location error while tracking
27
+ - **ui**: map search names only a rendered row in aria-activedescendant
28
+ - **ui**: a context menu that reopens elsewhere takes focus again
29
+ - **ui**: map overlays add layers after the frame, not during placement
30
+ - **ui**: the map context menu wires nothing on a non-interactive map
31
+ - **ui**: map search rings kept focus and never sends a pair to suggest
32
+ - **ui**: fullscreen reads nothing without a fullscreen API
33
+ - **ui**: geolocate is a plain action with track off
34
+ - **ui**: coordinates read Mapbox's 512px world and name the copied pair
35
+ - **docs**: chord, treemap and sankey demos keep their heights
36
+ - **charts**: resolveVar leaves the document alone without a DOM
37
+ - **docs**: the battery-swap stage keeps its start edge reachable
38
+ - **docs**: foundations and layout diagrams fit a phone
39
+ - **docs**: device frames scale down to fit a phone's pane
40
+ - **docs**: component specimens fit a phone's preview pane
41
+ - **filter**: the chip's number segments drop their steppers
42
+ - **docs**: the table page's pager drops its arrows on a phone
43
+ - **filter**: a chip wider than its bar wraps its segments
44
+ - **tokens**: the display role steps down to 4xl below 40rem
45
+ - **tokens**: long inline code wraps instead of overflowing its column
46
+ - **docs**: long inline code and prop types wrap instead of widening main
47
+ - **ui**: a vehicle mark's badge outline follows its edge
48
+ - **ui**: ramp swatches abut and round only the ramp's ends
49
+ - **docs**: a context switch keeps a page's live demos mounted
50
+ - **ui**: a bound popup closes on a second click and unbinds on unmount
51
+ - **ui**: a marker is a button only when it acts as one
52
+ - **ui**: FMap applies every padding side and logs each map error
53
+ - **ui**: a track slice no longer bridges a signal gap
54
+ - **ui**: track playback keeps the playhead when the speed changes
55
+ - **ui**: balance maps paint by magnitude, and legends label exactly
56
+ - **charts**: the tooltip snap looks for its panel on pointer entry
57
+ - **ui**: unavailable reasons name a state by its label
58
+ - **ui**: zones ignore a kind they do not know
59
+ - **ui**: a selected vehicle takes its new state on resync
60
+ - **docs**: a cross-page anchored link lands on its target
61
+ - **ui**: useMapReady never runs for a caller that has unmounted
62
+ - **tokens**: area fills keep their opacity under data-theme dark
63
+ - **charts**: crosshair dots sit on stacked band tops
64
+ - **charts**: a fitted London map labels only the areas it draws
65
+ - **charts**: a missing reading breaks a line sparkline
66
+ - **charts**: a dotted map's missing values add nothing
67
+ - **charts**: a selectable London map is reachable at every size
68
+ - **charts**: cell tracks and grids take one Tab stop, props read live
69
+ - **docs**: urgent vehicles draw on top of the fleet
70
+ - **ui**: alt shortcuts select, and deleted features leave the selection
71
+ - **ui**: a crossing edit springs back, and a drag reports once
72
+ - **ui**: selected markers rise, popups are named, one button opens them
73
+ - **ui**: the coordinate tile stays copyable as the pointer moves onto it
74
+ - **ui**: the scale bar's fill is as long as the distance it names
75
+
5
76
  ## 0.3.7 — 2026-09-13
6
77
 
7
78
  ### Features
@@ -90,36 +90,34 @@
90
90
  --vis-tooltip-title-border-bottom: 1px solid var(--ui-border);
91
91
  --vis-tooltip-label-color: var(--ui-text);
92
92
  --vis-tooltip-value-color: var(--ui-text-highlighted);
93
+ --vis-area-fill-opacity: 1;
93
94
  }
94
95
 
95
- /* Treemap concentric corners. Unovis draws group and leaf tiles as flat sibling rects sharing one
96
- rx, so nesting gets no radius of its own and equal radii read as pinched inner corners. The
97
- concentric rule is inner = outer − gap: with the house 8px corner and 4px padding, leaves take
98
- 4px. Leaves are the only tiles carrying the clickable class (treemapDefaults turns the affordance
96
+ /* Treemap concentric corners. Unovis draws group and leaf tiles as flat sibling rects under one
97
+ corner rule, so nesting gets no radius of its own and equal radii read as pinched inner corners.
98
+ The concentric rule is inner = outer − gap: with the house 12px corner and 4px gap, leaves take
99
+ 8px. Leaves are the only tiles carrying the clickable class (treemapDefaults turns the affordance
99
100
  on for exactly this reason); rx/ry are SVG geometry properties, so CSS overrides the attribute
100
101
  Unovis sets. Matched on the emotion class suffix — Unovis hashes the prefix per build, the label
101
- is stable. Separation comes from the padding gap showing the darker group tile beneath, not from
102
- a stroke: a stroke on every tile double-borders the nesting. */
102
+ is stable. Separation comes from the padding gap, not from a stroke: inside a group the gap shows
103
+ the group tile beneath, and a stroke on every tile double-borders the nesting. */
103
104
  rect[class*="-clickable-tile"] {
104
- rx: calc(var(--vis-treemap-tile-radius, 8px) - var(--vis-treemap-tile-gap, 4px));
105
- ry: calc(var(--vis-treemap-tile-radius, 8px) - var(--vis-treemap-tile-gap, 4px));
105
+ rx: calc(var(--vis-treemap-tile-radius, 12px) - var(--vis-treemap-tile-gap, 4px));
106
+ ry: calc(var(--vis-treemap-tile-radius, 12px) - var(--vis-treemap-tile-gap, 4px));
106
107
  }
107
108
 
108
- /* Re-centre the central label pair. Unovis anchors both texts on the hole's centre and separates
109
- them with dy ±0.55em on each text's OWN font-size, so the block's optical centre always sits
110
- above the true centre — and the gap widens with the label size. Shifting by a fraction of the
111
- label size restores the centring and keeps it correct if a chart overrides the size locally.
112
- dy is an attribute CSS cannot reach; translate composes with the transform attribute Unovis
113
- sets, so it is the one lever here.
109
+ /* Re-centre each donut's central label pair. Unovis anchors both texts on the centre with dy
110
+ ±0.55em on each text's own font size, so the pair sits above the centre; dy is an attribute CSS
111
+ cannot reach, and translate composes with the transform Unovis sets. The flat donut classes both
112
+ texts -central-label and sizes them from --vis-donut-*; the nested donut names its second text
113
+ -central-sub-label and sizes from --vis-nested-donut-*. Both texts of a pair shift equally.
114
114
 
115
- The two components need separate rules because they disagree twice. The flat donut sizes its
116
- label from --vis-donut-*, the nested one from --vis-nested-donut-*, so one shared rule shifts
117
- the nested label by half the FLAT donut's 28px. And they class their texts differently: the flat
118
- donut gives BOTH texts the -central-label suffix, while the nested one names its second
119
- -central-sub-label. Matching only the first suffix therefore moved the nested label down onto a
120
- sub-label that stayed put. Both texts of a pair must shift by the same amount — that is what preserves
121
- their spacing while re-centring the block. (No backticks in this comment: it lives inside a
122
- template literal.) */
115
+ On a top half donut Unovis has already set the pair on the flat edge, so the shift ends the
116
+ sub-label about 11px below it at house sizes. In a box less than twice as wide as it is tall the
117
+ edge sits half the height less a quarter of the width above the SVG's bottom, so the sub-label
118
+ clips once the width passes twice the height less about 46px (474px at the frame's 260). The markup separates that layout from a bottom half donut or a partial-range gauge only
119
+ by numbers in its attributes. (No backticks in this comment: it lives inside a template
120
+ literal.) */
123
121
  g[class*="-donut-component"]:not([class*="-nested-donut-component"]) text[class*="-central-label"] {
124
122
  translate: 0 calc(0.5 * var(--vis-donut-central-label-font-size, 16px));
125
123
  }
@@ -131,8 +129,8 @@ g[class*="-nested-donut-component"] text[class*="-central-sub-label"] {
131
129
 
132
130
  /* Group labels get their own size. Unovis scales label type to tile area for LEAVES only
133
131
  (enableTileLabelFontSizeVariation, gated on the node having no children), so an internal node
134
- keeps the flat default
135
- however large its group is — a zone heading ends up smaller than the station labels inside it.
132
+ keeps the flat default however large its group is, and a group heading can end up smaller than
133
+ the leaf labels inside it.
136
134
  Internal nodes carry their own class, so the heading can be sized here; it has to stay within
137
135
  the tilePaddingTop strip the group reserves for it. */
138
136
  text[class*="-internal-label"] {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humanforest/tokens",
3
- "version": "0.3.7",
3
+ "version": "0.4.0",
4
4
  "description": "Forest design tokens — the OKLCH colour engine (src/colourEngine.ts) plus the CSS + JSON it generates via scripts/{scales,type,motion,deck,dataviz,logo}.ts, and the logo SVGs. The generated files are framework-agnostic; do not edit them by hand, run `bun run build`.",
5
5
  "files": [
6
6
  "CHANGELOG.md",
package/type/type.css CHANGED
@@ -89,6 +89,14 @@
89
89
  --type-overline-transform: uppercase;
90
90
  }
91
91
 
92
+ /* phone widths, below Tailwind's sm: a single long word in display Mohr is wider than the column and Mohr
93
+ does not hyphenate, so the role steps down the ramp rather than break the word */
94
+ @media (width < 40rem) {
95
+ :root {
96
+ --type-display-size: 2.25rem;
97
+ }
98
+ }
99
+
92
100
  /* role utility classes — set the whole bundle in one class (text-transform baked in for Mohr/overline) */
93
101
  .type-display {
94
102
  font-family: var(--type-display-family);
@@ -213,13 +221,15 @@
213
221
  }
214
222
  /* Inline <code> renders as a boxed chip — the system's inline-code treatment, distinct from the bare
215
223
  .type-mono treatment (which is for data: hex, token names, numbers, never boxed). Use <code> for
216
- actual code/syntax in prose. Shiki blocks (pre code) opt out below. box-decoration-break keeps the
217
- box intact when inline code wraps across lines. */
224
+ actual code/syntax in prose. Shiki blocks (pre code) opt out below. A long unbroken token (a file
225
+ path, a generic) breaks anywhere rather than overflowing its column, and box-decoration-break keeps
226
+ the box intact on every line it wraps across. */
218
227
  :not(pre) > code {
219
228
  background: var(--ui-bg-elevated);
220
229
  border: 1px solid var(--ui-border-muted);
221
230
  border-radius: 0.25rem;
222
231
  padding: 0.05em 0.35em;
232
+ overflow-wrap: anywhere;
223
233
  box-decoration-break: clone;
224
234
  -webkit-box-decoration-break: clone;
225
235
  }
package/type/type.json CHANGED
@@ -228,6 +228,7 @@
228
228
  "transform": "uppercase",
229
229
  "italicCut": true,
230
230
  "fluid": "clamp(2.25rem, 1.5rem + 3vw, 4.5rem)",
231
+ "narrow": "4xl",
231
232
  "cssVar": "--type-display",
232
233
  "class": "type-display",
233
234
  "contexts": {
@@ -242,7 +243,7 @@
242
243
  "marketing": 1,
243
244
  "deck": 1
244
245
  },
245
- "note": "Hero statement, the big Mohr moment. Marketing keeps the full fluid clamp; product web trims it away."
246
+ "note": "Hero statement, the big Mohr moment. Marketing keeps the full fluid clamp; product web trims it away. Below 40rem it steps down to 4xl, because a single long word in Mohr is wider than a phone column and Mohr does not hyphenate."
246
247
  },
247
248
  {
248
249
  "name": "headline",
@@ -258,6 +259,7 @@
258
259
  "transform": "uppercase",
259
260
  "italicCut": true,
260
261
  "fluid": null,
262
+ "narrow": null,
261
263
  "cssVar": "--type-headline",
262
264
  "class": "type-headline",
263
265
  "contexts": {
@@ -288,6 +290,7 @@
288
290
  "transform": "none",
289
291
  "italicCut": false,
290
292
  "fluid": null,
293
+ "narrow": null,
291
294
  "cssVar": "--type-title",
292
295
  "class": "type-title",
293
296
  "contexts": {
@@ -318,6 +321,7 @@
318
321
  "transform": "none",
319
322
  "italicCut": false,
320
323
  "fluid": null,
324
+ "narrow": null,
321
325
  "cssVar": "--type-heading-sm",
322
326
  "class": "type-heading-sm",
323
327
  "contexts": {
@@ -348,6 +352,7 @@
348
352
  "transform": "none",
349
353
  "italicCut": false,
350
354
  "fluid": null,
355
+ "narrow": null,
351
356
  "cssVar": "--type-heading-xs",
352
357
  "class": "type-heading-xs",
353
358
  "contexts": {
@@ -378,6 +383,7 @@
378
383
  "transform": "none",
379
384
  "italicCut": false,
380
385
  "fluid": null,
386
+ "narrow": null,
381
387
  "cssVar": "--type-subtitle",
382
388
  "class": "type-subtitle",
383
389
  "contexts": {
@@ -408,6 +414,7 @@
408
414
  "transform": "none",
409
415
  "italicCut": false,
410
416
  "fluid": null,
417
+ "narrow": null,
411
418
  "cssVar": "--type-body",
412
419
  "class": "type-body",
413
420
  "contexts": {
@@ -438,6 +445,7 @@
438
445
  "transform": "none",
439
446
  "italicCut": false,
440
447
  "fluid": null,
448
+ "narrow": null,
441
449
  "cssVar": "--type-body-sm",
442
450
  "class": "type-body-sm",
443
451
  "contexts": {
@@ -468,6 +476,7 @@
468
476
  "transform": "none",
469
477
  "italicCut": false,
470
478
  "fluid": null,
479
+ "narrow": null,
471
480
  "cssVar": "--type-label",
472
481
  "class": "type-label",
473
482
  "contexts": {
@@ -498,6 +507,7 @@
498
507
  "transform": "none",
499
508
  "italicCut": false,
500
509
  "fluid": null,
510
+ "narrow": null,
501
511
  "cssVar": "--type-caption",
502
512
  "class": "type-caption",
503
513
  "contexts": {
@@ -528,6 +538,7 @@
528
538
  "transform": "uppercase",
529
539
  "italicCut": false,
530
540
  "fluid": null,
541
+ "narrow": null,
531
542
  "cssVar": "--type-overline",
532
543
  "class": "type-overline",
533
544
  "contexts": {