@felloinnovations/brand-kit 0.5.1 → 0.5.3

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 (104) hide show
  1. package/AGENTS.md +29 -9
  2. package/CHANGELOG.md +86 -0
  3. package/README.md +10 -4
  4. package/design-system/01-how-claude-decides.md +5 -3
  5. package/design-system/03-colour.md +19 -1
  6. package/design-system/05-space-and-layout.md +2 -1
  7. package/design-system/08-choosing-components.md +20 -4
  8. package/design-system/09-charts.md +6 -3
  9. package/design-system/11-page-recipes.md +40 -1
  10. package/design-system/13-motion.md +1 -1
  11. package/design-system/15-ai-experiences.md +11 -0
  12. package/design-system/17-decisions.md +41 -0
  13. package/design-system/components/AppShell/README.md +89 -21
  14. package/design-system/components/AppShell/preview.html +148 -3
  15. package/design-system/components/AppShell/style.css +47 -0
  16. package/design-system/components/Avatar/README.md +6 -3
  17. package/design-system/components/Avatar/preview.html +2 -1
  18. package/design-system/components/Avatar/style.css +3 -0
  19. package/design-system/components/Banner/README.md +9 -5
  20. package/design-system/components/Banner/preview.html +9 -0
  21. package/design-system/components/Banner/style.css +3 -0
  22. package/design-system/components/Button/README.md +29 -2
  23. package/design-system/components/Button/preview.html +33 -1
  24. package/design-system/components/Button/style.css +21 -0
  25. package/design-system/components/Calendar/README.md +148 -0
  26. package/design-system/components/Calendar/preview.html +449 -0
  27. package/design-system/components/Calendar/style.css +61 -0
  28. package/design-system/components/Card/README.md +13 -0
  29. package/design-system/components/Card/preview.html +23 -0
  30. package/design-system/components/Card/style.css +19 -0
  31. package/design-system/components/ChatCard/README.md +74 -3
  32. package/design-system/components/ChatCard/preview.html +126 -25
  33. package/design-system/components/ChatCard/style.css +6 -0
  34. package/design-system/components/Chip/README.md +16 -7
  35. package/design-system/components/Chip/preview.html +25 -1
  36. package/design-system/components/Chip/style.css +5 -0
  37. package/design-system/components/Combobox/README.md +10 -4
  38. package/design-system/components/Combobox/preview.html +19 -4
  39. package/design-system/components/Combobox/style.css +7 -1
  40. package/design-system/components/CommentThread/README.md +138 -0
  41. package/design-system/components/CommentThread/preview.html +134 -0
  42. package/design-system/components/CommentThread/style.css +41 -0
  43. package/design-system/components/DateInput/README.md +38 -4
  44. package/design-system/components/DateInput/preview.html +50 -2
  45. package/design-system/components/DateInput/style.css +6 -0
  46. package/design-system/components/Dialog/README.md +26 -3
  47. package/design-system/components/Dialog/preview.html +80 -0
  48. package/design-system/components/Dialog/style.css +28 -3
  49. package/design-system/components/EmptyState/README.md +1 -1
  50. package/design-system/components/Fold/README.md +21 -0
  51. package/design-system/components/Fold/preview.html +44 -1
  52. package/design-system/components/Fold/style.css +30 -0
  53. package/design-system/components/InlineEdit/README.md +116 -0
  54. package/design-system/components/InlineEdit/preview.html +135 -0
  55. package/design-system/components/InlineEdit/style.css +34 -0
  56. package/design-system/components/Input/README.md +20 -1
  57. package/design-system/components/Input/preview.html +24 -0
  58. package/design-system/components/Input/style.css +13 -1
  59. package/design-system/components/KanbanBoard/README.md +68 -4
  60. package/design-system/components/KanbanBoard/preview.html +129 -0
  61. package/design-system/components/KanbanBoard/style.css +31 -0
  62. package/design-system/components/PlanTimeline/README.md +144 -0
  63. package/design-system/components/PlanTimeline/preview.html +109 -0
  64. package/design-system/components/PlanTimeline/style.css +64 -0
  65. package/design-system/components/README.md +11 -6
  66. package/design-system/components/Skeleton/README.md +66 -2
  67. package/design-system/components/Skeleton/preview.html +83 -1
  68. package/design-system/components/Starter/README.md +1 -1
  69. package/design-system/components/Starter/preview.html +1 -1
  70. package/design-system/components/Stepper/README.md +81 -5
  71. package/design-system/components/Stepper/preview.html +64 -1
  72. package/design-system/components/Stepper/style.css +29 -0
  73. package/design-system/components/StreakCalendar/README.md +1 -0
  74. package/design-system/components/Table/README.md +7 -3
  75. package/design-system/components/Table/preview.html +15 -0
  76. package/design-system/components/Table/style.css +2 -0
  77. package/design-system/components/TextDiff/README.md +96 -0
  78. package/design-system/components/TextDiff/preview.html +42 -0
  79. package/design-system/components/TextDiff/style.css +21 -0
  80. package/design-system/components/Timeline/README.md +3 -1
  81. package/design-system/components/TrendLine/README.md +38 -7
  82. package/design-system/components/TrendLine/preview.html +140 -3
  83. package/design-system/components/TrendLine/style.css +12 -2
  84. package/design-system/components/bundle.css +483 -7
  85. package/design-system/tokens.json +15 -1
  86. package/dist/fello-core.css +2 -2
  87. package/dist/fello.css +2 -2
  88. package/dist/fello.scoped.css +2 -2
  89. package/dist/index.d.ts +1 -1
  90. package/dist/scoped.js +2 -2
  91. package/dist/tokens.d.ts +8 -0
  92. package/dist/tokens.js +8 -0
  93. package/package.json +7 -2
  94. package/scripts/check-drift.mjs +325 -0
  95. package/scripts/token-model.mjs +2 -1
  96. package/tokens/_dark.css +2 -0
  97. package/tokens/_product.css +2 -0
  98. package/tokens/palettes.ts +1 -1
  99. package/tokens/shadcn-compat.css +1 -1
  100. package/tokens/shadcn-compat.hsl.css +1 -1
  101. package/tokens/tokens.css +14 -1
  102. package/tokens/tokens.md +53 -46
  103. package/tokens/tokens.root.css +14 -1
  104. package/tokens/tokens.ts +9 -1
@@ -1,16 +1,23 @@
1
- Ordered steps on a line that fills: done is a tick, the one in hand is ringed, the rest wait their turn.
1
+ Ordered steps on a line that fills: done is a tick, the one in hand is ringed, the rest wait their turn. Down the page
2
+ by default; `--across` lays the stages left to right; `--milestones` makes them dated points on one row.
2
3
 
3
4
  ## Use it when
4
5
 
5
6
  - Something happens in a known sequence and the person benefits from seeing where it is (a setup's path, an onboarding
6
7
  checklist, an audit).
7
8
  - Several things run at once rather than in order (a scan's sources), and the page needs to say which are being read right now.
9
+ - One thing's stages under its title (an onboarding's Scheduling to Graduated, a deal's stages): `--across`.
10
+ - One thing's dated milestones, each reached or not, measured from the start (signed, first value, first agreement):
11
+ `--milestones`. A variant, not its own component: it is the same marks, line and done or waiting states on a row.
8
12
 
9
13
  ## Not for
10
14
 
11
15
  - A share of a goal or a percent: use `GoalTicks` or `Ring`.
12
16
  - A count out of a whole: use `TickRow`.
13
17
  - A plain loading wait with no named steps: a working chip or spinner, not a `Stepper`.
18
+ - What happened, in order, grouped by day: use `Timeline`.
19
+ - Many things across their stages at once: use `KanbanBoard`.
20
+ - Phases and tasks as bars across dates (a plan): not in the kit yet (issue #24).
14
21
 
15
22
  ## Anatomy
16
23
 
@@ -20,6 +27,17 @@ Ordered steps on a line that fills: done is a tick, the one in hand is ringed, t
20
27
  - Waiting: the cream with the fine hairline ring (drawn inside), muted grey number.
21
28
  - Connecting line: a 1 px (`calc(--ws-hairline * 2)`) line between marks in `--ws-edge`, filled teal top to bottom when its step above is done.
22
29
  - Title: 14 / 20 medium navy, its first line level with the mark; a note under it in `ws-muted` (how far, or "Done").
30
+ - A stall (`__late` on the note, with `ws-muted`): the note in needs-you words (`--ws-word-needs`), never a colour on
31
+ the mark: the in-hand ring stays navy.
32
+ - Across (`--across`): equal columns, each at least 120 (`--space-7xl`), the mark centred over its words, 8 between
33
+ them; the line runs at the mark's middle from its right edge to the next mark's left edge and fills left to right
34
+ (`ws-grow`). Wider than its column, the track scrolls inside and fades at the edge that hides stages (the house
35
+ `ws-scroll-edges` cue), and the in-hand stage opens in view (`scroll-initial-target`).
36
+ - Milestones (`--milestones`): an 8 px dot, 8 to the words, the name as the title and its measure beside it in
37
+ `ws-muted` (a date, or days since the start), on one line; the line takes the room left between the words and the
38
+ next dot, 8 from each. Reached is the teal dot; not reached a 1 px ring (`--ws-edge-card`) with "Not yet", and at
39
+ most one quiet act beside it (`ws-btn ws-btn--text`). The line after a milestone fills when the next is reached. 4 px
40
+ of room on every side, pulled back by a margin, so the act's focus ring is never cut.
23
41
 
24
42
  ## Variants and sizes
25
43
 
@@ -27,12 +45,18 @@ Ordered steps on a line that fills: done is a tick, the one in hand is ringed, t
27
45
  | --- | --- |
28
46
  | In order (default) | The first step not done is the one in hand, found automatically |
29
47
  | Side by side (`active` set on any step) | Only steps with `active: true` are in hand (several at once); none active means none are in hand, every undone step simply waits |
48
+ | Across (`ws-stepper--across`) | The same steps left to right, a mark over its words; for a thing's stages under its title |
49
+ | Milestones (`ws-stepper--milestones`) | Dated points on one row: dots, no numbers, no in-hand ring; each reached or not yet |
30
50
 
31
51
  ## States
32
52
 
33
53
  - Default: the first undone step is in hand, ringed in navy; everything after it waits.
34
54
  - In hand (parallel): rings navy and breathes on `ws-working`, because something is actually in progress right now.
35
55
  - Waiting (parallel, nothing active): every undone step sits quiet, no ring, no breathing; nothing has started reading yet.
56
+ - Stalled: the in-hand step has sat past its expected time. Its note says so with the time, in needs-you words
57
+ ("Stalled 40 days"); the ring stays navy and nothing breathes.
58
+ - Late (milestones): reached past its target; the measure says by how much, in needs-you words ("52 days, 7 late").
59
+ - Not yet (milestones): the ring dot, "Not yet", and the one act if the person can log it ("Add date").
36
60
  - Done: a filled teal circle with a check, popping in (`ws-pop`) the moment it completes, and the line below it filling down (`ws-grow-up`) the same moment.
37
61
  - Disabled / error / hover / focus / pressed / selected: do not apply; a step is a status, not a control.
38
62
  - Reduced motion: ticks, rings and the filling line are all simply in their final state, no pop, breathe or growth.
@@ -46,26 +70,36 @@ Ordered steps on a line that fills: done is a tick, the one in hand is ringed, t
46
70
 
47
71
  ## Content
48
72
 
49
- - A step's title says what it does, not just its name ("Writing the three recommendations", not "Step 3").
73
+ - A step's title says what it does, not just its name ("Writing the three recommendations", not "Step 3"). A stage's
74
+ title is the stage's own name, sentence case ("Review and handoff").
50
75
  - The note under an in-hand step says how far ("2 of 4") or what is happening now ("Reading now"); a waiting step carries no note.
76
+ - A stall or a late milestone says the time in words, never a bare "40d" or a colour alone.
77
+ - A milestone's name is plain words, not an acronym the reader must know ("First value", not "TTV1"); its measure is a
78
+ date ("Sep 11") or days since the start ("15 days").
51
79
 
52
80
  ## Props
53
81
 
54
82
  | Name | Type | Meaning |
55
83
  | --- | --- | --- |
56
84
  | `label` | `string` | Names the whole sequence for the `<ol>` |
57
- | `steps` | `{ key, title, note?, done, active? }[]` | Each step; `active` turns on the side-by-side mode across the whole list |
85
+ | `steps` | `{ key, title, note?, done, active?, late? }[]` | Each step; `active` turns on the side-by-side mode across the whole list; `late` puts the note in needs-you words |
86
+ | `layout` | `"down" \| "across" \| "milestones"` | Down the page (default), stages left to right, or dated milestones on a row |
58
87
 
59
88
  ## Rules
60
89
 
61
90
  - Sequences and waits are steps; do not reach for a progress bar or a percent where a named sequence says it plainer.
62
91
  - The in-hand ring is always navy; a step never borrows a chart's tone (teal, salmon, purple) for its own status beyond "done".
92
+ A stall or a late milestone is said in needs-you words, not painted on the mark.
93
+ - Across needs room for every stage at 120; on a phone it scrolls with the in-hand stage in view. Where every stage
94
+ must show at once in a narrow column, use the default, down the page.
95
+ - One act at most on a milestone row, on a milestone not yet reached; a step or a milestone itself never takes a click.
63
96
  - A step that cannot start yet (no data, blocked) still appears in its place, waiting; it is never removed from the list.
64
97
 
65
98
  ## Source
66
99
 
67
100
  **Derived from**: the brand kit's own guidance that sequences and waits are drawn as steps, not a progress bar
68
- (21 Oct 2026). **Precedent**: the first product built on the system.
101
+ (21 Oct 2026). **Precedent**: the first product built on the system. `--across` and `--milestones`: the Customer
102
+ Value onboarding detail (Hemanth, 10 Nov 2026), whose stage track and milestone row were built by hand for want of them.
69
103
 
70
104
  ## Markup
71
105
 
@@ -83,7 +117,49 @@ Ordered steps on a line that fills: done is a tick, the one in hand is ringed, t
83
117
  </ol>
84
118
  ```
85
119
 
120
+ Across: a thing's stages under its title (the line under a done stage fills; the stalled note under the in-hand one).
121
+
122
+ ```html
123
+ <ol class="ws-stepper ws-stepper--across" aria-label="Onboarding stages">
124
+ <li class="ws-stepper__item">
125
+ <span class="ws-stepper__line"><span class="ws-stepper__line-fill ws-grow"></span></span>
126
+ <span class="ws-stepper__mark ws-stepper__mark--done" aria-hidden="true"><svg viewBox="0 0 16 16" width="12" height="12" fill="none"><path d="M3.5 8.5L6.5 11.5L12.5 5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
127
+ <span class="ws-stepper__body"><span class="ws-stepper__title">Optimizing</span></span>
128
+ </li>
129
+ <li class="ws-stepper__item" aria-current="step">
130
+ <span class="ws-stepper__line"></span>
131
+ <span class="ws-stepper__mark ws-stepper__mark--current" aria-hidden="true">5</span>
132
+ <span class="ws-stepper__body"><span class="ws-stepper__title">Review and handoff</span><span class="ws-muted ws-stepper__late">Stalled 40 days</span></span>
133
+ </li>
134
+ <li class="ws-stepper__item">
135
+ <span class="ws-stepper__mark ws-stepper__mark--wait" aria-hidden="true">6</span>
136
+ <span class="ws-stepper__body"><span class="ws-stepper__title">Graduated</span></span>
137
+ </li>
138
+ </ol>
139
+ ```
140
+
141
+ Milestones: reached, late and not yet, with the one act.
142
+
143
+ ```html
144
+ <ol class="ws-stepper ws-stepper--milestones" aria-label="Milestones">
145
+ <li class="ws-stepper__item">
146
+ <span class="ws-stepper__line"><span class="ws-stepper__line-fill ws-grow"></span></span>
147
+ <span class="ws-stepper__mark ws-stepper__mark--done" aria-hidden="true"></span>
148
+ <span class="ws-stepper__body"><span class="ws-stepper__title">Signed</span><span class="ws-muted">Sep 11</span></span>
149
+ </li>
150
+ <li class="ws-stepper__item">
151
+ <span class="ws-stepper__line"></span>
152
+ <span class="ws-stepper__mark ws-stepper__mark--done" aria-hidden="true"></span>
153
+ <span class="ws-stepper__body"><span class="ws-stepper__title">First agreement</span><span class="ws-muted ws-stepper__late">52 days, 7 late</span></span>
154
+ </li>
155
+ <li class="ws-stepper__item">
156
+ <span class="ws-stepper__mark ws-stepper__mark--wait" aria-hidden="true"></span>
157
+ <span class="ws-stepper__body"><span class="ws-stepper__title">Graduated</span><span class="ws-muted">Not yet</span><button type="button" class="ws-btn ws-btn--text" aria-label="Add the graduation date">Add date</button></span>
158
+ </li>
159
+ </ol>
160
+ ```
161
+
86
162
  ## Styles
87
163
 
88
164
  `design-system/components/Stepper/style.css`: `ws-stepper`, `__item`, `__line`, `__line-fill`, `__mark` with `--done`,
89
- `--current`, `--wait`; `__body`, `__title`.
165
+ `--current`, `--wait`; `__body`, `__title`, `__late`; layouts `ws-stepper--across` and `ws-stepper--milestones`.
@@ -1,4 +1,4 @@
1
- <!-- @dsCard group="Progress" height=314 -->
1
+ <!-- @dsCard group="Progress" height=520 -->
2
2
  <!doctype html>
3
3
  <html>
4
4
  <head>
@@ -104,6 +104,69 @@
104
104
  </ol>
105
105
  </div>
106
106
 
107
+ <div class="ws-stack" style="width: 100%;">
108
+ <span class="ws-muted">Across: four stages done, the fifth in hand and stalled, the last to come</span>
109
+ <ol class="ws-stepper ws-stepper--across" aria-label="Onboarding stages">
110
+ <li class="ws-stepper__item">
111
+ <span class="ws-stepper__line"><span class="ws-stepper__line-fill ws-grow"></span></span>
112
+ <span class="ws-stepper__mark ws-stepper__mark--done" aria-hidden="true"><svg viewBox="0 0 16 16" width="12" height="12" fill="none"><path d="M3.5 8.5L6.5 11.5L12.5 5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
113
+ <span class="ws-stepper__body"><span class="ws-stepper__title">Scheduling</span></span>
114
+ </li>
115
+ <li class="ws-stepper__item">
116
+ <span class="ws-stepper__line"><span class="ws-stepper__line-fill ws-grow" style="animation-delay: 30ms;"></span></span>
117
+ <span class="ws-stepper__mark ws-stepper__mark--done" aria-hidden="true"><svg viewBox="0 0 16 16" width="12" height="12" fill="none"><path d="M3.5 8.5L6.5 11.5L12.5 5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
118
+ <span class="ws-stepper__body"><span class="ws-stepper__title">Implementing</span></span>
119
+ </li>
120
+ <li class="ws-stepper__item">
121
+ <span class="ws-stepper__line"><span class="ws-stepper__line-fill ws-grow" style="animation-delay: 60ms;"></span></span>
122
+ <span class="ws-stepper__mark ws-stepper__mark--done" aria-hidden="true"><svg viewBox="0 0 16 16" width="12" height="12" fill="none"><path d="M3.5 8.5L6.5 11.5L12.5 5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
123
+ <span class="ws-stepper__body"><span class="ws-stepper__title">Enabling</span></span>
124
+ </li>
125
+ <li class="ws-stepper__item">
126
+ <span class="ws-stepper__line"><span class="ws-stepper__line-fill ws-grow" style="animation-delay: 90ms;"></span></span>
127
+ <span class="ws-stepper__mark ws-stepper__mark--done" aria-hidden="true"><svg viewBox="0 0 16 16" width="12" height="12" fill="none"><path d="M3.5 8.5L6.5 11.5L12.5 5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
128
+ <span class="ws-stepper__body"><span class="ws-stepper__title">Optimizing</span></span>
129
+ </li>
130
+ <li class="ws-stepper__item" aria-current="step">
131
+ <span class="ws-stepper__line"></span>
132
+ <span class="ws-stepper__mark ws-stepper__mark--current" aria-hidden="true">5</span>
133
+ <span class="ws-stepper__body">
134
+ <span class="ws-stepper__title">Review and handoff</span>
135
+ <span class="ws-muted ws-stepper__late">Stalled 40 days</span>
136
+ </span>
137
+ </li>
138
+ <li class="ws-stepper__item">
139
+ <span class="ws-stepper__mark ws-stepper__mark--wait" aria-hidden="true">6</span>
140
+ <span class="ws-stepper__body"><span class="ws-stepper__title">Graduated</span></span>
141
+ </li>
142
+ </ol>
143
+ </div>
144
+
145
+ <div class="ws-stack" style="width: 100%;">
146
+ <span class="ws-muted">Milestones: three reached (one late), the last not yet</span>
147
+ <ol class="ws-stepper ws-stepper--milestones" aria-label="Milestones">
148
+ <li class="ws-stepper__item">
149
+ <span class="ws-stepper__line"><span class="ws-stepper__line-fill ws-grow"></span></span>
150
+ <span class="ws-stepper__mark ws-stepper__mark--done" aria-hidden="true"></span>
151
+ <span class="ws-stepper__body"><span class="ws-stepper__title">Signed</span><span class="ws-muted">Sep 11</span></span>
152
+ </li>
153
+ <li class="ws-stepper__item">
154
+ <span class="ws-stepper__line"><span class="ws-stepper__line-fill ws-grow" style="animation-delay: 30ms;"></span></span>
155
+ <span class="ws-stepper__mark ws-stepper__mark--done" aria-hidden="true"></span>
156
+ <span class="ws-stepper__body"><span class="ws-stepper__title">First value</span><span class="ws-muted">15 days</span></span>
157
+ </li>
158
+ <li class="ws-stepper__item">
159
+ <span class="ws-stepper__line"></span>
160
+ <span class="ws-stepper__mark ws-stepper__mark--done" aria-hidden="true"></span>
161
+ <span class="ws-stepper__body"><span class="ws-stepper__title">First agreement</span><span class="ws-muted ws-stepper__late">52 days, 7 late</span></span>
162
+ </li>
163
+ <li class="ws-stepper__item">
164
+ <span class="ws-stepper__mark ws-stepper__mark--wait" aria-hidden="true"></span>
165
+ <span class="ws-stepper__body"><span class="ws-stepper__title">Graduated</span><span class="ws-muted">Not yet</span><button type="button" class="ws-btn ws-btn--text" aria-label="Add the graduation date">Add date</button></span>
166
+ </li>
167
+ </ol>
168
+ </div>
169
+
107
170
  </div>
108
171
  </body>
109
172
  </html>
@@ -11,3 +11,32 @@
11
11
  .ws-stepper__body { display: flex; flex: 1; min-width: 0; flex-direction: column; padding-top: var(--space-3xs); }
12
12
  .ws-stepper__title { font-size: var(--ws-text-body); line-height: calc(var(--space-md) + var(--space-xxs)); font-weight: 500; color: var(--color-text-heading); }
13
13
 
14
+ /* A stall in words: the in-hand step's note when it has sat too long ("40 days in this stage"), and a milestone past its target. */
15
+ .ws-stepper__late { color: var(--ws-word-needs); }
16
+ /* Across: stages left to right, each mark over its words, the line running mark to mark. Equal columns (so the line
17
+ can reach the next mark's centre from its own item), each at least 120; past that the track scrolls inside and fades
18
+ at the edge that hides stages, like Segmented's stages. */
19
+ .ws-stepper--across { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(var(--space-7xl), 1fr); overflow-x: auto; scrollbar-width: none; }
20
+ .ws-stepper--across::-webkit-scrollbar { display: none; }
21
+ .ws-stepper--across > .ws-stepper__item { flex-direction: column; align-items: center; gap: var(--space-xs); padding: 0 var(--space-xxs); text-align: center; }
22
+ .ws-stepper--across .ws-stepper__line { top: calc(var(--space-lg) / 2 - var(--ws-hairline)); bottom: auto; left: calc(50% + var(--space-lg) / 2); right: calc(var(--space-lg) / 2 - 50%); width: auto; height: calc(var(--ws-hairline) * 2); }
23
+ .ws-stepper--across .ws-stepper__line-fill { transform-origin: left; }
24
+ .ws-stepper--across .ws-stepper__body { align-items: center; padding-top: 0; }
25
+ .ws-stepper--across .ws-stepper__title { text-wrap: balance; }
26
+ /* The in-hand stage opens in view on a track that scrolls (a phone): the browser scrolls to it once, on load. */
27
+ .ws-stepper--across > [aria-current="step"] { scroll-snap-align: center; scroll-initial-target: nearest; }
28
+ /* Milestones: dated points on one row, an 8 px dot and its words, the line between them taking the room left. The
29
+ measure sits on the milestone (a date, or days since the start), so the line carries no words. 4 px of room on every
30
+ side, pulled back by a margin, so an act's focus ring and a text button's bleed are never cut by the scroll box. */
31
+ .ws-stepper--milestones { flex-direction: row; gap: var(--space-xs); overflow-x: auto; scrollbar-width: none; padding: var(--space-xxs); margin: calc(var(--space-xxs) * -1); }
32
+ .ws-stepper--milestones::-webkit-scrollbar { display: none; }
33
+ .ws-stepper--milestones > .ws-stepper__item { flex: 1 0 auto; align-items: center; gap: var(--space-xs); padding: 0; }
34
+ .ws-stepper--milestones > .ws-stepper__item:last-child { flex-grow: 0; }
35
+ .ws-stepper--milestones .ws-stepper__line { position: relative; inset: auto; order: 1; flex: 1 0 var(--space-lg); width: auto; height: calc(var(--ws-hairline) * 2); }
36
+ .ws-stepper--milestones .ws-stepper__line-fill { transform-origin: left; }
37
+ .ws-stepper--milestones .ws-stepper__mark { width: var(--space-xs); height: var(--space-xs); }
38
+ .ws-stepper--milestones .ws-stepper__mark--wait { box-shadow: inset 0 0 0 calc(var(--ws-hairline) * 2) var(--ws-edge-card); }
39
+ .ws-stepper--milestones .ws-stepper__body { flex: 0 0 auto; flex-direction: row; align-items: baseline; gap: var(--space-xxs-plus); padding-top: 0; white-space: nowrap; }
40
+ @supports (animation-timeline: scroll()) {
41
+ .ws-stepper--across, .ws-stepper--milestones { mask-image: linear-gradient(90deg, transparent, currentColor var(--ws-fade-start), currentColor calc(100% - var(--ws-fade-end)), transparent); animation: ws-scroll-edges linear; animation-timeline: scroll(self inline); }
42
+ }
@@ -9,6 +9,7 @@ Eight weeks of activity as rows, Monday to Sunday across: a done day a pill, an
9
9
  **Not for**
10
10
 
11
11
  - Several people's attendance at once: use `ActivityGrid`.
12
+ - Things that fall on a day (posts, tasks, sends) to open from their date: use `Calendar`.
12
13
  - A count out of a whole with no date axis: use `TickRow` or `GoalTicks`.
13
14
  - A trend line of a number changing day to day: this chart never plots a value, only whether a day happened.
14
15
 
@@ -25,7 +25,9 @@ a phone fold. This is the whole wide-table pattern; build every records table fr
25
25
  and takes the card's corners). A wide table scrolls inside its card, never the page.
26
26
  - Every cell: 8 px above and below (`--space-xs`), 12 at the sides (`--space-sm`), one line (`white-space: nowrap`, so
27
27
  a squeezed table scrolls instead of breaking every cell to a word a line). A cell that may wrap takes `ws-table__wrap`
28
- (at least 200 wide).
28
+ (at least 200 wide: one long column of words); several short prose columns (a note, a reason, a description) take
29
+ `ws-table__prose`, which wraps at whatever width the column gets, with no floor, so five columns still fit the card.
30
+ Put either on the `th` and every `td` of the column.
29
31
  - The outer cells sit on the card's 20 px edge (`--ws-card-inset`): the first cell's left and the last cell's right, so
30
32
  the table lines up with the card's head, band and pagination.
31
33
  - Head: rows at least 40 (`--ws-row-h-dense`), 13 / 18 medium muted labels (`--ws-text-control`, `--color-text-muted`),
@@ -120,7 +122,7 @@ a phone fold. This is the whole wide-table pattern; build every records table fr
120
122
  "Rows vs table"), and it always shows that it scrolls: the shade and the fade.
121
123
  - **One content edge per card**: the first and last cells sit on the card's 20 px edge, like its head (K8).
122
124
  - **Numbers line up through fixed numeric columns** (North, A4), right-aligned, tabular, the `th` included.
123
- - **Every row is one line**: a squeezed table scrolls; only a cell marked `__wrap` breaks, and under 30rem the stuck
125
+ - **Every row is one line**: a squeezed table scrolls; only a cell marked `__wrap` or `__prose` breaks, and under 30rem the stuck
124
126
  name, which would otherwise leave no room for the columns sliding under it.
125
127
  - **A selected row never changes colour alone**: the navy mark travels with the tint.
126
128
  - **Row acts are always in the DOM**: hover-to-reveal is cosmetic only.
@@ -173,5 +175,7 @@ columns with no cue, cells 12 in against the card's 20, no vertical padding, no
173
175
 
174
176
  `design-system/components/Table/style.css`, in `bundle.css` and `fello.css`. Light, dark and the navy ground come from
175
177
  the tokens (`--color-bg-page`, `--ws-surface-sunk`, `--ws-hover-faint`, `--ws-divider`); a table on another surface
176
- sets `--ws-table-ground`. The card is a size container named `ws-table`, so it needs the width of its column (a card in
178
+ sets `--ws-table-ground` to that surface's token, on the `ws-table` element: a white card or panel
179
+ `var(--color-surface)`, a raised panel or menu `var(--ws-raised)`, a plate `var(--ws-surface-sunk)`. A product card
180
+ is the cream already, so it needs nothing. The card is a size container named `ws-table`, so it needs the width of its column (a card in
177
181
  a shrink-to-fit row collapses: give it a width). The edge cues use scroll-driven animations where the browser has them.
@@ -166,6 +166,21 @@
166
166
  </section>
167
167
  </div>
168
168
 
169
+ <div class="ws-stack" style="width: min(100%, 560px);">
170
+ <span class="ws-muted">Short prose columns (__prose): each wraps at the width its column gets, with no 200 px floor; on a phone the rest still scrolls</span>
171
+ <section class="ws-card ws-table" aria-label="Ads review">
172
+ <div class="ws-table__scroll">
173
+ <table>
174
+ <thead><tr><th scope="col" class="ws-table__name">Ad</th><th scope="col" class="ws-table__prose">Reason</th><th scope="col" class="ws-table__prose">Note</th><th scope="col" class="ws-table__num">Spend</th></tr></thead>
175
+ <tbody>
176
+ <tr><td class="ws-table__name">Spring open house</td><td class="ws-table__prose">The image is mostly words</td><td class="ws-table__prose">Swap in the porch photo</td><td class="ws-table__num">$420</td></tr>
177
+ <tr><td class="ws-table__name">First-time buyers</td><td class="ws-table__prose">Approved</td><td class="ws-table__prose">Runs through Friday</td><td class="ws-table__num">$1,180</td></tr>
178
+ </tbody>
179
+ </table>
180
+ </div>
181
+ </section>
182
+ </div>
183
+
169
184
  <div class="ws-stack" style="width: 300px;">
170
185
  <span class="ws-muted">Empty, no matches: the same card, the act that clears the filter</span>
171
186
  <section class="ws-card ws-table" aria-label="Leads, no matches">
@@ -19,6 +19,8 @@
19
19
  .ws-table tr > :first-child { padding-left: var(--ws-card-inset); }
20
20
  .ws-table tr > :last-child { padding-right: var(--ws-card-inset); }
21
21
  .ws-table .ws-table__wrap { white-space: normal; min-width: var(--space-9xl); }
22
+ /* A short prose cell (a note, a reason) that wraps at whatever width its column gets, with no floor (#21). */
23
+ .ws-table .ws-table__prose { white-space: normal; text-wrap: pretty; }
22
24
  /* Head: 13 / 18 medium muted labels, stuck to the top of the scroller. */
23
25
  .ws-table thead th { position: sticky; top: 0; z-index: 2; height: var(--ws-row-h-dense); font-size: var(--ws-text-control); line-height: var(--ws-leading-control); font-weight: 500; color: var(--color-text-muted); border-bottom: var(--ws-divider); }
24
26
  .ws-table thead th[aria-sort="ascending"], .ws-table thead th[aria-sort="descending"] { color: var(--color-text-heading); }
@@ -0,0 +1,96 @@
1
+ Two versions of a text side by side in one place: what was removed and what was added, by line or inside the words,
2
+ told apart by a sign, a strike or an underline as well as by colour.
3
+
4
+ ## Use it when
5
+
6
+ - A person checks a change to text before accepting it, or looks back at what changed: a prompt before and after an
7
+ edit, a proposed rewrite of a blog body, a saved version against the current one.
8
+ - The point is the change itself, not either version alone.
9
+
10
+ ## Not for
11
+
12
+ - A number that went up or down: a `ChangeChip` or the chart's own moment.
13
+ - A list of what someone did and when: `Timeline`.
14
+ - Showing one version: plain text in the card, with "Version of Nov 9" in its meta.
15
+
16
+ ## Anatomy
17
+
18
+ - **By line** (`ws-diff`): a framed block on the field's white ground (the fine edge drawn inside, 4 px corners), 14 /
19
+ 20 body words, line breaks kept (`pre-wrap`). Each line (`__line`) is a 16 px sign column (`__sign`), 8 apart, then
20
+ the words; 2 / 12 inside.
21
+ - A removed line (`<del class="ws-diff__line ws-diff__line--removed">`): the needs tint (`--ws-tint-needs`), its words
22
+ and its minus sign in the needs word (`--ws-word-needs`). Not struck: the old words stay easy to read.
23
+ - An added line (`<ins class="ws-diff__line ws-diff__line--added">`): the live tint (`--ws-tint-live`), its words and
24
+ its plus sign in the positive word (`--ws-word-positive`).
25
+ - An unchanged line: no ground, no sign, the body's words.
26
+ - A folded run of unchanged lines (`__skip`): one row on the sunk ground, the whole row the act ("6 unchanged
27
+ lines"), its words in the text column, navy medium in the meta size, the wash under the pointer, 24 tall (40 on
28
+ touch). Pressed, the run opens in its place (`aria-expanded`).
29
+ - **Inline** (`ws-diff__del`, `ws-diff__ins` inside a `ws-body` paragraph): removed words on the needs tint, in the
30
+ needs word, struck through; added words on the live tint, in the positive word, underlined. 2 px corners.
31
+ - Every change says itself to a screen reader: a hidden word first inside each `del` and `ins`
32
+ (`<span class="ws-sr">Removed: </span>`), because most screen readers do not announce the elements themselves.
33
+ - The card around it says what is compared ("Version of Nov 9 against the current one") and the count ("1 line
34
+ removed, 2 added") in its meta.
35
+
36
+ ## Variants and sizes
37
+
38
+ | Variant | Use | Notes |
39
+ | --- | --- | --- |
40
+ | By line (`ws-diff`) | a prompt, a list, a template, any text whose lines matter | unchanged runs of more than 3 lines fold, keeping 1 line of context on each side |
41
+ | Inline (`__del`, `__ins`) | a paragraph rewritten in places | when most words stay; a rewrite of most of it reads better by line |
42
+
43
+ ## States
44
+
45
+ - **Folded**: unchanged runs behind their `__skip` row; the card's act "Show the whole prompt" opens them all.
46
+ - **Open**: every line shown, the changes still marked.
47
+ - **No changes**: no diff; one line in the card ("No changes since Nov 9").
48
+ - **Long line**: wraps inside its column, under its own words, never under the sign; never a sideways scroll.
49
+ - **Dark**: the tints and the words take their dark values (AA on each tint).
50
+
51
+ ## Behaviour
52
+
53
+ - Keyboard: each `__skip` row is a button (Enter or Space opens its run); nothing else in a diff is a stop.
54
+ - The diff is computed by the app (by line or by word); the component only draws it. Lines keep their order: a changed
55
+ line is its removed version, then its added one.
56
+ - Copying a diff copies its words, not its signs (`user-select: none` on the sign).
57
+ - Motion: an opened run rises in (`ws-arrive` on its lines); still under reduced motion.
58
+
59
+ ## Content
60
+
61
+ - Say what is compared and when ("Version of Nov 9 against the current one"), never "Old" and "New".
62
+ - The count in words: "1 line removed, 2 added", "3 changes".
63
+
64
+ ## Rules
65
+
66
+ - Never colour alone: a removed line has its minus, an added one its plus; inline, removed is struck and added
67
+ underlined. An underline inside a diff means added, never a link: a diff holds no links.
68
+ - Removed is the needs tone, added the live tone, both at their AA word steps. Never the alert red, never a raw green.
69
+ - One diff per card, and the card says what it compares.
70
+
71
+ ## Source
72
+
73
+ - **Derived from**: the kit's needs and live tints and their AA words (Colour), the field's ground and edge (`Input`),
74
+ `Plate`'s sunk ground for the folded run.
75
+ - **Precedent**: Marketing OS (ContentOS): the prompt diff, the SEO refresh's body changes and the content version
76
+ history, each hand-built on app-local error and success tints (issue #28, Madan, 10 Nov 2026).
77
+
78
+ ## Markup
79
+
80
+ ```html
81
+ <!-- By line -->
82
+ <div class="ws-diff" role="group" aria-label="Changes to the prompt">
83
+ <div class="ws-diff__line"><span class="ws-diff__sign" aria-hidden="true"></span><span>You write follow-up emails for real estate agents.</span></div>
84
+ <button type="button" class="ws-diff__skip" aria-expanded="false"><span>6 unchanged lines</span></button>
85
+ <del class="ws-diff__line ws-diff__line--removed"><span class="ws-diff__sign" aria-hidden="true">&minus;</span><span><span class="ws-sr">Removed: </span>Keep every email under 200 words.</span></del>
86
+ <ins class="ws-diff__line ws-diff__line--added"><span class="ws-diff__sign" aria-hidden="true">+</span><span><span class="ws-sr">Added: </span>Keep every email under 120 words.</span></ins>
87
+ </div>
88
+
89
+ <!-- Inline -->
90
+ <p class="ws-body">Spring is <del class="ws-diff__del"><span class="ws-sr">removed: </span>usually</del> <ins class="ws-diff__ins"><span class="ws-sr">added: </span>still</ins> the busiest season.</p>
91
+ ```
92
+
93
+ ## Styles
94
+
95
+ `design-system/components/TextDiff/style.css`: `ws-diff`, `__line` (`--removed`, `--added`), `__sign`, `__skip`,
96
+ `__del`, `__ins`.
@@ -0,0 +1,42 @@
1
+ <!-- @dsCard group="Data" height=620 -->
2
+ <!doctype html>
3
+ <html>
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <title>TextDiff</title>
7
+ <link rel="stylesheet" href="../../tokens.css">
8
+ <link rel="stylesheet" href="../bundle.css">
9
+ </head>
10
+ <body>
11
+ <div class="ws-stage">
12
+ <div class="ws-stack" style="width: 460px;">
13
+ <span class="ws-muted">By line: a prompt before and after an edit, the unchanged run folded</span>
14
+ <div class="ws-card ws-card--pad-md ws-stack ws-stack--loose">
15
+ <div class="ws-stack ws-stack--tight">
16
+ <h3 class="ws-section">What changes in the prompt</h3>
17
+ <p class="ws-meta">1 line removed, 2 added</p>
18
+ </div>
19
+ <div class="ws-diff" role="group" aria-label="Changes to the prompt">
20
+ <div class="ws-diff__line"><span class="ws-diff__sign" aria-hidden="true"></span><span>You write follow-up emails for real estate agents.</span></div>
21
+ <button type="button" class="ws-diff__skip" aria-expanded="false"><span>6 unchanged lines</span></button>
22
+ <del class="ws-diff__line ws-diff__line--removed"><span class="ws-diff__sign" aria-hidden="true">&minus;</span><span><span class="ws-sr">Removed: </span>Keep every email under 200 words.</span></del>
23
+ <ins class="ws-diff__line ws-diff__line--added"><span class="ws-diff__sign" aria-hidden="true">+</span><span><span class="ws-sr">Added: </span>Keep every email under 120 words.</span></ins>
24
+ <ins class="ws-diff__line ws-diff__line--added"><span class="ws-diff__sign" aria-hidden="true">+</span><span><span class="ws-sr">Added: </span>Name the listing in the first line.</span></ins>
25
+ <div class="ws-diff__line"><span class="ws-diff__sign" aria-hidden="true"></span><span>Sign off with the agent's first name.</span></div>
26
+ </div>
27
+ </div>
28
+ </div>
29
+
30
+ <div class="ws-stack" style="width: 460px;">
31
+ <span class="ws-muted">Inline: a proposed rewrite of a blog paragraph, removed words struck, added words underlined</span>
32
+ <div class="ws-card ws-card--pad-md ws-stack ws-stack--loose">
33
+ <div class="ws-stack ws-stack--tight">
34
+ <h3 class="ws-section">Suggested rewrite</h3>
35
+ <p class="ws-meta">Version of Nov 9 against the current one</p>
36
+ </div>
37
+ <p class="ws-body">Spring is <del class="ws-diff__del"><span class="ws-sr">removed: </span>usually</del> <ins class="ws-diff__ins"><span class="ws-sr">added: </span>still</ins> the busiest season for listings in Ohio, and buyers who <del class="ws-diff__del"><span class="ws-sr">removed: </span>start looking in January</del> <ins class="ws-diff__ins"><span class="ws-sr">added: </span>get pre-approved by February</ins> close first.</p>
38
+ </div>
39
+ </div>
40
+ </div>
41
+ </body>
42
+ </html>
@@ -0,0 +1,21 @@
1
+ /* ---- TextDiff: two versions of a text, what was removed and what was added, told apart by more than colour. By line
2
+ (`ws-diff`): a framed block, each changed line on its tint with a sign in a 16 px gutter (minus removed, plus added),
3
+ unchanged runs folded behind a quiet act. Inline (`ws-diff__del`, `ws-diff__ins` in running words): removed words on
4
+ the needs tint and struck, added words on the live tint and underlined. Every change also says itself to a screen
5
+ reader (a ws-sr word inside the del or the ins). ---- */
6
+ .ws-diff { display: grid; min-width: 0; overflow: clip; border-radius: var(--ws-radius-control); background: var(--ws-field); box-shadow: var(--ws-shadow-hairline-in); color: var(--color-text-body); font-size: var(--ws-text-body); line-height: calc(var(--space-md) + var(--space-xxs)); }
7
+ .ws-diff__line { display: grid; grid-template-columns: var(--space-md) minmax(0, 1fr); column-gap: var(--space-xs); padding: var(--space-3xs) var(--space-sm); white-space: pre-wrap; overflow-wrap: anywhere; text-decoration: none; }
8
+ .ws-diff__sign { font-weight: 600; text-align: center; user-select: none; }
9
+ .ws-diff__line--removed { background: var(--ws-tint-needs); color: var(--ws-word-needs); }
10
+ .ws-diff__line--added { background: var(--ws-tint-live); color: var(--ws-word-positive); }
11
+ /* An unchanged run folded: the row itself is the act (it opens the run), on the sunk ground, its words in the text
12
+ column saying how many lines it holds, the wash under the pointer. */
13
+ .ws-diff__skip { display: grid; grid-template-columns: var(--space-md) minmax(0, 1fr); column-gap: var(--space-xs); width: 100%; min-height: var(--space-lg); padding: var(--space-3xs) var(--space-sm); border: 0; background: var(--ws-surface-sunk); color: var(--color-text-heading); font: inherit; font-size: var(--ws-text-meta); font-weight: 500; text-align: start; cursor: pointer; transition: background-color var(--ws-duration-hover) var(--ws-ease-soft); }
14
+ .ws-diff__skip > * { grid-column: 2; }
15
+ @media (pointer: coarse) { .ws-diff__skip { min-height: var(--ws-touch); } }
16
+ @media (hover: hover) { .ws-diff__skip:hover { background-image: linear-gradient(var(--ws-hover), var(--ws-hover)); } }
17
+ .ws-diff__skip:focus-visible { outline-offset: calc(var(--ws-focus-width) * -1); }
18
+ /* Inline: changed words inside a sentence. */
19
+ .ws-diff__del, .ws-diff__ins { border-radius: calc(var(--ws-radius-control) / 2); text-decoration-thickness: var(--ws-line); }
20
+ .ws-diff__del { background: var(--ws-tint-needs); color: var(--ws-word-needs); text-decoration-line: line-through; }
21
+ .ws-diff__ins { background: var(--ws-tint-live); color: var(--ws-word-positive); text-decoration-line: underline; text-underline-offset: var(--space-3xs); }
@@ -7,7 +7,9 @@ A vertical feed of what happened, grouped by day, a coloured dot on a fine line
7
7
 
8
8
  ## Not for
9
9
 
10
- - A sequence of steps still in progress toward an end: use `Stepper`.
10
+ - A sequence of steps still in progress toward an end: use `Stepper`; a thing's stages across its head: `Stepper --across`.
11
+ - A few dated milestones on one row (signed, first value): use `Stepper --milestones`.
12
+ - A plan's phases across their dates: use `PlanTimeline`. Things that fall on a day (posts, sends): use `Calendar`.
11
13
  - A list of records to compare or act on: use `Table` or rows in a `Card`.
12
14
  - A single moment worth calling out inside a chart: use `TrendBars`' own labelled moment.
13
15
 
@@ -1,4 +1,4 @@
1
- A value over periods against an axis, as a line with its dots and the hatched area under it, or as columns that collapse into the line's points and back.
1
+ A value over periods against an axis, as a line with its dots and the hatched area under it, or as columns that collapse into the line's points and back; up to three series in one unit as lines with a key.
2
2
 
3
3
  **Use it when**
4
4
 
@@ -7,13 +7,17 @@ A value over periods against an axis, as a line with its dots and the hatched ar
7
7
  - The same run reads two ways: columns to compare periods, a line to see the trend. A Bars / Line switch over the card
8
8
  shows both, from the same data, with no reload: the columns collapse into the line's points.
9
9
  - A tile needs a small preview of that run (`--bare`, inside `MetricTile`'s `__preview`).
10
+ - Two or three series in one unit move over the same periods and the reader compares them (onboardings started,
11
+ graduated and stalled, by month): one axis, a line each, a key naming them (`__series`).
10
12
 
11
13
  **Not for**
12
14
 
13
15
  - A day-by-day trend with one moment that explains it: use `TrendBars`.
14
16
  - A bill or a rate that steps against a reference, the gap hatched: use `StepLine`.
15
17
  - Parts of one whole: use `ShareBar`. A goal's share: `GoalTicks` or `PaceBar`.
16
- - Two series or more: one TrendLine draws one series (and one reference). Put the second in its own card.
18
+ - Series in different units, or one more than four times another (accounts and tickets): never two value axes. Stack
19
+ two TrendLines in one card on the same periods (Rules).
20
+ - More than three series: a `Table`, or the three that matter and the rest named in the sentence.
17
21
 
18
22
  **Anatomy**
19
23
 
@@ -42,8 +46,16 @@ A value over periods against an axis, as a line with its dots and the hatched ar
42
46
  each a `title`); the current one (`__now`) 600 navy.
43
47
  - Key (`__legend`, optional): centred, 16 apart, 16 above; a 12 px swatch in the mark's look (`__key--now`,
44
48
  `__key--ahead`, `__key--ref` for the dotted line), 8, 14/20 medium body grey.
45
- - The reading (`__table`): a real `<table>` of every period and its value, with a caption, hidden from the eye inside
46
- the `__table` box. The plot is `aria-hidden`.
49
+ - Several series (up to 3): the first is the figure's own `__draw` and points; each other is a `__series` box after
50
+ them (`display: contents`, so its points still sit on the plot) holding its own `__draw` (the SVG path only, no
51
+ hatch) and its points, the same `--i` and `--n`. Tones in order: navy, teal (`--ws-chart-finished`), purple
52
+ (`--ws-chart-past-half`); salmon stays the reference's. `--tone` on a series overrides its tone (a figure that sets
53
+ its own `--tone` sets one on each series too, since the figure's is inherited). The key names every series:
54
+ `__key--2` and `__key--3` take the second and third tones (an overridden series' `<li>` carries the same `--tone`).
55
+ With several series there is no hatch (one area claims a whole; two cover each other) and the period in progress
56
+ keeps its series' tone: its name (`__now`) and the dotted `__ahead` say it.
57
+ - The reading (`__table`): a real `<table>` of every period and its value (a column a series), with a caption, hidden
58
+ from the eye inside the `__table` box. The plot is `aria-hidden`.
47
59
 
48
60
  **Parts and modifiers**
49
61
 
@@ -54,6 +66,7 @@ A value over periods against an axis, as a line with its dots and the hatched ar
54
66
  - `__point--now`, `__point--ahead`: the period in progress; a period to come.
55
67
  - `__point--on`: the point held open (a tap, or the keyboard's mark when the app moves one); it looks hovered.
56
68
  - `__band`: the hover band, last in the plot. `__now`: the current period's name.
69
+ - `__series`: a second or third series, after the first series' points; `__key--2`, `__key--3`: its swatch in the key.
57
70
 
58
71
  **States**
59
72
 
@@ -77,7 +90,9 @@ A value over periods against an axis, as a line with its dots and the hatched ar
77
90
  tall, the other columns step back to `--ws-dim` over the hover. Line: the band is a dashed guide from the dot to the
78
91
  baseline, and the dot grows to 1.5 inside a faint halo over `--ws-duration-hover` (North's `r .2s`). The band rides
79
92
  CSS anchor positioning: without it the band is left out and the rest still happens. A `HoverCard`
80
- (`--above --follow`) reads the point.
93
+ (`--above --follow`) reads the point. With several series it reads the period, not the dot: the eyebrow names the
94
+ period (no swatch), then a row a series in the key's order, each with its swatch and value (`__row`); it sits over
95
+ the period's highest dot. The guide runs from the hovered dot to the baseline, through the others.
81
96
  - Narrow (the figure under 30rem): the plot is 200 tall; with more than six names, every other one and the current
82
97
  one's neighbours give way and the rest take two slots.
83
98
  - Zero: every column a 2 px stub on the baseline, every dot on it. One period: a dot (or a column) and no line.
@@ -104,7 +119,12 @@ A value over periods against an axis, as a line with its dots and the hatched ar
104
119
 
105
120
  **Rules**
106
121
 
107
- - **One series, one optional reference.** Two lines are two cards.
122
+ - **Up to three series in one unit, one optional reference.** One value axis, always: never two.
123
+ - **Different units or scales far apart** (one series more than four times another): two TrendLines stacked in one
124
+ card on the same periods (`--n`) and the same gutter (`--ws-trend-line-y`), each with its own value labels and a key
125
+ of one; the period names only under the last (leave the first's `__x` out). One title and one sentence for the pair.
126
+ - **Several series are lines only**: no hatch and no Bars / Line switch (two columns cannot share a slot and still
127
+ collapse into their dots). A chart that wants columns per period across series is a `Table` or two cards.
108
128
  - **Columns start at zero.** A chart with the switch, or any columns, has its baseline at 0; a line alone may start
109
129
  above zero only without the hatch (an area claims a whole).
110
130
  - **The same data in both modes**: the switch changes the drawing, never the numbers.
@@ -143,6 +163,17 @@ A value over periods against an axis, as a line with its dots and the hatched ar
143
163
  </figure>
144
164
  ```
145
165
 
166
+ A second series, after the first series' points (a third is another `__series` after it):
167
+
168
+ ```html
169
+ <span class="ws-trend-line__series">
170
+ <span class="ws-trend-line__draw"><svg class="ws-trend-line__lines" viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true"><path d="M12.5 70 L37.5 62.5 L62.5 52.5 L87.5 40"/></svg></span>
171
+ <span class="ws-trend-line__point" style="--i: 0; --y: 70%;"></span><!-- a point a period -->
172
+ </span>
173
+ <!-- the key, after the plot: <ul class="ws-trend-line__legend" aria-hidden="true"><li>Started</li><li class="ws-trend-line__key--2">Graduated</li></ul> -->
174
+ <!-- the table: a column a series, <th scope="col"> named as the key -->
175
+ ```
176
+
146
177
  A reference goes after the rules: `<span class="ws-trend-line__ref" style="--y: 19.94%;"><span>Plan $40.03M</span></span>`.
147
178
  What is to come: `__point--ahead` on its points, and a second path `<path class="ws-trend-line__ahead" d="...">` from
148
179
  the last real point on. The switch, in the card's head:
@@ -160,5 +191,5 @@ on tokens, except North's own numbers: the dotted line's 0.1 dash (a round cap's
160
191
  the hatch at 45%, the hovered dot's 1.5 and its halo at 12%, and the build's 55 / 45 share. Tokens: `--ws-duration-draw`,
161
192
  `--ws-duration-settle`, `--ws-duration-hover`, `--ws-duration-exit`, `--ws-ease-settle`, `--ws-ease-steady`,
162
193
  `--ws-ease-pop`, `--ws-ease-soft`, `--ws-ease-exit`, `--ws-spring-morph-chart` and its duration, `--ws-dim`,
163
- `--ws-hover`, `--ws-chart-line`, `--ws-chart-navy-light`, `--ws-chart-under-half`, `--ws-word-needs`, `--ws-edge-slot`,
194
+ `--ws-hover`, `--ws-chart-line`, `--ws-chart-navy-light`, `--ws-chart-finished`, `--ws-chart-past-half`, `--ws-chart-under-half`, `--ws-word-needs`, `--ws-edge-slot`,
164
195
  `--ws-edge-card`.