@felloinnovations/brand-kit 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (363) hide show
  1. package/AGENTS.md +198 -24
  2. package/CHANGELOG.md +197 -0
  3. package/README.md +34 -7
  4. package/design-system/01-how-claude-decides.md +26 -17
  5. package/design-system/02-principles.md +11 -6
  6. package/design-system/03-colour.md +134 -47
  7. package/design-system/04-type.md +45 -23
  8. package/design-system/05-space-and-layout.md +175 -52
  9. package/design-system/06-sizing-profiles.md +4 -4
  10. package/design-system/07-scrolling.md +66 -20
  11. package/design-system/08-choosing-components.md +50 -22
  12. package/design-system/09-charts.md +71 -28
  13. package/design-system/10-patterns.md +33 -10
  14. package/design-system/11-page-recipes.md +110 -23
  15. package/design-system/12-content.md +2 -1
  16. package/design-system/13-motion.md +330 -47
  17. package/design-system/14-accessibility.md +47 -24
  18. package/design-system/15-ai-experiences.md +11 -6
  19. package/design-system/16-creativity.md +1 -1
  20. package/design-system/17-decisions.md +357 -1
  21. package/design-system/18-checklist.md +79 -16
  22. package/design-system/19-public-pages.md +6 -4
  23. package/design-system/20-palettes.md +275 -0
  24. package/design-system/README.md +30 -17
  25. package/design-system/components/ActionList/README.md +96 -10
  26. package/design-system/components/ActionList/preview.html +52 -7
  27. package/design-system/components/ActionList/style.css +64 -0
  28. package/design-system/components/ActivityGrid/README.md +38 -2
  29. package/design-system/components/ActivityGrid/preview.html +1 -1
  30. package/design-system/components/ActivityGrid/style.css +35 -0
  31. package/design-system/components/AppShell/README.md +205 -75
  32. package/design-system/components/AppShell/preview.html +339 -58
  33. package/design-system/components/AppShell/style.css +154 -0
  34. package/design-system/components/Avatar/README.md +37 -10
  35. package/design-system/components/Avatar/preview.html +13 -4
  36. package/design-system/components/Avatar/style.css +14 -0
  37. package/design-system/components/Badge/README.md +41 -19
  38. package/design-system/components/Badge/preview.html +13 -10
  39. package/design-system/components/Badge/style.css +23 -0
  40. package/design-system/components/Banner/README.md +30 -9
  41. package/design-system/components/Banner/preview.html +1 -1
  42. package/design-system/components/Banner/style.css +19 -0
  43. package/design-system/components/Breadcrumb/README.md +58 -46
  44. package/design-system/components/Breadcrumb/preview.html +16 -16
  45. package/design-system/components/Breadcrumb/style.css +17 -0
  46. package/design-system/components/Button/README.md +113 -55
  47. package/design-system/components/Button/preview.html +98 -25
  48. package/design-system/components/Button/style.css +86 -0
  49. package/design-system/components/Card/README.md +158 -49
  50. package/design-system/components/Card/preview.html +141 -19
  51. package/design-system/components/Card/style.css +115 -0
  52. package/design-system/components/CardBoard/README.md +58 -37
  53. package/design-system/components/CardBoard/preview.html +26 -29
  54. package/design-system/components/ChatCard/README.md +115 -29
  55. package/design-system/components/ChatCard/preview.html +149 -32
  56. package/design-system/components/ChatCard/style.css +49 -0
  57. package/design-system/components/ChatShell/README.md +116 -0
  58. package/design-system/components/ChatShell/preview.html +201 -0
  59. package/design-system/components/ChatShell/style.css +86 -0
  60. package/design-system/components/ChatTurn/README.md +78 -35
  61. package/design-system/components/ChatTurn/preview.html +60 -28
  62. package/design-system/components/ChatTurn/style.css +50 -0
  63. package/design-system/components/CheckRow/README.md +141 -0
  64. package/design-system/components/CheckRow/preview.html +140 -0
  65. package/design-system/components/CheckRow/style.css +88 -0
  66. package/design-system/components/Checkbox/README.md +58 -22
  67. package/design-system/components/Checkbox/preview.html +112 -50
  68. package/design-system/components/Checkbox/style.css +45 -0
  69. package/design-system/components/Chip/README.md +78 -47
  70. package/design-system/components/Chip/preview.html +29 -4
  71. package/design-system/components/Chip/style.css +17 -0
  72. package/design-system/components/Combobox/README.md +44 -11
  73. package/design-system/components/Combobox/preview.html +35 -12
  74. package/design-system/components/Combobox/style.css +54 -0
  75. package/design-system/components/Composer/README.md +137 -35
  76. package/design-system/components/Composer/preview.html +115 -40
  77. package/design-system/components/Composer/style.css +81 -0
  78. package/design-system/components/ConfirmDialog/README.md +73 -52
  79. package/design-system/components/ConfirmDialog/preview.html +70 -26
  80. package/design-system/components/ConfirmDialog/style.css +5 -0
  81. package/design-system/components/Cover/preview.html +3 -3
  82. package/design-system/components/Cover/style.css +19 -0
  83. package/design-system/components/DateInput/README.md +77 -12
  84. package/design-system/components/DateInput/preview.html +27 -12
  85. package/design-system/components/DateInput/style.css +38 -0
  86. package/design-system/components/Dialog/README.md +112 -44
  87. package/design-system/components/Dialog/preview.html +141 -18
  88. package/design-system/components/Dialog/style.css +74 -0
  89. package/design-system/components/EmptyState/README.md +42 -12
  90. package/design-system/components/EmptyState/preview.html +11 -11
  91. package/design-system/components/EmptyState/style.css +9 -0
  92. package/design-system/components/EntryShell/README.md +73 -48
  93. package/design-system/components/EntryShell/preview.html +60 -27
  94. package/design-system/components/EntryShell/style.css +19 -0
  95. package/design-system/components/FeatureGrid/README.md +61 -39
  96. package/design-system/components/FeatureGrid/preview.html +2 -2
  97. package/design-system/components/FeatureGrid/style.css +20 -0
  98. package/design-system/components/FileUpload/README.md +33 -3
  99. package/design-system/components/FileUpload/preview.html +18 -4
  100. package/design-system/components/FileUpload/style.css +23 -0
  101. package/design-system/components/FilterBar/README.md +114 -0
  102. package/design-system/components/FilterBar/preview.html +82 -0
  103. package/design-system/components/FilterBar/style.css +22 -0
  104. package/design-system/components/Fold/README.md +84 -26
  105. package/design-system/components/Fold/preview.html +77 -34
  106. package/design-system/components/Fold/style.css +48 -0
  107. package/design-system/components/Footer/README.md +62 -39
  108. package/design-system/components/Footer/preview.html +5 -5
  109. package/design-system/components/Footer/style.css +30 -0
  110. package/design-system/components/FunnelBars/README.md +24 -3
  111. package/design-system/components/FunnelBars/preview.html +5 -5
  112. package/design-system/components/FunnelBars/style.css +13 -0
  113. package/design-system/components/GoalBars/README.md +54 -10
  114. package/design-system/components/GoalBars/preview.html +69 -28
  115. package/design-system/components/GoalBars/style.css +51 -0
  116. package/design-system/components/GoalTicks/README.md +74 -58
  117. package/design-system/components/GoalTicks/preview.html +41 -118
  118. package/design-system/components/GoalTicks/style.css +37 -0
  119. package/design-system/components/GoalTubes/README.md +29 -3
  120. package/design-system/components/GoalTubes/preview.html +6 -6
  121. package/design-system/components/GoalTubes/style.css +23 -0
  122. package/design-system/components/Heatmap/README.md +133 -0
  123. package/design-system/components/Heatmap/preview.html +168 -0
  124. package/design-system/components/Heatmap/style.css +119 -0
  125. package/design-system/components/Hero/README.md +58 -40
  126. package/design-system/components/Hero/preview.html +1 -1
  127. package/design-system/components/Hero/style.css +19 -0
  128. package/design-system/components/HoverCard/README.md +114 -0
  129. package/design-system/components/HoverCard/preview.html +107 -0
  130. package/design-system/components/HoverCard/style.css +59 -0
  131. package/design-system/components/IconButton/README.md +69 -38
  132. package/design-system/components/IconButton/preview.html +77 -43
  133. package/design-system/components/IconButton/style.css +45 -0
  134. package/design-system/components/Input/README.md +36 -11
  135. package/design-system/components/Input/preview.html +1 -1
  136. package/design-system/components/Input/style.css +23 -0
  137. package/design-system/components/KanbanBoard/README.md +93 -45
  138. package/design-system/components/KanbanBoard/preview.html +159 -43
  139. package/design-system/components/KanbanBoard/style.css +53 -0
  140. package/design-system/components/Kbd/README.md +25 -9
  141. package/design-system/components/Kbd/preview.html +6 -2
  142. package/design-system/components/Kbd/style.css +9 -0
  143. package/design-system/components/KitButton/README.md +81 -47
  144. package/design-system/components/KitButton/preview.html +35 -13
  145. package/design-system/components/KitButton/style.css +46 -0
  146. package/design-system/components/KitCard/README.md +75 -44
  147. package/design-system/components/KitCard/preview.html +18 -10
  148. package/design-system/components/KitCard/style.css +15 -0
  149. package/design-system/components/KitInput/README.md +54 -39
  150. package/design-system/components/KitInput/preview.html +7 -7
  151. package/design-system/components/KitInput/style.css +23 -0
  152. package/design-system/components/Leaderboard/README.md +76 -49
  153. package/design-system/components/Leaderboard/preview.html +61 -26
  154. package/design-system/components/Leaderboard/style.css +44 -0
  155. package/design-system/components/ListDetail/README.md +222 -0
  156. package/design-system/components/ListDetail/preview.html +213 -0
  157. package/design-system/components/ListDetail/style.css +148 -0
  158. package/design-system/components/LoadBar/README.md +79 -0
  159. package/design-system/components/LoadBar/preview.html +40 -0
  160. package/design-system/components/LoadBar/style.css +17 -0
  161. package/design-system/components/MarketingNav/README.md +76 -42
  162. package/design-system/components/MarketingNav/preview.html +37 -8
  163. package/design-system/components/MarketingNav/style.css +45 -0
  164. package/design-system/components/Matrix/README.md +129 -0
  165. package/design-system/components/Matrix/preview.html +99 -0
  166. package/design-system/components/Matrix/style.css +84 -0
  167. package/design-system/components/Menu/README.md +117 -41
  168. package/design-system/components/Menu/preview.html +85 -23
  169. package/design-system/components/Menu/style.css +61 -0
  170. package/design-system/components/Meter/README.md +39 -4
  171. package/design-system/components/Meter/preview.html +18 -1
  172. package/design-system/components/Meter/style.css +23 -0
  173. package/design-system/components/MetricTile/README.md +237 -0
  174. package/design-system/components/MetricTile/preview.html +241 -0
  175. package/design-system/components/MetricTile/style.css +129 -0
  176. package/design-system/components/Morph/README.md +106 -0
  177. package/design-system/components/Morph/preview.html +103 -0
  178. package/design-system/components/Morph/style.css +31 -0
  179. package/design-system/components/Motion/README.md +164 -0
  180. package/design-system/components/Motion/preview.html +2537 -0
  181. package/design-system/components/NumberInput/README.md +27 -8
  182. package/design-system/components/NumberInput/preview.html +21 -5
  183. package/design-system/components/NumberInput/style.css +34 -0
  184. package/design-system/components/PaceBar/README.md +75 -0
  185. package/design-system/components/PaceBar/preview.html +67 -0
  186. package/design-system/components/PaceBar/style.css +25 -0
  187. package/design-system/components/Page/README.md +57 -18
  188. package/design-system/components/Page/preview.html +15 -17
  189. package/design-system/components/Page/style.css +7 -0
  190. package/design-system/components/PageBar/README.md +90 -34
  191. package/design-system/components/PageBar/preview.html +60 -19
  192. package/design-system/components/PageBar/style.css +35 -0
  193. package/design-system/components/PageHero/README.md +87 -51
  194. package/design-system/components/PageHero/preview.html +35 -6
  195. package/design-system/components/PageHero/style.css +31 -0
  196. package/design-system/components/Pagination/README.md +70 -49
  197. package/design-system/components/Pagination/preview.html +24 -15
  198. package/design-system/components/Pagination/style.css +12 -0
  199. package/design-system/components/Panel/README.md +109 -51
  200. package/design-system/components/Panel/preview.html +118 -20
  201. package/design-system/components/Panel/style.css +51 -0
  202. package/design-system/components/PasswordInput/README.md +50 -18
  203. package/design-system/components/PasswordInput/preview.html +71 -21
  204. package/design-system/components/PasswordInput/style.css +31 -0
  205. package/design-system/components/Plate/README.md +75 -17
  206. package/design-system/components/Plate/preview.html +40 -7
  207. package/design-system/components/Plate/style.css +37 -0
  208. package/design-system/components/Popover/README.md +70 -49
  209. package/design-system/components/Popover/preview.html +44 -22
  210. package/design-system/components/Popover/style.css +41 -0
  211. package/design-system/components/PricingTable/README.md +69 -49
  212. package/design-system/components/PricingTable/preview.html +21 -21
  213. package/design-system/components/PricingTable/style.css +21 -0
  214. package/design-system/components/ProgressPetals/README.md +31 -3
  215. package/design-system/components/ProgressPetals/preview.html +14 -15
  216. package/design-system/components/ProgressPetals/style.css +27 -0
  217. package/design-system/components/README.md +151 -0
  218. package/design-system/components/Radio/README.md +40 -17
  219. package/design-system/components/Radio/preview.html +16 -1
  220. package/design-system/components/Radio/style.css +21 -0
  221. package/design-system/components/RankBar/README.md +29 -2
  222. package/design-system/components/RankBar/preview.html +14 -12
  223. package/design-system/components/RankBar/style.css +18 -0
  224. package/design-system/components/Ring/README.md +61 -11
  225. package/design-system/components/Ring/preview.html +55 -10
  226. package/design-system/components/Ring/style.css +49 -0
  227. package/design-system/components/ScoreHero/README.md +91 -0
  228. package/design-system/components/ScoreHero/preview.html +104 -0
  229. package/design-system/components/ScoreHero/style.css +32 -0
  230. package/design-system/components/Section/README.md +72 -33
  231. package/design-system/components/Section/preview.html +46 -16
  232. package/design-system/components/Section/style.css +18 -0
  233. package/design-system/components/Segmented/README.md +100 -55
  234. package/design-system/components/Segmented/preview.html +107 -26
  235. package/design-system/components/Segmented/style.css +94 -0
  236. package/design-system/components/SegmentedProgress/README.md +64 -0
  237. package/design-system/components/SegmentedProgress/preview.html +69 -0
  238. package/design-system/components/SegmentedProgress/style.css +11 -0
  239. package/design-system/components/Select/README.md +47 -12
  240. package/design-system/components/Select/preview.html +28 -2
  241. package/design-system/components/Select/style.css +22 -0
  242. package/design-system/components/ShareBar/README.md +69 -0
  243. package/design-system/components/ShareBar/preview.html +72 -0
  244. package/design-system/components/ShareBar/style.css +25 -0
  245. package/design-system/components/SideList/README.md +147 -0
  246. package/design-system/components/SideList/preview.html +139 -0
  247. package/design-system/components/SideList/style.css +94 -0
  248. package/design-system/components/Skeleton/README.md +52 -16
  249. package/design-system/components/Skeleton/preview.html +35 -23
  250. package/design-system/components/Skeleton/style.css +22 -0
  251. package/design-system/components/Slider/README.md +95 -0
  252. package/design-system/components/Slider/preview.html +49 -0
  253. package/design-system/components/Slider/style.css +13 -0
  254. package/design-system/components/Split/README.md +112 -0
  255. package/design-system/components/Split/preview.html +105 -0
  256. package/design-system/components/Split/style.css +30 -0
  257. package/design-system/components/Starter/README.md +21 -9
  258. package/design-system/components/Starter/preview.html +5 -5
  259. package/design-system/components/Stat/README.md +64 -43
  260. package/design-system/components/Stat/preview.html +3 -3
  261. package/design-system/components/Stat/style.css +15 -0
  262. package/design-system/components/StatTiles/README.md +88 -44
  263. package/design-system/components/StatTiles/preview.html +142 -71
  264. package/design-system/components/StatTiles/style.css +64 -0
  265. package/design-system/components/StatusLine/README.md +85 -0
  266. package/design-system/components/StatusLine/preview.html +49 -0
  267. package/design-system/components/StatusLine/style.css +14 -0
  268. package/design-system/components/StatusScreen/README.md +49 -35
  269. package/design-system/components/StatusScreen/preview.html +20 -5
  270. package/design-system/components/StatusScreen/style.css +9 -0
  271. package/design-system/components/StepLine/README.md +97 -0
  272. package/design-system/components/StepLine/preview.html +108 -0
  273. package/design-system/components/StepLine/style.css +62 -0
  274. package/design-system/components/Stepper/README.md +35 -13
  275. package/design-system/components/Stepper/style.css +13 -0
  276. package/design-system/components/StreakCalendar/README.md +22 -1
  277. package/design-system/components/StreakCalendar/preview.html +10 -10
  278. package/design-system/components/StreakCalendar/style.css +12 -0
  279. package/design-system/components/SuggestionChip/README.md +87 -0
  280. package/design-system/components/SuggestionChip/preview.html +42 -0
  281. package/design-system/components/SuggestionChip/style.css +20 -0
  282. package/design-system/components/Switch/README.md +44 -24
  283. package/design-system/components/Switch/preview.html +23 -8
  284. package/design-system/components/Switch/style.css +31 -0
  285. package/design-system/components/Table/README.md +129 -58
  286. package/design-system/components/Table/preview.html +106 -72
  287. package/design-system/components/Table/style.css +104 -0
  288. package/design-system/components/Tabs/README.md +71 -46
  289. package/design-system/components/Tabs/preview.html +56 -24
  290. package/design-system/components/Tabs/style.css +44 -0
  291. package/design-system/components/Testimonial/README.md +48 -32
  292. package/design-system/components/Testimonial/preview.html +14 -14
  293. package/design-system/components/Testimonial/style.css +12 -0
  294. package/design-system/components/TextArea/README.md +27 -11
  295. package/design-system/components/TextArea/preview.html +8 -2
  296. package/design-system/components/TextArea/style.css +12 -0
  297. package/design-system/components/ThemeSwitch/README.md +116 -0
  298. package/design-system/components/ThemeSwitch/preview.html +57 -0
  299. package/design-system/components/ThemeSwitch/style.css +6 -0
  300. package/design-system/components/TickRow/README.md +52 -36
  301. package/design-system/components/TickRow/preview.html +64 -41
  302. package/design-system/components/TickRow/style.css +23 -0
  303. package/design-system/components/TickStrip/README.md +95 -0
  304. package/design-system/components/TickStrip/preview.html +88 -0
  305. package/design-system/components/TickStrip/style.css +42 -0
  306. package/design-system/components/Tile/README.md +79 -18
  307. package/design-system/components/Tile/preview.html +91 -40
  308. package/design-system/components/Tile/style.css +36 -0
  309. package/design-system/components/Timeline/README.md +68 -48
  310. package/design-system/components/Timeline/preview.html +27 -15
  311. package/design-system/components/Timeline/style.css +25 -0
  312. package/design-system/components/Toast/README.md +70 -38
  313. package/design-system/components/Toast/preview.html +43 -8
  314. package/design-system/components/Toast/style.css +25 -0
  315. package/design-system/components/Tooltip/README.md +93 -35
  316. package/design-system/components/Tooltip/preview.html +69 -15
  317. package/design-system/components/Tooltip/style.css +78 -0
  318. package/design-system/components/TopBar/README.md +67 -53
  319. package/design-system/components/TopBar/preview.html +14 -27
  320. package/design-system/components/TopBar/style.css +35 -0
  321. package/design-system/components/TopicRail/README.md +108 -0
  322. package/design-system/components/TopicRail/preview.html +58 -0
  323. package/design-system/components/TopicRail/style.css +48 -0
  324. package/design-system/components/TrendBars/README.md +52 -14
  325. package/design-system/components/TrendBars/preview.html +65 -32
  326. package/design-system/components/TrendBars/style.css +25 -0
  327. package/design-system/components/TrendLine/README.md +164 -0
  328. package/design-system/components/TrendLine/preview.html +373 -0
  329. package/design-system/components/TrendLine/style.css +155 -0
  330. package/design-system/components/Well/README.md +64 -22
  331. package/design-system/components/Well/preview.html +14 -14
  332. package/design-system/components/Well/style.css +22 -0
  333. package/design-system/components/_house.css +324 -0
  334. package/design-system/components/bundle.css +4091 -744
  335. package/design-system/tokens.json +1314 -124
  336. package/dist/fello-core.css +2 -2
  337. package/dist/fello.css +6 -1369
  338. package/dist/fello.scoped.css +7 -0
  339. package/dist/index.d.ts +110 -11
  340. package/dist/index.js +781 -96
  341. package/dist/palettes.d.ts +464 -0
  342. package/dist/palettes.js +497 -0
  343. package/dist/scoped.d.ts +3 -0
  344. package/dist/scoped.js +16 -0
  345. package/dist/toast.d.ts +14 -0
  346. package/dist/toast.js +33 -29
  347. package/dist/tokens.d.ts +1686 -62
  348. package/dist/tokens.js +1668 -61
  349. package/package.json +20 -4
  350. package/scripts/check-palette.mjs +295 -0
  351. package/scripts/token-model.mjs +552 -0
  352. package/tokens/_dark.css +174 -0
  353. package/tokens/_palettes.css +46 -0
  354. package/tokens/_product.css +266 -53
  355. package/tokens/_source.hubl.css +39 -19
  356. package/tokens/design-tokens.json +319 -0
  357. package/tokens/palettes.ts +504 -0
  358. package/tokens/shadcn-compat.css +19 -19
  359. package/tokens/shadcn-compat.hsl.css +76 -0
  360. package/tokens/tokens.css +1292 -62
  361. package/tokens/tokens.md +588 -61
  362. package/tokens/tokens.root.css +1298 -62
  363. package/tokens/tokens.ts +1677 -64
@@ -0,0 +1,129 @@
1
+ /* ---- MetricTile: North's key metric, a rimmed tile that opens across its row into its chart and a side column ---- */
2
+ /* The set: 16 apart; one across under 30rem, two from 30rem, four from 60rem (it measures itself). */
3
+ .ws-metric-tiles { container-type: inline-size; }
4
+ .ws-metric-tiles__grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-md); }
5
+ @container (min-width: 30rem) { .ws-metric-tiles__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
6
+ @container (min-width: 60rem) { .ws-metric-tiles__grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
7
+ /* Closed: the tray rim (8 outside, 7 inside), a cream surface 16 inside with 12 between label, figure and bar, and a
8
+ foot on the tray. The whole tile is one button that opens it; hover and focus darken the rim (Seth's well hover). */
9
+ .ws-metric-tile { display: flex; min-width: 0; flex-direction: column; border: 0; border-radius: var(--ws-radius-card); background: var(--ws-tray); padding: var(--ws-rim); color: inherit; text-align: left; text-decoration: none; cursor: pointer; }
10
+ .ws-metric-tile:focus-visible, .ws-metric-tile--selected { background: var(--ws-well-hover); }
11
+ @media (hover: hover) { .ws-metric-tile:not(.ws-metric-tile--open):hover { background: var(--ws-well-hover); } }
12
+ .ws-metric-tile__surface { display: flex; flex: 1; min-width: 0; flex-direction: column; gap: var(--space-sm); padding: var(--space-md); border-radius: var(--ws-radius-card-inner); background: var(--color-bg-page); }
13
+ .ws-metric-tile__head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-xs); min-width: 0; }
14
+ .ws-metric-tile__label { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-size: var(--ws-text-control); font-weight: 500; line-height: var(--ws-leading-control); color: var(--color-text-body); }
15
+ /* The go arrow rests 4 px back and steps forward under the pointer or the focus. On touch, where nothing hovers, it
16
+ sits on the content edge. */
17
+ .ws-metric-tile__go { flex-shrink: 0; width: var(--ws-icon); height: var(--ws-icon); color: var(--color-text-heading); }
18
+ @media (hover: hover) {
19
+ .ws-metric-tile__go { transform: translateX(calc(var(--ws-nudge) * -1)); }
20
+ .ws-metric-tile:hover .ws-metric-tile__go, .ws-metric-tile:focus-visible .ws-metric-tile__go { transform: none; }
21
+ }
22
+ .ws-metric-tile__value { white-space: nowrap; font-size: var(--font-size-3xl); font-weight: 400; line-height: var(--space-3xl); letter-spacing: var(--ws-tracking-display); color: var(--color-text-heading); font-variant-numeric: tabular-nums; }
23
+ @media (prefers-reduced-motion: no-preference) {
24
+ .ws-metric-tile { transition: background-color var(--ws-duration-hover) var(--ws-ease-soft); }
25
+ .ws-metric-tile__go { transition: transform var(--ws-duration-settle) var(--ws-ease-soft); }
26
+ .ws-metric-tile__preview { transition: filter var(--ws-duration-shrink) ease; }
27
+ }
28
+ /* A closed preview chart is greyed and wakes to its colours under the pointer or the focus (North). */
29
+ .ws-metric-tile__preview { display: flex; align-items: center; height: var(--space-lg); filter: grayscale(1) contrast(0.55) brightness(1.32); }
30
+ .ws-metric-tile:focus-visible .ws-metric-tile__preview { display: flex; align-items: center; height: var(--space-lg); filter: none; }
31
+ @media (hover: hover) { .ws-metric-tile:hover .ws-metric-tile__preview { display: flex; align-items: center; height: var(--space-lg); filter: none; } }
32
+ /* The mini charts (North's Home tiles), in the bar's place: a column strip (__cols, one __col a period, the period in
33
+ progress __col--now, lighter) or a line with dots (__line, an inline SVG: one <line> a step, one <circle> a point,
34
+ the last __now and larger). Each is 36 tall and reaches up into the 12 above it, so its baseline sits where a
35
+ PaceBar's 24 slot ends and the tile keeps its height. Columns 8 apart and stopping 12 short of the right edge, 2 px
36
+ top corners, cut every --step of the height by a fine line in the ground's colour (North's scale lines through the
37
+ bars); the baseline runs the full width. The line's points run from 4 in to 12 short, over a 0.5 px mid rule. Grey
38
+ at rest (the heading navy at 20% over the ground whatever the tone, the period in progress 11%, so every tile rests
39
+ the same grey); under the pointer or the focus they wake to the tone (the period in progress 55%) over 400 ms, as
40
+ a preview does. --tone on the chart, navy by default. A zero column is a 1 px stub on the baseline, never left out. */
41
+ .ws-metric-tile { --woke: 0; }
42
+ .ws-metric-tile:focus-visible { --woke: 1; }
43
+ @media (hover: hover) { .ws-metric-tile:hover { --woke: 1; } }
44
+ .ws-metric-tile__cols, .ws-metric-tile__line { --mark-tone: var(--tone, var(--color-text-heading)); --mark-hue: color-mix(in srgb, var(--mark-tone) calc(var(--woke, 1) * 100%), var(--color-text-heading)); --mark: color-mix(in srgb, var(--mark-hue) calc(20% + var(--woke, 1) * 80%), var(--color-bg-page)); position: relative; flex-shrink: 0; height: calc(var(--space-lg) + var(--space-sm)); margin-top: calc(var(--space-sm) * -1); }
45
+ .ws-metric-tile__cols { display: flex; align-items: flex-end; gap: var(--space-xs); padding-right: var(--space-sm); border-bottom: var(--ws-line) solid color-mix(in srgb, var(--ws-edge-card) calc(var(--woke, 1) * 100%), var(--ws-chart-foot)); }
46
+ .ws-metric-tile__cols::after { content: ""; position: absolute; inset: 0 var(--space-sm) 0 0; background: repeating-linear-gradient(to top, transparent 0 calc(var(--step) - var(--ws-hairline) * 1.5), var(--color-bg-page) 0 var(--step)); pointer-events: none; }
47
+ .ws-metric-tile__col { flex: 1; min-width: 0; height: max(var(--ws-line), var(--v, 0%)); border-radius: var(--ws-radius-chip) var(--ws-radius-chip) 0 0; background: var(--mark); transform-origin: bottom; }
48
+ .ws-metric-tile__col--now { background: color-mix(in srgb, var(--mark-hue) calc(11% + var(--woke, 1) * 44%), var(--color-bg-page)); }
49
+ .ws-metric-tile__line::before { content: ""; position: absolute; top: 50%; left: 0; right: 0; border-top: var(--ws-hairline) solid color-mix(in srgb, var(--ws-edge-slot) calc(var(--woke, 1) * 100%), var(--ws-chart-foot)); }
50
+ .ws-metric-tile__line > svg { position: absolute; top: 0; left: var(--space-xxs); width: calc(100% - var(--space-xxs) - var(--space-sm)); height: 100%; overflow: visible; }
51
+ .ws-metric-tile__line line { stroke: var(--mark); stroke-width: calc(var(--ws-hairline) * 2.5); stroke-linecap: round; }
52
+ .ws-metric-tile__line circle { r: calc(var(--ws-hairline) * 5); fill: var(--mark); stroke: var(--color-bg-page); stroke-width: calc(var(--ws-hairline) * 3); }
53
+ .ws-metric-tile__line .ws-metric-tile__now { r: calc(var(--ws-hairline) * 9); }
54
+ @media (prefers-reduced-motion: no-preference) {
55
+ .ws-metric-tile__cols, .ws-metric-tile__col { transition: background-color var(--ws-duration-shrink) ease, border-color var(--ws-duration-shrink) ease; }
56
+ .ws-metric-tile__line::before { transition: border-color var(--ws-duration-shrink) ease; }
57
+ .ws-metric-tile__line :is(line, circle) { transition: stroke var(--ws-duration-shrink) ease, fill var(--ws-duration-shrink) ease; }
58
+ /* The draw (North's Home intro, "Metric + goal bars fill"): with --draw on the tile its chart draws over the card's
59
+ share of the page fill (--ws-duration-fill-page x 0.79: North's .21 card stagger leaves each card 0.79 of it).
60
+ Column i of --n starts at its share of the first half and rises from the baseline over the second half on the
61
+ settle curve. The line draws left to right at an even pace, step i (--i) after step i - 1 (each <line> wears
62
+ pathLength="1"), over the first three quarters; each dot pops as the line reaches it. Then the foot's figure rises
63
+ in. In a .ws-build card it all starts at the card's --ws-build-at. */
64
+ .ws-metric-tile--draw { --ws-tile-draw: calc(var(--ws-duration-fill-page) * 0.79); }
65
+ .ws-metric-tile--draw .ws-metric-tile__col { animation: ws-grow-up calc(var(--ws-tile-draw) * 0.5) var(--ws-ease-settle) backwards; animation-delay: calc(var(--ws-build-at, 0s) + var(--ws-tile-draw) * 0.5 * var(--i, 0) / max(var(--n, 2) - 1, 1)); }
66
+ .ws-metric-tile--draw .ws-metric-tile__line line { stroke-dasharray: 1 1; animation: ws-draw calc(var(--ws-tile-draw) * 0.75 / max(var(--n, 2) - 1, 1)) var(--ws-ease-steady) backwards; animation-delay: calc(var(--ws-build-at, 0s) + var(--ws-tile-draw) * 0.75 * var(--i, 0) / max(var(--n, 2) - 1, 1)); }
67
+ .ws-metric-tile--draw .ws-metric-tile__line circle { transform-box: fill-box; transform-origin: center; animation: ws-bloom var(--ws-duration-settle) var(--ws-ease-pop) backwards; animation-delay: calc(var(--ws-build-at, 0s) + var(--ws-tile-draw) * 0.75 * var(--i, 0) / max(var(--n, 2) - 1, 1)); }
68
+ .ws-metric-tile--draw .ws-metric-tile__foot b { animation: ws-rise var(--ws-duration-settle) var(--ws-ease-soft) backwards; animation-delay: calc(var(--ws-build-at, 0s) + var(--ws-tile-draw)); }
69
+ }
70
+ /* The foot on the tray: 32 tall, 16 at the sides, 12/16 words; figures 600 and never cut, the words give way first. */
71
+ .ws-metric-tile__foot { display: flex; align-items: center; justify-content: space-between; gap: var(--space-sm); min-height: var(--space-xl); padding: var(--space-xs) var(--space-md); font-size: var(--ws-text-dense); line-height: var(--space-md); color: var(--color-text-body); }
72
+ .ws-metric-tile__foot > span { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
73
+ .ws-metric-tile__foot b { flex-shrink: 0; font-weight: 600; color: var(--color-text-heading); }
74
+ /* Dense (North's KPI rail): 12 / 14 inside, 4 apart, a 28/32 figure. */
75
+ .ws-metric-tile--dense .ws-metric-tile__surface { gap: var(--space-xxs); padding: var(--space-sm) var(--space-sm-plus); }
76
+ .ws-metric-tile--dense .ws-metric-tile__label { font-size: var(--ws-text-dense); line-height: var(--space-md); }
77
+ .ws-metric-tile--dense .ws-metric-tile__value { font-size: var(--ws-text-figure-row); line-height: var(--ws-leading-figure-row); }
78
+ /* Selected in a row of tiles: the darker rim and a 2 px navy tab 8 under it, 16 in from each side. */
79
+ .ws-metric-tile--selected { position: relative; }
80
+ .ws-metric-tile--selected::after { content: ""; position: absolute; left: var(--space-md); right: var(--space-md); bottom: calc((var(--space-xs) + var(--ws-ring)) * -1); height: var(--ws-ring); border-radius: var(--ws-radius-chip); background: var(--color-text-heading); }
81
+
82
+ /* Open: the tile takes the whole row (12 outside, 11 inside), the others are gone; its surface is the chart (20 24 8 28)
83
+ beside a 420 side column behind a 0.5 px rule. Under 60rem the side column moves under the chart. */
84
+ .ws-metric-tiles__grid:has(> .ws-metric-tile--open) > :not(.ws-metric-tile--open) { display: none; }
85
+ .ws-metric-tile--open { grid-column: 1 / -1; min-height: calc(var(--space-9xl) + var(--space-8xl) + var(--space-md)); border-radius: var(--ws-radius-drawer); cursor: default; }
86
+ .ws-metric-tile--open > .ws-metric-tile__surface { display: grid; grid-template-columns: minmax(0, 1fr) calc(var(--space-9xl) * 2 + var(--space-md) + var(--space-xxs)); gap: 0; padding: 0; border-radius: var(--ws-radius-drawer-inner); }
87
+ .ws-metric-tile__main { display: flex; min-width: 0; flex-direction: column; gap: var(--space-xxs-plus); padding: var(--ws-card-inset) var(--space-lg) var(--space-xs) var(--ws-band-inset); }
88
+ .ws-metric-tile--open .ws-metric-tile__head { align-items: flex-start; }
89
+ /* The open label names the metric at 20 px; its dotted line says a description opens from it. */
90
+ .ws-metric-tile--open .ws-metric-tile__label { font-size: var(--font-size-lg2); line-height: var(--space-lg); text-decoration: underline dotted; text-decoration-thickness: calc(var(--ws-hairline) * 3); text-underline-offset: var(--space-xxs-plus); }
91
+ .ws-metric-tile--open .ws-metric-tile__value { font-size: calc(var(--space-3xl) + var(--space-xxs)); line-height: calc(var(--space-3xl) + var(--space-xs)); color: var(--tone-word, var(--color-text-heading)); }
92
+ .ws-metric-tile__chart { position: relative; flex: 1; min-height: var(--space-9xl); }
93
+ .ws-metric-tile__chart--segs { display: flex; flex: 0 1 auto; min-height: 0; flex-direction: column; gap: var(--space-lg); padding: var(--space-md) 0 var(--space-xs); }
94
+ /* A segment of the open chart (North's breakdown): name, figure and its share of pace over a PaceBar, 10 under. */
95
+ .ws-metric-tile__seg-line { display: flex; align-items: baseline; gap: var(--space-xs-plus); font-size: var(--ws-text-body); line-height: var(--ws-leading-row); color: var(--color-text-heading); }
96
+ .ws-metric-tile__seg-line > span { flex: 1; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-weight: 500; }
97
+ .ws-metric-tile__seg-line > b { font-weight: 600; font-variant-numeric: tabular-nums; }
98
+ .ws-metric-tile__seg-line > em { width: calc(var(--space-2xl) + var(--space-xxs)); font-style: normal; font-size: var(--ws-text-control); text-align: right; color: var(--color-text-muted); font-variant-numeric: tabular-nums; }
99
+ .ws-metric-tile__seg .ws-pace-bar { margin-top: var(--space-xxs); }
100
+ .ws-metric-tile__key { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-xxs) var(--space-md); font-size: var(--ws-text-dense); font-weight: 500; line-height: var(--space-md); color: var(--color-text-body); }
101
+ .ws-metric-tile__key > span { display: inline-flex; align-items: center; gap: var(--space-xxs-plus); }
102
+ .ws-metric-tile__key i { width: var(--space-xs); height: var(--space-xs); border-radius: var(--ws-radius-chip); background: var(--tone); }
103
+ .ws-metric-tile__side { display: flex; min-width: 0; flex-direction: column; padding: var(--space-sm) 0 var(--space-md); border-left: var(--ws-divider); }
104
+ .ws-metric-tile__section { display: flex; flex-direction: column; gap: var(--space-xs); padding: var(--space-sm) var(--space-sm-plus) var(--space-sm) var(--space-lg); }
105
+ .ws-metric-tile__section + .ws-metric-tile__section { border-top: var(--ws-divider); }
106
+ .ws-metric-tile__section--grow { flex: 1; }
107
+ .ws-metric-tile__section-title { font-size: var(--ws-text-control); font-weight: 500; line-height: var(--ws-leading-control); color: var(--color-text-heading); }
108
+ .ws-metric-tile__preview > .ws-tick-strip { flex: 1; }
109
+ .ws-metric-tile__tools { display: flex; align-items: center; gap: var(--space-xxs); padding: 0 var(--space-sm-plus) 0 var(--space-lg); }
110
+ .ws-metric-tile__tools > span { flex: 1; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-size: var(--ws-text-dense); font-weight: 500; color: var(--color-text-muted); }
111
+ .ws-metric-tile__mover { display: grid; grid-template-columns: var(--space-xs) minmax(0, 1fr) auto; align-items: center; column-gap: var(--space-xs-plus); font-size: var(--ws-text-control); line-height: var(--ws-leading-control); }
112
+ .ws-metric-tile__mover > i { width: var(--space-xs); height: var(--space-xs); border-radius: var(--ws-radius-chip); background: var(--tone); }
113
+ .ws-metric-tile__mover > b { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-weight: 500; color: var(--color-text-heading); }
114
+ .ws-metric-tile__mover > em { font-style: normal; font-weight: 600; color: var(--color-text-heading); font-variant-numeric: tabular-nums; text-align: right; }
115
+ .ws-metric-tile__acts { display: flex; flex-wrap: wrap; gap: var(--space-xs); }
116
+ /* Seth's pane pair: the two acts share the row in equal halves, the primary last; each takes the row when the two
117
+ do not fit side by side. */
118
+ .ws-metric-tile__acts > * { flex: 1 1 0; min-width: max-content; }
119
+ .ws-metric-tile--open > .ws-metric-tile__foot { min-height: var(--space-2xl); padding-inline: var(--ws-band-inset); }
120
+ @container (max-width: 60rem) {
121
+ .ws-metric-tile--open > .ws-metric-tile__surface { grid-template-columns: minmax(0, 1fr); }
122
+ .ws-metric-tile__side { border-left: 0; border-top: var(--ws-divider); }
123
+ .ws-metric-tile__chart:not(.ws-metric-tile__chart--segs) { min-height: calc(var(--space-9xl) + var(--space-4xl)); }
124
+ }
125
+ @container (max-width: 30rem) {
126
+ .ws-metric-tile__main { padding: var(--space-md); }
127
+ .ws-metric-tile__section, .ws-metric-tile__tools { padding-inline: var(--space-md); }
128
+ .ws-metric-tile--open > .ws-metric-tile__foot { padding-inline: var(--space-md); }
129
+ }
@@ -0,0 +1,106 @@
1
+ A thing that changes its place or its size goes there as itself: a card flying to the full screen and back, a figure growing open across its row, the chat moving between its modes. The browser's own view transitions, started by `morph()` from the package, on Seth's curves.
2
+
3
+ ## Use it when
4
+
5
+ - One element changes place or size because of a person's act and should be seen to go there: a chat card to its full
6
+ screen and back, a tile opening across its row and closing back into its slot, the chat between docked and full
7
+ screen.
8
+ - The change is a class or a state flip the app can make inside one function.
9
+
10
+ ## Not for
11
+
12
+ - Something arriving or leaving: that is the component's own entrance and exit (`Menu`, `Dialog`, `Panel`, `Toast`).
13
+ - A height opening in place (a fold, a lane): that is the component's own height motion.
14
+ - A page change: a route's own transition is the app's, not the kit's.
15
+
16
+ ## Anatomy
17
+
18
+ - `morph(update, { as, el, close })` from `@felloinnovations/brand-kit`: runs `update` as a same-document view
19
+ transition. It names `el` `ws-morph` for the move, copies its ground, corners and backdrop to the frame
20
+ (`--ws-morph-ground`, `--ws-morph-radius`, `--ws-morph-backdrop`) and marks the root `data-ws-morph="<as>"` while it
21
+ runs, so only the kit's own transitions take these curves. It resolves when the move has finished.
22
+ - The lift (`full` only): when `update` leaves `el` `position: fixed` (a chat card's `ws-chat-card--full`), morph()
23
+ puts it in the top layer as a manual popover (`popover="manual"`, `showPopover()`, marked `data-ws-lifted`), so the
24
+ full screen covers the window wherever the element sits. Without it, any ancestor with a transform, a filter or
25
+ backdrop-filter (AppShell's glass stage, the chat's glass), `contain` or `will-change` becomes the fixed element's
26
+ box, and the card fills only that ancestor, squashed. Lifted, focus moves into it (its `[autofocus]`, else its first
27
+ control, unless it already holds focus) and Escape runs `close` (default: `update` again, right when `update` is a
28
+ toggle); when an update leaves it unfixed it drops back into its slot and focus goes back where it was. The lift
29
+ happens with or without motion.
30
+ - The frame (`::view-transition-group(ws-morph)`): moves and resizes as one shape on the kind's curve, in the
31
+ element's ground with the hairline edge, and cuts what is inside to its corners.
32
+ - The words (`::view-transition-old` and `-new`): never scale (North css:1485). The old view rides in the frame's
33
+ corner at its own size until 55 % of the way, then fades off over .1 s as the new one fades in at its own size over
34
+ .32 s soft (`--ws-duration-settle`, `--ws-ease-soft`), so the frame never flies empty.
35
+ - The page behind cross-fades over .32 s soft (North's "Page behind fades").
36
+
37
+ ## Variants and sizes
38
+
39
+ | `as` | Seth's name for it | Time and curve |
40
+ | --- | --- | --- |
41
+ | `full` (default) | "Full screen: open / close (card, type and chart together)" | `--ws-duration-morph` (.45 s) on `--ws-ease-morph` (.5, 0, .15, 1), 32 ms late, both ways |
42
+ | `grow` | "Card grows open" | `--ws-duration-grow` (.55 s) on `--ws-ease-grow` (.32, .72, 0, 1) |
43
+ | `shrink` | "Card closes back into place" | `--ws-duration-shrink` (.4 s) on `--ws-ease-grow`: faster than it opened |
44
+ | `shift` | "Chat moves between full screen, dock and pop-out" | `--ws-duration-shift` (.55 s) on `--ws-ease-shift` (.25, 1, .5, 1) |
45
+
46
+ ## States
47
+
48
+ - **Moving**: the frame on its curve, the old words in its corner, then the new words fading in.
49
+ - **Interrupted**: a second morph before the first ends skips the first to its end and starts from there.
50
+ - **Reduced motion**: no transition; `update` runs at once (and `full` still lifts and drops the element).
51
+ - **No view transitions** (an older browser): `update` runs at once; nothing else changes.
52
+ - **Full**: lifted into the top layer above everything, Escape and its Back button close it, focus inside it.
53
+
54
+ ## Behaviour
55
+
56
+ - `el` must still be in the page after `update` (the same node, its class changed), or the page names the new element
57
+ `view-transition-name: ws-morph` itself and leaves `el` out. `full` needs `el`: it is the element lifted.
58
+ - Full screen's way back lives inside the element (a Back button in its head that runs the same morph): the lifted
59
+ element sits above the whole page, so a control outside it is covered. Escape closes it too, unless a menu, popover or
60
+ dialog open inside it takes the Escape first.
61
+ - In React, flush the state inside it, and give Escape the way back:
62
+ `morph(() => flushSync(() => setFull(true)), { el: ref.current, close: () => flushSync(() => setFull(false)) })`; the
63
+ Back button runs `morph(() => flushSync(() => setFull(false)), { el: ref.current })`.
64
+ - The page does not take presses for the length of the move (the browser's own rule while a view transition runs).
65
+ - In the scoped build (`fello.scoped.css`) the root is not `.fello`, so the move takes the browser's default cross-fade
66
+ instead of Seth's curves.
67
+
68
+ ## Rules
69
+
70
+ - One element in flight at a time, as itself; never a copy sliding in from elsewhere.
71
+ - Words never scale: they ride, fade off and fade back.
72
+ - Closing is never slower than opening (`shrink` .4 s after `grow` .55 s; `full` is .45 s both ways, as North).
73
+
74
+ ## Source
75
+
76
+ **Derived from**: Seth's North DialKit config (app.js@824790: `open.expand` .55 grow, `open.collapse` .4 grow,
77
+ `chat.morph` .45 (.5, 0, .15, 1) after .032 s, `chat.shift` .55 (.25, 1, .5, 1)), the chat card's fly-out (css:1259 to
78
+ 1505: the frame in flight, the parts under it fading out, snapping and fading back from 55 % of the way) and the chat's
79
+ mode moves (js@648700). North moves `top`, `left`, `width` and `height` every frame in script; the kit hands the move
80
+ to the browser's view transitions, so nothing runs per frame.
81
+
82
+ ## Markup
83
+
84
+ ```ts
85
+ import { morph } from '@felloinnovations/brand-kit';
86
+
87
+ // The toggle sits in the card's head ("Full screen", then "Back"); Escape runs the same toggle.
88
+ const go = card.querySelector('.ws-chat-card__head .ws-btn');
89
+ go.addEventListener('click', () =>
90
+ morph(() => {
91
+ const full = card.classList.toggle('ws-chat-card--full');
92
+ go.textContent = full ? 'Back' : 'Full screen';
93
+ }, { as: 'full', el: card }));
94
+
95
+ tile.addEventListener('click', () => {
96
+ const opening = tile.getAttribute('aria-expanded') !== 'true';
97
+ morph(() => tile.setAttribute('aria-expanded', String(opening)), { as: opening ? 'grow' : 'shrink', el: tile });
98
+ });
99
+ ```
100
+
101
+ ## Styles
102
+
103
+ `Morph/style.css`, built into `bundle.css` and `fello.css`: the `::view-transition-*` rules under
104
+ `:root[data-ws-morph]`, inside `(prefers-reduced-motion: no-preference)`, and the lifted element's reset
105
+ (`[data-ws-lifted]`: the browser's popover box gives way to the element's own). Tokens only. The helper is
106
+ `src/lib/motion.ts`.
@@ -0,0 +1,103 @@
1
+ <!-- @dsCard group="Cards" height=980 -->
2
+ <!doctype html>
3
+ <html>
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <title>Morph</title>
7
+ <link rel="stylesheet" href="../../tokens.css">
8
+ <link rel="stylesheet" href="../bundle.css">
9
+ </head>
10
+ <body>
11
+ <div class="ws-stage ws-stage--column">
12
+
13
+ <div class="ws-stack">
14
+ <span class="ws-muted">Live: press Full screen. The card flies to the full screen as itself on the morph curve (.45 s), its words fading off and back at their own size; Back carries it home the same way</span>
15
+ <div style="position: relative; contain: layout; height: 460px; padding: var(--space-lg); border-radius: var(--ws-radius-panel); background: var(--ws-frame);">
16
+ <div class="ws-card ws-chat-card" id="morph-card">
17
+ <div class="ws-chat-card__head">
18
+ <span class="ws-eyebrow">Chart</span>
19
+ <button type="button" class="ws-btn ws-btn--secondary" id="morph-card-go">Full screen</button>
20
+ </div>
21
+ <p class="ws-title">Calls this week, by agent</p>
22
+ <p class="ws-body">Sam is three behind the team; Dana is on pace.</p>
23
+ <ul class="ws-chat-card__rows" aria-label="Calls this week">
24
+ <li class="ws-chat-card__row"><span>Dana Okafor</span><b>12</b></li>
25
+ <li class="ws-chat-card__row"><span>Priya Nair</span><b>9</b></li>
26
+ <li class="ws-chat-card__row"><span>Sam Rivera</span><b>4</b></li>
27
+ </ul>
28
+ </div>
29
+ </div>
30
+ </div>
31
+
32
+ <div class="ws-stack">
33
+ <span class="ws-muted">Live: press a figure. It grows open across its row on the grow curve (.55 s) while the others step back; pressed again it closes back into its place, faster (.4 s)</span>
34
+ <div id="morph-row" style="display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-sm);">
35
+ <button type="button" class="ws-card" aria-expanded="false" style="display: flex; flex-direction: column; align-items: flex-start; padding: var(--space-md); text-align: left;"><span class="ws-meta">Calls</span><span class="ws-section">42</span></button>
36
+ <button type="button" class="ws-card" aria-expanded="false" style="display: flex; flex-direction: column; align-items: flex-start; padding: var(--space-md); text-align: left;"><span class="ws-meta">Replies</span><span class="ws-section">17</span></button>
37
+ <button type="button" class="ws-card" aria-expanded="false" style="display: flex; flex-direction: column; align-items: flex-start; padding: var(--space-md); text-align: left;"><span class="ws-meta">Booked</span><span class="ws-section">5</span></button>
38
+ </div>
39
+ </div>
40
+
41
+ <script>
42
+ (() => {
43
+ // A copy of morph() from @felloinnovations/brand-kit (src/lib/motion.ts): a preview cannot import the package.
44
+ // `full` lifts the element into the top layer while it is fixed, so no ancestor (a transform, a filter, the glass,
45
+ // contain) can trap it; Escape runs the update again (a toggle) and focus comes back.
46
+ const morph = (() => {
47
+ const lifted = new WeakMap(), root = document.documentElement;
48
+ const FOCUSABLE = 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), summary, [tabindex]:not([tabindex="-1"])';
49
+ let last = 0;
50
+ const place = (el, close) => {
51
+ const was = lifted.get(el);
52
+ if (was) { lifted.delete(el); el.removeEventListener('keydown', was.onKey); if (el.matches(':popover-open')) el.hidePopover(); el.removeAttribute('popover'); delete el.dataset.wsLifted; }
53
+ if (getComputedStyle(el).position === 'fixed' && el.showPopover) {
54
+ const onKey = (e) => { if (e.key !== 'Escape' || e.defaultPrevented || el.querySelector(':popover-open, dialog[open]')) return; e.preventDefault(); morph(close, { as: 'full', el, close }); };
55
+ el.dataset.wsLifted = ''; el.popover = 'manual'; el.showPopover(); el.addEventListener('keydown', onKey);
56
+ lifted.set(el, { opener: was ? was.opener : document.activeElement, onKey });
57
+ if (!el.contains(document.activeElement)) { const to = el.querySelector('[autofocus]') || el.querySelector(FOCUSABLE) || el; if (to === el && !el.hasAttribute('tabindex')) el.tabIndex = -1; to.focus({ preventScroll: true }); }
58
+ } else if (was) { const o = was.opener, here = document.activeElement; if (o && o.isConnected && o !== here && (!here || here === document.body || el.contains(here))) o.focus({ preventScroll: true }); }
59
+ };
60
+ const morph = (update, { as = 'full', el, close } = {}) => {
61
+ const run = as === 'full' && el ? async () => { await update(); place(el, close || update); } : update;
62
+ if (!document.startViewTransition || matchMedia('(prefers-reduced-motion: reduce)').matches) return Promise.resolve(run()).then(() => {});
63
+ const id = ++last;
64
+ if (el) {
65
+ const s = getComputedStyle(el);
66
+ root.style.setProperty('--ws-morph-ground', s.backgroundColor);
67
+ root.style.setProperty('--ws-morph-radius', s.borderRadius);
68
+ root.style.setProperty('--ws-morph-backdrop', s.backdropFilter || 'none');
69
+ el.style.viewTransitionName = 'ws-morph';
70
+ }
71
+ root.dataset.wsMorph = as;
72
+ const move = document.startViewTransition(run);
73
+ move.ready.catch(() => {});
74
+ return move.finished.finally(() => {
75
+ if (el) el.style.viewTransitionName = '';
76
+ if (id !== last) return;
77
+ delete root.dataset.wsMorph;
78
+ for (const p of ['--ws-morph-ground', '--ws-morph-radius', '--ws-morph-backdrop']) root.style.removeProperty(p);
79
+ });
80
+ };
81
+ return morph;
82
+ })();
83
+ const card = document.getElementById('morph-card'), go = document.getElementById('morph-card-go');
84
+ go.addEventListener('click', () => morph(() => {
85
+ const full = card.classList.toggle('ws-chat-card--full');
86
+ go.textContent = full ? 'Back' : 'Full screen';
87
+ }, { as: 'full', el: card }));
88
+ const row = document.getElementById('morph-row');
89
+ for (const tile of row.children) tile.addEventListener('click', () => {
90
+ const open = tile.getAttribute('aria-expanded') !== 'true';
91
+ morph(() => {
92
+ tile.setAttribute('aria-expanded', String(open));
93
+ tile.style.gridColumn = open ? '1 / -1' : '';
94
+ tile.style.minHeight = open ? '160px' : '';
95
+ for (const other of row.children) if (other !== tile) other.style.display = open ? 'none' : '';
96
+ }, { as: open ? 'grow' : 'shrink', el: tile });
97
+ });
98
+ })();
99
+ </script>
100
+
101
+ </div>
102
+ </body>
103
+ </html>
@@ -0,0 +1,31 @@
1
+ /* ---- Morph: a thing that changes its place or size goes there as itself (Seth's shared element in flight), on the
2
+ browser's own view transitions; morph() in the package starts one ---- */
3
+ /* morph(update, { as, el }) names the element ws-morph and marks the root with data-ws-morph (the kind) while it runs, so
4
+ only the kit's own transitions take these curves. The frame (the transition's group) moves and resizes as one shape
5
+ on the kind's curve, in the element's ground and corners (morph() copies them to --ws-morph-ground, --ws-morph-radius
6
+ and --ws-morph-backdrop) with the hairline edge. What is inside never scales (North css:1485): the old view rides in
7
+ the frame's corner at its own size, cut to the frame, until 55 % of the way, where it fades off over .1 s as the new
8
+ one, at its own size, fades in over .32 s soft (css:1489 to 1495), so the frame never flies empty. The page behind
9
+ fades over .32 s soft (North's "Page behind fades").
10
+ The kinds: full, .45 s on the morph curve, 32 ms late (North's "Full screen: open / close"); grow, .55 s on the grow
11
+ curve ("Card grows open"); shrink, .4 s on the grow curve ("Card closes back into place"); shift, .55 s on the shift
12
+ curve ("Chat moves between full screen, dock and pop-out"). A browser without view transitions, and reduced motion,
13
+ get the change at once: morph() then runs the update alone. */
14
+ /* An element morph() lifted into the top layer (`full`, data-ws-lifted) keeps its own look: the browser's popover
15
+ defaults (a fit-content box, auto margins, a medium border, padding, the Canvas colours) give way to anything the
16
+ element sets, and where it sets nothing it is a plain box in the page's words. */
17
+ :where([data-ws-lifted]) { width: auto; height: auto; margin: 0; padding: 0; border: 0; background: none; color: inherit; }
18
+ :root[data-ws-morph] { --ws-morph-for: var(--ws-duration-morph); --ws-morph-ease: var(--ws-ease-morph); --ws-morph-wait: 32ms; /* raw: North's morph waits .032 s */ --ws-morph-out: 100ms; /* raw: North's .1 s fade-out in flight */ }
19
+ :root[data-ws-morph="grow"] { --ws-morph-for: var(--ws-duration-grow); --ws-morph-ease: var(--ws-ease-grow); --ws-morph-wait: 0s; }
20
+ :root[data-ws-morph="shrink"] { --ws-morph-for: var(--ws-duration-shrink); --ws-morph-ease: var(--ws-ease-grow); --ws-morph-wait: 0s; }
21
+ :root[data-ws-morph="shift"] { --ws-morph-for: var(--ws-duration-shift); --ws-morph-ease: var(--ws-ease-shift); --ws-morph-wait: 0s; }
22
+ @keyframes ws-morph-out { to { opacity: 0; } }
23
+ @keyframes ws-morph-in { from { opacity: 0; } }
24
+ @media (prefers-reduced-motion: no-preference) {
25
+ :root[data-ws-morph]::view-transition-group(ws-morph) { animation-duration: var(--ws-morph-for); animation-timing-function: var(--ws-morph-ease); animation-delay: var(--ws-morph-wait); overflow: clip; border-radius: var(--ws-morph-radius, var(--ws-radius-card)); background: var(--ws-morph-ground, var(--ws-raised)); -webkit-backdrop-filter: var(--ws-morph-backdrop, none); backdrop-filter: var(--ws-morph-backdrop, none); box-shadow: var(--ws-shadow-hairline); }
26
+ :root[data-ws-morph]::view-transition-image-pair(ws-morph) { isolation: auto; }
27
+ :root[data-ws-morph]::view-transition-old(ws-morph), :root[data-ws-morph]::view-transition-new(ws-morph) { inline-size: auto; block-size: auto; mix-blend-mode: normal; }
28
+ :root[data-ws-morph]::view-transition-old(ws-morph) { animation: ws-morph-out var(--ws-morph-out) linear calc(var(--ws-morph-wait) + var(--ws-morph-for) * 0.55) both; }
29
+ :root[data-ws-morph]::view-transition-new(ws-morph) { animation: ws-morph-in var(--ws-duration-settle) var(--ws-ease-soft) calc(var(--ws-morph-wait) + var(--ws-morph-for) * 0.55) both; }
30
+ :root[data-ws-morph]::view-transition-old(root), :root[data-ws-morph]::view-transition-new(root) { animation-duration: var(--ws-duration-settle); animation-timing-function: var(--ws-ease-soft); }
31
+ }
@@ -0,0 +1,164 @@
1
+ Every move in Seth's North app on one live page: first a product screen to try them on, a numbered hint at each move and a list of what to notice; then, for developers, each behaviour by Seth's own label, North's values, a kit demo, the token or class to build with, and how close the kit is.
2
+
3
+ ## Use it when
4
+
5
+ - You want to feel how the kit moves: try the screen at the top, number by number.
6
+ - You need to know how something should move: find North's behaviour by its name in the reference, play it, and copy
7
+ the token or class from its row.
8
+ - You want to see a spring's shape: play it, or turn on Slow everything down 4x.
9
+ - You are checking what the kit does not do yet before you build it in an app: those rows say why.
10
+
11
+ ## Not for
12
+
13
+ - Building a page: this is a reference card, not a component. Build with the components each row names.
14
+ - A new spring or curve: add it to `tokens/design-tokens.json` (`motion.springs`) or `tokens/_product.css` and run
15
+ `node scripts/generate-tokens.mjs`; the springs come out as CSS for you.
16
+
17
+ ## Anatomy
18
+
19
+ - The head: what the page is and how to use it.
20
+ - Try it, the playground: a list of what to try and the screen beside it (above it on a phone).
21
+ - The list: the Slow motion switch, then each hint's navy number (`Badge`, `ws-badge--navy`), what to do and one
22
+ plain line on what to notice; the reduced-motion note under it.
23
+ - The screen: a real product screen built only from kit components and their README markup, in a 780 px frame that
24
+ stands for the window. `AppShell` with the glass stage and its nav (a group that folds, a `<details>` with a `Fold`
25
+ chevron; the fold button; on a phone the same nav in its drawer), `PageBar` as the bar (Ask Coach, Save), `Tabs`,
26
+ the dashboard that builds in on arrival (a `.ws-build` page: four `MetricTile--draw`s with their mini charts, three
27
+ column strips and a line with dots, beside a `Split` aside of `SideList--draw` goal rows, on track, over and
28
+ behind; Replay the arrival runs it again), a chart card (`ChatCard` with a `Segmented` Bars / Line and Full
29
+ screen around a `TrendLine` that morphs between columns and a line, its weeks pointed at through `HoverCard`, a
30
+ build card that lands after the goals), a `Heatmap`, a card that loads (`Skeleton`, then each line settling in), a list (`SideList`
31
+ picks) sorted from a `Menu`, the lead `Dialog` drawer, `ChatShell` in its bar, docked and full modes over the
32
+ `ws-chat-veil`, and a `Toast`.
33
+ - The hints: the number and a few words above the part they are about; on the bar and in the nav, where there is no
34
+ room, the number alone on the control's corner (`ws-badge-host`), its words in the list.
35
+ - Reference for developers: the whole table below, folded away by default (a `<details>`, its summary the one line
36
+ that opens it). Its head: what a row is, the four counts and the reduced-motion note.
37
+ - A group per section of North's motion notes (north-motion.md 1 to 14, then 17 for the states the others leave out):
38
+ Durations, Curves, Springs, Hover, Press, Focus, Open and close, Panels docking, Loading, Build-ins, Tooltips,
39
+ Stagger, Reduced motion, What never moves, Interaction states.
40
+ - A row (`article.ws-card` > `.ws-head`), one behaviour: the status chip and Seth's label; North's values (duration,
41
+ curve or spring, with the file reference); the kit's token, class or helper; and the live demo. A demo that needs
42
+ room (a heatmap, the chat, a panel, a chart) spans the board's width. A row whose behaviour is played elsewhere on
43
+ the page links to that row.
44
+ - In the playground, every move is the kit's own: CSS (hover, press, fold, the sliding pill and underline, menus,
45
+ the drawer, the toast, the heatmap square, the shimmer, the build-in on arrival, the bars to line morph), `morph()`
46
+ (the chart card to full screen and back, the chat
47
+ between its modes) and `reorder()` (the sorted list). Full screen lifts the card into the top layer, so it covers
48
+ the whole window even from inside the frame; Back or Escape brings it home.
49
+ - Demos are the kit's own components and house classes. The curve demo is a `PaceBar` track whose dot crosses on the
50
+ row's duration and curve: Play sends it to the other end. One-shots (a draw, a fill, a build-in, a stagger) have
51
+ Replay.
52
+
53
+ ## Variants and sizes
54
+
55
+ | Status | Chip | Meaning |
56
+ | --- | --- | --- |
57
+ | Exact | `ws-chip--live` | the kit moves as North does: Seth's value, Seth's curve or spring |
58
+ | Close | `ws-chip--middle` | the kit moves the same way with a difference the row names (a duration 20 to 50 ms off, a timed curve where North has a spring, a step left out) |
59
+ | JS helper | `ws-chip--accent` | CSS cannot start it alone; a small framework-free helper from the package does (`morph()`, `reorder()`, `rollPlaceholder()`) and the CSS moves it |
60
+ | Not yet | `ws-chip` | not in the kit; the row says why (no component for it, an open question for Seth, or a rule the kit keeps instead) |
61
+
62
+ Coverage, 206 behaviours (the page counts them in its head):
63
+
64
+ | Group | Rows | Exact | Close | JS helper | Not yet |
65
+ | --- | --- | --- | --- | --- | --- |
66
+ | 1. Durations | 16 | 11 | 5 | 0 | 0 |
67
+ | 2. Curves | 19 | 13 | 2 | 0 | 4 |
68
+ | 3. Springs | 27 | 26 | 0 | 0 | 1 |
69
+ | 4. Hover | 26 | 7 | 14 | 0 | 5 |
70
+ | 5. Press | 8 | 3 | 1 | 0 | 4 |
71
+ | 6. Focus | 7 | 4 | 2 | 0 | 1 |
72
+ | 7. Open and close | 23 | 10 | 5 | 2 | 6 |
73
+ | 8. Panels docking | 12 | 6 | 1 | 1 | 4 |
74
+ | 9. Loading, working and loops | 11 | 5 | 2 | 0 | 4 |
75
+ | 10. Number and chart build-ins | 13 | 5 | 2 | 0 | 6 |
76
+ | 11. Tooltips and hover cards | 6 | 2 | 4 | 0 | 0 |
77
+ | 12. Stagger | 11 | 6 | 2 | 1 | 2 |
78
+ | 13. Reduced motion | 4 | 4 | 0 | 0 | 0 |
79
+ | 14. What never moves | 10 | 6 | 3 | 0 | 1 |
80
+ | 17. Interaction states | 13 | 7 | 2 | 1 | 3 |
81
+ | All | 206 | 115 | 45 | 5 | 41 |
82
+
83
+ ## States
84
+
85
+ - **Rest**: every demo at rest, exactly as its component draws it.
86
+ - **Playing**: hover, press, Tab, click, Play or Replay.
87
+ - **Slowed** (Slow motion, in Try it; this page only): every `--ws-*` duration, delay, stagger and loop token on `<html>` becomes
88
+ `calc(<its value> * var(--ws-motion-slow))` with `--ws-motion-slow: 4`, and the few raws a component keeps for itself
89
+ (the chat and panel exits and blur, the tooltip fade, the dialog's wait) are stretched on their own elements, so CSS
90
+ springs, curves, view transitions and the helpers all run four times slower together. Off, the inline values go.
91
+ Not a kit feature: an app never slows its motion.
92
+ - **Reduced motion**: everything is still (D7): the house net makes every kit transition and animation instant, the
93
+ helpers skip to the end state, the placeholder holds, shimmers stop on their ghost. The page says which the browser
94
+ asks for. The slow switch then has nothing to slow.
95
+
96
+ ## Behaviour
97
+
98
+ - Native CSS first: springs are `linear()` curves (`--ws-spring-<name>` over `--ws-spring-<name>-duration`, sampled
99
+ from the motion library's own solver, overshoot kept); sliding indicators ride CSS anchor positioning; heights grow
100
+ with `interpolate-size` and `::details-content`; enters and exits use `@starting-style` and
101
+ `transition-behavior: allow-discrete` where the kit opens the layer itself (Menu and Popover as a native popover, Dialog
102
+ as a native dialog); Toast, HoverCard, Panel and ChatShell play keyframes, and the app sets `data-leaving` and keeps the
103
+ node until the exit has played; moves of place and size use same-document view transitions.
104
+ - A browser without one of these shows the change at once: no broken layout, nothing half-moved.
105
+ - The playground's arrival (Motion's Build-in, a kit default): on first paint the four tiles rise 70 ms apart
106
+ (`--ws-stagger-part`), each tile's columns grow in one after another and its line draws step by step with its dots
107
+ popping, over 0.79 of `--ws-duration-fill-page`, then its foot figure rises in; the goals card rises fifth and its
108
+ bars fill in order; the chart card rises sixth and its columns grow in over `--ws-duration-draw`. Replay the arrival
109
+ runs fresh copies. Bars / Line: the columns melt into the line on `--ws-spring-morph-chart` while the line draws over
110
+ `--ws-duration-draw`; back, they grow down from the dots. Reduced motion: all of it final at once.
111
+ - Motion changes how things move, never where they rest: a resting demo is its component's resting look, pixel for
112
+ pixel.
113
+ - Only transform, opacity, clip and size move where the platform allows; nothing runs a script per frame (the helpers
114
+ hand the move to the browser).
115
+
116
+ ## Rules
117
+
118
+ - Build with the token or class in the row, never North's raw numbers.
119
+ - Springs are for what the person drives (a press, a pointer, an indicator, a panel); timed curves are for what the
120
+ system plays (a draw, a build-in, a loop).
121
+ - Exits are shorter than entrances.
122
+ - Overshoot only on marks under 24 px and indicators, never a card, a panel, a field or words.
123
+ - Reduced motion is still: write motion inside `(prefers-reduced-motion: no-preference)`; the house net catches the
124
+ rest.
125
+
126
+ ## Source
127
+
128
+ **Derived from**: Seth's North artifact (style1.css and app.js: the DialKit "Motion" config `nn`, app.js@824790 to
129
+ @833500, the intro timeline `eb`, and the springs and tweens in the bundle), as inventoried in the kit-upgrade notes
130
+ (north-motion.md, sections 1 to 19), and the kit's decisions D7 (motion), D8 (focus) and D15 (palettes).
131
+
132
+ ## Markup
133
+
134
+ A row:
135
+
136
+ ```html
137
+ <article class="ws-card" aria-label="Toggle pill slide (bars / line, lanes)"><div class="ws-head">
138
+ <div class="ws-stack ws-stack--tight">
139
+ <span class="ws-cluster"><span class="ws-chip ws-chip--live">Exact</span><span class="ws-row-name">Toggle pill slide (bars / line, lanes)</span></span>
140
+ <span class="ws-meta">North: select.pill: spring visualDuration / bounce .38 / .16</span>
141
+ <span class="ws-meta">Kit: --ws-spring-indicator over --ws-spring-indicator-duration (700ms); Segmented</span>
142
+ </div>
143
+ <div class="ws-cluster"><!-- the live demo --></div>
144
+ </div></article>
145
+ ```
146
+
147
+ A spring in CSS, a helper in script:
148
+
149
+ ```css
150
+ .thing { transition: transform var(--ws-spring-press-duration) var(--ws-spring-press); }
151
+ ```
152
+
153
+ ```ts
154
+ import { morph, reorder, rollPlaceholder } from '@felloinnovations/brand-kit';
155
+ morph(() => card.classList.toggle('ws-chat-card--full'), { as: 'full', el: card });
156
+ reorder(() => list.append(...sorted), list);
157
+ const stop = rollPlaceholder(rollElement, 3400);
158
+ ```
159
+
160
+ ## Styles
161
+
162
+ None of its own: the page is the house classes and the components' own `style.css` (a few inline styles place the
163
+ playground's frame, the hints and the chat bar). Its script only flips states the way an app does and carries copies of
164
+ `morph()` and `reorder()` (a preview cannot import the package).