@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,154 @@
1
+ /* ---- AppShell: a product's frame, Seth's North (D1). The 232 px nav on the frame's wash, a glass stage 13 down and 12 in
2
+ from the window's edge, its 56 px bar and a body that owns the scroll. It fills its parent's height: give that parent
3
+ the window's (100dvh). It measures itself: under 60rem of its own width it is the phone form (no nav, a menu button,
4
+ the stage edge to edge). The gutter is Seth's 32 (D4, --ws-gutter). ---- */
5
+ .ws-app-shell { container: ws-app-shell / inline-size; display: flex; height: 100%; min-height: 0; background: var(--ws-frame); }
6
+
7
+ /* The nav: 232 open, 64 folded, on the stage's 13 / 12 insets. Its head sits on the bar's line (past the stage's 1 px edge, the 56 height), the
8
+ groups scroll between the head and the person, who never scrolls away (K11). */
9
+ .ws-app-shell__nav { display: flex; flex-direction: column; min-height: 0; padding-block: var(--ws-line) var(--space-xs); overflow-x: hidden; overflow-y: auto; scrollbar-width: none; }
10
+ .ws-app-shell > .ws-app-shell__nav { width: var(--ws-sidebar-width); flex-shrink: 0; margin-block: var(--ws-stage-inset-top) var(--ws-stage-inset); }
11
+ /* Head and person sit on the groups' 12 / 8 sides and take a row's 10 inside them (North: the nav's 12, the head's 10),
12
+ so the name and the avatar start on the row icons' edge, 22 in, from two steps on the scale. */
13
+ .ws-app-shell__head { display: flex; align-items: center; gap: var(--space-xs); height: var(--ws-header-h); padding-inline: var(--space-sm) var(--space-xs); }
14
+ .ws-app-shell__wordmark { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--font-size-lg); line-height: var(--space-lg); font-weight: 600; color: var(--color-text-heading); }
15
+ .ws-app-shell__head > .ws-app-shell__wordmark { flex: 1; padding-inline-start: var(--space-xs-plus); }
16
+ .ws-app-shell__groups { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--ws-ink) 18%, transparent) transparent; }
17
+ /* A group: rows at x 12, 212 wide; the 1 px rule between groups runs the nav's full width. Markup from 0.4 (eyebrows and
18
+ rows straight in the nav) keeps the same edges. */
19
+ .ws-app-shell__group { padding: var(--space-xxs) var(--space-xs) var(--space-xxs) var(--space-sm); }
20
+ .ws-app-shell__group + .ws-app-shell__group { border-top: var(--ws-line) solid var(--ws-edge-card); }
21
+ .ws-app-shell__nav > :is(.ws-app-shell__rows, .ws-app-shell__eyebrow, .ws-app-shell__wordmark) { margin-inline: var(--space-sm) var(--space-xs); }
22
+ .ws-app-shell__nav > .ws-app-shell__wordmark { flex-shrink: 0; display: flex; align-items: center; min-height: var(--ws-header-h); padding-inline: var(--space-xs-plus); }
23
+ /* A group's name: Seth's sentence-case head, 11 / 16 medium (his 10 px, at the kit's 11 px floor), on the icons' edge. */
24
+ .ws-app-shell__eyebrow { display: block; min-height: var(--ws-control-h-sm); padding: var(--space-sm) var(--space-xs-plus) 0; font-size: var(--ws-text-micro); line-height: var(--space-md); font-weight: 500; letter-spacing: normal; text-transform: none; color: var(--color-text-muted); }
25
+ .ws-app-shell__rows { display: flex; flex-direction: column; gap: var(--space-3xs); }
26
+
27
+ /* A row: 28 tall (40 on touch, K10), 2 apart, 13 px medium words, a 20 px icon box, 6 px corners. Hover is Seth's navy
28
+ wash; the current page is a white pill with his coral, the word at its AA step (D2). */
29
+ .ws-app-shell__row { position: relative; display: flex; align-items: center; gap: var(--space-xxs); width: 100%; height: var(--ws-control-h-sm); padding: 0 var(--space-xs-plus); border: 0; border-radius: var(--ws-radius-row); background: none; text-align: left; text-decoration: none; font-size: var(--ws-text-control); line-height: var(--ws-leading-control); font-weight: 500; color: var(--color-text-body); cursor: pointer; }
30
+ .ws-app-shell__row > svg { flex-shrink: 0; width: var(--ws-icon-lg); height: var(--ws-icon-lg); padding: var(--space-3xs); color: var(--ws-mark-muted); }
31
+ .ws-app-shell__row:focus-visible { outline-offset: calc(var(--ws-focus-width) * -1); }
32
+ @media (hover: hover) { .ws-app-shell__row:hover { background: var(--ws-hover); } }
33
+ .ws-app-shell__row:active { background: var(--ws-hover-strong); }
34
+ .ws-app-shell__row:is(.ws-app-shell__row--current, [aria-current="page"]) { border-radius: var(--ws-radius-control); background: var(--color-surface); box-shadow: var(--ws-float-rim), 0 var(--ws-line) var(--ws-ring) color-mix(in srgb, var(--ws-shade) 6%, transparent), 0 0 0 var(--ws-hairline) color-mix(in srgb, var(--ws-shade) 4%, transparent); color: var(--color-text-brand-strong); }
35
+ .ws-app-shell__row:is(.ws-app-shell__row--current, [aria-current="page"]) > svg { color: var(--ws-chart-under-half); }
36
+ .ws-app-shell__name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
37
+ /* A row's count: Seth's navy pill, 18 tall, its figure on the act fill's word colour. */
38
+ .ws-app-shell__count { flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; min-width: calc(var(--space-md) + var(--space-3xs)); height: calc(var(--space-md) + var(--space-3xs)); padding: 0 var(--space-xxs); border-radius: var(--radius-full); background: var(--color-text-heading); color: var(--ws-on-act); font-size: var(--ws-text-micro); line-height: 1; font-weight: 600; font-variant-numeric: tabular-nums; }
39
+
40
+ /* The person, pinned at the foot under a full-width rule. */
41
+ .ws-app-shell__foot { flex-shrink: 0; display: flex; align-items: center; gap: var(--space-xs-plus); margin-top: auto; padding: var(--space-sm) var(--space-xs) var(--space-xxs-plus) var(--space-sm); border-top: var(--ws-line) solid var(--ws-edge-card); }
42
+ .ws-app-shell__foot > :first-child { margin-inline-start: var(--space-xs-plus); }
43
+ .ws-app-shell__foot > :last-child { margin-inline-end: var(--space-xs-plus); }
44
+ /* 0.4 markup (wordmark, rows and person straight in the nav, no __groups): the nav itself scrolls, so the wordmark and
45
+ the person stick to its ends (K11), the nav's full width, on the frame's wash fixed to the window as the frame is, so
46
+ no seam shows. The nav's own 1 / 8 ends move into them, so no row peeks past either. */
47
+ .ws-app-shell__nav:not(:has(> .ws-app-shell__groups)) { padding-block: 0; }
48
+ .ws-app-shell__nav:not(:has(> .ws-app-shell__groups)) > :is(.ws-app-shell__wordmark, .ws-app-shell__foot) { position: sticky; z-index: 1; background: var(--ws-frame); background-attachment: fixed; }
49
+ .ws-app-shell__nav:not(:has(> .ws-app-shell__groups)) > .ws-app-shell__wordmark { top: 0; min-height: calc(var(--ws-header-h) + var(--ws-line)); margin-inline: 0; padding: var(--ws-line) var(--space-xs) 0 calc(var(--space-sm) + var(--space-xs-plus)); }
50
+ .ws-app-shell__nav:not(:has(> .ws-app-shell__groups)) > .ws-app-shell__foot { bottom: 0; padding-bottom: var(--space-sm-plus); }
51
+ .ws-app-shell__foot-words { display: flex; flex-direction: column; min-width: 0; }
52
+ .ws-app-shell__foot-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--ws-text-control); line-height: var(--space-md); font-weight: 500; color: var(--color-text-heading); }
53
+
54
+ /* Folded: the same nav as a 64 px rail of 40 px rows, names faded (still read aloud), counts gone, no group names or
55
+ rules. Each row carries its name and count in title. */
56
+ .ws-app-shell--folded > .ws-app-shell__nav { width: var(--ws-rail-width); }
57
+ .ws-app-shell--folded .ws-app-shell__head { justify-content: flex-end; gap: 0; padding-inline: 0 var(--space-md); }
58
+ @media (pointer: coarse) { .ws-app-shell--folded .ws-app-shell__head { padding-inline-end: var(--space-sm); } }
59
+ .ws-app-shell--folded :is(.ws-app-shell__head > .ws-app-shell__wordmark, .ws-app-shell__foot-words) { flex: 0 0 0; padding: 0; opacity: 0; }
60
+ .ws-app-shell--folded :is(.ws-app-shell__group, .ws-app-shell__nav > .ws-app-shell__rows) { padding-inline: var(--space-sm); }
61
+ .ws-app-shell--folded .ws-app-shell__nav > .ws-app-shell__rows { margin-inline: 0; }
62
+ .ws-app-shell--folded .ws-app-shell__row { overflow: hidden; }
63
+ .ws-app-shell--folded .ws-app-shell__name { opacity: 0; }
64
+ /* The count leaves the rail; the row's title carries it ("Pipeline, 7 waiting"), read as its description. */
65
+ .ws-app-shell--folded .ws-app-shell__count { display: none; }
66
+ .ws-app-shell--folded .ws-app-shell__eyebrow { display: none; }
67
+ .ws-app-shell--folded .ws-app-shell__group + .ws-app-shell__group { border-top-color: transparent; }
68
+ /* The person takes the rail's own 64 at once, so the avatar sits centred on the rail from the fold's first frame. */
69
+ .ws-app-shell--folded .ws-app-shell__foot { width: var(--ws-rail-width); justify-content: center; gap: 0; padding-inline: 0; border-top-color: transparent; }
70
+ .ws-app-shell--folded .ws-app-shell__foot > * { margin-inline: 0; }
71
+ /* A rail drawn on its own (0.4 markup): the same 64 px rail of 40 x 36 marks. */
72
+ .ws-app-shell__rail { width: var(--ws-rail-width); flex-shrink: 0; display: flex; flex-direction: column; align-items: center; gap: var(--space-3xs); margin-block: var(--ws-stage-inset-top) var(--ws-stage-inset); padding-block: var(--ws-line) var(--space-xs); }
73
+ .ws-app-shell__mark { display: flex; align-items: center; justify-content: center; width: var(--space-2xl); height: var(--ws-control-h-md); border-radius: var(--ws-radius-row); color: var(--color-text-body); }
74
+ .ws-app-shell__mark--wordmark { height: var(--ws-header-h); font-size: var(--font-size-lg); font-weight: 600; color: var(--color-text-heading); }
75
+ .ws-app-shell__mark--current { border-radius: var(--ws-radius-control); background: var(--color-surface); box-shadow: var(--ws-float-rim), 0 var(--ws-line) var(--ws-ring) color-mix(in srgb, var(--ws-shade) 6%, transparent), 0 0 0 var(--ws-hairline) color-mix(in srgb, var(--ws-shade) 4%, transparent); color: var(--ws-chart-under-half); }
76
+ .ws-app-shell__mark svg { width: var(--ws-icon); height: var(--ws-icon); }
77
+
78
+ /* The stage: Seth's glass, 16 px corners, his 1 px edge; --wash is the Fia frames' cream stage. The blur sits on a layer
79
+ under the content (::before), not on the stage: an element with a backdrop-filter is the box of every position: fixed
80
+ thing inside it, so a toaster, a phone's full-screen panel or a full-screen card would fill only the stage and be cut
81
+ to it. Through the layer they reach the window. */
82
+ .ws-app-shell__stage { position: relative; isolation: isolate; flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; margin: var(--ws-stage-inset-top) var(--ws-stage-inset) var(--ws-stage-inset) 0; border: var(--ws-line) solid var(--ws-edge-stage); border-radius: var(--ws-radius-panel); background: var(--ws-glass); overflow: clip; }
83
+ .ws-app-shell__stage::before { content: ""; position: absolute; inset: 0; z-index: -1; -webkit-backdrop-filter: blur(var(--ws-glass-blur)); backdrop-filter: blur(var(--ws-glass-blur)); pointer-events: none; }
84
+ .ws-app-shell--wash .ws-app-shell__stage { background: var(--ws-stage-wash); }
85
+ .ws-app-shell--wash .ws-app-shell__stage::before { content: none; }
86
+ /* The bar: the 56 px line on the content's 32 edges, flat at rest; its 0.5 px edge shows once the body scrolls under it
87
+ (set --scrolled from the body's scroll). It holds the page's name (or a Breadcrumb, or a PageBar) and its acts; 8
88
+ above and below, so a bar that wraps (a PageBar on a phone) never touches the stage's edge. */
89
+ .ws-app-shell__bar { flex-shrink: 0; display: flex; align-items: center; gap: var(--space-md); min-height: var(--ws-header-h); padding: var(--space-xs) var(--ws-gutter); }
90
+ .ws-app-shell__bar--scrolled { box-shadow: inset 0 calc(var(--ws-hairline) * -1) 0 var(--ws-edge-card); }
91
+ .ws-app-shell__title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--font-size-md); line-height: var(--ws-leading-section); font-weight: 600; letter-spacing: var(--ws-tracking-title); color: var(--color-text-heading); }
92
+ .ws-app-shell__acts { display: flex; flex-shrink: 0; align-items: center; gap: var(--space-xs); margin-inline-start: auto; }
93
+ .ws-app-shell__search { display: flex; flex-shrink: 1; align-items: center; gap: var(--space-xxs); width: calc(var(--space-9xl) + var(--space-md) + var(--space-xxs)); min-width: var(--space-8xl); height: var(--ws-control-h); padding: 0 var(--space-xs); border-radius: var(--ws-radius-control); background: var(--ws-field); box-shadow: inset 0 0 0 var(--ws-hairline) var(--ws-edge-card); color: var(--color-text-muted); font-size: var(--ws-text-meta); white-space: nowrap; overflow: hidden; cursor: text; }
94
+ .ws-app-shell__search svg { width: var(--ws-icon-sm); height: var(--ws-icon-sm); flex-shrink: 0; }
95
+ /* The search is a real field: a <label> round an <input type="search">; the ring goes on the box, not the bare input. */
96
+ .ws-app-shell__search > input { flex: 1; min-width: 0; height: 100%; padding: 0; border: 0; background: none; -webkit-appearance: none; appearance: none; font: inherit; color: var(--ws-field-ink); }
97
+ .ws-app-shell__search > input::placeholder { color: var(--color-text-muted); opacity: 1; }
98
+ .ws-app-shell__search > input:focus-visible { outline: none; }
99
+ .ws-app-shell__search:focus-within { outline: var(--ws-focus-width) solid var(--ws-focus-color); outline-offset: var(--ws-focus-offset); }
100
+ @media (pointer: coarse) { .ws-app-shell__search { height: var(--ws-touch); font-size: var(--ws-text-value); } } /* 16 px words: a phone does not zoom into the field on focus */
101
+ .ws-app-shell .ws-app-shell__menu { display: none; } /* two classes: IconButton's display sits later in the bundle */
102
+ /* The body is the page: it scrolls (and never chains into the window), sizes what is inside it (a container, as .ws-page
103
+ is) and caps its content at the kit's content width on one start edge (K12). No .ws-page inside it. */
104
+ .ws-app-shell__body { flex: 1; min-width: 0; min-height: 0; container-type: inline-size; display: flex; flex-direction: column; gap: var(--ws-gap-component); padding: var(--space-md) var(--ws-gutter) var(--space-2xl); overflow: auto; overscroll-behavior: contain; scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--ws-ink) 18%, transparent) transparent; }
105
+ .ws-app-shell__stage > .ws-app-shell__body:first-child { padding-top: var(--ws-gutter); }
106
+ .ws-app-shell :is(.ws-app-shell__body, .ws-app-shell__groups):focus-visible { outline-offset: calc(var(--ws-focus-width) * -1); }
107
+ /* The content's cap is the board's two columns, as on .ws-page (Seth's North: 1119), so a section, its tiles and a
108
+ board all end on one edge. */
109
+ .ws-app-shell__body > * { max-width: calc(var(--ws-chart-card-w) * 2 + var(--ws-gap-component)); }
110
+ /* --wide: a Fia app page (Grow, Fixes, Inbox, Tally) runs the body's full width, Seth's 1350 in a 1416 stage. */
111
+ .ws-app-shell__body--wide > * { max-width: none; }
112
+ /* A filled page (a --fill Split) ends 24 above the stage's foot, Seth's drawer inset (301:2011), not the page's 40. */
113
+ .ws-app-shell__body:has(> .ws-split--fill) { padding-bottom: max(var(--ws-gap-group), env(safe-area-inset-bottom)); }
114
+
115
+ /* The phone form, under 60rem of the shell's own width (North drops the nav at 960): the nav leaves for a drawer opened
116
+ from the bar's menu button (the same nav markup, at the drawer's width), the stage goes edge to edge, 16 px sides. */
117
+ @container ws-app-shell (max-width: 60rem) {
118
+ .ws-app-shell > :is(.ws-app-shell__nav, .ws-app-shell__rail) { display: none; }
119
+ .ws-app-shell__stage { margin: 0; border: 0; border-radius: 0; }
120
+ .ws-app-shell .ws-app-shell__menu { display: inline-flex; }
121
+ .ws-app-shell__search { display: none; }
122
+ .ws-app-shell__bar, .ws-app-shell__body { padding-inline: max(var(--space-md), env(safe-area-inset-left)) max(var(--space-md), env(safe-area-inset-right)); }
123
+ .ws-app-shell .ws-app-shell__bar { padding-top: max(var(--space-xs), env(safe-area-inset-top)); }
124
+ .ws-app-shell__body { padding-bottom: max(var(--space-2xl), env(safe-area-inset-bottom)); }
125
+ }
126
+
127
+ /* The current pill slides (North "Sidebar active row slide", health.navSlide, springs.nav .4 / .1; north-motion 3): a
128
+ white pill slides to the new page's row instead of jumping. The current row is the anchor (CSS anchor positioning)
129
+ and the rows' scroller draws the travelling pill (its ::before: __groups, or a 0.4 nav that holds its rows), under
130
+ the rows, its four edges easing on motion's own spring(.4, .1) written out as linear() (550 ms). At rest the current
131
+ row paints its own pill, so a resting nav is the 0.4 nav pixel for pixel; while the rows are pointed at or hold focus
132
+ the travelling pill takes its place (the same box, the same paint, at once), so a page chosen then slides to. The
133
+ fold (its button is outside the rows) and a resize carry the pill with its row at once. No new markup. Without
134
+ anchor positioning, or under reduced motion, the pill moves at once. The spring is the global --ws-spring-nav over --ws-spring-nav-duration. The 0.4 rail's
135
+ marks stay put. */
136
+ @supports (anchor-name: --a) {
137
+ :is(.ws-app-shell__groups, .ws-app-shell__nav:not(:has(> .ws-app-shell__groups))) { position: relative; anchor-scope: --ws-app-shell-current; }
138
+ :is(.ws-app-shell__groups, .ws-app-shell__nav:not(:has(> .ws-app-shell__groups)))::before { content: ""; position: absolute; position-anchor: --ws-app-shell-current; inset: anchor(top) anchor(right) anchor(bottom) anchor(left); border-radius: var(--ws-radius-control); background: var(--color-surface); box-shadow: var(--ws-float-rim), 0 var(--ws-line) var(--ws-ring) color-mix(in srgb, var(--ws-shade) 6%, transparent), 0 0 0 var(--ws-hairline) color-mix(in srgb, var(--ws-shade) 4%, transparent); opacity: 0; pointer-events: none; }
139
+ .ws-app-shell__row:is(.ws-app-shell__row--current, [aria-current="page"]) { anchor-name: --ws-app-shell-current; }
140
+ .ws-app-shell__eyebrow { position: relative; } /* a group's name stays over the pill as it passes, as the rows do */
141
+ }
142
+
143
+ /* Motion (North): the fold trades width on the shift curve, names fade, washes and the bar's edge ease in. */
144
+ @media (prefers-reduced-motion: no-preference) {
145
+ .ws-app-shell > .ws-app-shell__nav { transition: width var(--ws-duration-shift) var(--ws-ease-shift); }
146
+ .ws-app-shell :is(.ws-app-shell__wordmark, .ws-app-shell__name, .ws-app-shell__foot-words) { transition: opacity var(--ws-duration-hover) var(--ws-ease-soft); }
147
+ .ws-app-shell__row { transition: background-color var(--ws-duration-hover) var(--ws-ease-soft), color var(--ws-duration-hover) var(--ws-ease-soft); }
148
+ .ws-app-shell__bar { transition: box-shadow var(--ws-duration-settle) var(--ws-ease-soft); }
149
+ @supports (anchor-name: --a) {
150
+ :is(.ws-app-shell__groups, .ws-app-shell__nav:not(:has(> .ws-app-shell__groups))):is(:hover, :focus-within)::before { opacity: 1; transition: inset var(--ws-spring-nav-duration) var(--ws-spring-nav); }
151
+ :is(.ws-app-shell__groups, .ws-app-shell__nav:not(:has(> .ws-app-shell__groups))):is(:hover, :focus-within) .ws-app-shell__row:is(.ws-app-shell__row--current, [aria-current="page"]) { background: none; box-shadow: none; }
152
+ .ws-app-shell__row:is(.ws-app-shell__row--current, [aria-current="page"]) { transition: color var(--ws-duration-hover) var(--ws-ease-soft); }
153
+ }
154
+ }
@@ -17,14 +17,17 @@ never coloured, never an app's face.
17
17
 
18
18
  - Shape: a disc, `radius-full`, `overflow: hidden` so a photo never spills past its edge.
19
19
  - Ground: `--ws-surface-sunk`, with navy initials (`--color-text-heading`) semibold, sized to the disc (12 to
20
- 20 px across the sizes below).
21
- - A photo fades in over the initials once it has loaded (`--ws-duration-gentle` on `--ws-ease-soft`), so the disc
22
- never pops in empty or shows a blank frame.
20
+ 24 px across the sizes below: 12, 14, 14, 16, 24).
21
+ - A photo: an `<img alt="">` inside the disc, after the initials, covering it (`object-fit: cover`). The initials stay
22
+ under it, so the disc is never empty: while it loads, and if it never does. With `data-state="loading"` (the React
23
+ component sets it, or a page script until `load`) it waits hidden, then fades in over 200 ms (`--ws-duration-gentle`
24
+ on `--ws-ease-soft`); without it the photo simply shows when the browser has it.
23
25
  - `ring`: an outer hairline (`--ws-shadow-hairline`) so the disc reads on a cream card.
24
- - `dim`: 60% opacity, for someone invited who has not joined yet.
26
+ - `dim`: for someone invited who has not joined yet: the initials in the muted word (`--color-text-muted`, AA on every
27
+ ground), a photo at 60% opacity. The disc itself never fades, so its initials never drop under AA.
25
28
  - In fello.css: `.ws-avatar` is the 32 px disc with the ring (it mostly sits on a cream card); `--plain` drops the
26
29
  ring, `--dim` dims it, `--sm`, `--deck`, `--panel` and `--xl` size it. A stack is `.ws-avatar-stack` with
27
- `.ws-avatar-stack__more` on the "+N" disc.
30
+ `.ws-avatar-stack__more` on the "+N" disc (tabular figures, so "+9" and "+12" sit alike).
28
31
 
29
32
  ## Variants and sizes
30
33
 
@@ -44,16 +47,18 @@ steps `sm` 28, `md` 32, `deck` 48, `panel` 56, `xl` 64. This README and preview
44
47
 
45
48
  - **Default**: initials on the sunk ground.
46
49
  - **Photo loaded**: the photo fills the disc, cropped to cover.
47
- - **Loading**: initials show from the first frame; the photo crossfades in once `load` fires (or at once if the
48
- browser already had it cached).
50
+ - **Loading**: initials show from the first frame; a photo marked `data-state="loading"` crossfades in once the mark is
51
+ dropped on `load` (or at once if the browser already had it cached).
52
+ - **Photo failed**: the initials stay; nothing breaks, nothing shows a broken image (drop the `<img>` on `error`).
49
53
  - **Ring**: the hairline edge, for a disc sitting directly on a cream card with nothing else to separate them.
50
- - **Dim**: 60% opacity, for an invited person who has not joined.
54
+ - **Dim**: muted initials (a photo at 60%), for an invited person who has not joined.
51
55
  - **Stack overflow**: a disc in the same ground reading "+N" in place of a fifth face.
52
56
 
53
57
  ## Behaviour
54
58
 
55
59
  - Decorative: `aria-hidden`, since whoever uses it names the person in words beside it (a row's title, a card's
56
- label) or in its own accessible label.
60
+ label) or in its own accessible label. A stack that stands alone takes `role="img"` and a label naming the people
61
+ ("Priya Nair, Dana Sato and 12 more").
57
62
  - No keyboard or pointer behaviour of its own: it carries no click.
58
63
  - No motion but the photo's own crossfade; nothing moves with reduced motion beyond that opacity change settling
59
64
  at once instead of easing.
@@ -70,7 +75,7 @@ steps `sm` 28, `md` 32, `deck` 48, `panel` 56, `xl` 64. This README and preview
70
75
  | `name` | `string` | Used for the initials and the alt text context |
71
76
  | `photo` | `string \| null` | A URL to their picture, or `null` for initials only |
72
77
  | `size` | `'sm' \| 'md' \| 'deck' \| 'panel' \| 'xl'` | The disc's size |
73
- | `dim` | `boolean` | 60% opacity, for someone invited who has not joined |
78
+ | `dim` | `boolean` | Muted initials (a photo at 60%), for someone invited who has not joined |
74
79
  | `ring` | `boolean` | The hairline edge for a cream card |
75
80
 
76
81
  `AvatarStack` takes `people: { name, photo }[]` and shows the first four, overlapping by 4 px, with "+N" for the
@@ -90,3 +95,25 @@ rest.
90
95
 
91
96
  **Derived from**: the system rules for people as neutral navy-initial discs, the sizing scale and the hairline
92
97
  ring for a cream card. **Precedent**: the first product built on the system.
98
+
99
+ ## Markup
100
+
101
+ ```html
102
+ <span class="ws-avatar ws-avatar--sm" aria-hidden="true">PN</span>
103
+
104
+ <span class="ws-avatar ws-avatar--plain" aria-hidden="true">DS<img alt="" src="/people/dana.jpg"></span>
105
+
106
+ <div class="ws-avatar-stack" role="img" aria-label="Priya Nair, Dana Sato, Sam Chen, Nina Ross and 12 more">
107
+ <span class="ws-avatar ws-avatar--sm">PN</span>
108
+ <span class="ws-avatar ws-avatar--sm">DS</span>
109
+ <span class="ws-avatar ws-avatar--sm">SC</span>
110
+ <span class="ws-avatar ws-avatar--sm">NR</span>
111
+ <span class="ws-avatar ws-avatar--sm ws-avatar-stack__more">+12</span>
112
+ </div>
113
+ ```
114
+
115
+ ## Styles
116
+
117
+ The disc, its sizes and the stack are house styles (`design-system/components/_house.css`, "Rows and lists"); the photo
118
+ layer is `design-system/components/Avatar/style.css`. Both are in `bundle.css` and `fello.css`. Dark comes from the
119
+ tokens: the sunk ground, the navy initials (`--color-text-heading`), the stack's rings in `--color-bg-page`.
@@ -1,4 +1,4 @@
1
- <!-- @dsCard group="People" height=332 -->
1
+ <!-- @dsCard group="People" height=420 -->
2
2
  <!doctype html>
3
3
  <html>
4
4
  <head>
@@ -12,7 +12,7 @@
12
12
 
13
13
  <div class="ws-stack" style="align-items: flex-start;">
14
14
  <span class="ws-muted">Sizes: sm 28, md 32, deck 48, panel 56, xl 64 (initials until a photo loads)</span>
15
- <div class="ws-cluster ws-cluster--group">
15
+ <div class="ws-cluster ws-cluster--group" style="align-items: flex-end;">
16
16
  <div class="ws-stack ws-stack--tight" style="align-items: center;"><span class="ws-avatar ws-avatar--plain ws-avatar--sm" aria-hidden="true">PN</span><span class="ws-muted">sm</span></div>
17
17
  <div class="ws-stack ws-stack--tight" style="align-items: center;"><span class="ws-avatar ws-avatar--plain" aria-hidden="true">PN</span><span class="ws-muted">md</span></div>
18
18
  <div class="ws-stack ws-stack--tight" style="align-items: center;"><span class="ws-avatar ws-avatar--plain ws-avatar--deck" aria-hidden="true">PN</span><span class="ws-muted">deck</span></div>
@@ -21,6 +21,15 @@
21
21
  </div>
22
22
  </div>
23
23
 
24
+ <div class="ws-stack" style="align-items: flex-start;">
25
+ <span class="ws-muted">Photo: it covers the disc once loaded, the initials stay under it (the stand-in picture here is a drawing, not a person)</span>
26
+ <div class="ws-cluster ws-cluster--group" style="align-items: flex-end;">
27
+ <div class="ws-stack ws-stack--tight" style="align-items: center;"><span class="ws-avatar ws-avatar--plain" aria-hidden="true">DS<img alt="" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' fill='%23cfc8c0'/%3E%3Ccircle cx='32' cy='26' r='12' fill='%23a39a90'/%3E%3Cpath d='M10 64c2-14 12-22 22-22s20 8 22 22z' fill='%23a39a90'/%3E%3C/svg%3E"></span><span class="ws-muted">loaded</span></div>
28
+ <div class="ws-stack ws-stack--tight" style="align-items: center;"><span class="ws-avatar ws-avatar--plain" aria-hidden="true">DS<img alt="" data-state="loading" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' fill='%23cfc8c0'/%3E%3C/svg%3E"></span><span class="ws-muted">loading</span></div>
29
+ <div class="ws-stack ws-stack--tight" style="align-items: center;"><span class="ws-avatar ws-avatar--plain ws-avatar--panel" aria-hidden="true">DS<img alt="" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' fill='%23cfc8c0'/%3E%3Ccircle cx='32' cy='26' r='12' fill='%23a39a90'/%3E%3Cpath d='M10 64c2-14 12-22 22-22s20 8 22 22z' fill='%23a39a90'/%3E%3C/svg%3E"></span><span class="ws-muted">panel</span></div>
30
+ </div>
31
+ </div>
32
+
24
33
  <div class="ws-stack" style="align-items: flex-start;">
25
34
  <span class="ws-muted">Ring (on a cream card) and dim (invited, not yet joined)</span>
26
35
  <div class="ws-cluster ws-cluster--group">
@@ -31,12 +40,12 @@
31
40
 
32
41
  <div class="ws-stack" style="align-items: flex-start;">
33
42
  <span class="ws-muted">AvatarStack: four faces overlapping by 4 px on cream rings, the rest as "+N"</span>
34
- <div class="ws-avatar-stack" aria-hidden="true">
43
+ <div class="ws-avatar-stack" role="img" aria-label="Priya Nair, Dana Sato, Sam Chen, Nina Ross and 12 more">
35
44
  <span class="ws-avatar ws-avatar--sm">PN</span>
36
45
  <span class="ws-avatar ws-avatar--sm">DS</span>
37
46
  <span class="ws-avatar ws-avatar--sm">SC</span>
38
47
  <span class="ws-avatar ws-avatar--sm">NR</span>
39
- <span class="ws-avatar ws-avatar--sm ws-avatar-stack__more">+3</span>
48
+ <span class="ws-avatar ws-avatar--sm ws-avatar-stack__more">+12</span>
40
49
  </div>
41
50
  </div>
42
51
 
@@ -0,0 +1,14 @@
1
+ /* ---- Avatar: the photo layer over the initials disc (the disc, its sizes and the stack are house styles). The photo
2
+ covers the disc once it loads; until then, and if it never does, the initials under it are the mark. ---- */
3
+ .ws-avatar { position: relative; }
4
+ .ws-avatar > img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; border-radius: inherit; }
5
+ /* A photo still loading (the React component sets data-state, or a page script on load) shows the initials; it fades in
6
+ over them once it has loaded. Without data-state the photo simply shows when the browser has it. */
7
+ .ws-avatar > img[data-state="loading"] { opacity: 0; }
8
+ @media (prefers-reduced-motion: no-preference) { .ws-avatar > img { transition: opacity var(--ws-duration-gentle) var(--ws-ease-soft); } }
9
+ /* Dim (invited, not yet joined): the disc stays whole and its initials take the muted word, AA on every ground (the
10
+ house fade of the whole disc left them at 3.7:1); a photo in it fades to 60 %. */
11
+ .ws-avatar--dim { opacity: 1; color: var(--color-text-muted); }
12
+ .ws-avatar--dim > img { opacity: 0.6; }
13
+ /* "+N" in a stack: the same disc, tabular figures, so +9 and +12 sit alike. */
14
+ .ws-avatar-stack__more { font-variant-numeric: tabular-nums; }
@@ -15,26 +15,23 @@ A small count or mark riding on the corner of a face, an icon or a nav row, sayi
15
15
 
16
16
  ## Anatomy
17
17
 
18
- - Box: at least 16 px tall, `--ws-radius-chip` (2 px) corners, matching `Chip`'s own corner rule rather than the kit's
19
- pill shape, since this sits on top of a host, not inline in a sentence.
20
- - Words: 11 px semibold (`--ws-text-micro` at weight 600). 04-type.md's floor is "nothing under 11 px, and 11 only for a
21
- chart's axis letters"; a badge's number is the one other place that floor value is reused, because a crowded count on
22
- a small face cannot afford more room and this is the smallest the type system allows, not an invented smaller size.
23
- - Dot: 6 px, solid `--color-brand-salmon`, no words, for a face too small for a number.
24
- - Position: overlapping the host's corner (a small negative offset), so it reads as riding on the host, not beside it.
18
+ - Box: an 18 px pill (`--space-md` + `--space-3xs`, `--radius-full`), at least as wide as it is tall, 6 px sides: North's
19
+ count. On a host's corner it is 16 px, 4 px sides, so it covers less of a face, with a 2 px ring of the page round it.
20
+ - Words: 11 px semibold (`--ws-text-micro` at 600), even numerals, so a count that changes does not jiggle.
21
+ - Dot: 6 px, solid salmon (`--ws-chart-under-half`), no words, for a face too small for a number.
22
+ - Position on a host: the top-right corner, 4 px out, riding on the host.
25
23
 
26
24
  ## Variants and sizes
27
25
 
28
26
  | Tone | Fill | Words | Where |
29
27
  | --- | --- | --- | --- |
28
+ | `navy` | `--color-text-heading` | `--ws-on-act` | a count in the nav or on a row (North's count) |
30
29
  | `needs` | `--ws-tint-needs` | `--ws-word-needs` | a count that needs you (unread, overdue) |
31
30
  | `neutral` | `--ws-surface-sunk` | `--color-text-heading` | a plain count with no urgency |
32
- | dot | `--color-brand-salmon` | (none) | a face too small to hold a number |
31
+ | dot | `--ws-chart-under-half` | (none) | a face too small to hold a number |
33
32
 
34
- These two tones reuse `Chip`'s own `needs` and `sunk`/`navy` colour pairs rather than inventing new ones: `Chip`'s own
35
- rule is "a seventh look is a sign the screen needs a different component, not a new tone," and `Badge` earns its own
36
- component by being a different shape and role (an overlay mark, not an inline fact), not by adding a seventh `Chip`
37
- tone.
33
+ The kit's marketing pill (`ws-kit-badge`, six tones) is shown captioned "kit, marketing only"; its words now take the AA
34
+ step on their own tint (brand, success and error were 2.3 to 3.6:1).
38
35
 
39
36
  ## States
40
37
 
@@ -44,15 +41,21 @@ tone.
44
41
  - **On a nav row:** sits at the row's trailing edge.
45
42
  - **On an icon button:** sits at the button's top-right corner.
46
43
  - **The dot alone:** no number, no words, just the 6 px mark.
44
+ - **Default, navy:** the act's navy, cream numerals (in the nav).
45
+ - **Changed:** re-keyed with `ws-pop`, it pops in from 0.85 on the small-mark overshoot (`--ws-ease-pop`); still under
46
+ reduced motion.
47
47
  - **Empty:** a badge with nothing to say is left out, never rendered at zero.
48
+ - **Dark:** every tone takes its dark values; the ring round a corner badge is the page's dark ground.
48
49
 
49
50
  ## Behaviour
50
51
 
51
52
  - Keyboard / pointer: none of its own; a badge is a `<span>`, not a control. If the host is interactive (a nav row, an
52
53
  icon button), the badge's count is read as part of that host's accessible name ("Needs you, 3"), never as a separate
53
- focusable element.
54
- - Motion: none on its own; a badge appearing with its host may rise with it (`ws-rise`).
55
- - Reduced motion: no change, since it has no motion of its own.
54
+ focusable element: inside a row it stays readable; on a face or an icon button it is `aria-hidden` and the host's
55
+ label carries the count ("Leads, 12 new"), never a count only the eye gets.
56
+ - Motion: a badge whose number changes is re-keyed with `ws-pop` and pops in (North's badge spring); one appearing with
57
+ its host rises with it (`ws-rise`).
58
+ - Reduced motion: still.
56
59
 
57
60
  ## Content
58
61
 
@@ -64,18 +67,18 @@ tone.
64
67
 
65
68
  | Name | Type | Meaning |
66
69
  | --- | --- | --- |
67
- | `tone` | `'needs' \| 'neutral'` | the look, default `needs` |
70
+ | `tone` | `'navy' \| 'needs' \| 'neutral'` | the look, default `needs` |
68
71
  | `count` | `number` | the number shown; omit together with `dot` to show nothing |
69
72
  | `dot` | `boolean` | shows the plain salmon dot instead of a number |
70
73
  | `max` | `number` | a count past this shows as `"${max}+"` |
71
74
 
72
75
  ## Rules
73
76
 
74
- - Two tones only, both borrowed from `Chip`'s existing colour pairs, so a badge never introduces a colour the system
75
- does not already use for "needs you" or "a neutral count."
77
+ - Three tones only: the act's navy (a count in the nav, North's), and `Chip`'s "needs you" and neutral pairs, so a badge
78
+ never brings a colour the system does not already use.
76
79
  - The dot is the one case with no words, used only where a number would crowd a small face, matching the system's
77
80
  existing "a small salmon dot" needs-you mark used elsewhere on a sidebar face.
78
- - Never a seventh tone: a look that does not fit `needs` or `neutral` is a sign the screen needs a different component.
81
+ - Never a fourth tone: a look that does not fit is a sign the screen needs a different component.
79
82
 
80
83
  ## Source
81
84
 
@@ -90,3 +93,22 @@ without overwhelming it), 11 px semibold instead of 12 px medium (the type floor
90
93
  only two tones instead of six, both reusing `Chip`'s existing AA-safe colour pairs rather than the kit's PROVISIONAL
91
94
  feedback tokens. The kit's six marketing tones are shown here captioned "kit, marketing only" and are not used in the
92
95
  product.
96
+
97
+ 0.5.0: North's count (style1.css:482; C6, C30) overturns the 2 px corners and the "no navy pill" rule: an 18 px navy pill
98
+ with cream 11 px numerals; the corner badge keeps 16 px.
99
+
100
+ ## Markup
101
+
102
+ ```html
103
+ <span class="ws-app-shell__row">Needs you <span class="ws-badge ws-badge--navy">5</span></span>
104
+ <span class="ws-badge-host">
105
+ <span class="ws-avatar" role="img" aria-label="Priya Nair, 3 unread">PN</span>
106
+ <span class="ws-badge ws-badge--needs" aria-hidden="true">3</span>
107
+ </span>
108
+ ```
109
+
110
+ ## Styles
111
+
112
+ `design-system/components/Badge/style.css`: `ws-badge` with `--navy`, `--needs`, `--neutral`, `--dot`; `ws-badge-host`;
113
+ the marketing `ws-kit-badge` and its six tones: `ws-kit-badge--navy`, `ws-kit-badge--neutral`, `ws-kit-badge--brand`,
114
+ `ws-kit-badge--success`, `ws-kit-badge--error`, `ws-kit-badge--outline` (marketing pages only).
@@ -1,4 +1,4 @@
1
- <!-- @dsCard group="Feedback" height=244 -->
1
+ <!-- @dsCard group="Feedback" height=280 -->
2
2
  <!doctype html>
3
3
  <html>
4
4
  <head>
@@ -12,25 +12,26 @@
12
12
  <div class="ws-stack ws-stack--group">
13
13
 
14
14
  <div class="ws-stack">
15
- <span class="ws-muted">On an avatar, a nav row and an icon button</span>
15
+ <span class="ws-muted">A count: navy in a nav row, the needs tint on a face, sunk on an icon button (18 px pills)</span>
16
16
  <div class="ws-cluster ws-cluster--component">
17
- <span class="ws-badge-host">
18
- <span class="ws-avatar">PN</span>
19
- <span class="ws-badge ws-badge--needs" aria-hidden="true">3</span>
20
- </span>
21
17
  <div style="width: 180px;">
22
18
  <span class="ws-app-shell__row ws-app-shell__row--current">
23
19
  <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 4h10M3 8h10M3 12h7"/></svg>
24
20
  <span class="ws-app-shell__name">Needs you</span>
25
- <span class="ws-badge ws-badge--needs" aria-hidden="true">5</span>
21
+ <span class="ws-badge ws-badge--navy">5</span>
26
22
  </span>
27
23
  </div>
24
+ <span class="ws-badge-host">
25
+ <span class="ws-avatar" role="img" aria-label="Priya Nair, 3 unread">PN</span>
26
+ <span class="ws-badge ws-badge--needs" aria-hidden="true">3</span>
27
+ </span>
28
28
  <span class="ws-badge-host">
29
29
  <button type="button" class="ws-icon-btn ws-icon-btn--outline" aria-label="Leads, 12 new">
30
- <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="5.5" r="2.5"/><path d="M3 13c0-2.5 2.2-4 5-4s5 1.5 5 4"/></svg>
30
+ <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="8" cy="5.5" r="2.5"/><path d="M3 13c0-2.5 2.2-4 5-4s5 1.5 5 4"/></svg>
31
31
  </button>
32
32
  <span class="ws-badge ws-badge--neutral" aria-hidden="true">12</span>
33
33
  </span>
34
+ <span class="ws-badge ws-badge--navy ws-pop" aria-hidden="true">128</span>
34
35
  </div>
35
36
  </div>
36
37
 
@@ -45,11 +46,13 @@
45
46
  </div>
46
47
 
47
48
  <div class="ws-stack">
48
- <span class="ws-muted">Kit, marketing only: the six badgeVariants tones (dist/index.js), not used in the product</span>
49
- <div class="ws-cluster ws-cluster--component">
49
+ <span class="ws-muted">Kit, marketing only: the six badgeVariants tones, words on their AA step</span>
50
+ <div class="ws-cluster">
50
51
  <span class="ws-kit-badge ws-kit-badge--navy">Navy</span>
51
52
  <span class="ws-kit-badge ws-kit-badge--neutral">Neutral</span>
52
53
  <span class="ws-kit-badge ws-kit-badge--brand">Brand</span>
54
+ </div>
55
+ <div class="ws-cluster">
53
56
  <span class="ws-kit-badge ws-kit-badge--success">Success</span>
54
57
  <span class="ws-kit-badge ws-kit-badge--error">Error</span>
55
58
  <span class="ws-kit-badge ws-kit-badge--outline">Outline</span>
@@ -0,0 +1,23 @@
1
+ /* ---- Badge: a count or a dot on a face, a row or a button (and the kit's marketing pill). North's count (0.5.0, D1;
2
+ C6): an 18 px pill, 11 px semibold numerals that keep their width; navy for a count in the nav, the needs tint for
3
+ something that needs you, sunk for a plain count. It pops in when its number changes (re-key it with ws-pop). ---- */
4
+ .ws-badge { display: inline-flex; align-items: center; justify-content: center; min-width: calc(var(--space-md) + var(--space-3xs)); height: calc(var(--space-md) + var(--space-3xs)); padding: 0 var(--space-xxs-plus); border-radius: var(--radius-full); font-size: var(--ws-text-micro); line-height: var(--ws-leading-micro); font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
5
+ .ws-badge--navy { background: var(--color-text-heading); color: var(--ws-on-act); }
6
+ .ws-badge--needs { background: var(--ws-tint-needs); color: var(--ws-word-needs); }
7
+ .ws-badge--neutral { background: var(--ws-surface-sunk); color: var(--color-text-heading); }
8
+ .ws-badge--dot { width: var(--space-xxs-plus); min-width: var(--space-xxs-plus); height: var(--space-xxs-plus); padding: 0; background: var(--ws-chart-under-half); }
9
+ .ws-badge.ws-pop { animation-timing-function: var(--ws-ease-pop); }
10
+ /* What a badge sits on: the badge on its top right corner, 4 px out, 16 tall so it covers less of a face, a 2 px ring
11
+ of the page keeping it apart. */
12
+ .ws-badge-host { position: relative; display: inline-flex; }
13
+ .ws-badge-host > .ws-badge { position: absolute; top: calc(var(--space-xxs) * -1); right: calc(var(--space-xxs) * -1); min-width: var(--space-md); height: var(--space-md); padding: 0 var(--space-xxs); box-shadow: 0 0 0 var(--ws-ring) var(--color-bg-page); }
14
+ .ws-badge-host > .ws-badge--dot { min-width: var(--space-xxs-plus); height: var(--space-xxs-plus); padding: 0; }
15
+ /* The kit's badge, marketing pages only: a pill, 12 px medium, six tones. Words take their AA step on their own tint;
16
+ the outline is drawn inside, so every tone is the same size. */
17
+ .ws-kit-badge { display: inline-flex; align-items: center; gap: var(--space-xxs); height: var(--space-lg); padding: 0 var(--space-xs-plus); border-radius: var(--radius-full); font-size: var(--font-size-2xs); line-height: var(--space-md); font-weight: 500; white-space: nowrap; }
18
+ .ws-kit-badge--navy { background: color-mix(in srgb, var(--color-text-heading) 10%, transparent); color: var(--color-text-heading); }
19
+ .ws-kit-badge--neutral { background: var(--ws-track); color: var(--color-text-heading); }
20
+ .ws-kit-badge--brand { background: color-mix(in srgb, var(--ws-chart-under-half) 12%, transparent); color: var(--color-text-brand-strong); }
21
+ .ws-kit-badge--success { background: var(--color-feedback-success-bg); color: var(--color-text-success-strong); }
22
+ .ws-kit-badge--error { background: color-mix(in srgb, var(--color-feedback-error) 14%, transparent); color: var(--color-text-error-strong); }
23
+ .ws-kit-badge--outline { background: transparent; color: var(--color-text-body); box-shadow: inset 0 0 0 var(--ws-line) var(--color-border-hairline); }
@@ -14,13 +14,14 @@ A full-width notice for a state of the whole page or card, in one of four quiet
14
14
 
15
15
  ## Anatomy
16
16
 
17
- - Shell: full width of its container, `--space-sm` (12 px) padding, `--ws-radius-card` (8 px) corners, a flex row of an
18
- icon, words, an act and an optional close.
19
- - Icon: 16 px line icon at the left, coloured to match the tone's words.
20
- - Words: 14 px, two parts: the first sentence navy medium (`--color-text-heading`, weight 500), the rest body grey
21
- regular (`--color-text-body`): an emphasis on the headline clause, not a two-tone treatment of every word.
22
- - Act: a text act (`ws-act`, quiet) or a secondary small (28 px) button, at the right.
23
- - Optional close: a 24 px `xs` ghost `IconButton` "x".
17
+ - Shell: full width of its container, 8 / 12 px inside (`--space-xs`, `--space-sm`), 8 px corners (`--ws-radius-card`),
18
+ a row of a mark, words, an act and an optional close, 8 apart.
19
+ - One band: everything is centred on a 28 px band at the top (40 on touch): the mark, the first line of words and each
20
+ act. When the words wrap on a phone the mark and the acts stay level with the first line.
21
+ - Mark: 16 px line icon at the left, in the tone's words colour.
22
+ - Words: 14 / 20, two parts: the first sentence navy medium, the rest body grey.
23
+ - Act: a `text` button or a `well` small (28 px) button, at the right.
24
+ - Optional close: a 24 px `xs` ghost `IconButton` x (a 40 px target on touch).
24
25
 
25
26
  ## Variants and sizes
26
27
 
@@ -37,7 +38,9 @@ There is one size; it spans whatever width its container gives it.
37
38
 
38
39
  - **The four tones:** as the table above, each shown with its own icon, words and act.
39
40
  - **With a close act:** the `x` icon button sits at the far right.
41
+ - **Long words / narrow:** the words wrap; the mark and the acts stay on the first line's band.
40
42
  - **Dismissed:** the banner is removed entirely; nothing is drawn in its place.
43
+ - **Dark:** each tint and its words take their dark values; every pair stays AA.
41
44
 
42
45
  ## Behaviour
43
46
 
@@ -59,7 +62,7 @@ There is one size; it spans whatever width its container gives it.
59
62
  | `tone` | `'neutral' \| 'needs' \| 'positive' \| 'working'` | the look, default `neutral` |
60
63
  | `icon` | `IconName` | the 16 px icon at the left |
61
64
  | `children` | `ReactNode` | the words |
62
- | `action` | `ReactNode` | the text or secondary small button at the right |
65
+ | `action` | `ReactNode` | the `text` or `well` small button at the right |
63
66
  | `onDismiss` | `() => void` | present only when the close act shows |
64
67
 
65
68
  ## Rules
@@ -74,5 +77,23 @@ There is one size; it spans whatever width its container gives it.
74
77
 
75
78
  **Derived from**: 10-patterns.md's one ad hoc case ("Offline, a muted line at the top of the stage"), generalized into
76
79
  four tones built on the system's own wash tokens (`ws-tint-needs`, `ws-tint-live`, `ws-card-tint-blue`,
77
- `ws-surface-sunk`) and `Button`'s secondary small size for its act. **Precedent**: an intentional addition decided
80
+ `ws-surface-sunk`) and `Button`'s small size for its act. **Precedent**: an intentional addition decided
78
81
  from the system, 26 Oct 2026.
82
+
83
+ 0.5.0: the band fix (the mark and the acts floated to the middle of wrapped words).
84
+
85
+ ## Markup
86
+
87
+ ```html
88
+ <div class="ws-banner ws-banner--needs" role="alert">
89
+ <svg class="ws-banner__mark" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 2.3 14.6 13.5H1.4L8 2.3Z"/><path d="M8 6.4v3"/><path d="M8 11.5v.05"/></svg>
90
+ <span class="ws-banner__words"><span class="ws-banner__lead">You're offline.</span> <span class="ws-banner__rest">Changes will send once you're back.</span></span>
91
+ <button type="button" class="ws-btn ws-btn--well ws-btn--sm">Retry</button>
92
+ <button type="button" class="ws-icon-btn ws-icon-btn--xs" aria-label="Dismiss"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true"><path d="M4 4l8 8M12 4l-8 8"/></svg></button>
93
+ </div>
94
+ ```
95
+
96
+ ## Styles
97
+
98
+ `design-system/components/Banner/style.css`: `ws-banner` with `--neutral`, `--needs`, `--positive`, `--working`;
99
+ `__mark`, `__words`, `__lead`, `__rest`.
@@ -25,7 +25,7 @@
25
25
  <div class="ws-banner ws-banner--needs" role="alert">
26
26
  <svg class="ws-banner__mark" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 2.3 14.6 13.5H1.4L8 2.3Z"/><path d="M8 6.4v3"/><path d="M8 11.5v.05"/></svg>
27
27
  <span class="ws-banner__words"><span class="ws-banner__lead">You're offline.</span> <span class="ws-banner__rest">Changes will send once you're back.</span></span>
28
- <button type="button" class="ws-btn ws-btn--secondary ws-btn--sm">Retry</button>
28
+ <button type="button" class="ws-btn ws-btn--well ws-btn--sm">Retry</button>
29
29
  <button type="button" class="ws-icon-btn ws-icon-btn--xs" aria-label="Dismiss">
30
30
  <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true"><path d="M4 4l8 8M12 4l-8 8"/></svg>
31
31
  </button>
@@ -0,0 +1,19 @@
1
+ /* ---- Banner: a full-width line of news on a tint, its mark, its words and at most one act (and a close). Everything
2
+ sits on one 28 px band at the top (40 on touch): the mark, the first line of words and each act are centred on it, so
3
+ when the words wrap on a phone the mark and the acts stay with the first line instead of floating in the middle. ---- */
4
+ .ws-banner { --ws-banner-band: var(--ws-control-h-sm); display: flex; align-items: flex-start; gap: var(--space-xs); width: 100%; padding: var(--space-xs) var(--space-sm); border-radius: var(--ws-radius-card); }
5
+ .ws-banner__mark { flex-shrink: 0; width: var(--ws-icon); height: var(--ws-icon); margin-block: calc((var(--ws-banner-band) - var(--ws-icon)) / 2); }
6
+ .ws-banner__words { flex: 1; min-width: 0; padding-block: calc((var(--ws-banner-band) - var(--space-md) - var(--space-xxs)) / 2); font-size: var(--ws-text-body); line-height: calc(var(--space-md) + var(--space-xxs)); }
7
+ .ws-banner > :is(.ws-btn--link, .ws-btn--text) { min-height: var(--space-lg); }
8
+ .ws-banner > :is(.ws-btn--link, .ws-btn--text, .ws-icon-btn--xs) { margin-block: calc((var(--ws-banner-band) - var(--space-lg)) / 2); }
9
+ .ws-banner__lead { font-weight: 500; color: var(--color-text-heading); }
10
+ .ws-banner__rest { color: var(--color-text-body); }
11
+ .ws-banner--neutral { background: var(--ws-surface-sunk); }
12
+ .ws-banner--neutral .ws-banner__mark { color: var(--color-text-muted); }
13
+ .ws-banner--needs { background: var(--ws-tint-needs); }
14
+ .ws-banner--needs .ws-banner__mark, .ws-banner--needs .ws-banner__lead { color: var(--ws-word-needs); }
15
+ .ws-banner--positive { background: var(--ws-tint-live); }
16
+ .ws-banner--positive .ws-banner__mark, .ws-banner--positive .ws-banner__lead { color: var(--ws-word-positive); }
17
+ .ws-banner--working { background: var(--ws-card-tint-blue); }
18
+ .ws-banner--working .ws-banner__mark, .ws-banner--working .ws-banner__lead { color: var(--color-text-interactive); }
19
+ @media (pointer: coarse) { .ws-banner { --ws-banner-band: var(--ws-touch); } }