@micha.bigler/ui-core-micha 3.14.3 → 4.0.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 (367) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +7 -0
  3. package/dist/auth/UserMenu.js +1 -1
  4. package/dist/auth/apiClient.js +7 -6
  5. package/dist/chat/ChatList.js +1 -1
  6. package/dist/chat/ChatThread.js +2 -1
  7. package/dist/chat/ChatWorkspace.js +1 -1
  8. package/dist/chat/ModelPicker.js +2 -5
  9. package/dist/chat/kitSxRegistry.js +9 -0
  10. package/dist/chat/useChatSocket.js +1 -1
  11. package/dist/components/LoginForm.js +1 -1
  12. package/dist/components/SecurityComponent.js +1 -1
  13. package/dist/components/UserListComponent.js +2 -8
  14. package/dist/components/charts/ChartFrame.js +1 -1
  15. package/dist/components/charts/ScatterChart.js +2 -1
  16. package/dist/components/charts/kitSxRegistry.js +6 -0
  17. package/dist/entries/charts.js +6 -0
  18. package/dist/entries/chat.js +8 -0
  19. package/dist/entries/markdown.js +1 -0
  20. package/dist/index.js +0 -14
  21. package/dist/layout/SectionNav.js +1 -1
  22. package/dist/messaging/AttachmentList.js +2 -2
  23. package/dist/messaging/Composer.js +2 -1
  24. package/dist/messaging/ConversationLaunchers.js +1 -4
  25. package/dist/messaging/ConversationList.js +1 -1
  26. package/dist/messaging/DirectMessageLauncher.js +1 -1
  27. package/dist/messaging/MessagingProvider.js +0 -0
  28. package/dist/messaging/PollCard.js +7 -5
  29. package/dist/messaging/ReadTicks.js +2 -2
  30. package/dist/messaging/Thread.js +5 -3
  31. package/dist/notifications/NotificationBell.js +4 -7
  32. package/dist/notifications/NotificationSettings.js +1 -1
  33. package/dist/onboarding/OnboardingProvider.js +3 -6
  34. package/dist/pages/AccountPage.js +1 -2
  35. package/dist/pages/LoginPage.js +2 -3
  36. package/dist/pages/PasswordChangePage.js +2 -3
  37. package/dist/pages/PasswordInvitePage.js +2 -3
  38. package/dist/pages/PasswordResetRequestPage.js +2 -3
  39. package/dist/pages/SignUpPage.js +2 -3
  40. package/dist/pages/SignupConfirmPage.js +2 -3
  41. package/dist/theme/createAppTheme.js +5 -4
  42. package/dist/theme/kitSxEntry.js +11 -0
  43. package/dist/theme/kitSxRegistry.js +2 -15
  44. package/dist/theme/themeCompleteness.js +3 -12
  45. package/dist/utils/auth-errors.js +4 -3
  46. package/package.json +41 -16
  47. package/.github/workflows/publish.yml +0 -126
  48. package/DESIGN.md +0 -60
  49. package/WORK_ORDERS.md +0 -148
  50. package/dev/entries.jsx +0 -721
  51. package/dev/fixtures.js +0 -26
  52. package/dev/main.jsx +0 -75
  53. package/dev/mockTransport.jsx +0 -128
  54. package/docs/CHART-LAYOUT.md +0 -157
  55. package/docs/DARK-TOKENS.md +0 -140
  56. package/docs/TYPE-SCALE.md +0 -111
  57. package/docs/messaging-deviations.md +0 -118
  58. package/index.html +0 -12
  59. package/renovate.json +0 -10
  60. package/scripts/chart-api-census.mjs +0 -415
  61. package/src/auth/AuthContext.jsx +0 -158
  62. package/src/auth/ReauthModal.jsx +0 -82
  63. package/src/auth/UserMenu.jsx +0 -121
  64. package/src/auth/apiClient.jsx +0 -232
  65. package/src/auth/authApi.jsx +0 -539
  66. package/src/auth/authConfig.jsx +0 -34
  67. package/src/auth/reauth.js +0 -47
  68. package/src/chat/ChatComposer.jsx +0 -36
  69. package/src/chat/ChatList.jsx +0 -87
  70. package/src/chat/ChatThread.jsx +0 -128
  71. package/src/chat/ChatWorkspace.jsx +0 -244
  72. package/src/chat/MarkdownReply.jsx +0 -22
  73. package/src/chat/ModelPicker.jsx +0 -85
  74. package/src/chat/api.js +0 -18
  75. package/src/chat/useChatSocket.js +0 -32
  76. package/src/chat/wire.js +0 -7
  77. package/src/components/AccessCodeManager.jsx +0 -288
  78. package/src/components/AccessCodeSingleUseToggle.jsx +0 -92
  79. package/src/components/AllowedEmailDomainsManager.jsx +0 -92
  80. package/src/components/AuthFactorRequirementCard.jsx +0 -95
  81. package/src/components/BulkInviteCsvTab.jsx +0 -198
  82. package/src/components/EmptyState.jsx +0 -38
  83. package/src/components/LoginForm.jsx +0 -159
  84. package/src/components/MFAComponent.jsx +0 -350
  85. package/src/components/MfaLoginComponent.jsx +0 -252
  86. package/src/components/MobileBottomNav.jsx +0 -82
  87. package/src/components/PasskeysComponent.jsx +0 -252
  88. package/src/components/PasswordChangeForm.jsx +0 -87
  89. package/src/components/PasswordResetRequestForm.jsx +0 -43
  90. package/src/components/PasswordSetForm.jsx +0 -97
  91. package/src/components/ProfileComponent.jsx +0 -348
  92. package/src/components/QrSignupManager.jsx +0 -404
  93. package/src/components/QrSignupValidityManager.jsx +0 -106
  94. package/src/components/RegistrationMethodsManager.jsx +0 -150
  95. package/src/components/SecurityComponent.jsx +0 -141
  96. package/src/components/SocialLoginButtons.jsx +0 -83
  97. package/src/components/SoftChip.jsx +0 -93
  98. package/src/components/StatTile.jsx +0 -39
  99. package/src/components/SupportRecoveryRequestsTab.jsx +0 -482
  100. package/src/components/TurnstileWidget.jsx +0 -99
  101. package/src/components/UserInviteComponent.jsx +0 -76
  102. package/src/components/UserListComponent.jsx +0 -618
  103. package/src/components/charts/BarChart.jsx +0 -102
  104. package/src/components/charts/ChartFrame.jsx +0 -214
  105. package/src/components/charts/LineChart.jsx +0 -126
  106. package/src/components/charts/ScatterChart.jsx +0 -299
  107. package/src/components/charts/TimeSeriesChart.jsx +0 -300
  108. package/src/components/charts/chartDefaults.js +0 -644
  109. package/src/components/charts/chartLabels.js +0 -36
  110. package/src/components/charts/exportChart.js +0 -258
  111. package/src/components/charts/formatters.js +0 -39
  112. package/src/components/charts/palette.js +0 -33
  113. package/src/components/charts/yearTickInterval.js +0 -7
  114. package/src/i18n/authTranslations.ts +0 -2651
  115. package/src/i18n/chartsTranslations.ts +0 -91
  116. package/src/i18n/chatTranslations.ts +0 -55
  117. package/src/i18n/messagingTranslations.ts +0 -120
  118. package/src/i18n/notificationsTranslations.ts +0 -32
  119. package/src/i18n/onboardingTranslations.ts +0 -29
  120. package/src/i18n/sectionNavTranslations.ts +0 -20
  121. package/src/i18n/uiCoreTranslations.js +0 -29
  122. package/src/i18n/userMenuTranslations.ts +0 -5
  123. package/src/index.js +0 -158
  124. package/src/layout/PageLayout.jsx +0 -36
  125. package/src/layout/SectionNav.jsx +0 -320
  126. package/src/messaging/AttachmentList.jsx +0 -145
  127. package/src/messaging/Composer.jsx +0 -197
  128. package/src/messaging/ConversationLaunchers.jsx +0 -51
  129. package/src/messaging/ConversationList.jsx +0 -109
  130. package/src/messaging/DirectMessageLauncher.jsx +0 -112
  131. package/src/messaging/MessageBubble.jsx +0 -183
  132. package/src/messaging/MessagingProvider.jsx +0 -0
  133. package/src/messaging/MessagingScopeConfig.jsx +0 -24
  134. package/src/messaging/PollCard.jsx +0 -163
  135. package/src/messaging/ReactionBar.jsx +0 -43
  136. package/src/messaging/ReadTicks.jsx +0 -82
  137. package/src/messaging/Thread.jsx +0 -195
  138. package/src/messaging/api.js +0 -48
  139. package/src/messaging/composerImageCompression.js +0 -35
  140. package/src/messaging/linkifyText.jsx +0 -37
  141. package/src/notifications/NotificationBell.jsx +0 -84
  142. package/src/notifications/NotificationSettings.jsx +0 -330
  143. package/src/notifications/NotificationsProvider.jsx +0 -224
  144. package/src/notifications/PopupSurface.jsx +0 -105
  145. package/src/notifications/api.js +0 -55
  146. package/src/notifications/feedApi.js +0 -18
  147. package/src/notifications/realtime.jsx +0 -138
  148. package/src/notifications/serviceWorker/sw.js +0 -50
  149. package/src/onboarding/OnboardingProvider.jsx +0 -326
  150. package/src/onboarding/OnboardingWizard.jsx +0 -61
  151. package/src/onboarding/WizardDialogShell.jsx +0 -51
  152. package/src/onboarding/api.js +0 -13
  153. package/src/onboarding/stepSelection.js +0 -16
  154. package/src/onboarding/steps/BrowserPushStep.jsx +0 -121
  155. package/src/onboarding/steps/CompleteNameStep.jsx +0 -46
  156. package/src/onboarding/steps/CookieConsentStep.jsx +0 -133
  157. package/src/onboarding/steps/PwaInstallStep.jsx +0 -47
  158. package/src/pages/AccountPage.jsx +0 -394
  159. package/src/pages/LoginPage.jsx +0 -343
  160. package/src/pages/PasswordChangePage.jsx +0 -62
  161. package/src/pages/PasswordInvitePage.jsx +0 -177
  162. package/src/pages/PasswordResetRequestPage.jsx +0 -68
  163. package/src/pages/SignUpPage.jsx +0 -268
  164. package/src/pages/SignupConfirmPage.jsx +0 -155
  165. package/src/testing/checkKitIntegration.js +0 -233
  166. package/src/theme/createAppTheme.js +0 -423
  167. package/src/theme/fonts.js +0 -3
  168. package/src/theme/index.js +0 -21
  169. package/src/theme/kitSxRegistry.js +0 -285
  170. package/src/theme/themeCompleteness.js +0 -1045
  171. package/src/theme/tokens.js +0 -478
  172. package/src/utils/auth-errors.js +0 -76
  173. package/src/utils/authService.js +0 -223
  174. package/src/utils/resolveErrorText.js +0 -28
  175. package/src/utils/webauthn.js +0 -51
  176. package/tests/AccountPage.profileStatements.test.jsx +0 -162
  177. package/tests/AccountPage.test.jsx +0 -200
  178. package/tests/AuthContext.test.jsx +0 -129
  179. package/tests/BarChart.test.jsx +0 -112
  180. package/tests/BrowserPushStep.test.jsx +0 -110
  181. package/tests/ChartFrame.test.jsx +0 -336
  182. package/tests/ConversationList.test.jsx +0 -144
  183. package/tests/CookieConsentStep.test.jsx +0 -69
  184. package/tests/EmptyState.test.jsx +0 -62
  185. package/tests/LineChart.test.jsx +0 -80
  186. package/tests/MessagingProvider.test.jsx +0 -50
  187. package/tests/MobileBottomNav.test.jsx +0 -319
  188. package/tests/NotificationBell.test.jsx +0 -60
  189. package/tests/NotificationSettings.test.jsx +0 -276
  190. package/tests/NotificationsProvider.test.jsx +0 -240
  191. package/tests/OnboardingProvider.test.jsx +0 -294
  192. package/tests/OnboardingWizard.test.jsx +0 -143
  193. package/tests/PasswordChangeForm.test.jsx +0 -70
  194. package/tests/PasswordInvitePage.test.jsx +0 -142
  195. package/tests/PasswordSetForm.test.jsx +0 -70
  196. package/tests/PopupSurface.test.jsx +0 -142
  197. package/tests/ProfileComponent.refresh.test.jsx +0 -194
  198. package/tests/QrSignupManager.registrationContext.test.jsx +0 -106
  199. package/tests/QrSignupManagerInfoBox.test.jsx +0 -49
  200. package/tests/ScatterChart.test.jsx +0 -270
  201. package/tests/SectionNav.test.jsx +0 -363
  202. package/tests/SignUpPage.test.jsx +0 -208
  203. package/tests/SoftChip.test.jsx +0 -105
  204. package/tests/StatTile.test.jsx +0 -57
  205. package/tests/Thread.test.jsx +0 -151
  206. package/tests/TimeSeriesChart.test.jsx +0 -367
  207. package/tests/UserListComponent.test.jsx +0 -192
  208. package/tests/UserMenu.test.jsx +0 -171
  209. package/tests/apiClientLanguage.test.js +0 -33
  210. package/tests/apiClientPublicPaths.test.js +0 -178
  211. package/tests/authApiLogin.test.js +0 -42
  212. package/tests/authApiRegistration.test.js +0 -31
  213. package/tests/authErrorCodeRendering.test.jsx +0 -77
  214. package/tests/authErrors.test.js +0 -74
  215. package/tests/authTranslations.test.js +0 -74
  216. package/tests/chart-api-census.test.js +0 -366
  217. package/tests/chartAxisTickBand.test.jsx +0 -95
  218. package/tests/chartDefaults.test.js +0 -902
  219. package/tests/chartLabels.test.js +0 -50
  220. package/tests/chartsPalette.test.js +0 -99
  221. package/tests/chatApi.test.js +0 -48
  222. package/tests/chatDriverNames.test.jsx +0 -71
  223. package/tests/chatExports.test.js +0 -3
  224. package/tests/chatList.test.jsx +0 -84
  225. package/tests/chatPicker.test.jsx +0 -82
  226. package/tests/chatSocket.test.jsx +0 -26
  227. package/tests/chatThread.test.jsx +0 -116
  228. package/tests/chatTranslations.test.js +0 -47
  229. package/tests/chatWorkspace.test.jsx +0 -234
  230. package/tests/checkKitIntegration.test.jsx +0 -219
  231. package/tests/createAppTheme.test.js +0 -132
  232. package/tests/createAppThemeDark.test.js +0 -127
  233. package/tests/createAppThemeLightGuard.test.js +0 -40
  234. package/tests/createAppThemePageContrast.test.js +0 -108
  235. package/tests/createAppThemeTypography.test.js +0 -53
  236. package/tests/errorCallSiteGuard.test.js +0 -32
  237. package/tests/exportChart.test.js +0 -313
  238. package/tests/feedApi.test.js +0 -46
  239. package/tests/fixtures/light-theme-baseline.json +0 -2234
  240. package/tests/i18nAggregate.test.js +0 -120
  241. package/tests/i18nGermanVariant.test.js +0 -65
  242. package/tests/loginPageErrorResolution.test.jsx +0 -102
  243. package/tests/messagingApi.test.js +0 -32
  244. package/tests/messagingChunk6.test.jsx +0 -116
  245. package/tests/messagingCompactBubble.test.jsx +0 -64
  246. package/tests/messagingComposer.test.jsx +0 -136
  247. package/tests/messagingComposerChunk5.test.jsx +0 -113
  248. package/tests/messagingContractConformance.test.js +0 -69
  249. package/tests/messagingDirectMessageLauncher.test.jsx +0 -151
  250. package/tests/messagingExports.test.js +0 -21
  251. package/tests/messagingInteractions.test.jsx +0 -136
  252. package/tests/messagingLastMessagePreview.test.jsx +0 -93
  253. package/tests/messagingLinkify.test.jsx +0 -51
  254. package/tests/messagingMessageActions.test.jsx +0 -163
  255. package/tests/messagingMsg6.test.jsx +0 -105
  256. package/tests/messagingMsg6f.test.jsx +0 -155
  257. package/tests/messagingMsg6g.test.jsx +0 -166
  258. package/tests/messagingMsg6h.test.jsx +0 -136
  259. package/tests/messagingMsg6i.test.jsx +0 -129
  260. package/tests/messagingPollRendering.test.jsx +0 -142
  261. package/tests/messagingReactionMerge.test.jsx +0 -77
  262. package/tests/messagingReadTicksTooltip.test.jsx +0 -36
  263. package/tests/messagingThreadReceipts.test.jsx +0 -178
  264. package/tests/messagingThreadingQuoting.test.jsx +0 -87
  265. package/tests/messagingUnreadLifecycle.test.jsx +0 -78
  266. package/tests/notificationsApi.test.js +0 -41
  267. package/tests/notificationsExports.test.js +0 -34
  268. package/tests/onboardingDescriptors.test.js +0 -132
  269. package/tests/packageTreeShaking.test.js +0 -126
  270. package/tests/realtime.test.jsx +0 -219
  271. package/tests/resolveErrorText.test.js +0 -72
  272. package/tests/stepSelection.test.js +0 -53
  273. package/tests/themeCompleteness.test.js +0 -626
  274. package/tests/timeSeriesLegend.test.js +0 -99
  275. package/tests/userScreenTranslations.test.jsx +0 -218
  276. package/tsconfig.build.json +0 -18
  277. package/vite.config.mjs +0 -6
  278. package/work-orders/AUTH-1.md +0 -74
  279. package/work-orders/AUTH-2.md +0 -74
  280. package/work-orders/AUTH-4.md +0 -183
  281. package/work-orders/AUTH-5.md +0 -154
  282. package/work-orders/AUTH-7.md +0 -181
  283. package/work-orders/AUTH-8.md +0 -184
  284. package/work-orders/CHART-1.md +0 -285
  285. package/work-orders/CHART-2.md +0 -222
  286. package/work-orders/CHART-3.md +0 -163
  287. package/work-orders/CHART-4.md +0 -203
  288. package/work-orders/CHART-5.md +0 -226
  289. package/work-orders/CHART-6.md +0 -428
  290. package/work-orders/CHART-7.md +0 -175
  291. package/work-orders/DX-1.md +0 -169
  292. package/work-orders/DX-2.md +0 -188
  293. package/work-orders/DX-3.md +0 -195
  294. package/work-orders/I18N-1.md +0 -254
  295. package/work-orders/I18N-3.md +0 -207
  296. package/work-orders/MSG-3.md +0 -375
  297. package/work-orders/MSG-3b.md +0 -1002
  298. package/work-orders/MSG-3c.md +0 -378
  299. package/work-orders/MSG-3d.md +0 -268
  300. package/work-orders/MSG-3e.md +0 -164
  301. package/work-orders/MSG-3f.md +0 -391
  302. package/work-orders/MSG-6e.md +0 -308
  303. package/work-orders/MSG-6f.md +0 -214
  304. package/work-orders/MSG-6g.md +0 -244
  305. package/work-orders/MSG-6h.md +0 -238
  306. package/work-orders/MSG-6i.md +0 -271
  307. package/work-orders/MSG-6j.md +0 -117
  308. package/work-orders/NOTIF-12.md +0 -214
  309. package/work-orders/NOTIF-13.md +0 -142
  310. package/work-orders/NOTIF-14.md +0 -151
  311. package/work-orders/PRIM-1.md +0 -177
  312. package/work-orders/SHELL-1.md +0 -255
  313. package/work-orders/SHELL-2.md +0 -197
  314. package/work-orders/SHELL-3.md +0 -359
  315. package/work-orders/SHELL-4.md +0 -351
  316. package/work-orders/SHELL-5.md +0 -345
  317. package/work-orders/SHELL-6.md +0 -210
  318. package/work-orders/THEME-1.md +0 -587
  319. package/work-orders/THEME-10.md +0 -303
  320. package/work-orders/THEME-11.md +0 -208
  321. package/work-orders/THEME-2.md +0 -339
  322. package/work-orders/THEME-3.md +0 -210
  323. package/work-orders/THEME-4.md +0 -271
  324. package/work-orders/THEME-5.md +0 -341
  325. package/work-orders/THEME-6.md +0 -247
  326. package/work-orders/THEME-7.md +0 -82
  327. package/work-orders/THEME-8.md +0 -211
  328. package/work-orders/THEME-9.md +0 -194
  329. package/work-orders/UCM-AUTH-10.md +0 -490
  330. package/work-orders/UCM-AUTH-11.md +0 -185
  331. package/work-orders/UCM-AUTH-12.md +0 -98
  332. package/work-orders/UCM-AUTH-13.md +0 -47
  333. package/work-orders/UCM-AUTH-9.md +0 -207
  334. package/work-orders/UCM-CHART-10.md +0 -150
  335. package/work-orders/UCM-CHART-11.md +0 -132
  336. package/work-orders/UCM-CHART-12.md +0 -348
  337. package/work-orders/UCM-CHART-13.md +0 -122
  338. package/work-orders/UCM-CHART-14.md +0 -198
  339. package/work-orders/UCM-CHART-15.md +0 -187
  340. package/work-orders/UCM-CHART-16.md +0 -169
  341. package/work-orders/UCM-CHART-17.md +0 -195
  342. package/work-orders/UCM-CHART-18.md +0 -200
  343. package/work-orders/UCM-CHART-8.md +0 -147
  344. package/work-orders/UCM-CHART-9.md +0 -265
  345. package/work-orders/UCM-CHAT-1.md +0 -190
  346. package/work-orders/UCM-CHAT-2.md +0 -75
  347. package/work-orders/UCM-DEP-1.md +0 -63
  348. package/work-orders/UCM-I18N-4.md +0 -226
  349. package/work-orders/UCM-I18N-5.md +0 -211
  350. package/work-orders/UCM-MSG-20.md +0 -192
  351. package/work-orders/UCM-PRIM-2.md +0 -114
  352. package/work-orders/UCM-SHELL-7.md +0 -134
  353. package/work-orders/UCM-SHELL-8.md +0 -125
  354. package/work-orders/UCM-TEST-1.md +0 -304
  355. package/work-orders/UCM-THEME-13.md +0 -69
  356. package/work-orders/UCM-THEME-14.md +0 -312
  357. package/work-orders/UCM-THEME-15.md +0 -356
  358. package/work-orders/UCM-THEME-16.md +0 -80
  359. package/work-orders/UCM-THEME-17.md +0 -111
  360. package/work-orders/UCM-THEME-18.md +0 -184
  361. package/work-orders/assets/AUTH-4-user-list-card.html +0 -407
  362. package/work-orders/assets/PRIM-1-dashboard-primitives.html +0 -624
  363. package/work-orders/assets/SHELL-2-account-navigation.html +0 -1159
  364. package/work-orders/assets/THEME-1-baseline-reference-sheet.html +0 -1352
  365. package/work-orders/assets/UCM-CHAT-1-ai-chat.html +0 -447
  366. package/work-orders/assets/UCM-THEME-13-dark-prototype.html +0 -390
  367. package/work-orders/assets/UCM-THEME-17-type-scale.html +0 -391
@@ -1,624 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>DS-5 — Dashboard primitives: StatTile and SoftChip</title>
7
- <style>
8
- /* ------------------------------------------------------------------
9
- Token values are read from ui-core-micha/src/theme/tokens.js, not
10
- chosen here. Where a value is derived (SoftChip's tints and text),
11
- the derivation is stated next to it.
12
- ------------------------------------------------------------------ */
13
- :root {
14
- /* text */
15
- --text-primary: #212529;
16
- --text-secondary: #5B6670;
17
- --text-disabled: #6A7178;
18
- /* surfaces */
19
- --bg-default: #FAFAFA;
20
- --bg-paper: #FFFFFF;
21
- --bg-subtle: #F4F5F6;
22
- --divider: rgba(33,37,41,.10);
23
- /* shape */
24
- --radius-base: 3px; /* shape.borderRadius */
25
- --radius-card: 8px; /* radius.card */
26
- /* the four semantic tones: main as authored, dark = darken(main, .3) */
27
- --success-main: #1B8038; --success-dark: #135A27;
28
- --warning-main: #976100; --warning-dark: #6A4400;
29
- --error-main: #BF3227; --error-dark: #86231B;
30
- --info-main: #01579B; --info-dark: #013D6D;
31
- }
32
-
33
- * { box-sizing: border-box; }
34
- body {
35
- margin: 0;
36
- padding: 32px 24px 64px;
37
- background: var(--bg-default);
38
- color: var(--text-primary);
39
- font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
40
- font-size: 14px; line-height: 1.55; /* body1 */
41
- }
42
- .wrap { max-width: 1180px; margin: 0 auto; }
43
-
44
- h1 { font-size: 32px; font-weight: 600; line-height: 1.2; margin: 0 0 8px; }
45
- h2 { font-size: 20px; font-weight: 600; line-height: 1.3; margin: 44px 0 4px; }
46
- h3 { font-size: 15px; font-weight: 600; line-height: 1.4; margin: 26px 0 10px; }
47
- .lede { color: var(--text-secondary); max-width: 74ch; margin: 0 0 4px; }
48
- .note {
49
- color: var(--text-secondary); font-size: 13px; line-height: 1.55;
50
- max-width: 78ch; margin: 8px 0 0;
51
- }
52
- code {
53
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
54
- font-size: .92em; background: var(--bg-subtle);
55
- padding: 1px 5px; border-radius: var(--radius-base);
56
- }
57
-
58
- .panel {
59
- background: var(--bg-paper); border: 1px solid var(--divider);
60
- border-radius: var(--radius-card); padding: 20px; margin-top: 14px;
61
- }
62
- .panel--flush { padding: 0; overflow: hidden; }
63
-
64
- /* ============ StatTile — the actual component, faithfully ============
65
- border 1px divider · radius 1 (=3px) · px 1.5 (12px) · py 1 (8px)
66
- minWidth 120 · label caption 12/400 secondary · value h6 16/600
67
- tabular-nums · caption 12/400 secondary */
68
- .stat-row { display: flex; flex-wrap: wrap; gap: 12px; }
69
- .stat-tile {
70
- flex: 1 1 auto; /* decided: flex with a minWidth floor */
71
- border: 1px solid var(--divider);
72
- border-radius: var(--radius-base);
73
- padding: 8px; /* decided: responsive, xs */
74
- min-width: 120px;
75
- background: var(--bg-paper);
76
- }
77
- @media (min-width: 600px) { .stat-tile { padding: 12px; } } /* sm and up */
78
- .stat-tile--accent { border-width: 2px; border-color: #1F6FA8; } /* decided: accent kept */
79
- .stat-tile__label,
80
- .stat-tile__caption {
81
- display: block;
82
- font-size: 12px; font-weight: 400; line-height: 1.4;
83
- color: var(--text-secondary);
84
- }
85
- .stat-tile__value { /* decided: h5 18/600, tabular-nums */
86
- font-size: 18px; font-weight: 600; line-height: 1.3;
87
- font-variant-numeric: tabular-nums;
88
- color: var(--text-primary);
89
- }
90
-
91
- /* ============ SoftChip — as it exists today ============
92
- dot 6px in tone.dark · bg alpha(main,.12) · border alpha(main,.30)
93
- text tone.dark · caveat radius 3px 12/500 · status pill 11/600 */
94
- .chip-row { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
95
- .chip {
96
- display: inline-flex; align-items: center; gap: 6px;
97
- padding: 4px 8px;
98
- border: 1px solid; border-radius: var(--radius-base);
99
- font-size: 12px; font-weight: 500; line-height: 1.4;
100
- white-space: nowrap;
101
- }
102
- /* decided: status uses the baseline's `overline` — 11/600, uppercase, .4px tracking */
103
- .chip--status {
104
- border-radius: 999px; font-size: 11px; font-weight: 600;
105
- text-transform: uppercase; letter-spacing: .4px;
106
- }
107
- .chip--borderless { border-color: transparent; }
108
- .chip__dot { width: 6px; height: 6px; border-radius: 50%; flex: none; }
109
-
110
- .t-success { background: rgba(27,128,56,.12); border-color: rgba(27,128,56,.30); color: var(--success-dark); }
111
- .t-warning { background: rgba(151,97,0,.12); border-color: rgba(151,97,0,.30); color: var(--warning-dark); }
112
- .t-error { background: rgba(191,50,39,.12); border-color: rgba(191,50,39,.30); color: var(--error-dark); }
113
- .t-info { background: rgba(1,87,155,.12); border-color: rgba(1,87,155,.30); color: var(--info-dark); }
114
- .t-success .chip__dot { background: var(--success-dark); }
115
- .t-warning .chip__dot { background: var(--warning-dark); }
116
- .t-error .chip__dot { background: var(--error-dark); }
117
- .t-info .chip__dot { background: var(--info-dark); }
118
-
119
- /* measurement table */
120
- table { border-collapse: collapse; width: 100%; font-size: 13px; }
121
- th, td { padding: 10px 16px; text-align: left; border-bottom: 1px solid var(--divider); }
122
- th {
123
- font-size: 11px; font-weight: 600; line-height: 1.4;
124
- text-transform: uppercase; letter-spacing: .4px;
125
- color: var(--text-secondary); background: var(--bg-subtle);
126
- }
127
- tbody tr:last-child td { border-bottom: 0; }
128
- td.num { font-variant-numeric: tabular-nums; }
129
- .verdict { font-weight: 600; }
130
- .pass { color: var(--success-dark); }
131
- .fail { color: var(--error-dark); }
132
-
133
- /* the two-width frames */
134
- .frames { display: flex; gap: 24px; align-items: flex-start; flex-wrap: wrap; margin-top: 14px; }
135
- .frame { border: 1px solid var(--divider); border-radius: var(--radius-card); background: var(--bg-paper); overflow: hidden; }
136
- .frame__bar {
137
- font-size: 11px; font-weight: 600; letter-spacing: .4px; text-transform: uppercase;
138
- color: var(--text-secondary); background: var(--bg-subtle);
139
- padding: 8px 12px; border-bottom: 1px solid var(--divider);
140
- }
141
- .frame__body { padding: 16px; }
142
- .frame--mobile { width: 375px; }
143
- .frame--mobile .frame__body { width: 375px; }
144
- .frame--desktop { flex: 1 1 620px; min-width: 320px; }
145
-
146
- /* the THEME-4 comparison */
147
- .cmp { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 16px; margin-top: 12px; }
148
- .cmp__cell { border: 1px solid var(--divider); border-radius: var(--radius-base); padding: 14px; background: var(--bg-paper); }
149
- .cmp__head { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .4px; color: var(--text-secondary); margin-bottom: 10px; }
150
- .spec { font-size: 12px; color: var(--text-secondary); margin-top: 8px; font-variant-numeric: tabular-nums; }
151
-
152
- .flag {
153
- border-left: 3px solid var(--warning-main);
154
- background: rgba(151,97,0,.06);
155
- padding: 12px 16px; margin-top: 14px;
156
- border-radius: 0 var(--radius-base) var(--radius-base) 0;
157
- }
158
- .flag strong { color: var(--warning-dark); }
159
- </style>
160
- </head>
161
- <body>
162
- <div class="wrap">
163
-
164
- <h1>Dashboard primitives</h1>
165
- <p class="lede">
166
- <code>StatTile</code> and <code>SoftChip</code> drawn at the shared baseline's own token values.
167
- A decision artifact for <code>DS-5</code> / <code>PRIM-1</code>, not an approved spec. <strong>Each component already
168
- exists twice</strong> — hram as a file, and a second build inline inside a page in
169
- <strong>fitness-monitor</strong> (<code>StatTile</code>) and <strong>cockpit</strong>
170
- (<code>SoftChip</code>). The promotion trigger is met on evidence, and the
171
- <a href="#divergences" style="color:var(--info-dark)">seven divergences between the two builds</a>
172
- were <a href="#decided" style="color:var(--info-dark)">decided on 2026-08-12</a> — everything drawn
173
- here is the promoted spec.
174
- </p>
175
- <p class="note">
176
- Every colour, size and radius below is read from <code>ui-core-micha/src/theme/tokens.js</code>.
177
- The chip tints are derived the way the component derives them — <code>alpha(main, .12)</code> fill,
178
- <code>alpha(main, .30)</code> border, <code>darken(main, .3)</code> text and dot — so nothing here
179
- is a hand-picked hex. Contrast ratios are computed in the page against the <em>blended</em>
180
- background, because a ratio taken against the raw <code>rgba()</code> would be meaningless.
181
- </p>
182
-
183
- <!-- ================= StatTile ================= -->
184
- <h2>StatTile — the KPI tile</h2>
185
- <p class="lede">
186
- A small bordered card: muted label, one bold tabular number, optional caption. Two call sites in
187
- hram today — the Calibration KPI row and the Allocation rail.
188
- </p>
189
-
190
- <div class="frames">
191
- <div class="frame frame--desktop">
192
- <div class="frame__bar">1280 px — Calibration KPI row (three tiles)</div>
193
- <div class="frame__body">
194
- <div class="stat-row">
195
- <div class="stat-tile">
196
- <span class="stat-tile__label">theta*</span>
197
- <div class="stat-tile__value">0.8421</div>
198
- </div>
199
- <div class="stat-tile">
200
- <span class="stat-tile__label">Recovered</span>
201
- <div class="stat-tile__value">0.8397</div>
202
- <span class="stat-tile__caption">bias −0.0024</span>
203
- </div>
204
- <div class="stat-tile">
205
- <span class="stat-tile__label">Interval covers theta*</span>
206
- <div class="stat-tile__value">Yes</div>
207
- <span class="stat-tile__caption">94.2 % of draws</span>
208
- </div>
209
- </div>
210
-
211
- <h3>Allocation rail (two tiles, with a child slot)</h3>
212
- <div class="stat-row">
213
- <div class="stat-tile" style="min-width:180px">
214
- <span class="stat-tile__label">Coverage at budget</span>
215
- <div class="stat-tile__value">71.4 %</div>
216
- <span class="stat-tile__caption">of maximum realizable</span>
217
- </div>
218
- <div class="stat-tile" style="min-width:180px">
219
- <span class="stat-tile__label">Deaths averted</span>
220
- <div class="stat-tile__value">63.8 %</div>
221
- <div style="margin-top:6px">
222
- <span class="chip chip--status t-info"><span class="chip__dot"></span>children slot</span>
223
- </div>
224
- </div>
225
- </div>
226
- </div>
227
- </div>
228
-
229
- <div class="frame frame--mobile">
230
- <div class="frame__bar">375 px</div>
231
- <div class="frame__body">
232
- <div class="stat-row">
233
- <div class="stat-tile">
234
- <span class="stat-tile__label">theta*</span>
235
- <div class="stat-tile__value">0.8421</div>
236
- </div>
237
- <div class="stat-tile">
238
- <span class="stat-tile__label">Recovered</span>
239
- <div class="stat-tile__value">0.8397</div>
240
- <span class="stat-tile__caption">bias −0.0024</span>
241
- </div>
242
- <div class="stat-tile">
243
- <span class="stat-tile__label">Interval covers theta*</span>
244
- <div class="stat-tile__value">Yes</div>
245
- <span class="stat-tile__caption">94.2 % of draws</span>
246
- </div>
247
- </div>
248
- </div>
249
- </div>
250
- </div>
251
-
252
- <p class="note">
253
- At 375 px the tiles fill the width and wrap once they hit the 120 px floor — the decided
254
- <code>flex: 1</code> plus <code>minWidth</code> rule, which is why two fit here where hram's
255
- original <code>minWidth</code>-only version dropped to one per line.
256
- </p>
257
-
258
- <div class="flag">
259
- <strong>StatTile passes <code>THEME-4</code> as written.</strong> It references tokens throughout —
260
- <code>variant="caption"</code>, <code>variant="h5"</code>, <code>text.secondary</code>,
261
- <code>divider</code> — and its only raw numbers are theme-relative multipliers
262
- (<code>borderRadius: 1</code>, the spacing steps), not literals. What it lacks is a test:
263
- <strong>neither build has one</strong>, in either app, and tests are part of promotion.
264
- </div>
265
-
266
- <!-- ================= SoftChip ================= -->
267
- <h2>SoftChip — the annotation chip</h2>
268
- <p class="lede">
269
- Soft tinted chips that read as annotation rather than alarm. Two variants, four tones, a 6 px
270
- leading dot. Replaced loud saturated fills — a bright-orange pill and a full-width green banner.
271
- </p>
272
-
273
- <div class="panel">
274
- <h3 style="margin-top:0">caveat — radius 3 px, 12/500</h3>
275
- <div class="chip-row">
276
- <span class="chip t-warning"><span class="chip__dot"></span>Provisional ranges</span>
277
- <span class="chip t-info"><span class="chip__dot"></span>Derived value</span>
278
- <span class="chip t-error"><span class="chip__dot"></span>Outside calibrated range</span>
279
- <span class="chip t-success"><span class="chip__dot"></span>Within tolerance</span>
280
- </div>
281
-
282
- <h3>status — pill radius, 11/600</h3>
283
- <div class="chip-row">
284
- <span class="chip chip--status t-success"><span class="chip__dot"></span>passed</span>
285
- <span class="chip chip--status t-warning"><span class="chip__dot"></span>degraded</span>
286
- <span class="chip chip--status t-error"><span class="chip__dot"></span>failed</span>
287
- <span class="chip chip--status t-info"><span class="chip__dot"></span>queued</span>
288
- </div>
289
-
290
- <h3>In place — a card header, where the chip actually sits</h3>
291
- <div style="border:1px solid var(--divider);border-radius:var(--radius-card);padding:16px;max-width:520px">
292
- <div style="display:flex;align-items:flex-start;justify-content:space-between;gap:16px">
293
- <div>
294
- <div style="font-size:15px;font-weight:600;line-height:1.4">Sensitivity analysis</div>
295
- <div style="font-size:12px;color:var(--text-secondary)">Tornado over 14 parameters</div>
296
- </div>
297
- <span class="chip t-warning"><span class="chip__dot"></span>Provisional ranges</span>
298
- </div>
299
- </div>
300
- </div>
301
-
302
- <h3>Computed contrast — text on the blended fill, not on the raw rgba</h3>
303
- <div class="panel panel--flush">
304
- <table id="contrast">
305
- <thead>
306
- <tr>
307
- <th>Tone</th><th>main</th><th>text = darken(main,.3)</th>
308
- <th>effective fill over paper</th><th>ratio</th><th>WCAG AA (4.5)</th>
309
- </tr>
310
- </thead>
311
- <tbody></tbody>
312
- </table>
313
- </div>
314
- <p class="note">
315
- The fill is <code>alpha(main, .12)</code> over <code>background.paper</code>, so the real
316
- background is a blend. <code>getContrastRatio</code> ignores alpha and would return the ratio
317
- against white — which is close here but is the wrong number for the wrong reason. These are
318
- computed from the blend.
319
- </p>
320
-
321
- <!-- ================= the THEME-4 problem ================= -->
322
- <h2>The typography problem, and how it was resolved</h2>
323
- <p class="lede">
324
- SoftChip set typography as raw numbers, which is exactly what <code>THEME-4</code>'s shadowing check
325
- forbids in a kit component. The four cells below are the options as they stood; the
326
- <em>status</em> variant now uses <code>overline</code>, and <em>caveat</em> is deliberately left
327
- unresolved (see the end of this page).
328
- </p>
329
-
330
- <div class="cmp">
331
- <div class="cmp__cell">
332
- <div class="cmp__head">status, as hram authored it</div>
333
- <span class="chip chip--status t-success" style="text-transform:none;letter-spacing:normal">
334
- <span class="chip__dot"></span>passed
335
- </span>
336
- <div class="spec">raw <code>fontSize: 11</code>, <code>fontWeight: 600</code> — no variant</div>
337
- </div>
338
- <div class="cmp__cell" style="border-color:var(--success-main)">
339
- <div class="cmp__head"><code>overline</code> — chosen</div>
340
- <span class="chip chip--status t-success"><span class="chip__dot"></span>passed</span>
341
- <div class="spec">11/600 — identical metrics, <strong>plus uppercase + .4 px tracking</strong></div>
342
- </div>
343
- <div class="cmp__cell">
344
- <div class="cmp__head">caveat, as authored</div>
345
- <span class="chip t-warning"><span class="chip__dot"></span>Provisional ranges</span>
346
- <div class="spec">raw <code>fontSize: 12</code>, <code>fontWeight: 500</code></div>
347
- </div>
348
- <div class="cmp__cell">
349
- <div class="cmp__head">baseline <code>caption</code></div>
350
- <span class="chip t-warning" style="font-weight:400"><span class="chip__dot"></span>Provisional ranges</span>
351
- <div class="spec">12/<strong>400</strong> — the nearest variant, one weight step lighter</div>
352
- </div>
353
- </div>
354
-
355
- <div class="flag">
356
- <strong>Two clean ways out, and they are not equivalent.</strong>
357
- Use <code>overline</code> for <em>status</em> and accept uppercase — which changes how every status
358
- chip reads, from <span style="white-space:nowrap">“passed”</span> to
359
- <span style="white-space:nowrap">“PASSED”</span>. Or add one variant to the baseline for chip text
360
- and let both variants reference it — which is a baseline change, so an app-level decision that does
361
- not belong inside a component promotion. <em>caveat</em> has the same shape of problem at 12/500
362
- against <code>caption</code>'s 12/400: visually a hair bolder, but there is no variant for it.
363
- </div>
364
-
365
- <!-- ================= the two builds ================= -->
366
- <h2 id="divergences">Both components exist twice already — and the differences are the decision</h2>
367
- <p class="lede">
368
- The second build of each is an <strong>inline component inside a page</strong>, not a file, which is
369
- why a filename search misses it. The promotion rule is met: two independent builds each, three
370
- apps consuming.
371
- </p>
372
-
373
- <h3>StatTile — hram vs fitness-monitor</h3>
374
- <div class="frames">
375
- <div class="frame frame--desktop">
376
- <div class="frame__bar">hram — <code>ResearchComponents/StatTile.jsx</code></div>
377
- <div class="frame__body">
378
- <div class="stat-row">
379
- <div class="stat-tile" style="flex:0 0 auto">
380
- <span class="stat-tile__label">Recovered</span>
381
- <div class="stat-tile__value" style="font-size:16px;line-height:1.35">0.8397</div>
382
- <span class="stat-tile__caption">bias −0.0024</span>
383
- </div>
384
- </div>
385
- <div class="spec">
386
- value <code>h6</code> 16/600 · <strong>tabular-nums</strong> · padding 12/8 fixed ·
387
- <code>minWidth</code> 120 · caption sub-line · <code>children</code> slot
388
- </div>
389
- </div>
390
- </div>
391
- <div class="frame frame--desktop">
392
- <div class="frame__bar">fitness-monitor — <code>BodyHistoryPage.jsx:554</code></div>
393
- <div class="frame__body">
394
- <div class="stat-row">
395
- <div class="stat-tile" style="flex:1">
396
- <span class="stat-tile__label">Body fat</span>
397
- <div class="stat-tile__value" style="font-size:18px;font-variant-numeric:normal">18.4 %</div>
398
- </div>
399
- <div class="stat-tile stat-tile--accent" style="flex:1">
400
- <span class="stat-tile__label">Lean mass</span>
401
- <div class="stat-tile__value" style="font-variant-numeric:normal">62.1 kg</div>
402
- </div>
403
- </div>
404
- <div class="spec">
405
- value <code>h5</code> 18/600 · <strong>no tabular-nums</strong> · padding responsive
406
- <code>{xs:1, sm:1.5}</code> · <code>flex: 1</code> · <strong><code>accent</code></strong>
407
- = 2 px <code>primary.main</code> border (right-hand tile)
408
- </div>
409
- </div>
410
- </div>
411
- </div>
412
- <p class="note">
413
- Five decisions fall out of this, and none is arbitrary: <strong>h6 or h5</strong> for the value;
414
- <strong>tabular-nums</strong> (hram has it, fm does not — for a KPI row it is what makes digits line
415
- up between tiles, so this one has a right answer); whether <strong><code>accent</code></strong>
416
- survives as a variant; <strong>fixed or responsive padding</strong>; and
417
- <strong><code>flex: 1</code> vs <code>minWidth</code></strong>, which is the 375 px wrap behaviour
418
- above expressed as an API choice.
419
- </p>
420
-
421
- <h3>SoftChip — hram vs cockpit</h3>
422
- <div class="cmp">
423
- <div class="cmp__cell">
424
- <div class="cmp__head">hram — <code>SoftChip.jsx</code>, status variant</div>
425
- <span class="chip chip--status t-success"><span class="chip__dot"></span>passed</span>
426
- <div class="spec">
427
- 11/<strong>600</strong> · lowercase · 6 px dot · <strong>1 px border</strong> at
428
- <code>alpha(main,.30)</code> · fill <code>alpha(main,.12)</code> · auto height
429
- </div>
430
- </div>
431
- <div class="cmp__cell">
432
- <div class="cmp__head">cockpit — <code>BoardView.jsx:65</code>, lane pill</div>
433
- <span class="chip chip--status t-info"
434
- style="text-transform:uppercase;letter-spacing:.4px;font-weight:700;border-color:transparent;height:22px;padding:0 9px">
435
- <span class="chip__dot"></span>recurring 3
436
- </span>
437
- <div class="spec">
438
- 11/<strong>700</strong> · <strong>uppercase + .4 px tracking</strong> · leading
439
- <strong>icon</strong>, not a dot · <strong>no border</strong> · fixed height 22 ·
440
- tabular-nums
441
- </div>
442
- </div>
443
- </div>
444
-
445
- <div class="flag">
446
- <strong>cockpit's build resolves the typography question the section above left open.</strong>
447
- Its pill is already 11-with-uppercase-and-.4 px-tracking — which is the baseline's
448
- <code>overline</code> in all but the weight step. So <code>overline</code> is not an imposition on
449
- the shared component; it is what the second consumer independently chose, and hram's lowercase is
450
- the outlier. That turned “changes how every status chip reads” from a cost into a
451
- <em>one-app</em> cost — which is how it was decided.
452
- </div>
453
-
454
- <!-- ================= resolved ================= -->
455
- <h2 id="decided">Decided — the promoted spec</h2>
456
- <p class="lede">
457
- Operator decisions, 2026-08-12. Everything rendered above the divergence section already reflects
458
- them; the two comparison panels keep the original values as the record of what changed.
459
- </p>
460
-
461
- <div class="panel panel--flush">
462
- <table>
463
- <thead>
464
- <tr><th>Divergence</th><th>Decision</th><th>Which app changes</th></tr>
465
- </thead>
466
- <tbody>
467
- <tr>
468
- <td>StatTile value size</td>
469
- <td><strong>h5 — 18/600</strong></td>
470
- <td>hram (16 → 18)</td>
471
- </tr>
472
- <tr>
473
- <td>StatTile digit alignment</td>
474
- <td><strong>tabular-nums, always</strong> — the reason a KPI row reads as a row</td>
475
- <td>fitness-monitor</td>
476
- </tr>
477
- <tr>
478
- <td>StatTile row behaviour</td>
479
- <td><strong><code>flex: 1</code> with a <code>minWidth</code> floor</strong> — covers both
480
- existing behaviours with one rule, no prop</td>
481
- <td>hram (tiles now share the row)</td>
482
- </tr>
483
- <tr>
484
- <td>StatTile <code>accent</code></td>
485
- <td><strong>kept</strong> — 2 px <code>primary.main</code>; without it fitness-monitor cannot
486
- adopt</td>
487
- <td>neither</td>
488
- </tr>
489
- <tr>
490
- <td>StatTile padding</td>
491
- <td><strong>responsive</strong> <code>{xs: 1, sm: 1.5}</code></td>
492
- <td>hram (was fixed)</td>
493
- </tr>
494
- <tr>
495
- <td>SoftChip status typography</td>
496
- <td><strong><code>overline</code> — 11/600, uppercase, .4 px tracking.</strong> No new
497
- baseline variant, so <code>THEME-4</code> is satisfied without a theme change</td>
498
- <td>hram (<span style="white-space:nowrap">passed → PASSED</span>); cockpit drops 700 → 600</td>
499
- </tr>
500
- <tr>
501
- <td>SoftChip leading element + border</td>
502
- <td><strong>dot by default, any icon via prop; border optional</strong></td>
503
- <td>neither — both adopt unchanged</td>
504
- </tr>
505
- </tbody>
506
- </table>
507
- </div>
508
-
509
- <h3>The promoted components, as decided</h3>
510
- <div class="frames">
511
- <div class="frame frame--desktop">
512
- <div class="frame__bar">1280 px</div>
513
- <div class="frame__body">
514
- <div class="stat-row">
515
- <div class="stat-tile">
516
- <span class="stat-tile__label">Coverage at budget</span>
517
- <div class="stat-tile__value">71.4 %</div>
518
- <span class="stat-tile__caption">of maximum realizable</span>
519
- </div>
520
- <div class="stat-tile">
521
- <span class="stat-tile__label">Deaths averted</span>
522
- <div class="stat-tile__value">63.8 %</div>
523
- <span class="stat-tile__caption">of max avertable</span>
524
- </div>
525
- <div class="stat-tile stat-tile--accent">
526
- <span class="stat-tile__label">Lean mass</span>
527
- <div class="stat-tile__value">62.1 kg</div>
528
- <span class="stat-tile__caption">accent variant</span>
529
- </div>
530
- </div>
531
- <div style="margin-top:14px" class="chip-row">
532
- <span class="chip chip--status t-success"><span class="chip__dot"></span>passed</span>
533
- <span class="chip chip--status t-warning"><span class="chip__dot"></span>degraded</span>
534
- <span class="chip chip--status t-error chip--borderless"><span class="chip__dot"></span>failed</span>
535
- <span class="chip t-info"><span class="chip__dot"></span>Derived value</span>
536
- </div>
537
- <div class="spec">
538
- third chip borderless · fourth is the <code>caveat</code> variant, unchanged at 12/500
539
- </div>
540
- </div>
541
- </div>
542
- <div class="frame frame--mobile">
543
- <div class="frame__bar">375 px</div>
544
- <div class="frame__body">
545
- <div class="stat-row">
546
- <div class="stat-tile">
547
- <span class="stat-tile__label">Coverage at budget</span>
548
- <div class="stat-tile__value">71.4 %</div>
549
- </div>
550
- <div class="stat-tile stat-tile--accent">
551
- <span class="stat-tile__label">Lean mass</span>
552
- <div class="stat-tile__value">62.1 kg</div>
553
- </div>
554
- </div>
555
- <div style="margin-top:12px" class="chip-row">
556
- <span class="chip chip--status t-success"><span class="chip__dot"></span>passed</span>
557
- <span class="chip chip--status t-warning"><span class="chip__dot"></span>degraded</span>
558
- </div>
559
- </div>
560
- </div>
561
- </div>
562
-
563
- <div class="flag">
564
- <strong>Still open, and not part of the promotion:</strong> the <code>caveat</code> variant keeps its
565
- raw 12/500, which has no baseline variant (<code>caption</code> is 12/400). It is left as-is
566
- deliberately — resolving it needs either a baseline addition or a visible weight change, and neither
567
- belongs in the same change as the promotion. It will surface as a <code>THEME-4</code> finding on the
568
- promoted component and should be exempted with that reason, not silently flattened to 400.
569
- </div>
570
- <p class="note">
571
- Not shown, because it cannot be: motion, focus and hover states, and how the chips read on a real
572
- dense results screen rather than a specimen sheet.
573
- </p>
574
-
575
- </div>
576
-
577
- <script>
578
- // Contrast computed from the blended fill. Alpha-aware on purpose:
579
- // getContrastRatio() ignores alpha and would answer a different question.
580
- const TONES = [
581
- ['success', [27, 128, 56], [19, 90, 39]],
582
- ['warning', [151, 97, 0], [106, 68, 0]],
583
- ['error', [191, 50, 39], [134, 35, 27]],
584
- ['info', [1, 87, 155], [1, 61, 109]],
585
- ];
586
- const PAPER = [255, 255, 255];
587
- const FILL_ALPHA = 0.12;
588
-
589
- const hex = ([r, g, b]) =>
590
- '#' + [r, g, b].map((v) => v.toString(16).padStart(2, '0').toUpperCase()).join('');
591
-
592
- const blend = (fg, bg, a) => fg.map((c, i) => Math.round(c * a + bg[i] * (1 - a)));
593
-
594
- const lum = ([r, g, b]) => {
595
- const f = (v) => {
596
- const s = v / 255;
597
- return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
598
- };
599
- return 0.2126 * f(r) + 0.7152 * f(g) + 0.0722 * f(b);
600
- };
601
-
602
- const ratio = (a, b) => {
603
- const [hi, lo] = [lum(a), lum(b)].sort((x, y) => y - x);
604
- return (hi + 0.05) / (lo + 0.05);
605
- };
606
-
607
- const tbody = document.querySelector('#contrast tbody');
608
- TONES.forEach(([name, main, dark]) => {
609
- const fill = blend(main, PAPER, FILL_ALPHA);
610
- const r = ratio(dark, fill);
611
- const ok = r >= 4.5;
612
- tbody.insertAdjacentHTML('beforeend', `
613
- <tr>
614
- <td>${name}</td>
615
- <td class="num">${hex(main)}</td>
616
- <td class="num">${hex(dark)}</td>
617
- <td class="num">${hex(fill)}</td>
618
- <td class="num">${r.toFixed(2)}</td>
619
- <td class="verdict ${ok ? 'pass' : 'fail'}">${ok ? 'pass' : 'FAIL'}</td>
620
- </tr>`);
621
- });
622
- </script>
623
- </body>
624
- </html>