@felloinnovations/brand-kit 0.5.0 → 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 (124) hide show
  1. package/AGENTS.md +29 -9
  2. package/CHANGELOG.md +110 -0
  3. package/README.md +14 -6
  4. package/design-system/01-how-claude-decides.md +5 -3
  5. package/design-system/03-colour.md +22 -3
  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 +7 -4
  9. package/design-system/10-patterns.md +1 -1
  10. package/design-system/11-page-recipes.md +40 -1
  11. package/design-system/13-motion.md +4 -4
  12. package/design-system/15-ai-experiences.md +11 -0
  13. package/design-system/17-decisions.md +51 -0
  14. package/design-system/components/AppShell/README.md +89 -21
  15. package/design-system/components/AppShell/preview.html +148 -3
  16. package/design-system/components/AppShell/style.css +47 -0
  17. package/design-system/components/Avatar/README.md +6 -3
  18. package/design-system/components/Avatar/preview.html +2 -1
  19. package/design-system/components/Avatar/style.css +3 -0
  20. package/design-system/components/Banner/README.md +9 -5
  21. package/design-system/components/Banner/preview.html +9 -0
  22. package/design-system/components/Banner/style.css +3 -0
  23. package/design-system/components/Button/README.md +29 -2
  24. package/design-system/components/Button/preview.html +33 -1
  25. package/design-system/components/Button/style.css +21 -0
  26. package/design-system/components/Calendar/README.md +148 -0
  27. package/design-system/components/Calendar/preview.html +449 -0
  28. package/design-system/components/Calendar/style.css +61 -0
  29. package/design-system/components/Card/README.md +13 -0
  30. package/design-system/components/Card/preview.html +23 -0
  31. package/design-system/components/Card/style.css +19 -0
  32. package/design-system/components/ChatCard/README.md +74 -3
  33. package/design-system/components/ChatCard/preview.html +126 -25
  34. package/design-system/components/ChatCard/style.css +6 -0
  35. package/design-system/components/CheckRow/README.md +1 -1
  36. package/design-system/components/CheckRow/preview.html +1 -1
  37. package/design-system/components/CheckRow/style.css +1 -1
  38. package/design-system/components/Chip/README.md +16 -7
  39. package/design-system/components/Chip/preview.html +25 -1
  40. package/design-system/components/Chip/style.css +5 -0
  41. package/design-system/components/Combobox/README.md +10 -4
  42. package/design-system/components/Combobox/preview.html +19 -4
  43. package/design-system/components/Combobox/style.css +7 -1
  44. package/design-system/components/CommentThread/README.md +138 -0
  45. package/design-system/components/CommentThread/preview.html +134 -0
  46. package/design-system/components/CommentThread/style.css +41 -0
  47. package/design-system/components/DateInput/README.md +38 -4
  48. package/design-system/components/DateInput/preview.html +50 -2
  49. package/design-system/components/DateInput/style.css +6 -0
  50. package/design-system/components/Dialog/README.md +26 -3
  51. package/design-system/components/Dialog/preview.html +80 -0
  52. package/design-system/components/Dialog/style.css +28 -3
  53. package/design-system/components/EmptyState/README.md +1 -1
  54. package/design-system/components/Fold/README.md +21 -0
  55. package/design-system/components/Fold/preview.html +44 -1
  56. package/design-system/components/Fold/style.css +30 -0
  57. package/design-system/components/InlineEdit/README.md +116 -0
  58. package/design-system/components/InlineEdit/preview.html +135 -0
  59. package/design-system/components/InlineEdit/style.css +34 -0
  60. package/design-system/components/Input/README.md +20 -1
  61. package/design-system/components/Input/preview.html +24 -0
  62. package/design-system/components/Input/style.css +13 -1
  63. package/design-system/components/KanbanBoard/README.md +69 -5
  64. package/design-system/components/KanbanBoard/preview.html +129 -0
  65. package/design-system/components/KanbanBoard/style.css +32 -1
  66. package/design-system/components/ListDetail/preview.html +1 -1
  67. package/design-system/components/Matrix/README.md +1 -1
  68. package/design-system/components/Matrix/preview.html +1 -1
  69. package/design-system/components/Motion/README.md +2 -2
  70. package/design-system/components/Motion/preview.html +8 -8
  71. package/design-system/components/PlanTimeline/README.md +144 -0
  72. package/design-system/components/PlanTimeline/preview.html +109 -0
  73. package/design-system/components/PlanTimeline/style.css +64 -0
  74. package/design-system/components/README.md +12 -7
  75. package/design-system/components/ScoreHero/README.md +2 -2
  76. package/design-system/components/ScoreHero/style.css +1 -1
  77. package/design-system/components/ShareBar/README.md +1 -1
  78. package/design-system/components/ShareBar/style.css +1 -1
  79. package/design-system/components/Skeleton/README.md +82 -17
  80. package/design-system/components/Skeleton/preview.html +84 -2
  81. package/design-system/components/Skeleton/style.css +9 -14
  82. package/design-system/components/Starter/README.md +1 -1
  83. package/design-system/components/Starter/preview.html +1 -1
  84. package/design-system/components/Stepper/README.md +81 -5
  85. package/design-system/components/Stepper/preview.html +64 -1
  86. package/design-system/components/Stepper/style.css +29 -0
  87. package/design-system/components/StreakCalendar/README.md +1 -0
  88. package/design-system/components/Table/README.md +8 -4
  89. package/design-system/components/Table/preview.html +16 -1
  90. package/design-system/components/Table/style.css +3 -1
  91. package/design-system/components/TextDiff/README.md +96 -0
  92. package/design-system/components/TextDiff/preview.html +42 -0
  93. package/design-system/components/TextDiff/style.css +21 -0
  94. package/design-system/components/TickStrip/README.md +1 -1
  95. package/design-system/components/TickStrip/preview.html +1 -1
  96. package/design-system/components/TickStrip/style.css +1 -1
  97. package/design-system/components/Timeline/README.md +4 -2
  98. package/design-system/components/TrendLine/README.md +38 -7
  99. package/design-system/components/TrendLine/preview.html +140 -3
  100. package/design-system/components/TrendLine/style.css +12 -2
  101. package/design-system/components/Well/README.md +1 -1
  102. package/design-system/components/Well/style.css +2 -2
  103. package/design-system/components/_house.css +1 -1
  104. package/design-system/components/bundle.css +501 -30
  105. package/design-system/tokens.json +17 -7
  106. package/dist/fello-core.css +2 -2
  107. package/dist/fello.css +2 -2
  108. package/dist/fello.scoped.css +2 -2
  109. package/dist/index.d.ts +1 -1
  110. package/dist/scoped.js +2 -2
  111. package/dist/tokens.d.ts +16 -8
  112. package/dist/tokens.js +16 -8
  113. package/package.json +7 -2
  114. package/scripts/check-drift.mjs +325 -0
  115. package/scripts/token-model.mjs +2 -1
  116. package/tokens/_dark.css +3 -2
  117. package/tokens/_product.css +13 -11
  118. package/tokens/palettes.ts +1 -1
  119. package/tokens/shadcn-compat.css +1 -1
  120. package/tokens/shadcn-compat.hsl.css +1 -1
  121. package/tokens/tokens.css +23 -15
  122. package/tokens/tokens.md +56 -50
  123. package/tokens/tokens.root.css +23 -15
  124. package/tokens/tokens.ts +17 -9
@@ -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`),
@@ -81,7 +83,7 @@ a phone fold. This is the whole wide-table pattern; build every records table fr
81
83
  stuck columns leave the sliding ones over 100 px, not a sliver. A name that fits keeps its one line.
82
84
  - **Empty, no matches**: the same card, a sentence and the act that clears the filter (`__empty`).
83
85
  - **Empty, first use**: the same card, a sentence inviting the first record and the act that adds one.
84
- - **Loading**: a ghost bar per cell in the chart colours' sweep (`__placeholder ws-shimmer`), the head shown at once;
86
+ - **Loading**: a ghost bar per cell under the shimmer (`__placeholder ws-shimmer`), the head shown at once;
85
87
  still under reduced motion.
86
88
  - **Touch**: the check target, the More act and the row acts reach 40, the sort button 24.
87
89
 
@@ -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.
@@ -151,7 +151,7 @@
151
151
  </figure>
152
152
 
153
153
  <div class="ws-stack" style="width: 300px;">
154
- <span class="ws-muted">Loading: ghost bars in the chart colours' sweep, the head shown at once</span>
154
+ <span class="ws-muted">Loading: ghost bars under the shimmer, the head shown at once</span>
155
155
  <section class="ws-card ws-table" aria-busy="true" aria-label="Leads, loading">
156
156
  <div class="ws-table__scroll">
157
157
  <table>
@@ -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); }
@@ -97,7 +99,7 @@
97
99
  .ws-table .ws-table__more { display: inline-flex; }
98
100
  .ws-table .ws-table__name { min-width: 40cqi; white-space: normal; }
99
101
  }
100
- /* Loading: a ghost bar in each cell, in the chart colours' sweep (add ws-shimmer), the head shown at once. */
102
+ /* Loading: a ghost bar in each cell under the shimmer (add ws-shimmer), the head shown at once. */
101
103
  .ws-table__placeholder { height: calc(var(--space-xs) + var(--space-3xs)); border-radius: var(--ws-radius-bar); background: var(--ws-track); }
102
104
  .ws-table__placeholder.ws-shimmer { background: var(--ws-shimmer); background-size: 300% 100%; }
103
105
  /* Empty: the same card, a sentence and the act that clears the filter. */
@@ -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); }
@@ -55,7 +55,7 @@ A share drawn as equal ticks across the whole width: the done ones in one tone,
55
55
  - Draw (`--draw`): one 900 ms draw (`--ws-duration-draw`); tick i starts at its share of the first 55% and rises from
56
56
  its foot on `--ws-ease-settle`. Set `--i` on each lit tick.
57
57
  - Not checked or zero: every tick the track; the figure beside it says so in words (a dash, "Could not check").
58
- - Loading (`--loading`): the ticks as a still ghost with the chart colours' sweep (`--ws-shimmer`, 1.8 s). The ticks
58
+ - Loading (`--loading`): the ticks as a still ghost with the shimmer passing over them (`--ws-shimmer`, 1.8 s). The ticks
59
59
  inside are hidden, so the ghost has the strip's exact shape.
60
60
  - Hover (`--hover`, opt in): the tick under the pointer grows to 116% from its foot on `--ws-ease-lift`, its two
61
61
  neighbours half as much 4 ms later, the rest dim to 30%. Only with a hovering pointer.
@@ -46,7 +46,7 @@
46
46
  </div>
47
47
 
48
48
  <div class="ws-stack">
49
- <span class="ws-muted">Loading: the ticks as a ghost with the chart colours' sweep (still with reduced motion)</span>
49
+ <span class="ws-muted">Loading: the ticks as a ghost with the shimmer passing over them (still with reduced motion)</span>
50
50
  <div class="ws-tick-strip ws-tick-strip--lg ws-tick-strip--loading" style="--n: 50;" aria-hidden="true"><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span><span class="ws-tick-strip__tick"></span></div>
51
51
  </div>
52
52
 
@@ -24,7 +24,7 @@
24
24
  .ws-tick-strip__label { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-size: var(--ws-text-micro); line-height: var(--space-sm-plus); color: var(--color-text-muted); font-variant-numeric: tabular-nums; }
25
25
  .ws-tick-strip__marker { position: absolute; top: 0; left: var(--at, 0%); display: flex; align-items: center; gap: var(--space-xxs-plus); white-space: nowrap; font-size: var(--ws-text-micro); font-weight: 500; line-height: var(--space-sm-plus); color: var(--ws-word-needs); }
26
26
  .ws-tick-strip__marker::before { content: ""; width: var(--space-xs); height: var(--space-xs); border-radius: var(--radius-full); background: var(--ws-chart-under-half); }
27
- /* Loading (North): the ticks as a still ghost, the chart colours' sweep passing over them. */
27
+ /* Loading (North): the ticks as a still ghost, the shimmer passing over them. */
28
28
  .ws-tick-strip--loading { --pitch: calc((100% + var(--g)) / var(--n)); background: var(--ws-shimmer); background-size: 300% 100%; -webkit-mask-image: repeating-linear-gradient(90deg, var(--ws-mask) 0 calc(var(--pitch) - var(--g)), transparent 0 var(--pitch)); mask-image: repeating-linear-gradient(90deg, var(--ws-mask) 0 calc(var(--pitch) - var(--g)), transparent 0 var(--pitch)); }
29
29
  .ws-tick-strip--loading > * { visibility: hidden; }
30
30
  @media (prefers-reduced-motion: no-preference) {
@@ -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
 
@@ -40,7 +42,7 @@ A vertical feed of what happened, grouped by day, a coloured dot on a fine line
40
42
  - **An entry that opens something**: the title is a link (`a.ws-timeline__title`, medium), underlined on hover; on
41
43
  touch its box grows to 24 without moving anything.
42
44
  - **Empty**: the same card, a sentence ("Nothing here yet") in place of the list; never a bare line with no dots.
43
- - **Loading**: ghost lines where the titles go, in the chart colours' sweep (`__ghost ws-shimmer`), the dots plain grey;
45
+ - **Loading**: ghost lines where the titles go under the shimmer (`__ghost ws-shimmer`), the dots plain grey;
44
46
  still under reduced motion.
45
47
  - **Long title**: wraps; a history entry is read once and should say the whole thing.
46
48
  - **Many entries**: the feed scrolls inside its card past a comfortable length; it is never paginated.
@@ -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`.
@@ -1,4 +1,4 @@
1
- <!-- @dsCard group="Charts" height=2320 -->
1
+ <!-- @dsCard group="Charts" height=3500 -->
2
2
  <!doctype html>
3
3
  <html>
4
4
  <head>
@@ -100,6 +100,126 @@
100
100
  </section>
101
101
  </div>
102
102
 
103
+ <div class="ws-stack" style="max-width: 1194px;">
104
+ <span class="ws-muted">Several series (up to three, one unit, one axis): a __series box for each after the first, the tones navy, teal, purple; lines only, no hatch and no Bars / Line switch. Point along it: the reading names every series.</span>
105
+ <section class="ws-card ws-card--compound" aria-labelledby="tl-s-t">
106
+ <header class="ws-head">
107
+ <div>
108
+ <h3 class="ws-title" id="tl-s-t">More onboardings start than graduate</h3>
109
+ <p class="ws-body">30 started and 21 graduated so far in November; 12 stalled, the most this year.</p>
110
+ </div>
111
+ </header>
112
+ <div class="ws-card__body">
113
+ <figure class="ws-trend-line" style="--n: 6; --ticks: 4;" aria-labelledby="tl-s-t" data-trend-live>
114
+ <div class="ws-trend-line__plot" aria-hidden="true">
115
+ <div class="ws-trend-line__y"><span style="--y: 0%;">40</span><span style="--y: 25%;">30</span><span style="--y: 50%;">20</span><span style="--y: 75%;">10</span><span style="--y: 100%;">0</span></div>
116
+ <div class="ws-trend-line__area">
117
+ <span class="ws-trend-line__rule" style="--y: 0%;"></span><span class="ws-trend-line__rule" style="--y: 25%;"></span><span class="ws-trend-line__rule" style="--y: 50%;"></span><span class="ws-trend-line__rule" style="--y: 75%;"></span>
118
+ <span class="ws-trend-line__draw"><svg class="ws-trend-line__lines" viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true"><path d="M8.33 45 L25 30 L41.67 37.5 L58.33 22.5 L75 15 L91.67 25"/></svg></span>
119
+ <span class="ws-trend-line__point" style="--i: 0; --y: 45%;"></span>
120
+ <span class="ws-trend-line__point" style="--i: 1; --y: 30%;"></span>
121
+ <span class="ws-trend-line__point" style="--i: 2; --y: 37.5%;"></span>
122
+ <span class="ws-trend-line__point" style="--i: 3; --y: 22.5%;"></span>
123
+ <span class="ws-trend-line__point" style="--i: 4; --y: 15%;"></span>
124
+ <span class="ws-trend-line__point ws-trend-line__point--now" style="--i: 5; --y: 25%;"></span>
125
+ <span class="ws-trend-line__series">
126
+ <span class="ws-trend-line__draw"><svg class="ws-trend-line__lines" viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true"><path d="M8.33 70 L25 62.5 L41.67 52.5 L58.33 55 L75 40 L91.67 47.5"/></svg></span>
127
+ <span class="ws-trend-line__point" style="--i: 0; --y: 70%;"></span>
128
+ <span class="ws-trend-line__point" style="--i: 1; --y: 62.5%;"></span>
129
+ <span class="ws-trend-line__point" style="--i: 2; --y: 52.5%;"></span>
130
+ <span class="ws-trend-line__point" style="--i: 3; --y: 55%;"></span>
131
+ <span class="ws-trend-line__point" style="--i: 4; --y: 40%;"></span>
132
+ <span class="ws-trend-line__point ws-trend-line__point--now" style="--i: 5; --y: 47.5%;"></span>
133
+ </span>
134
+ <span class="ws-trend-line__series">
135
+ <span class="ws-trend-line__draw"><svg class="ws-trend-line__lines" viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true"><path d="M8.33 90 L25 85 L41.67 87.5 L58.33 80 L75 77.5 L91.67 70"/></svg></span>
136
+ <span class="ws-trend-line__point" style="--i: 0; --y: 90%;"></span>
137
+ <span class="ws-trend-line__point" style="--i: 1; --y: 85%;"></span>
138
+ <span class="ws-trend-line__point" style="--i: 2; --y: 87.5%;"></span>
139
+ <span class="ws-trend-line__point" style="--i: 3; --y: 80%;"></span>
140
+ <span class="ws-trend-line__point" style="--i: 4; --y: 77.5%;"></span>
141
+ <span class="ws-trend-line__point ws-trend-line__point--now" style="--i: 5; --y: 70%;"></span>
142
+ </span>
143
+ <span class="ws-trend-line__band"></span>
144
+ </div>
145
+ <div class="ws-trend-line__x"><span title="June">Jun</span><span title="July">Jul</span><span title="August">Aug</span><span title="September">Sep</span><span title="October">Oct</span><span class="ws-trend-line__now" title="November, month to date">Nov</span></div>
146
+ </div>
147
+ <ul class="ws-trend-line__legend" aria-hidden="true"><li>Started</li><li class="ws-trend-line__key--2">Graduated</li><li class="ws-trend-line__key--3">Stalled</li></ul>
148
+ <div class="ws-trend-line__table"><table>
149
+ <caption>Onboardings by month</caption>
150
+ <thead><tr><th scope="col">Month</th><th scope="col">Started</th><th scope="col">Graduated</th><th scope="col">Stalled</th></tr></thead>
151
+ <tbody><tr><th scope="row">June</th><td>22</td><td>12</td><td>4</td></tr><tr><th scope="row">July</th><td>28</td><td>15</td><td>6</td></tr><tr><th scope="row">August</th><td>25</td><td>19</td><td>5</td></tr><tr><th scope="row">September</th><td>31</td><td>18</td><td>8</td></tr><tr><th scope="row">October</th><td>34</td><td>24</td><td>9</td></tr><tr><th scope="row">November, month to date</th><td>30</td><td>21</td><td>12</td></tr></tbody>
152
+ </table></div>
153
+ </figure>
154
+ </div>
155
+ </section>
156
+ </div>
157
+
158
+ <div class="ws-stack" style="max-width: 1194px;">
159
+ <span class="ws-muted">Units or scales far apart (one series four times the other): never two value axes. Two TrendLines stacked in one card on the same periods, each with its own axis and key; the period names only under the last.</span>
160
+ <section class="ws-card ws-card--compound" aria-labelledby="tl-p-t">
161
+ <header class="ws-head">
162
+ <div>
163
+ <h3 class="ws-title" id="tl-p-t">Tickets grew faster than accounts</h3>
164
+ <p class="ws-body">Accounts rose 11% since June; tickets opened rose 28%.</p>
165
+ </div>
166
+ </header>
167
+ <div class="ws-card__body">
168
+ <figure class="ws-trend-line" style="--n: 6; --ticks: 3;" aria-label="Accounts by month" data-trend-live>
169
+ <div class="ws-trend-line__plot" aria-hidden="true">
170
+ <div class="ws-trend-line__y"><span style="--y: 0%;">3K</span><span style="--y: 33.33%;">2K</span><span style="--y: 66.67%;">1K</span><span style="--y: 100%;">0</span></div>
171
+ <div class="ws-trend-line__area">
172
+ <span class="ws-trend-line__rule" style="--y: 0%;"></span><span class="ws-trend-line__rule" style="--y: 33.33%;"></span><span class="ws-trend-line__rule" style="--y: 66.67%;"></span>
173
+ <span class="ws-trend-line__draw">
174
+ <span class="ws-trend-line__hatch" style="clip-path: polygon(8.33% 19.67%, 25% 17.67%, 41.67% 16%, 58.33% 14%, 75% 12.33%, 91.67% 10.47%, 91.67% 100%, 8.33% 100%);"></span>
175
+ <svg class="ws-trend-line__lines" viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true"><path d="M8.33 19.67 L25 17.67 L41.67 16 L58.33 14 L75 12.33 L91.67 10.47"/></svg>
176
+ </span>
177
+ <span class="ws-trend-line__point" style="--i: 0; --y: 19.67%;"></span>
178
+ <span class="ws-trend-line__point" style="--i: 1; --y: 17.67%;"></span>
179
+ <span class="ws-trend-line__point" style="--i: 2; --y: 16%;"></span>
180
+ <span class="ws-trend-line__point" style="--i: 3; --y: 14%;"></span>
181
+ <span class="ws-trend-line__point" style="--i: 4; --y: 12.33%;"></span>
182
+ <span class="ws-trend-line__point" style="--i: 5; --y: 10.47%;"></span>
183
+ <span class="ws-trend-line__band"></span>
184
+ </div>
185
+ </div>
186
+ <ul class="ws-trend-line__legend" aria-hidden="true"><li>Accounts</li></ul>
187
+ <div class="ws-trend-line__table"><table>
188
+ <caption>Accounts by month</caption>
189
+ <thead><tr><th scope="col">Month</th><th scope="col">Accounts</th></tr></thead>
190
+ <tbody><tr><th scope="row">June</th><td>2,410 accounts</td></tr><tr><th scope="row">July</th><td>2,470 accounts</td></tr><tr><th scope="row">August</th><td>2,520 accounts</td></tr><tr><th scope="row">September</th><td>2,580 accounts</td></tr><tr><th scope="row">October</th><td>2,630 accounts</td></tr><tr><th scope="row">November</th><td>2,686 accounts</td></tr></tbody>
191
+ </table></div>
192
+ </figure>
193
+ <figure class="ws-trend-line" style="--n: 6; --ticks: 3; --tone: var(--ws-chart-finished);" aria-label="Tickets opened by month" data-trend-live>
194
+ <div class="ws-trend-line__plot" aria-hidden="true">
195
+ <div class="ws-trend-line__y"><span style="--y: 0%;">300</span><span style="--y: 33.33%;">200</span><span style="--y: 66.67%;">100</span><span style="--y: 100%;">0</span></div>
196
+ <div class="ws-trend-line__area">
197
+ <span class="ws-trend-line__rule" style="--y: 0%;"></span><span class="ws-trend-line__rule" style="--y: 33.33%;"></span><span class="ws-trend-line__rule" style="--y: 66.67%;"></span>
198
+ <span class="ws-trend-line__draw">
199
+ <span class="ws-trend-line__hatch" style="clip-path: polygon(8.33% 40%, 25% 30%, 41.67% 35%, 58.33% 20%, 75% 13.33%, 91.67% 23.33%, 91.67% 100%, 8.33% 100%);"></span>
200
+ <svg class="ws-trend-line__lines" viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true"><path d="M8.33 40 L25 30 L41.67 35 L58.33 20 L75 13.33 L91.67 23.33"/></svg>
201
+ </span>
202
+ <span class="ws-trend-line__point" style="--i: 0; --y: 40%;"></span>
203
+ <span class="ws-trend-line__point" style="--i: 1; --y: 30%;"></span>
204
+ <span class="ws-trend-line__point" style="--i: 2; --y: 35%;"></span>
205
+ <span class="ws-trend-line__point" style="--i: 3; --y: 20%;"></span>
206
+ <span class="ws-trend-line__point" style="--i: 4; --y: 13.33%;"></span>
207
+ <span class="ws-trend-line__point" style="--i: 5; --y: 23.33%;"></span>
208
+ <span class="ws-trend-line__band"></span>
209
+ </div>
210
+ <div class="ws-trend-line__x"><span title="June">Jun</span><span title="July">Jul</span><span title="August">Aug</span><span title="September">Sep</span><span title="October">Oct</span><span title="November">Nov</span></div>
211
+ </div>
212
+ <ul class="ws-trend-line__legend" aria-hidden="true"><li>Tickets opened</li></ul>
213
+ <div class="ws-trend-line__table"><table>
214
+ <caption>Tickets opened by month</caption>
215
+ <thead><tr><th scope="col">Month</th><th scope="col">Tickets opened</th></tr></thead>
216
+ <tbody><tr><th scope="row">June</th><td>180 tickets</td></tr><tr><th scope="row">July</th><td>210 tickets</td></tr><tr><th scope="row">August</th><td>195 tickets</td></tr><tr><th scope="row">September</th><td>240 tickets</td></tr><tr><th scope="row">October</th><td>260 tickets</td></tr><tr><th scope="row">November</th><td>230 tickets</td></tr></tbody>
217
+ </table></div>
218
+ </figure>
219
+ </div>
220
+ </section>
221
+ </div>
222
+
103
223
  <div class="ws-stack" style="max-width: 1194px;">
104
224
  <span class="ws-muted">Hover, held (__point--on): the band behind the column and the rest stepped back; on the line, the guide to the baseline and the grown dot</span>
105
225
  <div class="ws-board">
@@ -338,12 +458,29 @@
338
458
  card.innerHTML = '<span class="ws-hover-card__eyebrow"><span class="ws-hover-card__swatch" style="--ws-hover-card-swatch: var(--color-text-heading);"></span><span></span></span><span class="ws-hover-card__title"></span>';
339
459
  area.append(card);
340
460
  }
461
+ /* Several series: the card sits over the period's highest dot and reads every series, a row each with its
462
+ swatch in the key's order (navy, teal, purple), from the table's columns. */
463
+ const tops = $$('.ws-trend-line__point', area).filter((p) => Number(p.style.getPropertyValue('--i')) === i).map((p) => p.style.getPropertyValue('--y').trim());
464
+ const top = tops.sort((a, b) => parseFloat(a) - parseFloat(b))[0];
341
465
  card.style.left = x + '%';
342
- card.style.top = 'calc(' + pt.style.getPropertyValue('--y') + ' - ' + (fig.classList.contains('ws-trend-line--bars') ? '0px' : 'var(--ws-trend-line-dot)') + ')';
466
+ card.style.top = 'calc(' + top + ' - ' + (fig.classList.contains('ws-trend-line--bars') ? '0px' : 'var(--ws-trend-line-dot)') + ')';
343
467
  card.classList.toggle('ws-hover-card--start', x < 25);
344
468
  card.classList.toggle('ws-hover-card--end', x > 75);
345
469
  card.querySelector('.ws-hover-card__eyebrow > span:last-child').textContent = rows[i].querySelector('th').textContent;
346
- card.querySelector('.ws-hover-card__title').textContent = rows[i].querySelector('td').textContent;
470
+ const cells = $$('td', rows[i]), title = card.querySelector('.ws-hover-card__title');
471
+ const tones = ['var(--color-text-heading)', 'var(--ws-chart-finished)', 'var(--ws-chart-past-half)'];
472
+ $$('.ws-hover-card__row', card).forEach((r) => r.replaceWith());
473
+ title.hidden = cells.length > 1;
474
+ card.querySelector('.ws-hover-card__eyebrow > .ws-hover-card__swatch').hidden = cells.length > 1;
475
+ if (cells.length === 1) title.textContent = cells[0].textContent;
476
+ else cells.forEach((td, j) => {
477
+ const row = document.createElement('span');
478
+ row.className = 'ws-hover-card__row';
479
+ row.innerHTML = '<span class="ws-hover-card__swatch" style="--ws-hover-card-swatch: ' + tones[j] + ';"></span><span class="ws-hover-card__label"></span><span class="ws-hover-card__value"></span>';
480
+ row.children[1].textContent = $$('thead th', fig)[j + 1].textContent;
481
+ row.children[2].textContent = td.textContent;
482
+ card.append(row);
483
+ });
347
484
  };
348
485
  const hide = () => {
349
486
  clearTimeout(leave);