@crisp-ui-kit/crisp 0.57.1 → 0.57.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (277) hide show
  1. package/dist/authshell.css +9 -4
  2. package/dist/authshell.layered.css +9 -4
  3. package/dist/badge.css +3 -3
  4. package/dist/badge.layered.css +3 -3
  5. package/dist/board.css +28 -8
  6. package/dist/board.layered.css +28 -8
  7. package/dist/bottomsheet.css +26 -0
  8. package/dist/bottomsheet.d.ts +7 -0
  9. package/dist/bottomsheet.layered.css +28 -0
  10. package/dist/button.css +10 -6
  11. package/dist/button.d.ts +2 -2
  12. package/dist/button.layered.css +10 -6
  13. package/dist/checkbox.css +7 -3
  14. package/dist/checkbox.layered.css +7 -3
  15. package/dist/chip.css +16 -1
  16. package/dist/chip.d.ts +3 -1
  17. package/dist/chip.layered.css +16 -1
  18. package/dist/chipgroup.css +5 -0
  19. package/dist/chipgroup.d.ts +7 -0
  20. package/dist/chipgroup.layered.css +7 -0
  21. package/dist/cjs/components/badge/badge.recipe.js +22 -8
  22. package/dist/cjs/components/bottomsheet/bottomsheet.js +90 -0
  23. package/dist/cjs/components/bottomsheet/bottomsheet.recipe.js +77 -0
  24. package/dist/cjs/components/bottomsheet/index.js +9 -0
  25. package/dist/cjs/components/breadcrumb/breadcrumb.js +1 -1
  26. package/dist/cjs/components/button/button.recipe.js +67 -15
  27. package/dist/cjs/components/checkbox/checkbox.recipe.js +61 -9
  28. package/dist/cjs/components/chip/chip.js +14 -2
  29. package/dist/cjs/components/chip/chip.recipe.js +72 -2
  30. package/dist/cjs/components/chipgroup/chipgroup.js +51 -0
  31. package/dist/cjs/components/chipgroup/chipgroup.recipe.js +36 -0
  32. package/dist/cjs/components/chipgroup/index.js +7 -0
  33. package/dist/cjs/components/datatable/datatable.js +5 -1
  34. package/dist/cjs/components/datatable/datatable.recipe.js +21 -6
  35. package/dist/cjs/components/datepickerfield/datepickerfield.js +2 -2
  36. package/dist/cjs/components/datepickerfield/datepickerfield.recipe.js +16 -3
  37. package/dist/cjs/components/dialog/dialog.recipe.js +21 -10
  38. package/dist/cjs/components/emptystate/emptystate.recipe.js +7 -2
  39. package/dist/cjs/components/fileupload/fileupload.js +21 -4
  40. package/dist/cjs/components/input/index.js +2 -1
  41. package/dist/cjs/components/input/input.js +31 -1
  42. package/dist/cjs/components/input/input.recipe.js +16 -9
  43. package/dist/cjs/components/menu/menu.js +18 -2
  44. package/dist/cjs/components/menu/menu.recipe.js +21 -7
  45. package/dist/cjs/components/menusheet/index.js +9 -0
  46. package/dist/cjs/components/menusheet/menusheet.js +133 -0
  47. package/dist/cjs/components/menusheet/menusheet.recipe.js +103 -0
  48. package/dist/cjs/components/multiselect/multiselect.js +1 -1
  49. package/dist/cjs/components/multiselect/multiselect.recipe.js +33 -9
  50. package/dist/cjs/components/select/select.js +1 -1
  51. package/dist/cjs/components/select/select.recipe.js +38 -12
  52. package/dist/cjs/components/snackbar/index.js +8 -0
  53. package/dist/cjs/components/snackbar/snackbar.js +121 -0
  54. package/dist/cjs/components/snackbar/snackbar.recipe.js +71 -0
  55. package/dist/cjs/components/table/table.recipe.js +7 -1
  56. package/dist/cjs/components/tabletoolbar/tabletoolbar.chrome.js +7 -2
  57. package/dist/cjs/components/tabletoolbar/tabletoolbar.js +37 -12
  58. package/dist/cjs/components/tabs/tabs.js +10 -4
  59. package/dist/cjs/components/tabs/tabs.recipe.js +23 -6
  60. package/dist/cjs/components/textarea/textarea.recipe.js +11 -8
  61. package/dist/cjs/core/floating-portal.js +7 -4
  62. package/dist/cjs/index.js +7 -0
  63. package/dist/cjs/product/authshell/authshell.recipe.js +5 -1
  64. package/dist/cjs/product/command/command.recipe.js +4 -1
  65. package/dist/cjs/product/comments/comments.js +7 -2
  66. package/dist/cjs/product/comments/comments.recipe.js +25 -7
  67. package/dist/cjs/product/inbox/inbox.js +11 -3
  68. package/dist/cjs/product/memberssettings/index.js +6 -0
  69. package/dist/cjs/product/memberssettings/memberssettings.js +99 -0
  70. package/dist/cjs/product/memberssettings/memberssettings.recipe.js +83 -0
  71. package/dist/cjs/product/notificationssettings/index.js +5 -0
  72. package/dist/cjs/product/notificationssettings/notificationssettings.js +19 -0
  73. package/dist/cjs/product/notificationssettings/notificationssettings.recipe.js +44 -0
  74. package/dist/cjs/product/recordpage/index.js +2 -1
  75. package/dist/cjs/product/recordpage/recordpage.js +106 -9
  76. package/dist/cjs/product/recordpage/recordpage.recipe.js +68 -6
  77. package/dist/cjs/product/workspacegeneralsettings/index.js +5 -0
  78. package/dist/cjs/product/workspacegeneralsettings/workspacegeneralsettings.js +21 -0
  79. package/dist/cjs/product/workspacegeneralsettings/workspacegeneralsettings.recipe.js +51 -0
  80. package/dist/cjs/tokens/contract.js +1 -0
  81. package/dist/cjs/tokens/elevation.js +8 -5
  82. package/dist/cjs/tokens/palette.js +28 -8
  83. package/dist/cjs/tokens/scale.js +44 -2
  84. package/dist/cjs/tokens/semantic.js +2 -0
  85. package/dist/columncalc.css +15 -7
  86. package/dist/columncalc.layered.css +15 -7
  87. package/dist/command.css +23 -19
  88. package/dist/command.layered.css +23 -19
  89. package/dist/comments.css +28 -19
  90. package/dist/comments.d.ts +2 -2
  91. package/dist/comments.layered.css +28 -19
  92. package/dist/datatable.css +171 -71
  93. package/dist/datatable.layered.css +171 -71
  94. package/dist/datepicker.css +13 -41
  95. package/dist/datepicker.layered.css +13 -41
  96. package/dist/datepickerfield.css +12 -3
  97. package/dist/datepickerfield.d.ts +2 -2
  98. package/dist/datepickerfield.layered.css +12 -3
  99. package/dist/dialog.css +12 -7
  100. package/dist/dialog.layered.css +12 -7
  101. package/dist/emptystate.css +5 -3
  102. package/dist/emptystate.layered.css +5 -3
  103. package/dist/esm/components/badge/badge.recipe.js +22 -8
  104. package/dist/esm/components/bottomsheet/bottomsheet.js +52 -0
  105. package/dist/esm/components/bottomsheet/bottomsheet.recipe.js +74 -0
  106. package/dist/esm/components/bottomsheet/index.js +2 -0
  107. package/dist/esm/components/breadcrumb/breadcrumb.js +1 -1
  108. package/dist/esm/components/button/button.recipe.js +68 -16
  109. package/dist/esm/components/checkbox/checkbox.recipe.js +61 -9
  110. package/dist/esm/components/chip/chip.js +14 -2
  111. package/dist/esm/components/chip/chip.recipe.js +72 -2
  112. package/dist/esm/components/chipgroup/chipgroup.js +15 -0
  113. package/dist/esm/components/chipgroup/chipgroup.recipe.js +33 -0
  114. package/dist/esm/components/chipgroup/index.js +2 -0
  115. package/dist/esm/components/datatable/datatable.js +5 -1
  116. package/dist/esm/components/datatable/datatable.recipe.js +21 -6
  117. package/dist/esm/components/datepickerfield/datepickerfield.js +2 -2
  118. package/dist/esm/components/datepickerfield/datepickerfield.recipe.js +16 -3
  119. package/dist/esm/components/dialog/dialog.recipe.js +22 -11
  120. package/dist/esm/components/emptystate/emptystate.recipe.js +7 -2
  121. package/dist/esm/components/fileupload/fileupload.js +20 -5
  122. package/dist/esm/components/input/index.js +1 -1
  123. package/dist/esm/components/input/input.js +30 -0
  124. package/dist/esm/components/input/input.recipe.js +17 -10
  125. package/dist/esm/components/menu/menu.js +18 -2
  126. package/dist/esm/components/menu/menu.recipe.js +21 -7
  127. package/dist/esm/components/menusheet/index.js +2 -0
  128. package/dist/esm/components/menusheet/menusheet.js +95 -0
  129. package/dist/esm/components/menusheet/menusheet.recipe.js +100 -0
  130. package/dist/esm/components/multiselect/multiselect.js +1 -1
  131. package/dist/esm/components/multiselect/multiselect.recipe.js +33 -9
  132. package/dist/esm/components/select/select.js +1 -1
  133. package/dist/esm/components/select/select.recipe.js +38 -12
  134. package/dist/esm/components/snackbar/index.js +2 -0
  135. package/dist/esm/components/snackbar/snackbar.js +84 -0
  136. package/dist/esm/components/snackbar/snackbar.recipe.js +68 -0
  137. package/dist/esm/components/table/table.recipe.js +7 -1
  138. package/dist/esm/components/tabletoolbar/tabletoolbar.chrome.js +7 -2
  139. package/dist/esm/components/tabletoolbar/tabletoolbar.js +37 -12
  140. package/dist/esm/components/tabs/tabs.js +10 -4
  141. package/dist/esm/components/tabs/tabs.recipe.js +23 -6
  142. package/dist/esm/components/textarea/textarea.recipe.js +12 -9
  143. package/dist/esm/core/floating-portal.js +7 -4
  144. package/dist/esm/index.js +7 -0
  145. package/dist/esm/product/authshell/authshell.recipe.js +5 -1
  146. package/dist/esm/product/command/command.recipe.js +4 -1
  147. package/dist/esm/product/comments/comments.js +7 -2
  148. package/dist/esm/product/comments/comments.recipe.js +25 -7
  149. package/dist/esm/product/inbox/inbox.js +11 -3
  150. package/dist/esm/product/memberssettings/index.js +1 -0
  151. package/dist/esm/product/memberssettings/memberssettings.js +62 -0
  152. package/dist/esm/product/memberssettings/memberssettings.recipe.js +80 -0
  153. package/dist/esm/product/notificationssettings/index.js +1 -0
  154. package/dist/esm/product/notificationssettings/notificationssettings.js +16 -0
  155. package/dist/esm/product/notificationssettings/notificationssettings.recipe.js +41 -0
  156. package/dist/esm/product/recordpage/index.js +1 -1
  157. package/dist/esm/product/recordpage/recordpage.js +106 -10
  158. package/dist/esm/product/recordpage/recordpage.recipe.js +68 -6
  159. package/dist/esm/product/workspacegeneralsettings/index.js +1 -0
  160. package/dist/esm/product/workspacegeneralsettings/workspacegeneralsettings.js +18 -0
  161. package/dist/esm/product/workspacegeneralsettings/workspacegeneralsettings.recipe.js +48 -0
  162. package/dist/esm/tokens/contract.js +2 -1
  163. package/dist/esm/tokens/elevation.js +8 -5
  164. package/dist/esm/tokens/palette.js +28 -8
  165. package/dist/esm/tokens/scale.js +43 -1
  166. package/dist/esm/tokens/semantic.js +2 -0
  167. package/dist/fileupload.css +3 -1
  168. package/dist/fileupload.layered.css +3 -1
  169. package/dist/input.css +6 -4
  170. package/dist/input.layered.css +6 -4
  171. package/dist/listpage.css +3 -1
  172. package/dist/listpage.layered.css +3 -1
  173. package/dist/memberssettings.css +67 -0
  174. package/dist/memberssettings.d.ts +7 -0
  175. package/dist/memberssettings.layered.css +69 -0
  176. package/dist/menu.css +2 -2
  177. package/dist/menu.layered.css +2 -2
  178. package/dist/menusheet.css +36 -0
  179. package/dist/menusheet.d.ts +7 -0
  180. package/dist/menusheet.layered.css +38 -0
  181. package/dist/multiselect.css +13 -6
  182. package/dist/multiselect.layered.css +13 -6
  183. package/dist/native/tokens.d.ts +2 -1
  184. package/dist/native/tokens.js +20 -3
  185. package/dist/notificationssettings.css +22 -0
  186. package/dist/notificationssettings.d.ts +7 -0
  187. package/dist/notificationssettings.layered.css +24 -0
  188. package/dist/objectsettings.css +4 -4
  189. package/dist/objectsettings.layered.css +4 -4
  190. package/dist/recordpage.css +90 -23
  191. package/dist/recordpage.layered.css +90 -23
  192. package/dist/recordpanel.css +35 -16
  193. package/dist/recordpanel.layered.css +35 -16
  194. package/dist/resizablepanel.css +8 -5
  195. package/dist/resizablepanel.layered.css +8 -5
  196. package/dist/richtext.css +24 -8
  197. package/dist/richtext.layered.css +24 -8
  198. package/dist/savedviews.css +9 -2
  199. package/dist/savedviews.layered.css +9 -2
  200. package/dist/select.css +25 -5
  201. package/dist/select.layered.css +25 -5
  202. package/dist/settingsshell.css +2 -3
  203. package/dist/settingsshell.layered.css +2 -3
  204. package/dist/slider.css +6 -27
  205. package/dist/slider.layered.css +6 -27
  206. package/dist/snackbar.css +24 -0
  207. package/dist/snackbar.d.ts +7 -0
  208. package/dist/snackbar.layered.css +26 -0
  209. package/dist/statcard.css +3 -1
  210. package/dist/statcard.layered.css +3 -1
  211. package/dist/styles.css +911 -354
  212. package/dist/styles.layered.css +911 -354
  213. package/dist/table.css +35 -12
  214. package/dist/table.layered.css +35 -12
  215. package/dist/tabletoolbar.css +35 -24
  216. package/dist/tabletoolbar.layered.css +35 -24
  217. package/dist/tabs.css +18 -4
  218. package/dist/tabs.layered.css +18 -4
  219. package/dist/textarea.css +2 -6
  220. package/dist/textarea.layered.css +2 -6
  221. package/dist/themepicker.css +2 -1
  222. package/dist/themepicker.layered.css +2 -1
  223. package/dist/tokens.css +13 -2
  224. package/dist/tokens.json +20 -3
  225. package/dist/types/components/bottomsheet/bottomsheet.d.ts +35 -0
  226. package/dist/types/components/bottomsheet/bottomsheet.recipe.d.ts +3 -0
  227. package/dist/types/components/bottomsheet/index.d.ts +2 -0
  228. package/dist/types/components/breadcrumb/breadcrumb.d.ts +9 -1
  229. package/dist/types/components/button/button.d.ts +2 -0
  230. package/dist/types/components/button/button.recipe.d.ts +24 -6
  231. package/dist/types/components/checkbox/checkbox.recipe.d.ts +28 -5
  232. package/dist/types/components/chip/chip.d.ts +22 -0
  233. package/dist/types/components/chip/chip.recipe.d.ts +49 -1
  234. package/dist/types/components/chipgroup/chipgroup.d.ts +14 -0
  235. package/dist/types/components/chipgroup/chipgroup.recipe.d.ts +5 -0
  236. package/dist/types/components/chipgroup/index.d.ts +2 -0
  237. package/dist/types/components/datatable/datatable.d.ts +19 -0
  238. package/dist/types/components/datepickerfield/datepickerfield.d.ts +4 -1
  239. package/dist/types/components/datepickerfield/datepickerfield.recipe.d.ts +18 -3
  240. package/dist/types/components/fileupload/fileupload.d.ts +26 -0
  241. package/dist/types/components/input/index.d.ts +1 -1
  242. package/dist/types/components/input/input.d.ts +72 -0
  243. package/dist/types/components/menusheet/index.d.ts +2 -0
  244. package/dist/types/components/menusheet/menusheet.d.ts +42 -0
  245. package/dist/types/components/menusheet/menusheet.recipe.d.ts +3 -0
  246. package/dist/types/components/snackbar/index.d.ts +2 -0
  247. package/dist/types/components/snackbar/snackbar.d.ts +36 -0
  248. package/dist/types/components/snackbar/snackbar.recipe.d.ts +5 -0
  249. package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +28 -1
  250. package/dist/types/components/tabs/tabs.d.ts +11 -1
  251. package/dist/types/core/floating-portal.d.ts +6 -5
  252. package/dist/types/index.d.ts +7 -0
  253. package/dist/types/product/comments/comments.d.ts +9 -3
  254. package/dist/types/product/comments/comments.recipe.d.ts +12 -2
  255. package/dist/types/product/inbox/inbox.d.ts +13 -1
  256. package/dist/types/product/memberssettings/index.d.ts +1 -0
  257. package/dist/types/product/memberssettings/memberssettings.d.ts +85 -0
  258. package/dist/types/product/memberssettings/memberssettings.recipe.d.ts +3 -0
  259. package/dist/types/product/notificationssettings/index.d.ts +1 -0
  260. package/dist/types/product/notificationssettings/notificationssettings.d.ts +37 -0
  261. package/dist/types/product/notificationssettings/notificationssettings.recipe.d.ts +3 -0
  262. package/dist/types/product/recordpage/index.d.ts +1 -1
  263. package/dist/types/product/recordpage/recordpage.d.ts +99 -2
  264. package/dist/types/product/workspacegeneralsettings/index.d.ts +1 -0
  265. package/dist/types/product/workspacegeneralsettings/workspacegeneralsettings.d.ts +48 -0
  266. package/dist/types/product/workspacegeneralsettings/workspacegeneralsettings.recipe.d.ts +3 -0
  267. package/dist/types/tokens/contract.d.ts +4 -1
  268. package/dist/types/tokens/elevation.d.ts +8 -5
  269. package/dist/types/tokens/palette.d.ts +9 -1
  270. package/dist/types/tokens/scale.d.ts +11 -1
  271. package/dist/types/tokens/semantic.d.ts +2 -0
  272. package/dist/wheelpicker.css +4 -2
  273. package/dist/wheelpicker.layered.css +4 -2
  274. package/dist/workspacegeneralsettings.css +30 -0
  275. package/dist/workspacegeneralsettings.d.ts +7 -0
  276. package/dist/workspacegeneralsettings.layered.css +32 -0
  277. package/package.json +36 -1
package/dist/styles.css CHANGED
@@ -45,7 +45,9 @@
45
45
  --crisp-ring-switch: #d9dade;
46
46
  --crisp-bg-tile-active: #ffffff;
47
47
  --crisp-shadow-tile-active: rgba(0,0,0,0.04);
48
- --crisp-bg-scrim: rgba(0,0,0,0.25);
48
+ --crisp-bg-scrim: rgba(0,0,0,0.45);
49
+ --crisp-bg-overlay-strong: #00000074;
50
+ --crisp-stroke-group-divider: #00000010;
49
51
  --crisp-fg-alert-description: rgba(0,0,0,0.63);
50
52
  --crisp-bg-overlay-frost: #ffffff;
51
53
  --crisp-shadow-overlay1: rgba(28,40,64,0.12);
@@ -117,6 +119,8 @@
117
119
  --crisp-bg-tile-active: #101112;
118
120
  --crisp-shadow-tile-active: rgba(0,0,0,0);
119
121
  --crisp-bg-scrim: rgba(0,0,0,0.55);
122
+ --crisp-bg-overlay-strong: #00000074;
123
+ --crisp-stroke-group-divider: #ffffff17;
120
124
  --crisp-fg-alert-description: rgba(255,255,255,0.6);
121
125
  --crisp-bg-overlay-frost: #1c1d1f;
122
126
  --crisp-shadow-overlay1: rgba(0,0,0,0.48);
@@ -182,13 +186,14 @@
182
186
  --crisp-space-2-5: 10px;
183
187
  --crisp-space-3-5: 14px;
184
188
  --crisp-radius-xs: 4px;
185
- --crisp-radius-chip: 7px;
189
+ --crisp-radius-chip: 999px;
186
190
  --crisp-radius-sm: 6px;
187
191
  --crisp-radius-md: 8px;
188
192
  --crisp-radius-lg: 9px;
189
193
  --crisp-radius-xl: 12px;
190
194
  --crisp-radius-2xl: 16px;
191
195
  --crisp-radius-3xl: 20px;
196
+ --crisp-radius-4xl: 24px;
192
197
  --crisp-radius-full: 999px;
193
198
  --crisp-size-icon: 16px;
194
199
  --crisp-size-chip: 22px;
@@ -212,6 +217,12 @@
212
217
  --crisp-weight-semibold: 600;
213
218
  --crisp-weight-bold: 700;
214
219
  --crisp-font-sans: "VK Sans Display", "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
220
+ --crisp-tracking-18: -0.36px;
221
+ --crisp-tracking-20: -0.4px;
222
+ --crisp-tracking-24: -0.48px;
223
+ --crisp-tracking-26: -0.52px;
224
+ --crisp-tracking-28: -0.56px;
225
+ --crisp-tracking-32: -0.64px;
215
226
  }
216
227
 
217
228
  .crisp-accentpicker {
@@ -312,13 +323,13 @@
312
323
  background: var(--crisp-bg-purple-soft); color: var(--crisp-fg-purple-soft); box-shadow: inset 0 0 0 1px var(--crisp-ring-purple-soft);
313
324
  }
314
325
  .crisp-badge--size_small {
315
- height: 16px; padding-inline: var(--crisp-space-1); font-size: var(--crisp-text-sm); line-height: 16px; border-radius: var(--crisp-radius-sm);
326
+ height: 16px; padding-inline: var(--crisp-space-1); font-size: var(--crisp-text-sm); line-height: 16px; border-radius: var(--crisp-radius-xs);
316
327
  }
317
328
  .crisp-badge--size_medium {
318
- height: 20px; padding-inline: var(--crisp-space-1); font-size: var(--crisp-text-sm); line-height: 16px; gap: 3px; border-radius: var(--crisp-radius-sm);
329
+ height: 20px; padding-inline: var(--crisp-space-1); font-size: var(--crisp-text-sm); line-height: 16px; gap: 3px; border-radius: var(--crisp-radius-xs);
319
330
  }
320
331
  .crisp-badge--size_large {
321
- height: var(--crisp-size-chip); padding-block: var(--crisp-space-px); padding-inline-start: var(--crisp-space-1); padding-inline-end: var(--crisp-space-1-5); font-size: var(--crisp-text-lg); letter-spacing: -0.1px; border-radius: var(--crisp-radius-lg);
332
+ height: var(--crisp-size-chip); padding-block: var(--crisp-space-px); padding-inline-start: var(--crisp-space-1); padding-inline-end: var(--crisp-space-1-5); font-size: var(--crisp-text-lg); letter-spacing: -0.1px; border-radius: var(--crisp-radius-sm);
322
333
  }
323
334
  .crisp-badge--variant_soft {
324
335
 
@@ -366,6 +377,33 @@
366
377
  }
367
378
  }
368
379
 
380
+ .crisp-bottomsheet {
381
+ display: contents; font-family: var(--crisp-font-sans);
382
+ & .crisp-bottomsheet-backdrop { position: fixed; inset: 0; z-index: 60; background: var(--crisp-bg-overlay-strong); /* bottom-sheet.yaml:99 backdrop.color = $color.bg.overlay -- a different, darker, theme-constant scrim than the Dialog bgScrim token, see its own comment */ opacity: 1; transition: opacity 300ms cubic-bezier(0,0,.15,1); /* bottom-sheet.yaml:100-102 enterDuration/enterTimingFunction */ @starting-style { opacity: 0; } }
383
+ & .crisp-bottomsheet-positioner { position: fixed; inset: 0; z-index: 61; display: flex; align-items: flex-end; justify-content: center; }
384
+ & .crisp-bottomsheet-content { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; position: relative; box-sizing: border-box; display: flex; flex-direction: column; overflow: hidden; width: 100%; max-width: 480px; /* bottom-sheet.yaml:110 content.maxWidth */ max-height: 85vh; background: var(--crisp-bg-surface); /* bottom-sheet.yaml:109 content.color = $color.bg.layer-floating */ border-radius: var(--crisp-radius-4xl) var(--crisp-radius-4xl) 0 0; /* bottom-sheet.yaml:111 content.topCornerRadius = $radius.r6 (24px), bottom flush with the viewport */ transform: translateY(0); transition: transform 300ms cubic-bezier(.03,.4,.1,1); /* bottom-sheet.yaml:112-113 content.enterDuration/enterTimingFunction (enter-expressive) */ @starting-style { transform: translateY(100%); } }
385
+ & .crisp-bottomsheet-header { position: relative; display: flex; flex-direction: column; gap: var(--crisp-space-2); /* bottom-sheet.yaml:120 header.gap = $dimension.x2 (8px) */ padding-top: var(--crisp-space-6); padding-bottom: var(--crisp-space-4); /* bottom-sheet.yaml:121-122 header.paddingTop/paddingBottom = $dimension.x6/x4 (24px/16px) */ flex-shrink: 0; }
386
+ & .crisp-bottomsheet-title { color: var(--crisp-fg-primary); /* bottom-sheet.yaml:130 title.color = $color.fg.neutral */ font-size: 22px; line-height: 30px; font-weight: var(--crisp-weight-bold); /* bottom-sheet.yaml:131-133 title.fontSize/lineHeight/fontWeight = $font-size.t8/$line-height.t8/bold */ padding-inline: var(--crisp-space-4); /* default (headerAlignment=left, no close button): bottom-sheet.yaml:150-152 */ }
387
+ &[data-header-align="left"][data-has-close] .crisp-bottomsheet-title { padding-inline-start: var(--crisp-space-4); padding-inline-end: 56px; }
388
+ /* bottom-sheet.yaml:141-144 */
389
+ &[data-header-align="center"] .crisp-bottomsheet-title { text-align: center; }
390
+ &[data-header-align="center"][data-has-close] .crisp-bottomsheet-title { padding-inline: 56px; }
391
+ /* bottom-sheet.yaml:154-157 */
392
+ & .crisp-bottomsheet-description { color: var(--crisp-fg-muted); /* bottom-sheet.yaml:138 description.color = $color.fg.neutral-muted */ font-size: var(--crisp-text-xl); line-height: 22px; font-weight: var(--crisp-weight-regular); /* bottom-sheet.yaml:139-141 description.fontSize/lineHeight/fontWeight = $font-size.t5/$line-height.t5/regular */ padding-inline: var(--crisp-space-4); /* bottom-sheet.yaml:142 description.paddingX = global-gutter (16px) */ }
393
+ & .crisp-bottomsheet-close { position: absolute; top: var(--crisp-space-6); inset-inline-end: var(--crisp-space-4); /* bottom-sheet.yaml:145-146 closeButton.fromTop/fromRight = $dimension.x6/x4 (24px/16px) */ display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-md); background: none; color: var(--crisp-fg-muted); font-family: inherit; cursor: pointer; }
394
+ & .crisp-bottomsheet-close:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: var(--crisp-fg-primary); }
395
+ & .crisp-bottomsheet-body { flex: 1; min-height: 0; overflow-y: auto; padding-inline: var(--crisp-space-4); /* bottom-sheet.yaml:126 body.paddingX = global-gutter (16px) -- no vertical padding in the spec */ }
396
+ & .crisp-bottomsheet-footer { display: flex; align-items: center; justify-content: flex-end; gap: var(--crisp-space-3); flex-shrink: 0; padding-inline: var(--crisp-space-4); padding-top: var(--crisp-space-3); padding-bottom: var(--crisp-space-4); /* bottom-sheet.yaml:127-129 footer.paddingX/paddingTop/paddingBottom = global-gutter/x3/x4 (16px/12px/16px) */ }
397
+ }
398
+
399
+ @media (prefers-reduced-motion: reduce) {
400
+ .crisp-bottomsheet, .crisp-bottomsheet * {
401
+ animation-duration: 0.01ms !important;
402
+ animation-iteration-count: 1 !important;
403
+ transition-duration: 0.01ms !important;
404
+ }
405
+ }
406
+
369
407
  .crisp-breadcrumb {
370
408
  display: flex; align-items: center; gap: var(--crisp-space-0-5); font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg);
371
409
  & .crisp-breadcrumb-item { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); box-sizing: border-box; height: 24px; padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-md); color: var(--crisp-fg-muted); text-decoration: none; white-space: nowrap; }
@@ -406,35 +444,39 @@
406
444
  .crisp-button {
407
445
  display: inline-flex; align-items: center; justify-content: center; gap: var(--crisp-space-1-5); font-family: var(--crisp-font-sans); font-weight: var(--crisp-weight-medium); letter-spacing: -0.07px; line-height: 20px; white-space: nowrap; border: none; cursor: pointer; transition: background-color .2s, box-shadow .2s, color .2s;
408
446
  &:disabled { opacity: 0.5; cursor: not-allowed; }
447
+ &:active { transform: scale(0.98); }
409
448
  }
410
449
  .crisp-button--intent_primary {
411
- background: var(--crisp-bg-brand-contrast); color: var(--crisp-fg-on-brand); box-shadow: inset 0 0 0 1px rgba(0,0,0,0.06), 0 2px 4px -2px rgba(38,109,240,0.12), 0 3px 6px -2px rgba(38,109,240,0.08);
450
+ background: var(--crisp-bg-brand-contrast); color: var(--crisp-fg-on-brand); box-shadow: none;
412
451
  &:hover { filter: brightness(0.96); }
413
452
  &:active { filter: brightness(0.92); }
414
453
  }
415
454
  .crisp-button--intent_neutral {
416
- background: var(--crisp-bg-surface); color: var(--crisp-fg-primary); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04);
417
- &:hover { background: var(--crisp-bg-raised); }
455
+ background: var(--crisp-bg-neutral-weak); color: var(--crisp-fg-primary); box-shadow: none;
456
+ &:hover { background: var(--crisp-border-subtle); }
418
457
  }
419
458
  .crisp-button--intent_danger {
420
- background: var(--crisp-bg-danger-solid); color: var(--crisp-fg-on-brand); box-shadow: inset 0 0 0 1px rgba(0,0,0,0.06), 0 2px 4px -2px rgba(186,37,37,0.20), 0 3px 6px -2px rgba(186,37,37,0.12);
459
+ background: var(--crisp-bg-danger-solid); color: var(--crisp-fg-on-brand); box-shadow: none;
421
460
  &:hover { filter: brightness(0.96); }
422
461
  &:active { filter: brightness(0.92); }
423
462
  }
424
463
  .crisp-button--intent_ghost {
425
- background: transparent; color: var(--crisp-fg-muted); box-shadow: none;
464
+ background: transparent; color: var(--crisp-fg-primary); box-shadow: none;
426
465
  &:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
427
466
  }
428
467
  .crisp-button--size_sm {
429
468
  height: 24px; padding-inline: var(--crisp-space-1-5); font-size: var(--crisp-text-md); border-radius: var(--crisp-radius-md);
430
469
  }
470
+ .crisp-button--size_xs {
471
+ height: 20px; padding-inline: var(--crisp-space-1-5); font-size: 10px; font-weight: var(--crisp-weight-regular); line-height: 15px; letter-spacing: normal; border-radius: var(--crisp-radius-sm); gap: var(--crisp-space-1);
472
+ }
431
473
  .crisp-button--size_md {
432
474
  height: 28px; padding-inline: var(--crisp-space-2); font-size: var(--crisp-text-lg); border-radius: var(--crisp-radius-md);
433
475
  &[data-icon-start] { padding-inline-start: var(--crisp-space-1-5); }
434
476
  &[data-icon-end] { padding-inline-end: var(--crisp-space-1-5); }
435
477
  }
436
478
  .crisp-button--size_lg {
437
- height: 40px; padding-inline: 14px; font-size: var(--crisp-text-lg); border-radius: 10px;
479
+ height: 40px; padding-inline: 14px; font-size: var(--crisp-text-lg); border-radius: var(--crisp-radius-xl);
438
480
  }
439
481
  .crisp-button--shape_text {
440
482
 
@@ -512,12 +554,16 @@
512
554
  }
513
555
 
514
556
  .crisp-checkbox {
515
- appearance: none; -webkit-appearance: none; box-sizing: border-box; flex-shrink: 0; margin: 0; width: 16px; height: 16px; border: none; border-radius: var(--crisp-radius-sm); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 9%, transparent); cursor: pointer; transition: background-color .12s, box-shadow .12s;
516
- &:hover { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 30%, transparent); }
517
- &:checked, &:indeterminate { background: var(--crisp-bg-brand-solid) center / 10px no-repeat; box-shadow: none; }
557
+ appearance: none; -webkit-appearance: none; box-sizing: border-box; flex-shrink: 0; margin: 0; width: 16px; height: 16px; border: 1px solid var(--crisp-border-default); border-radius: var(--crisp-radius-xs); background: var(--crisp-bg-surface); cursor: pointer; transition: background-color .12s, border-color .12s;
558
+ &:hover { border-color: color-mix(in srgb, var(--crisp-fg-primary) 30%, transparent); }
559
+ &:checked, &:indeterminate { background: var(--crisp-bg-brand-solid) center / 10px no-repeat; border-color: transparent; }
518
560
  &:checked { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 12 12' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.5 6.5 5 9l4.5-5'/%3E%3C/svg%3E"); }
519
561
  &:indeterminate { background-image: linear-gradient(var(--crisp-fg-on-brand), var(--crisp-fg-on-brand)); background-size: 8px 2px; }
520
562
  &:focus-visible { outline: 2px solid var(--crisp-bg-brand-solid); outline-offset: 1px; }
563
+ &:disabled { background: var(--crisp-bg-neutral-weak); border-color: var(--crisp-border-subtle); cursor: not-allowed; }
564
+ &:checked:disabled, &:indeterminate:disabled { background: var(--crisp-bg-neutral-weak) center / 10px no-repeat; border-color: var(--crisp-border-subtle); }
565
+ &:checked:disabled { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 12 12' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.5 6.5 5 9l4.5-5'/%3E%3C/svg%3E"); }
566
+ &:indeterminate:disabled { background-image: linear-gradient(var(--crisp-fg-on-brand), var(--crisp-fg-on-brand)); background-size: 8px 2px; }
521
567
  }
522
568
 
523
569
  @media (prefers-reduced-motion: reduce) {
@@ -532,7 +578,7 @@
532
578
  display: inline-flex; align-items: center; gap: 3px; height: var(--crisp-size-chip); min-width: 0; max-width: 100%; padding-inline: var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap;
533
579
  }
534
580
  .crisp-chip--truncate_true {
535
- & .crisp-chip-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; }
581
+ & .crisp-chip-label { min-width: 0; overflow: clip; text-overflow: ellipsis; white-space: nowrap; display: block; }
536
582
  }
537
583
  .crisp-chip--truncate_false {
538
584
 
@@ -561,34 +607,63 @@
561
607
  .crisp-chip--tone_orange {
562
608
  background: color-mix(in srgb, var(--crisp-bg-orange) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-orange) 82%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-orange) 30%, transparent);
563
609
  }
610
+ .crisp-chip--appearance_filled {
611
+
612
+ }
613
+ .crisp-chip--appearance_outline {
614
+ background: transparent;
615
+ }
616
+ .crisp-chip--selected_true {
617
+ background: var(--crisp-bg-neutral-inverted); color: var(--crisp-fg-on-neutral-inverted); box-shadow: none;
618
+ }
619
+ .crisp-chip--selected_false {
620
+
621
+ }
622
+ .crisp-chip--compound_0 {
623
+ box-shadow: inset 0 0 0 1px var(--crisp-border-default);
624
+ }
625
+
626
+ .crisp-chipgroup {
627
+ display: flex; flex-wrap: nowrap; align-items: center; gap: var(--crisp-space-1-5); overflow-x: auto; overscroll-behavior-x: contain; scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch; scrollbar-width: none; -ms-overflow-style: none;
628
+ &::-webkit-scrollbar { display: none; }
629
+ & > * { flex-shrink: 0; scroll-snap-align: start; }
630
+ }
564
631
 
565
632
  .crisp-columncalc {
566
- display: flex; align-items: stretch; width: 100%; height: 100%; font-family: var(--crisp-font-sans); /* Menu wraps its trigger in a flex div that sizes to content, which would shrink the button back to its text. Make the wrapper fill too, so the whole cell stays clickable. */
633
+ display: flex; align-items: stretch; width: 100%; height: 100%; font-family: var(--crisp-font-sans); /* Menu wraps its trigger in a flex div that sizes to content, which would
634
+ shrink the button back to its text. Make the wrapper fill too, so the
635
+ whole cell stays clickable. */
567
636
  & .crisp-menu { width: 100%; height: 100%; }
568
- /* No radius: the trigger fills the cell, so rounding its own corners leaves the cell's background showing through at the four corners as
637
+ /* No radius: the trigger fills the cell, so rounding its own corners
638
+ leaves the cell's background showing through at the four corners as
569
639
  soon as the hover tint paints. Everything else that fills a grid cell
570
640
  agrees -- recordstable-sum-btn, the same summary trigger, declares no
571
641
  radius, and datatable-grip and the embedded pickers zero theirs. An 8px
572
642
  radius survived the chip-to-full-cell conversion and shipped rounded
573
- until #282, so the spec check now guards it. */
574
- /* The hit target is the WHOLE summary cell -- measured 246x36 with
643
+ until #282, so the spec check now guards it. */ /* The hit target is the WHOLE summary cell -- measured 246x36 with
575
644
  padding 0 12px, not a content-sized chip. Clicking anywhere in the cell
576
645
  opens the aggregation menu.
577
646
  No `min-height` (crisp#834, found reproducing the footer-row-38px-
578
- not-36px bug in an isolated browser test, not table.recipe.ts's own border/box-sizing, the hypothesis the issue itself flagged as unconfirmed): the table cell's OWN `height: tableRow` (36px,
647
+ not-36px bug in an isolated browser test, not table.recipe.ts's own
648
+ border/box-sizing, the hypothesis the issue itself flagged as
649
+ unconfirmed): the table cell's OWN `height: tableRow` (36px,
579
650
  border-box) leaves 34px of content box once its 2px of top+bottom
580
651
  border are counted, and `min-height: tableRow` here demanded 36px
581
652
  of CONTENT height regardless -- overflowing the cell by 2px, which
582
653
  auto-height table layout resolves by growing the whole row to fit,
583
654
  landing at exactly the reported 38. `height: 100%` alone already
584
- fills whatever the cell's real content box is (the doc comment above this rule's own stated intent), with nothing left demanding more. */
655
+ fills whatever the cell's real content box is (the doc comment above
656
+ this rule's own stated intent), with nothing left demanding more. */
585
657
  & .crisp-columncalc-trigger { display: flex; align-items: center; gap: var(--crisp-space-1); width: 100%; height: 100%; margin: 0; padding: 0 var(--crisp-space-3); border: none; background: transparent; color: var(--crisp-fg-muted); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); white-space: nowrap; cursor: pointer; transition: background-color .12s, color .12s; }
586
658
  & .crisp-columncalc-trigger:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); color: var(--crisp-fg-primary); }
587
659
  & .crisp-columncalc-trigger svg { flex-shrink: 0; opacity: 0.7; }
588
660
  & .crisp-columncalc-trigger[data-align="right"] { justify-content: flex-end; }
589
661
  & .crisp-columncalc-placeholder { overflow: hidden; text-overflow: ellipsis; }
590
662
  & .crisp-columncalc-value { color: var(--crisp-fg-primary); font-variant-numeric: tabular-nums; }
591
- /* crisp#837: re-measured live against Attio's real footer calc label -- 14px (was 13px, `c.text.md`, fixed on the shared trigger rule above) and rgba(0,0,0,.63), the same color-mix(fgPrimary, 63%) figure this file's own -op label already should have used instead of the flatter
663
+ /* crisp#837: re-measured live against Attio's real footer calc label --
664
+ 14px (was 13px, `c.text.md`, fixed on the shared trigger rule above)
665
+ and rgba(0,0,0,.63), the same color-mix(fgPrimary, 63%) figure this
666
+ file's own -op label already should have used instead of the flatter
592
667
  `fgMuted` token (a close but not measured match). */
593
668
  & .crisp-columncalc-op { color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
594
669
  & .crisp-columncalc-section { padding: var(--crisp-space-1-5) var(--crisp-space-2) var(--crisp-space-1); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
@@ -680,9 +755,13 @@
680
755
  }
681
756
 
682
757
  .crisp-datatable {
683
- display: flex; flex-direction: column; gap: var(--crisp-space-2-5); font-family: var(--crisp-font-sans); /* The toolbar carried no horizontal inset of its own, so its content (search box, sort/filter chips) sat flush at the table's outer edge
684
- (x=0) while the header row's own th/td padding (table.recipe.ts, #465) starts every column's text at 12px -- one row down, the SAME
685
- grid's own header cell already begins at that inset. c.space["3"] here just matches the row directly below it, measured live against Attio (the sort/filter row's search chip lines up with the first
758
+ display: flex; flex-direction: column; gap: var(--crisp-space-2-5); font-family: var(--crisp-font-sans); /* The toolbar carried no horizontal inset of its own, so its content
759
+ (search box, sort/filter chips) sat flush at the table's outer edge
760
+ (x=0) while the header row's own th/td padding (table.recipe.ts,
761
+ #465) starts every column's text at 12px -- one row down, the SAME
762
+ grid's own header cell already begins at that inset. c.space["3"]
763
+ here just matches the row directly below it, measured live against
764
+ Attio (the sort/filter row's search chip lines up with the first
686
765
  column header's text, not the grid's raw edge). */
687
766
  & .crisp-datatable-toolbar { display: flex; align-items: center; gap: var(--crisp-space-2); min-height: 49px; border-top: 1px solid var(--crisp-border-subtle); padding: 0 var(--crisp-space-3); }
688
767
  & .crisp-datatable-search { position: relative; flex: 1; max-width: 280px; display: inline-flex; align-items: center; }
@@ -692,10 +771,7 @@
692
771
  & .crisp-datatable-search input:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
693
772
  & .crisp-datatable-count { margin-inline-start: auto; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
694
773
  /* announced, never drawn */
695
- & .crisp-datatable-live {
696
- position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
697
- overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
698
- }
774
+ & .crisp-datatable-live { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
699
775
  /* crisp/#645: visually-hidden-but-focusable, not visually-hidden-and-
700
776
  inert like .crisp-datatable-live above -- same clip-rect shape,
701
777
  this one just needs to actually hold DOM focus so a native
@@ -703,7 +779,8 @@
703
779
  in datatable.tsx). resize: none keeps a stray native textarea handle
704
780
  from ever being drawable, even though it's clipped to 1px anyway. */
705
781
  & .crisp-datatable-clipboard-proxy { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; resize: none; }
706
- /* single active cell (crisp/#645 follow-up -- this had no styling of its own at all before, a gap against Golden Rule 0: RecordsTable's own
782
+ /* single active cell (crisp/#645 follow-up -- this had no styling of its
783
+ own at all before, a gap against Golden Rule 0: RecordsTable's own
707
784
  measured value, .crisp-recordstable-td[data-selected], brought
708
785
  across unchanged now that a visible anchor actually matters for the
709
786
  Shift+Click range below). */
@@ -712,57 +789,91 @@
712
789
  tint formula already used twice below (tbody tr[data-selected] td)
713
790
  for the same reason a selected group of rows gets one: a selected
714
791
  GROUP of cells is the same kind of state, just cell-scoped instead of
715
- row-scoped. Not independently re-measured against Attio's own range highlight (out of scope this slice -- flagged on crisp#645). */
792
+ row-scoped. Not independently re-measured against Attio's own range
793
+ highlight (out of scope this slice -- flagged on crisp#645). */
716
794
  & td[data-in-range] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 10%, var(--crisp-bg-surface)); }
717
- /* The header cell has no padding of its own -- the button owns the full cell so the whole header is clickable (its own inset is 12px). Mirrors the summary-row fix. `& .crisp-table th`, not bare `& th` (found while re-checking #691's
795
+ /* The header cell has no padding of its own -- the button owns the
796
+ full cell so the whole header is clickable (its own inset is 12px).
797
+ Mirrors the summary-row fix.
798
+ `& .crisp-table th`, not bare `& th` (found while re-checking #691's
718
799
  own fix live): DataTable's root nests `Table`'s own wrapper, whose
719
800
  `table.recipe.ts` carries `& th, & td { padding: 0 12px }` at the
720
801
  SAME specificity (.crisp-table th vs .crisp-datatable th, both one
721
802
  class + one element) -- a bare `& th` here only won by source
722
- order, which flipped the moment table.recipe.ts's own CSS file happened to land after datatable's in the build (alphabetical:
803
+ order, which flipped the moment table.recipe.ts's own CSS file
804
+ happened to land after datatable's in the build (alphabetical:
723
805
  datatable < table), silently reinstating 12px on every header cell
724
- in the whole product and making every interactive column's button double it to 24px. Scoping through `.crisp-table` adds a second class to the selector (0,2,1 beats 0,1,1) so this can never lose a specificity tie to source order again. `thead th`, not a bare `th` (crisp#829): #779 made the primary column's BODY cell a real `<th scope="row">` for a11y, and a bare
806
+ in the whole product and making every interactive column's button
807
+ double it to 24px. Scoping through `.crisp-table` adds a second
808
+ class to the selector (0,2,1 beats 0,1,1) so this can never lose a
809
+ specificity tie to source order again.
810
+ `thead th`, not a bare `th` (crisp#829): #779 made the primary
811
+ column's BODY cell a real `<th scope="row">` for a11y, and a bare
725
812
  `th` here matches that one too, zeroing an inset nothing else
726
813
  supplies -- the primary column's text sat flush left, one column's
727
814
  cell no longer matching its own header or any sibling `<td>`. Only
728
- the header row's own `<th>` needs zero padding (its sort BUTTON owns the inset); the body row's `<th scope="row">` has no button,
815
+ the header row's own `<th>` needs zero padding (its sort BUTTON
816
+ owns the inset); the body row's `<th scope="row">` has no button,
729
817
  and needs the SAME base 12px (table.recipe.ts, #465) every other
730
818
  cell already gets. */
731
819
  & .crisp-table thead th { padding: 0; }
732
- /* Same wrapper trap as AUTHORING.md Lesson 40 (ColumnCalc's summary trigger): Menu wraps its trigger in a flex div that sizes to its content, so a long header grew past the column instead of filling it -- with nothing left to squeeze, the label's own ellipsis CSS (below) had no properly
733
- -sized box to truncate against, and the th's own overflow hard-clipped it with no "...". Fill the wrapper, scoped to th so Menu's default
820
+ /* Same wrapper trap as AUTHORING.md Lesson 40 (ColumnCalc's summary trigger):
821
+ Menu wraps its trigger in a flex div that sizes to its content, so a
822
+ long header grew past the column instead of filling it -- with nothing
823
+ left to squeeze, the label's own ellipsis CSS (below) had no properly
824
+ -sized box to truncate against, and the th's own overflow hard-clipped
825
+ it with no "...". Fill the wrapper, scoped to th so Menu's default
734
826
  (correct everywhere else) is untouched. */
735
827
  & th .crisp-menu { width: 100%; height: 100%; }
736
828
  & .crisp-datatable-th-inner { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); width: 100%; min-width: 0; height: 100%; }
737
829
  & .crisp-datatable-th-inner > svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
738
830
  /* crisp/#605 follow-up: a header with neither a Menu nor a sort button
739
- (the primary/entity column's now-inert path, or any column with no columnMenu/sortable at all) renders .crisp-datatable-th-inner as a DIRECT child of th instead of nested inside a button -- and loses the button's own 12px inset, drifting left of the td's 12px base inset. Measured live on Attio's own Companies grid: the header label
740
- and the cell's own text land at the exact same x (335px from the viewport, both 40px from the column's left edge) via two different
831
+ (the primary/entity column's now-inert path, or any column with no
832
+ columnMenu/sortable at all) renders .crisp-datatable-th-inner as a
833
+ DIRECT child of th instead of nested inside a button -- and loses
834
+ the button's own 12px inset, drifting left of the td's 12px base
835
+ inset. Measured live on Attio's own Companies grid: the header label
836
+ and the cell's own text land at the exact same x (335px from the
837
+ viewport, both 40px from the column's left edge) via two different
741
838
  padding+icon+gap combinations -- Attio does not leave the inert
742
839
  primary header flush left either. The `>` combinator targets only
743
- the button-less case: Menu's own wrapper (`.crisp-menu`) and the plain sort `button` both put >=1 more level between `th` and this span, so their own 12px (already on the button) isn't doubled. */
840
+ the button-less case: Menu's own wrapper (`.crisp-menu`) and the
841
+ plain sort `button` both put >=1 more level between `th` and this
842
+ span, so their own 12px (already on the button) isn't doubled. */
744
843
  & th > .crisp-datatable-th-inner { box-sizing: border-box; padding: 0 var(--crisp-space-3); }
745
- /* The summary cell has NO padding of its own -- the calculation trigger
844
+ /* The summary cell has NO padding of its own -- the calculation trigger
746
845
  owns the whole 246x37 area so a click anywhere in the cell opens the
747
846
  menu. Zero the cell's inset and let ColumnCalc supply the 12px. */
748
847
  & tfoot td { padding: 0; }
749
- /* Group heading row -- a view is grouped under a subtle label with the bucket's count beside it. */
750
- & .crisp-datatable-viewsettings { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: transparent; color: var(--crisp-fg-muted); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); white-space: nowrap; cursor: pointer; transition: background-color .12s, color .12s; }
848
+ /* Group heading row -- a view is grouped under a subtle label with the
849
+ bucket's count beside it. */
850
+ & .crisp-datatable-viewsettings { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: transparent; color: var(--crisp-fg-muted); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); white-space: nowrap; cursor: pointer; transition: background-color .12s, color .12s; }
751
851
  & .crisp-datatable-viewsettings:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); color: var(--crisp-fg-primary); }
752
852
  & .crisp-datatable-group-label { padding: var(--crisp-space-1-5) var(--crisp-space-2) var(--crisp-space-1); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
753
- & .crisp-datatable-group { height: var(--crisp-size-menuRow); padding: 0 var(--crisp-space-3); background: var(--crisp-bg-subtle); border-bottom: 1px solid var(--crisp-border-subtle); font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
853
+ & .crisp-datatable-group { height: var(--crisp-size-menuRow); padding: 0 var(--crisp-space-3); background: var(--crisp-bg-subtle); border-bottom: 1px solid var(--crisp-border-subtle); font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
754
854
  & .crisp-datatable-group-count { margin-inline-start: var(--crisp-space-1-5); color: var(--crisp-fg-muted); font-variant-numeric: tabular-nums; }
755
855
  & .crisp-datatable-th-rename { width: 100%; min-width: 0; height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-1); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); outline: none; }
756
856
  & .crisp-datatable-th-rename:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
757
- & .crisp-datatable-th-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
758
- /* #465: 12px per the comment above (and Attio's measured header cell padding: 0 12px) -- was still c.space["2"] (8px), drifted from its own documented value. */
857
+ /* #719: overflow: clip, not hidden, on every truncation rule below --
858
+ hidden still honours a programmatic scrollLeft assignment (found via
859
+ a screenshot tool that pans anything with scrollWidth > clientWidth),
860
+ blanking or fragmenting the visible text even though real user
861
+ scrolling was already blocked. clip renders the ellipsis identically
862
+ and leaves no scroll position to move. */
863
+ & .crisp-datatable-th-label { overflow: clip; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
864
+ /* #465: 12px per the comment above (and Attio's measured header cell
865
+ padding: 0 12px) -- was still c.space["2"] (8px), drifted from its
866
+ own documented value. */
759
867
  & .crisp-datatable-sort { display: flex; align-items: center; gap: var(--crisp-space-1-5); width: 100%; min-width: 0; height: 100%; margin: 0; padding: 0 var(--crisp-space-3); border: none; background: none; font: inherit; letter-spacing: inherit; color: inherit; text-align: start; cursor: pointer; }
760
868
  /* A right-aligned column's header keeps its field-icon + label packed at
761
869
  the START, matching its own values only in that the VALUES right-align
762
870
  -- Attio's own right-aligned header (measured live: "Twitter follower
763
- count") keeps the icon+label left and only the trailing column-menu icon sits pinned to the right edge, independent of the label's width
764
- (#406, reversing #393's own justify-content: flex-end here). The chevron's own margin-inline-start: auto below already pins IT to the
765
- end of a flex-start row with no override needed -- that's what pins the trailing icon on Attio too. */
871
+ count") keeps the icon+label left and only the trailing column-menu
872
+ icon sits pinned to the right edge, independent of the label's width
873
+ (#406, reversing #393's own justify-content: flex-end here). The
874
+ chevron's own margin-inline-start: auto below already pins IT to the
875
+ end of a flex-start row with no override needed -- that's what pins
876
+ the trailing icon on Attio too. */
766
877
  & .crisp-datatable-chevron { margin-inline-start: auto; flex-shrink: 0; color: var(--crisp-fg-muted); opacity: 0; transition: opacity .12s; }
767
878
  & .crisp-datatable-sort:hover .crisp-datatable-chevron { opacity: 0.6; }
768
879
  & .crisp-datatable-sort[data-sorted] .crisp-datatable-chevron { opacity: 1; color: var(--crisp-fg-primary); }
@@ -770,17 +881,23 @@
770
881
  & th[data-sticky], & td[data-sticky] { position: sticky; z-index: 1; background: var(--crisp-bg-surface); }
771
882
  /* `thead th[data-sticky]`, not a bare `th[data-sticky]` (crisp#848, same
772
883
  bare-`th` family as #829/#833/#835): this z-index:3 was written for
773
- the HEADER's own sticky corner cell (sticky column x sticky header), meant to win over everything else. Unscoped, it also matched the primary column's BODY cell -- a real `<th scope="row" data-sticky>`
774
- since #779 -- giving every scrolled body row's own sticky cell a HIGHER z-index than the sticky FOOTER's (2), so it painted over the
884
+ the HEADER's own sticky corner cell (sticky column x sticky header),
885
+ meant to win over everything else. Unscoped, it also matched the
886
+ primary column's BODY cell -- a real `<th scope="row" data-sticky>`
887
+ since #779 -- giving every scrolled body row's own sticky cell a
888
+ HIGHER z-index than the sticky FOOTER's (2), so it painted over the
775
889
  footer instead of the footer showing through. Confirmed live on a
776
- real bounded-height, 25-row table (GRC's Risk register): scrolled partway, `elementFromPoint` at the footer's own on-screen cell
890
+ real bounded-height, 25-row table (GRC's Risk register): scrolled
891
+ partway, `elementFromPoint` at the footer's own on-screen cell
777
892
  resolved to a body row's `<th>`, not the footer's `<td>`, and that
778
893
  `<th>`'s computed z-index read 3. Scoped to `thead`, a body row's
779
894
  own sticky `<th>` now falls back to the base rule above (z-index 1,
780
- correct -- the same as a plain sticky `<td>`), and the footer's own explicit z-index below is unaffected either way. */
895
+ correct -- the same as a plain sticky `<td>`), and the footer's own
896
+ explicit z-index below is unaffected either way. */
781
897
  & thead th[data-sticky] { z-index: 3; }
782
898
  /* #554: a footer sticky-column cell sits in the bottom-sticky row (table.recipe.ts's
783
- `tfoot td { z-index: 2 }`), which `td[data-sticky]`'s own higher-specificity `z-index: 1` would otherwise undercut -- keep it level with the row it's in. */
899
+ `tfoot td { z-index: 2 }`), which `td[data-sticky]`'s own higher-specificity
900
+ `z-index: 1` would otherwise undercut -- keep it level with the row it's in. */
784
901
  & tfoot td[data-sticky] { z-index: 2; }
785
902
  & tbody tr[data-selected] td[data-sticky] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 10%, var(--crisp-bg-surface)); }
786
903
  & tbody tr:hover td[data-sticky] { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-surface)); }
@@ -800,7 +917,8 @@
800
917
  /* crisp/#758: AI-enriched column marker, ported from RecordsTable's own
801
918
  already-measured value -- a full-bleed #9b69ff (bgPurple) wash at 4%,
802
919
  composited OVER whatever row-state background the cell already has
803
- (hover, selected), which is why it's an ::after overlay and not a plain background. */
920
+ (hover, selected), which is why it's an ::after overlay and not a
921
+ plain background. */
804
922
  & td[data-enriched]::after { content: ""; position: absolute; inset: 0; pointer-events: none; background: color-mix(in srgb, var(--crisp-bg-purple) 4%, transparent); }
805
923
  & .crisp-datatable-edit { width: 100%; height: 28px; margin: 0; padding: 0 var(--crisp-space-1-5); border: none; border-radius: var(--crisp-radius-sm); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); outline: none; vertical-align: middle; }
806
924
  & .crisp-datatable-edit:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
@@ -811,40 +929,56 @@
811
929
  & .crisp-datatable-edit:disabled { opacity: 0.5; cursor: not-allowed; }
812
930
  & .crisp-datatable-edit-pending { flex-shrink: 0; margin-inline-start: var(--crisp-space-1); color: var(--crisp-fg-muted); vertical-align: middle; }
813
931
  /* The rejection's message, beside the cell's own content -- a sibling in
814
- the TableCell, never nested inside a column's own render output, so a hover-only wrapper (a Tooltip, say) around a custom render can't hide
932
+ the TableCell, never nested inside a column's own render output, so a
933
+ hover-only wrapper (a Tooltip, say) around a custom render can't hide
815
934
  it. Block-level: the row grows to fit it rather than floating over
816
935
  the row below, so it can never end up clipped by the scroll
817
- container's overflow the way a position:fixed escape would need guarding against. */
936
+ container's overflow the way a position:fixed escape would need
937
+ guarding against. */
818
938
  & .crisp-datatable-edit-error { display: block; padding: var(--crisp-space-0-5) 0 0; font-size: var(--crisp-text-sm); color: var(--crisp-bg-danger); }
819
- /* editor: "select"/"switch" -- always live, no separate edit mode, so the cell IS the picker: same chromeless-fill treatment as RecordsTable's own --picker cells (measured there already; DataTable
939
+ /* editor: "select"/"switch" -- always live, no separate edit mode, so
940
+ the cell IS the picker: same chromeless-fill treatment as
941
+ RecordsTable's own --picker cells (measured there already; DataTable
820
942
  shares RecordsTable's grid, Golden Rule 0). */
821
943
  & .crisp-datatable-td--picker { padding: 0; }
822
944
  & .crisp-datatable-edit-picker { display: flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-tableRow); padding: 0 var(--crisp-space-2); }
823
945
  & .crisp-datatable-edit-picker .crisp-select-trigger { flex: 1; min-width: 0; height: 100%; min-height: 0; border-radius: 0; box-shadow: none; padding: 0; }
824
946
  & .crisp-datatable-td--picker .crisp-datatable-edit-error { padding-inline-start: var(--crisp-space-2); }
825
- /* reference picker: a leading 16px avatar per option, plus a trailing "Create X" row when the consumer supplies one (#449) -- same measured values as RecordsTable's own .crisp-recordstable-refoption*. */
826
- & .crisp-datatable-refoption { display: flex; align-items: center; gap: var(--crisp-space-2); min-width: 0; overflow: hidden; text-overflow: ellipsis; }
947
+ /* reference picker: a leading 16px avatar per option, plus a trailing
948
+ "Create X" row when the consumer supplies one (#449) -- same measured
949
+ values as RecordsTable's own .crisp-recordstable-refoption*. */
950
+ & .crisp-datatable-refoption { display: flex; align-items: center; gap: var(--crisp-space-2); min-width: 0; overflow: clip; text-overflow: ellipsis; }
827
951
  & .crisp-datatable-refoption-avatar { flex-shrink: 0; }
828
952
  & .crisp-datatable-refoption--create { color: var(--crisp-fg-muted); }
829
953
  & .crisp-datatable-empty { padding: var(--crisp-space-8) var(--crisp-space-3); text-align: center; color: var(--crisp-fg-muted); font-size: var(--crisp-text-lg); }
830
954
  /* #826 -- the full-body loading state (a filter/sort re-query, or a
831
- fresh page boot), measured live on Attio's Companies grid on both triggers: icon+text centred in the row-body area, 10px gap, 14px/500 weight/20px line-height, both icon and text in the SAME muted rgba(0,0,0,0.63) -- the same color-mix figure this file already uses for other muted text (e.g. -phone-dial). Distinct from the cold in-place row load (blank, no indicator) and the warm-navigation per-cell shimmer (#599) -- neither of those swaps the whole body. */
955
+ fresh page boot), measured live on Attio's Companies grid on both
956
+ triggers: icon+text centred in the row-body area, 10px gap, 14px/500
957
+ weight/20px line-height, both icon and text in the SAME muted
958
+ rgba(0,0,0,0.63) -- the same color-mix figure this file already uses
959
+ for other muted text (e.g. -phone-dial). Distinct from the cold
960
+ in-place row load (blank, no indicator) and the warm-navigation
961
+ per-cell shimmer (#599) -- neither of those swaps the whole body. */
832
962
  & .crisp-datatable-loading { display: flex; align-items: center; justify-content: center; gap: 10px; padding: var(--crisp-space-8) var(--crisp-space-3); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); line-height: 20px; }
833
963
  & .crisp-datatable-loading .crisp-spinner { border-top-color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
834
- /* #579: click-to-expand overlay for a truncated cell -- a read-only, full-value card, settled live as click (not Attio's usual
835
- hover-expand) and measured position: absolute, unlike this file's floating chrome (Menu/Select popovers) which portals and escapes the scroll container. Deliberately NOT the edit-error pattern above (grow the row, block-level, never clipped): a virtualized grid assumes a fixed row height for its scroll math (owned by the grid core, not this cell), so growing a row here would fight it. Anchored to the SAME column width the cell itself renders at (measured: Attio's own overlay came out only slightly wider than the
836
- triggering cell's clientWidth -- the overshoot is this padding), wrapping onto as many lines as the value needs; it CAN be clipped by `.crisp-table-scroll`'s own `overflow: auto` if it grows past the
964
+ /* #579: click-to-expand overlay for a truncated cell -- a read-only,
965
+ full-value card, settled live as click (not Attio's usual
966
+ hover-expand) and measured position: absolute, unlike this file's
967
+ floating chrome (Menu/Select popovers) which portals and escapes the
968
+ scroll container. Deliberately NOT the edit-error pattern above
969
+ (grow the row, block-level, never clipped): a virtualized grid
970
+ assumes a fixed row height for its scroll math (owned by the grid
971
+ core, not this cell), so growing a row here would fight it. Anchored
972
+ to the SAME column width the cell itself renders at (measured:
973
+ Attio's own overlay came out only slightly wider than the
974
+ triggering cell's clientWidth -- the overshoot is this padding),
975
+ wrapping onto as many lines as the value needs; it CAN be clipped by
976
+ `.crisp-table-scroll`'s own `overflow: auto` if it grows past the
837
977
  visible viewport, same as the edit-error comment above already
838
978
  notes for any position:absolute escape -- unmeasured against Attio
839
979
  for this exact case, flagged rather than guessed. */
840
980
  & td[data-cell-expanded] { overflow: visible; z-index: 7; }
841
- & .crisp-datatable-cell-overlay {
842
- position: absolute; inset-inline-start: 0; top: 0; width: 100%; box-sizing: border-box;
843
- padding: var(--crisp-space-2) var(--crisp-space-3); background: var(--crisp-bg-surface);
844
- border: 1px solid var(--crisp-bg-brand-solid); border-radius: 4px;
845
- font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium);
846
- color: var(--crisp-fg-primary); white-space: normal; word-break: break-word; cursor: default;
847
- }
981
+ & .crisp-datatable-cell-overlay { position: absolute; inset-inline-start: 0; top: 0; width: 100%; box-sizing: border-box; padding: var(--crisp-space-2) var(--crisp-space-3); background: var(--crisp-bg-surface); border: 1px solid var(--crisp-bg-brand-solid); border-radius: 4px; font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); white-space: normal; word-break: break-word; cursor: default; }
848
982
  /* fixed layout so column widths (and resize) are exact — like RecordsTable */
849
983
  & table { table-layout: fixed; }
850
984
  /* read-only/computed column marker (#596) -- sits just inside the
@@ -856,42 +990,70 @@
856
990
  & .crisp-datatable-resize { position: absolute; top: 0; inset-inline-end: -3px; width: 9px; height: 100%; cursor: col-resize; z-index: 6; touch-action: none; }
857
991
  & .crisp-datatable-resize::after { content: ""; position: absolute; top: 0; inset-inline-start: 3px; width: 2px; height: 100%; background: var(--crisp-bg-brand-solid); opacity: 0; transition: opacity .12s; }
858
992
  & .crisp-datatable-resize:hover::after, & .crisp-datatable-resize[data-resizing]::after { opacity: 1; }
859
- /* crisp/#731 -- column drag-to-reorder. Measured live against Attio's own grid (the issue's own comment): the dragged header keeps full
993
+ /* crisp/#731 -- column drag-to-reorder. Measured live against Attio's
994
+ own grid (the issue's own comment): the dragged header keeps full
860
995
  opacity and the plain `pointer` cursor throughout (Attio draws no
861
996
  ghost and no "grabbing" cursor of its own), so this adds nothing to
862
997
  `[data-dragging]` itself -- the ONLY new paint is the drop-indicator
863
- line, a 3px bar in the same measured brand blue the resize handle's own hover state (above) already uses, positioned on whichever edge of the hovered header the pointer is closer to. */
998
+ line, a 3px bar in the same measured brand blue the resize handle's
999
+ own hover state (above) already uses, positioned on whichever edge of
1000
+ the hovered header the pointer is closer to. */
864
1001
  & th[data-dragging] { cursor: pointer; }
865
1002
  & th[data-drop-before]::before, & th[data-drop-after]::after { content: ""; position: absolute; top: 0; width: 3px; height: 100%; background: var(--crisp-bg-brand-solid); z-index: 6; pointer-events: none; }
866
1003
  & th[data-drop-before]::before { inset-inline-start: -1.5px; }
867
1004
  & th[data-drop-after]::after { inset-inline-end: -1.5px; }
868
1005
  /* typed cells — entity (avatar + name), colour-coded tags, numbers, dates */
869
1006
  & .crisp-datatable-entity { display: flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; color: var(--crisp-fg-primary); font-weight: var(--crisp-weight-medium); }
870
- /* crisp/#601 supersession follow-up -- the same flex-row shape .crisp-datatable-entity gives its own avatar+name+actions, but structural only (no entity-specific colour/weight): lets onOpenComments/onOpenPreview compose alongside ANY column's own
1007
+ /* crisp/#601 supersession follow-up -- the same flex-row shape
1008
+ .crisp-datatable-entity gives its own avatar+name+actions, but
1009
+ structural only (no entity-specific colour/weight): lets
1010
+ onOpenComments/onOpenPreview compose alongside ANY column's own
871
1011
  content (a custom render, or a non-entity built-in type), not just
872
1012
  type: "entity"'s own inline rendering. */
873
1013
  & .crisp-datatable-cell-actions-row { display: flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; width: 100%; }
874
- /* crisp/#601, G-050: measured LIVE (getComputedStyle on the name text, plain hover vs rest) that Attio's underline is unconditional, not a
1014
+ /* crisp/#601, G-050: measured LIVE (getComputedStyle on the name text,
1015
+ plain hover vs rest) that Attio's underline is unconditional, not a
875
1016
  hover reveal -- identical at rest and on hover: text-decoration-color
876
- is rgba(0,0,0,0.09), a 9% tint, not the name's own full-contrast text colour. The previous :hover-gated, full-contrast rule here was itself unmeasured and wrong in two ways at once. */ /* crisp#851 -- Attio's own entity/record names are measured at
877
- font-weight 500, distinct from a plain-text primary column's 400 (table.recipe.ts's own `tbody th` default). This was never its own
1017
+ is rgba(0,0,0,0.09), a 9% tint, not the name's own full-contrast text
1018
+ colour. The previous :hover-gated, full-contrast rule here was itself
1019
+ unmeasured and wrong in two ways at once. */ /* crisp#851 -- Attio's own entity/record names are measured at
1020
+ font-weight 500, distinct from a plain-text primary column's 400
1021
+ (table.recipe.ts's own `tbody th` default). This was never its own
878
1022
  explicit style -- it silently rode on a since-fixed header-rule leak
879
1023
  (#833/#835/#848) that happened to apply the same 500 to every
880
1024
  primary-column body cell regardless of type. Made explicit here so
881
1025
  fixing that leak doesn't also (wrongly) drop entity names to 400. */
882
- & .crisp-datatable-entity-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: var(--crisp-weight-medium); text-decoration: underline; text-decoration-color: color-mix(in srgb, var(--crisp-fg-primary) 9%, transparent); }
883
- & button.crisp-datatable-entity-name--link { margin: 0; padding: 0; border: none; background: none; font: inherit; color: inherit; letter-spacing: inherit; text-align: start; cursor: pointer; max-width: 100%; }
884
- /* crisp/#601, G-048: two 20px ghost icons, right-aligned in the primary column, revealed on ROW hover (the issue's own title says "row
1026
+ & .crisp-datatable-entity-name { overflow: clip; text-overflow: ellipsis; white-space: nowrap; font-weight: var(--crisp-weight-medium); text-decoration: underline; text-decoration-color: color-mix(in srgb, var(--crisp-fg-primary) 9%, transparent); }
1027
+ /* #893: shared by the button (onEntityClick, a different record) and
1028
+ the anchor (href, the row's own record) -- was button-only, which
1029
+ silently left a real <a> unstyled the day #893 added one. */
1030
+ & .crisp-datatable-entity-name--link { margin: 0; padding: 0; border: none; background: none; font: inherit; color: inherit; letter-spacing: inherit; text-align: start; cursor: pointer; max-width: 100%; }
1031
+ /* crisp/#601, G-048: two 20px ghost icons, right-aligned in the primary
1032
+ column, revealed on ROW hover (the issue's own title says "row
885
1033
  hover", and a live check confirmed hovering well past the name --
886
- not just the icons' own hit target -- still reveals them) or keyboard focus. Measured live: border-radius 6px, icon colour rgba(0,0,0,0.55) at rest, darkening to the full text colour with a rgba(0,0,0,0.04) background on the icon's OWN hover -- both
1034
+ not just the icons' own hit target -- still reveals them) or
1035
+ keyboard focus. Measured live: border-radius 6px, icon colour
1036
+ rgba(0,0,0,0.55) at rest, darkening to the full text colour with a
1037
+ rgba(0,0,0,0.04) background on the icon's OWN hover -- both
887
1038
  expressed as color-mix against fgPrimary so they stay correct in
888
1039
  dark mode instead of a literal black that would invert. */
889
1040
  & .crisp-datatable-entity-actions { display: flex; align-items: center; gap: var(--crisp-space-1-5); margin-inline-start: auto; flex-shrink: 0; opacity: 0; }
890
1041
  & tr:hover .crisp-datatable-entity-actions, & .crisp-datatable-entity-actions:focus-within { opacity: 1; }
891
1042
  & .crisp-datatable-entity-action { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-sm); background: none; font-family: inherit; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); cursor: pointer; flex-shrink: 0; }
892
1043
  & .crisp-datatable-entity-action:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); color: var(--crisp-fg-primary); }
893
- & .crisp-datatable-avatar { width: var(--crisp-size-icon); height: var(--crisp-size-icon); border-radius: var(--crisp-radius-sm); flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; font-size: 10px; line-height: 1; font-weight: var(--crisp-weight-semibold); text-transform: uppercase; color: var(--crisp-fg-on-brand); background: var(--crisp-bg-brand-solid); }
894
- & .crisp-datatable-avatar[data-tone="success"] { background: var(--crisp-bg-success); } & .crisp-datatable-avatar[data-tone="warning"] { background: var(--crisp-bg-warning); } & .crisp-datatable-avatar[data-tone="danger"] { background: var(--crisp-bg-danger); } & .crisp-datatable-avatar[data-tone="purple"] { background: var(--crisp-bg-purple); } & .crisp-datatable-avatar[data-tone="cyan"] { background: var(--crisp-bg-cyan); } & .crisp-datatable-avatar[data-tone="orange"] { background: var(--crisp-bg-orange); }
1044
+ /* #892: the default shape's radius was a stray literal (radius.sm, 6px)
1045
+ -- Attio's own real company/object avatar measures 30%, which is
1046
+ Avatar's own already-shipped shape="rounded" value, not a new number.
1047
+ shape="circle" reuses Avatar's own shape="circle" value (radius.full)
1048
+ the same way, measured live on a real Attio person avatar. */
1049
+ & .crisp-datatable-avatar { width: var(--crisp-size-icon); height: var(--crisp-size-icon); border-radius: 30%; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; font-size: 10px; line-height: 1; font-weight: var(--crisp-weight-semibold); text-transform: uppercase; color: var(--crisp-fg-on-brand); background: var(--crisp-bg-brand-solid); }
1050
+ & .crisp-datatable-avatar[data-shape="circle"] { border-radius: var(--crisp-radius-full); }
1051
+ & .crisp-datatable-avatar[data-tone="success"] { background: var(--crisp-bg-success); }
1052
+ & .crisp-datatable-avatar[data-tone="warning"] { background: var(--crisp-bg-warning); }
1053
+ & .crisp-datatable-avatar[data-tone="danger"] { background: var(--crisp-bg-danger); }
1054
+ & .crisp-datatable-avatar[data-tone="purple"] { background: var(--crisp-bg-purple); }
1055
+ & .crisp-datatable-avatar[data-tone="cyan"] { background: var(--crisp-bg-cyan); }
1056
+ & .crisp-datatable-avatar[data-tone="orange"] { background: var(--crisp-bg-orange); }
895
1057
  & .crisp-datatable-avatar img { width: 100%; height: 100%; border-radius: inherit; object-fit: cover; }
896
1058
  & .crisp-datatable-tags { display: flex; align-items: center; gap: var(--crisp-space-1); overflow: hidden; }
897
1059
  & .crisp-datatable-tag { display: inline-flex; align-items: center; height: var(--crisp-size-chip); padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap; background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: color-mix(in srgb, var(--crisp-fg-primary) 82%, transparent); }
@@ -904,13 +1066,19 @@
904
1066
  & .crisp-datatable-tag[data-tone="orange"] { background: color-mix(in srgb, var(--crisp-bg-orange) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-orange) 82%, var(--crisp-fg-primary)); }
905
1067
  & .crisp-datatable-num { font-variant-numeric: tabular-nums; }
906
1068
  & .crisp-datatable-muted { color: var(--crisp-fg-muted); }
907
- /* crisp#822 -- a date/datetime cell's danger/overdue tone: same token as TasksTable's own overdue due-date (crisp#462), plain text colour, no
1069
+ /* crisp#822 -- a date/datetime cell's danger/overdue tone: same token as
1070
+ TasksTable's own overdue due-date (crisp#462), plain text colour, no
908
1071
  fill/weight change. */
909
1072
  & .crisp-datatable-danger { color: var(--crisp-fg-danger); }
910
- /* url/email cell types (#654) -- same measured values as RecordsTable's own .crisp-recordstable-link, which this mirrors: no underline at rest, underline on hover, own-colour text rather than a browser-blue link. */
911
- & .crisp-datatable-link { color: var(--crisp-fg-primary); text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
1073
+ /* url/email cell types (#654) -- same measured values as RecordsTable's
1074
+ own .crisp-recordstable-link, which this mirrors: no underline at
1075
+ rest, underline on hover, own-colour text rather than a browser-blue
1076
+ link. */
1077
+ & .crisp-datatable-link { color: var(--crisp-fg-primary); text-decoration: none; overflow: clip; text-overflow: ellipsis; }
912
1078
  & .crisp-datatable-link:hover { text-decoration: underline; }
913
- /* phone cell type/editor (#765) -- measured live: the dial-code prefix is a muted rgba(0,0,0,0.63) at 14px, matching every other muted-text value already measured elsewhere in this file. */
1079
+ /* phone cell type/editor (#765) -- measured live: the dial-code prefix
1080
+ is a muted rgba(0,0,0,0.63) at 14px, matching every other muted-text
1081
+ value already measured elsewhere in this file. */
914
1082
  & .crisp-datatable-phone { display: inline-flex; align-items: center; gap: var(--crisp-space-1); }
915
1083
  & .crisp-datatable-phone-dial { color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
916
1084
  & .crisp-datatable-phone-editor { display: flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-tableRow); padding: 0 var(--crisp-space-2); }
@@ -940,7 +1108,8 @@
940
1108
  value rows (32px = size.menuRow, 5px 8px padding, 8px gap between
941
1109
  the grip/value/actions, 16px icons) above one ALWAYS-blank trailing
942
1110
  DataPhoneEditor-shaped row -- that blank row is the only "add
943
- another" affordance, there's no separate button for it. The copy/delete pair reuses .crisp-datatable-entity-action's own
1111
+ another" affordance, there's no separate button for it. The
1112
+ copy/delete pair reuses .crisp-datatable-entity-action's own
944
1113
  hover-reveal chrome (not independently re-measured as its own hit
945
1114
  target this pass, only the 16px icon glyphs were). */
946
1115
  & .crisp-datatable-phone-multi-editor { display: flex; flex-direction: column; }
@@ -948,8 +1117,7 @@
948
1117
  & .crisp-datatable-phone-multi-row .crisp-datatable-phone-link { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
949
1118
  & .crisp-datatable-phone-multi-grip { color: var(--crisp-fg-muted); flex-shrink: 0; cursor: grab; }
950
1119
  & .crisp-datatable-phone-multi-actions { display: flex; align-items: center; gap: var(--crisp-space-1); margin-inline-start: auto; flex-shrink: 0; opacity: 0; }
951
- & .crisp-datatable-phone-multi-row:hover .crisp-datatable-phone-multi-actions,
952
- & .crisp-datatable-phone-multi-actions:focus-within { opacity: 1; }
1120
+ & .crisp-datatable-phone-multi-row:hover .crisp-datatable-phone-multi-actions, & .crisp-datatable-phone-multi-actions:focus-within { opacity: 1; }
953
1121
  & .crisp-datatable-phone-multi-action { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-sm); background: none; font-family: inherit; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); cursor: pointer; flex-shrink: 0; }
954
1122
  & .crisp-datatable-phone-multi-action:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); color: var(--crisp-fg-primary); }
955
1123
  /* datetime cell editor (#762) -- DatePickerField + TimePicker side by side. */
@@ -958,7 +1126,10 @@
958
1126
  18px pitch (a 2px gap), an empty star at rgba(0,0,0,0.1), a
959
1127
  committed/filled star at brand600 (--crisp-bg-brand-solid, an exact
960
1128
  match to the measured #266df0), and a distinct hover-preview colour
961
- -- rgb(33,91,196) -- that's close to but NOT brand700/bgBrandSolidPressed (#1f5fd6); kept as its own literal rather than forced onto that token, since the two were measured stable and different. The read-only svg and the editable button's own inner svg both carry the
1129
+ -- rgb(33,91,196) -- that's close to but NOT brand700/bgBrandSolidPressed
1130
+ (#1f5fd6); kept as its own literal rather than forced onto that token,
1131
+ since the two were measured stable and different. The read-only svg
1132
+ and the editable button's own inner svg both carry the
962
1133
  crisp-datatable-rating-star class on the svg itself (not the
963
1134
  button), so one set of fill rules covers both -- the button only
964
1135
  adds its own reset + cursor via a separate -btn class. */
@@ -969,9 +1140,13 @@
969
1140
  & .crisp-datatable-rating-star[data-hovering] path { fill: #215bc4; }
970
1141
  & .crisp-datatable-rating-star-btn { display: inline-flex; padding: 0; margin: 0; border: none; background: none; font-family: inherit; cursor: pointer; }
971
1142
  /* row drag-to-reorder -- a Settings > Objects > Attributes screen measures
972
- a leading 20px grip column, a 16px grip icon, and the row's own hairline doubling as the drop indicator (it tints toward the brand colour as the active drop target). The affordance + geometry are measured; the drag MECHANISM is crisp's existing house idiom -- HTML5
1143
+ a leading 20px grip column, a 16px grip icon, and the row's own
1144
+ hairline doubling as the drop indicator (it tints toward the brand
1145
+ colour as the active drop target). The affordance + geometry are
1146
+ measured; the drag MECHANISM is crisp's existing house idiom -- HTML5
973
1147
  draggable + dataTransfer, the same one column reorder already uses
974
- (RecordsTable's reorderColumn) -- not a proprietary pointer-events implementation. */
1148
+ (RecordsTable's reorderColumn) -- not a proprietary
1149
+ pointer-events implementation. */
975
1150
  & .crisp-datatable-col-grip { width: 20px; padding: 0; text-align: center; }
976
1151
  & .crisp-datatable-grip { display: inline-flex; align-items: center; justify-content: center; width: 100%; height: 100%; margin: 0; border: none; border-radius: 0; background: none; font-family: inherit; color: var(--crisp-fg-muted); cursor: grab; }
977
1152
  & .crisp-datatable-grip:active { cursor: grabbing; }
@@ -1005,36 +1180,22 @@
1005
1180
  measurement -- accessibility wins over the seed reference.
1006
1181
  neutralMuted clears it, 6.62:1 light / 14.71:1 dark (dark was
1007
1182
  already fine at 9.43:1). */
1008
- & .crisp-datepicker-weekday {
1009
- display: flex; align-items: center; justify-content: center;
1010
- height: var(--crisp-size-calendarCell); color: var(--crisp-fg-neutral-muted);
1011
- font-size: var(--crisp-text-sm); line-height: 16px; font-weight: var(--crisp-weight-medium);
1012
- }
1013
-
1183
+ & .crisp-datepicker-weekday { display: flex; align-items: center; justify-content: center; height: var(--crisp-size-calendarCell); color: var(--crisp-fg-neutral-muted); font-size: var(--crisp-text-sm); line-height: 16px; font-weight: var(--crisp-weight-medium); }
1014
1184
  & .crisp-datepicker-months { display: grid; grid-template-columns: minmax(0, 1fr); align-items: start; min-width: 0; }
1015
1185
  & .crisp-datepicker-months[data-wheel-open] { visibility: hidden; }
1016
1186
  & .crisp-datepicker-month { min-width: 0; }
1017
- & .crisp-datepicker-month-label {
1018
- display: flex; align-items: center; height: 24px;
1019
- color: var(--crisp-fg-primary); font-size: var(--crisp-text-lg); line-height: 20px;
1020
- font-weight: var(--crisp-weight-medium);
1021
- }
1022
-
1187
+ & .crisp-datepicker-month-label { display: flex; align-items: center; height: 24px; color: var(--crisp-fg-primary); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); }
1023
1188
  & .crisp-datepicker-grid { display: grid; min-width: 0; }
1024
1189
  & .crisp-datepicker-week-row { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); }
1025
-
1026
- & .crisp-datepicker-cell {
1027
- position: relative; min-width: 0; min-height: var(--crisp-size-calendarCell);
1028
- isolation: isolate;
1029
- }
1190
+ & .crisp-datepicker-cell { position: relative; min-width: 0; min-height: var(--crisp-size-calendarCell); isolation: isolate; }
1030
1191
  & .crisp-datepicker-empty-cell { min-height: var(--crisp-size-calendarCell); }
1031
-
1032
1192
  /* The bridge joining days inside a completed range.
1033
1193
  The endpoints do NOT carry data-in-range -- only the interior days do --
1034
1194
  so these are three independent rules, not a base plus two modifiers. A
1035
1195
  modifier that sets only inset-inline-start never generates a pseudo
1036
1196
  element on an endpoint, which leaves a half-cell of white between the
1037
- endpoint's pill and the run of grey beside it. Each rule declares its own content. */
1197
+ endpoint's pill and the run of grey beside it. Each rule declares its
1198
+ own content. */
1038
1199
  & .crisp-datepicker-cell[data-range-complete][data-in-range]::before { content: ""; position: absolute; z-index: -1; inset-inline: 0; top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); height: var(--crisp-size-calendarPill); background: var(--crisp-bg-neutral-weak); }
1039
1200
  & .crisp-datepicker-cell[data-range-complete][data-range-start]:not([data-range-end])::before { content: ""; position: absolute; z-index: -1; inset-inline-start: 50%; inset-inline-end: 0; top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); height: var(--crisp-size-calendarPill); background: var(--crisp-bg-neutral-weak); }
1040
1201
  & .crisp-datepicker-cell[data-range-complete][data-range-end]:not([data-range-start])::before { content: ""; position: absolute; z-index: -1; inset-inline-start: 0; inset-inline-end: 50%; top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); height: var(--crisp-size-calendarPill); background: var(--crisp-bg-neutral-weak); }
@@ -1050,33 +1211,19 @@
1050
1211
  & .crisp-datepicker-date:disabled { color: var(--crisp-fg-disabled); cursor: not-allowed; }
1051
1212
  & .crisp-datepicker-date:focus-visible::after { content: ""; position: absolute; top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); left: 50%; width: var(--crisp-size-calendarPill); height: var(--crisp-size-calendarPill); transform: translateX(-50%); border-radius: var(--crisp-radius-md); outline: 2px solid var(--crisp-stroke-focus-ring); outline-offset: -2px; }
1052
1213
  & .crisp-datepicker-date-content { position: relative; z-index: 1; display: flex; width: 100%; min-height: var(--crisp-size-calendarCell); min-width: 0; box-sizing: border-box; flex-direction: column; align-items: center; gap: 3px; padding-top: calc((var(--crisp-size-calendarCell) - var(--crisp-size-calendarPill)) / 2); color: inherit; font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); font-variant-numeric: tabular-nums; }
1053
- /* The numeral is its own 42px flex box so it centres on the 42px pill. Without this the content column top-aligns the glyph and the pill reads as offset — the cell is 48px, the pill 42px inset 3px, and only a 42px-tall numeral box shares the pill's centre line. */
1054
- & .crisp-datepicker-date-content > [data-date-picker-day] {
1055
- display: flex; min-height: var(--crisp-size-calendarPill); flex-shrink: 0;
1056
- align-items: center; justify-content: center;
1057
- }
1058
-
1214
+ /* The numeral is its own 42px flex box so it centres on the 42px pill.
1215
+ Without this the content column top-aligns the glyph and the pill reads
1216
+ as offset — the cell is 48px, the pill 42px inset 3px, and only a
1217
+ 42px-tall numeral box shares the pill's centre line. */
1218
+ & .crisp-datepicker-date-content > [data-date-picker-day] { display: flex; min-height: var(--crisp-size-calendarPill); flex-shrink: 0; align-items: center; justify-content: center; }
1059
1219
  /* the year/month wheel, laid over the grid it replaces */
1060
- & .crisp-datepicker-wheel-container {
1061
- position: absolute; z-index: 1; inset-inline: 0;
1062
- top: var(--crisp-size-menuRow); bottom: 0;
1063
- display: flex; align-items: center; overflow: hidden;
1064
- background: var(--crisp-bg-surface);
1065
- }
1220
+ & .crisp-datepicker-wheel-container { position: absolute; z-index: 1; inset-inline: 0; top: var(--crisp-size-menuRow); bottom: 0; display: flex; align-items: center; overflow: hidden; background: var(--crisp-bg-surface); }
1066
1221
  & .crisp-datepicker-wheel { width: 100%; }
1067
-
1068
- & .crisp-datepicker-continuous-scroll {
1069
- height: 100%; min-height: 0; overflow-y: auto;
1070
- overscroll-behavior: contain; scrollbar-width: none;
1071
- }
1222
+ & .crisp-datepicker-continuous-scroll { height: 100%; min-height: 0; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: none; }
1072
1223
  & .crisp-datepicker-continuous-scroll::-webkit-scrollbar { display: none; }
1073
1224
  & .crisp-datepicker-continuous-content { display: flex; flex-direction: column; }
1074
-
1075
1225
  /* announced, never drawn */
1076
- & .crisp-datepicker-live-region {
1077
- position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
1078
- overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
1079
- }
1226
+ & .crisp-datepicker-live-region { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
1080
1227
  }
1081
1228
  .crisp-datepicker--visibleRange_month {
1082
1229
 
@@ -1103,14 +1250,17 @@
1103
1250
 
1104
1251
  .crisp-datepickerfield {
1105
1252
  display: inline-flex; font-family: var(--crisp-font-sans);
1106
- & .crisp-datepickerfield-box { display: inline-flex; align-items: center; box-sizing: border-box; height: 32px; margin: 0; border-radius: var(--crisp-radius-md); background: transparent; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 7%, transparent); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-lg); }
1253
+ & .crisp-datepickerfield-box { display: inline-flex; align-items: center; width: 100%; box-sizing: border-box; height: 32px; margin: 0; border-radius: var(--crisp-radius-md); background: transparent; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 7%, transparent); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-lg); }
1107
1254
  & .crisp-datepickerfield-box[data-ghost] { background: transparent; box-shadow: none; }
1108
1255
  & .crisp-datepickerfield-box[data-ghost]:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); }
1109
- & .crisp-datepickerfield-trigger { display: inline-flex; align-items: center; gap: var(--crisp-space-2); height: 100%; padding: 0 var(--crisp-space-2-5); margin: 0; border: none; background: none; color: inherit; font-family: inherit; font-size: inherit; white-space: nowrap; cursor: pointer; }
1256
+ & .crisp-datepickerfield-trigger { flex: 1; min-width: 0; display: inline-flex; align-items: center; gap: var(--crisp-space-2); height: 100%; padding: 0 var(--crisp-space-2-5); margin: 0; border: none; background: none; color: inherit; font-family: inherit; font-size: inherit; white-space: nowrap; cursor: pointer; }
1110
1257
  & .crisp-datepickerfield-box[data-placeholder] .crisp-datepickerfield-trigger { color: var(--crisp-fg-muted); }
1111
1258
  & .crisp-datepickerfield-trigger svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
1112
1259
  /* Reuses TableToolbar's measured filter-chip remove button
1113
- (tabletoolbar.chrome.ts's .crisp-tabletoolbar-fchip-x) -- the same small inline "clear this value" affordance, not a freshly-measured one: no live Attio composition for a DatePickerField-equivalent clear was reachable this round (#394). */
1260
+ (tabletoolbar.chrome.ts's .crisp-tabletoolbar-fchip-x) -- the same
1261
+ small inline "clear this value" affordance, not a freshly-measured
1262
+ one: no live Attio composition for a DatePickerField-equivalent
1263
+ clear was reachable this round (#394). */
1114
1264
  & .crisp-datepickerfield-clear { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; margin-inline-end: var(--crisp-space-1); padding: 0; border: none; border-radius: var(--crisp-radius-sm); background: none; color: var(--crisp-fg-muted); font-family: inherit; cursor: pointer; }
1115
1265
  & .crisp-datepickerfield-clear:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 10%, transparent); color: var(--crisp-fg-primary); }
1116
1266
  & .crisp-datepickerfield-panel { padding: var(--crisp-space-2); width: 300px; }
@@ -1118,16 +1268,20 @@
1118
1268
  & .crisp-datepickerfield-preset { height: var(--crisp-size-chip); padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-chip); background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-sm); cursor: pointer; }
1119
1269
  & .crisp-datepickerfield-preset:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 8%, transparent); }
1120
1270
  }
1271
+ .crisp-datepickerfield--width_full {
1272
+ width: 100%;
1273
+ }
1274
+ .crisp-datepickerfield--width_auto {
1275
+ width: auto;
1276
+ }
1121
1277
 
1122
1278
  .crisp-dialog {
1123
1279
  display: contents; font-family: var(--crisp-font-sans);
1124
1280
  & .crisp-dialog-backdrop { position: fixed; inset: 0; z-index: 60; background: var(--crisp-bg-scrim); }
1125
1281
  & .crisp-dialog-positioner { position: fixed; inset: 0; z-index: 61; display: flex; align-items: flex-start; justify-content: center; padding: 15vh var(--crisp-space-4) var(--crisp-space-4); }
1126
1282
  & .crisp-dialog-positioner[data-variant="alert"] { align-items: center; padding: var(--crisp-space-4); }
1127
- & .crisp-dialog-content { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; position: relative; display: flex; flex-direction: column; box-sizing: border-box; width: 90vw; max-height: 74vh; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-2xl); background: color-mix(in srgb, var(--crisp-bg-overlay-frost) 60%, transparent); backdrop-filter: blur(15px); -webkit-backdrop-filter: blur(15px); box-shadow: 0 8px 28px -6px var(--crisp-shadow-overlay1), 0 18px 88px -4px var(--crisp-shadow-overlay2), 0 0 1px var(--crisp-shadow-overlay3); opacity: 1; transform: scale(1); transition: opacity 0.2s ease, transform 0.2s ease; @starting-style { opacity: 0; transform: scale(0.97); } }
1128
- & .crisp-dialog-content[data-variant="alert"] { padding: 0; background: transparent; backdrop-filter: none; -webkit-backdrop-filter: none; box-shadow: none; }
1129
- & .crisp-dialog-card { display: flex; flex-direction: column; flex: 1; min-height: 0; box-sizing: border-box; border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); box-shadow: 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 8%, transparent); overflow: hidden; }
1130
- & .crisp-dialog-content[data-variant="alert"] .crisp-dialog-card { border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); }
1283
+ & .crisp-dialog-content { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; position: relative; display: flex; flex-direction: column; box-sizing: border-box; width: 90vw; max-height: 74vh; opacity: 1; transform: scale(1); transition: opacity 0.2s ease, transform 0.2s ease; @starting-style { opacity: 0; transform: scale(0.97); } }
1284
+ & .crisp-dialog-card { display: flex; flex-direction: column; flex: 1; min-height: 0; box-sizing: border-box; border-radius: var(--crisp-radius-3xl); background: var(--crisp-bg-surface); overflow: hidden; }
1131
1285
  & .crisp-dialog-header { display: flex; align-items: center; gap: var(--crisp-space-2); flex-shrink: 0; height: 52px; padding: 0 var(--crisp-space-2) 0 var(--crisp-space-4); border-bottom: 1px solid var(--crisp-border-subtle); }
1132
1286
  & .crisp-dialog-header svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
1133
1287
  & .crisp-dialog-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); margin: 0; }
@@ -1136,8 +1290,10 @@
1136
1290
  /* The discard-confirm's own stacked title + sentence (crisp#677) — no
1137
1291
  border, no close button, unlike the panel header above: measured
1138
1292
  live, both themes, 18px/24px 600-weight title (no scale token
1139
- reaches 18px — the scale tops out at text.xl's 16px, so this is raw px per Golden Rule 3's "genuinely intrinsic size" exception, same as
1140
- this file's own submit-key glyphs) then a 14px/20px 500-weight sentence 4px below it. */
1293
+ reaches 18px — the scale tops out at text.xl's 16px, so this is raw
1294
+ px per Golden Rule 3's "genuinely intrinsic size" exception, same as
1295
+ this file's own submit-key glyphs) then a 14px/20px 500-weight
1296
+ sentence 4px below it. */
1141
1297
  & .crisp-dialog-alert-header { display: flex; flex-direction: column; gap: var(--crisp-space-1); padding: var(--crisp-space-4) var(--crisp-space-4) 0; }
1142
1298
  & .crisp-dialog-alert-title { margin: 0; font-size: 18px; line-height: 24px; font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-primary); }
1143
1299
  & .crisp-dialog-alert-description { margin: 0; font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-alert-description); }
@@ -1147,7 +1303,12 @@
1147
1303
  & .crisp-dialog-field { display: flex; flex-direction: column; gap: var(--crisp-space-1-5); }
1148
1304
  & .crisp-dialog-field-label { font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 50%, transparent); }
1149
1305
  & .crisp-dialog-more { display: inline-flex; align-items: center; gap: var(--crisp-space-2); margin-inline-end: auto; border: none; background: none; padding: 0; font-family: inherit; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); cursor: pointer; }
1150
- & .crisp-dialog-submit { display: inline-flex; align-items: center; gap: var(--crisp-space-2); height: 28px; padding: 0 var(--crisp-space-1) 0 var(--crisp-space-2-5); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-brand-solid); color: var(--crisp-fg-on-brand); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); cursor: pointer; }
1306
+ /* your-moon/crisp#953: bgBrandContrast, not bgBrandSolid --
1307
+ the accent (#0298d9) only clears 3.23:1 with white; the darker
1308
+ sibling clears 4.5:1+, same fix already applied to Button/Badge/
1309
+ Avatar (#897). Scoped to this one fill only -- Dialog's own frosted-
1310
+ surface identity (#934) is untouched here. */
1311
+ & .crisp-dialog-submit { display: inline-flex; align-items: center; gap: var(--crisp-space-2); height: 28px; padding: 0 var(--crisp-space-1) 0 var(--crisp-space-2-5); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-brand-contrast); color: var(--crisp-fg-on-brand); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); cursor: pointer; }
1151
1312
  & .crisp-dialog-submit-key { display: inline-flex; align-items: center; gap: var(--crisp-space-1); }
1152
1313
  & .crisp-dialog-submit-key kbd { box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; padding: 0 3px; border-radius: 4px; background: transparent; color: var(--crisp-fg-on-brand); font-family: inherit; font-style: normal; font-size: 10px; }
1153
1314
  }
@@ -1221,7 +1382,9 @@
1221
1382
  & .crisp-emptystate-icon { display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px; margin-bottom: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: color-mix(in srgb, var(--crisp-fg-primary) 5%, transparent); color: var(--crisp-fg-muted); }
1222
1383
  & .crisp-emptystate-title { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-primary); }
1223
1384
  & .crisp-emptystate-desc { max-width: 320px; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
1224
- /* #603: measured 8px gap between "Import CSV"/"Connect data source" -- inline-flex so one action (the common case) or several lay out the same way, wrapping if the container is narrow. */
1385
+ /* #603: measured 8px gap between "Import CSV"/"Connect data source" --
1386
+ inline-flex so one action (the common case) or several lay out the
1387
+ same way, wrapping if the container is narrow. */
1225
1388
  & .crisp-emptystate-action { display: inline-flex; align-items: center; flex-wrap: wrap; justify-content: center; gap: var(--crisp-space-2); margin-top: var(--crisp-space-2); }
1226
1389
  /* #603: Attio's zero-records "Learn more" card -- a label above a
1227
1390
  clickable icon+title tile. Left/start-aligned, unlike the rest of
@@ -1241,13 +1404,13 @@
1241
1404
  .crisp-emptystate--variant_page {
1242
1405
  gap: var(--crisp-space-2);
1243
1406
  & .crisp-emptystate-icon { width: 45px; height: 45px; margin-bottom: var(--crisp-space-2); border-radius: var(--crisp-radius-full); border: 1px solid var(--crisp-border-subtle); background: none; color: var(--crisp-fg-muted); }
1244
- & .crisp-emptystate-title { font-size: 20px; line-height: 24px; }
1407
+ & .crisp-emptystate-title { font-size: 20px; line-height: 24px; letter-spacing: var(--crisp-tracking-20); }
1245
1408
  & .crisp-emptystate-desc { max-width: 266px; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); line-height: 20px; }
1246
1409
  & .crisp-emptystate-action { margin-top: var(--crisp-space-2); }
1247
1410
  }
1248
1411
  .crisp-emptystate--variant_records {
1249
1412
  & .crisp-emptystate-icon { width: 138px; height: 138px; margin-bottom: var(--crisp-space-2); border-radius: 0; color: color-mix(in srgb, var(--crisp-fg-primary) 22%, transparent); background: radial-gradient(circle, color-mix(in srgb, var(--crisp-fg-primary) 12%, transparent) 1.2px, transparent 1.2px) 0 0 / 16px 16px; }
1250
- & .crisp-emptystate-title { font-size: 20px; line-height: 24px; }
1413
+ & .crisp-emptystate-title { font-size: 20px; line-height: 24px; letter-spacing: var(--crisp-tracking-20); }
1251
1414
  & .crisp-emptystate-desc { max-width: 266px; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); line-height: 20px; }
1252
1415
  }
1253
1416
 
@@ -1278,7 +1441,9 @@
1278
1441
  & .crisp-fileupload-remove:hover { color: var(--crisp-fg-primary); }
1279
1442
  & .crisp-fileupload-remove:focus-visible { outline-color: var(--crisp-stroke-focus-ring); }
1280
1443
  & .crisp-fileupload-errors { display: flex; flex-direction: column; gap: var(--crisp-space-0-5); margin: 0; padding: 0; list-style: none; }
1281
- /* out of flow while empty, so the always-mounted live region costs no layout: display:none would take it out of the accessibility tree, which is the one thing it exists to be in */
1444
+ /* out of flow while empty, so the always-mounted live region costs no
1445
+ layout: display:none would take it out of the accessibility tree, which
1446
+ is the one thing it exists to be in */
1282
1447
  & .crisp-fileupload-errors:empty { position: absolute; }
1283
1448
  & .crisp-fileupload-error { color: var(--crisp-bg-danger); font-size: var(--crisp-text-md); line-height: 18px; }
1284
1449
  }
@@ -1391,7 +1556,7 @@
1391
1556
  }
1392
1557
 
1393
1558
  .crisp-input {
1394
- position: relative; box-sizing: border-box; display: inline-flex; align-items: center; width: 100%; background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-field); font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); line-height: 20px;
1559
+ position: relative; box-sizing: border-box; display: inline-flex; align-items: center; width: 100%; background: var(--crisp-bg-surface); border: 1px solid var(--crisp-border-default); font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); line-height: 20px;
1395
1560
  &::after { content: ""; position: absolute; inset: 0; border-radius: inherit; border: 2px solid transparent; transition: border-color .1s ease; pointer-events: none; }
1396
1561
  &:focus-within::after { border-color: var(--crisp-stroke-field-focus); }
1397
1562
  & .crisp-input-value { box-sizing: border-box; flex: 1 1 auto; min-width: 0; width: 100%; align-self: stretch; margin: 0; border: none; background: none; outline: none; font: inherit; color: var(--crisp-fg-primary); }
@@ -1402,8 +1567,10 @@
1402
1567
  primary-email field -- a tinted field, not just muted text, driven off
1403
1568
  the native `readOnly` attribute. `ringField` (rgba(0,0,0,.06)) is an
1404
1569
  exact match for the measured ring; the 2%-fgPrimary tint reuses the
1405
- same color-mix idiom every other soft crisp surface already uses. */
1406
- &[data-readonly] { background: color-mix(in srgb, var(--crisp-fg-primary) 2%, transparent); box-shadow: inset 0 0 0 1px var(--crisp-ring-field); }
1570
+ same color-mix idiom every other soft crisp surface already uses.
1571
+ `border-color: transparent` neutralises #910's new real rest border
1572
+ so this measured ring isn't drawn twice. */
1573
+ &[data-readonly] { background: color-mix(in srgb, var(--crisp-fg-primary) 2%, transparent); border-color: transparent; box-shadow: inset 0 0 0 1px var(--crisp-ring-field); }
1407
1574
  & .crisp-input-suffix { flex-shrink: 0; margin-inline-start: var(--crisp-space-1); }
1408
1575
  }
1409
1576
  .crisp-input--size_sm {
@@ -1415,7 +1582,7 @@
1415
1582
  & .crisp-input-value { padding-inline: var(--crisp-space-2-5); }
1416
1583
  }
1417
1584
  .crisp-input--size_large {
1418
- height: 36px; border-radius: 10px;
1585
+ height: 36px; border-radius: var(--crisp-radius-xl);
1419
1586
  & .crisp-input-value { padding-inline: var(--crisp-space-3); }
1420
1587
  }
1421
1588
  .crisp-input--invalid_true {
@@ -1468,8 +1635,8 @@
1468
1635
  font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
1469
1636
  position: fixed; z-index: 70; pointer-events: auto; box-sizing: border-box; display: flex; flex-direction: column; gap: var(--crisp-space-px);
1470
1637
  min-width: 200px; max-height: 360px; overflow-y: auto; padding: var(--crisp-space-1);
1471
- border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover);
1472
- box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3);
1638
+ border-radius: var(--crisp-radius-3xl); background: var(--crisp-bg-popover);
1639
+ box-shadow: 0 4px 16px var(--crisp-shadow-select);
1473
1640
  transform-origin: top left; opacity: 1; transform: none; transition: opacity .08s ease, transform .08s ease;
1474
1641
  }
1475
1642
  .crisp-menu-content[data-align="end"] { transform-origin: top right; }
@@ -1517,6 +1684,43 @@
1517
1684
  }
1518
1685
  }
1519
1686
 
1687
+ .crisp-menusheet {
1688
+ display: contents; font-family: var(--crisp-font-sans);
1689
+ & .crisp-menusheet-backdrop { position: fixed; inset: 0; z-index: 60; background: var(--crisp-bg-overlay-strong); /* menu-sheet.yaml:95 backdrop.color = $color.bg.overlay */ opacity: 1; transition: opacity 100ms cubic-bezier(0,0,.15,1); /* menu-sheet.yaml:96-98 enterDuration/enterTimingFunction = $duration.d2/$timing-function.enter */ @starting-style { opacity: 0; } }
1690
+ & .crisp-menusheet-positioner { position: fixed; inset: 0; z-index: 61; display: flex; align-items: flex-end; justify-content: center; }
1691
+ & .crisp-menusheet-content { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; position: relative; box-sizing: border-box; display: flex; flex-direction: column; overflow: hidden; width: 100%; max-width: 480px; /* menu-sheet.yaml:104 content.maxWidth */ max-height: 85vh; background: var(--crisp-bg-surface); /* menu-sheet.yaml:103 content.color = $color.bg.layer-floating */ padding: var(--crisp-space-6) var(--crisp-space-4) var(--crisp-space-4); /* menu-sheet.yaml:105-107 content.paddingX/paddingTop/paddingBottom = global-gutter/x6/x4 (16px/24px/16px) */ border-radius: var(--crisp-radius-3xl) var(--crisp-radius-3xl) 0 0; /* menu-sheet.yaml:108 content.topCornerRadius = $radius.r5 (20px) */ transform: translateY(0); transition: transform 300ms cubic-bezier(.03,.4,.1,1); /* menu-sheet.yaml:109-110 content.enterDuration/enterTimingFunction (enter-expressive) */ @starting-style { transform: translateY(100%); } }
1692
+ & .crisp-menusheet-header { display: flex; flex-direction: column; gap: var(--crisp-space-1); /* menu-sheet.yaml:115 header.gap = $dimension.x1 (4px) */ padding-bottom: var(--crisp-space-4); /* menu-sheet.yaml:116 header.paddingBottom = $dimension.x4 (16px) */ flex-shrink: 0; }
1693
+ & .crisp-menusheet-title { color: var(--crisp-fg-primary); /* menu-sheet.yaml:121 title.color = $color.fg.neutral */ font-size: 18px; line-height: 24px; font-weight: var(--crisp-weight-bold); /* menu-sheet.yaml:118-120 title.fontSize/lineHeight/fontWeight = $font-size.t6/$line-height.t6/bold */ }
1694
+ & .crisp-menusheet-description { color: var(--crisp-fg-muted); /* menu-sheet.yaml:126 description.color = $color.fg.neutral-muted */ font-size: var(--crisp-text-lg); line-height: 19px; font-weight: var(--crisp-weight-regular); /* menu-sheet.yaml:123-125 description.fontSize/lineHeight/fontWeight = $font-size.t4/$line-height.t4/regular */ }
1695
+ & .crisp-menusheet-list { display: flex; flex-direction: column; gap: var(--crisp-space-2-5); /* menu-sheet.yaml:129 list.gap = $dimension.x2_5 (10px) */ overflow-y: auto; min-height: 0; }
1696
+ & .crisp-menusheet-group { display: flex; flex-direction: column; border-radius: var(--crisp-radius-2xl); overflow: hidden; /* menu-sheet.yaml:131 group.cornerRadius = $radius.r4 (16px) */ }
1697
+ & .crisp-menusheet-group > * + * { border-top: 1px solid var(--crisp-stroke-group-divider); }
1698
+ /* menu-sheet.yaml:132-134 divider.strokeBottomWidth/strokeColor = 1px/$color.stroke.neutral-muted */
1699
+ & .crisp-menusheet-footer { flex-shrink: 0; padding-top: var(--crisp-space-2-5); /* menu-sheet.yaml:136 footer.paddingTop = $dimension.x2_5 (10px) */ }
1700
+ & .crisp-menusheet-item { position: relative; box-sizing: border-box; display: flex; align-items: center; width: 100%; min-height: 52px; /* menu-sheet-item.yaml:72 root.minHeight */ padding: var(--crisp-space-3-5) var(--crisp-space-4); gap: var(--crisp-space-3-5); /* menu-sheet-item.yaml:73-75 root.paddingX/paddingY/gap = x4/x3_5/x3_5 (16px/14px/14px) */ border: none; margin: 0; cursor: pointer; text-align: start; background: var(--crisp-bg-neutral-weak); /* menu-sheet-item.yaml:71 root.color = $color.bg.neutral-weak */ font-family: inherit; }
1701
+ & .crisp-menusheet-item:hover, & .crisp-menusheet-item:active { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, var(--crisp-bg-neutral-weak)); }
1702
+ /* menu-sheet-item.yaml:90-91 pressed.root.color = $color.bg.neutral-weak-pressed, approximated as a tint over the base fill */
1703
+ & .crisp-menusheet-item:active .crisp-menusheet-item-content { transform: scale(0.98); }
1704
+ /* menu-sheet-item.yaml:92 pressed.root.scaleScope = content */
1705
+ & .crisp-menusheet-item-icon { flex-shrink: 0; display: inline-flex; width: 22px; height: 22px; }
1706
+ /* menu-sheet-item.yaml:77 prefixIcon.size */
1707
+ & .crisp-menusheet-item-content { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: var(--crisp-space-0-5); /* menu-sheet-item.yaml:79 content.gap = $dimension.x0_5 (2px) */ transition: transform 150ms cubic-bezier(0,0,.15,1); }
1708
+ & .crisp-menusheet-item-label { font-size: var(--crisp-text-xl); line-height: 22px; font-weight: var(--crisp-weight-regular); /* menu-sheet-item.yaml:81-83 label.fontSize/lineHeight/fontWeight = $font-size.t5/$line-height.t5/regular */ }
1709
+ & .crisp-menusheet-item-description { color: var(--crisp-fg-neutral-subtle); /* menu-sheet-item.yaml:88 description.color = $color.fg.neutral-subtle */ font-size: var(--crisp-text-md); line-height: 18px; font-weight: var(--crisp-weight-medium); /* menu-sheet-item.yaml:85-87 description.fontSize/lineHeight/fontWeight = $font-size.t3/$line-height.t3/medium */ }
1710
+ & .crisp-menusheet-item[data-tone="neutral"] .crisp-menusheet-item-icon, & .crisp-menusheet-item[data-tone="neutral"] .crisp-menusheet-item-label { color: var(--crisp-fg-primary); }
1711
+ /* menu-sheet-item.yaml:93-98 tone=neutral */
1712
+ & .crisp-menusheet-item[data-tone="critical"] .crisp-menusheet-item-icon, & .crisp-menusheet-item[data-tone="critical"] .crisp-menusheet-item-label { color: var(--crisp-fg-danger); }
1713
+ /* menu-sheet-item.yaml:99-104 tone=critical */
1714
+ }
1715
+
1716
+ @media (prefers-reduced-motion: reduce) {
1717
+ .crisp-menusheet, .crisp-menusheet * {
1718
+ animation-duration: 0.01ms !important;
1719
+ animation-iteration-count: 1 !important;
1720
+ transition-duration: 0.01ms !important;
1721
+ }
1722
+ }
1723
+
1520
1724
 
1521
1725
  /* pointer-events: auto (#375) -- see Select's matching comment. */
1522
1726
  .crisp-multiselect-listbox {
@@ -1530,13 +1734,19 @@
1530
1734
  }
1531
1735
  .crisp-multiselect-listbox[data-up] { transform-origin: bottom left; }
1532
1736
  .crisp-multiselect-listbox { @starting-style { opacity: 0; transform: scale(0.95); } }
1533
- /* LOCKED geometry (#481, guarded by build/spec-check.mjs): height 36px,
1534
- gap space["1.5"] (6px), no border-bottom -- an Attio measurement, not
1535
- part of this seed-parity pass. Only colours below follow Select. */
1536
- .crisp-multiselect-search { display: flex; align-items: center; gap: var(--crisp-space-1-5); height: 36px; flex-shrink: 0; margin: calc(-1 * var(--crisp-space-2)) calc(-1 * var(--crisp-space-2)) var(--crisp-space-2); padding: var(--crisp-space-2) var(--crisp-space-3); color: var(--crisp-fg-neutral-muted); }
1737
+ /* Seed geometry (build/spec-check.mjs guards these four properties
1738
+ directly), superseding #481's old Attio lock -- see this file's
1739
+ header comment and Select's matching .crisp-select-search comment
1740
+ for the full citation. An ordinary flex child of the listbox, so its
1741
+ own 8px gap provides the inset/seam on every side, same as Select. */
1742
+ .crisp-multiselect-search { display: flex; align-items: center; gap: var(--crisp-space-2); height: var(--crisp-size-selectTriggerMd); flex-shrink: 0; padding: 0 var(--crisp-space-3-5); border: 1px solid var(--crisp-stroke-select-trigger); border-radius: var(--crisp-radius-md); color: var(--crisp-fg-neutral-muted); }
1537
1743
  .crisp-multiselect-search svg { flex-shrink: 0; }
1538
- .crisp-multiselect-search input { flex: 1; min-width: 0; border: none; outline: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-select-neutral); }
1744
+ .crisp-multiselect-search input { flex: 1; min-width: 0; border: none; outline: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); line-height: 19px; color: var(--crisp-fg-select-neutral); }
1539
1745
  .crisp-multiselect-search input::placeholder { color: var(--crisp-fg-select-placeholder); }
1746
+ /* Same header/body seam as Select's own .crisp-select-divider -- see
1747
+ that recipe's comment for the seed source (select.yaml's divider
1748
+ slot, stroke.neutral-muted). */
1749
+ .crisp-multiselect-divider { flex-shrink: 0; height: 1px; margin-inline: var(--crisp-space-4); background: var(--crisp-stroke-select-divider); }
1540
1750
  /* Same groupLabel treatment as Select's (13px/regular/neutral-subtle). */
1541
1751
  .crisp-multiselect-label { padding: var(--crisp-space-2) var(--crisp-space-4) 0; font-size: var(--crisp-text-md); line-height: 18px; font-weight: var(--crisp-weight-regular); color: var(--crisp-fg-neutral-subtle); }
1542
1752
  .crisp-multiselect-option {
@@ -1576,7 +1786,8 @@
1576
1786
  & .crisp-multiselect-trigger[aria-readonly="true"] { background: var(--crisp-bg-select-disabled); cursor: default; }
1577
1787
  & .crisp-multiselect-chips { flex: 1; min-width: 0; display: flex; flex-wrap: wrap; align-items: center; gap: var(--crisp-space-1); }
1578
1788
  & .crisp-multiselect-placeholder { color: var(--crisp-fg-select-placeholder); }
1579
- /* closeRotateDuration/openRotateDuration, same asymmetric timing as Select's own chevron (see that recipe's comment). */
1789
+ /* closeRotateDuration/openRotateDuration, same asymmetric timing as
1790
+ Select's own chevron (see that recipe's comment). */
1580
1791
  & .crisp-multiselect-chevron { flex-shrink: 0; color: var(--crisp-fg-neutral-muted); transition: transform .1s cubic-bezier(.35,0,.35,1); }
1581
1792
  & .crisp-multiselect-trigger[data-open] .crisp-multiselect-chevron { transform: rotate(180deg); transition-duration: .15s; }
1582
1793
  }
@@ -1736,13 +1947,15 @@
1736
1947
  & .crisp-resizablepanel-panel[data-floating="false"] { border-radius: 0; box-shadow: none; }
1737
1948
  & .crisp-resizablepanel-panel[data-floating="false"][data-side="left"] { border-inline-end: 1px solid var(--crisp-border-subtle); }
1738
1949
  & .crisp-resizablepanel-panel[data-floating="false"][data-side="right"] { border-inline-start: 1px solid var(--crisp-border-subtle); }
1739
- /* floating — position: fixed, 8px gutter, r10, the measured floating elevation. Rest is transform: none (Lesson 27); the off-canvas value is the @starting-style enter only. */
1950
+ /* floating — position: fixed, 8px gutter, r10, the measured floating elevation.
1951
+ Rest is transform: none (Lesson 27); the off-canvas value is the @starting-style enter only. */
1740
1952
  & .crisp-resizablepanel-panel[data-floating="true"] { position: fixed; top: var(--crisp-space-2); bottom: var(--crisp-space-2); border-inline-end: none; border-inline-start: none; border-radius: 10px; box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); transform: none; transition: transform 0.14s, padding 0.14s; }
1741
1953
  & .crisp-resizablepanel-panel[data-floating="true"][data-side="left"] { padding: var(--crisp-space-2) 0 var(--crisp-space-2) var(--crisp-space-2); @starting-style { transform: translateX(calc(-100% - var(--crisp-space-2))); } }
1742
1954
  & .crisp-resizablepanel-panel[data-floating="true"][data-side="right"] { padding: var(--crisp-space-2) var(--crisp-space-2) var(--crisp-space-2) 0; @starting-style { transform: translateX(calc(100% + var(--crisp-space-2))); } }
1743
1955
  & .crisp-resizablepanel-inner { height: 100%; opacity: 1; }
1744
1956
  & .crisp-resizablepanel-panel[data-floating="true"] .crisp-resizablepanel-inner { opacity: 1; transition: opacity 0.14s; @starting-style { opacity: 0; } }
1745
- /* handle — variant "panel" (default): a 10px hit target, its ::before the only visible divider (1px rest -> 2px on hover/drag, brand-solid). */
1957
+ /* handle — variant "panel" (default): a 10px hit target, its ::before the
1958
+ only visible divider (1px rest -> 2px on hover/drag, brand-solid). */
1746
1959
  & .crisp-resizablepanel-handle { position: absolute; top: 0; width: 10px; height: 100%; z-index: 2; cursor: col-resize; background: transparent; touch-action: none; }
1747
1960
  & .crisp-resizablepanel-handle[data-side="left"] { right: 0; }
1748
1961
  & .crisp-resizablepanel-handle[data-side="right"] { left: 0; }
@@ -1750,9 +1963,10 @@
1750
1963
  & .crisp-resizablepanel-handle[data-side="left"]::before { right: 0; }
1751
1964
  & .crisp-resizablepanel-handle[data-side="right"]::before { left: 0; }
1752
1965
  & .crisp-resizablepanel-handle:hover::before, & .crisp-resizablepanel-handle[data-dragging="true"]::before { width: 2px; background: var(--crisp-bg-brand-solid); }
1753
- /* handle — variant "column": a wider 7px hit target straddling a 1px divider (table column edges), with a real 3px inner bar whose opacity comes in on a 100ms DELAY — distinct from the panel variant's instant
1754
- ::before swap. */
1755
- /* .crisp-resizablepanel-handle.crisp-resizablepanel-handle--column (both classes) to
1966
+ /* handle — variant "column": a wider 7px hit target straddling a 1px
1967
+ divider (table column edges), with a real 3px inner bar whose opacity
1968
+ comes in on a 100ms DELAY — distinct from the panel variant's instant
1969
+ ::before swap. */ /* .crisp-resizablepanel-handle.crisp-resizablepanel-handle--column (both classes) to
1756
1970
  out-specificity the base [data-side] left/right rules above, which
1757
1971
  otherwise tie-break by source order alone and would win. */
1758
1972
  & .crisp-resizablepanel-handle.crisp-resizablepanel-handle--column { display: flex; align-items: center; justify-content: center; width: 7px; left: -3px; right: -3px; z-index: 1; cursor: col-resize; }
@@ -1817,6 +2031,20 @@
1817
2031
  .crisp-select-search svg { flex-shrink: 0; }
1818
2032
  .crisp-select-search input { flex: 1; min-width: 0; border: none; outline: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); line-height: 19px; color: var(--crisp-fg-select-neutral); }
1819
2033
  .crisp-select-search input::placeholder { color: var(--crisp-fg-select-placeholder); }
2034
+ /* select.yaml's own divider slot (marginX x4, height 1px, colour
2035
+ stroke.neutral-muted -- described upstream as "the line marking a
2036
+ boundary between semantic units: between sections, between content
2037
+ and an action area, between a header and a body" -- exactly the
2038
+ search-field/option-list boundary crisp#904's review flagged as
2039
+ reading too tight). Not the general-purpose separator #867 declined
2040
+ to build (nothing groups options with one) -- scoped to this one
2041
+ specific header/body seam, which now exists because searchable
2042
+ does. Sits as an ordinary flex child, so the listbox's own 8px gap
2043
+ still applies on both sides of it -- total visual seam is
2044
+ gap+1px+gap (17px), deliberately more than the 8px between two
2045
+ plain option rows, so it reads as a section break, not a wider list
2046
+ gap. */
2047
+ .crisp-select-divider { flex-shrink: 0; height: 1px; margin-inline: var(--crisp-space-4); background: var(--crisp-stroke-select-divider); }
1820
2048
  .crisp-select-empty { padding: var(--crisp-space-3) var(--crisp-space-4); text-align: center; font-size: var(--crisp-text-md); color: var(--crisp-fg-neutral-subtle); }
1821
2049
  /* select.yaml's groupLabel -- font-size/line-height/weight are the same
1822
2050
  across sizes, only paddingY differs (medium 8px, large 10px). */
@@ -1846,21 +2074,27 @@
1846
2074
  .crisp-select {
1847
2075
  position: relative; display: inline-flex; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
1848
2076
  & .crisp-select-trigger { box-sizing: border-box; display: inline-flex; align-items: center; width: 100%; min-width: 0; margin: 0; border: 1px solid var(--crisp-stroke-select-trigger); background: transparent; cursor: pointer; font-family: inherit; color: var(--crisp-fg-select-neutral); transition: background-color .15s cubic-bezier(.35,0,.35,1), border-color .1s cubic-bezier(.35,0,.35,1), border-width .1s cubic-bezier(.35,0,.35,1); }
1849
- /* seed defines no hover state for this trigger -- only pressed, invalid, disabled and readonly. The scale-down is scoped to the CONTENT (seed's scaleScope: content), not the button itself, so the border
1850
- never moves. */
1851
- /* Only the value scales, not the chevron: the chevron's own transform is its open/close rotation, and a scale would fight it for one shared CSS property when both are true at once (pressing while open). */
2077
+ /* seed defines no hover state for this trigger -- only pressed, invalid,
2078
+ disabled and readonly. The scale-down is scoped to the CONTENT
2079
+ (seed's scaleScope: content), not the button itself, so the border
2080
+ never moves. */ /* Only the value scales, not the chevron: the chevron's own transform is
2081
+ its open/close rotation, and a scale would fight it for one shared
2082
+ CSS property when both are true at once (pressing while open). */
1852
2083
  & .crisp-select-trigger:active:not(:disabled):not([aria-readonly="true"]) { background: var(--crisp-bg-select-pressed); }
1853
2084
  & .crisp-select-trigger:active:not(:disabled):not([aria-readonly="true"]) .crisp-select-value { transform: scale(0.98); }
1854
2085
  & .crisp-select-trigger:focus-visible { outline: 2px solid var(--crisp-stroke-focus-ring); outline-offset: -2px; }
1855
2086
  & .crisp-select-trigger:disabled { background: var(--crisp-bg-select-disabled); cursor: not-allowed; }
1856
2087
  & .crisp-select-trigger:disabled .crisp-select-value, & .crisp-select-trigger:disabled .crisp-select-placeholder, & .crisp-select-trigger:disabled .crisp-select-chevron { color: var(--crisp-fg-disabled); }
1857
- /* readonly: same fill as disabled, but the value/placeholder text stays normal ink -- it reads as "answered and locked", not "answered and
2088
+ /* readonly: same fill as disabled, but the value/placeholder text stays
2089
+ normal ink -- it reads as "answered and locked", not "answered and
1858
2090
  greyed out" (select-trigger.yaml's readonly state only overrides
1859
2091
  root.color; value/placeholder keep their enabled colours). */
1860
2092
  & .crisp-select-trigger[aria-readonly="true"] { background: var(--crisp-bg-select-disabled); cursor: default; }
1861
2093
  & .crisp-select-value { flex: 1; min-width: 0; display: inline-flex; align-items: center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: start; transition: transform .15s cubic-bezier(0,0,.15,1); }
1862
2094
  & .crisp-select-placeholder { color: var(--crisp-fg-select-placeholder); }
1863
- /* closeRotateDuration/closeRotateTimingFunction: 100ms, seed's "easing" curve. Overridden to 150ms on open (openRotateDuration) below -- same curve both ways, only the duration differs. */
2095
+ /* closeRotateDuration/closeRotateTimingFunction: 100ms, seed's "easing"
2096
+ curve. Overridden to 150ms on open (openRotateDuration) below --
2097
+ same curve both ways, only the duration differs. */
1864
2098
  & .crisp-select-chevron { flex-shrink: 0; color: var(--crisp-fg-neutral-muted); transition: transform .1s cubic-bezier(.35,0,.35,1); }
1865
2099
  & .crisp-select-trigger[data-open] .crisp-select-chevron { transform: rotate(180deg); transition-duration: .15s; }
1866
2100
  }
@@ -1943,42 +2177,21 @@
1943
2177
  }
1944
2178
 
1945
2179
  .crisp-slider {
1946
- display: flex; flex-direction: column; align-items: stretch; width: 100%; min-width: 0; gap: var(--crisp-space-0-5); font-family: var(--crisp-font-sans); user-select: none; touch-action: none; /* the position words ("minimum"/"maximum") that ARIA concatenates onto a thumb's name — read, never drawn */
1947
- & .crisp-slider-thumblabel {
1948
- position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
1949
- overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
1950
- }
2180
+ display: flex; flex-direction: column; align-items: stretch; width: 100%; min-width: 0; gap: var(--crisp-space-0-5); font-family: var(--crisp-font-sans); user-select: none; touch-action: none; /* the position words ("minimum"/"maximum") that ARIA concatenates onto a
2181
+ thumb's name — read, never drawn */
2182
+ & .crisp-slider-thumblabel { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
1951
2183
  & .crisp-slider-control { position: relative; height: 26px; display: flex; align-items: center; }
1952
-
1953
- & .crisp-slider-track {
1954
- position: relative; flex-grow: 1; height: var(--crisp-space-1);
1955
- border-radius: var(--crisp-radius-full); background: var(--crisp-bg-neutral-weak);
1956
- overflow: hidden;
1957
- }
1958
-
2184
+ & .crisp-slider-track { position: relative; flex-grow: 1; height: var(--crisp-space-1); border-radius: var(--crisp-radius-full); background: var(--crisp-bg-neutral-weak); overflow: hidden; }
1959
2185
  & .crisp-slider-range { position: absolute; height: 100%; background: var(--crisp-bg-brand-solid); }
1960
2186
  & .crisp-slider-range[data-dir="ltr"] { left: var(--range-start); right: var(--range-end); }
1961
2187
  & .crisp-slider-range[data-dir="rtl"] { right: var(--range-start); left: var(--range-end); }
1962
-
1963
- & .crisp-slider-thumb {
1964
- position: absolute; top: 50%;
1965
- width: var(--crisp-space-5); height: var(--crisp-space-5);
1966
- transform: translate(-50%, -50%);
1967
- border: none; background: none; padding: 0;
1968
- }
2188
+ & .crisp-slider-thumb { position: absolute; top: 50%; width: var(--crisp-space-5); height: var(--crisp-space-5); transform: translate(-50%, -50%); border: none; background: none; padding: 0; }
1969
2189
  & .crisp-slider-thumb[data-dir="ltr"] { left: calc(var(--thumb-position) * 1% + var(--thumb-offset)); }
1970
2190
  & .crisp-slider-thumb[data-dir="rtl"] { right: calc(var(--thumb-position) * 1% + var(--thumb-offset)); }
1971
- & .crisp-slider-thumb::after {
1972
- content: ""; position: absolute; inset: 0;
1973
- border-radius: var(--crisp-radius-full); background: #fff; box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04);
1974
- outline: 2px solid transparent; outline-offset: 2px;
1975
- transition: transform .2s ease, outline-color .2s ease;
1976
- cursor: grab;
1977
- }
2191
+ & .crisp-slider-thumb::after { content: ""; position: absolute; inset: 0; border-radius: var(--crisp-radius-full); background: #fff; box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04); outline: 2px solid transparent; outline-offset: 2px; transition: transform .2s ease, outline-color .2s ease; cursor: grab; }
1978
2192
  & .crisp-slider-thumb[data-thumb-dragging]::after { transform: scale(1.2); cursor: grabbing; }
1979
2193
  & .crisp-slider-thumb:focus { outline: none; }
1980
2194
  & .crisp-slider-thumb:focus-visible::after { outline-color: var(--crisp-stroke-focus-ring); }
1981
-
1982
2195
  &[data-disabled] { cursor: not-allowed; }
1983
2196
  &[data-disabled] .crisp-slider-range { background: var(--crisp-fg-disabled); }
1984
2197
  &[data-disabled] .crisp-slider-thumb::after { cursor: not-allowed; }
@@ -1992,6 +2205,31 @@
1992
2205
  }
1993
2206
  }
1994
2207
 
2208
+ .crisp-snackbar {
2209
+ & .crisp-snackbar-region { position: fixed; z-index: 80; inset-inline: 0; bottom: 0; display: flex; justify-content: center; padding: var(--crisp-space-2); /* snackbar.yaml:95-96 region.paddingX/paddingY = $dimension.x2 (8px) */ pointer-events: none; }
2210
+ & .crisp-snackbar { pointer-events: auto; box-sizing: border-box; display: flex; align-items: center; gap: var(--crisp-space-2-5); /* snackbar.yaml:116 content.gap = $dimension.x2_5 (10px), reused as the icon+message-to-action gap */ min-height: 44px; max-width: 464px; /* snackbar.yaml:102-103 */ width: max-content; padding: var(--crisp-space-2-5); /* snackbar.yaml:104-105 root.paddingX/paddingY = $dimension.x2_5 (10px) */ border-radius: var(--crisp-radius-md); /* snackbar.yaml:101 root.cornerRadius = $radius.r2 (8px) */ background: var(--crisp-bg-neutral-inverted); /* snackbar.yaml:100 root.color = $color.bg.neutral-inverted */ font-family: var(--crisp-font-sans); opacity: 1; transform: scale(1); transition: opacity 150ms cubic-bezier(0,0,.15,1), transform 150ms cubic-bezier(0,0,.15,1); /* snackbar.yaml:108-109 enterDuration/enterTimingFunction = $duration.d3 (150ms) / $timing-function.enter */ @starting-style { opacity: 0; transform: scale(0.8); } /* snackbar.yaml:106-107 enterOpacity/enterScale */ }
2211
+ & .crisp-snackbar-content { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: var(--crisp-space-0-5); }
2212
+ /* snackbar.yaml:124 prefixIcon.paddingRight = $dimension.x0_5 (2px), used as the icon-to-message gap */
2213
+ & .crisp-snackbar-icon { flex-shrink: 0; display: inline-flex; }
2214
+ & .crisp-snackbar[data-variant="positive"] .crisp-snackbar-icon { color: var(--crisp-bg-success); }
2215
+ /* snackbar.yaml:138-139 variant=positive prefixIcon.color = $color.fg.positive */
2216
+ & .crisp-snackbar[data-variant="critical"] .crisp-snackbar-icon { color: var(--crisp-bg-danger); }
2217
+ /* snackbar.yaml:142-143 variant=critical prefixIcon.color = $color.fg.critical */
2218
+ & .crisp-snackbar-message { flex: 1 1 auto; min-width: 0; color: var(--crisp-fg-on-neutral-inverted); font-size: var(--crisp-text-lg); line-height: 19px; font-weight: var(--crisp-weight-regular); }
2219
+ /* snackbar.yaml:117-121: message.color = $color.fg.neutral-inverted, fontSize/lineHeight = $font-size.t4/$line-height.t4 (14px/19px), fontWeight = regular */
2220
+ & .crisp-snackbar-action { flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; min-height: 44px; padding: 0 var(--crisp-space-2); margin: -12px calc(-1 * var(--crisp-space-2)); /* snackbar.yaml:126-127 targetPaddingX/targetMinHeight = $dimension.x2 (8px, doubled here as horizontal padding)/44px -- a larger invisible tap target than the pill's own 44px content row, via negative margin so it doesn't grow the pill visually */ border: none; border-radius: var(--crisp-radius-md); background: none; color: var(--crisp-fg-on-neutral-inverted); font-family: inherit; font-size: var(--crisp-text-lg); line-height: 19px; font-weight: var(--crisp-weight-bold); /* fontSize/lineHeight = t4/line-height.t4; fontWeight = bold (snackbar.yaml:129-131); colour deviation documented above */ cursor: pointer; transition: transform 150ms cubic-bezier(0,0,.15,1); }
2221
+ & .crisp-snackbar-action:active { transform: scale(0.98); }
2222
+ /* snackbar.yaml:133-134 pressed.actionButton.scaleScope = self */
2223
+ }
2224
+
2225
+ @media (prefers-reduced-motion: reduce) {
2226
+ .crisp-snackbar, .crisp-snackbar * {
2227
+ animation-duration: 0.01ms !important;
2228
+ animation-iteration-count: 1 !important;
2229
+ transition-duration: 0.01ms !important;
2230
+ }
2231
+ }
2232
+
1995
2233
  @keyframes crisp-spin { to { transform: rotate(360deg); } }
1996
2234
  .crisp-spinner {
1997
2235
  display: inline-block; box-sizing: border-box; width: 16px; height: 16px; border-radius: var(--crisp-radius-full); border: 2px solid color-mix(in srgb, var(--crisp-fg-primary) 15%, transparent); border-top-color: var(--crisp-fg-muted); animation: crisp-spin 0.6s linear infinite;
@@ -2027,7 +2265,9 @@
2027
2265
  /* caption's own status colour, opt-in via data-tone: not a new measured
2028
2266
  value — the same fg*Soft tokens Badge's tone already uses (and Badge's
2029
2267
  own doc comment records why: a dedicated soft-text token, not the
2030
- tone's accent colour, is the one that clears AA). No "ok" reading on Badge itself; fgSuccessSoft is Badge's own "success" tone's text colour, reused here under the name a caption's own good-news state
2268
+ tone's accent colour, is the one that clears AA). No "ok" reading on
2269
+ Badge itself; fgSuccessSoft is Badge's own "success" tone's text
2270
+ colour, reused here under the name a caption's own good-news state
2031
2271
  reads as. */
2032
2272
  & .crisp-statcard-caption[data-tone="ok"] { color: var(--crisp-fg-success-soft); }
2033
2273
  & .crisp-statcard-caption[data-tone="warning"] { color: var(--crisp-fg-warning-soft); }
@@ -2067,40 +2307,63 @@
2067
2307
  }
2068
2308
 
2069
2309
  .crisp-table {
2070
- display: block; box-sizing: border-box; background: var(--crisp-bg-surface); /* #554: both axes -- a consumer bounds the height via `scrollProps` (style/className on this element), and the header/footer below pin inside it exactly the way Attio's own grid does ("Both the header and
2310
+ display: block; box-sizing: border-box; background: var(--crisp-bg-surface); /* #554: both axes -- a consumer bounds the height via `scrollProps`
2311
+ (style/className on this element), and the header/footer below pin
2312
+ inside it exactly the way Attio's own grid does ("Both the header and
2071
2313
  the summary row are sticky, on the scroll container rather than the
2072
2314
  cells" -- docs/study/attio-table.md). Unbounded, `overflow: auto`
2073
2315
  shows no vertical scrollbar and nothing sticks, so this is a no-op
2074
2316
  for every existing consumer that never set a height. */
2075
2317
  & .crisp-table-scroll { overflow: auto; }
2076
2318
  & table { width: 100%; border-collapse: separate; border-spacing: 0; font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); }
2077
- /* #465: 12px, not crisp's old 8px -- measured on a live header cell (padding: 0 12px) and independently on a body cell's text inset
2319
+ /* #465: 12px, not crisp's old 8px -- measured on a live header cell
2320
+ (padding: 0 12px) and independently on a body cell's text inset
2078
2321
  (getBoundingClientRect delta from the cell edge). Matches
2079
- ColumnCalc's own already-correct 12px trigger inset. */
2080
- & th, & td { position: relative; height: var(--crisp-size-tableRow); padding: 0 var(--crisp-space-3); border-bottom: 1px solid var(--crisp-border-subtle); border-inline-end: 1px solid var(--crisp-border-subtle); text-align: start; vertical-align: middle; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
2322
+ ColumnCalc's own already-correct 12px trigger inset. */ /* #719: overflow: clip, not hidden -- both render the ellipsis
2323
+ identically, but hidden still honours a programmatic scrollLeft
2324
+ assignment (a screenshot tool, a browser extension) even though it
2325
+ already blocks user-driven wheel/scrollbar input; clip leaves no
2326
+ scroll position for anything to move, so the truncated text can
2327
+ never be panned out of view. */
2328
+ & th, & td { position: relative; height: var(--crisp-size-tableRow); padding: 0 var(--crisp-space-3); border-bottom: 1px solid var(--crisp-border-subtle); border-inline-end: 1px solid var(--crisp-border-subtle); text-align: start; vertical-align: middle; white-space: nowrap; overflow: clip; text-overflow: ellipsis; }
2081
2329
  /* #554: sticky on the vertical axis, mirroring RecordsTable's own
2082
2330
  `.crisp-recordstable-th`. z-index 2 sits above a plain body cell (auto)
2083
- and below a sticky-column corner cell (DataTable's `th[data-sticky]`, z-index 3), so the corner always wins. `thead th`, not a bare `th` (crisp#833/#835): DataTable's primary
2331
+ and below a sticky-column corner cell (DataTable's `th[data-sticky]`,
2332
+ z-index 3), so the corner always wins.
2333
+ `thead th`, not a bare `th` (crisp#833/#835): DataTable's primary
2084
2334
  column renders its BODY cell as a real `<th scope="row">` (#779, for
2085
2335
  a11y), and a bare `th` here matched that one too -- every property in
2086
2336
  this rule leaked onto it: `height: tableHeader` (40px) stretched the
2087
2337
  whole body row to 40px instead of 36 (#833, confirmed live on the
2088
2338
  showcase, every td in the row equalized to the one th); `color:
2089
- color-mix(...63%...)` muted the primary column's own text below every sibling `<td>`'s full-opacity color (#835, re-measured live against
2090
- Attio's own header labels -- including a genuinely computed one, "Twitter follower count" -- all fully opaque `rgb(16,17,18)`, no muting at all, so this drops the color-mix entirely rather than narrowing it); `position:sticky; top:0` tried to stick a BODY row to the scroll container's top; the extra `border-top` doubled that
2091
- row's own grid line. `thead th` simply never matches `tbody th[scope=row]` at all, so none of this reaches it; the base `& th, & td` rule above (36px, one border-bottom, full opacity via `& table`'s own color) applies to it like every other cell. */
2339
+ color-mix(...63%...)` muted the primary column's own text below every
2340
+ sibling `<td>`'s full-opacity color (#835, re-measured live against
2341
+ Attio's own header labels -- including a genuinely computed one,
2342
+ "Twitter follower count" -- all fully opaque `rgb(16,17,18)`, no
2343
+ muting at all, so this drops the color-mix entirely rather than
2344
+ narrowing it); `position:sticky; top:0` tried to stick a BODY row
2345
+ to the scroll container's top; the extra `border-top` doubled that
2346
+ row's own grid line. `thead th` simply never matches
2347
+ `tbody th[scope=row]` at all, so none of this reaches it; the base
2348
+ `& th, & td` rule above (36px, one border-bottom, full opacity via
2349
+ `& table`'s own color) applies to it like every other cell. */
2092
2350
  & thead th { position: sticky; top: 0; z-index: 2; height: var(--crisp-size-tableHeader); background: var(--crisp-bg-surface); border-top: 1px solid var(--crisp-border-subtle); font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); letter-spacing: -0.14px; color: var(--crisp-fg-primary); user-select: none; }
2093
2351
  & td { color: var(--crisp-fg-primary); background: var(--crisp-bg-surface); }
2094
- /* crisp#851 -- a REGRESSION from #833/#835/#848's own fix: scoping the header rule above to `thead th` correctly stopped it leaking onto the primary column's body cell (a real `<th scope="row">` since
2095
- #779), but that same leaked rule's `font-weight: var(--crisp-weight-medium)` was the ONLY thing overriding the browser's own UA stylesheet default
2352
+ /* crisp#851 -- a REGRESSION from #833/#835/#848's own fix: scoping the
2353
+ header rule above to `thead th` correctly stopped it leaking onto
2354
+ the primary column's body cell (a real `<th scope="row">` since
2355
+ #779), but that same leaked rule's `font-weight: var(--crisp-weight-medium)`
2356
+ was the ONLY thing overriding the browser's own UA stylesheet default
2096
2357
  for `<th>` (`font-weight: bold; text-align: center`) on that cell --
2097
2358
  once the leak stopped, a plain-text primary column fell through to
2098
2359
  bold+centred instead of matching its sibling `<td>`s (`400`/start,
2099
- Attio's own plain-text primary cell). `text-align` was already safe (the base `& th, & td` rule's own `text-align: start` above is
2360
+ Attio's own plain-text primary cell). `text-align` was already safe
2361
+ (the base `& th, & td` rule's own `text-align: start` above is
2100
2362
  author-origin and always beats the UA stylesheet regardless of
2101
2363
  specificity) but is restated here explicitly, defensively -- cheap,
2102
2364
  and removes any doubt. `type: "entity"` primary columns are a
2103
- separate case (Attio's own entity/record names are 500, not 400) -- see `.crisp-datatable-entity-name`'s own new explicit weight in
2365
+ separate case (Attio's own entity/record names are 500, not 400) --
2366
+ see `.crisp-datatable-entity-name`'s own new explicit weight in
2104
2367
  datatable.recipe.ts, which wins here by class specificity. */
2105
2368
  & tbody th { font-weight: var(--crisp-weight-regular); text-align: start; }
2106
2369
  & th:last-child, & td:last-child { border-inline-end: none; }
@@ -2144,12 +2407,18 @@
2144
2407
  & .crisp-tabletoolbar-search svg { flex-shrink: 0; }
2145
2408
  & .crisp-tabletoolbar-search input { border: none; outline: none; background: transparent; font-family: inherit; font-size: var(--crisp-text-md); color: var(--crisp-fg-primary); width: 180px; }
2146
2409
  & .crisp-tabletoolbar-search input::placeholder { color: var(--crisp-fg-muted); }
2147
- /* crisp/#830: every trigger label here (Sort, Filter, View settings, Import/Export -- all compose this one shared .tool rule) read the "md" text size, 13px. Re-measured on Attio's own trigger LEAF node
2148
- (the button root itself inherits Attio's own 10px rem base and is a red herring, the same trap the original "10px font" layout-audit finding fell into): every label leaf is 14px/500/lh20, the "lg" text size. */
2410
+ /* crisp/#830: every trigger label here (Sort, Filter, View settings,
2411
+ Import/Export -- all compose this one shared .tool rule) read the
2412
+ "md" text size, 13px. Re-measured on Attio's own trigger LEAF node
2413
+ (the button root itself inherits Attio's own 10px rem base and is a
2414
+ red herring, the same trap the original "10px font" layout-audit
2415
+ finding fell into): every label leaf is 14px/500/lh20, the "lg" text
2416
+ size. */
2149
2417
  & .crisp-tabletoolbar-tool { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-border-divider); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); white-space: nowrap; cursor: pointer; transition: background-color .12s; }
2150
2418
  & .crisp-tabletoolbar-tool:hover { background: var(--crisp-bg-raised); }
2151
2419
  & .crisp-tabletoolbar-tool svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
2152
- /* crisp/#772: re-measured live on Sort with an actual sort applied -- an active trigger's own fill is white (bgSurface), the same as the
2420
+ /* crisp/#772: re-measured live on Sort with an actual sort applied --
2421
+ an active trigger's own fill is white (bgSurface), the same as the
2153
2422
  base .tool rule above now already sets; this override existed to
2154
2423
  distinguish active from a base state that used to be transparent.
2155
2424
  Kept explicit (not just deleted) so a future change to the base
@@ -2160,13 +2429,23 @@
2160
2429
  /* crisp/#772 follow-up, re-measured live 2026-09-25: an EMPTY Sort/Filter
2161
2430
  trigger (no sort/condition set) isn't a solid-ring chip at all -- it's
2162
2431
  a dashed ::before (1px dashed, border-radius: inherit, absolutely
2163
- positioned inset: 0) over a barely-there fill, confirmed on Attio's own empty Filter chip (its ::before border computes to exactly "1px dashed rgba(0, 0, 0, 0.06)"). Once a value IS set, the trigger drops the dashed pseudo and picks up the base tool's own solid ring
2432
+ positioned inset: 0) over a barely-there fill, confirmed on Attio's
2433
+ own empty Filter chip (its ::before border computes to exactly
2434
+ "1px dashed rgba(0, 0, 0, 0.06)"). Once a value IS set, the trigger
2435
+ drops the dashed pseudo and picks up the base tool's own solid ring
2164
2436
  instead (the --active modifier, unchanged) -- confirmed on Sort with
2165
2437
  an actual sort applied. color-mix (not the raw rgba Attio computes
2166
2438
  to) so both the border and the fill flip correctly in dark mode, the
2167
2439
  same reasoning --ghost above already uses. Only wired into
2168
- TableToolbar itself (tabletoolbar.tsx) this pass -- RecordsTable's own Sort/Filter triggers share this same chrome function but are deprecated, not touched here. crisp/#844, correcting #831: the label colour was added here later, muting only the FILTER trigger (via a "--filter" modifier), on the assumption that was a Filter-specific tone distinct from Sort's.
2169
- Re-measured live and that premise was wrong -- Sort's own resting label ("Sort", no condition set) is the exact SAME muted rgba(0,0,0,.5) as Filter's, not full-strength. (The rgba(0,0,0,.63) that looked like
2440
+ TableToolbar itself (tabletoolbar.tsx) this pass -- RecordsTable's
2441
+ own Sort/Filter triggers share this same chrome function but are
2442
+ deprecated, not touched here.
2443
+ crisp/#844, correcting #831: the label colour was added here later,
2444
+ muting only the FILTER trigger (via a "--filter" modifier), on the
2445
+ assumption that was a Filter-specific tone distinct from Sort's.
2446
+ Re-measured live and that premise was wrong -- Sort's own resting
2447
+ label ("Sort", no condition set) is the exact SAME muted rgba(0,0,0,.5)
2448
+ as Filter's, not full-strength. (The rgba(0,0,0,.63) that looked like
2170
2449
  "Sort's colour" in an earlier pass is a different thing entirely: the
2171
2450
  "Sorted by" PREFIX word inside an ACTIVE sort chip, #788 -- a
2172
2451
  compound-label treatment that only exists once a sort is applied, not
@@ -2253,17 +2532,21 @@
2253
2532
  & .crisp-tabletoolbar-fchip-x:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 10%, transparent); color: var(--crisp-fg-primary); }
2254
2533
  & .crisp-tabletoolbar-spacer { flex: 1; }
2255
2534
  & .crisp-tabletoolbar-count { font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
2256
- & .crisp-tabletoolbar-new { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-row); padding: 0 var(--crisp-space-2-5); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-brand-solid); color: var(--crisp-fg-on-brand); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); cursor: pointer; transition: background-color .12s; }
2257
- & .crisp-tabletoolbar-new:hover { background: var(--crisp-bg-brand-solid-pressed); }
2258
-
2535
+ /* your-moon/crisp#953: bgBrandContrast, not bgBrandSolid --
2536
+ the accent (#0298d9) only clears 3.23:1 with white; the darker sibling
2537
+ clears 4.5:1+, same fix already applied to Button/Badge/Avatar (#897).
2538
+ Hover is a brightness filter on the new fill, not bgBrandSolidPressed
2539
+ (that token stays tuned for the lighter accent's own consumers). */
2540
+ & .crisp-tabletoolbar-new { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-row); padding: 0 var(--crisp-space-2-5); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-brand-contrast); color: var(--crisp-fg-on-brand); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); cursor: pointer; transition: filter .12s; }
2541
+ & .crisp-tabletoolbar-new:hover { filter: brightness(0.96); }
2259
2542
  & .crisp-tabletoolbar-fcond { display: inline-flex; align-items: center; height: var(--crisp-size-row); font-size: var(--crisp-text-md); color: var(--crisp-fg-primary); white-space: nowrap; }
2260
2543
  & .crisp-tabletoolbar-fcond-seg { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: 100%; margin: 0; padding: 0 var(--crisp-space-2); border: none; background: var(--crisp-bg-surface-muted); box-shadow: inset 0 0 0 1px var(--crisp-border-divider); font-family: inherit; font-size: inherit; color: inherit; cursor: pointer; }
2261
2544
  & .crisp-tabletoolbar-fcond-seg svg { flex-shrink: 0; }
2262
- & .crisp-tabletoolbar-fcond[data-complete] .crisp-tabletoolbar-fcond-seg--field,
2263
- & .crisp-tabletoolbar-fcond[data-complete] .crisp-tabletoolbar-fcond-seg--val { background: var(--crisp-bg-surface); }
2545
+ & .crisp-tabletoolbar-fcond[data-complete] .crisp-tabletoolbar-fcond-seg--field, & .crisp-tabletoolbar-fcond[data-complete] .crisp-tabletoolbar-fcond-seg--val { background: var(--crisp-bg-surface); }
2264
2546
  & .crisp-tabletoolbar-fcond-seg--field { border-radius: var(--crisp-radius-md) 0 0 var(--crisp-radius-md); font-weight: var(--crisp-weight-medium); max-width: 200px; }
2265
2547
  & .crisp-tabletoolbar-fcond-seg--field svg { color: var(--crisp-fg-muted); }
2266
- /* #592: a nested pick's field segment ("Team › Twitter follower count") — measured live: BOTH path segments the same colour (chip
2548
+ /* #592: a nested pick's field segment ("Team › Twitter follower
2549
+ count") — measured live: BOTH path segments the same colour (chip
2267
2550
  context only; the picker's own menu rows keep the parent muted, see
2268
2551
  tabletoolbar.chrome.ts), each independently truncated. Attio's own
2269
2552
  layout gives the leaf segment priority (it stays fully readable
@@ -2289,8 +2572,7 @@
2289
2572
  & .crisp-tabletoolbar-sort-field-picker { position: relative; display: inline-flex; flex: 1; min-width: 0; }
2290
2573
  & .crisp-tabletoolbar-sort-field-trigger { box-sizing: border-box; display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: var(--crisp-size-menuRow); width: 100%; min-width: 0; margin: 0; padding: 0 var(--crisp-space-1-5) 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: transparent; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 7%, transparent); font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); cursor: pointer; transition: background-color .12s ease, box-shadow .12s ease; }
2291
2574
  & .crisp-tabletoolbar-sort-field-trigger:hover { background: var(--crisp-bg-raised); }
2292
- & .crisp-tabletoolbar-sort-field-trigger[aria-expanded="true"],
2293
- & .crisp-tabletoolbar-sort-field-trigger:focus-visible { outline: none; box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); background: var(--crisp-bg-surface); }
2575
+ & .crisp-tabletoolbar-sort-field-trigger[aria-expanded="true"], & .crisp-tabletoolbar-sort-field-trigger:focus-visible { outline: none; box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); background: var(--crisp-bg-surface); }
2294
2576
  & .crisp-tabletoolbar-sort-field-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: start; }
2295
2577
  & .crisp-tabletoolbar-sort-field-picker .crisp-tabletoolbar-menu { min-width: 200px; }
2296
2578
  & .crisp-tabletoolbar-menu--val { min-width: 134px; }
@@ -2298,18 +2580,10 @@
2298
2580
  & .crisp-tabletoolbar-datepop-presets { display: flex; flex-wrap: wrap; gap: var(--crisp-space-1); margin-top: var(--crisp-space-2); padding: 0 var(--crisp-space-1); }
2299
2581
  & .crisp-tabletoolbar-datepop-preset { height: var(--crisp-size-chip); padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-chip); background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-sm); cursor: pointer; }
2300
2582
  & .crisp-tabletoolbar-datepop-preset:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 8%, transparent); }
2301
-
2302
2583
  & .crisp-tabletoolbar-tool--raised { border: none; background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04); font-family: inherit; cursor: pointer; }
2303
-
2304
2584
  & .crisp-tabletoolbar-sorted-prefix { color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
2305
- & .crisp-tabletoolbar-sorted-count { display: inline-flex; align-items: center; height: 16px; box-sizing: border-box;
2306
- padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-sm); background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent);
2307
- box-shadow: inset 0 0 0 1px var(--crisp-border-subtle); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-neutral-soft); }
2308
-
2309
- & .crisp-tabletoolbar-tool-count { display: inline-flex; align-items: center; height: 16px; box-sizing: border-box;
2310
- padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-sm); background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent);
2311
- box-shadow: inset 0 0 0 1px var(--crisp-border-subtle); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-neutral-soft);
2312
- font-variant-numeric: tabular-nums; }
2585
+ & .crisp-tabletoolbar-sorted-count { display: inline-flex; align-items: center; height: 16px; box-sizing: border-box; padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-sm); background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); box-shadow: inset 0 0 0 1px var(--crisp-border-subtle); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-neutral-soft); }
2586
+ & .crisp-tabletoolbar-tool-count { display: inline-flex; align-items: center; height: 16px; box-sizing: border-box; padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-sm); background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); box-shadow: inset 0 0 0 1px var(--crisp-border-subtle); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-neutral-soft); font-variant-numeric: tabular-nums; }
2313
2587
  [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
2314
2588
  }
2315
2589
 
@@ -2325,17 +2599,31 @@
2325
2599
  display: flex; flex-direction: column; min-height: 0; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
2326
2600
  & .crisp-tabs-list { position: relative; display: flex; align-items: center; gap: var(--crisp-space-1); box-sizing: content-box; height: var(--crisp-size-row); padding: 0 var(--crisp-space-3) var(--crisp-space-2); flex-shrink: 0; }
2327
2601
  & .crisp-tabs-list::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px; background: var(--crisp-border-subtle); }
2602
+ /* crisp#908: an alternative to the "+N more" collapse above (crisp#863) --
2603
+ a consumer opts into a single scrolling row instead (Naver/Kakao's own
2604
+ pattern, docs/study/korean/), never both at once. No new visual value:
2605
+ reuses the same list gap/tab geometry, only adds overflow-x + snap. */
2606
+ & .crisp-tabs-list[data-overflow="scroll"] { overflow-x: auto; flex-wrap: nowrap; scroll-snap-type: x mandatory; scrollbar-width: none; }
2607
+ & .crisp-tabs-list[data-overflow="scroll"]::-webkit-scrollbar { display: none; }
2608
+ & .crisp-tabs-list[data-overflow="scroll"] .crisp-tabs-tab { flex-shrink: 0; scroll-snap-align: start; }
2328
2609
  & .crisp-tabs-leading { display: inline-flex; align-items: center; margin-inline-end: var(--crisp-space-1); }
2329
- & .crisp-tabs-tab { position: relative; display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: 28px; margin: 0; padding: 0 var(--crisp-space-2); border: none; background: none; box-shadow: inset 0 0 0 1px transparent; border-radius: var(--crisp-radius-md); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); letter-spacing: -0.14px; color: var(--crisp-fg-muted); cursor: pointer; white-space: nowrap; transition: color .12s ease, background-color .12s ease, box-shadow .12s ease; }
2610
+ & .crisp-tabs-tab { position: relative; display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: 28px; margin: 0; padding: 0 var(--crisp-space-2); border: none; background: none; border-radius: var(--crisp-radius-md); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); letter-spacing: -0.14px; color: var(--crisp-fg-muted); cursor: pointer; white-space: nowrap; transition: color .12s ease, background-color .12s ease; }
2330
2611
  & .crisp-tabs-tab svg { flex-shrink: 0; opacity: 0.85; }
2331
2612
  & .crisp-tabs-tab:hover:not([data-selected]) { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); color: var(--crisp-fg-primary); }
2332
- & .crisp-tabs-tab[data-selected] { color: var(--crisp-fg-primary); background: var(--crisp-bg-subtle); box-shadow: inset 0 0 0 1px var(--crisp-border-subtle); }
2613
+ & .crisp-tabs-tab[data-selected] { color: var(--crisp-fg-primary); }
2333
2614
  & .crisp-tabs-tab:focus-visible { outline: 2px solid var(--crisp-bg-brand-solid); outline-offset: 1px; }
2334
2615
  & .crisp-tabs-tab-badge { display: inline-flex; align-items: center; justify-content: center; min-width: 14px; height: 14px; padding: 0 var(--crisp-space-1); border-radius: 4px; background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); box-shadow: inset 0 0 0 1px var(--crisp-border-subtle); font-size: 10px; line-height: 14px; font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-neutral-soft); }
2335
2616
  & .crisp-tabs-tab[data-selected] .crisp-tabs-tab-badge { color: var(--crisp-fg-primary); }
2336
2617
  & .crisp-tabs-indicator { position: absolute; bottom: 0; left: 0; z-index: 1; height: 1px; background: var(--crisp-fg-primary); border-radius: 2px 2px 0 0; transition: transform .2s cubic-bezier(.4,0,.2,1), width .2s cubic-bezier(.4,0,.2,1); pointer-events: none; }
2337
- /* crisp#863: the "+N more" overflow trigger, measured live -- its computed style is identical to a plain .crisp-tabs-tab (same pill, same 6px icon/label gap the tab class already gives via space["1.5"]), and the dropdown it opens is crisp's own Menu/
2338
- MenuItem chrome (already matches Attio's popover, nothing new to style there). Only the wrapper needs a rule: an overflowed tab (or the trigger itself, when nothing is hidden) is taken out of the flex row and out of the a11y tree without ever making its own offsetWidth unmeasurable the way display:none would -- that's what
2618
+ /* crisp#863: the "+N more" overflow trigger, measured live -- its
2619
+ computed style is identical to a plain .crisp-tabs-tab (same pill,
2620
+ same 6px icon/label gap the tab class already gives via
2621
+ space["1.5"]), and the dropdown it opens is crisp's own Menu/
2622
+ MenuItem chrome (already matches Attio's popover, nothing new to
2623
+ style there). Only the wrapper needs a rule: an overflowed tab (or
2624
+ the trigger itself, when nothing is hidden) is taken out of the
2625
+ flex row and out of the a11y tree without ever making its own
2626
+ offsetWidth unmeasurable the way display:none would -- that's what
2339
2627
  lets the tabs.tsx effect recompute the cutoff from cached widths on
2340
2628
  every resize with no flash of an unclipped row. */
2341
2629
  & .crisp-tabs-more { display: inline-flex; }
@@ -2376,18 +2664,14 @@
2376
2664
 
2377
2665
  .crisp-textarea {
2378
2666
  position: relative; display: block; width: 100%; font-family: var(--crisp-font-sans);
2379
- & .crisp-textarea-field { box-sizing: border-box; display: block; width: 100%; min-height: 80px; resize: vertical; margin: 0; padding: var(--crisp-space-2-5) 14px; border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-field); font-family: inherit; font-size: var(--crisp-text-lg); line-height: 20px; color: var(--crisp-fg-primary); outline: none; transition: box-shadow .15s ease; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
2667
+ & .crisp-textarea-field { box-sizing: border-box; display: block; width: 100%; min-height: 80px; resize: vertical; margin: 0; padding: var(--crisp-space-2-5) 14px; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); border: 1px solid var(--crisp-border-default); font-family: inherit; font-size: var(--crisp-text-lg); line-height: 20px; color: var(--crisp-fg-primary); outline: none; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
2380
2668
  & .crisp-textarea-field::placeholder { color: var(--crisp-fg-muted); }
2381
2669
  & .crisp-textarea-field:disabled { opacity: 0.5; cursor: not-allowed; }
2382
2670
  /* seed-design's focus treatment, same as Input: a 2px ring on an ::after
2383
2671
  overlay that is present-but-transparent at rest, so focusing never
2384
2672
  shifts the box, animating only its colour. */
2385
2673
  & .crisp-textarea-wrap { position: relative; display: block; }
2386
- & .crisp-textarea-wrap::after {
2387
- content: ""; position: absolute; inset: 0; border-radius: var(--crisp-radius-md);
2388
- border: 2px solid transparent; transition: border-color .1s ease;
2389
- pointer-events: none;
2390
- }
2674
+ & .crisp-textarea-wrap::after { content: ""; position: absolute; inset: 0; border-radius: var(--crisp-radius-md); border: 2px solid transparent; transition: border-color .1s ease; pointer-events: none; }
2391
2675
  & .crisp-textarea-wrap:focus-within::after { border-color: var(--crisp-stroke-field-focus); }
2392
2676
  &[data-invalid] .crisp-textarea-wrap::after { border-color: var(--crisp-bg-danger); }
2393
2677
  &[data-invalid] .crisp-textarea-wrap:focus-within::after { border-color: var(--crisp-bg-danger); }
@@ -2479,14 +2763,16 @@
2479
2763
  .crisp-wheelpicker {
2480
2764
  position: relative; box-sizing: border-box; width: 100%; overflow: hidden; background: var(--crisp-bg-surface); font-family: var(--crisp-font-sans); /* the band the selected row sits in; behind the items, never hit-tested */
2481
2765
  & .crisp-wheelpicker-indicator { position: absolute; z-index: 0; inset-inline: var(--crisp-space-4); top: 50%; transform: translateY(-50%); border-radius: var(--crisp-radius-md); background: var(--crisp-bg-neutral-weak); pointer-events: none; }
2482
- /* fades the rows furthest from the centre, so the column reads as a wheel rather than a list. 40% of the viewport, capped, top and bottom. */
2766
+ /* fades the rows furthest from the centre, so the column reads as a wheel
2767
+ rather than a list. 40% of the viewport, capped, top and bottom. */
2483
2768
  & .crisp-wheelpicker-fog { position: relative; z-index: 1; height: 100%; overflow: hidden; mask-image: linear-gradient( to bottom, transparent 0, #000 var(--crisp-wheelpicker-fog), #000 calc(100% - var(--crisp-wheelpicker-fog)), transparent 100% ); }
2484
2769
  & .crisp-wheelpicker-columns { display: flex; width: 100%; height: 100%; justify-content: center; }
2485
2770
  & .crisp-wheelpicker-column { box-sizing: border-box; flex: 0 0 auto; width: max-content; height: 100%; overflow-x: hidden; overflow-y: auto; overscroll-behavior-y: contain; overscroll-behavior-x: none; scroll-snap-type: y mandatory; touch-action: pan-y; scrollbar-width: none; -ms-overflow-style: none; outline: none; padding-block: var(--crisp-wheelpicker-offset); }
2486
2771
  & .crisp-wheelpicker-column::-webkit-scrollbar { display: none; }
2487
2772
  & .crisp-wheelpicker-item { display: flex; align-items: center; justify-content: center; scroll-snap-align: center; user-select: none; white-space: nowrap; padding-inline: var(--crisp-space-4); color: var(--crisp-fg-disabled); background: none; border: none; font: inherit; cursor: pointer; }
2488
2773
  & .crisp-wheelpicker-item[data-selected] { color: var(--crisp-fg-primary); }
2489
- /* the ring is drawn on the SELECTED row, and only for keyboard focus: a pointer user has just tapped the row they meant and does not need it */
2774
+ /* the ring is drawn on the SELECTED row, and only for keyboard focus:
2775
+ a pointer user has just tapped the row they meant and does not need it */
2490
2776
  & .crisp-wheelpicker-column:focus-visible .crisp-wheelpicker-item[data-selected] { outline: 2px solid var(--crisp-stroke-focus-ring); outline-offset: -2px; border-radius: var(--crisp-radius-md); }
2491
2777
  }
2492
2778
  .crisp-wheelpicker--size_small {
@@ -2540,7 +2826,11 @@
2540
2826
  & .crisp-authshell-progress { position: absolute; top: 0; left: 0; width: 100%; height: 2px; background: var(--crisp-border-default); }
2541
2827
  & .crisp-authshell-progress-fill { height: 2px; background: var(--crisp-fg-primary); border-radius: 4px; transition: width .2s; }
2542
2828
  & .crisp-authshell-logo { margin-top: var(--crisp-space-8); min-height: 24px; display: flex; align-items: center; gap: var(--crisp-space-2-5); }
2543
- & .crisp-authshell-logo-badge { flex-shrink: 0; box-sizing: border-box; width: 32px; height: 32px; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-brand-solid); color: #fff; display: flex; align-items: center; justify-content: center; }
2829
+ /* your-moon/crisp#953: bgBrandContrast, not bgBrandSolid --
2830
+ the accent (#0298d9) only clears 3.23:1 with white; the darker
2831
+ sibling clears 4.5:1+, same fix already applied to Button/Badge/
2832
+ Avatar (#897). */
2833
+ & .crisp-authshell-logo-badge { flex-shrink: 0; box-sizing: border-box; width: 32px; height: 32px; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-brand-contrast); color: #fff; display: flex; align-items: center; justify-content: center; }
2544
2834
  & .crisp-authshell-logo-text { display: flex; flex-direction: column; line-height: 1.2; }
2545
2835
  & .crisp-authshell-logo-title { font-size: var(--crisp-text-xl); font-weight: var(--crisp-weight-bold); letter-spacing: -0.2px; color: var(--crisp-fg-primary); }
2546
2836
  & .crisp-authshell-logo-subtitle { font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); }
@@ -2556,7 +2846,8 @@
2556
2846
  & .crisp-authshell-form { align-self: stretch; display: flex; flex-direction: column; gap: 28px; }
2557
2847
  & .crisp-authshell-footer { margin-top: var(--crisp-space-6); display: flex; align-items: center; gap: var(--crisp-space-1); font-size: var(--crisp-text-sm); line-height: 16px; color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
2558
2848
  & .crisp-authshell-footer a { color: inherit; font-weight: var(--crisp-weight-medium); text-decoration: underline; }
2559
- /* Auth-surface control treatment: flatter than the in-app chrome — a plain hairline ring instead of elevation.field's soft glow, and a 36px (not
2849
+ /* Auth-surface control treatment: flatter than the in-app chrome — a plain
2850
+ hairline ring instead of elevation.field's soft glow, and a 36px (not
2560
2851
  40px) submit. Scoped to .crisp-authshell-body so a consumer keeps using
2561
2852
  crisp's Input / FormField / Button and gets the auth treatment for free. */
2562
2853
  & .crisp-authshell-body .crisp-formfield-label { font-size: var(--crisp-text-sm); line-height: 16px; font-weight: var(--crisp-weight-medium); letter-spacing: -0.12px; color: color-mix(in srgb, var(--crisp-fg-primary) 50%, transparent); padding-inline-start: var(--crisp-space-1); }
@@ -2564,10 +2855,10 @@
2564
2855
  & .crisp-authshell-body .crisp-input { height: 34px; padding-inline: var(--crisp-space-2-5); border-radius: 10px; background: transparent; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); letter-spacing: -0.14px; transition: box-shadow .14s, background-color .14s; }
2565
2856
  & .crisp-authshell-body .crisp-input:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
2566
2857
  /* Same three-layer brand shadow as Button's primary intent, but the auth
2567
- button's inset ring is measured at rgba(0,0,0,0.06) — a hairline, not the in-app button's rgba(255,255,255,0.07) highlight. */
2858
+ button's inset ring is measured at rgba(0,0,0,0.06) — a hairline, not
2859
+ the in-app button's rgba(255,255,255,0.07) highlight. */
2568
2860
  & .crisp-authshell-body .crisp-button { height: 36px; padding-inline: var(--crisp-space-3); border-radius: 10px; }
2569
2861
  & .crisp-authshell-body .crisp-button--intent_primary { box-shadow: inset 0 0 0 1px rgba(0,0,0,0.06), 0 2px 4px -2px rgba(38,109,240,0.12), 0 3px 6px -2px rgba(38,109,240,0.08); }
2570
-
2571
2862
  /* The preview pane drops out in JS on narrow viewports (measured: two
2572
2863
  panes at 1393px, one at 954px), so 1200px is a chosen threshold inside
2573
2864
  that bracket, not a measured value. A CONTAINER query, not a media
@@ -2598,19 +2889,29 @@
2598
2889
  & .crisp-board-density button[data-state="on"] { background: var(--crisp-bg-surface); color: var(--crisp-fg-primary); }
2599
2890
  & .crisp-board-row { display: flex; gap: var(--crisp-space-2); flex: 1; min-height: 0; box-sizing: border-box; padding: var(--crisp-space-3); overflow-x: auto; align-items: flex-start; }
2600
2891
  & .crisp-board-col { display: flex; flex-direction: column; width: 268px; flex-shrink: 0; max-height: 100%; box-sizing: border-box; transition: width 0.15s; }
2601
- /* #718: a collapsed column is a 52px vertical rail -- header only (dot + count stacked above a vertical-rl title), body/footer hidden entirely. Measured live: 52px width, 8px 0 padding on the header, 14px/600 title text. */
2892
+ /* #718: a collapsed column is a 52px vertical rail -- header only (dot
2893
+ + count stacked above a vertical-rl title), body/footer hidden
2894
+ entirely. Measured live: 52px width, 8px 0 padding on the header,
2895
+ 14px/600 title text. */
2602
2896
  & .crisp-board-col[data-density="collapse"] { width: 52px; }
2603
2897
  & .crisp-board-col[data-density="collapse"] .crisp-board-col-header { flex-direction: column; align-items: center; height: auto; padding: var(--crisp-space-2) 0; gap: var(--crisp-space-1); }
2604
- /* Measured stacking order (top to bottom): dot, count, THEN the vertical title -- reordered visually via `order`, not by changing the JSX/DOM order, which stays dot/title/count (reading order, unaffected by density). */
2898
+ /* Measured stacking order (top to bottom): dot, count, THEN the vertical
2899
+ title -- reordered visually via `order`, not by changing the JSX/DOM
2900
+ order, which stays dot/title/count (reading order, unaffected by
2901
+ density). */
2605
2902
  & .crisp-board-col[data-density="collapse"] .crisp-board-col-title { order: 1; writing-mode: vertical-rl; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); }
2606
2903
  & .crisp-board-col[data-density="collapse"] .crisp-board-col-count { order: 0; }
2607
2904
  & .crisp-board-col[data-density="collapse"] .crisp-board-col-body, & .crisp-board-col[data-density="collapse"] .crisp-board-col-footer { display: none; }
2608
2905
  & .crisp-board-col-header { display: flex; align-items: center; gap: var(--crisp-space-2); height: 36px; padding: 0 var(--crisp-space-1); flex-shrink: 0; }
2609
2906
  & .crisp-board-col-dot { width: 12px; height: 12px; border-radius: var(--crisp-radius-full); flex-shrink: 0; }
2610
2907
  & .crisp-board-col-title { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-primary); }
2611
- /* crisp#697: HANDOFF's #472 note claimed the count pill's color-mix(fgPrimary, 63%, transparent) reproduced the measured rgb(80,81,85) -- it doesn't; 63% of fgPrimary composited on white
2908
+ /* crisp#697: HANDOFF's #472 note claimed the count pill's
2909
+ color-mix(fgPrimary, 63%, transparent) reproduced the measured
2910
+ rgb(80,81,85) -- it doesn't; 63% of fgPrimary composited on white
2612
2911
  paints rgb(104,105,105), visibly lighter. Text now reads
2613
- fgNeutralSoft directly (the same rgb(80,81,85) token Badge's neutral tone uses, #696), and the ring the pill was also missing (inset 0 0 0 1px borderSubtle, measured rgb(238,239,241)) is added. */
2912
+ fgNeutralSoft directly (the same rgb(80,81,85) token Badge's neutral
2913
+ tone uses, #696), and the ring the pill was also missing
2914
+ (inset 0 0 0 1px borderSubtle, measured rgb(238,239,241)) is added. */
2614
2915
  & .crisp-board-col-count { display: inline-flex; align-items: center; justify-content: center; min-width: 16px; height: 16px; padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-sm); background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); box-shadow: inset 0 0 0 1px var(--crisp-border-subtle); font-size: var(--crisp-text-sm); line-height: 16px; font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-neutral-soft); }
2615
2916
  & .crisp-board-col-add { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; margin-inline-start: auto; padding: 0; border: none; border-radius: var(--crisp-radius-sm); background: none; color: var(--crisp-fg-muted); font-family: inherit; cursor: pointer; }
2616
2917
  & .crisp-board-col-add:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: var(--crisp-fg-primary); }
@@ -2619,7 +2920,11 @@
2619
2920
  & .crisp-board-newcard { display: flex; align-items: center; justify-content: center; gap: var(--crisp-space-1-5); height: 32px; flex-shrink: 0; border: none; border-radius: var(--crisp-radius-md); background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); color: var(--crisp-fg-muted); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); cursor: pointer; }
2620
2921
  & .crisp-board-newcard:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 7%, transparent); color: var(--crisp-fg-primary); }
2621
2922
  & .crisp-board-card { display: flex; flex-direction: column; gap: var(--crisp-space-2); box-sizing: border-box; padding: var(--crisp-space-2-5) var(--crisp-space-3); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-border-subtle); cursor: grab; touch-action: none; }
2622
- /* #718: a collapsed card is a single 40px row -- measured live and independent of `c.size.tableRow`/`tableHeader` (36/40px) even though it happens to match the latter; a plain literal, not a cross-reference to a token measured for a different surface. `renderCard`'s own second argument is what actually decides what a
2923
+ /* #718: a collapsed card is a single 40px row -- measured live and
2924
+ independent of `c.size.tableRow`/`tableHeader` (36/40px) even
2925
+ though it happens to match the latter; a plain literal, not a
2926
+ cross-reference to a token measured for a different surface.
2927
+ `renderCard`'s own second argument is what actually decides what a
2623
2928
  collapsed card SHOWS (Board never sees inside its opaque output);
2624
2929
  this only supplies the row-shaped box that content sits in. */
2625
2930
  & .crisp-board-card[data-density="collapse"] { flex-direction: row; align-items: center; height: 40px; padding: 0 var(--crisp-space-3); }
@@ -2627,11 +2932,15 @@
2627
2932
  & .crisp-board-card[data-dragging] { opacity: 0.4; }
2628
2933
  & .crisp-board-card:focus { outline: none; }
2629
2934
  & .crisp-board-card:focus-visible { outline: 2px solid var(--crisp-bg-brand-solid); outline-offset: 1px; }
2630
- /* #674: the pointer-events drag's own drag image -- the same card surface above, just pinned to the pointer instead of the flex flow. No shadow lift or scale: neither was measured against Attio, and this is the same card, momentarily somewhere else, not a new look. */
2935
+ /* #674: the pointer-events drag's own drag image -- the same card
2936
+ surface above, just pinned to the pointer instead of the flex flow.
2937
+ No shadow lift or scale: neither was measured against Attio, and this
2938
+ is the same card, momentarily somewhere else, not a new look. */
2631
2939
  & .crisp-board-ghost { position: fixed; z-index: 1; pointer-events: none; cursor: grabbing; }
2632
2940
  /* #604: a per-column calc footer, mirroring the table's own (measured
2633
2941
  live, Deals pipeline). Sits below the scrollable card body, not
2634
- inside it or pinned to it -- Attio's own is position: static, not sticky, so it's a plain flex-shrink:0 sibling here too. */
2942
+ inside it or pinned to it -- Attio's own is position: static, not
2943
+ sticky, so it's a plain flex-shrink:0 sibling here too. */
2635
2944
  & .crisp-board-col-footer { flex-shrink: 0; padding: var(--crisp-space-1) 0; }
2636
2945
  & .crisp-board-col-calc { display: flex; align-items: center; justify-content: center; gap: var(--crisp-space-1); width: 100%; height: 28px; padding: var(--crisp-space-1) var(--crisp-space-2); margin: 0; border: none; border-radius: var(--crisp-radius-md); background: none; font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); cursor: pointer; }
2637
2946
  & .crisp-board-col-calc:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); }
@@ -2640,7 +2949,9 @@
2640
2949
  & .crisp-board-col-calc-value { color: var(--crisp-fg-primary); font-variant-numeric: tabular-nums; }
2641
2950
  /* #718: "Add stage" -- measured live: 52×52px, transparent, a 1px
2642
2951
  dashed border (rgb(230,231,234), exactly `borderDropzone`/`line150`
2643
- -- the same dashed-border colour ImportDialog's own dropzone already uses, not borderSubtle/line100, a close but different value), radius.xl (16px), a centered 16px "+". */
2952
+ -- the same dashed-border colour ImportDialog's own dropzone already
2953
+ uses, not borderSubtle/line100, a close but different value), radius.xl
2954
+ (16px), a centered 16px "+". */
2644
2955
  & .crisp-board-addstage { display: inline-flex; align-items: center; justify-content: center; width: 52px; height: 52px; flex-shrink: 0; border: 1px dashed var(--crisp-border-dropzone); border-radius: var(--crisp-radius-xl); background: none; color: var(--crisp-fg-primary); font-family: inherit; cursor: pointer; }
2645
2956
  & .crisp-board-addstage:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); }
2646
2957
  /* announced, never drawn */
@@ -2664,28 +2975,32 @@
2664
2975
  & .crisp-command-card { display: flex; flex-direction: column; flex: 1; min-height: 0; box-sizing: border-box; border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); box-shadow: 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 8%, transparent); overflow: hidden; }
2665
2976
  & .crisp-command-control { display: flex; align-items: center; gap: var(--crisp-space-2-5); height: 52px; flex-shrink: 0; padding: 0 var(--crisp-space-4); border-bottom: 1px solid var(--crisp-border-subtle); color: var(--crisp-fg-muted); }
2666
2977
  & .crisp-command-control svg { flex-shrink: 0; }
2667
- /* The palette's label element. It exists for the assistive tree only: zag's combobox names the input through it and points the listbox's
2978
+ /* The palette's label element. It exists for the assistive tree only: zag's
2979
+ combobox names the input through it and points the listbox's
2668
2980
  aria-labelledby at it, and without it both were unnamed and the reference
2669
2981
  resolved to nothing (#225). Clipped rather than display:none, which would
2670
2982
  take it out of the tree it exists for. */
2671
- & .crisp-command-srlabel { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
2672
- & .crisp-command-input { flex: 1; min-width: 0; border: none; outline: none; background: transparent; font-family: var(--crisp-font-sans); font-size: var(--crisp-text-xl); color: var(--crisp-fg-primary); }
2673
- & .crisp-command-input::placeholder { color: var(--crisp-fg-muted); }
2674
- & .crisp-command-list { flex: 1; min-height: 0; overflow-y: auto; padding: var(--crisp-space-1) var(--crisp-space-2-5); display: flex; flex-direction: column; gap: var(--crisp-space-px); }
2675
- & .crisp-command-empty { padding: var(--crisp-space-8) var(--crisp-space-3); text-align: center; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
2676
- & .crisp-command-item { box-sizing: border-box; display: flex; align-items: center; gap: var(--crisp-space-2); height: 36px; padding-inline: var(--crisp-space-2-5); border-radius: var(--crisp-radius-xl); font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); cursor: pointer; }
2677
- & .crisp-command-item svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
2678
- & .crisp-command-item[data-highlighted] { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); }
2679
- & .crisp-command-item-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2680
- & .crisp-command-item-shortcut { margin-inline-start: auto; flex-shrink: 0; display: flex; align-items: center; gap: var(--crisp-space-0-5); }
2681
- & .crisp-command-group-label { padding: var(--crisp-space-2) var(--crisp-space-2-5) var(--crisp-space-1); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
2682
- & .crisp-command-list > .crisp-command-group-label:first-child { padding-top: var(--crisp-space-1); }
2683
- & .crisp-command-footer { display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; height: 44px; padding: 0 var(--crisp-space-2-5); border-top: 1px solid var(--crisp-border-subtle); }
2684
- & .crisp-command-footer-nav { display: flex; align-items: center; gap: var(--crisp-space-1-5); font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); }
2685
- & .crisp-command-footer-action { display: flex; align-items: center; gap: var(--crisp-space-2); height: 28px; max-width: 60%; padding: 0 var(--crisp-space-1) 0 var(--crisp-space-2-5); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-brand-solid); color: var(--crisp-fg-on-brand); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); cursor: pointer; }
2686
- & .crisp-command-footer-action:disabled { opacity: 0.5; cursor: default; }
2687
- & .crisp-command-footer-action-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2688
- & .crisp-command-footer-key { display: inline-flex; align-items: center; justify-content: center; min-width: 18px; height: 18px; padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-sm); background: rgba(255,255,255,0.22); font-size: var(--crisp-text-xs); }
2983
+ & .crisp-command-srlabel { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
2984
+ & .crisp-command-input { flex: 1; min-width: 0; border: none; outline: none; background: transparent; font-family: var(--crisp-font-sans); font-size: var(--crisp-text-xl); color: var(--crisp-fg-primary); }
2985
+ & .crisp-command-input::placeholder { color: var(--crisp-fg-muted); }
2986
+ & .crisp-command-list { flex: 1; min-height: 0; overflow-y: auto; padding: var(--crisp-space-1) var(--crisp-space-2-5); display: flex; flex-direction: column; gap: var(--crisp-space-px); }
2987
+ & .crisp-command-empty { padding: var(--crisp-space-8) var(--crisp-space-3); text-align: center; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
2988
+ & .crisp-command-item { box-sizing: border-box; display: flex; align-items: center; gap: var(--crisp-space-2); height: 36px; padding-inline: var(--crisp-space-2-5); border-radius: var(--crisp-radius-xl); font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); cursor: pointer; }
2989
+ & .crisp-command-item svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
2990
+ & .crisp-command-item[data-highlighted] { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); }
2991
+ & .crisp-command-item-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2992
+ & .crisp-command-item-shortcut { margin-inline-start: auto; flex-shrink: 0; display: flex; align-items: center; gap: var(--crisp-space-0-5); }
2993
+ & .crisp-command-group-label { padding: var(--crisp-space-2) var(--crisp-space-2-5) var(--crisp-space-1); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
2994
+ & .crisp-command-list > .crisp-command-group-label:first-child { padding-top: var(--crisp-space-1); }
2995
+ & .crisp-command-footer { display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; height: 44px; padding: 0 var(--crisp-space-2-5); border-top: 1px solid var(--crisp-border-subtle); }
2996
+ & .crisp-command-footer-nav { display: flex; align-items: center; gap: var(--crisp-space-1-5); font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); }
2997
+ /* your-moon/crisp#953: bgBrandContrast, not bgBrandSolid --
2998
+ the accent (#0298d9) only clears 3.23:1 with white; the darker sibling
2999
+ clears 4.5:1+, same fix already applied to Button/Badge/Avatar (#897). */
3000
+ & .crisp-command-footer-action { display: flex; align-items: center; gap: var(--crisp-space-2); height: 28px; max-width: 60%; padding: 0 var(--crisp-space-1) 0 var(--crisp-space-2-5); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-brand-contrast); color: var(--crisp-fg-on-brand); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); cursor: pointer; }
3001
+ & .crisp-command-footer-action:disabled { opacity: 0.5; cursor: default; }
3002
+ & .crisp-command-footer-action-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
3003
+ & .crisp-command-footer-key { display: inline-flex; align-items: center; justify-content: center; min-width: 18px; height: 18px; padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-sm); background: rgba(255,255,255,0.22); font-size: var(--crisp-text-xs); }
2689
3004
  }
2690
3005
 
2691
3006
  @media (prefers-reduced-motion: reduce) {
@@ -2728,7 +3043,7 @@
2728
3043
  [dir="rtl"] .crisp-comments-badge [data-rtl-flip] { transform: scaleX(-1); }
2729
3044
 
2730
3045
  .crisp-comments {
2731
- display: flex; flex-direction: column; box-sizing: border-box; width: 320px; height: 100%; background: var(--crisp-bg-surface); border-inline-start: 1px solid var(--crisp-border-subtle); font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
3046
+ display: flex; flex-direction: column; box-sizing: border-box; height: 100%; background: var(--crisp-bg-surface); font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
2732
3047
  & * { box-sizing: border-box; }
2733
3048
  & .crisp-comments-head { display: flex; align-items: center; gap: var(--crisp-space-1); flex-shrink: 0; height: 49px; padding: 0 var(--crisp-space-2) 0 var(--crisp-space-4); }
2734
3049
  & .crisp-comments-headbtn { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 28px; height: 28px; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-md); background: none; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); font-family: inherit; cursor: pointer; transition: background-color .12s, color .12s; }
@@ -2738,13 +3053,21 @@
2738
3053
  & .crisp-comments-breadcrumb { display: flex; align-items: center; gap: var(--crisp-space-1); font-size: 12px; font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2739
3054
  & .crisp-comments-body { flex: 1; min-height: 0; overflow-y: auto; padding: 0 var(--crisp-space-4); display: flex; flex-direction: column; gap: var(--crisp-space-4); }
2740
3055
  & .crisp-comments-body--pad-top { padding-top: var(--crisp-space-3); }
2741
- /* One comment row -- avatar 16px (Avatar size="sm"), 6px gap to the name/time line, 14px/600 name + 12px/500 63%-muted time, 14px/400 body two lines below. Hover actions fade in flush with the name line. .crisp-comments-row-head's own height: 20px (above) is a single
2742
- text-line's worth, on the same assumption .crisp-comments-title/ .crisp-comments-breadcrumb already act on higher up in this same recipe -- a name that doesn't fit wraps to two lines with nothing
3056
+ /* One comment row -- avatar 16px (Avatar size="sm"), 6px gap to the
3057
+ name/time line, 14px/600 name + 12px/500 63%-muted time, 14px/400 body
3058
+ two lines below. Hover actions fade in flush with the name line.
3059
+ .crisp-comments-row-head's own height: 20px (above) is a single
3060
+ text-line's worth, on the same assumption .crisp-comments-title/
3061
+ .crisp-comments-breadcrumb already act on higher up in this same
3062
+ recipe -- a name that doesn't fit wraps to two lines with nothing
2743
3063
  reserving room for the second one, overflowing straight into the
2744
- body text underneath (found live: this app's own dev-bypass author name, an email long enough to wrap, sat on top of the comment's own
3064
+ body text underneath (found live: this app's own dev-bypass author
3065
+ name, an email long enough to wrap, sat on top of the comment's own
2745
3066
  body text). .crisp-comments-author gets the identical nowrap-
2746
3067
  ellipsis treatment those two already have, plus min-width: 0 so a
2747
- flex item is actually allowed to shrink below its own text's natural width in the first place; .crisp-comments-time gets flex-shrink: 0 so it's the name that gives, never the timestamp -- the same "one
3068
+ flex item is actually allowed to shrink below its own text's natural
3069
+ width in the first place; .crisp-comments-time gets flex-shrink: 0
3070
+ so it's the name that gives, never the timestamp -- the same "one
2748
3071
  side shrinks, the other one doesn't" split PersonChip's own name/
2749
3072
  avatar pair already uses (crisp#668/#671). */
2750
3073
  & .crisp-comments-row { position: relative; display: flex; gap: var(--crisp-space-1-5); }
@@ -2754,16 +3077,13 @@
2754
3077
  & .crisp-comments-time { flex-shrink: 0; font-size: 12px; font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); white-space: nowrap; }
2755
3078
  & .crisp-comments-resolved-label { display: flex; align-items: center; gap: var(--crisp-space-1); margin-bottom: var(--crisp-space-1); font-size: 12px; font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
2756
3079
  & .crisp-comments-body-text { font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-regular); color: var(--crisp-fg-primary); white-space: pre-wrap; word-break: break-word; }
2757
-
2758
3080
  /* Hover actions -- measured: 4 buttons, 28x28, radius 8, flush
2759
3081
  side-by-side (0 gap), transparent by default, 16px icon at 55% ink. */
2760
3082
  & .crisp-comments-row-actions { position: absolute; top: 0; inset-inline-end: 0; display: flex; opacity: 0; pointer-events: none; background: var(--crisp-bg-surface); }
2761
- & .crisp-comments-row:hover .crisp-comments-row-actions,
2762
- & .crisp-comments-row-actions:focus-within { opacity: 1; pointer-events: auto; }
3083
+ & .crisp-comments-row:hover .crisp-comments-row-actions, & .crisp-comments-row-actions:focus-within { opacity: 1; pointer-events: auto; }
2763
3084
  & .crisp-comments-action { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-md); background: transparent; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); font-family: inherit; cursor: pointer; transition: background-color .12s, color .12s; }
2764
3085
  & .crisp-comments-action:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
2765
3086
  & .crisp-comments-action[data-active] { color: var(--crisp-bg-brand-solid); }
2766
-
2767
3087
  /* Reactions -- measured: 28px pill, radius 8, subtle 4% ink fill,
2768
3088
  padding 4px 8px 4px 6px, gap 4px between the emoji glyph and count. */
2769
3089
  & .crisp-comments-reactions { display: flex; flex-wrap: wrap; gap: var(--crisp-space-1); margin-top: var(--crisp-space-1-5); }
@@ -2772,12 +3092,9 @@
2772
3092
  & .crisp-comments-reaction-add { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border: none; border-radius: var(--crisp-radius-md); background: transparent; font-family: inherit; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); cursor: pointer; opacity: 0; transition: opacity .12s, background-color .12s; }
2773
3093
  & .crisp-comments-row:hover .crisp-comments-reaction-add { opacity: 1; }
2774
3094
  & .crisp-comments-reaction-add:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
2775
-
2776
3095
  /* Reply-in-thread view -- an indented block of replies below the root. */
2777
3096
  & .crisp-comments-replies { display: flex; flex-direction: column; gap: var(--crisp-space-4); margin-top: var(--crisp-space-3); padding-inline-start: var(--crisp-space-4); border-inline-start: 1px solid var(--crisp-border-subtle); }
2778
-
2779
3097
  & .crisp-comments-empty { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--crisp-space-1); padding: var(--crisp-space-6) var(--crisp-space-4); text-align: center; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); font-size: var(--crisp-text-lg); }
2780
-
2781
3098
  /* Composer -- measured: 14px/20px body text, 16px gap above the
2782
3099
  reply/mention icon row, Comment button 84x32 padding 6px 10px radius
2783
3100
  9 (= radius.lg) brand-solid fill, 14px/500 white label, opacity .4
@@ -2795,21 +3112,28 @@
2795
3112
  box-shadow, so this reuses that literal rather than re-deriving it.
2796
3113
  32px/radius.lg/6px-10px padding has no matching Button `size` yet
2797
3114
  (24/28/40 are the three that exist), so this stays hand-rolled instead
2798
- of stretching one of those. */
2799
- & .crisp-comments-post { display: inline-flex; align-items: center; justify-content: center; height: 32px; padding: 6px var(--crisp-space-2-5); border: none; border-radius: var(--crisp-radius-lg); background: var(--crisp-bg-brand-solid); box-shadow: inset 0 0 0 1px rgba(0,0,0,0.06), 0 2px 4px -2px rgba(38,109,240,0.12), 0 3px 6px -2px rgba(38,109,240,0.08); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-on-brand); cursor: pointer; opacity: 1; transition: opacity .12s, background-color .12s; }
2800
- & .crisp-comments-post:hover:not(:disabled) { background: var(--crisp-bg-brand-solid-pressed); }
3115
+ of stretching one of those. your-moon/crisp#953:
3116
+ bgBrandContrast, not bgBrandSolid -- the accent (#0298d9) only clears
3117
+ 3.23:1 with white; same fix already applied to Button/Badge/Avatar
3118
+ (#897). Hover is a brightness filter on the new fill, matching that
3119
+ PR's own Button primary treatment, not bgBrandSolidPressed. */
3120
+ & .crisp-comments-post { display: inline-flex; align-items: center; justify-content: center; height: 32px; padding: 6px var(--crisp-space-2-5); border: none; border-radius: var(--crisp-radius-lg); background: var(--crisp-bg-brand-contrast); box-shadow: inset 0 0 0 1px rgba(0,0,0,0.06), 0 2px 4px -2px rgba(38,109,240,0.12), 0 3px 6px -2px rgba(38,109,240,0.08); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-on-brand); cursor: pointer; opacity: 1; transition: opacity .12s, filter .12s; }
3121
+ & .crisp-comments-post:hover:not(:disabled) { filter: brightness(0.96); }
2801
3122
  & .crisp-comments-post:disabled { opacity: .4; cursor: default; }
2802
-
2803
3123
  /* Filter menu -- measured: 220px, radius 12 (radius.xl), 4px padding,
2804
3124
  elevation.floating -- a plain reuse of crisp's own Menu, so no rules
2805
- live here; kept as a comment for the record. */
2806
-
2807
- /* An inserted "@mention" chip in the composer -- same shape as
3125
+ live here; kept as a comment for the record. */ /* An inserted "@mention" chip in the composer -- same shape as
2808
3126
  RichTextEditor's own `.crisp-rte-mention` (crisp#428), reused here
2809
3127
  rather than re-derived since it's the same atomic-chip affordance. */
2810
3128
  & .crisp-comments-mention { display: inline; padding: 0 2px; border-radius: 4px; background: color-mix(in srgb, var(--crisp-bg-brand-solid) 12%, transparent); color: var(--crisp-bg-brand-solid); font-weight: var(--crisp-weight-medium); user-select: none; }
2811
3129
  [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
2812
3130
  }
3131
+ .crisp-comments--variant_drawer {
3132
+ width: 320px; border-inline-start: 1px solid var(--crisp-border-subtle);
3133
+ }
3134
+ .crisp-comments--variant_pane {
3135
+ width: 100%;
3136
+ }
2813
3137
 
2814
3138
  @media (prefers-reduced-motion: reduce) {
2815
3139
  .crisp-comments, .crisp-comments * {
@@ -2951,7 +3275,9 @@
2951
3275
  & .crisp-listpage-count { margin-inline-start: auto; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); white-space: nowrap; }
2952
3276
  /* #641: trailing (View settings / New) has to reach the toolbar's own
2953
3277
  inline-end edge on its own -- the only thing pushing it there used
2954
- to be .crisp-listpage-count's margin-inline-start: auto, which a consumer with no count badge (TasksTable, measured live: trailing sat right after leading, nowhere near the row's own right edge)
3278
+ to be .crisp-listpage-count's margin-inline-start: auto, which a
3279
+ consumer with no count badge (TasksTable, measured live: trailing
3280
+ sat right after leading, nowhere near the row's own right edge)
2955
3281
  never renders. A bare margin-inline-start: auto here is safely
2956
3282
  overridden by the more specific rule right below whenever count IS
2957
3283
  present, so the two cases don't fight -- count's own auto margin
@@ -2981,6 +3307,74 @@
2981
3307
  }
2982
3308
  }
2983
3309
 
3310
+ .crisp-memberssettings {
3311
+ & .crisp-memberssettings-page { display: flex; flex-direction: column; gap: var(--crisp-space-6); font-family: var(--crisp-font-sans); }
3312
+ & .crisp-memberssettings-heading { display: flex; flex-direction: column; gap: var(--crisp-space-0-5); }
3313
+ & .crisp-memberssettings-title { font-size: 24px; line-height: 28px; font-weight: var(--crisp-weight-semibold); letter-spacing: -0.48px; color: var(--crisp-fg-primary); margin: 0; }
3314
+ & .crisp-memberssettings-desc { font-size: var(--crisp-text-lg); line-height: 20px; color: var(--crisp-fg-muted); margin: 0; }
3315
+ /* Segmented Members/Teams switcher -- measured: 28px pills, radius 8,
3316
+ 4px gap, 6px icon-label gap, 14/500 label, selected bg bgSubtle +
3317
+ inset borderSubtle ring, unselected transparent + a flat 63%-opacity
3318
+ fgPrimary mix (measured rgba(0,0,0,.63) on the live page, not the
3319
+ solid fgMuted token — same alpha technique this file's table header
3320
+ already uses). */
3321
+ & .crisp-memberssettings-tabpair { display: inline-flex; align-items: center; gap: 4px; }
3322
+ & .crisp-memberssettings-tab { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: 28px; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: none; box-shadow: inset 0 0 0 1px transparent; font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); cursor: pointer; white-space: nowrap; transition: color .12s ease, background-color .12s ease, box-shadow .12s ease; }
3323
+ & .crisp-memberssettings-tab svg { flex-shrink: 0; }
3324
+ & .crisp-memberssettings-tab:hover:not([data-selected]) { color: var(--crisp-fg-primary); }
3325
+ & .crisp-memberssettings-tab[data-selected] { color: var(--crisp-fg-primary); background: var(--crisp-bg-subtle); box-shadow: inset 0 0 0 1px var(--crisp-border-subtle); }
3326
+ & .crisp-memberssettings-tab:focus-visible { outline: 2px solid var(--crisp-bg-brand-solid); outline-offset: 1px; }
3327
+ /* Toolbar row: search grows, Filter + Invite sit trailing -- measured
3328
+ 32px height on all three, search radius 9 with a 6% ink ring,
3329
+ Filter/Invite radius 9 with the button drop-shadow pairing. */
3330
+ & .crisp-memberssettings-toolbar { display: flex; align-items: center; gap: var(--crisp-space-2); }
3331
+ & .crisp-memberssettings-search { display: flex; align-items: center; flex: 1; min-width: 0; gap: var(--crisp-space-1-5); height: 32px; padding: 0 var(--crisp-space-2-5) 0 var(--crisp-space-2); border-radius: 9px; background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: var(--crisp-fg-muted); }
3332
+ & .crisp-memberssettings-search:focus-within { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
3333
+ & .crisp-memberssettings-search input { flex: 1; min-width: 0; border: none; outline: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
3334
+ & .crisp-memberssettings-search input::placeholder { color: var(--crisp-fg-muted); }
3335
+ & .crisp-memberssettings-filterbtn { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: 32px; padding: 6px 10px 6px 8px; border: none; border-radius: 9px; background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px transparent, 0 0 2px 0 rgba(28,40,64,.18), 0 1px 3px 0 rgba(0,0,0,.04); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); cursor: pointer; white-space: nowrap; }
3336
+ & .crisp-memberssettings-filterbtn:hover { background: var(--crisp-bg-raised); }
3337
+ /* your-moon/crisp#979: bgBrandContrast, not bgBrandSolid -- the accent
3338
+ (#0298d9) only clears 3.23:1 with white; the darker sibling clears
3339
+ 4.5:1+, same fix already applied to Button/Badge/Avatar (#897) and
3340
+ TableToolbar/Dialog/Command/Comments/AuthShell (#953/#961), which
3341
+ explicitly flagged this button as found-not-fixed. */
3342
+ & .crisp-memberssettings-invitebtn { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: 32px; padding: 6px 10px 6px 8px; border: none; border-radius: 9px; background: var(--crisp-bg-brand-contrast); box-shadow: inset 0 0 0 1px rgba(0,0,0,.06), 0 2px 4px -2px color-mix(in srgb, var(--crisp-bg-brand-contrast) 12%, transparent), 0 3px 6px -2px color-mix(in srgb, var(--crisp-bg-brand-contrast) 8%, transparent); color: var(--crisp-fg-on-brand); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); cursor: pointer; white-space: nowrap; }
3343
+ & .crisp-memberssettings-invitebtn:hover { filter: brightness(0.95); }
3344
+ /* Table wrapper -- measured card: radius 12, 1px borderSubtle, 40px
3345
+ rows, header label 12/500/63%-opacity fgPrimary. */
3346
+ & .crisp-memberssettings-table-wrap { border-radius: 12px; border: 1px solid var(--crisp-border-subtle); overflow: hidden; }
3347
+ & .crisp-memberssettings-table { width: 100%; border-collapse: collapse; font-family: inherit; }
3348
+ & .crisp-memberssettings-table th { height: 32px; padding: 0 var(--crisp-space-4); text-align: start; font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); border-bottom: 1px solid var(--crisp-border-subtle); background: var(--crisp-bg-surface); }
3349
+ & .crisp-memberssettings-table td { height: 40px; padding: 0 var(--crisp-space-4); font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); border-bottom: 1px solid var(--crisp-border-subtle); }
3350
+ & .crisp-memberssettings-table tr:last-child td { border-bottom: none; }
3351
+ & .crisp-memberssettings-user { display: flex; align-items: center; gap: var(--crisp-space-2); }
3352
+ & .crisp-memberssettings-avatarimg { width: 24px; height: 24px; border-radius: var(--crisp-radius-full); object-fit: cover; }
3353
+ /* Name + email sit on ONE line, not stacked -- confirmed live: both spans
3354
+ share the same y and the same 14px size, only the email's colour
3355
+ differs (a flat 63%-opacity fgPrimary mix, not a smaller muted font). */
3356
+ & .crisp-memberssettings-usermeta { display: flex; align-items: baseline; gap: var(--crisp-space-2); min-width: 0; }
3357
+ & .crisp-memberssettings-usermeta > span:first-child { flex-shrink: 0; }
3358
+ & .crisp-memberssettings-useremail { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-regular); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
3359
+ & .crisp-memberssettings-kebab { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; margin-inline-start: auto; border: none; border-radius: var(--crisp-radius-sm); background: none; font-family: inherit; color: var(--crisp-fg-muted); cursor: pointer; }
3360
+ & .crisp-memberssettings-kebab:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
3361
+ & .crisp-memberssettings-teamspanel { display: flex; align-items: center; justify-content: center; padding: var(--crisp-space-8) 0; color: var(--crisp-fg-muted); font-size: var(--crisp-text-lg); }
3362
+ /* Invite dialog fields -- measured 708x386 dialog, radius 16; the
3363
+ "Invite as"/"Add to team" selects are 32/34px with a flat 1px
3364
+ 6%-ink border and 9/10px radius respectively (Attio's own two
3365
+ selects don't share one radius). */
3366
+ & .crisp-memberssettings-invitefield { display: flex; flex-direction: column; gap: var(--crisp-space-1-5); }
3367
+ & .crisp-memberssettings-invitelabel { font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
3368
+ }
3369
+
3370
+ @media (prefers-reduced-motion: reduce) {
3371
+ .crisp-memberssettings, .crisp-memberssettings * {
3372
+ animation-duration: 0.01ms !important;
3373
+ animation-iteration-count: 1 !important;
3374
+ transition-duration: 0.01ms !important;
3375
+ }
3376
+ }
3377
+
2984
3378
 
2985
3379
  .crisp-navitem-shortcut { display: flex; align-items: baseline; justify-content: space-between; gap: var(--crisp-space-3); }
2986
3380
  .crisp-navitem-shortcut-desc { font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); line-height: 16px; white-space: nowrap; }
@@ -3004,6 +3398,29 @@
3004
3398
  }
3005
3399
  }
3006
3400
 
3401
+ .crisp-notificationssettings {
3402
+ & .crisp-notificationssettings-page { display: flex; flex-direction: column; gap: var(--crisp-space-6); font-family: var(--crisp-font-sans); }
3403
+ & .crisp-notificationssettings-heading { display: flex; flex-direction: column; gap: var(--crisp-space-0-5); }
3404
+ & .crisp-notificationssettings-title { font-size: 24px; line-height: 28px; font-weight: var(--crisp-weight-semibold); letter-spacing: -0.48px; color: var(--crisp-fg-primary); margin: 0; }
3405
+ & .crisp-notificationssettings-desc { font-size: var(--crisp-text-lg); line-height: 20px; color: var(--crisp-fg-muted); margin: 0; }
3406
+ & .crisp-notificationssettings-section { display: flex; flex-direction: column; gap: var(--crisp-space-4); }
3407
+ & .crisp-notificationssettings-sectiontitle { font-size: 16px; line-height: 20px; font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-primary); margin: 0; }
3408
+ /* Daily digest card: one bordered 12px-radius row. */
3409
+ & .crisp-notificationssettings-digestcard { display: flex; align-items: center; justify-content: space-between; gap: var(--crisp-space-6); padding: var(--crisp-space-2) var(--crisp-space-3); border: 1px solid var(--crisp-border-subtle); border-radius: 12px; background: var(--crisp-bg-surface); }
3410
+ & .crisp-notificationssettings-digesttext { display: flex; flex-direction: column; gap: var(--crisp-space-1); }
3411
+ & .crisp-notificationssettings-rowtitle { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
3412
+ & .crisp-notificationssettings-rowdesc { font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
3413
+ /* Collaboration matrix: a bordered card, header row + N body rows. */
3414
+ & .crisp-notificationssettings-matrix { border: 1px solid var(--crisp-border-subtle); border-radius: 12px; overflow: hidden; background: var(--crisp-bg-surface); }
3415
+ & .crisp-notificationssettings-matrixhead { display: flex; align-items: center; gap: var(--crisp-space-3); padding: 0 var(--crisp-space-4); height: 40px; border-bottom: 1px solid var(--crisp-border-subtle); }
3416
+ & .crisp-notificationssettings-matrixhead-about { display: flex; align-items: center; gap: var(--crisp-space-1-5); flex: 1; min-width: 0; font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
3417
+ & .crisp-notificationssettings-matrixhead-col { display: flex; align-items: center; justify-content: center; gap: var(--crisp-space-1-5); width: 64px; flex-shrink: 0; font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
3418
+ & .crisp-notificationssettings-matrixrow { display: flex; align-items: center; gap: var(--crisp-space-3); padding: var(--crisp-space-2) var(--crisp-space-4); min-height: 52px; }
3419
+ & .crisp-notificationssettings-matrixrow + .crisp-notificationssettings-matrixrow { border-top: 1px solid var(--crisp-border-subtle); }
3420
+ & .crisp-notificationssettings-matrixrow-about { display: flex; flex-direction: column; gap: var(--crisp-space-1); flex: 1; min-width: 0; }
3421
+ & .crisp-notificationssettings-matrixrow-col { display: flex; align-items: center; justify-content: center; width: 64px; flex-shrink: 0; }
3422
+ }
3423
+
3007
3424
  .crisp-objectsettings {
3008
3425
  & .crisp-objectsettings-page { display: flex; flex-direction: column; gap: var(--crisp-space-6); }
3009
3426
  & .crisp-objectsettings-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--crisp-space-4); }
@@ -3018,22 +3435,22 @@
3018
3435
  & .crisp-objectsettings-filterbtn { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: var(--crisp-size-menuRow); padding: 0 var(--crisp-space-2-5) 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-lg); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-border-default); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); cursor: pointer; white-space: nowrap; }
3019
3436
  & .crisp-objectsettings-filterbtn:hover { background: var(--crisp-bg-raised); }
3020
3437
  /* Object / attribute icon tile — Attio's own squircle (measured
3021
- border-radius: 30%, not a scale token: it's a proportional radius tied to the tile's own size, unlike every other radius crisp uses,
3438
+ border-radius: 30%, not a scale token: it's a proportional radius
3439
+ tied to the tile's own size, unlike every other radius crisp uses,
3022
3440
  which is a fixed px). */
3023
3441
  & .crisp-objectsettings-icon { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 16px; height: 16px; border-radius: 30%; color: var(--crisp-fg-on-brand); }
3024
3442
  & .crisp-objectsettings-icon--lg { width: 48px; height: 48px; border-radius: 16px; }
3025
-
3026
3443
  /* "Standard"/"Custom" type badge and the muted "No records" cell —
3027
3444
  plain text, not a Badge pill (measured: transparent background). */
3028
3445
  & .crisp-objectsettings-typebadge { font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-neutral-soft); }
3029
3446
  & .crisp-objectsettings-muted { color: var(--crisp-fg-muted); }
3030
-
3031
3447
  /* Attribute-type option row (icon + label + optional trailing badge),
3032
3448
  composed as a Select option's `label` ReactNode. */
3033
3449
  & .crisp-objectsettings-typeoption { display: flex; align-items: center; gap: var(--crisp-space-2); width: 100%; min-width: 0; }
3034
3450
  & .crisp-objectsettings-typeoption-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
3035
3451
  & .crisp-objectsettings-typeoption-badge { flex-shrink: 0; font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-brand-soft); }
3036
- /* Create-attribute dialog's AI-autofill row and the submit button's ⌘↵ hint (measured off the same dialog, same shape as Dialog's own
3452
+ /* Create-attribute dialog's AI-autofill row and the submit button's
3453
+ ⌘↵ hint (measured off the same dialog, same shape as Dialog's own
3037
3454
  .crisp-dialog-submit-key, kept local rather than reaching into
3038
3455
  another component's scoped classes). */
3039
3456
  & .crisp-objectsettings-aiautofill { display: flex; flex-direction: column; gap: var(--crisp-space-1-5); }
@@ -3089,6 +3506,21 @@
3089
3506
  display: flex; flex-direction: column; height: 100%; min-height: 0; background: var(--crisp-bg-surface); font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; color: var(--crisp-fg-primary);
3090
3507
  & * { box-sizing: border-box; }
3091
3508
  & .crisp-recordpage-topbar { display: flex; align-items: center; gap: var(--crisp-space-2); height: 44px; flex-shrink: 0; padding: 0 var(--crisp-space-3); border-bottom: 1px solid var(--crisp-border-subtle); }
3509
+ /* #624: expanding a peek to its full record keeps a hybrid header
3510
+ (Close/Previous/Next/counter) instead of reverting to the plain
3511
+ breadcrumb above -- measured live on a real expanded record: 49px
3512
+ tall (not 44px), 8px/10px padding, same borderSubtle hairline. Same
3513
+ tokens as product/recordpanel's own already-shipped toolbar, not
3514
+ new values -- this is the same row, reused in a second component. */
3515
+ & .crisp-recordpage-topbar[data-peek] { height: 49px; padding: var(--crisp-space-2) var(--crisp-space-2-5); }
3516
+ & .crisp-recordpage-peekbar { display: flex; align-items: center; }
3517
+ & .crisp-recordpage-peek-iconbtn { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; flex-shrink: 0; padding: 0; border: none; border-radius: var(--crisp-radius-md); background: transparent; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); font-family: inherit; cursor: pointer; transition: background-color .12s; }
3518
+ & .crisp-recordpage-peek-iconbtn:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
3519
+ & .crisp-recordpage-peek-pager { display: flex; align-items: center; gap: var(--crisp-space-1-5); margin-inline-start: var(--crisp-space-3); }
3520
+ & .crisp-recordpage-peek-navbtn { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; flex-shrink: 0; padding: 0; border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04); color: var(--crisp-fg-primary); font-family: inherit; cursor: pointer; }
3521
+ & .crisp-recordpage-peek-navbtn:hover:not(:disabled) { background: var(--crisp-bg-raised); }
3522
+ & .crisp-recordpage-peek-navbtn:disabled { opacity: 0.4; cursor: not-allowed; }
3523
+ & .crisp-recordpage-peek-counter { margin-inline-start: var(--crisp-space-3); font-size: 12px; font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
3092
3524
  & .crisp-recordpage-crumbs { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; }
3093
3525
  & .crisp-recordpage-crumb { display: inline-flex; align-items: center; height: 26px; padding: 0 var(--crisp-space-1-5); margin: 0 calc(-1 * var(--crisp-space-1-5)) 0 0; border: none; border-radius: var(--crisp-radius-md); background: none; font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); cursor: pointer; }
3094
3526
  & .crisp-recordpage-crumb:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
@@ -3106,6 +3538,17 @@
3106
3538
  & .crisp-recordpage-resizer::before { content: ""; position: absolute; top: 0; bottom: 0; inset-inline-start: -4px; width: 9px; }
3107
3539
  & .crisp-recordpage-resizer::after { content: ""; position: absolute; top: 0; bottom: 0; inset-inline-start: -1px; width: 2px; background: transparent; transition: background-color .12s ease; }
3108
3540
  & .crisp-recordpage-resizer:hover::after, & .crisp-recordpage-resizer[data-active]::after { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 55%, transparent); }
3541
+ /* #930: RecordDetails' own standalone resize handle -- same visible
3542
+ hit-slop/line-highlight shape as .crisp-recordpage-resizer just above,
3543
+ just absolutely positioned at the trailing edge instead of a 0-width
3544
+ flex sibling, since RecordDetails always returns a single root (the
3545
+ same internal-strip placement RecordPanel's own resize handle uses,
3546
+ mirrored to the opposite edge -- this column is leading, RecordPanel
3547
+ is trailing). */
3548
+ & .crisp-recordpage-section[data-resizable] { position: relative; }
3549
+ & .crisp-recordpage-detailresize { position: absolute; top: 0; bottom: 0; inset-inline-end: -4px; width: 9px; cursor: col-resize; z-index: 5; touch-action: none; }
3550
+ & .crisp-recordpage-detailresize::after { content: ""; position: absolute; top: 0; bottom: 0; inset-inline-end: 3px; width: 2px; background: transparent; transition: background-color .12s ease; }
3551
+ & .crisp-recordpage-detailresize:hover::after, & .crisp-recordpage-detailresize[data-active]::after { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 55%, transparent); }
3109
3552
  & .crisp-recordpage-entity { display: flex; align-items: center; gap: var(--crisp-space-2); padding: var(--crisp-space-1) var(--crisp-space-1) var(--crisp-space-2); }
3110
3553
  & .crisp-recordpage-entity-logo { width: 32px; height: 32px; border-radius: 30%; flex-shrink: 0; object-fit: cover; background: var(--crisp-bg-raised); }
3111
3554
  & .crisp-recordpage-entity-name { flex: 0 1 auto; min-width: 0; font-size: var(--crisp-text-xl); font-weight: var(--crisp-weight-semibold); letter-spacing: -0.3px; color: var(--crisp-fg-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@@ -3121,7 +3564,8 @@
3121
3564
  & .crisp-recordpage-quickbtn:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
3122
3565
  & .crisp-recordpage-section { padding-top: var(--crisp-space-2); }
3123
3566
  & .crisp-recordpage-section-title { font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); letter-spacing: -0.12px; color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); padding: var(--crisp-space-1); }
3124
- /* main-area section headers (Highlights / Activity / Emails / Notes / Tasks) are 14px; the left column's (Record Details / Lists) stay 12px. */
3567
+ /* main-area section headers (Highlights / Activity / Emails / Notes / Tasks)
3568
+ are 14px; the left column's (Record Details / Lists) stay 12px. */
3125
3569
  & .crisp-recordpage-main .crisp-recordpage-section-title { font-size: var(--crisp-text-lg); }
3126
3570
  & .crisp-recordpage-detailhead { display: flex; align-items: center; gap: var(--crisp-space-0-5); margin-bottom: var(--crisp-space-3); padding-inline-end: var(--crisp-space-0-5); }
3127
3571
  & .crisp-recordpage-collapse { display: inline-flex; align-items: center; gap: 2px; flex: 1; min-width: 0; margin: 0; border: none; background: none; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-md); font-family: inherit; font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); letter-spacing: -0.12px; color: var(--crisp-fg-muted); cursor: pointer; text-align: start; }
@@ -3136,7 +3580,8 @@
3136
3580
  & .crisp-recordpage-noresults { padding: var(--crisp-space-1-5) var(--crisp-space-1); font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
3137
3581
  /* A caller-supplied `sections` grouping (#528) is not an Attio-measured
3138
3582
  shape (its own Details column is flat) -- this heading reuses
3139
- .crisp-recordpage-section-title's own type treatment verbatim, just spaced from the group above it. */
3583
+ .crisp-recordpage-section-title's own type treatment verbatim, just
3584
+ spaced from the group above it. */
3140
3585
  & .crisp-recordpage-fieldsection-title { margin-top: var(--crisp-space-3); }
3141
3586
  & .crisp-recordpage-morevalues { display: inline-flex; align-items: center; padding: 0; margin: 0; border: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-muted); cursor: pointer; }
3142
3587
  & .crisp-recordpage-morevalues:hover { color: var(--crisp-fg-primary); }
@@ -3152,25 +3597,38 @@
3152
3597
  & .crisp-recordpage-field-label-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
3153
3598
  & .crisp-recordpage-field-value { flex: 1; min-width: 0; display: flex; flex-wrap: wrap; align-items: center; gap: var(--crisp-space-1); padding-top: 2px; line-height: 20px; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
3154
3599
  & .crisp-recordpage-field-value[data-editable] { cursor: pointer; }
3155
- /* the click-to-edit trigger is a real <button> (keyboard-operable), reset back to plain inline text and made a flex-wrap container of its own -- FieldValue can render several chips (tags), which still need to wrap the way they did before this had a button around them at all. It wraps only FieldValue, never the error slot beside it (see the component's own comment) -- NOT `display: contents`, which
3600
+ /* the click-to-edit trigger is a real <button> (keyboard-operable),
3601
+ reset back to plain inline text and made a flex-wrap container of its
3602
+ own -- FieldValue can render several chips (tags), which still need
3603
+ to wrap the way they did before this had a button around them at
3604
+ all. It wraps only FieldValue, never the error slot beside it (see
3605
+ the component's own comment) -- NOT `display: contents`, which
3156
3606
  drops a button's accessible semantics in some engines. */
3157
3607
  & .crisp-recordpage-edit-trigger { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--crisp-space-1); margin: 0; padding: 0; border: none; background: none; text-align: start; font: inherit; color: inherit; cursor: pointer; }
3158
3608
  & .crisp-recordpage-empty { color: var(--crisp-fg-muted); }
3159
3609
  /* #431: measured live on app.attio.com's record page Details column --
3160
- clicking a field's value opens a plain, borderless field exactly filling the value's own box (no border, no background, no
3610
+ clicking a field's value opens a plain, borderless field exactly
3611
+ filling the value's own box (no border, no background, no
3161
3612
  box-shadow), same font as the value it replaces (hence `font:
3162
- inherit`, not a restated size/weight). A field row's own hover tint above already carries the "this is interactive" affordance; the pending/error treatment isn't Attio-measured -- it reuses the same
3163
- tokens DataTable's identical states already use (opacity 0.5, `bgDanger`), per #385/#430. */
3613
+ inherit`, not a restated size/weight). A field row's own hover tint
3614
+ above already carries the "this is interactive" affordance; the
3615
+ pending/error treatment isn't Attio-measured -- it reuses the same
3616
+ tokens DataTable's identical states already use (opacity 0.5,
3617
+ `bgDanger`), per #385/#430. */
3164
3618
  & .crisp-recordpage-edit { display: block; width: 100%; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-sm); background: none; box-shadow: none; outline: none; font: inherit; color: inherit; }
3165
- /* not Attio-measured (its own field shows no focus ring either, caret only) -- kept for real keyboard users, the same brand ring every other crisp field already shows on focus (DataTable's identical
3619
+ /* not Attio-measured (its own field shows no focus ring either, caret
3620
+ only) -- kept for real keyboard users, the same brand ring every
3621
+ other crisp field already shows on focus (DataTable's identical
3166
3622
  .crisp-datatable-edit:focus, e.g.). */
3167
3623
  & .crisp-recordpage-edit:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
3168
3624
  & .crisp-recordpage-edit:disabled { opacity: 0.5; cursor: not-allowed; }
3169
3625
  & .crisp-recordpage-edit-pending { flex-shrink: 0; color: var(--crisp-fg-muted); }
3170
3626
  & .crisp-recordpage-edit-error { flex-basis: 100%; margin-top: var(--crisp-space-0-5); font-size: var(--crisp-text-sm); color: var(--crisp-bg-danger); }
3171
- /* #457: an `editor: "select"` field's at-rest chip -- same shape as RecordsTable's own `.crisp-recordstable-status[-dot]` (already
3627
+ /* #457: an `editor: "select"` field's at-rest chip -- same shape as
3628
+ RecordsTable's own `.crisp-recordstable-status[-dot]` (already
3172
3629
  measured against Attio there), but its own classes: that pair is
3173
- scoped under `.crisp-recordstable`'s own recipe root, so reusing the classnames verbatim here would resolve to nothing. */
3630
+ scoped under `.crisp-recordstable`'s own recipe root, so reusing the
3631
+ classnames verbatim here would resolve to nothing. */
3174
3632
  & .crisp-recordpage-status { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; }
3175
3633
  & .crisp-recordpage-status-dot { flex-shrink: 0; width: 8px; height: 8px; border-radius: var(--crisp-radius-full); }
3176
3634
  & .crisp-recordpage-edit-picker { display: flex; align-items: center; gap: var(--crisp-space-1); width: 100%; }
@@ -3193,7 +3651,6 @@
3193
3651
  & .crisp-recordpage-hl-label { font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); margin-bottom: var(--crisp-space-2-5); padding-inline-end: var(--crisp-space-4); }
3194
3652
  & .crisp-recordpage-hl-value { display: flex; flex-wrap: wrap; align-items: center; gap: var(--crisp-space-1); font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
3195
3653
  & .crisp-recordpage-hl-value--empty { color: var(--crisp-fg-muted); font-weight: var(--crisp-weight-regular); }
3196
-
3197
3654
  & .crisp-recordpage-feed { display: flex; flex-direction: column; }
3198
3655
  & .crisp-recordpage-feed-item { display: flex; align-items: flex-start; gap: var(--crisp-space-2-5); padding: var(--crisp-space-2) 0; }
3199
3656
  & .crisp-recordpage-feed-dot { position: relative; display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; flex-shrink: 0; border-radius: var(--crisp-radius-full); background: var(--crisp-bg-raised); color: var(--crisp-fg-muted); }
@@ -3202,7 +3659,6 @@
3202
3659
  & .crisp-recordpage-feed-time { font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); margin-top: 2px; }
3203
3660
  & .crisp-recordpage-feed-more { align-self: flex-start; margin-top: var(--crisp-space-1); border: none; background: none; padding: var(--crisp-space-1) 0; color: var(--crisp-bg-brand-solid); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); cursor: pointer; }
3204
3661
  & .crisp-recordpage-feed-more:hover { text-decoration: underline; }
3205
-
3206
3662
  & .crisp-recordpage-section-head { display: flex; align-items: center; gap: var(--crisp-space-1-5); padding: var(--crisp-space-1); }
3207
3663
  & .crisp-recordpage-section-head .crisp-recordpage-section-title { padding: 0; }
3208
3664
  & .crisp-recordpage-section-count { display: inline-flex; align-items: center; justify-content: center; min-width: 16px; height: 16px; padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-sm); background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); font-size: var(--crisp-text-xs); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
@@ -3210,7 +3666,6 @@
3210
3666
  & .crisp-recordpage-addbtn { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: 26px; padding: 0 var(--crisp-space-1-5); margin: 0; border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); cursor: pointer; }
3211
3667
  & .crisp-recordpage-addbtn:hover { background: var(--crisp-bg-raised); }
3212
3668
  & .crisp-recordpage-addbtn svg { flex-shrink: 0; }
3213
-
3214
3669
  & .crisp-recordpage-composer { display: flex; flex-direction: column; gap: var(--crisp-space-2); padding: var(--crisp-space-2); border: 1px solid var(--crisp-border-default); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); margin-bottom: var(--crisp-space-2); }
3215
3670
  & .crisp-recordpage-composer textarea { width: 100%; min-height: 56px; resize: vertical; border: none; outline: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); }
3216
3671
  & .crisp-recordpage-composer textarea:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); border-radius: var(--crisp-radius-md); }
@@ -3220,23 +3675,52 @@
3220
3675
  & .crisp-recordpage-btn:hover { background: var(--crisp-bg-raised); }
3221
3676
  & .crisp-recordpage-btn--primary { box-shadow: none; background: var(--crisp-bg-brand-solid); color: var(--crisp-fg-on-brand); }
3222
3677
  & .crisp-recordpage-btn--primary:disabled { opacity: 0.5; cursor: not-allowed; }
3223
-
3224
- & .crisp-recordpage-note { display: flex; gap: var(--crisp-space-2-5); padding: var(--crisp-space-2-5) 0; border-top: 1px solid var(--crisp-border-subtle); }
3225
- & .crisp-recordpage-note-body { flex: 1; min-width: 0; }
3226
- & .crisp-recordpage-note-head { display: flex; align-items: center; gap: var(--crisp-space-1-5); margin-bottom: var(--crisp-space-0-5); }
3227
- & .crisp-recordpage-note-author { font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
3228
- & .crisp-recordpage-note-time { font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); }
3229
- & .crisp-recordpage-note-text { font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); white-space: pre-wrap; word-break: break-word; }
3230
-
3678
+ /* #871: one bordered card per note (border-radius.xl, borderSubtle),
3679
+ not a flush divider-list row -- measured live on a real populated
3680
+ note: the whole row is flex space-between, avatar+title+body on the
3681
+ left (its own flex, gap.2), the relative time alone on the right.
3682
+ The real DOM nests an inner clickable button (padding 6/8, radius.md)
3683
+ inside an outer card (padding 4px, radius.xl) -- collapsed into this
3684
+ one non-interactive div's own combined padding (2.5/3) since crisp's
3685
+ NotesSection has no click-to-open behaviour to justify the extra
3686
+ layer. The 6px gap between stacked cards is NOT independently
3687
+ measured (this workspace only ever had one real note to check) --
3688
+ a judgement call off the existing space scale, not a live value. */
3689
+ & .crisp-recordpage-note { display: flex; align-items: center; justify-content: space-between; gap: var(--crisp-space-3); padding: var(--crisp-space-2-5) var(--crisp-space-3); border: 1px solid var(--crisp-border-subtle); border-radius: var(--crisp-radius-xl); }
3690
+ & .crisp-recordpage-note + .crisp-recordpage-note { margin-top: var(--crisp-space-1-5); }
3691
+ & .crisp-recordpage-note-left { display: flex; align-items: center; gap: var(--crisp-space-2); min-width: 0; overflow: hidden; }
3692
+ & .crisp-recordpage-note-title { flex-shrink: 0; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
3693
+ & .crisp-recordpage-note-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
3694
+ & .crisp-recordpage-note-time { flex-shrink: 0; font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 50%, transparent); }
3231
3695
  & .crisp-recordpage-task { display: flex; align-items: flex-start; gap: var(--crisp-space-2); padding: var(--crisp-space-1-5) 0; border-top: 1px solid var(--crisp-border-subtle); }
3232
3696
  & .crisp-recordpage-task-text { flex: 1; min-width: 0; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); padding-top: 1px; }
3233
3697
  & .crisp-recordpage-task[data-done] .crisp-recordpage-task-text { text-decoration: line-through; color: var(--crisp-fg-muted); }
3234
-
3235
3698
  & .crisp-recordpage-emptycard { display: flex; flex-direction: column; align-items: center; gap: var(--crisp-space-1-5); padding: var(--crisp-space-6) var(--crisp-space-3); border: 1px dashed var(--crisp-border-default); border-radius: var(--crisp-radius-xl); color: var(--crisp-fg-muted); font-size: var(--crisp-text-md); text-align: center; }
3236
3699
  & .crisp-recordpage-emptycard svg { color: var(--crisp-fg-muted); opacity: 0.7; }
3237
-
3700
+ /* #622: the Activity tab's own toolbar -- "Filters" + "+ Add meeting",
3701
+ both the same measured button chrome as the topbar's own action
3702
+ buttons (28px, e.field shadow, 14/500 label). */
3703
+ & .crisp-recordpage-tabtoolbar { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); }
3704
+ /* #622: Files tab's empty state -- measured live on Attio's own record
3705
+ page (a company with no files uploaded). The 138x138 dotted-grid
3706
+ backdrop is the same measured tile EmptyState's records variant uses
3707
+ (crisp/#603), duplicated here rather than composed through that
3708
+ component because this surface's title (16/600) and body (14/500,
3709
+ 63%-mixed fgPrimary, not the flat fgMuted EmptyState uses) measure to
3710
+ their own values, matching neither of that component's existing
3711
+ variants. */
3712
+ & .crisp-recordpage-panel--center { flex: 1; align-items: center; justify-content: center; }
3713
+ & .crisp-recordpage-illustrated { display: flex; flex-direction: column; align-items: center; gap: var(--crisp-space-1-5); text-align: center; }
3714
+ & .crisp-recordpage-illustrated-icon { display: flex; align-items: center; justify-content: center; width: 138px; height: 138px; margin-bottom: var(--crisp-space-2); color: color-mix(in srgb, var(--crisp-fg-primary) 22%, transparent); background: radial-gradient(circle, color-mix(in srgb, var(--crisp-fg-primary) 12%, transparent) 1.2px, transparent 1.2px) 0 0 / 16px 16px; }
3715
+ & .crisp-recordpage-illustrated-title { font-size: var(--crisp-text-xl); font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-primary); }
3716
+ /* #988: the Calls tab's own gated state measures a larger 20/24 title
3717
+ than Files/Deals' 16/600 -- Attio's own "empty view" pattern (the
3718
+ same 20/600 EmptyState's page/records variants already carry, not a
3719
+ one-off). */
3720
+ & .crisp-recordpage-illustrated-title--lg { font-size: 20px; line-height: 24px; }
3721
+ & .crisp-recordpage-illustrated-desc { max-width: 320px; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
3722
+ & .crisp-recordpage-illustrated-actions { display: inline-flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: var(--crisp-space-1-5); margin-top: var(--crisp-space-2); }
3238
3723
  & .crisp-recordpage-action[data-active] svg { color: #f5b83d; fill: #f5b83d; }
3239
-
3240
3724
  & .crisp-recordstable-tag { display: inline-flex; align-items: center; height: var(--crisp-size-chip); padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap; background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: color-mix(in srgb, var(--crisp-fg-primary) 82%, transparent); }
3241
3725
  & .crisp-recordstable-tag[data-tone="brand"] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 14%, transparent); color: color-mix(in srgb, var(--crisp-bg-brand-solid) 82%, var(--crisp-fg-primary)); }
3242
3726
  & .crisp-recordstable-tag[data-tone="success"] { background: color-mix(in srgb, var(--crisp-bg-success) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-success) 78%, var(--crisp-fg-primary)); }
@@ -3265,11 +3749,14 @@
3265
3749
  beneath (the same borderSubtle every other panel/row edge here
3266
3750
  already uses). Close keeps the existing transparent ghost button;
3267
3751
  Previous/Next/Open record are a distinct field-elevation style
3268
- (white fill, e.field's own ring+shadow, solid icon) -- measured as genuinely different chrome from Close, not a reuse. */
3752
+ (white fill, e.field's own ring+shadow, solid icon) -- measured as
3753
+ genuinely different chrome from Close, not a reuse. */
3269
3754
  & .crisp-recordpanel-toolbar { display: flex; align-items: center; height: 49px; flex-shrink: 0; padding: var(--crisp-space-2) var(--crisp-space-2-5); border-bottom: 1px solid var(--crisp-border-subtle); }
3270
3755
  & .crisp-recordpanel-toolbar-spacer { flex: 1; }
3271
3756
  & .crisp-recordpanel-pager { display: flex; align-items: center; gap: var(--crisp-space-1-5); margin-inline-start: var(--crisp-space-3); }
3272
- /* #413: "2 of 37 in All Companies" -- measured live, same toolbar row, right after Prev/Next. 12px/500, the same 63%-of-fgPrimary mix every other muted heading in this panel already uses. */
3757
+ /* #413: "2 of 37 in All Companies" -- measured live, same toolbar row,
3758
+ right after Prev/Next. 12px/500, the same 63%-of-fgPrimary mix every
3759
+ other muted heading in this panel already uses. */
3273
3760
  & .crisp-recordpanel-counter { margin-inline-start: var(--crisp-space-3); font-size: 12px; font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
3274
3761
  & .crisp-recordpanel-iconbtn { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; flex-shrink: 0; padding: 0; border: none; border-radius: var(--crisp-radius-md); background: transparent; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); font-family: inherit; cursor: pointer; transition: background-color .12s; }
3275
3762
  & .crisp-recordpanel-iconbtn:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
@@ -3277,26 +3764,36 @@
3277
3764
  & .crisp-recordpanel-navbtn:hover:not(:disabled) { background: var(--crisp-bg-raised); }
3278
3765
  & .crisp-recordpanel-navbtn:disabled { opacity: 0.4; cursor: not-allowed; }
3279
3766
  /* #413: measured live -- a 10px strip at the panel's own left edge,
3280
- cursor: col-resize (the issue's own repro calls it "ew-resize"; the actual computed value read off the live DOM is col-resize, the same cursor family under a different keyword). Kept fully inside the panel's own box, not extending past inset-inline-start: 0, so the
3281
- panel's own overflow: hidden (there for its rounded corners) never clips it. touch-action: none stops a touchscreen's own pan gesture
3767
+ cursor: col-resize (the issue's own repro calls it "ew-resize";
3768
+ the actual computed value read off the live DOM is col-resize, the
3769
+ same cursor family under a different keyword). Kept fully inside the
3770
+ panel's own box, not extending past inset-inline-start: 0, so the
3771
+ panel's own overflow: hidden (there for its rounded corners) never
3772
+ clips it. touch-action: none stops a touchscreen's own pan gesture
3282
3773
  from fighting the drag. */
3283
3774
  & .crisp-recordpanel-resize-handle { position: absolute; top: 0; bottom: 0; inset-inline-start: 0; width: 10px; cursor: col-resize; z-index: 1; touch-action: none; }
3284
3775
  & .crisp-recordpanel-resize-handle:hover, & .crisp-recordpanel-resize-handle:active { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); }
3285
-
3286
3776
  & .crisp-recordpanel-header { display: flex; align-items: center; gap: var(--crisp-space-2); height: 52px; flex-shrink: 0; padding: 0 var(--crisp-space-3); }
3287
3777
  & .crisp-recordpanel-fav { width: 22px; height: var(--crisp-size-chip); border-radius: var(--crisp-radius-sm); flex-shrink: 0; object-fit: cover; background: var(--crisp-bg-raised); }
3288
3778
  & .crisp-recordpanel-title { flex: 1; min-width: 0; font-size: var(--crisp-text-xl); font-weight: var(--crisp-weight-semibold); letter-spacing: -0.2px; color: var(--crisp-fg-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
3289
-
3290
3779
  & .crisp-recordpanel-body { flex: 1; min-height: 0; overflow-y: auto; padding: var(--crisp-space-2) var(--crisp-space-3); }
3291
-
3292
- /* #448: measured live on app.attio.com's peek (hover a Companies row, click "Open preview"). Section label reads identically for both Highlights and Activity: 14px/500, the same 63%-of-fgPrimary mix RecordPage's own field-label already uses (product/recordpage's .crisp-recordpage-field-label) -- one more surface landing on the same measured "muted section heading" colour. */
3780
+ /* #448: measured live on app.attio.com's peek (hover a Companies row,
3781
+ click "Open preview"). Section label reads identically for both
3782
+ Highlights and Activity: 14px/500, the same 63%-of-fgPrimary mix
3783
+ RecordPage's own field-label already uses (product/recordpage's
3784
+ .crisp-recordpage-field-label) -- one more surface landing on the
3785
+ same measured "muted section heading" colour. */
3293
3786
  & .crisp-recordpanel-section { padding: var(--crisp-space-2) var(--crisp-space-1) 0; }
3294
3787
  & .crisp-recordpanel-section-head { display: flex; align-items: center; justify-content: space-between; gap: var(--crisp-space-2); height: 20px; margin-bottom: var(--crisp-space-2); }
3295
3788
  & .crisp-recordpanel-section-label { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
3296
- /* "View all" -- measured 10px/400, a lighter mix than the section label (55%, not 63%) -- its own distinct, honestly-measured value, not a reuse of a nearby one that happened to be close. */
3789
+ /* "View all" -- measured 10px/400, a lighter mix than the section label
3790
+ (55%, not 63%) -- its own distinct, honestly-measured value, not a
3791
+ reuse of a nearby one that happened to be close. */
3297
3792
  & .crisp-recordpanel-section-action { border: none; background: none; padding: 0; font-family: inherit; font-size: 10px; font-weight: var(--crisp-weight-regular); color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); cursor: pointer; }
3298
3793
  & .crisp-recordpanel-section-action:hover { color: var(--crisp-fg-primary); }
3299
- /* Highlights: a 2-column grid of label+icon+value cards, no card chrome at all (no border/background/radius/shadow -- confirmed on the live DOM: every highlight's computed style is fully transparent).
3794
+ /* Highlights: a 2-column grid of label+icon+value cards, no card
3795
+ chrome at all (no border/background/radius/shadow -- confirmed on
3796
+ the live DOM: every highlight's computed style is fully transparent).
3300
3797
  8px row/column gap; two rows measured "233x86" inside 473px content
3301
3798
  width is exactly (473 - 8) / 2. */
3302
3799
  & .crisp-recordpanel-highlights { display: grid; grid-template-columns: 1fr 1fr; gap: var(--crisp-space-2); }
@@ -3305,27 +3802,33 @@
3305
3802
  & .crisp-recordpanel-highlight-label span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
3306
3803
  & .crisp-recordpanel-highlight-label svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
3307
3804
  & .crisp-recordpanel-highlight-value { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); overflow: hidden; text-overflow: ellipsis; }
3308
-
3309
3805
  /* Activity is a consumer-supplied slot (crisp has no activity-feed
3310
3806
  model of its own to render) -- this only wraps it with the measured
3311
3807
  section heading above. */
3312
3808
  & .crisp-recordpanel-activity { font-size: var(--crisp-text-lg); }
3313
-
3314
3809
  /* #413: collapsed Emails/Notes/Tasks below Activity -- measured live: a
3315
- 473px-wide row (the panel's own content width, 497 minus its 12px/8px side insets), 20px tall, 14px/500 solid fgPrimary label (NOT the 63% mix Highlights/Activity headings use -- this is body text, not a muted heading), a trailing count. padding "0 8px 0 12px" measured on the row itself; reproduced here as the trigger's own padding, since
3316
- the trigger IS the row (a plain div, not a button, when there's no content to expand into -- matches the empty ("Emails 0") row measured live, which has no expand affordance at all). */
3810
+ 473px-wide row (the panel's own content width, 497 minus its 12px/8px
3811
+ side insets), 20px tall, 14px/500 solid fgPrimary label (NOT the 63%
3812
+ mix Highlights/Activity headings use -- this is body text, not a
3813
+ muted heading), a trailing count. padding "0 8px 0 12px" measured on
3814
+ the row itself; reproduced here as the trigger's own padding, since
3815
+ the trigger IS the row (a plain div, not a button, when there's no
3816
+ content to expand into -- matches the empty ("Emails 0") row
3817
+ measured live, which has no expand affordance at all). */
3317
3818
  & .crisp-recordpanel-sections { display: flex; flex-direction: column; padding-top: var(--crisp-space-2); }
3318
3819
  & .crisp-recordpanel-section-trigger { display: flex; align-items: center; gap: var(--crisp-space-1); width: 100%; height: 20px; padding: 0 var(--crisp-space-2) 0 var(--crisp-space-3); border: none; background: none; font-family: inherit; text-align: start; cursor: pointer; }
3319
3820
  & .crisp-recordpanel-section-trigger-inert { cursor: default; }
3320
3821
  & .crisp-recordpanel-section-row-label { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
3321
3822
  /* The count wasn't captured with its own background/pill chrome in the
3322
3823
  live measurement (only its text: 14px, a "tertiary" internal variant
3323
- name that doesn't map to anything crisp defines) -- muted text next to the label, not an invented badge shape. */
3824
+ name that doesn't map to anything crisp defines) -- muted text next
3825
+ to the label, not an invented badge shape. */
3324
3826
  & .crisp-recordpanel-section-row-count { font-size: var(--crisp-text-lg); color: var(--crisp-fg-muted); }
3325
3827
  & .crisp-recordpanel-section-chevron { margin-inline-start: auto; flex-shrink: 0; color: var(--crisp-fg-muted); transition: transform .15s ease; }
3326
3828
  & .crisp-recordpanel-section-chevron[data-open] { transform: rotate(180deg); }
3327
3829
  & .crisp-recordpanel-section-content { padding: var(--crisp-space-2) var(--crisp-space-2) var(--crisp-space-2) var(--crisp-space-3); }
3328
- /* #329: an optional slot for record actions (e.g. Edit). Reuses the header's own measured values rather than inventing new ones -- the
3830
+ /* #329: an optional slot for record actions (e.g. Edit). Reuses the
3831
+ header's own measured values rather than inventing new ones -- the
3329
3832
  same horizontal inset (space.3), the same hairline border token
3330
3833
  (borderSubtle, flipped to border-top since this sits at the opposite
3331
3834
  edge), and the header/body gap (space.2) for its own vertical padding
@@ -3353,13 +3856,28 @@
3353
3856
  & .crisp-rte-body h1 { font-size: var(--crisp-text-xl); line-height: 28px; font-weight: var(--crisp-weight-semibold); letter-spacing: -0.3px; margin-top: var(--crisp-space-4); }
3354
3857
  & .crisp-rte-body h2 { font-size: 17px; line-height: 24px; font-weight: var(--crisp-weight-semibold); letter-spacing: -0.2px; margin-top: var(--crisp-space-3); }
3355
3858
  & .crisp-rte-body h3 { font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-semibold); margin-top: var(--crisp-space-2-5); }
3356
- /* #575: a consumer's CSS reset (e.g. Tailwind preflight's un-scoped ul, ol { list-style: none }
3357
- , specificity 0,0,1) was winning every list marker outright -- this rule set padding but never declared list-style-type, so there was no higher-specificity value (this selector is 0,1,1) to beat the reset with. Split into two single-selector rules, each declaring its own marker type, rather than the old single comma-joined rule ( .crisp-rte-body ul, & .crisp-rte-body ol ) the issue flagged as malformed. #611: padding-inline-start re-measured live once CRUD on the measurement workspace was allowed (a "zz-parity" note with a bulleted list, a nested bulleted list, and a numbered list) -- Attio's own <ul>/<ol> measures 36px (getComputedStyle,
3859
+ /* #575: a consumer's CSS reset (e.g. Tailwind preflight's un-scoped
3860
+ ul, ol { list-style: none }, specificity 0,0,1) was winning every
3861
+ list marker outright -- this rule set padding but never declared
3862
+ list-style-type, so there was no higher-specificity value (this
3863
+ selector is 0,1,1) to beat the reset with. Split into two
3864
+ single-selector rules, each declaring its own marker type, rather
3865
+ than the old single comma-joined rule ( .crisp-rte-body ul, &
3866
+ .crisp-rte-body ol ) the issue flagged as malformed.
3867
+ #611: padding-inline-start re-measured live once CRUD on the
3868
+ measurement workspace was allowed (a "zz-parity" note with a
3869
+ bulleted list, a nested bulleted list, and a numbered list) --
3870
+ Attio's own <ul>/<ol> measures 36px (getComputedStyle,
3358
3871
  ~36.1353px), not the 20px placeholder #575 shipped with no real
3359
- content to check against. 36px isn't in tokens/scale.ts (steps stop at 24px, jump to 32px) and is a single component-intrinsic value, so it stays a raw px per AUTHORING.md's golden rule 4b
3872
+ content to check against. 36px isn't in tokens/scale.ts (steps
3873
+ stop at 24px, jump to 32px) and is a single component-intrinsic
3874
+ value, so it stays a raw px per AUTHORING.md's golden rule 4b
3360
3875
  rather than being rounded to an existing token or added to the
3361
3876
  shared scale. Marker colour (currentColor via the already-measured
3362
- fgPrimary, confirmed to equal Attio's measured rgb(16,17,18) == #101112 exactly) and the ~4px li-to-li gap (the existing li margin-top rule below) were re-checked at the same time and already matched -- only the padding was wrong. Attio doesn't vary
3877
+ fgPrimary, confirmed to equal Attio's measured rgb(16,17,18) ==
3878
+ #101112 exactly) and the ~4px li-to-li gap (the existing li
3879
+ margin-top rule below) were re-checked at the same time and
3880
+ already matched -- only the padding was wrong. Attio doesn't vary
3363
3881
  marker type for a nested list either (nested <ul> is disc too,
3364
3882
  not circle), which this already doesn't attempt. */
3365
3883
  & .crisp-rte-body ul { list-style-type: disc; padding-inline-start: 36px; }
@@ -3370,10 +3888,13 @@
3370
3888
  & .crisp-rte-body a { color: var(--crisp-bg-brand-solid); text-decoration: underline; text-underline-offset: 2px; }
3371
3889
  & .crisp-rte-body strong { font-weight: var(--crisp-weight-semibold); }
3372
3890
  & .crisp-rte-body img { display: block; max-width: 100%; border-radius: var(--crisp-radius-md); }
3373
- /* Not measured against Attio (#428) — nothing in a read-only pass over app.attio.com had a live @mention rendered to read computed styles from, and typing one to create one is off-limits there. Reused verbatim from the one measured "brand" tag/chip this repo already has (RecordsTable/MultiSelect's [data-tone="brand"]) rather than
3891
+ /* Not measured against Attio (#428) — nothing in a read-only pass over
3892
+ app.attio.com had a live @mention rendered to read computed styles
3893
+ from, and typing one to create one is off-limits there. Reused
3894
+ verbatim from the one measured "brand" tag/chip this repo already
3895
+ has (RecordsTable/MultiSelect's [data-tone="brand"]) rather than
3374
3896
  inventing a new color or shape. */
3375
3897
  & .crisp-rte-mention { display: inline; border-radius: var(--crisp-radius-chip); padding: 0 var(--crisp-space-1); background: color-mix(in srgb, var(--crisp-bg-brand-solid) 14%, transparent); color: color-mix(in srgb, var(--crisp-bg-brand-solid) 82%, var(--crisp-fg-primary)); font-weight: var(--crisp-weight-medium); cursor: default; }
3376
-
3377
3898
  & .crisp-rte-toolbar { position: fixed; z-index: 70; display: inline-flex; align-items: center; gap: 2px; padding: 2px; border-radius: 10px; background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); transform: translate(-50%, -100%); opacity: 1; transition: opacity 0.1s ease; }
3378
3899
  & .crisp-rte-toolbar { @starting-style { opacity: 0; } }
3379
3900
  & .crisp-rte-tool { display: inline-flex; align-items: center; justify-content: center; gap: var(--crisp-space-1); min-width: 30px; height: 28px; padding: 0 7px; margin: 0; border: none; border-radius: var(--crisp-radius-md); background: none; color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); font-family: inherit; font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); cursor: pointer; transition: background-color .1s; }
@@ -3381,13 +3902,11 @@
3381
3902
  & .crisp-rte-tool:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: var(--crisp-fg-primary); }
3382
3903
  & .crisp-rte-tool[data-active], & .crisp-rte-tool[aria-expanded="true"] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 12%, transparent); color: var(--crisp-bg-brand-solid); }
3383
3904
  & .crisp-rte-tool-sep { width: 1px; height: 16px; margin: 0 2px; background: var(--crisp-border-subtle); }
3384
-
3385
3905
  & .crisp-rte-swatchpanel { position: fixed; z-index: 71; display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--crisp-space-1); width: 168px; padding: var(--crisp-space-1-5); border-radius: var(--crisp-radius-lg); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); transform: translate(-50%, var(--crisp-space-1-5)); opacity: 1; transition: opacity 0.1s ease; }
3386
3906
  & .crisp-rte-swatchpanel { @starting-style { opacity: 0; } }
3387
3907
  & .crisp-rte-swatch { width: 28px; height: 28px; padding: 0; margin: 0; border: none; border-radius: 50%; background: none; font-family: inherit; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 10%, transparent); cursor: pointer; }
3388
3908
  & .crisp-rte-swatch--none { background: var(--crisp-bg-surface); position: relative; }
3389
3909
  & .crisp-rte-swatch--none::after { content: ""; position: absolute; inset: 6px; border-top: 1.5px solid color-mix(in srgb, var(--crisp-fg-primary) 40%, transparent); transform: rotate(-45deg); }
3390
-
3391
3910
  & .crisp-rte-blockmenu { position: fixed; z-index: 71; box-sizing: border-box; width: 356px; padding: 0; border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); opacity: 1; transition: opacity 0.1s ease; }
3392
3911
  & .crisp-rte-blockmenu { @starting-style { opacity: 0; } }
3393
3912
  & .crisp-rte-blockmenu-search { display: flex; align-items: center; gap: var(--crisp-space-1-5); padding: var(--crisp-space-2-5) var(--crisp-space-3); border-bottom: 1px solid var(--crisp-border-subtle); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
@@ -3487,9 +4006,16 @@
3487
4006
 
3488
4007
  .crisp-savedviews {
3489
4008
  position: relative; display: inline-flex; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; /* crisp/#772 follow-up, re-measured live 2026-09-25 in the trigger's
3490
- actual position (Attio's real view bar, next to View settings -- fixed there in #766/#768/#778): white (bgSurface, rgb(255,255,255) exact) with elevation.field's 3-layer shadow, the same raised
4009
+ actual position (Attio's real view bar, next to View settings --
4010
+ fixed there in #766/#768/#778): white (bgSurface, rgb(255,255,255)
4011
+ exact) with elevation.field's 3-layer shadow, the same raised
3491
4012
  chrome View settings and Import/Export both carry
3492
- (tabletoolbar.recipe.ts's own --raised, not reused here directly since this is a separate recipe/component, but the identical measured values). Supersedes the original #555 measurement (rgb(246,247,247), reused as bgRaised -- a flat grey fill with no shadow at all), which was wrong: this trigger was never re-checked once it moved into its real view-bar position. */
4013
+ (tabletoolbar.recipe.ts's own --raised, not reused here directly
4014
+ since this is a separate recipe/component, but the identical measured
4015
+ values). Supersedes the original #555 measurement (rgb(246,247,247),
4016
+ reused as bgRaised -- a flat grey fill with no shadow at all), which
4017
+ was wrong: this trigger was never re-checked once it moved into its
4018
+ real view-bar position. */
3493
4019
  & .crisp-savedviews-trigger { box-sizing: border-box; display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: var(--crisp-size-row); margin: 0; padding: var(--crisp-space-1) var(--crisp-space-1-5); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); cursor: pointer; white-space: nowrap; transition: background-color .12s ease; }
3494
4020
  & .crisp-savedviews-trigger:hover, & .crisp-savedviews-trigger[data-open] { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-surface)); }
3495
4021
  & .crisp-savedviews-trigger-icon { flex-shrink: 0; display: inline-flex; width: var(--crisp-size-icon); height: var(--crisp-size-icon); }
@@ -3522,14 +4048,14 @@
3522
4048
  & .crisp-settingsshell-back svg { color: var(--crisp-fg-muted); }
3523
4049
  & .crisp-settingsshell-back:hover svg { color: var(--crisp-fg-primary); }
3524
4050
  & .crisp-settingsshell-search { display: flex; align-items: center; gap: var(--crisp-space-2); height: 32px; margin-bottom: var(--crisp-space-2); padding: 0 var(--crisp-space-2-5); border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-border-default); color: var(--crisp-fg-muted); }
3525
- /* The field box owns the ring, so focus has to move it -- the inner input kills its own outline. Measured live: a focused field's
4051
+ /* The field box owns the ring, so focus has to move it -- the inner
4052
+ input kills its own outline. Measured live: a focused field's
3526
4053
  input carries box-shadow: none while its wrapper carries
3527
4054
  inset 0 0 0 1px #266df0. */
3528
4055
  & .crisp-settingsshell-search:focus-within { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
3529
4056
  & .crisp-settingsshell-search svg { flex-shrink: 0; }
3530
4057
  & .crisp-settingsshell-search input { flex: 1; min-width: 0; border: none; outline: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); }
3531
4058
  & .crisp-settingsshell-search input::placeholder { color: var(--crisp-fg-muted); }
3532
-
3533
4059
  & .crisp-settingsshell-groups { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: var(--crisp-space-2-5); }
3534
4060
  & .crisp-settingsshell-group { display: flex; flex-direction: column; gap: 1px; }
3535
4061
  & .crisp-settingsshell-group-label { padding: var(--crisp-space-1) var(--crisp-space-2); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
@@ -3540,7 +4066,6 @@
3540
4066
  & .crisp-settingsshell-item[data-active] { background: var(--crisp-bg-raised); font-weight: var(--crisp-weight-medium); }
3541
4067
  & .crisp-settingsshell-item[data-active] svg { color: var(--crisp-fg-primary); }
3542
4068
  & .crisp-settingsshell-empty { padding: var(--crisp-space-3) var(--crisp-space-2); font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
3543
-
3544
4069
  & .crisp-settingsshell-content { flex: 1; min-width: 0; min-height: 0; overflow-y: auto; padding: var(--crisp-space-6) var(--crisp-space-8); }
3545
4070
  [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
3546
4071
  }
@@ -3648,7 +4173,8 @@
3648
4173
  & [data-swatch="light"] .crisp-themepicker-preview-bar:not(:nth-child(1)), & [data-swatch="system"] .crisp-themepicker-preview-bar:not(:nth-child(1)) { background: #eeeff1; }
3649
4174
  & [data-swatch="light"] .crisp-themepicker-preview-main, & [data-swatch="system"] .crisp-themepicker-preview-main { background: #ffffff; }
3650
4175
  & [data-swatch="light"] .crisp-themepicker-preview-main .crisp-themepicker-preview-bar, & [data-swatch="system"] .crisp-themepicker-preview-main .crisp-themepicker-preview-bar { background: #eeeff1; }
3651
- /* Dark swatch colours -- always for "dark", and for "system" only when the OS itself reports dark. */
4176
+ /* Dark swatch colours -- always for "dark", and for "system" only
4177
+ when the OS itself reports dark. */
3652
4178
  & .crisp-themepicker-preview[data-swatch="dark"] { border: 1px solid #1c1d1f; background: #0e0f10; }
3653
4179
  & [data-swatch="dark"] .crisp-themepicker-preview-bar:not(:nth-child(1)) { background: #1c1d1f; }
3654
4180
  & [data-swatch="dark"] .crisp-themepicker-preview-main { background: #0a0a0a; }
@@ -3693,3 +4219,34 @@
3693
4219
  transition-duration: 0.01ms !important;
3694
4220
  }
3695
4221
  }
4222
+
4223
+ .crisp-workspacegeneralsettings {
4224
+ & .crisp-workspacegeneralsettings-page { display: flex; flex-direction: column; gap: var(--crisp-space-6); font-family: var(--crisp-font-sans); }
4225
+ & .crisp-workspacegeneralsettings-heading { display: flex; flex-direction: column; gap: var(--crisp-space-0-5); }
4226
+ & .crisp-workspacegeneralsettings-title { font-size: 24px; line-height: 28px; font-weight: var(--crisp-weight-semibold); letter-spacing: -0.48px; color: var(--crisp-fg-primary); margin: 0; }
4227
+ & .crisp-workspacegeneralsettings-desc { font-size: var(--crisp-text-lg); line-height: 20px; color: var(--crisp-fg-muted); margin: 0; }
4228
+ & .crisp-workspacegeneralsettings-section { display: flex; flex-direction: column; gap: var(--crisp-space-4); }
4229
+ & .crisp-workspacegeneralsettings-sectiontitle { font-size: 16px; line-height: 20px; font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-primary); margin: 0; }
4230
+ & .crisp-workspacegeneralsettings-sectionhead { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--crisp-space-4); }
4231
+ & .crisp-workspacegeneralsettings-sectionhead-text { display: flex; flex-direction: column; gap: var(--crisp-space-1); }
4232
+ /* Logo row: a tile + helper text + "Upload logo" button. */
4233
+ & .crisp-workspacegeneralsettings-logorow { display: flex; align-items: center; gap: var(--crisp-space-4); }
4234
+ & .crisp-workspacegeneralsettings-logohelp { font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); }
4235
+ & .crisp-workspacegeneralsettings-logotext { display: flex; flex-direction: column; align-items: flex-start; gap: var(--crisp-space-1); }
4236
+ /* Name + Slug sit side by side, each ~half the content width. */
4237
+ & .crisp-workspacegeneralsettings-fieldrow { display: flex; gap: var(--crisp-space-4); }
4238
+ & .crisp-workspacegeneralsettings-fieldrow > * { flex: 1; min-width: 0; }
4239
+ & .crisp-workspacegeneralsettings-slugfield { display: flex; align-items: center; gap: var(--crisp-space-1-5); }
4240
+ & .crisp-workspacegeneralsettings-slugfield > :first-child { flex: 1; min-width: 0; }
4241
+ & .crisp-workspacegeneralsettings-copybtn { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 32px; height: 32px; border: none; border-radius: var(--crisp-radius-md); background: none; font-family: inherit; color: var(--crisp-fg-muted); cursor: pointer; }
4242
+ & .crisp-workspacegeneralsettings-copybtn:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
4243
+ /* Export-data empty state -- measured: 14/500/63%-opacity, centred in a
4244
+ 40px row, no border of its own (the Table wrapper already draws it). */
4245
+ & .crisp-workspacegeneralsettings-emptyrow { padding: var(--crisp-space-2-5) var(--crisp-space-4); font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); text-align: center; }
4246
+ /* Danger zone: a red-tinted bordered row, distinct from every other
4247
+ card on the page. */
4248
+ & .crisp-workspacegeneralsettings-dangerrow { display: flex; align-items: center; justify-content: space-between; gap: var(--crisp-space-6); padding: var(--crisp-space-3) var(--crisp-space-4); border: 1px solid color-mix(in srgb, var(--crisp-bg-danger) 30%, transparent); border-radius: 12px; background: color-mix(in srgb, var(--crisp-bg-danger) 6%, transparent); }
4249
+ & .crisp-workspacegeneralsettings-dangertext { display: flex; flex-direction: column; gap: var(--crisp-space-1); }
4250
+ & .crisp-workspacegeneralsettings-rowtitle { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
4251
+ & .crisp-workspacegeneralsettings-dangerwarning { font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
4252
+ }