@crisp-ui-kit/crisp 0.57.0 → 0.57.2

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 (293) hide show
  1. package/dist/accentpicker.css +5 -0
  2. package/dist/accentpicker.d.ts +7 -0
  3. package/dist/accentpicker.layered.css +7 -0
  4. package/dist/authshell.css +9 -4
  5. package/dist/authshell.layered.css +9 -4
  6. package/dist/avatar.css +1 -1
  7. package/dist/avatar.layered.css +1 -1
  8. package/dist/badge.css +5 -5
  9. package/dist/badge.layered.css +5 -5
  10. package/dist/board.css +28 -8
  11. package/dist/board.layered.css +28 -8
  12. package/dist/bottomsheet.css +26 -0
  13. package/dist/bottomsheet.d.ts +7 -0
  14. package/dist/bottomsheet.layered.css +28 -0
  15. package/dist/button.css +9 -8
  16. package/dist/button.layered.css +9 -8
  17. package/dist/checkbox.css +7 -3
  18. package/dist/checkbox.layered.css +7 -3
  19. package/dist/chip.css +15 -0
  20. package/dist/chip.d.ts +3 -1
  21. package/dist/chip.layered.css +15 -0
  22. package/dist/chipgroup.css +5 -0
  23. package/dist/chipgroup.d.ts +7 -0
  24. package/dist/chipgroup.layered.css +7 -0
  25. package/dist/cjs/components/accentpicker/accentpicker.js +66 -0
  26. package/dist/cjs/components/accentpicker/accentpicker.recipe.js +25 -0
  27. package/dist/cjs/components/accentpicker/index.js +7 -0
  28. package/dist/cjs/components/avatar/avatar.recipe.js +4 -1
  29. package/dist/cjs/components/badge/badge.recipe.js +38 -10
  30. package/dist/cjs/components/bottomsheet/bottomsheet.js +90 -0
  31. package/dist/cjs/components/bottomsheet/bottomsheet.recipe.js +77 -0
  32. package/dist/cjs/components/bottomsheet/index.js +9 -0
  33. package/dist/cjs/components/button/button.recipe.js +55 -21
  34. package/dist/cjs/components/checkbox/checkbox.recipe.js +61 -9
  35. package/dist/cjs/components/chip/chip.js +14 -2
  36. package/dist/cjs/components/chip/chip.recipe.js +69 -1
  37. package/dist/cjs/components/chipgroup/chipgroup.js +51 -0
  38. package/dist/cjs/components/chipgroup/chipgroup.recipe.js +36 -0
  39. package/dist/cjs/components/chipgroup/index.js +7 -0
  40. package/dist/cjs/components/datatable/datatable.js +5 -1
  41. package/dist/cjs/components/datatable/datatable.recipe.js +11 -2
  42. package/dist/cjs/components/dialog/dialog.recipe.js +21 -10
  43. package/dist/cjs/components/emptystate/emptystate.recipe.js +7 -2
  44. package/dist/cjs/components/fileupload/fileupload.js +21 -4
  45. package/dist/cjs/components/input/index.js +2 -1
  46. package/dist/cjs/components/input/input.js +33 -3
  47. package/dist/cjs/components/input/input.recipe.js +22 -7
  48. package/dist/cjs/components/menu/menu.recipe.js +21 -7
  49. package/dist/cjs/components/menusheet/index.js +9 -0
  50. package/dist/cjs/components/menusheet/menusheet.js +133 -0
  51. package/dist/cjs/components/menusheet/menusheet.recipe.js +103 -0
  52. package/dist/cjs/components/multiselect/multiselect.js +2 -2
  53. package/dist/cjs/components/multiselect/multiselect.recipe.js +45 -12
  54. package/dist/cjs/components/progress/progress.js +21 -4
  55. package/dist/cjs/components/progress/progress.recipe.js +30 -0
  56. package/dist/cjs/components/select/select.js +3 -3
  57. package/dist/cjs/components/select/select.recipe.js +63 -10
  58. package/dist/cjs/components/snackbar/index.js +8 -0
  59. package/dist/cjs/components/snackbar/snackbar.js +121 -0
  60. package/dist/cjs/components/snackbar/snackbar.recipe.js +71 -0
  61. package/dist/cjs/components/tabletoolbar/tabletoolbar.chrome.js +7 -2
  62. package/dist/cjs/components/tabletoolbar/tabletoolbar.js +37 -12
  63. package/dist/cjs/components/tabs/tabs.js +10 -4
  64. package/dist/cjs/components/tabs/tabs.recipe.js +23 -6
  65. package/dist/cjs/components/text/text.recipe.js +8 -7
  66. package/dist/cjs/components/textarea/textarea.js +3 -2
  67. package/dist/cjs/components/textarea/textarea.recipe.js +13 -9
  68. package/dist/cjs/components/textlink/textlink.recipe.js +6 -3
  69. package/dist/cjs/index.js +8 -0
  70. package/dist/cjs/product/authshell/authshell.recipe.js +5 -1
  71. package/dist/cjs/product/command/command.recipe.js +4 -1
  72. package/dist/cjs/product/comments/comments.js +7 -2
  73. package/dist/cjs/product/comments/comments.recipe.js +25 -7
  74. package/dist/cjs/product/memberssettings/index.js +6 -0
  75. package/dist/cjs/product/memberssettings/memberssettings.js +99 -0
  76. package/dist/cjs/product/memberssettings/memberssettings.recipe.js +83 -0
  77. package/dist/cjs/product/notificationssettings/index.js +5 -0
  78. package/dist/cjs/product/notificationssettings/notificationssettings.js +19 -0
  79. package/dist/cjs/product/notificationssettings/notificationssettings.recipe.js +44 -0
  80. package/dist/cjs/product/recordpage/recordpage.js +101 -9
  81. package/dist/cjs/product/recordpage/recordpage.recipe.js +43 -6
  82. package/dist/cjs/product/workspacegeneralsettings/index.js +5 -0
  83. package/dist/cjs/product/workspacegeneralsettings/workspacegeneralsettings.js +21 -0
  84. package/dist/cjs/product/workspacegeneralsettings/workspacegeneralsettings.recipe.js +51 -0
  85. package/dist/cjs/tokens/contract.js +1 -0
  86. package/dist/cjs/tokens/elevation.js +8 -5
  87. package/dist/cjs/tokens/palette.js +107 -33
  88. package/dist/cjs/tokens/scale.js +45 -2
  89. package/dist/cjs/tokens/semantic.js +26 -0
  90. package/dist/columncalc.css +15 -7
  91. package/dist/columncalc.layered.css +15 -7
  92. package/dist/command.css +23 -19
  93. package/dist/command.layered.css +23 -19
  94. package/dist/comments.css +28 -19
  95. package/dist/comments.d.ts +2 -2
  96. package/dist/comments.layered.css +28 -19
  97. package/dist/datatable.css +161 -67
  98. package/dist/datatable.layered.css +161 -67
  99. package/dist/datepicker.css +13 -41
  100. package/dist/datepicker.layered.css +13 -41
  101. package/dist/datepickerfield.css +4 -1
  102. package/dist/datepickerfield.layered.css +4 -1
  103. package/dist/dialog.css +12 -7
  104. package/dist/dialog.layered.css +12 -7
  105. package/dist/emptystate.css +5 -3
  106. package/dist/emptystate.layered.css +5 -3
  107. package/dist/esm/components/accentpicker/accentpicker.js +30 -0
  108. package/dist/esm/components/accentpicker/accentpicker.recipe.js +22 -0
  109. package/dist/esm/components/accentpicker/index.js +2 -0
  110. package/dist/esm/components/avatar/avatar.recipe.js +4 -1
  111. package/dist/esm/components/badge/badge.recipe.js +38 -10
  112. package/dist/esm/components/bottomsheet/bottomsheet.js +52 -0
  113. package/dist/esm/components/bottomsheet/bottomsheet.recipe.js +74 -0
  114. package/dist/esm/components/bottomsheet/index.js +2 -0
  115. package/dist/esm/components/button/button.recipe.js +56 -22
  116. package/dist/esm/components/checkbox/checkbox.recipe.js +61 -9
  117. package/dist/esm/components/chip/chip.js +14 -2
  118. package/dist/esm/components/chip/chip.recipe.js +69 -1
  119. package/dist/esm/components/chipgroup/chipgroup.js +15 -0
  120. package/dist/esm/components/chipgroup/chipgroup.recipe.js +33 -0
  121. package/dist/esm/components/chipgroup/index.js +2 -0
  122. package/dist/esm/components/datatable/datatable.js +5 -1
  123. package/dist/esm/components/datatable/datatable.recipe.js +11 -2
  124. package/dist/esm/components/dialog/dialog.recipe.js +22 -11
  125. package/dist/esm/components/emptystate/emptystate.recipe.js +7 -2
  126. package/dist/esm/components/fileupload/fileupload.js +20 -5
  127. package/dist/esm/components/input/index.js +1 -1
  128. package/dist/esm/components/input/input.js +33 -3
  129. package/dist/esm/components/input/input.recipe.js +23 -8
  130. package/dist/esm/components/menu/menu.recipe.js +21 -7
  131. package/dist/esm/components/menusheet/index.js +2 -0
  132. package/dist/esm/components/menusheet/menusheet.js +95 -0
  133. package/dist/esm/components/menusheet/menusheet.recipe.js +100 -0
  134. package/dist/esm/components/multiselect/multiselect.js +2 -2
  135. package/dist/esm/components/multiselect/multiselect.recipe.js +45 -12
  136. package/dist/esm/components/progress/progress.js +22 -5
  137. package/dist/esm/components/progress/progress.recipe.js +30 -0
  138. package/dist/esm/components/select/select.js +3 -3
  139. package/dist/esm/components/select/select.recipe.js +63 -10
  140. package/dist/esm/components/snackbar/index.js +2 -0
  141. package/dist/esm/components/snackbar/snackbar.js +84 -0
  142. package/dist/esm/components/snackbar/snackbar.recipe.js +68 -0
  143. package/dist/esm/components/tabletoolbar/tabletoolbar.chrome.js +7 -2
  144. package/dist/esm/components/tabletoolbar/tabletoolbar.js +37 -12
  145. package/dist/esm/components/tabs/tabs.js +10 -4
  146. package/dist/esm/components/tabs/tabs.recipe.js +23 -6
  147. package/dist/esm/components/text/text.recipe.js +8 -7
  148. package/dist/esm/components/textarea/textarea.js +3 -2
  149. package/dist/esm/components/textarea/textarea.recipe.js +14 -10
  150. package/dist/esm/components/textlink/textlink.recipe.js +6 -3
  151. package/dist/esm/index.js +8 -0
  152. package/dist/esm/product/authshell/authshell.recipe.js +5 -1
  153. package/dist/esm/product/command/command.recipe.js +4 -1
  154. package/dist/esm/product/comments/comments.js +7 -2
  155. package/dist/esm/product/comments/comments.recipe.js +25 -7
  156. package/dist/esm/product/memberssettings/index.js +1 -0
  157. package/dist/esm/product/memberssettings/memberssettings.js +62 -0
  158. package/dist/esm/product/memberssettings/memberssettings.recipe.js +80 -0
  159. package/dist/esm/product/notificationssettings/index.js +1 -0
  160. package/dist/esm/product/notificationssettings/notificationssettings.js +16 -0
  161. package/dist/esm/product/notificationssettings/notificationssettings.recipe.js +41 -0
  162. package/dist/esm/product/recordpage/recordpage.js +102 -10
  163. package/dist/esm/product/recordpage/recordpage.recipe.js +43 -6
  164. package/dist/esm/product/workspacegeneralsettings/index.js +1 -0
  165. package/dist/esm/product/workspacegeneralsettings/workspacegeneralsettings.js +18 -0
  166. package/dist/esm/product/workspacegeneralsettings/workspacegeneralsettings.recipe.js +48 -0
  167. package/dist/esm/tokens/contract.js +2 -1
  168. package/dist/esm/tokens/elevation.js +8 -5
  169. package/dist/esm/tokens/palette.js +107 -33
  170. package/dist/esm/tokens/scale.js +44 -1
  171. package/dist/esm/tokens/semantic.js +26 -0
  172. package/dist/fileupload.css +3 -1
  173. package/dist/fileupload.layered.css +3 -1
  174. package/dist/input.css +11 -2
  175. package/dist/input.layered.css +11 -2
  176. package/dist/listpage.css +3 -1
  177. package/dist/listpage.layered.css +3 -1
  178. package/dist/memberssettings.css +67 -0
  179. package/dist/memberssettings.d.ts +7 -0
  180. package/dist/memberssettings.layered.css +69 -0
  181. package/dist/menu.css +2 -2
  182. package/dist/menu.layered.css +2 -2
  183. package/dist/menusheet.css +36 -0
  184. package/dist/menusheet.d.ts +7 -0
  185. package/dist/menusheet.layered.css +38 -0
  186. package/dist/multiselect.css +17 -6
  187. package/dist/multiselect.d.ts +2 -2
  188. package/dist/multiselect.layered.css +17 -6
  189. package/dist/native/tokens.d.ts +2 -1
  190. package/dist/native/tokens.js +53 -25
  191. package/dist/notificationssettings.css +22 -0
  192. package/dist/notificationssettings.d.ts +7 -0
  193. package/dist/notificationssettings.layered.css +24 -0
  194. package/dist/objectsettings.css +4 -4
  195. package/dist/objectsettings.layered.css +4 -4
  196. package/dist/progress.css +3 -0
  197. package/dist/progress.layered.css +3 -0
  198. package/dist/recordpage.css +67 -23
  199. package/dist/recordpage.layered.css +67 -23
  200. package/dist/recordpanel.css +35 -16
  201. package/dist/recordpanel.layered.css +35 -16
  202. package/dist/resizablepanel.css +8 -5
  203. package/dist/resizablepanel.layered.css +8 -5
  204. package/dist/richtext.css +24 -8
  205. package/dist/richtext.layered.css +24 -8
  206. package/dist/savedviews.css +9 -2
  207. package/dist/savedviews.layered.css +9 -2
  208. package/dist/select.css +47 -7
  209. package/dist/select.d.ts +2 -2
  210. package/dist/select.layered.css +47 -7
  211. package/dist/settingsshell.css +2 -3
  212. package/dist/settingsshell.layered.css +2 -3
  213. package/dist/slider.css +6 -27
  214. package/dist/slider.layered.css +6 -27
  215. package/dist/snackbar.css +24 -0
  216. package/dist/snackbar.d.ts +7 -0
  217. package/dist/snackbar.layered.css +26 -0
  218. package/dist/statcard.css +3 -1
  219. package/dist/statcard.layered.css +3 -1
  220. package/dist/styles.css +927 -365
  221. package/dist/styles.layered.css +927 -365
  222. package/dist/table.css +28 -10
  223. package/dist/table.layered.css +28 -10
  224. package/dist/tabletoolbar.css +35 -24
  225. package/dist/tabletoolbar.layered.css +35 -24
  226. package/dist/tabs.css +18 -4
  227. package/dist/tabs.layered.css +18 -4
  228. package/dist/text.css +1 -1
  229. package/dist/text.layered.css +1 -1
  230. package/dist/textarea.css +4 -7
  231. package/dist/textarea.layered.css +4 -7
  232. package/dist/textlink.css +2 -2
  233. package/dist/textlink.layered.css +2 -2
  234. package/dist/themepicker.css +2 -1
  235. package/dist/themepicker.layered.css +2 -1
  236. package/dist/tokens.css +31 -13
  237. package/dist/tokens.json +53 -25
  238. package/dist/types/components/accentpicker/accentpicker.d.ts +23 -0
  239. package/dist/types/components/accentpicker/accentpicker.recipe.d.ts +3 -0
  240. package/dist/types/components/accentpicker/index.d.ts +2 -0
  241. package/dist/types/components/bottomsheet/bottomsheet.d.ts +35 -0
  242. package/dist/types/components/bottomsheet/bottomsheet.recipe.d.ts +3 -0
  243. package/dist/types/components/bottomsheet/index.d.ts +2 -0
  244. package/dist/types/components/button/button.recipe.d.ts +13 -8
  245. package/dist/types/components/checkbox/checkbox.recipe.d.ts +28 -5
  246. package/dist/types/components/chip/chip.d.ts +22 -0
  247. package/dist/types/components/chip/chip.recipe.d.ts +48 -0
  248. package/dist/types/components/chipgroup/chipgroup.d.ts +14 -0
  249. package/dist/types/components/chipgroup/chipgroup.recipe.d.ts +5 -0
  250. package/dist/types/components/chipgroup/index.d.ts +2 -0
  251. package/dist/types/components/datatable/datatable.d.ts +19 -0
  252. package/dist/types/components/fileupload/fileupload.d.ts +26 -0
  253. package/dist/types/components/input/index.d.ts +1 -1
  254. package/dist/types/components/input/input.d.ts +84 -0
  255. package/dist/types/components/menusheet/index.d.ts +2 -0
  256. package/dist/types/components/menusheet/menusheet.d.ts +42 -0
  257. package/dist/types/components/menusheet/menusheet.recipe.d.ts +3 -0
  258. package/dist/types/components/multiselect/multiselect.d.ts +4 -3
  259. package/dist/types/components/multiselect/multiselect.recipe.d.ts +3 -0
  260. package/dist/types/components/progress/progress.d.ts +16 -4
  261. package/dist/types/components/progress/progress.recipe.d.ts +22 -0
  262. package/dist/types/components/select/select.d.ts +6 -5
  263. package/dist/types/components/select/select.recipe.d.ts +3 -0
  264. package/dist/types/components/snackbar/index.d.ts +2 -0
  265. package/dist/types/components/snackbar/snackbar.d.ts +36 -0
  266. package/dist/types/components/snackbar/snackbar.recipe.d.ts +5 -0
  267. package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +28 -1
  268. package/dist/types/components/tabs/tabs.d.ts +11 -1
  269. package/dist/types/components/textarea/textarea.d.ts +16 -0
  270. package/dist/types/index.d.ts +8 -0
  271. package/dist/types/product/comments/comments.d.ts +9 -3
  272. package/dist/types/product/comments/comments.recipe.d.ts +12 -2
  273. package/dist/types/product/memberssettings/index.d.ts +1 -0
  274. package/dist/types/product/memberssettings/memberssettings.d.ts +85 -0
  275. package/dist/types/product/memberssettings/memberssettings.recipe.d.ts +3 -0
  276. package/dist/types/product/notificationssettings/index.d.ts +1 -0
  277. package/dist/types/product/notificationssettings/notificationssettings.d.ts +37 -0
  278. package/dist/types/product/notificationssettings/notificationssettings.recipe.d.ts +3 -0
  279. package/dist/types/product/recordpage/recordpage.d.ts +89 -2
  280. package/dist/types/product/workspacegeneralsettings/index.d.ts +1 -0
  281. package/dist/types/product/workspacegeneralsettings/workspacegeneralsettings.d.ts +48 -0
  282. package/dist/types/product/workspacegeneralsettings/workspacegeneralsettings.recipe.d.ts +3 -0
  283. package/dist/types/tokens/contract.d.ts +7 -1
  284. package/dist/types/tokens/elevation.d.ts +8 -5
  285. package/dist/types/tokens/palette.d.ts +28 -12
  286. package/dist/types/tokens/scale.d.ts +12 -1
  287. package/dist/types/tokens/semantic.d.ts +17 -0
  288. package/dist/wheelpicker.css +4 -2
  289. package/dist/wheelpicker.layered.css +4 -2
  290. package/dist/workspacegeneralsettings.css +30 -0
  291. package/dist/workspacegeneralsettings.d.ts +7 -0
  292. package/dist/workspacegeneralsettings.layered.css +32 -0
  293. package/package.json +41 -1
package/dist/styles.css CHANGED
@@ -27,14 +27,16 @@
27
27
  font-display: swap;
28
28
  }
29
29
  :root {
30
- --crisp-bg-brand-solid: #266df0;
31
- --crisp-bg-brand-solid-pressed: #1f5fd6;
30
+ --crisp-bg-brand-solid: #0298d9;
31
+ --crisp-bg-brand-solid-pressed: #0283bb;
32
+ --crisp-bg-brand-contrast: #0278ac;
33
+ --crisp-fg-brand-contrast: #0278ac;
32
34
  --crisp-fg-on-brand: #ffffff;
33
35
  --crisp-fg-on-tone: #101112;
34
36
  --crisp-bg-surface: #ffffff;
35
- --crisp-bg-raised: #f4f4f5;
37
+ --crisp-bg-raised: #f7f8f9;
36
38
  --crisp-bg-surface-muted: #f6f7f7;
37
- --crisp-border-default: #e4e4e7;
39
+ --crisp-border-default: #dcdee3;
38
40
  --crisp-border-subtle: #eeeff1;
39
41
  --crisp-border-dropzone: #e6e7ea;
40
42
  --crisp-border-divider: #e6e7ea;
@@ -43,7 +45,9 @@
43
45
  --crisp-ring-switch: #d9dade;
44
46
  --crisp-bg-tile-active: #ffffff;
45
47
  --crisp-shadow-tile-active: rgba(0,0,0,0.04);
46
- --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;
47
51
  --crisp-fg-alert-description: rgba(0,0,0,0.63);
48
52
  --crisp-bg-overlay-frost: #ffffff;
49
53
  --crisp-shadow-overlay1: rgba(28,40,64,0.12);
@@ -56,9 +60,9 @@
56
60
  --crisp-shadow-popover3: rgba(24,41,75,0.16);
57
61
  --crisp-fg-primary: #101112;
58
62
  --crisp-fg-muted: #71717a;
59
- --crisp-bg-danger: #e5484d;
60
- --crisp-bg-danger-solid: #ba2525;
61
- --crisp-fg-danger: #eb3b3b;
63
+ --crisp-bg-danger: #fa342c;
64
+ --crisp-bg-danger-solid: #ca1d13;
65
+ --crisp-fg-danger: #fa342c;
62
66
  --crisp-stroke-field-focus: #1a1c20;
63
67
  --crisp-fg-disabled: #d1d3d8;
64
68
  --crisp-bg-neutral-weak: #f3f4f5;
@@ -67,8 +71,9 @@
67
71
  --crisp-fg-neutral-subtle: #868b94;
68
72
  --crisp-fg-neutral-muted: #555d6d;
69
73
  --crisp-stroke-focus-ring: #1e82eb;
70
- --crisp-bg-success: #1f9d57;
71
- --crisp-bg-warning: #b5730f;
74
+ --crisp-bg-success: #079171;
75
+ --crisp-bg-warning: #9b7821;
76
+ --crisp-bg-warning-solid: #fbdc65;
72
77
  --crisp-bg-orange: #f97514;
73
78
  --crisp-bg-cyan: #00b9eb;
74
79
  --crisp-bg-purple: #9b69ff;
@@ -95,8 +100,10 @@
95
100
  }
96
101
 
97
102
  [data-theme="dark"] {
98
- --crisp-bg-brand-solid: #266df0;
99
- --crisp-bg-brand-solid-pressed: #1f5fd6;
103
+ --crisp-bg-brand-solid: #0298d9;
104
+ --crisp-bg-brand-solid-pressed: #0283bb;
105
+ --crisp-bg-brand-contrast: #0278ac;
106
+ --crisp-fg-brand-contrast: #0278ac;
100
107
  --crisp-fg-on-brand: #ffffff;
101
108
  --crisp-fg-on-tone: #101112;
102
109
  --crisp-bg-surface: #0a0a0a;
@@ -112,6 +119,8 @@
112
119
  --crisp-bg-tile-active: #101112;
113
120
  --crisp-shadow-tile-active: rgba(0,0,0,0);
114
121
  --crisp-bg-scrim: rgba(0,0,0,0.55);
122
+ --crisp-bg-overlay-strong: #00000074;
123
+ --crisp-stroke-group-divider: #ffffff17;
115
124
  --crisp-fg-alert-description: rgba(255,255,255,0.6);
116
125
  --crisp-bg-overlay-frost: #1c1d1f;
117
126
  --crisp-shadow-overlay1: rgba(0,0,0,0.48);
@@ -137,6 +146,7 @@
137
146
  --crisp-stroke-focus-ring: #5e98fe;
138
147
  --crisp-bg-success: #2ea968;
139
148
  --crisp-bg-warning: #e0982a;
149
+ --crisp-bg-warning-solid: #e0982a;
140
150
  --crisp-bg-orange: #f97514;
141
151
  --crisp-bg-cyan: #00b9eb;
142
152
  --crisp-bg-purple: #9b69ff;
@@ -175,13 +185,15 @@
175
185
  --crisp-space-1-5: 6px;
176
186
  --crisp-space-2-5: 10px;
177
187
  --crisp-space-3-5: 14px;
178
- --crisp-radius-chip: 7px;
188
+ --crisp-radius-xs: 4px;
189
+ --crisp-radius-chip: 999px;
179
190
  --crisp-radius-sm: 6px;
180
191
  --crisp-radius-md: 8px;
181
192
  --crisp-radius-lg: 9px;
182
193
  --crisp-radius-xl: 12px;
183
194
  --crisp-radius-2xl: 16px;
184
195
  --crisp-radius-3xl: 20px;
196
+ --crisp-radius-4xl: 24px;
185
197
  --crisp-radius-full: 999px;
186
198
  --crisp-size-icon: 16px;
187
199
  --crisp-size-chip: 22px;
@@ -205,6 +217,18 @@
205
217
  --crisp-weight-semibold: 600;
206
218
  --crisp-weight-bold: 700;
207
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;
226
+ }
227
+
228
+ .crisp-accentpicker {
229
+ display: flex; gap: var(--crisp-space-2);
230
+ & .crisp-accentpicker-swatch { box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; padding: 0; margin: 0; border-radius: var(--crisp-radius-full); border: 1px solid transparent; background: none; font-family: inherit; cursor: pointer; }
231
+ & .crisp-accentpicker-swatch:focus-visible { outline: 2px solid var(--crisp-bg-brand-solid); outline-offset: 2px; }
208
232
  }
209
233
 
210
234
  .crisp-accordion {
@@ -255,7 +279,7 @@
255
279
  background: var(--crisp-bg-raised); color: var(--crisp-fg-neutral-muted); box-shadow: inset 0 0 0 1px var(--crisp-border-default);
256
280
  }
257
281
  .crisp-avatar--tone_brand {
258
- background: var(--crisp-bg-brand-solid); color: #fff;
282
+ background: var(--crisp-bg-brand-contrast); color: #fff;
259
283
  }
260
284
  .crisp-avatar--tone_orange {
261
285
  background: var(--crisp-bg-orange); color: #fff;
@@ -299,13 +323,13 @@
299
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);
300
324
  }
301
325
  .crisp-badge--size_small {
302
- 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);
303
327
  }
304
328
  .crisp-badge--size_medium {
305
- 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);
306
330
  }
307
331
  .crisp-badge--size_large {
308
- 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);
309
333
  }
310
334
  .crisp-badge--variant_soft {
311
335
 
@@ -314,13 +338,13 @@
314
338
 
315
339
  }
316
340
  .crisp-badge--compound_0 {
317
- background: var(--crisp-bg-brand-solid); color: var(--crisp-fg-on-brand); box-shadow: none;
341
+ background: var(--crisp-bg-brand-contrast); color: var(--crisp-fg-on-brand); box-shadow: none;
318
342
  }
319
343
  .crisp-badge--compound_1 {
320
344
  background: var(--crisp-bg-success); color: var(--crisp-fg-on-tone); box-shadow: none;
321
345
  }
322
346
  .crisp-badge--compound_2 {
323
- background: var(--crisp-bg-warning); color: var(--crisp-fg-on-tone); box-shadow: none;
347
+ background: var(--crisp-bg-warning-solid); color: var(--crisp-fg-on-tone); box-shadow: none;
324
348
  }
325
349
  .crisp-badge--compound_3 {
326
350
  background: var(--crisp-bg-danger); color: var(--crisp-fg-on-tone); box-shadow: none;
@@ -353,6 +377,33 @@
353
377
  }
354
378
  }
355
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
+
356
407
  .crisp-breadcrumb {
357
408
  display: flex; align-items: center; gap: var(--crisp-space-0-5); font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg);
358
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; }
@@ -393,23 +444,24 @@
393
444
  .crisp-button {
394
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;
395
446
  &:disabled { opacity: 0.5; cursor: not-allowed; }
447
+ &:active { transform: scale(0.98); }
396
448
  }
397
449
  .crisp-button--intent_primary {
398
- background: var(--crisp-bg-brand-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(38,109,240,0.12), 0 3px 6px -2px rgba(38,109,240,0.08);
399
- &:hover { background: var(--crisp-bg-brand-solid-pressed); }
400
- &:active { background: var(--crisp-bg-brand-solid-pressed); }
450
+ background: var(--crisp-bg-brand-contrast); color: var(--crisp-fg-on-brand); box-shadow: none;
451
+ &:hover { filter: brightness(0.96); }
452
+ &:active { filter: brightness(0.92); }
401
453
  }
402
454
  .crisp-button--intent_neutral {
403
- 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);
404
- &: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); }
405
457
  }
406
458
  .crisp-button--intent_danger {
407
- 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;
408
460
  &:hover { filter: brightness(0.96); }
409
461
  &:active { filter: brightness(0.92); }
410
462
  }
411
463
  .crisp-button--intent_ghost {
412
- background: transparent; color: var(--crisp-fg-muted); box-shadow: none;
464
+ background: transparent; color: var(--crisp-fg-primary); box-shadow: none;
413
465
  &:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
414
466
  }
415
467
  .crisp-button--size_sm {
@@ -421,7 +473,7 @@
421
473
  &[data-icon-end] { padding-inline-end: var(--crisp-space-1-5); }
422
474
  }
423
475
  .crisp-button--size_lg {
424
- height: 40px; padding-inline: 14px; font-size: var(--crisp-text-lg); border-radius: 10px;
476
+ height: 40px; padding-inline: 14px; font-size: var(--crisp-text-lg); border-radius: var(--crisp-radius-xl);
425
477
  }
426
478
  .crisp-button--shape_text {
427
479
 
@@ -499,12 +551,16 @@
499
551
  }
500
552
 
501
553
  .crisp-checkbox {
502
- 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;
503
- &:hover { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 30%, transparent); }
504
- &:checked, &:indeterminate { background: var(--crisp-bg-brand-solid) center / 10px no-repeat; box-shadow: none; }
554
+ 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;
555
+ &:hover { border-color: color-mix(in srgb, var(--crisp-fg-primary) 30%, transparent); }
556
+ &:checked, &:indeterminate { background: var(--crisp-bg-brand-solid) center / 10px no-repeat; border-color: transparent; }
505
557
  &: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"); }
506
558
  &:indeterminate { background-image: linear-gradient(var(--crisp-fg-on-brand), var(--crisp-fg-on-brand)); background-size: 8px 2px; }
507
559
  &:focus-visible { outline: 2px solid var(--crisp-bg-brand-solid); outline-offset: 1px; }
560
+ &:disabled { background: var(--crisp-bg-neutral-weak); border-color: var(--crisp-border-subtle); cursor: not-allowed; }
561
+ &:checked:disabled, &:indeterminate:disabled { background: var(--crisp-bg-neutral-weak) center / 10px no-repeat; border-color: var(--crisp-border-subtle); }
562
+ &: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"); }
563
+ &:indeterminate:disabled { background-image: linear-gradient(var(--crisp-fg-on-brand), var(--crisp-fg-on-brand)); background-size: 8px 2px; }
508
564
  }
509
565
 
510
566
  @media (prefers-reduced-motion: reduce) {
@@ -548,34 +604,63 @@
548
604
  .crisp-chip--tone_orange {
549
605
  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);
550
606
  }
607
+ .crisp-chip--appearance_filled {
608
+
609
+ }
610
+ .crisp-chip--appearance_outline {
611
+ background: transparent;
612
+ }
613
+ .crisp-chip--selected_true {
614
+ background: var(--crisp-bg-neutral-inverted); color: var(--crisp-fg-on-neutral-inverted); box-shadow: none;
615
+ }
616
+ .crisp-chip--selected_false {
617
+
618
+ }
619
+ .crisp-chip--compound_0 {
620
+ box-shadow: inset 0 0 0 1px var(--crisp-border-default);
621
+ }
622
+
623
+ .crisp-chipgroup {
624
+ 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;
625
+ &::-webkit-scrollbar { display: none; }
626
+ & > * { flex-shrink: 0; scroll-snap-align: start; }
627
+ }
551
628
 
552
629
  .crisp-columncalc {
553
- 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. */
630
+ 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
631
+ shrink the button back to its text. Make the wrapper fill too, so the
632
+ whole cell stays clickable. */
554
633
  & .crisp-menu { width: 100%; height: 100%; }
555
- /* No radius: the trigger fills the cell, so rounding its own corners leaves the cell's background showing through at the four corners as
634
+ /* No radius: the trigger fills the cell, so rounding its own corners
635
+ leaves the cell's background showing through at the four corners as
556
636
  soon as the hover tint paints. Everything else that fills a grid cell
557
637
  agrees -- recordstable-sum-btn, the same summary trigger, declares no
558
638
  radius, and datatable-grip and the embedded pickers zero theirs. An 8px
559
639
  radius survived the chip-to-full-cell conversion and shipped rounded
560
- until #282, so the spec check now guards it. */
561
- /* The hit target is the WHOLE summary cell -- measured 246x36 with
640
+ until #282, so the spec check now guards it. */ /* The hit target is the WHOLE summary cell -- measured 246x36 with
562
641
  padding 0 12px, not a content-sized chip. Clicking anywhere in the cell
563
642
  opens the aggregation menu.
564
643
  No `min-height` (crisp#834, found reproducing the footer-row-38px-
565
- 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,
644
+ not-36px bug in an isolated browser test, not table.recipe.ts's own
645
+ border/box-sizing, the hypothesis the issue itself flagged as
646
+ unconfirmed): the table cell's OWN `height: tableRow` (36px,
566
647
  border-box) leaves 34px of content box once its 2px of top+bottom
567
648
  border are counted, and `min-height: tableRow` here demanded 36px
568
649
  of CONTENT height regardless -- overflowing the cell by 2px, which
569
650
  auto-height table layout resolves by growing the whole row to fit,
570
651
  landing at exactly the reported 38. `height: 100%` alone already
571
- fills whatever the cell's real content box is (the doc comment above this rule's own stated intent), with nothing left demanding more. */
652
+ fills whatever the cell's real content box is (the doc comment above
653
+ this rule's own stated intent), with nothing left demanding more. */
572
654
  & .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; }
573
655
  & .crisp-columncalc-trigger:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); color: var(--crisp-fg-primary); }
574
656
  & .crisp-columncalc-trigger svg { flex-shrink: 0; opacity: 0.7; }
575
657
  & .crisp-columncalc-trigger[data-align="right"] { justify-content: flex-end; }
576
658
  & .crisp-columncalc-placeholder { overflow: hidden; text-overflow: ellipsis; }
577
659
  & .crisp-columncalc-value { color: var(--crisp-fg-primary); font-variant-numeric: tabular-nums; }
578
- /* 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
660
+ /* crisp#837: re-measured live against Attio's real footer calc label --
661
+ 14px (was 13px, `c.text.md`, fixed on the shared trigger rule above)
662
+ and rgba(0,0,0,.63), the same color-mix(fgPrimary, 63%) figure this
663
+ file's own -op label already should have used instead of the flatter
579
664
  `fgMuted` token (a close but not measured match). */
580
665
  & .crisp-columncalc-op { color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
581
666
  & .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); }
@@ -667,9 +752,13 @@
667
752
  }
668
753
 
669
754
  .crisp-datatable {
670
- 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
671
- (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
672
- 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
755
+ 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
756
+ (search box, sort/filter chips) sat flush at the table's outer edge
757
+ (x=0) while the header row's own th/td padding (table.recipe.ts,
758
+ #465) starts every column's text at 12px -- one row down, the SAME
759
+ grid's own header cell already begins at that inset. c.space["3"]
760
+ here just matches the row directly below it, measured live against
761
+ Attio (the sort/filter row's search chip lines up with the first
673
762
  column header's text, not the grid's raw edge). */
674
763
  & .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); }
675
764
  & .crisp-datatable-search { position: relative; flex: 1; max-width: 280px; display: inline-flex; align-items: center; }
@@ -679,10 +768,7 @@
679
768
  & .crisp-datatable-search input:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
680
769
  & .crisp-datatable-count { margin-inline-start: auto; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
681
770
  /* announced, never drawn */
682
- & .crisp-datatable-live {
683
- position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
684
- overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
685
- }
771
+ & .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; }
686
772
  /* crisp/#645: visually-hidden-but-focusable, not visually-hidden-and-
687
773
  inert like .crisp-datatable-live above -- same clip-rect shape,
688
774
  this one just needs to actually hold DOM focus so a native
@@ -690,7 +776,8 @@
690
776
  in datatable.tsx). resize: none keeps a stray native textarea handle
691
777
  from ever being drawable, even though it's clipped to 1px anyway. */
692
778
  & .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; }
693
- /* 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
779
+ /* single active cell (crisp/#645 follow-up -- this had no styling of its
780
+ own at all before, a gap against Golden Rule 0: RecordsTable's own
694
781
  measured value, .crisp-recordstable-td[data-selected], brought
695
782
  across unchanged now that a visible anchor actually matters for the
696
783
  Shift+Click range below). */
@@ -699,57 +786,85 @@
699
786
  tint formula already used twice below (tbody tr[data-selected] td)
700
787
  for the same reason a selected group of rows gets one: a selected
701
788
  GROUP of cells is the same kind of state, just cell-scoped instead of
702
- row-scoped. Not independently re-measured against Attio's own range highlight (out of scope this slice -- flagged on crisp#645). */
789
+ row-scoped. Not independently re-measured against Attio's own range
790
+ highlight (out of scope this slice -- flagged on crisp#645). */
703
791
  & td[data-in-range] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 10%, var(--crisp-bg-surface)); }
704
- /* 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
792
+ /* The header cell has no padding of its own -- the button owns the
793
+ full cell so the whole header is clickable (its own inset is 12px).
794
+ Mirrors the summary-row fix.
795
+ `& .crisp-table th`, not bare `& th` (found while re-checking #691's
705
796
  own fix live): DataTable's root nests `Table`'s own wrapper, whose
706
797
  `table.recipe.ts` carries `& th, & td { padding: 0 12px }` at the
707
798
  SAME specificity (.crisp-table th vs .crisp-datatable th, both one
708
799
  class + one element) -- a bare `& th` here only won by source
709
- order, which flipped the moment table.recipe.ts's own CSS file happened to land after datatable's in the build (alphabetical:
800
+ order, which flipped the moment table.recipe.ts's own CSS file
801
+ happened to land after datatable's in the build (alphabetical:
710
802
  datatable < table), silently reinstating 12px on every header cell
711
- 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
803
+ in the whole product and making every interactive column's button
804
+ double it to 24px. Scoping through `.crisp-table` adds a second
805
+ class to the selector (0,2,1 beats 0,1,1) so this can never lose a
806
+ specificity tie to source order again.
807
+ `thead th`, not a bare `th` (crisp#829): #779 made the primary
808
+ column's BODY cell a real `<th scope="row">` for a11y, and a bare
712
809
  `th` here matches that one too, zeroing an inset nothing else
713
810
  supplies -- the primary column's text sat flush left, one column's
714
811
  cell no longer matching its own header or any sibling `<td>`. Only
715
- 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,
812
+ the header row's own `<th>` needs zero padding (its sort BUTTON
813
+ owns the inset); the body row's `<th scope="row">` has no button,
716
814
  and needs the SAME base 12px (table.recipe.ts, #465) every other
717
815
  cell already gets. */
718
816
  & .crisp-table thead th { padding: 0; }
719
- /* 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
720
- -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
817
+ /* Same wrapper trap as AUTHORING.md Lesson 40 (ColumnCalc's summary trigger):
818
+ Menu wraps its trigger in a flex div that sizes to its content, so a
819
+ long header grew past the column instead of filling it -- with nothing
820
+ left to squeeze, the label's own ellipsis CSS (below) had no properly
821
+ -sized box to truncate against, and the th's own overflow hard-clipped
822
+ it with no "...". Fill the wrapper, scoped to th so Menu's default
721
823
  (correct everywhere else) is untouched. */
722
824
  & th .crisp-menu { width: 100%; height: 100%; }
723
825
  & .crisp-datatable-th-inner { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); width: 100%; min-width: 0; height: 100%; }
724
826
  & .crisp-datatable-th-inner > svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
725
827
  /* crisp/#605 follow-up: a header with neither a Menu nor a sort button
726
- (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
727
- 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
828
+ (the primary/entity column's now-inert path, or any column with no
829
+ columnMenu/sortable at all) renders .crisp-datatable-th-inner as a
830
+ DIRECT child of th instead of nested inside a button -- and loses
831
+ the button's own 12px inset, drifting left of the td's 12px base
832
+ inset. Measured live on Attio's own Companies grid: the header label
833
+ and the cell's own text land at the exact same x (335px from the
834
+ viewport, both 40px from the column's left edge) via two different
728
835
  padding+icon+gap combinations -- Attio does not leave the inert
729
836
  primary header flush left either. The `>` combinator targets only
730
- 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. */
837
+ the button-less case: Menu's own wrapper (`.crisp-menu`) and the
838
+ plain sort `button` both put >=1 more level between `th` and this
839
+ span, so their own 12px (already on the button) isn't doubled. */
731
840
  & th > .crisp-datatable-th-inner { box-sizing: border-box; padding: 0 var(--crisp-space-3); }
732
- /* The summary cell has NO padding of its own -- the calculation trigger
841
+ /* The summary cell has NO padding of its own -- the calculation trigger
733
842
  owns the whole 246x37 area so a click anywhere in the cell opens the
734
843
  menu. Zero the cell's inset and let ColumnCalc supply the 12px. */
735
844
  & tfoot td { padding: 0; }
736
- /* Group heading row -- a view is grouped under a subtle label with the bucket's count beside it. */
737
- & .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; }
845
+ /* Group heading row -- a view is grouped under a subtle label with the
846
+ bucket's count beside it. */
847
+ & .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; }
738
848
  & .crisp-datatable-viewsettings:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); color: var(--crisp-fg-primary); }
739
849
  & .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); }
740
- & .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); }
850
+ & .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); }
741
851
  & .crisp-datatable-group-count { margin-inline-start: var(--crisp-space-1-5); color: var(--crisp-fg-muted); font-variant-numeric: tabular-nums; }
742
852
  & .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; }
743
853
  & .crisp-datatable-th-rename:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
744
854
  & .crisp-datatable-th-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
745
- /* #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. */
855
+ /* #465: 12px per the comment above (and Attio's measured header cell
856
+ padding: 0 12px) -- was still c.space["2"] (8px), drifted from its
857
+ own documented value. */
746
858
  & .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; }
747
859
  /* A right-aligned column's header keeps its field-icon + label packed at
748
860
  the START, matching its own values only in that the VALUES right-align
749
861
  -- Attio's own right-aligned header (measured live: "Twitter follower
750
- 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
751
- (#406, reversing #393's own justify-content: flex-end here). The chevron's own margin-inline-start: auto below already pins IT to the
752
- end of a flex-start row with no override needed -- that's what pins the trailing icon on Attio too. */
862
+ count") keeps the icon+label left and only the trailing column-menu
863
+ icon sits pinned to the right edge, independent of the label's width
864
+ (#406, reversing #393's own justify-content: flex-end here). The
865
+ chevron's own margin-inline-start: auto below already pins IT to the
866
+ end of a flex-start row with no override needed -- that's what pins
867
+ the trailing icon on Attio too. */
753
868
  & .crisp-datatable-chevron { margin-inline-start: auto; flex-shrink: 0; color: var(--crisp-fg-muted); opacity: 0; transition: opacity .12s; }
754
869
  & .crisp-datatable-sort:hover .crisp-datatable-chevron { opacity: 0.6; }
755
870
  & .crisp-datatable-sort[data-sorted] .crisp-datatable-chevron { opacity: 1; color: var(--crisp-fg-primary); }
@@ -757,17 +872,23 @@
757
872
  & th[data-sticky], & td[data-sticky] { position: sticky; z-index: 1; background: var(--crisp-bg-surface); }
758
873
  /* `thead th[data-sticky]`, not a bare `th[data-sticky]` (crisp#848, same
759
874
  bare-`th` family as #829/#833/#835): this z-index:3 was written for
760
- 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>`
761
- 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
875
+ the HEADER's own sticky corner cell (sticky column x sticky header),
876
+ meant to win over everything else. Unscoped, it also matched the
877
+ primary column's BODY cell -- a real `<th scope="row" data-sticky>`
878
+ since #779 -- giving every scrolled body row's own sticky cell a
879
+ HIGHER z-index than the sticky FOOTER's (2), so it painted over the
762
880
  footer instead of the footer showing through. Confirmed live on a
763
- real bounded-height, 25-row table (GRC's Risk register): scrolled partway, `elementFromPoint` at the footer's own on-screen cell
881
+ real bounded-height, 25-row table (GRC's Risk register): scrolled
882
+ partway, `elementFromPoint` at the footer's own on-screen cell
764
883
  resolved to a body row's `<th>`, not the footer's `<td>`, and that
765
884
  `<th>`'s computed z-index read 3. Scoped to `thead`, a body row's
766
885
  own sticky `<th>` now falls back to the base rule above (z-index 1,
767
- correct -- the same as a plain sticky `<td>`), and the footer's own explicit z-index below is unaffected either way. */
886
+ correct -- the same as a plain sticky `<td>`), and the footer's own
887
+ explicit z-index below is unaffected either way. */
768
888
  & thead th[data-sticky] { z-index: 3; }
769
889
  /* #554: a footer sticky-column cell sits in the bottom-sticky row (table.recipe.ts's
770
- `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. */
890
+ `tfoot td { z-index: 2 }`), which `td[data-sticky]`'s own higher-specificity
891
+ `z-index: 1` would otherwise undercut -- keep it level with the row it's in. */
771
892
  & tfoot td[data-sticky] { z-index: 2; }
772
893
  & tbody tr[data-selected] td[data-sticky] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 10%, var(--crisp-bg-surface)); }
773
894
  & tbody tr:hover td[data-sticky] { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-surface)); }
@@ -787,7 +908,8 @@
787
908
  /* crisp/#758: AI-enriched column marker, ported from RecordsTable's own
788
909
  already-measured value -- a full-bleed #9b69ff (bgPurple) wash at 4%,
789
910
  composited OVER whatever row-state background the cell already has
790
- (hover, selected), which is why it's an ::after overlay and not a plain background. */
911
+ (hover, selected), which is why it's an ::after overlay and not a
912
+ plain background. */
791
913
  & td[data-enriched]::after { content: ""; position: absolute; inset: 0; pointer-events: none; background: color-mix(in srgb, var(--crisp-bg-purple) 4%, transparent); }
792
914
  & .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; }
793
915
  & .crisp-datatable-edit:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
@@ -798,40 +920,56 @@
798
920
  & .crisp-datatable-edit:disabled { opacity: 0.5; cursor: not-allowed; }
799
921
  & .crisp-datatable-edit-pending { flex-shrink: 0; margin-inline-start: var(--crisp-space-1); color: var(--crisp-fg-muted); vertical-align: middle; }
800
922
  /* The rejection's message, beside the cell's own content -- a sibling in
801
- 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
923
+ the TableCell, never nested inside a column's own render output, so a
924
+ hover-only wrapper (a Tooltip, say) around a custom render can't hide
802
925
  it. Block-level: the row grows to fit it rather than floating over
803
926
  the row below, so it can never end up clipped by the scroll
804
- container's overflow the way a position:fixed escape would need guarding against. */
927
+ container's overflow the way a position:fixed escape would need
928
+ guarding against. */
805
929
  & .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); }
806
- /* 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
930
+ /* editor: "select"/"switch" -- always live, no separate edit mode, so
931
+ the cell IS the picker: same chromeless-fill treatment as
932
+ RecordsTable's own --picker cells (measured there already; DataTable
807
933
  shares RecordsTable's grid, Golden Rule 0). */
808
934
  & .crisp-datatable-td--picker { padding: 0; }
809
935
  & .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); }
810
936
  & .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; }
811
937
  & .crisp-datatable-td--picker .crisp-datatable-edit-error { padding-inline-start: var(--crisp-space-2); }
812
- /* 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*. */
938
+ /* reference picker: a leading 16px avatar per option, plus a trailing
939
+ "Create X" row when the consumer supplies one (#449) -- same measured
940
+ values as RecordsTable's own .crisp-recordstable-refoption*. */
813
941
  & .crisp-datatable-refoption { display: flex; align-items: center; gap: var(--crisp-space-2); min-width: 0; overflow: hidden; text-overflow: ellipsis; }
814
942
  & .crisp-datatable-refoption-avatar { flex-shrink: 0; }
815
943
  & .crisp-datatable-refoption--create { color: var(--crisp-fg-muted); }
816
944
  & .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); }
817
945
  /* #826 -- the full-body loading state (a filter/sort re-query, or a
818
- 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. */
946
+ fresh page boot), measured live on Attio's Companies grid on both
947
+ triggers: icon+text centred in the row-body area, 10px gap, 14px/500
948
+ weight/20px line-height, both icon and text in the SAME muted
949
+ rgba(0,0,0,0.63) -- the same color-mix figure this file already uses
950
+ for other muted text (e.g. -phone-dial). Distinct from the cold
951
+ in-place row load (blank, no indicator) and the warm-navigation
952
+ per-cell shimmer (#599) -- neither of those swaps the whole body. */
819
953
  & .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; }
820
954
  & .crisp-datatable-loading .crisp-spinner { border-top-color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
821
- /* #579: click-to-expand overlay for a truncated cell -- a read-only, full-value card, settled live as click (not Attio's usual
822
- 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
823
- 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
955
+ /* #579: click-to-expand overlay for a truncated cell -- a read-only,
956
+ full-value card, settled live as click (not Attio's usual
957
+ hover-expand) and measured position: absolute, unlike this file's
958
+ floating chrome (Menu/Select popovers) which portals and escapes the
959
+ scroll container. Deliberately NOT the edit-error pattern above
960
+ (grow the row, block-level, never clipped): a virtualized grid
961
+ assumes a fixed row height for its scroll math (owned by the grid
962
+ core, not this cell), so growing a row here would fight it. Anchored
963
+ to the SAME column width the cell itself renders at (measured:
964
+ Attio's own overlay came out only slightly wider than the
965
+ triggering cell's clientWidth -- the overshoot is this padding),
966
+ wrapping onto as many lines as the value needs; it CAN be clipped by
967
+ `.crisp-table-scroll`'s own `overflow: auto` if it grows past the
824
968
  visible viewport, same as the edit-error comment above already
825
969
  notes for any position:absolute escape -- unmeasured against Attio
826
970
  for this exact case, flagged rather than guessed. */
827
971
  & td[data-cell-expanded] { overflow: visible; z-index: 7; }
828
- & .crisp-datatable-cell-overlay {
829
- position: absolute; inset-inline-start: 0; top: 0; width: 100%; box-sizing: border-box;
830
- padding: var(--crisp-space-2) var(--crisp-space-3); background: var(--crisp-bg-surface);
831
- border: 1px solid var(--crisp-bg-brand-solid); border-radius: 4px;
832
- font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium);
833
- color: var(--crisp-fg-primary); white-space: normal; word-break: break-word; cursor: default;
834
- }
972
+ & .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; }
835
973
  /* fixed layout so column widths (and resize) are exact — like RecordsTable */
836
974
  & table { table-layout: fixed; }
837
975
  /* read-only/computed column marker (#596) -- sits just inside the
@@ -843,42 +981,70 @@
843
981
  & .crisp-datatable-resize { position: absolute; top: 0; inset-inline-end: -3px; width: 9px; height: 100%; cursor: col-resize; z-index: 6; touch-action: none; }
844
982
  & .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; }
845
983
  & .crisp-datatable-resize:hover::after, & .crisp-datatable-resize[data-resizing]::after { opacity: 1; }
846
- /* crisp/#731 -- column drag-to-reorder. Measured live against Attio's own grid (the issue's own comment): the dragged header keeps full
984
+ /* crisp/#731 -- column drag-to-reorder. Measured live against Attio's
985
+ own grid (the issue's own comment): the dragged header keeps full
847
986
  opacity and the plain `pointer` cursor throughout (Attio draws no
848
987
  ghost and no "grabbing" cursor of its own), so this adds nothing to
849
988
  `[data-dragging]` itself -- the ONLY new paint is the drop-indicator
850
- 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. */
989
+ line, a 3px bar in the same measured brand blue the resize handle's
990
+ own hover state (above) already uses, positioned on whichever edge of
991
+ the hovered header the pointer is closer to. */
851
992
  & th[data-dragging] { cursor: pointer; }
852
993
  & 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; }
853
994
  & th[data-drop-before]::before { inset-inline-start: -1.5px; }
854
995
  & th[data-drop-after]::after { inset-inline-end: -1.5px; }
855
996
  /* typed cells — entity (avatar + name), colour-coded tags, numbers, dates */
856
997
  & .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); }
857
- /* 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
998
+ /* crisp/#601 supersession follow-up -- the same flex-row shape
999
+ .crisp-datatable-entity gives its own avatar+name+actions, but
1000
+ structural only (no entity-specific colour/weight): lets
1001
+ onOpenComments/onOpenPreview compose alongside ANY column's own
858
1002
  content (a custom render, or a non-entity built-in type), not just
859
1003
  type: "entity"'s own inline rendering. */
860
1004
  & .crisp-datatable-cell-actions-row { display: flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; width: 100%; }
861
- /* crisp/#601, G-050: measured LIVE (getComputedStyle on the name text, plain hover vs rest) that Attio's underline is unconditional, not a
1005
+ /* crisp/#601, G-050: measured LIVE (getComputedStyle on the name text,
1006
+ plain hover vs rest) that Attio's underline is unconditional, not a
862
1007
  hover reveal -- identical at rest and on hover: text-decoration-color
863
- 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
864
- 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
1008
+ is rgba(0,0,0,0.09), a 9% tint, not the name's own full-contrast text
1009
+ colour. The previous :hover-gated, full-contrast rule here was itself
1010
+ unmeasured and wrong in two ways at once. */ /* crisp#851 -- Attio's own entity/record names are measured at
1011
+ font-weight 500, distinct from a plain-text primary column's 400
1012
+ (table.recipe.ts's own `tbody th` default). This was never its own
865
1013
  explicit style -- it silently rode on a since-fixed header-rule leak
866
1014
  (#833/#835/#848) that happened to apply the same 500 to every
867
1015
  primary-column body cell regardless of type. Made explicit here so
868
1016
  fixing that leak doesn't also (wrongly) drop entity names to 400. */
869
1017
  & .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); }
870
- & 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%; }
871
- /* 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
1018
+ /* #893: shared by the button (onEntityClick, a different record) and
1019
+ the anchor (href, the row's own record) -- was button-only, which
1020
+ silently left a real <a> unstyled the day #893 added one. */
1021
+ & .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%; }
1022
+ /* crisp/#601, G-048: two 20px ghost icons, right-aligned in the primary
1023
+ column, revealed on ROW hover (the issue's own title says "row
872
1024
  hover", and a live check confirmed hovering well past the name --
873
- 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
1025
+ not just the icons' own hit target -- still reveals them) or
1026
+ keyboard focus. Measured live: border-radius 6px, icon colour
1027
+ rgba(0,0,0,0.55) at rest, darkening to the full text colour with a
1028
+ rgba(0,0,0,0.04) background on the icon's OWN hover -- both
874
1029
  expressed as color-mix against fgPrimary so they stay correct in
875
1030
  dark mode instead of a literal black that would invert. */
876
1031
  & .crisp-datatable-entity-actions { display: flex; align-items: center; gap: var(--crisp-space-1-5); margin-inline-start: auto; flex-shrink: 0; opacity: 0; }
877
1032
  & tr:hover .crisp-datatable-entity-actions, & .crisp-datatable-entity-actions:focus-within { opacity: 1; }
878
1033
  & .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; }
879
1034
  & .crisp-datatable-entity-action:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); color: var(--crisp-fg-primary); }
880
- & .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); }
881
- & .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); }
1035
+ /* #892: the default shape's radius was a stray literal (radius.sm, 6px)
1036
+ -- Attio's own real company/object avatar measures 30%, which is
1037
+ Avatar's own already-shipped shape="rounded" value, not a new number.
1038
+ shape="circle" reuses Avatar's own shape="circle" value (radius.full)
1039
+ the same way, measured live on a real Attio person avatar. */
1040
+ & .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); }
1041
+ & .crisp-datatable-avatar[data-shape="circle"] { border-radius: var(--crisp-radius-full); }
1042
+ & .crisp-datatable-avatar[data-tone="success"] { background: var(--crisp-bg-success); }
1043
+ & .crisp-datatable-avatar[data-tone="warning"] { background: var(--crisp-bg-warning); }
1044
+ & .crisp-datatable-avatar[data-tone="danger"] { background: var(--crisp-bg-danger); }
1045
+ & .crisp-datatable-avatar[data-tone="purple"] { background: var(--crisp-bg-purple); }
1046
+ & .crisp-datatable-avatar[data-tone="cyan"] { background: var(--crisp-bg-cyan); }
1047
+ & .crisp-datatable-avatar[data-tone="orange"] { background: var(--crisp-bg-orange); }
882
1048
  & .crisp-datatable-avatar img { width: 100%; height: 100%; border-radius: inherit; object-fit: cover; }
883
1049
  & .crisp-datatable-tags { display: flex; align-items: center; gap: var(--crisp-space-1); overflow: hidden; }
884
1050
  & .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); }
@@ -891,13 +1057,19 @@
891
1057
  & .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)); }
892
1058
  & .crisp-datatable-num { font-variant-numeric: tabular-nums; }
893
1059
  & .crisp-datatable-muted { color: var(--crisp-fg-muted); }
894
- /* 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
1060
+ /* crisp#822 -- a date/datetime cell's danger/overdue tone: same token as
1061
+ TasksTable's own overdue due-date (crisp#462), plain text colour, no
895
1062
  fill/weight change. */
896
1063
  & .crisp-datatable-danger { color: var(--crisp-fg-danger); }
897
- /* 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. */
1064
+ /* url/email cell types (#654) -- same measured values as RecordsTable's
1065
+ own .crisp-recordstable-link, which this mirrors: no underline at
1066
+ rest, underline on hover, own-colour text rather than a browser-blue
1067
+ link. */
898
1068
  & .crisp-datatable-link { color: var(--crisp-fg-primary); text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
899
1069
  & .crisp-datatable-link:hover { text-decoration: underline; }
900
- /* 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. */
1070
+ /* phone cell type/editor (#765) -- measured live: the dial-code prefix
1071
+ is a muted rgba(0,0,0,0.63) at 14px, matching every other muted-text
1072
+ value already measured elsewhere in this file. */
901
1073
  & .crisp-datatable-phone { display: inline-flex; align-items: center; gap: var(--crisp-space-1); }
902
1074
  & .crisp-datatable-phone-dial { color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
903
1075
  & .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); }
@@ -927,7 +1099,8 @@
927
1099
  value rows (32px = size.menuRow, 5px 8px padding, 8px gap between
928
1100
  the grip/value/actions, 16px icons) above one ALWAYS-blank trailing
929
1101
  DataPhoneEditor-shaped row -- that blank row is the only "add
930
- another" affordance, there's no separate button for it. The copy/delete pair reuses .crisp-datatable-entity-action's own
1102
+ another" affordance, there's no separate button for it. The
1103
+ copy/delete pair reuses .crisp-datatable-entity-action's own
931
1104
  hover-reveal chrome (not independently re-measured as its own hit
932
1105
  target this pass, only the 16px icon glyphs were). */
933
1106
  & .crisp-datatable-phone-multi-editor { display: flex; flex-direction: column; }
@@ -935,8 +1108,7 @@
935
1108
  & .crisp-datatable-phone-multi-row .crisp-datatable-phone-link { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
936
1109
  & .crisp-datatable-phone-multi-grip { color: var(--crisp-fg-muted); flex-shrink: 0; cursor: grab; }
937
1110
  & .crisp-datatable-phone-multi-actions { display: flex; align-items: center; gap: var(--crisp-space-1); margin-inline-start: auto; flex-shrink: 0; opacity: 0; }
938
- & .crisp-datatable-phone-multi-row:hover .crisp-datatable-phone-multi-actions,
939
- & .crisp-datatable-phone-multi-actions:focus-within { opacity: 1; }
1111
+ & .crisp-datatable-phone-multi-row:hover .crisp-datatable-phone-multi-actions, & .crisp-datatable-phone-multi-actions:focus-within { opacity: 1; }
940
1112
  & .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; }
941
1113
  & .crisp-datatable-phone-multi-action:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); color: var(--crisp-fg-primary); }
942
1114
  /* datetime cell editor (#762) -- DatePickerField + TimePicker side by side. */
@@ -945,7 +1117,10 @@
945
1117
  18px pitch (a 2px gap), an empty star at rgba(0,0,0,0.1), a
946
1118
  committed/filled star at brand600 (--crisp-bg-brand-solid, an exact
947
1119
  match to the measured #266df0), and a distinct hover-preview colour
948
- -- 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
1120
+ -- rgb(33,91,196) -- that's close to but NOT brand700/bgBrandSolidPressed
1121
+ (#1f5fd6); kept as its own literal rather than forced onto that token,
1122
+ since the two were measured stable and different. The read-only svg
1123
+ and the editable button's own inner svg both carry the
949
1124
  crisp-datatable-rating-star class on the svg itself (not the
950
1125
  button), so one set of fill rules covers both -- the button only
951
1126
  adds its own reset + cursor via a separate -btn class. */
@@ -956,9 +1131,13 @@
956
1131
  & .crisp-datatable-rating-star[data-hovering] path { fill: #215bc4; }
957
1132
  & .crisp-datatable-rating-star-btn { display: inline-flex; padding: 0; margin: 0; border: none; background: none; font-family: inherit; cursor: pointer; }
958
1133
  /* row drag-to-reorder -- a Settings > Objects > Attributes screen measures
959
- 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
1134
+ a leading 20px grip column, a 16px grip icon, and the row's own
1135
+ hairline doubling as the drop indicator (it tints toward the brand
1136
+ colour as the active drop target). The affordance + geometry are
1137
+ measured; the drag MECHANISM is crisp's existing house idiom -- HTML5
960
1138
  draggable + dataTransfer, the same one column reorder already uses
961
- (RecordsTable's reorderColumn) -- not a proprietary pointer-events implementation. */
1139
+ (RecordsTable's reorderColumn) -- not a proprietary
1140
+ pointer-events implementation. */
962
1141
  & .crisp-datatable-col-grip { width: 20px; padding: 0; text-align: center; }
963
1142
  & .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; }
964
1143
  & .crisp-datatable-grip:active { cursor: grabbing; }
@@ -992,36 +1171,22 @@
992
1171
  measurement -- accessibility wins over the seed reference.
993
1172
  neutralMuted clears it, 6.62:1 light / 14.71:1 dark (dark was
994
1173
  already fine at 9.43:1). */
995
- & .crisp-datepicker-weekday {
996
- display: flex; align-items: center; justify-content: center;
997
- height: var(--crisp-size-calendarCell); color: var(--crisp-fg-neutral-muted);
998
- font-size: var(--crisp-text-sm); line-height: 16px; font-weight: var(--crisp-weight-medium);
999
- }
1000
-
1174
+ & .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); }
1001
1175
  & .crisp-datepicker-months { display: grid; grid-template-columns: minmax(0, 1fr); align-items: start; min-width: 0; }
1002
1176
  & .crisp-datepicker-months[data-wheel-open] { visibility: hidden; }
1003
1177
  & .crisp-datepicker-month { min-width: 0; }
1004
- & .crisp-datepicker-month-label {
1005
- display: flex; align-items: center; height: 24px;
1006
- color: var(--crisp-fg-primary); font-size: var(--crisp-text-lg); line-height: 20px;
1007
- font-weight: var(--crisp-weight-medium);
1008
- }
1009
-
1178
+ & .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); }
1010
1179
  & .crisp-datepicker-grid { display: grid; min-width: 0; }
1011
1180
  & .crisp-datepicker-week-row { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); }
1012
-
1013
- & .crisp-datepicker-cell {
1014
- position: relative; min-width: 0; min-height: var(--crisp-size-calendarCell);
1015
- isolation: isolate;
1016
- }
1181
+ & .crisp-datepicker-cell { position: relative; min-width: 0; min-height: var(--crisp-size-calendarCell); isolation: isolate; }
1017
1182
  & .crisp-datepicker-empty-cell { min-height: var(--crisp-size-calendarCell); }
1018
-
1019
1183
  /* The bridge joining days inside a completed range.
1020
1184
  The endpoints do NOT carry data-in-range -- only the interior days do --
1021
1185
  so these are three independent rules, not a base plus two modifiers. A
1022
1186
  modifier that sets only inset-inline-start never generates a pseudo
1023
1187
  element on an endpoint, which leaves a half-cell of white between the
1024
- endpoint's pill and the run of grey beside it. Each rule declares its own content. */
1188
+ endpoint's pill and the run of grey beside it. Each rule declares its
1189
+ own content. */
1025
1190
  & .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); }
1026
1191
  & .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); }
1027
1192
  & .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); }
@@ -1037,33 +1202,19 @@
1037
1202
  & .crisp-datepicker-date:disabled { color: var(--crisp-fg-disabled); cursor: not-allowed; }
1038
1203
  & .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; }
1039
1204
  & .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; }
1040
- /* 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. */
1041
- & .crisp-datepicker-date-content > [data-date-picker-day] {
1042
- display: flex; min-height: var(--crisp-size-calendarPill); flex-shrink: 0;
1043
- align-items: center; justify-content: center;
1044
- }
1045
-
1205
+ /* The numeral is its own 42px flex box so it centres on the 42px pill.
1206
+ Without this the content column top-aligns the glyph and the pill reads
1207
+ as offset — the cell is 48px, the pill 42px inset 3px, and only a
1208
+ 42px-tall numeral box shares the pill's centre line. */
1209
+ & .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; }
1046
1210
  /* the year/month wheel, laid over the grid it replaces */
1047
- & .crisp-datepicker-wheel-container {
1048
- position: absolute; z-index: 1; inset-inline: 0;
1049
- top: var(--crisp-size-menuRow); bottom: 0;
1050
- display: flex; align-items: center; overflow: hidden;
1051
- background: var(--crisp-bg-surface);
1052
- }
1211
+ & .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); }
1053
1212
  & .crisp-datepicker-wheel { width: 100%; }
1054
-
1055
- & .crisp-datepicker-continuous-scroll {
1056
- height: 100%; min-height: 0; overflow-y: auto;
1057
- overscroll-behavior: contain; scrollbar-width: none;
1058
- }
1213
+ & .crisp-datepicker-continuous-scroll { height: 100%; min-height: 0; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: none; }
1059
1214
  & .crisp-datepicker-continuous-scroll::-webkit-scrollbar { display: none; }
1060
1215
  & .crisp-datepicker-continuous-content { display: flex; flex-direction: column; }
1061
-
1062
1216
  /* announced, never drawn */
1063
- & .crisp-datepicker-live-region {
1064
- position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
1065
- overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
1066
- }
1217
+ & .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; }
1067
1218
  }
1068
1219
  .crisp-datepicker--visibleRange_month {
1069
1220
 
@@ -1097,7 +1248,10 @@
1097
1248
  & .crisp-datepickerfield-box[data-placeholder] .crisp-datepickerfield-trigger { color: var(--crisp-fg-muted); }
1098
1249
  & .crisp-datepickerfield-trigger svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
1099
1250
  /* Reuses TableToolbar's measured filter-chip remove button
1100
- (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). */
1251
+ (tabletoolbar.chrome.ts's .crisp-tabletoolbar-fchip-x) -- the same
1252
+ small inline "clear this value" affordance, not a freshly-measured
1253
+ one: no live Attio composition for a DatePickerField-equivalent
1254
+ clear was reachable this round (#394). */
1101
1255
  & .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; }
1102
1256
  & .crisp-datepickerfield-clear:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 10%, transparent); color: var(--crisp-fg-primary); }
1103
1257
  & .crisp-datepickerfield-panel { padding: var(--crisp-space-2); width: 300px; }
@@ -1111,10 +1265,8 @@
1111
1265
  & .crisp-dialog-backdrop { position: fixed; inset: 0; z-index: 60; background: var(--crisp-bg-scrim); }
1112
1266
  & .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); }
1113
1267
  & .crisp-dialog-positioner[data-variant="alert"] { align-items: center; padding: var(--crisp-space-4); }
1114
- & .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); } }
1115
- & .crisp-dialog-content[data-variant="alert"] { padding: 0; background: transparent; backdrop-filter: none; -webkit-backdrop-filter: none; box-shadow: none; }
1116
- & .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; }
1117
- & .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); }
1268
+ & .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); } }
1269
+ & .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; }
1118
1270
  & .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); }
1119
1271
  & .crisp-dialog-header svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
1120
1272
  & .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; }
@@ -1123,8 +1275,10 @@
1123
1275
  /* The discard-confirm's own stacked title + sentence (crisp#677) — no
1124
1276
  border, no close button, unlike the panel header above: measured
1125
1277
  live, both themes, 18px/24px 600-weight title (no scale token
1126
- 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
1127
- this file's own submit-key glyphs) then a 14px/20px 500-weight sentence 4px below it. */
1278
+ reaches 18px — the scale tops out at text.xl's 16px, so this is raw
1279
+ px per Golden Rule 3's "genuinely intrinsic size" exception, same as
1280
+ this file's own submit-key glyphs) then a 14px/20px 500-weight
1281
+ sentence 4px below it. */
1128
1282
  & .crisp-dialog-alert-header { display: flex; flex-direction: column; gap: var(--crisp-space-1); padding: var(--crisp-space-4) var(--crisp-space-4) 0; }
1129
1283
  & .crisp-dialog-alert-title { margin: 0; font-size: 18px; line-height: 24px; font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-primary); }
1130
1284
  & .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); }
@@ -1134,7 +1288,12 @@
1134
1288
  & .crisp-dialog-field { display: flex; flex-direction: column; gap: var(--crisp-space-1-5); }
1135
1289
  & .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); }
1136
1290
  & .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; }
1137
- & .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; }
1291
+ /* your-moon/crisp#953: bgBrandContrast, not bgBrandSolid --
1292
+ the accent (#0298d9) only clears 3.23:1 with white; the darker
1293
+ sibling clears 4.5:1+, same fix already applied to Button/Badge/
1294
+ Avatar (#897). Scoped to this one fill only -- Dialog's own frosted-
1295
+ surface identity (#934) is untouched here. */
1296
+ & .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; }
1138
1297
  & .crisp-dialog-submit-key { display: inline-flex; align-items: center; gap: var(--crisp-space-1); }
1139
1298
  & .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; }
1140
1299
  }
@@ -1208,7 +1367,9 @@
1208
1367
  & .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); }
1209
1368
  & .crisp-emptystate-title { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-primary); }
1210
1369
  & .crisp-emptystate-desc { max-width: 320px; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
1211
- /* #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. */
1370
+ /* #603: measured 8px gap between "Import CSV"/"Connect data source" --
1371
+ inline-flex so one action (the common case) or several lay out the
1372
+ same way, wrapping if the container is narrow. */
1212
1373
  & .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); }
1213
1374
  /* #603: Attio's zero-records "Learn more" card -- a label above a
1214
1375
  clickable icon+title tile. Left/start-aligned, unlike the rest of
@@ -1228,13 +1389,13 @@
1228
1389
  .crisp-emptystate--variant_page {
1229
1390
  gap: var(--crisp-space-2);
1230
1391
  & .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); }
1231
- & .crisp-emptystate-title { font-size: 20px; line-height: 24px; }
1392
+ & .crisp-emptystate-title { font-size: 20px; line-height: 24px; letter-spacing: var(--crisp-tracking-20); }
1232
1393
  & .crisp-emptystate-desc { max-width: 266px; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); line-height: 20px; }
1233
1394
  & .crisp-emptystate-action { margin-top: var(--crisp-space-2); }
1234
1395
  }
1235
1396
  .crisp-emptystate--variant_records {
1236
1397
  & .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; }
1237
- & .crisp-emptystate-title { font-size: 20px; line-height: 24px; }
1398
+ & .crisp-emptystate-title { font-size: 20px; line-height: 24px; letter-spacing: var(--crisp-tracking-20); }
1238
1399
  & .crisp-emptystate-desc { max-width: 266px; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); line-height: 20px; }
1239
1400
  }
1240
1401
 
@@ -1265,7 +1426,9 @@
1265
1426
  & .crisp-fileupload-remove:hover { color: var(--crisp-fg-primary); }
1266
1427
  & .crisp-fileupload-remove:focus-visible { outline-color: var(--crisp-stroke-focus-ring); }
1267
1428
  & .crisp-fileupload-errors { display: flex; flex-direction: column; gap: var(--crisp-space-0-5); margin: 0; padding: 0; list-style: none; }
1268
- /* 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 */
1429
+ /* out of flow while empty, so the always-mounted live region costs no
1430
+ layout: display:none would take it out of the accessibility tree, which
1431
+ is the one thing it exists to be in */
1269
1432
  & .crisp-fileupload-errors:empty { position: absolute; }
1270
1433
  & .crisp-fileupload-error { color: var(--crisp-bg-danger); font-size: var(--crisp-text-md); line-height: 18px; }
1271
1434
  }
@@ -1378,13 +1541,22 @@
1378
1541
  }
1379
1542
 
1380
1543
  .crisp-input {
1381
- 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;
1544
+ 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;
1382
1545
  &::after { content: ""; position: absolute; inset: 0; border-radius: inherit; border: 2px solid transparent; transition: border-color .1s ease; pointer-events: none; }
1383
1546
  &:focus-within::after { border-color: var(--crisp-stroke-field-focus); }
1384
1547
  & .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); }
1385
1548
  & .crisp-input-value::placeholder { color: var(--crisp-fg-muted); }
1386
1549
  & .crisp-input-value:disabled { opacity: 0.5; cursor: not-allowed; }
1387
1550
  &:has(.crisp-input-value:disabled) { opacity: 1; }
1551
+ /* crisp#634: measured live on Attio's own Settings > Profile read-only
1552
+ primary-email field -- a tinted field, not just muted text, driven off
1553
+ the native `readOnly` attribute. `ringField` (rgba(0,0,0,.06)) is an
1554
+ exact match for the measured ring; the 2%-fgPrimary tint reuses the
1555
+ same color-mix idiom every other soft crisp surface already uses.
1556
+ `border-color: transparent` neutralises #910's new real rest border
1557
+ so this measured ring isn't drawn twice. */
1558
+ &[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); }
1559
+ & .crisp-input-suffix { flex-shrink: 0; margin-inline-start: var(--crisp-space-1); }
1388
1560
  }
1389
1561
  .crisp-input--size_sm {
1390
1562
  height: var(--crisp-size-row); border-radius: var(--crisp-radius-md);
@@ -1395,7 +1567,7 @@
1395
1567
  & .crisp-input-value { padding-inline: var(--crisp-space-2-5); }
1396
1568
  }
1397
1569
  .crisp-input--size_large {
1398
- height: 36px; border-radius: 10px;
1570
+ height: 36px; border-radius: var(--crisp-radius-xl);
1399
1571
  & .crisp-input-value { padding-inline: var(--crisp-space-3); }
1400
1572
  }
1401
1573
  .crisp-input--invalid_true {
@@ -1448,8 +1620,8 @@
1448
1620
  font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
1449
1621
  position: fixed; z-index: 70; pointer-events: auto; box-sizing: border-box; display: flex; flex-direction: column; gap: var(--crisp-space-px);
1450
1622
  min-width: 200px; max-height: 360px; overflow-y: auto; padding: var(--crisp-space-1);
1451
- border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover);
1452
- 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);
1623
+ border-radius: var(--crisp-radius-3xl); background: var(--crisp-bg-popover);
1624
+ box-shadow: 0 4px 16px var(--crisp-shadow-select);
1453
1625
  transform-origin: top left; opacity: 1; transform: none; transition: opacity .08s ease, transform .08s ease;
1454
1626
  }
1455
1627
  .crisp-menu-content[data-align="end"] { transform-origin: top right; }
@@ -1497,6 +1669,43 @@
1497
1669
  }
1498
1670
  }
1499
1671
 
1672
+ .crisp-menusheet {
1673
+ display: contents; font-family: var(--crisp-font-sans);
1674
+ & .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; } }
1675
+ & .crisp-menusheet-positioner { position: fixed; inset: 0; z-index: 61; display: flex; align-items: flex-end; justify-content: center; }
1676
+ & .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%); } }
1677
+ & .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; }
1678
+ & .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 */ }
1679
+ & .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 */ }
1680
+ & .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; }
1681
+ & .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) */ }
1682
+ & .crisp-menusheet-group > * + * { border-top: 1px solid var(--crisp-stroke-group-divider); }
1683
+ /* menu-sheet.yaml:132-134 divider.strokeBottomWidth/strokeColor = 1px/$color.stroke.neutral-muted */
1684
+ & .crisp-menusheet-footer { flex-shrink: 0; padding-top: var(--crisp-space-2-5); /* menu-sheet.yaml:136 footer.paddingTop = $dimension.x2_5 (10px) */ }
1685
+ & .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; }
1686
+ & .crisp-menusheet-item:hover, & .crisp-menusheet-item:active { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, var(--crisp-bg-neutral-weak)); }
1687
+ /* menu-sheet-item.yaml:90-91 pressed.root.color = $color.bg.neutral-weak-pressed, approximated as a tint over the base fill */
1688
+ & .crisp-menusheet-item:active .crisp-menusheet-item-content { transform: scale(0.98); }
1689
+ /* menu-sheet-item.yaml:92 pressed.root.scaleScope = content */
1690
+ & .crisp-menusheet-item-icon { flex-shrink: 0; display: inline-flex; width: 22px; height: 22px; }
1691
+ /* menu-sheet-item.yaml:77 prefixIcon.size */
1692
+ & .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); }
1693
+ & .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 */ }
1694
+ & .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 */ }
1695
+ & .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); }
1696
+ /* menu-sheet-item.yaml:93-98 tone=neutral */
1697
+ & .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); }
1698
+ /* menu-sheet-item.yaml:99-104 tone=critical */
1699
+ }
1700
+
1701
+ @media (prefers-reduced-motion: reduce) {
1702
+ .crisp-menusheet, .crisp-menusheet * {
1703
+ animation-duration: 0.01ms !important;
1704
+ animation-iteration-count: 1 !important;
1705
+ transition-duration: 0.01ms !important;
1706
+ }
1707
+ }
1708
+
1500
1709
 
1501
1710
  /* pointer-events: auto (#375) -- see Select's matching comment. */
1502
1711
  .crisp-multiselect-listbox {
@@ -1510,13 +1719,19 @@
1510
1719
  }
1511
1720
  .crisp-multiselect-listbox[data-up] { transform-origin: bottom left; }
1512
1721
  .crisp-multiselect-listbox { @starting-style { opacity: 0; transform: scale(0.95); } }
1513
- /* LOCKED geometry (#481, guarded by build/spec-check.mjs): height 36px,
1514
- gap space["1.5"] (6px), no border-bottom -- an Attio measurement, not
1515
- part of this seed-parity pass. Only colours below follow Select. */
1516
- .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); }
1722
+ /* Seed geometry (build/spec-check.mjs guards these four properties
1723
+ directly), superseding #481's old Attio lock -- see this file's
1724
+ header comment and Select's matching .crisp-select-search comment
1725
+ for the full citation. An ordinary flex child of the listbox, so its
1726
+ own 8px gap provides the inset/seam on every side, same as Select. */
1727
+ .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); }
1517
1728
  .crisp-multiselect-search svg { flex-shrink: 0; }
1518
- .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); }
1729
+ .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); }
1519
1730
  .crisp-multiselect-search input::placeholder { color: var(--crisp-fg-select-placeholder); }
1731
+ /* Same header/body seam as Select's own .crisp-select-divider -- see
1732
+ that recipe's comment for the seed source (select.yaml's divider
1733
+ slot, stroke.neutral-muted). */
1734
+ .crisp-multiselect-divider { flex-shrink: 0; height: 1px; margin-inline: var(--crisp-space-4); background: var(--crisp-stroke-select-divider); }
1520
1735
  /* Same groupLabel treatment as Select's (13px/regular/neutral-subtle). */
1521
1736
  .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); }
1522
1737
  .crisp-multiselect-option {
@@ -1556,10 +1771,15 @@
1556
1771
  & .crisp-multiselect-trigger[aria-readonly="true"] { background: var(--crisp-bg-select-disabled); cursor: default; }
1557
1772
  & .crisp-multiselect-chips { flex: 1; min-width: 0; display: flex; flex-wrap: wrap; align-items: center; gap: var(--crisp-space-1); }
1558
1773
  & .crisp-multiselect-placeholder { color: var(--crisp-fg-select-placeholder); }
1559
- /* closeRotateDuration/openRotateDuration, same asymmetric timing as Select's own chevron (see that recipe's comment). */
1774
+ /* closeRotateDuration/openRotateDuration, same asymmetric timing as
1775
+ Select's own chevron (see that recipe's comment). */
1560
1776
  & .crisp-multiselect-chevron { flex-shrink: 0; color: var(--crisp-fg-neutral-muted); transition: transform .1s cubic-bezier(.35,0,.35,1); }
1561
1777
  & .crisp-multiselect-trigger[data-open] .crisp-multiselect-chevron { transform: rotate(180deg); transition-duration: .15s; }
1562
1778
  }
1779
+ .crisp-multiselect--size_small {
1780
+ & .crisp-multiselect-trigger { min-height: var(--crisp-size-menuRow); border-radius: var(--crisp-radius-md); gap: var(--crisp-space-1); padding: var(--crisp-space-0-5) var(--crisp-space-1-5) var(--crisp-space-0-5) var(--crisp-space-2); }
1781
+ & .crisp-multiselect-chevron { width: var(--crisp-size-icon); height: var(--crisp-size-icon); }
1782
+ }
1563
1783
  .crisp-multiselect--size_medium {
1564
1784
  & .crisp-multiselect-trigger { min-height: var(--crisp-size-selectTriggerMd); border-radius: var(--crisp-radius-md); gap: var(--crisp-space-2); padding: var(--crisp-space-1) var(--crisp-space-3-5); }
1565
1785
  & .crisp-multiselect-chevron { width: var(--crisp-size-icon); height: var(--crisp-size-icon); }
@@ -1618,6 +1838,9 @@
1618
1838
  &[data-segmented] { display: flex; gap: var(--crisp-space-1); background: transparent; overflow: visible; }
1619
1839
  &[data-segmented] .crisp-progress-seg { flex: 1; height: 100%; border-radius: var(--crisp-radius-full); background: color-mix(in srgb, var(--crisp-fg-primary) 8%, transparent); transition: background 0.2s; }
1620
1840
  &[data-segmented] .crisp-progress-seg[data-filled] { background: var(--crisp-bg-brand-solid); }
1841
+ &[data-variant="circular"] { display: inline-flex; height: auto; border-radius: 0; background: transparent; overflow: visible; }
1842
+ & .crisp-progress-circle-track { stroke: color-mix(in srgb, var(--crisp-bg-brand-solid) 14%, transparent); }
1843
+ & .crisp-progress-circle-fill { stroke: var(--crisp-bg-brand-solid); transition: stroke-dashoffset 0.25s ease; }
1621
1844
  }
1622
1845
 
1623
1846
  @media (prefers-reduced-motion: reduce) {
@@ -1709,13 +1932,15 @@
1709
1932
  & .crisp-resizablepanel-panel[data-floating="false"] { border-radius: 0; box-shadow: none; }
1710
1933
  & .crisp-resizablepanel-panel[data-floating="false"][data-side="left"] { border-inline-end: 1px solid var(--crisp-border-subtle); }
1711
1934
  & .crisp-resizablepanel-panel[data-floating="false"][data-side="right"] { border-inline-start: 1px solid var(--crisp-border-subtle); }
1712
- /* 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. */
1935
+ /* floating — position: fixed, 8px gutter, r10, the measured floating elevation.
1936
+ Rest is transform: none (Lesson 27); the off-canvas value is the @starting-style enter only. */
1713
1937
  & .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; }
1714
1938
  & .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))); } }
1715
1939
  & .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))); } }
1716
1940
  & .crisp-resizablepanel-inner { height: 100%; opacity: 1; }
1717
1941
  & .crisp-resizablepanel-panel[data-floating="true"] .crisp-resizablepanel-inner { opacity: 1; transition: opacity 0.14s; @starting-style { opacity: 0; } }
1718
- /* handle — variant "panel" (default): a 10px hit target, its ::before the only visible divider (1px rest -> 2px on hover/drag, brand-solid). */
1942
+ /* handle — variant "panel" (default): a 10px hit target, its ::before the
1943
+ only visible divider (1px rest -> 2px on hover/drag, brand-solid). */
1719
1944
  & .crisp-resizablepanel-handle { position: absolute; top: 0; width: 10px; height: 100%; z-index: 2; cursor: col-resize; background: transparent; touch-action: none; }
1720
1945
  & .crisp-resizablepanel-handle[data-side="left"] { right: 0; }
1721
1946
  & .crisp-resizablepanel-handle[data-side="right"] { left: 0; }
@@ -1723,9 +1948,10 @@
1723
1948
  & .crisp-resizablepanel-handle[data-side="left"]::before { right: 0; }
1724
1949
  & .crisp-resizablepanel-handle[data-side="right"]::before { left: 0; }
1725
1950
  & .crisp-resizablepanel-handle:hover::before, & .crisp-resizablepanel-handle[data-dragging="true"]::before { width: 2px; background: var(--crisp-bg-brand-solid); }
1726
- /* 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
1727
- ::before swap. */
1728
- /* .crisp-resizablepanel-handle.crisp-resizablepanel-handle--column (both classes) to
1951
+ /* handle — variant "column": a wider 7px hit target straddling a 1px
1952
+ divider (table column edges), with a real 3px inner bar whose opacity
1953
+ comes in on a 100ms DELAY — distinct from the panel variant's instant
1954
+ ::before swap. */ /* .crisp-resizablepanel-handle.crisp-resizablepanel-handle--column (both classes) to
1729
1955
  out-specificity the base [data-side] left/right rules above, which
1730
1956
  otherwise tie-break by source order alone and would win. */
1731
1957
  & .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; }
@@ -1776,10 +2002,34 @@
1776
2002
  }
1777
2003
  .crisp-select-listbox[data-up] { transform-origin: bottom left; }
1778
2004
  .crisp-select-listbox { @starting-style { opacity: 0; transform: scale(0.95); } }
1779
- .crisp-select-search { display: flex; align-items: center; gap: var(--crisp-space-2); height: 34px; flex-shrink: 0; margin: calc(-1 * var(--crisp-space-2)) calc(-1 * var(--crisp-space-2)) var(--crisp-space-2); padding: 0 var(--crisp-space-2-5); border-bottom: 1px solid var(--crisp-border-subtle); color: var(--crisp-fg-neutral-muted); }
2005
+ /* Composed from @seed-design/rootage's text-input.yaml
2006
+ (variant=outline,size=medium) -- seed's Select has no search field of
2007
+ its own to resolve this from (see the file header), so this reuses
2008
+ the SAME tokens #867 already resolved for the trigger at this size:
2009
+ minHeight x10 (var(--crisp-size-selectTriggerMd)), paddingX x3_5
2010
+ (var(--crisp-space-3-5)), gap x2 (var(--crisp-space-2)), cornerRadius r2
2011
+ (var(--crisp-radius-md)), strokeColor stroke.neutral-weak
2012
+ (var(--crisp-stroke-select-trigger)) -- a real bordered field sitting flush
2013
+ inside the listbox's own 8px padding, replacing the old bare
2014
+ 34px/no-border/bleeds-to-the-edge strip (crisp/#904). */
2015
+ .crisp-select-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); }
1780
2016
  .crisp-select-search svg { flex-shrink: 0; }
1781
- .crisp-select-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); }
2017
+ .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); }
1782
2018
  .crisp-select-search input::placeholder { color: var(--crisp-fg-select-placeholder); }
2019
+ /* select.yaml's own divider slot (marginX x4, height 1px, colour
2020
+ stroke.neutral-muted -- described upstream as "the line marking a
2021
+ boundary between semantic units: between sections, between content
2022
+ and an action area, between a header and a body" -- exactly the
2023
+ search-field/option-list boundary crisp#904's review flagged as
2024
+ reading too tight). Not the general-purpose separator #867 declined
2025
+ to build (nothing groups options with one) -- scoped to this one
2026
+ specific header/body seam, which now exists because searchable
2027
+ does. Sits as an ordinary flex child, so the listbox's own 8px gap
2028
+ still applies on both sides of it -- total visual seam is
2029
+ gap+1px+gap (17px), deliberately more than the 8px between two
2030
+ plain option rows, so it reads as a section break, not a wider list
2031
+ gap. */
2032
+ .crisp-select-divider { flex-shrink: 0; height: 1px; margin-inline: var(--crisp-space-4); background: var(--crisp-stroke-select-divider); }
1783
2033
  .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); }
1784
2034
  /* select.yaml's groupLabel -- font-size/line-height/weight are the same
1785
2035
  across sizes, only paddingY differs (medium 8px, large 10px). */
@@ -1809,21 +2059,27 @@
1809
2059
  .crisp-select {
1810
2060
  position: relative; display: inline-flex; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
1811
2061
  & .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); }
1812
- /* 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
1813
- never moves. */
1814
- /* 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). */
2062
+ /* seed defines no hover state for this trigger -- only pressed, invalid,
2063
+ disabled and readonly. The scale-down is scoped to the CONTENT
2064
+ (seed's scaleScope: content), not the button itself, so the border
2065
+ never moves. */ /* Only the value scales, not the chevron: the chevron's own transform is
2066
+ its open/close rotation, and a scale would fight it for one shared
2067
+ CSS property when both are true at once (pressing while open). */
1815
2068
  & .crisp-select-trigger:active:not(:disabled):not([aria-readonly="true"]) { background: var(--crisp-bg-select-pressed); }
1816
2069
  & .crisp-select-trigger:active:not(:disabled):not([aria-readonly="true"]) .crisp-select-value { transform: scale(0.98); }
1817
2070
  & .crisp-select-trigger:focus-visible { outline: 2px solid var(--crisp-stroke-focus-ring); outline-offset: -2px; }
1818
2071
  & .crisp-select-trigger:disabled { background: var(--crisp-bg-select-disabled); cursor: not-allowed; }
1819
2072
  & .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); }
1820
- /* readonly: same fill as disabled, but the value/placeholder text stays normal ink -- it reads as "answered and locked", not "answered and
2073
+ /* readonly: same fill as disabled, but the value/placeholder text stays
2074
+ normal ink -- it reads as "answered and locked", not "answered and
1821
2075
  greyed out" (select-trigger.yaml's readonly state only overrides
1822
2076
  root.color; value/placeholder keep their enabled colours). */
1823
2077
  & .crisp-select-trigger[aria-readonly="true"] { background: var(--crisp-bg-select-disabled); cursor: default; }
1824
2078
  & .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); }
1825
2079
  & .crisp-select-placeholder { color: var(--crisp-fg-select-placeholder); }
1826
- /* closeRotateDuration/closeRotateTimingFunction: 100ms, seed's "easing" curve. Overridden to 150ms on open (openRotateDuration) below -- same curve both ways, only the duration differs. */
2080
+ /* closeRotateDuration/closeRotateTimingFunction: 100ms, seed's "easing"
2081
+ curve. Overridden to 150ms on open (openRotateDuration) below --
2082
+ same curve both ways, only the duration differs. */
1827
2083
  & .crisp-select-chevron { flex-shrink: 0; color: var(--crisp-fg-neutral-muted); transition: transform .1s cubic-bezier(.35,0,.35,1); }
1828
2084
  & .crisp-select-trigger[data-open] .crisp-select-chevron { transform: rotate(180deg); transition-duration: .15s; }
1829
2085
  }
@@ -1833,6 +2089,16 @@
1833
2089
  .crisp-select--width_auto {
1834
2090
  width: auto;
1835
2091
  }
2092
+ .crisp-select--size_small {
2093
+ & .crisp-select-trigger { height: var(--crisp-size-menuRow); border-radius: var(--crisp-radius-md); gap: var(--crisp-space-1-5); padding-inline: var(--crisp-space-2-5); }
2094
+ & .crisp-select-value, & .crisp-select-placeholder { font-size: var(--crisp-text-lg); line-height: 20px; }
2095
+ & .crisp-select-chevron { width: var(--crisp-size-icon); height: var(--crisp-size-icon); }
2096
+ .crisp-select-option { gap: var(--crisp-space-2); }
2097
+ .crisp-select-option-label { font-size: var(--crisp-text-lg); line-height: 19px; }
2098
+ .crisp-select-option-description { font-size: var(--crisp-text-sm); line-height: 16px; }
2099
+ .crisp-select-option-check { width: var(--crisp-space-3); height: var(--crisp-space-3); }
2100
+ .crisp-select-label { padding-top: var(--crisp-space-2); }
2101
+ }
1836
2102
  .crisp-select--size_medium {
1837
2103
  & .crisp-select-trigger { height: var(--crisp-size-selectTriggerMd); border-radius: var(--crisp-radius-md); gap: var(--crisp-space-2); padding-inline: var(--crisp-space-3-5); }
1838
2104
  & .crisp-select-value, & .crisp-select-placeholder { font-size: var(--crisp-text-lg); line-height: 19px; }
@@ -1896,42 +2162,21 @@
1896
2162
  }
1897
2163
 
1898
2164
  .crisp-slider {
1899
- 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 */
1900
- & .crisp-slider-thumblabel {
1901
- position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
1902
- overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
1903
- }
2165
+ 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
2166
+ thumb's name — read, never drawn */
2167
+ & .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; }
1904
2168
  & .crisp-slider-control { position: relative; height: 26px; display: flex; align-items: center; }
1905
-
1906
- & .crisp-slider-track {
1907
- position: relative; flex-grow: 1; height: var(--crisp-space-1);
1908
- border-radius: var(--crisp-radius-full); background: var(--crisp-bg-neutral-weak);
1909
- overflow: hidden;
1910
- }
1911
-
2169
+ & .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; }
1912
2170
  & .crisp-slider-range { position: absolute; height: 100%; background: var(--crisp-bg-brand-solid); }
1913
2171
  & .crisp-slider-range[data-dir="ltr"] { left: var(--range-start); right: var(--range-end); }
1914
2172
  & .crisp-slider-range[data-dir="rtl"] { right: var(--range-start); left: var(--range-end); }
1915
-
1916
- & .crisp-slider-thumb {
1917
- position: absolute; top: 50%;
1918
- width: var(--crisp-space-5); height: var(--crisp-space-5);
1919
- transform: translate(-50%, -50%);
1920
- border: none; background: none; padding: 0;
1921
- }
2173
+ & .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; }
1922
2174
  & .crisp-slider-thumb[data-dir="ltr"] { left: calc(var(--thumb-position) * 1% + var(--thumb-offset)); }
1923
2175
  & .crisp-slider-thumb[data-dir="rtl"] { right: calc(var(--thumb-position) * 1% + var(--thumb-offset)); }
1924
- & .crisp-slider-thumb::after {
1925
- content: ""; position: absolute; inset: 0;
1926
- 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);
1927
- outline: 2px solid transparent; outline-offset: 2px;
1928
- transition: transform .2s ease, outline-color .2s ease;
1929
- cursor: grab;
1930
- }
2176
+ & .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; }
1931
2177
  & .crisp-slider-thumb[data-thumb-dragging]::after { transform: scale(1.2); cursor: grabbing; }
1932
2178
  & .crisp-slider-thumb:focus { outline: none; }
1933
2179
  & .crisp-slider-thumb:focus-visible::after { outline-color: var(--crisp-stroke-focus-ring); }
1934
-
1935
2180
  &[data-disabled] { cursor: not-allowed; }
1936
2181
  &[data-disabled] .crisp-slider-range { background: var(--crisp-fg-disabled); }
1937
2182
  &[data-disabled] .crisp-slider-thumb::after { cursor: not-allowed; }
@@ -1945,6 +2190,31 @@
1945
2190
  }
1946
2191
  }
1947
2192
 
2193
+ .crisp-snackbar {
2194
+ & .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; }
2195
+ & .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 */ }
2196
+ & .crisp-snackbar-content { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: var(--crisp-space-0-5); }
2197
+ /* snackbar.yaml:124 prefixIcon.paddingRight = $dimension.x0_5 (2px), used as the icon-to-message gap */
2198
+ & .crisp-snackbar-icon { flex-shrink: 0; display: inline-flex; }
2199
+ & .crisp-snackbar[data-variant="positive"] .crisp-snackbar-icon { color: var(--crisp-bg-success); }
2200
+ /* snackbar.yaml:138-139 variant=positive prefixIcon.color = $color.fg.positive */
2201
+ & .crisp-snackbar[data-variant="critical"] .crisp-snackbar-icon { color: var(--crisp-bg-danger); }
2202
+ /* snackbar.yaml:142-143 variant=critical prefixIcon.color = $color.fg.critical */
2203
+ & .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); }
2204
+ /* snackbar.yaml:117-121: message.color = $color.fg.neutral-inverted, fontSize/lineHeight = $font-size.t4/$line-height.t4 (14px/19px), fontWeight = regular */
2205
+ & .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); }
2206
+ & .crisp-snackbar-action:active { transform: scale(0.98); }
2207
+ /* snackbar.yaml:133-134 pressed.actionButton.scaleScope = self */
2208
+ }
2209
+
2210
+ @media (prefers-reduced-motion: reduce) {
2211
+ .crisp-snackbar, .crisp-snackbar * {
2212
+ animation-duration: 0.01ms !important;
2213
+ animation-iteration-count: 1 !important;
2214
+ transition-duration: 0.01ms !important;
2215
+ }
2216
+ }
2217
+
1948
2218
  @keyframes crisp-spin { to { transform: rotate(360deg); } }
1949
2219
  .crisp-spinner {
1950
2220
  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;
@@ -1980,7 +2250,9 @@
1980
2250
  /* caption's own status colour, opt-in via data-tone: not a new measured
1981
2251
  value — the same fg*Soft tokens Badge's tone already uses (and Badge's
1982
2252
  own doc comment records why: a dedicated soft-text token, not the
1983
- 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
2253
+ tone's accent colour, is the one that clears AA). No "ok" reading on
2254
+ Badge itself; fgSuccessSoft is Badge's own "success" tone's text
2255
+ colour, reused here under the name a caption's own good-news state
1984
2256
  reads as. */
1985
2257
  & .crisp-statcard-caption[data-tone="ok"] { color: var(--crisp-fg-success-soft); }
1986
2258
  & .crisp-statcard-caption[data-tone="warning"] { color: var(--crisp-fg-warning-soft); }
@@ -2020,40 +2292,58 @@
2020
2292
  }
2021
2293
 
2022
2294
  .crisp-table {
2023
- 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
2295
+ display: block; box-sizing: border-box; background: var(--crisp-bg-surface); /* #554: both axes -- a consumer bounds the height via `scrollProps`
2296
+ (style/className on this element), and the header/footer below pin
2297
+ inside it exactly the way Attio's own grid does ("Both the header and
2024
2298
  the summary row are sticky, on the scroll container rather than the
2025
2299
  cells" -- docs/study/attio-table.md). Unbounded, `overflow: auto`
2026
2300
  shows no vertical scrollbar and nothing sticks, so this is a no-op
2027
2301
  for every existing consumer that never set a height. */
2028
2302
  & .crisp-table-scroll { overflow: auto; }
2029
2303
  & 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); }
2030
- /* #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
2304
+ /* #465: 12px, not crisp's old 8px -- measured on a live header cell
2305
+ (padding: 0 12px) and independently on a body cell's text inset
2031
2306
  (getBoundingClientRect delta from the cell edge). Matches
2032
2307
  ColumnCalc's own already-correct 12px trigger inset. */
2033
2308
  & 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; }
2034
2309
  /* #554: sticky on the vertical axis, mirroring RecordsTable's own
2035
2310
  `.crisp-recordstable-th`. z-index 2 sits above a plain body cell (auto)
2036
- 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
2311
+ and below a sticky-column corner cell (DataTable's `th[data-sticky]`,
2312
+ z-index 3), so the corner always wins.
2313
+ `thead th`, not a bare `th` (crisp#833/#835): DataTable's primary
2037
2314
  column renders its BODY cell as a real `<th scope="row">` (#779, for
2038
2315
  a11y), and a bare `th` here matched that one too -- every property in
2039
2316
  this rule leaked onto it: `height: tableHeader` (40px) stretched the
2040
2317
  whole body row to 40px instead of 36 (#833, confirmed live on the
2041
2318
  showcase, every td in the row equalized to the one th); `color:
2042
- color-mix(...63%...)` muted the primary column's own text below every sibling `<td>`'s full-opacity color (#835, re-measured live against
2043
- 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
2044
- 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. */
2319
+ color-mix(...63%...)` muted the primary column's own text below every
2320
+ sibling `<td>`'s full-opacity color (#835, re-measured live against
2321
+ Attio's own header labels -- including a genuinely computed one,
2322
+ "Twitter follower count" -- all fully opaque `rgb(16,17,18)`, no
2323
+ muting at all, so this drops the color-mix entirely rather than
2324
+ narrowing it); `position:sticky; top:0` tried to stick a BODY row
2325
+ to the scroll container's top; the extra `border-top` doubled that
2326
+ row's own grid line. `thead th` simply never matches
2327
+ `tbody th[scope=row]` at all, so none of this reaches it; the base
2328
+ `& th, & td` rule above (36px, one border-bottom, full opacity via
2329
+ `& table`'s own color) applies to it like every other cell. */
2045
2330
  & 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; }
2046
2331
  & td { color: var(--crisp-fg-primary); background: var(--crisp-bg-surface); }
2047
- /* 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
2048
- #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
2332
+ /* crisp#851 -- a REGRESSION from #833/#835/#848's own fix: scoping the
2333
+ header rule above to `thead th` correctly stopped it leaking onto
2334
+ the primary column's body cell (a real `<th scope="row">` since
2335
+ #779), but that same leaked rule's `font-weight: var(--crisp-weight-medium)`
2336
+ was the ONLY thing overriding the browser's own UA stylesheet default
2049
2337
  for `<th>` (`font-weight: bold; text-align: center`) on that cell --
2050
2338
  once the leak stopped, a plain-text primary column fell through to
2051
2339
  bold+centred instead of matching its sibling `<td>`s (`400`/start,
2052
- Attio's own plain-text primary cell). `text-align` was already safe (the base `& th, & td` rule's own `text-align: start` above is
2340
+ Attio's own plain-text primary cell). `text-align` was already safe
2341
+ (the base `& th, & td` rule's own `text-align: start` above is
2053
2342
  author-origin and always beats the UA stylesheet regardless of
2054
2343
  specificity) but is restated here explicitly, defensively -- cheap,
2055
2344
  and removes any doubt. `type: "entity"` primary columns are a
2056
- separate case (Attio's own entity/record names are 500, not 400) -- see `.crisp-datatable-entity-name`'s own new explicit weight in
2345
+ separate case (Attio's own entity/record names are 500, not 400) --
2346
+ see `.crisp-datatable-entity-name`'s own new explicit weight in
2057
2347
  datatable.recipe.ts, which wins here by class specificity. */
2058
2348
  & tbody th { font-weight: var(--crisp-weight-regular); text-align: start; }
2059
2349
  & th:last-child, & td:last-child { border-inline-end: none; }
@@ -2097,12 +2387,18 @@
2097
2387
  & .crisp-tabletoolbar-search svg { flex-shrink: 0; }
2098
2388
  & .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; }
2099
2389
  & .crisp-tabletoolbar-search input::placeholder { color: var(--crisp-fg-muted); }
2100
- /* 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
2101
- (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. */
2390
+ /* crisp/#830: every trigger label here (Sort, Filter, View settings,
2391
+ Import/Export -- all compose this one shared .tool rule) read the
2392
+ "md" text size, 13px. Re-measured on Attio's own trigger LEAF node
2393
+ (the button root itself inherits Attio's own 10px rem base and is a
2394
+ red herring, the same trap the original "10px font" layout-audit
2395
+ finding fell into): every label leaf is 14px/500/lh20, the "lg" text
2396
+ size. */
2102
2397
  & .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; }
2103
2398
  & .crisp-tabletoolbar-tool:hover { background: var(--crisp-bg-raised); }
2104
2399
  & .crisp-tabletoolbar-tool svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
2105
- /* 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
2400
+ /* crisp/#772: re-measured live on Sort with an actual sort applied --
2401
+ an active trigger's own fill is white (bgSurface), the same as the
2106
2402
  base .tool rule above now already sets; this override existed to
2107
2403
  distinguish active from a base state that used to be transparent.
2108
2404
  Kept explicit (not just deleted) so a future change to the base
@@ -2113,13 +2409,23 @@
2113
2409
  /* crisp/#772 follow-up, re-measured live 2026-09-25: an EMPTY Sort/Filter
2114
2410
  trigger (no sort/condition set) isn't a solid-ring chip at all -- it's
2115
2411
  a dashed ::before (1px dashed, border-radius: inherit, absolutely
2116
- 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
2412
+ positioned inset: 0) over a barely-there fill, confirmed on Attio's
2413
+ own empty Filter chip (its ::before border computes to exactly
2414
+ "1px dashed rgba(0, 0, 0, 0.06)"). Once a value IS set, the trigger
2415
+ drops the dashed pseudo and picks up the base tool's own solid ring
2117
2416
  instead (the --active modifier, unchanged) -- confirmed on Sort with
2118
2417
  an actual sort applied. color-mix (not the raw rgba Attio computes
2119
2418
  to) so both the border and the fill flip correctly in dark mode, the
2120
2419
  same reasoning --ghost above already uses. Only wired into
2121
- 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.
2122
- 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
2420
+ TableToolbar itself (tabletoolbar.tsx) this pass -- RecordsTable's
2421
+ own Sort/Filter triggers share this same chrome function but are
2422
+ deprecated, not touched here.
2423
+ crisp/#844, correcting #831: the label colour was added here later,
2424
+ muting only the FILTER trigger (via a "--filter" modifier), on the
2425
+ assumption that was a Filter-specific tone distinct from Sort's.
2426
+ Re-measured live and that premise was wrong -- Sort's own resting
2427
+ label ("Sort", no condition set) is the exact SAME muted rgba(0,0,0,.5)
2428
+ as Filter's, not full-strength. (The rgba(0,0,0,.63) that looked like
2123
2429
  "Sort's colour" in an earlier pass is a different thing entirely: the
2124
2430
  "Sorted by" PREFIX word inside an ACTIVE sort chip, #788 -- a
2125
2431
  compound-label treatment that only exists once a sort is applied, not
@@ -2206,17 +2512,21 @@
2206
2512
  & .crisp-tabletoolbar-fchip-x:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 10%, transparent); color: var(--crisp-fg-primary); }
2207
2513
  & .crisp-tabletoolbar-spacer { flex: 1; }
2208
2514
  & .crisp-tabletoolbar-count { font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
2209
- & .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; }
2210
- & .crisp-tabletoolbar-new:hover { background: var(--crisp-bg-brand-solid-pressed); }
2211
-
2515
+ /* your-moon/crisp#953: bgBrandContrast, not bgBrandSolid --
2516
+ the accent (#0298d9) only clears 3.23:1 with white; the darker sibling
2517
+ clears 4.5:1+, same fix already applied to Button/Badge/Avatar (#897).
2518
+ Hover is a brightness filter on the new fill, not bgBrandSolidPressed
2519
+ (that token stays tuned for the lighter accent's own consumers). */
2520
+ & .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; }
2521
+ & .crisp-tabletoolbar-new:hover { filter: brightness(0.96); }
2212
2522
  & .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; }
2213
2523
  & .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; }
2214
2524
  & .crisp-tabletoolbar-fcond-seg svg { flex-shrink: 0; }
2215
- & .crisp-tabletoolbar-fcond[data-complete] .crisp-tabletoolbar-fcond-seg--field,
2216
- & .crisp-tabletoolbar-fcond[data-complete] .crisp-tabletoolbar-fcond-seg--val { background: var(--crisp-bg-surface); }
2525
+ & .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); }
2217
2526
  & .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; }
2218
2527
  & .crisp-tabletoolbar-fcond-seg--field svg { color: var(--crisp-fg-muted); }
2219
- /* #592: a nested pick's field segment ("Team › Twitter follower count") — measured live: BOTH path segments the same colour (chip
2528
+ /* #592: a nested pick's field segment ("Team › Twitter follower
2529
+ count") — measured live: BOTH path segments the same colour (chip
2220
2530
  context only; the picker's own menu rows keep the parent muted, see
2221
2531
  tabletoolbar.chrome.ts), each independently truncated. Attio's own
2222
2532
  layout gives the leaf segment priority (it stays fully readable
@@ -2242,8 +2552,7 @@
2242
2552
  & .crisp-tabletoolbar-sort-field-picker { position: relative; display: inline-flex; flex: 1; min-width: 0; }
2243
2553
  & .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; }
2244
2554
  & .crisp-tabletoolbar-sort-field-trigger:hover { background: var(--crisp-bg-raised); }
2245
- & .crisp-tabletoolbar-sort-field-trigger[aria-expanded="true"],
2246
- & .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); }
2555
+ & .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); }
2247
2556
  & .crisp-tabletoolbar-sort-field-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: start; }
2248
2557
  & .crisp-tabletoolbar-sort-field-picker .crisp-tabletoolbar-menu { min-width: 200px; }
2249
2558
  & .crisp-tabletoolbar-menu--val { min-width: 134px; }
@@ -2251,18 +2560,10 @@
2251
2560
  & .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); }
2252
2561
  & .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; }
2253
2562
  & .crisp-tabletoolbar-datepop-preset:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 8%, transparent); }
2254
-
2255
2563
  & .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; }
2256
-
2257
2564
  & .crisp-tabletoolbar-sorted-prefix { color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
2258
- & .crisp-tabletoolbar-sorted-count { display: inline-flex; align-items: center; height: 16px; box-sizing: border-box;
2259
- padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-sm); background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent);
2260
- 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); }
2261
-
2262
- & .crisp-tabletoolbar-tool-count { display: inline-flex; align-items: center; height: 16px; box-sizing: border-box;
2263
- padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-sm); background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent);
2264
- 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);
2265
- font-variant-numeric: tabular-nums; }
2565
+ & .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); }
2566
+ & .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; }
2266
2567
  [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
2267
2568
  }
2268
2569
 
@@ -2278,17 +2579,31 @@
2278
2579
  display: flex; flex-direction: column; min-height: 0; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
2279
2580
  & .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; }
2280
2581
  & .crisp-tabs-list::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px; background: var(--crisp-border-subtle); }
2582
+ /* crisp#908: an alternative to the "+N more" collapse above (crisp#863) --
2583
+ a consumer opts into a single scrolling row instead (Naver/Kakao's own
2584
+ pattern, docs/study/korean/), never both at once. No new visual value:
2585
+ reuses the same list gap/tab geometry, only adds overflow-x + snap. */
2586
+ & .crisp-tabs-list[data-overflow="scroll"] { overflow-x: auto; flex-wrap: nowrap; scroll-snap-type: x mandatory; scrollbar-width: none; }
2587
+ & .crisp-tabs-list[data-overflow="scroll"]::-webkit-scrollbar { display: none; }
2588
+ & .crisp-tabs-list[data-overflow="scroll"] .crisp-tabs-tab { flex-shrink: 0; scroll-snap-align: start; }
2281
2589
  & .crisp-tabs-leading { display: inline-flex; align-items: center; margin-inline-end: var(--crisp-space-1); }
2282
- & .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; }
2590
+ & .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; }
2283
2591
  & .crisp-tabs-tab svg { flex-shrink: 0; opacity: 0.85; }
2284
2592
  & .crisp-tabs-tab:hover:not([data-selected]) { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); color: var(--crisp-fg-primary); }
2285
- & .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); }
2593
+ & .crisp-tabs-tab[data-selected] { color: var(--crisp-fg-primary); }
2286
2594
  & .crisp-tabs-tab:focus-visible { outline: 2px solid var(--crisp-bg-brand-solid); outline-offset: 1px; }
2287
2595
  & .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); }
2288
2596
  & .crisp-tabs-tab[data-selected] .crisp-tabs-tab-badge { color: var(--crisp-fg-primary); }
2289
2597
  & .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; }
2290
- /* 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/
2291
- 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
2598
+ /* crisp#863: the "+N more" overflow trigger, measured live -- its
2599
+ computed style is identical to a plain .crisp-tabs-tab (same pill,
2600
+ same 6px icon/label gap the tab class already gives via
2601
+ space["1.5"]), and the dropdown it opens is crisp's own Menu/
2602
+ MenuItem chrome (already matches Attio's popover, nothing new to
2603
+ style there). Only the wrapper needs a rule: an overflowed tab (or
2604
+ the trigger itself, when nothing is hidden) is taken out of the
2605
+ flex row and out of the a11y tree without ever making its own
2606
+ offsetWidth unmeasurable the way display:none would -- that's what
2292
2607
  lets the tabs.tsx effect recompute the cutoff from cached widths on
2293
2608
  every resize with no flash of an unclipped row. */
2294
2609
  & .crisp-tabs-more { display: inline-flex; }
@@ -2321,7 +2636,7 @@
2321
2636
  color: var(--crisp-fg-neutral-muted);
2322
2637
  }
2323
2638
  .crisp-text--tone_brand {
2324
- color: var(--crisp-bg-brand-solid);
2639
+ color: var(--crisp-fg-brand-contrast);
2325
2640
  }
2326
2641
  .crisp-text--tone_danger {
2327
2642
  color: var(--crisp-fg-danger);
@@ -2329,24 +2644,21 @@
2329
2644
 
2330
2645
  .crisp-textarea {
2331
2646
  position: relative; display: block; width: 100%; font-family: var(--crisp-font-sans);
2332
- & .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; }
2647
+ & .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; }
2333
2648
  & .crisp-textarea-field::placeholder { color: var(--crisp-fg-muted); }
2334
2649
  & .crisp-textarea-field:disabled { opacity: 0.5; cursor: not-allowed; }
2335
2650
  /* seed-design's focus treatment, same as Input: a 2px ring on an ::after
2336
2651
  overlay that is present-but-transparent at rest, so focusing never
2337
2652
  shifts the box, animating only its colour. */
2338
2653
  & .crisp-textarea-wrap { position: relative; display: block; }
2339
- & .crisp-textarea-wrap::after {
2340
- content: ""; position: absolute; inset: 0; border-radius: var(--crisp-radius-md);
2341
- border: 2px solid transparent; transition: border-color .1s ease;
2342
- pointer-events: none;
2343
- }
2654
+ & .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; }
2344
2655
  & .crisp-textarea-wrap:focus-within::after { border-color: var(--crisp-stroke-field-focus); }
2345
2656
  &[data-invalid] .crisp-textarea-wrap::after { border-color: var(--crisp-bg-danger); }
2346
2657
  &[data-invalid] .crisp-textarea-wrap:focus-within::after { border-color: var(--crisp-bg-danger); }
2347
2658
  &[data-counted] .crisp-textarea-field { padding-bottom: var(--crisp-space-5); }
2348
- & .crisp-textarea-count { position: absolute; inset-inline-end: 14px; bottom: var(--crisp-space-2-5); font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); pointer-events: none; }
2659
+ & .crisp-textarea-count { position: absolute; inset-inline-end: 14px; bottom: var(--crisp-space-2-5); display: flex; align-items: center; gap: var(--crisp-space-1); font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); pointer-events: none; }
2349
2660
  & .crisp-textarea-count[data-over] { color: var(--crisp-bg-danger); }
2661
+ & .crisp-textarea-progress { flex-shrink: 0; }
2350
2662
  }
2351
2663
 
2352
2664
  @media (prefers-reduced-motion: reduce) {
@@ -2362,11 +2674,11 @@
2362
2674
  &:hover { text-decoration: underline; }
2363
2675
  }
2364
2676
  .crisp-textlink--tone_brand {
2365
- font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-bg-brand-solid);
2677
+ font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-brand-contrast);
2366
2678
  }
2367
2679
  .crisp-textlink--tone_muted {
2368
2680
  font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-regular); color: var(--crisp-fg-muted);
2369
- &:hover { color: var(--crisp-bg-brand-solid); }
2681
+ &:hover { color: var(--crisp-fg-brand-contrast); }
2370
2682
  }
2371
2683
  .crisp-textlink--size_sm {
2372
2684
  font-size: var(--crisp-text-sm);
@@ -2431,14 +2743,16 @@
2431
2743
  .crisp-wheelpicker {
2432
2744
  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 */
2433
2745
  & .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; }
2434
- /* 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. */
2746
+ /* fades the rows furthest from the centre, so the column reads as a wheel
2747
+ rather than a list. 40% of the viewport, capped, top and bottom. */
2435
2748
  & .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% ); }
2436
2749
  & .crisp-wheelpicker-columns { display: flex; width: 100%; height: 100%; justify-content: center; }
2437
2750
  & .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); }
2438
2751
  & .crisp-wheelpicker-column::-webkit-scrollbar { display: none; }
2439
2752
  & .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; }
2440
2753
  & .crisp-wheelpicker-item[data-selected] { color: var(--crisp-fg-primary); }
2441
- /* 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 */
2754
+ /* the ring is drawn on the SELECTED row, and only for keyboard focus:
2755
+ a pointer user has just tapped the row they meant and does not need it */
2442
2756
  & .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); }
2443
2757
  }
2444
2758
  .crisp-wheelpicker--size_small {
@@ -2492,7 +2806,11 @@
2492
2806
  & .crisp-authshell-progress { position: absolute; top: 0; left: 0; width: 100%; height: 2px; background: var(--crisp-border-default); }
2493
2807
  & .crisp-authshell-progress-fill { height: 2px; background: var(--crisp-fg-primary); border-radius: 4px; transition: width .2s; }
2494
2808
  & .crisp-authshell-logo { margin-top: var(--crisp-space-8); min-height: 24px; display: flex; align-items: center; gap: var(--crisp-space-2-5); }
2495
- & .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; }
2809
+ /* your-moon/crisp#953: bgBrandContrast, not bgBrandSolid --
2810
+ the accent (#0298d9) only clears 3.23:1 with white; the darker
2811
+ sibling clears 4.5:1+, same fix already applied to Button/Badge/
2812
+ Avatar (#897). */
2813
+ & .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; }
2496
2814
  & .crisp-authshell-logo-text { display: flex; flex-direction: column; line-height: 1.2; }
2497
2815
  & .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); }
2498
2816
  & .crisp-authshell-logo-subtitle { font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); }
@@ -2508,7 +2826,8 @@
2508
2826
  & .crisp-authshell-form { align-self: stretch; display: flex; flex-direction: column; gap: 28px; }
2509
2827
  & .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); }
2510
2828
  & .crisp-authshell-footer a { color: inherit; font-weight: var(--crisp-weight-medium); text-decoration: underline; }
2511
- /* 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
2829
+ /* Auth-surface control treatment: flatter than the in-app chrome — a plain
2830
+ hairline ring instead of elevation.field's soft glow, and a 36px (not
2512
2831
  40px) submit. Scoped to .crisp-authshell-body so a consumer keeps using
2513
2832
  crisp's Input / FormField / Button and gets the auth treatment for free. */
2514
2833
  & .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); }
@@ -2516,10 +2835,10 @@
2516
2835
  & .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; }
2517
2836
  & .crisp-authshell-body .crisp-input:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
2518
2837
  /* Same three-layer brand shadow as Button's primary intent, but the auth
2519
- 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. */
2838
+ button's inset ring is measured at rgba(0,0,0,0.06) — a hairline, not
2839
+ the in-app button's rgba(255,255,255,0.07) highlight. */
2520
2840
  & .crisp-authshell-body .crisp-button { height: 36px; padding-inline: var(--crisp-space-3); border-radius: 10px; }
2521
2841
  & .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); }
2522
-
2523
2842
  /* The preview pane drops out in JS on narrow viewports (measured: two
2524
2843
  panes at 1393px, one at 954px), so 1200px is a chosen threshold inside
2525
2844
  that bracket, not a measured value. A CONTAINER query, not a media
@@ -2550,19 +2869,29 @@
2550
2869
  & .crisp-board-density button[data-state="on"] { background: var(--crisp-bg-surface); color: var(--crisp-fg-primary); }
2551
2870
  & .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; }
2552
2871
  & .crisp-board-col { display: flex; flex-direction: column; width: 268px; flex-shrink: 0; max-height: 100%; box-sizing: border-box; transition: width 0.15s; }
2553
- /* #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. */
2872
+ /* #718: a collapsed column is a 52px vertical rail -- header only (dot
2873
+ + count stacked above a vertical-rl title), body/footer hidden
2874
+ entirely. Measured live: 52px width, 8px 0 padding on the header,
2875
+ 14px/600 title text. */
2554
2876
  & .crisp-board-col[data-density="collapse"] { width: 52px; }
2555
2877
  & .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); }
2556
- /* 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). */
2878
+ /* Measured stacking order (top to bottom): dot, count, THEN the vertical
2879
+ title -- reordered visually via `order`, not by changing the JSX/DOM
2880
+ order, which stays dot/title/count (reading order, unaffected by
2881
+ density). */
2557
2882
  & .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); }
2558
2883
  & .crisp-board-col[data-density="collapse"] .crisp-board-col-count { order: 0; }
2559
2884
  & .crisp-board-col[data-density="collapse"] .crisp-board-col-body, & .crisp-board-col[data-density="collapse"] .crisp-board-col-footer { display: none; }
2560
2885
  & .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; }
2561
2886
  & .crisp-board-col-dot { width: 12px; height: 12px; border-radius: var(--crisp-radius-full); flex-shrink: 0; }
2562
2887
  & .crisp-board-col-title { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-primary); }
2563
- /* 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
2888
+ /* crisp#697: HANDOFF's #472 note claimed the count pill's
2889
+ color-mix(fgPrimary, 63%, transparent) reproduced the measured
2890
+ rgb(80,81,85) -- it doesn't; 63% of fgPrimary composited on white
2564
2891
  paints rgb(104,105,105), visibly lighter. Text now reads
2565
- 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. */
2892
+ fgNeutralSoft directly (the same rgb(80,81,85) token Badge's neutral
2893
+ tone uses, #696), and the ring the pill was also missing
2894
+ (inset 0 0 0 1px borderSubtle, measured rgb(238,239,241)) is added. */
2566
2895
  & .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); }
2567
2896
  & .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; }
2568
2897
  & .crisp-board-col-add:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: var(--crisp-fg-primary); }
@@ -2571,7 +2900,11 @@
2571
2900
  & .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; }
2572
2901
  & .crisp-board-newcard:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 7%, transparent); color: var(--crisp-fg-primary); }
2573
2902
  & .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; }
2574
- /* #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
2903
+ /* #718: a collapsed card is a single 40px row -- measured live and
2904
+ independent of `c.size.tableRow`/`tableHeader` (36/40px) even
2905
+ though it happens to match the latter; a plain literal, not a
2906
+ cross-reference to a token measured for a different surface.
2907
+ `renderCard`'s own second argument is what actually decides what a
2575
2908
  collapsed card SHOWS (Board never sees inside its opaque output);
2576
2909
  this only supplies the row-shaped box that content sits in. */
2577
2910
  & .crisp-board-card[data-density="collapse"] { flex-direction: row; align-items: center; height: 40px; padding: 0 var(--crisp-space-3); }
@@ -2579,11 +2912,15 @@
2579
2912
  & .crisp-board-card[data-dragging] { opacity: 0.4; }
2580
2913
  & .crisp-board-card:focus { outline: none; }
2581
2914
  & .crisp-board-card:focus-visible { outline: 2px solid var(--crisp-bg-brand-solid); outline-offset: 1px; }
2582
- /* #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. */
2915
+ /* #674: the pointer-events drag's own drag image -- the same card
2916
+ surface above, just pinned to the pointer instead of the flex flow.
2917
+ No shadow lift or scale: neither was measured against Attio, and this
2918
+ is the same card, momentarily somewhere else, not a new look. */
2583
2919
  & .crisp-board-ghost { position: fixed; z-index: 1; pointer-events: none; cursor: grabbing; }
2584
2920
  /* #604: a per-column calc footer, mirroring the table's own (measured
2585
2921
  live, Deals pipeline). Sits below the scrollable card body, not
2586
- 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. */
2922
+ inside it or pinned to it -- Attio's own is position: static, not
2923
+ sticky, so it's a plain flex-shrink:0 sibling here too. */
2587
2924
  & .crisp-board-col-footer { flex-shrink: 0; padding: var(--crisp-space-1) 0; }
2588
2925
  & .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; }
2589
2926
  & .crisp-board-col-calc:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); }
@@ -2592,7 +2929,9 @@
2592
2929
  & .crisp-board-col-calc-value { color: var(--crisp-fg-primary); font-variant-numeric: tabular-nums; }
2593
2930
  /* #718: "Add stage" -- measured live: 52×52px, transparent, a 1px
2594
2931
  dashed border (rgb(230,231,234), exactly `borderDropzone`/`line150`
2595
- -- 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 "+". */
2932
+ -- the same dashed-border colour ImportDialog's own dropzone already
2933
+ uses, not borderSubtle/line100, a close but different value), radius.xl
2934
+ (16px), a centered 16px "+". */
2596
2935
  & .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; }
2597
2936
  & .crisp-board-addstage:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); }
2598
2937
  /* announced, never drawn */
@@ -2616,28 +2955,32 @@
2616
2955
  & .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; }
2617
2956
  & .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); }
2618
2957
  & .crisp-command-control svg { flex-shrink: 0; }
2619
- /* 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
2958
+ /* The palette's label element. It exists for the assistive tree only: zag's
2959
+ combobox names the input through it and points the listbox's
2620
2960
  aria-labelledby at it, and without it both were unnamed and the reference
2621
2961
  resolved to nothing (#225). Clipped rather than display:none, which would
2622
2962
  take it out of the tree it exists for. */
2623
- & .crisp-command-srlabel { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
2624
- & .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); }
2625
- & .crisp-command-input::placeholder { color: var(--crisp-fg-muted); }
2626
- & .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); }
2627
- & .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); }
2628
- & .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; }
2629
- & .crisp-command-item svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
2630
- & .crisp-command-item[data-highlighted] { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); }
2631
- & .crisp-command-item-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2632
- & .crisp-command-item-shortcut { margin-inline-start: auto; flex-shrink: 0; display: flex; align-items: center; gap: var(--crisp-space-0-5); }
2633
- & .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); }
2634
- & .crisp-command-list > .crisp-command-group-label:first-child { padding-top: var(--crisp-space-1); }
2635
- & .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); }
2636
- & .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); }
2637
- & .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; }
2638
- & .crisp-command-footer-action:disabled { opacity: 0.5; cursor: default; }
2639
- & .crisp-command-footer-action-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2640
- & .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); }
2963
+ & .crisp-command-srlabel { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
2964
+ & .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); }
2965
+ & .crisp-command-input::placeholder { color: var(--crisp-fg-muted); }
2966
+ & .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); }
2967
+ & .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); }
2968
+ & .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; }
2969
+ & .crisp-command-item svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
2970
+ & .crisp-command-item[data-highlighted] { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); }
2971
+ & .crisp-command-item-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2972
+ & .crisp-command-item-shortcut { margin-inline-start: auto; flex-shrink: 0; display: flex; align-items: center; gap: var(--crisp-space-0-5); }
2973
+ & .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); }
2974
+ & .crisp-command-list > .crisp-command-group-label:first-child { padding-top: var(--crisp-space-1); }
2975
+ & .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); }
2976
+ & .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); }
2977
+ /* your-moon/crisp#953: bgBrandContrast, not bgBrandSolid --
2978
+ the accent (#0298d9) only clears 3.23:1 with white; the darker sibling
2979
+ clears 4.5:1+, same fix already applied to Button/Badge/Avatar (#897). */
2980
+ & .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; }
2981
+ & .crisp-command-footer-action:disabled { opacity: 0.5; cursor: default; }
2982
+ & .crisp-command-footer-action-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2983
+ & .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); }
2641
2984
  }
2642
2985
 
2643
2986
  @media (prefers-reduced-motion: reduce) {
@@ -2680,7 +3023,7 @@
2680
3023
  [dir="rtl"] .crisp-comments-badge [data-rtl-flip] { transform: scaleX(-1); }
2681
3024
 
2682
3025
  .crisp-comments {
2683
- 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;
3026
+ 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;
2684
3027
  & * { box-sizing: border-box; }
2685
3028
  & .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); }
2686
3029
  & .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; }
@@ -2690,13 +3033,21 @@
2690
3033
  & .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; }
2691
3034
  & .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); }
2692
3035
  & .crisp-comments-body--pad-top { padding-top: var(--crisp-space-3); }
2693
- /* 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
2694
- 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
3036
+ /* One comment row -- avatar 16px (Avatar size="sm"), 6px gap to the
3037
+ name/time line, 14px/600 name + 12px/500 63%-muted time, 14px/400 body
3038
+ two lines below. Hover actions fade in flush with the name line.
3039
+ .crisp-comments-row-head's own height: 20px (above) is a single
3040
+ text-line's worth, on the same assumption .crisp-comments-title/
3041
+ .crisp-comments-breadcrumb already act on higher up in this same
3042
+ recipe -- a name that doesn't fit wraps to two lines with nothing
2695
3043
  reserving room for the second one, overflowing straight into the
2696
- 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
3044
+ body text underneath (found live: this app's own dev-bypass author
3045
+ name, an email long enough to wrap, sat on top of the comment's own
2697
3046
  body text). .crisp-comments-author gets the identical nowrap-
2698
3047
  ellipsis treatment those two already have, plus min-width: 0 so a
2699
- 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
3048
+ flex item is actually allowed to shrink below its own text's natural
3049
+ width in the first place; .crisp-comments-time gets flex-shrink: 0
3050
+ so it's the name that gives, never the timestamp -- the same "one
2700
3051
  side shrinks, the other one doesn't" split PersonChip's own name/
2701
3052
  avatar pair already uses (crisp#668/#671). */
2702
3053
  & .crisp-comments-row { position: relative; display: flex; gap: var(--crisp-space-1-5); }
@@ -2706,16 +3057,13 @@
2706
3057
  & .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; }
2707
3058
  & .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); }
2708
3059
  & .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; }
2709
-
2710
3060
  /* Hover actions -- measured: 4 buttons, 28x28, radius 8, flush
2711
3061
  side-by-side (0 gap), transparent by default, 16px icon at 55% ink. */
2712
3062
  & .crisp-comments-row-actions { position: absolute; top: 0; inset-inline-end: 0; display: flex; opacity: 0; pointer-events: none; background: var(--crisp-bg-surface); }
2713
- & .crisp-comments-row:hover .crisp-comments-row-actions,
2714
- & .crisp-comments-row-actions:focus-within { opacity: 1; pointer-events: auto; }
3063
+ & .crisp-comments-row:hover .crisp-comments-row-actions, & .crisp-comments-row-actions:focus-within { opacity: 1; pointer-events: auto; }
2715
3064
  & .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; }
2716
3065
  & .crisp-comments-action:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
2717
3066
  & .crisp-comments-action[data-active] { color: var(--crisp-bg-brand-solid); }
2718
-
2719
3067
  /* Reactions -- measured: 28px pill, radius 8, subtle 4% ink fill,
2720
3068
  padding 4px 8px 4px 6px, gap 4px between the emoji glyph and count. */
2721
3069
  & .crisp-comments-reactions { display: flex; flex-wrap: wrap; gap: var(--crisp-space-1); margin-top: var(--crisp-space-1-5); }
@@ -2724,12 +3072,9 @@
2724
3072
  & .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; }
2725
3073
  & .crisp-comments-row:hover .crisp-comments-reaction-add { opacity: 1; }
2726
3074
  & .crisp-comments-reaction-add:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
2727
-
2728
3075
  /* Reply-in-thread view -- an indented block of replies below the root. */
2729
3076
  & .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); }
2730
-
2731
3077
  & .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); }
2732
-
2733
3078
  /* Composer -- measured: 14px/20px body text, 16px gap above the
2734
3079
  reply/mention icon row, Comment button 84x32 padding 6px 10px radius
2735
3080
  9 (= radius.lg) brand-solid fill, 14px/500 white label, opacity .4
@@ -2747,21 +3092,28 @@
2747
3092
  box-shadow, so this reuses that literal rather than re-deriving it.
2748
3093
  32px/radius.lg/6px-10px padding has no matching Button `size` yet
2749
3094
  (24/28/40 are the three that exist), so this stays hand-rolled instead
2750
- of stretching one of those. */
2751
- & .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; }
2752
- & .crisp-comments-post:hover:not(:disabled) { background: var(--crisp-bg-brand-solid-pressed); }
3095
+ of stretching one of those. your-moon/crisp#953:
3096
+ bgBrandContrast, not bgBrandSolid -- the accent (#0298d9) only clears
3097
+ 3.23:1 with white; same fix already applied to Button/Badge/Avatar
3098
+ (#897). Hover is a brightness filter on the new fill, matching that
3099
+ PR's own Button primary treatment, not bgBrandSolidPressed. */
3100
+ & .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; }
3101
+ & .crisp-comments-post:hover:not(:disabled) { filter: brightness(0.96); }
2753
3102
  & .crisp-comments-post:disabled { opacity: .4; cursor: default; }
2754
-
2755
3103
  /* Filter menu -- measured: 220px, radius 12 (radius.xl), 4px padding,
2756
3104
  elevation.floating -- a plain reuse of crisp's own Menu, so no rules
2757
- live here; kept as a comment for the record. */
2758
-
2759
- /* An inserted "@mention" chip in the composer -- same shape as
3105
+ live here; kept as a comment for the record. */ /* An inserted "@mention" chip in the composer -- same shape as
2760
3106
  RichTextEditor's own `.crisp-rte-mention` (crisp#428), reused here
2761
3107
  rather than re-derived since it's the same atomic-chip affordance. */
2762
3108
  & .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; }
2763
3109
  [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
2764
3110
  }
3111
+ .crisp-comments--variant_drawer {
3112
+ width: 320px; border-inline-start: 1px solid var(--crisp-border-subtle);
3113
+ }
3114
+ .crisp-comments--variant_pane {
3115
+ width: 100%;
3116
+ }
2765
3117
 
2766
3118
  @media (prefers-reduced-motion: reduce) {
2767
3119
  .crisp-comments, .crisp-comments * {
@@ -2903,7 +3255,9 @@
2903
3255
  & .crisp-listpage-count { margin-inline-start: auto; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); white-space: nowrap; }
2904
3256
  /* #641: trailing (View settings / New) has to reach the toolbar's own
2905
3257
  inline-end edge on its own -- the only thing pushing it there used
2906
- 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)
3258
+ to be .crisp-listpage-count's margin-inline-start: auto, which a
3259
+ consumer with no count badge (TasksTable, measured live: trailing
3260
+ sat right after leading, nowhere near the row's own right edge)
2907
3261
  never renders. A bare margin-inline-start: auto here is safely
2908
3262
  overridden by the more specific rule right below whenever count IS
2909
3263
  present, so the two cases don't fight -- count's own auto margin
@@ -2933,6 +3287,74 @@
2933
3287
  }
2934
3288
  }
2935
3289
 
3290
+ .crisp-memberssettings {
3291
+ & .crisp-memberssettings-page { display: flex; flex-direction: column; gap: var(--crisp-space-6); font-family: var(--crisp-font-sans); }
3292
+ & .crisp-memberssettings-heading { display: flex; flex-direction: column; gap: var(--crisp-space-0-5); }
3293
+ & .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; }
3294
+ & .crisp-memberssettings-desc { font-size: var(--crisp-text-lg); line-height: 20px; color: var(--crisp-fg-muted); margin: 0; }
3295
+ /* Segmented Members/Teams switcher -- measured: 28px pills, radius 8,
3296
+ 4px gap, 6px icon-label gap, 14/500 label, selected bg bgSubtle +
3297
+ inset borderSubtle ring, unselected transparent + a flat 63%-opacity
3298
+ fgPrimary mix (measured rgba(0,0,0,.63) on the live page, not the
3299
+ solid fgMuted token — same alpha technique this file's table header
3300
+ already uses). */
3301
+ & .crisp-memberssettings-tabpair { display: inline-flex; align-items: center; gap: 4px; }
3302
+ & .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; }
3303
+ & .crisp-memberssettings-tab svg { flex-shrink: 0; }
3304
+ & .crisp-memberssettings-tab:hover:not([data-selected]) { color: var(--crisp-fg-primary); }
3305
+ & .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); }
3306
+ & .crisp-memberssettings-tab:focus-visible { outline: 2px solid var(--crisp-bg-brand-solid); outline-offset: 1px; }
3307
+ /* Toolbar row: search grows, Filter + Invite sit trailing -- measured
3308
+ 32px height on all three, search radius 9 with a 6% ink ring,
3309
+ Filter/Invite radius 9 with the button drop-shadow pairing. */
3310
+ & .crisp-memberssettings-toolbar { display: flex; align-items: center; gap: var(--crisp-space-2); }
3311
+ & .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); }
3312
+ & .crisp-memberssettings-search:focus-within { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
3313
+ & .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); }
3314
+ & .crisp-memberssettings-search input::placeholder { color: var(--crisp-fg-muted); }
3315
+ & .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; }
3316
+ & .crisp-memberssettings-filterbtn:hover { background: var(--crisp-bg-raised); }
3317
+ /* your-moon/crisp#979: bgBrandContrast, not bgBrandSolid -- the accent
3318
+ (#0298d9) only clears 3.23:1 with white; the darker sibling clears
3319
+ 4.5:1+, same fix already applied to Button/Badge/Avatar (#897) and
3320
+ TableToolbar/Dialog/Command/Comments/AuthShell (#953/#961), which
3321
+ explicitly flagged this button as found-not-fixed. */
3322
+ & .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; }
3323
+ & .crisp-memberssettings-invitebtn:hover { filter: brightness(0.95); }
3324
+ /* Table wrapper -- measured card: radius 12, 1px borderSubtle, 40px
3325
+ rows, header label 12/500/63%-opacity fgPrimary. */
3326
+ & .crisp-memberssettings-table-wrap { border-radius: 12px; border: 1px solid var(--crisp-border-subtle); overflow: hidden; }
3327
+ & .crisp-memberssettings-table { width: 100%; border-collapse: collapse; font-family: inherit; }
3328
+ & .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); }
3329
+ & .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); }
3330
+ & .crisp-memberssettings-table tr:last-child td { border-bottom: none; }
3331
+ & .crisp-memberssettings-user { display: flex; align-items: center; gap: var(--crisp-space-2); }
3332
+ & .crisp-memberssettings-avatarimg { width: 24px; height: 24px; border-radius: var(--crisp-radius-full); object-fit: cover; }
3333
+ /* Name + email sit on ONE line, not stacked -- confirmed live: both spans
3334
+ share the same y and the same 14px size, only the email's colour
3335
+ differs (a flat 63%-opacity fgPrimary mix, not a smaller muted font). */
3336
+ & .crisp-memberssettings-usermeta { display: flex; align-items: baseline; gap: var(--crisp-space-2); min-width: 0; }
3337
+ & .crisp-memberssettings-usermeta > span:first-child { flex-shrink: 0; }
3338
+ & .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; }
3339
+ & .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; }
3340
+ & .crisp-memberssettings-kebab:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
3341
+ & .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); }
3342
+ /* Invite dialog fields -- measured 708x386 dialog, radius 16; the
3343
+ "Invite as"/"Add to team" selects are 32/34px with a flat 1px
3344
+ 6%-ink border and 9/10px radius respectively (Attio's own two
3345
+ selects don't share one radius). */
3346
+ & .crisp-memberssettings-invitefield { display: flex; flex-direction: column; gap: var(--crisp-space-1-5); }
3347
+ & .crisp-memberssettings-invitelabel { font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
3348
+ }
3349
+
3350
+ @media (prefers-reduced-motion: reduce) {
3351
+ .crisp-memberssettings, .crisp-memberssettings * {
3352
+ animation-duration: 0.01ms !important;
3353
+ animation-iteration-count: 1 !important;
3354
+ transition-duration: 0.01ms !important;
3355
+ }
3356
+ }
3357
+
2936
3358
 
2937
3359
  .crisp-navitem-shortcut { display: flex; align-items: baseline; justify-content: space-between; gap: var(--crisp-space-3); }
2938
3360
  .crisp-navitem-shortcut-desc { font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); line-height: 16px; white-space: nowrap; }
@@ -2956,6 +3378,29 @@
2956
3378
  }
2957
3379
  }
2958
3380
 
3381
+ .crisp-notificationssettings {
3382
+ & .crisp-notificationssettings-page { display: flex; flex-direction: column; gap: var(--crisp-space-6); font-family: var(--crisp-font-sans); }
3383
+ & .crisp-notificationssettings-heading { display: flex; flex-direction: column; gap: var(--crisp-space-0-5); }
3384
+ & .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; }
3385
+ & .crisp-notificationssettings-desc { font-size: var(--crisp-text-lg); line-height: 20px; color: var(--crisp-fg-muted); margin: 0; }
3386
+ & .crisp-notificationssettings-section { display: flex; flex-direction: column; gap: var(--crisp-space-4); }
3387
+ & .crisp-notificationssettings-sectiontitle { font-size: 16px; line-height: 20px; font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-primary); margin: 0; }
3388
+ /* Daily digest card: one bordered 12px-radius row. */
3389
+ & .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); }
3390
+ & .crisp-notificationssettings-digesttext { display: flex; flex-direction: column; gap: var(--crisp-space-1); }
3391
+ & .crisp-notificationssettings-rowtitle { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
3392
+ & .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); }
3393
+ /* Collaboration matrix: a bordered card, header row + N body rows. */
3394
+ & .crisp-notificationssettings-matrix { border: 1px solid var(--crisp-border-subtle); border-radius: 12px; overflow: hidden; background: var(--crisp-bg-surface); }
3395
+ & .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); }
3396
+ & .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); }
3397
+ & .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); }
3398
+ & .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; }
3399
+ & .crisp-notificationssettings-matrixrow + .crisp-notificationssettings-matrixrow { border-top: 1px solid var(--crisp-border-subtle); }
3400
+ & .crisp-notificationssettings-matrixrow-about { display: flex; flex-direction: column; gap: var(--crisp-space-1); flex: 1; min-width: 0; }
3401
+ & .crisp-notificationssettings-matrixrow-col { display: flex; align-items: center; justify-content: center; width: 64px; flex-shrink: 0; }
3402
+ }
3403
+
2959
3404
  .crisp-objectsettings {
2960
3405
  & .crisp-objectsettings-page { display: flex; flex-direction: column; gap: var(--crisp-space-6); }
2961
3406
  & .crisp-objectsettings-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--crisp-space-4); }
@@ -2970,22 +3415,22 @@
2970
3415
  & .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; }
2971
3416
  & .crisp-objectsettings-filterbtn:hover { background: var(--crisp-bg-raised); }
2972
3417
  /* Object / attribute icon tile — Attio's own squircle (measured
2973
- 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,
3418
+ border-radius: 30%, not a scale token: it's a proportional radius
3419
+ tied to the tile's own size, unlike every other radius crisp uses,
2974
3420
  which is a fixed px). */
2975
3421
  & .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); }
2976
3422
  & .crisp-objectsettings-icon--lg { width: 48px; height: 48px; border-radius: 16px; }
2977
-
2978
3423
  /* "Standard"/"Custom" type badge and the muted "No records" cell —
2979
3424
  plain text, not a Badge pill (measured: transparent background). */
2980
3425
  & .crisp-objectsettings-typebadge { font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-neutral-soft); }
2981
3426
  & .crisp-objectsettings-muted { color: var(--crisp-fg-muted); }
2982
-
2983
3427
  /* Attribute-type option row (icon + label + optional trailing badge),
2984
3428
  composed as a Select option's `label` ReactNode. */
2985
3429
  & .crisp-objectsettings-typeoption { display: flex; align-items: center; gap: var(--crisp-space-2); width: 100%; min-width: 0; }
2986
3430
  & .crisp-objectsettings-typeoption-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
2987
3431
  & .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); }
2988
- /* Create-attribute dialog's AI-autofill row and the submit button's ⌘↵ hint (measured off the same dialog, same shape as Dialog's own
3432
+ /* Create-attribute dialog's AI-autofill row and the submit button's
3433
+ ⌘↵ hint (measured off the same dialog, same shape as Dialog's own
2989
3434
  .crisp-dialog-submit-key, kept local rather than reaching into
2990
3435
  another component's scoped classes). */
2991
3436
  & .crisp-objectsettings-aiautofill { display: flex; flex-direction: column; gap: var(--crisp-space-1-5); }
@@ -3041,6 +3486,21 @@
3041
3486
  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);
3042
3487
  & * { box-sizing: border-box; }
3043
3488
  & .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); }
3489
+ /* #624: expanding a peek to its full record keeps a hybrid header
3490
+ (Close/Previous/Next/counter) instead of reverting to the plain
3491
+ breadcrumb above -- measured live on a real expanded record: 49px
3492
+ tall (not 44px), 8px/10px padding, same borderSubtle hairline. Same
3493
+ tokens as product/recordpanel's own already-shipped toolbar, not
3494
+ new values -- this is the same row, reused in a second component. */
3495
+ & .crisp-recordpage-topbar[data-peek] { height: 49px; padding: var(--crisp-space-2) var(--crisp-space-2-5); }
3496
+ & .crisp-recordpage-peekbar { display: flex; align-items: center; }
3497
+ & .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; }
3498
+ & .crisp-recordpage-peek-iconbtn:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
3499
+ & .crisp-recordpage-peek-pager { display: flex; align-items: center; gap: var(--crisp-space-1-5); margin-inline-start: var(--crisp-space-3); }
3500
+ & .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; }
3501
+ & .crisp-recordpage-peek-navbtn:hover:not(:disabled) { background: var(--crisp-bg-raised); }
3502
+ & .crisp-recordpage-peek-navbtn:disabled { opacity: 0.4; cursor: not-allowed; }
3503
+ & .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; }
3044
3504
  & .crisp-recordpage-crumbs { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; }
3045
3505
  & .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; }
3046
3506
  & .crisp-recordpage-crumb:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
@@ -3058,6 +3518,17 @@
3058
3518
  & .crisp-recordpage-resizer::before { content: ""; position: absolute; top: 0; bottom: 0; inset-inline-start: -4px; width: 9px; }
3059
3519
  & .crisp-recordpage-resizer::after { content: ""; position: absolute; top: 0; bottom: 0; inset-inline-start: -1px; width: 2px; background: transparent; transition: background-color .12s ease; }
3060
3520
  & .crisp-recordpage-resizer:hover::after, & .crisp-recordpage-resizer[data-active]::after { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 55%, transparent); }
3521
+ /* #930: RecordDetails' own standalone resize handle -- same visible
3522
+ hit-slop/line-highlight shape as .crisp-recordpage-resizer just above,
3523
+ just absolutely positioned at the trailing edge instead of a 0-width
3524
+ flex sibling, since RecordDetails always returns a single root (the
3525
+ same internal-strip placement RecordPanel's own resize handle uses,
3526
+ mirrored to the opposite edge -- this column is leading, RecordPanel
3527
+ is trailing). */
3528
+ & .crisp-recordpage-section[data-resizable] { position: relative; }
3529
+ & .crisp-recordpage-detailresize { position: absolute; top: 0; bottom: 0; inset-inline-end: -4px; width: 9px; cursor: col-resize; z-index: 5; touch-action: none; }
3530
+ & .crisp-recordpage-detailresize::after { content: ""; position: absolute; top: 0; bottom: 0; inset-inline-end: 3px; width: 2px; background: transparent; transition: background-color .12s ease; }
3531
+ & .crisp-recordpage-detailresize:hover::after, & .crisp-recordpage-detailresize[data-active]::after { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 55%, transparent); }
3061
3532
  & .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); }
3062
3533
  & .crisp-recordpage-entity-logo { width: 32px; height: 32px; border-radius: 30%; flex-shrink: 0; object-fit: cover; background: var(--crisp-bg-raised); }
3063
3534
  & .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; }
@@ -3073,7 +3544,8 @@
3073
3544
  & .crisp-recordpage-quickbtn:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
3074
3545
  & .crisp-recordpage-section { padding-top: var(--crisp-space-2); }
3075
3546
  & .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); }
3076
- /* main-area section headers (Highlights / Activity / Emails / Notes / Tasks) are 14px; the left column's (Record Details / Lists) stay 12px. */
3547
+ /* main-area section headers (Highlights / Activity / Emails / Notes / Tasks)
3548
+ are 14px; the left column's (Record Details / Lists) stay 12px. */
3077
3549
  & .crisp-recordpage-main .crisp-recordpage-section-title { font-size: var(--crisp-text-lg); }
3078
3550
  & .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); }
3079
3551
  & .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; }
@@ -3088,7 +3560,8 @@
3088
3560
  & .crisp-recordpage-noresults { padding: var(--crisp-space-1-5) var(--crisp-space-1); font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
3089
3561
  /* A caller-supplied `sections` grouping (#528) is not an Attio-measured
3090
3562
  shape (its own Details column is flat) -- this heading reuses
3091
- .crisp-recordpage-section-title's own type treatment verbatim, just spaced from the group above it. */
3563
+ .crisp-recordpage-section-title's own type treatment verbatim, just
3564
+ spaced from the group above it. */
3092
3565
  & .crisp-recordpage-fieldsection-title { margin-top: var(--crisp-space-3); }
3093
3566
  & .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; }
3094
3567
  & .crisp-recordpage-morevalues:hover { color: var(--crisp-fg-primary); }
@@ -3104,25 +3577,38 @@
3104
3577
  & .crisp-recordpage-field-label-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
3105
3578
  & .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); }
3106
3579
  & .crisp-recordpage-field-value[data-editable] { cursor: pointer; }
3107
- /* 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
3580
+ /* the click-to-edit trigger is a real <button> (keyboard-operable),
3581
+ reset back to plain inline text and made a flex-wrap container of its
3582
+ own -- FieldValue can render several chips (tags), which still need
3583
+ to wrap the way they did before this had a button around them at
3584
+ all. It wraps only FieldValue, never the error slot beside it (see
3585
+ the component's own comment) -- NOT `display: contents`, which
3108
3586
  drops a button's accessible semantics in some engines. */
3109
3587
  & .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; }
3110
3588
  & .crisp-recordpage-empty { color: var(--crisp-fg-muted); }
3111
3589
  /* #431: measured live on app.attio.com's record page Details column --
3112
- clicking a field's value opens a plain, borderless field exactly filling the value's own box (no border, no background, no
3590
+ clicking a field's value opens a plain, borderless field exactly
3591
+ filling the value's own box (no border, no background, no
3113
3592
  box-shadow), same font as the value it replaces (hence `font:
3114
- 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
3115
- tokens DataTable's identical states already use (opacity 0.5, `bgDanger`), per #385/#430. */
3593
+ inherit`, not a restated size/weight). A field row's own hover tint
3594
+ above already carries the "this is interactive" affordance; the
3595
+ pending/error treatment isn't Attio-measured -- it reuses the same
3596
+ tokens DataTable's identical states already use (opacity 0.5,
3597
+ `bgDanger`), per #385/#430. */
3116
3598
  & .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; }
3117
- /* 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
3599
+ /* not Attio-measured (its own field shows no focus ring either, caret
3600
+ only) -- kept for real keyboard users, the same brand ring every
3601
+ other crisp field already shows on focus (DataTable's identical
3118
3602
  .crisp-datatable-edit:focus, e.g.). */
3119
3603
  & .crisp-recordpage-edit:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
3120
3604
  & .crisp-recordpage-edit:disabled { opacity: 0.5; cursor: not-allowed; }
3121
3605
  & .crisp-recordpage-edit-pending { flex-shrink: 0; color: var(--crisp-fg-muted); }
3122
3606
  & .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); }
3123
- /* #457: an `editor: "select"` field's at-rest chip -- same shape as RecordsTable's own `.crisp-recordstable-status[-dot]` (already
3607
+ /* #457: an `editor: "select"` field's at-rest chip -- same shape as
3608
+ RecordsTable's own `.crisp-recordstable-status[-dot]` (already
3124
3609
  measured against Attio there), but its own classes: that pair is
3125
- scoped under `.crisp-recordstable`'s own recipe root, so reusing the classnames verbatim here would resolve to nothing. */
3610
+ scoped under `.crisp-recordstable`'s own recipe root, so reusing the
3611
+ classnames verbatim here would resolve to nothing. */
3126
3612
  & .crisp-recordpage-status { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; }
3127
3613
  & .crisp-recordpage-status-dot { flex-shrink: 0; width: 8px; height: 8px; border-radius: var(--crisp-radius-full); }
3128
3614
  & .crisp-recordpage-edit-picker { display: flex; align-items: center; gap: var(--crisp-space-1); width: 100%; }
@@ -3145,7 +3631,6 @@
3145
3631
  & .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); }
3146
3632
  & .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); }
3147
3633
  & .crisp-recordpage-hl-value--empty { color: var(--crisp-fg-muted); font-weight: var(--crisp-weight-regular); }
3148
-
3149
3634
  & .crisp-recordpage-feed { display: flex; flex-direction: column; }
3150
3635
  & .crisp-recordpage-feed-item { display: flex; align-items: flex-start; gap: var(--crisp-space-2-5); padding: var(--crisp-space-2) 0; }
3151
3636
  & .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); }
@@ -3154,7 +3639,6 @@
3154
3639
  & .crisp-recordpage-feed-time { font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); margin-top: 2px; }
3155
3640
  & .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; }
3156
3641
  & .crisp-recordpage-feed-more:hover { text-decoration: underline; }
3157
-
3158
3642
  & .crisp-recordpage-section-head { display: flex; align-items: center; gap: var(--crisp-space-1-5); padding: var(--crisp-space-1); }
3159
3643
  & .crisp-recordpage-section-head .crisp-recordpage-section-title { padding: 0; }
3160
3644
  & .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); }
@@ -3162,7 +3646,6 @@
3162
3646
  & .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; }
3163
3647
  & .crisp-recordpage-addbtn:hover { background: var(--crisp-bg-raised); }
3164
3648
  & .crisp-recordpage-addbtn svg { flex-shrink: 0; }
3165
-
3166
3649
  & .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); }
3167
3650
  & .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); }
3168
3651
  & .crisp-recordpage-composer textarea:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); border-radius: var(--crisp-radius-md); }
@@ -3172,23 +3655,29 @@
3172
3655
  & .crisp-recordpage-btn:hover { background: var(--crisp-bg-raised); }
3173
3656
  & .crisp-recordpage-btn--primary { box-shadow: none; background: var(--crisp-bg-brand-solid); color: var(--crisp-fg-on-brand); }
3174
3657
  & .crisp-recordpage-btn--primary:disabled { opacity: 0.5; cursor: not-allowed; }
3175
-
3176
- & .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); }
3177
- & .crisp-recordpage-note-body { flex: 1; min-width: 0; }
3178
- & .crisp-recordpage-note-head { display: flex; align-items: center; gap: var(--crisp-space-1-5); margin-bottom: var(--crisp-space-0-5); }
3179
- & .crisp-recordpage-note-author { font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
3180
- & .crisp-recordpage-note-time { font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); }
3181
- & .crisp-recordpage-note-text { font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); white-space: pre-wrap; word-break: break-word; }
3182
-
3658
+ /* #871: one bordered card per note (border-radius.xl, borderSubtle),
3659
+ not a flush divider-list row -- measured live on a real populated
3660
+ note: the whole row is flex space-between, avatar+title+body on the
3661
+ left (its own flex, gap.2), the relative time alone on the right.
3662
+ The real DOM nests an inner clickable button (padding 6/8, radius.md)
3663
+ inside an outer card (padding 4px, radius.xl) -- collapsed into this
3664
+ one non-interactive div's own combined padding (2.5/3) since crisp's
3665
+ NotesSection has no click-to-open behaviour to justify the extra
3666
+ layer. The 6px gap between stacked cards is NOT independently
3667
+ measured (this workspace only ever had one real note to check) --
3668
+ a judgement call off the existing space scale, not a live value. */
3669
+ & .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); }
3670
+ & .crisp-recordpage-note + .crisp-recordpage-note { margin-top: var(--crisp-space-1-5); }
3671
+ & .crisp-recordpage-note-left { display: flex; align-items: center; gap: var(--crisp-space-2); min-width: 0; overflow: hidden; }
3672
+ & .crisp-recordpage-note-title { flex-shrink: 0; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
3673
+ & .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); }
3674
+ & .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); }
3183
3675
  & .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); }
3184
3676
  & .crisp-recordpage-task-text { flex: 1; min-width: 0; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); padding-top: 1px; }
3185
3677
  & .crisp-recordpage-task[data-done] .crisp-recordpage-task-text { text-decoration: line-through; color: var(--crisp-fg-muted); }
3186
-
3187
3678
  & .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; }
3188
3679
  & .crisp-recordpage-emptycard svg { color: var(--crisp-fg-muted); opacity: 0.7; }
3189
-
3190
3680
  & .crisp-recordpage-action[data-active] svg { color: #f5b83d; fill: #f5b83d; }
3191
-
3192
3681
  & .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); }
3193
3682
  & .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)); }
3194
3683
  & .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)); }
@@ -3217,11 +3706,14 @@
3217
3706
  beneath (the same borderSubtle every other panel/row edge here
3218
3707
  already uses). Close keeps the existing transparent ghost button;
3219
3708
  Previous/Next/Open record are a distinct field-elevation style
3220
- (white fill, e.field's own ring+shadow, solid icon) -- measured as genuinely different chrome from Close, not a reuse. */
3709
+ (white fill, e.field's own ring+shadow, solid icon) -- measured as
3710
+ genuinely different chrome from Close, not a reuse. */
3221
3711
  & .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); }
3222
3712
  & .crisp-recordpanel-toolbar-spacer { flex: 1; }
3223
3713
  & .crisp-recordpanel-pager { display: flex; align-items: center; gap: var(--crisp-space-1-5); margin-inline-start: var(--crisp-space-3); }
3224
- /* #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. */
3714
+ /* #413: "2 of 37 in All Companies" -- measured live, same toolbar row,
3715
+ right after Prev/Next. 12px/500, the same 63%-of-fgPrimary mix every
3716
+ other muted heading in this panel already uses. */
3225
3717
  & .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; }
3226
3718
  & .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; }
3227
3719
  & .crisp-recordpanel-iconbtn:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
@@ -3229,26 +3721,36 @@
3229
3721
  & .crisp-recordpanel-navbtn:hover:not(:disabled) { background: var(--crisp-bg-raised); }
3230
3722
  & .crisp-recordpanel-navbtn:disabled { opacity: 0.4; cursor: not-allowed; }
3231
3723
  /* #413: measured live -- a 10px strip at the panel's own left edge,
3232
- 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
3233
- panel's own overflow: hidden (there for its rounded corners) never clips it. touch-action: none stops a touchscreen's own pan gesture
3724
+ cursor: col-resize (the issue's own repro calls it "ew-resize";
3725
+ the actual computed value read off the live DOM is col-resize, the
3726
+ same cursor family under a different keyword). Kept fully inside the
3727
+ panel's own box, not extending past inset-inline-start: 0, so the
3728
+ panel's own overflow: hidden (there for its rounded corners) never
3729
+ clips it. touch-action: none stops a touchscreen's own pan gesture
3234
3730
  from fighting the drag. */
3235
3731
  & .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; }
3236
3732
  & .crisp-recordpanel-resize-handle:hover, & .crisp-recordpanel-resize-handle:active { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); }
3237
-
3238
3733
  & .crisp-recordpanel-header { display: flex; align-items: center; gap: var(--crisp-space-2); height: 52px; flex-shrink: 0; padding: 0 var(--crisp-space-3); }
3239
3734
  & .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); }
3240
3735
  & .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; }
3241
-
3242
3736
  & .crisp-recordpanel-body { flex: 1; min-height: 0; overflow-y: auto; padding: var(--crisp-space-2) var(--crisp-space-3); }
3243
-
3244
- /* #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. */
3737
+ /* #448: measured live on app.attio.com's peek (hover a Companies row,
3738
+ click "Open preview"). Section label reads identically for both
3739
+ Highlights and Activity: 14px/500, the same 63%-of-fgPrimary mix
3740
+ RecordPage's own field-label already uses (product/recordpage's
3741
+ .crisp-recordpage-field-label) -- one more surface landing on the
3742
+ same measured "muted section heading" colour. */
3245
3743
  & .crisp-recordpanel-section { padding: var(--crisp-space-2) var(--crisp-space-1) 0; }
3246
3744
  & .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); }
3247
3745
  & .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); }
3248
- /* "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. */
3746
+ /* "View all" -- measured 10px/400, a lighter mix than the section label
3747
+ (55%, not 63%) -- its own distinct, honestly-measured value, not a
3748
+ reuse of a nearby one that happened to be close. */
3249
3749
  & .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; }
3250
3750
  & .crisp-recordpanel-section-action:hover { color: var(--crisp-fg-primary); }
3251
- /* 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).
3751
+ /* Highlights: a 2-column grid of label+icon+value cards, no card
3752
+ chrome at all (no border/background/radius/shadow -- confirmed on
3753
+ the live DOM: every highlight's computed style is fully transparent).
3252
3754
  8px row/column gap; two rows measured "233x86" inside 473px content
3253
3755
  width is exactly (473 - 8) / 2. */
3254
3756
  & .crisp-recordpanel-highlights { display: grid; grid-template-columns: 1fr 1fr; gap: var(--crisp-space-2); }
@@ -3257,27 +3759,33 @@
3257
3759
  & .crisp-recordpanel-highlight-label span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
3258
3760
  & .crisp-recordpanel-highlight-label svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
3259
3761
  & .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; }
3260
-
3261
3762
  /* Activity is a consumer-supplied slot (crisp has no activity-feed
3262
3763
  model of its own to render) -- this only wraps it with the measured
3263
3764
  section heading above. */
3264
3765
  & .crisp-recordpanel-activity { font-size: var(--crisp-text-lg); }
3265
-
3266
3766
  /* #413: collapsed Emails/Notes/Tasks below Activity -- measured live: a
3267
- 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
3268
- 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). */
3767
+ 473px-wide row (the panel's own content width, 497 minus its 12px/8px
3768
+ side insets), 20px tall, 14px/500 solid fgPrimary label (NOT the 63%
3769
+ mix Highlights/Activity headings use -- this is body text, not a
3770
+ muted heading), a trailing count. padding "0 8px 0 12px" measured on
3771
+ the row itself; reproduced here as the trigger's own padding, since
3772
+ the trigger IS the row (a plain div, not a button, when there's no
3773
+ content to expand into -- matches the empty ("Emails 0") row
3774
+ measured live, which has no expand affordance at all). */
3269
3775
  & .crisp-recordpanel-sections { display: flex; flex-direction: column; padding-top: var(--crisp-space-2); }
3270
3776
  & .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; }
3271
3777
  & .crisp-recordpanel-section-trigger-inert { cursor: default; }
3272
3778
  & .crisp-recordpanel-section-row-label { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
3273
3779
  /* The count wasn't captured with its own background/pill chrome in the
3274
3780
  live measurement (only its text: 14px, a "tertiary" internal variant
3275
- name that doesn't map to anything crisp defines) -- muted text next to the label, not an invented badge shape. */
3781
+ name that doesn't map to anything crisp defines) -- muted text next
3782
+ to the label, not an invented badge shape. */
3276
3783
  & .crisp-recordpanel-section-row-count { font-size: var(--crisp-text-lg); color: var(--crisp-fg-muted); }
3277
3784
  & .crisp-recordpanel-section-chevron { margin-inline-start: auto; flex-shrink: 0; color: var(--crisp-fg-muted); transition: transform .15s ease; }
3278
3785
  & .crisp-recordpanel-section-chevron[data-open] { transform: rotate(180deg); }
3279
3786
  & .crisp-recordpanel-section-content { padding: var(--crisp-space-2) var(--crisp-space-2) var(--crisp-space-2) var(--crisp-space-3); }
3280
- /* #329: an optional slot for record actions (e.g. Edit). Reuses the header's own measured values rather than inventing new ones -- the
3787
+ /* #329: an optional slot for record actions (e.g. Edit). Reuses the
3788
+ header's own measured values rather than inventing new ones -- the
3281
3789
  same horizontal inset (space.3), the same hairline border token
3282
3790
  (borderSubtle, flipped to border-top since this sits at the opposite
3283
3791
  edge), and the header/body gap (space.2) for its own vertical padding
@@ -3305,13 +3813,28 @@
3305
3813
  & .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); }
3306
3814
  & .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); }
3307
3815
  & .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); }
3308
- /* #575: a consumer's CSS reset (e.g. Tailwind preflight's un-scoped ul, ol { list-style: none }
3309
- , 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,
3816
+ /* #575: a consumer's CSS reset (e.g. Tailwind preflight's un-scoped
3817
+ ul, ol { list-style: none }, specificity 0,0,1) was winning every
3818
+ list marker outright -- this rule set padding but never declared
3819
+ list-style-type, so there was no higher-specificity value (this
3820
+ selector is 0,1,1) to beat the reset with. Split into two
3821
+ single-selector rules, each declaring its own marker type, rather
3822
+ than the old single comma-joined rule ( .crisp-rte-body ul, &
3823
+ .crisp-rte-body ol ) the issue flagged as malformed.
3824
+ #611: padding-inline-start re-measured live once CRUD on the
3825
+ measurement workspace was allowed (a "zz-parity" note with a
3826
+ bulleted list, a nested bulleted list, and a numbered list) --
3827
+ Attio's own <ul>/<ol> measures 36px (getComputedStyle,
3310
3828
  ~36.1353px), not the 20px placeholder #575 shipped with no real
3311
- 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
3829
+ content to check against. 36px isn't in tokens/scale.ts (steps
3830
+ stop at 24px, jump to 32px) and is a single component-intrinsic
3831
+ value, so it stays a raw px per AUTHORING.md's golden rule 4b
3312
3832
  rather than being rounded to an existing token or added to the
3313
3833
  shared scale. Marker colour (currentColor via the already-measured
3314
- 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
3834
+ fgPrimary, confirmed to equal Attio's measured rgb(16,17,18) ==
3835
+ #101112 exactly) and the ~4px li-to-li gap (the existing li
3836
+ margin-top rule below) were re-checked at the same time and
3837
+ already matched -- only the padding was wrong. Attio doesn't vary
3315
3838
  marker type for a nested list either (nested <ul> is disc too,
3316
3839
  not circle), which this already doesn't attempt. */
3317
3840
  & .crisp-rte-body ul { list-style-type: disc; padding-inline-start: 36px; }
@@ -3322,10 +3845,13 @@
3322
3845
  & .crisp-rte-body a { color: var(--crisp-bg-brand-solid); text-decoration: underline; text-underline-offset: 2px; }
3323
3846
  & .crisp-rte-body strong { font-weight: var(--crisp-weight-semibold); }
3324
3847
  & .crisp-rte-body img { display: block; max-width: 100%; border-radius: var(--crisp-radius-md); }
3325
- /* 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
3848
+ /* Not measured against Attio (#428) — nothing in a read-only pass over
3849
+ app.attio.com had a live @mention rendered to read computed styles
3850
+ from, and typing one to create one is off-limits there. Reused
3851
+ verbatim from the one measured "brand" tag/chip this repo already
3852
+ has (RecordsTable/MultiSelect's [data-tone="brand"]) rather than
3326
3853
  inventing a new color or shape. */
3327
3854
  & .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; }
3328
-
3329
3855
  & .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; }
3330
3856
  & .crisp-rte-toolbar { @starting-style { opacity: 0; } }
3331
3857
  & .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; }
@@ -3333,13 +3859,11 @@
3333
3859
  & .crisp-rte-tool:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: var(--crisp-fg-primary); }
3334
3860
  & .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); }
3335
3861
  & .crisp-rte-tool-sep { width: 1px; height: 16px; margin: 0 2px; background: var(--crisp-border-subtle); }
3336
-
3337
3862
  & .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; }
3338
3863
  & .crisp-rte-swatchpanel { @starting-style { opacity: 0; } }
3339
3864
  & .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; }
3340
3865
  & .crisp-rte-swatch--none { background: var(--crisp-bg-surface); position: relative; }
3341
3866
  & .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); }
3342
-
3343
3867
  & .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; }
3344
3868
  & .crisp-rte-blockmenu { @starting-style { opacity: 0; } }
3345
3869
  & .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); }
@@ -3439,9 +3963,16 @@
3439
3963
 
3440
3964
  .crisp-savedviews {
3441
3965
  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
3442
- 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
3966
+ actual position (Attio's real view bar, next to View settings --
3967
+ fixed there in #766/#768/#778): white (bgSurface, rgb(255,255,255)
3968
+ exact) with elevation.field's 3-layer shadow, the same raised
3443
3969
  chrome View settings and Import/Export both carry
3444
- (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. */
3970
+ (tabletoolbar.recipe.ts's own --raised, not reused here directly
3971
+ since this is a separate recipe/component, but the identical measured
3972
+ values). Supersedes the original #555 measurement (rgb(246,247,247),
3973
+ reused as bgRaised -- a flat grey fill with no shadow at all), which
3974
+ was wrong: this trigger was never re-checked once it moved into its
3975
+ real view-bar position. */
3445
3976
  & .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; }
3446
3977
  & .crisp-savedviews-trigger:hover, & .crisp-savedviews-trigger[data-open] { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-surface)); }
3447
3978
  & .crisp-savedviews-trigger-icon { flex-shrink: 0; display: inline-flex; width: var(--crisp-size-icon); height: var(--crisp-size-icon); }
@@ -3474,14 +4005,14 @@
3474
4005
  & .crisp-settingsshell-back svg { color: var(--crisp-fg-muted); }
3475
4006
  & .crisp-settingsshell-back:hover svg { color: var(--crisp-fg-primary); }
3476
4007
  & .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); }
3477
- /* 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
4008
+ /* The field box owns the ring, so focus has to move it -- the inner
4009
+ input kills its own outline. Measured live: a focused field's
3478
4010
  input carries box-shadow: none while its wrapper carries
3479
4011
  inset 0 0 0 1px #266df0. */
3480
4012
  & .crisp-settingsshell-search:focus-within { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
3481
4013
  & .crisp-settingsshell-search svg { flex-shrink: 0; }
3482
4014
  & .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); }
3483
4015
  & .crisp-settingsshell-search input::placeholder { color: var(--crisp-fg-muted); }
3484
-
3485
4016
  & .crisp-settingsshell-groups { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: var(--crisp-space-2-5); }
3486
4017
  & .crisp-settingsshell-group { display: flex; flex-direction: column; gap: 1px; }
3487
4018
  & .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); }
@@ -3492,7 +4023,6 @@
3492
4023
  & .crisp-settingsshell-item[data-active] { background: var(--crisp-bg-raised); font-weight: var(--crisp-weight-medium); }
3493
4024
  & .crisp-settingsshell-item[data-active] svg { color: var(--crisp-fg-primary); }
3494
4025
  & .crisp-settingsshell-empty { padding: var(--crisp-space-3) var(--crisp-space-2); font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
3495
-
3496
4026
  & .crisp-settingsshell-content { flex: 1; min-width: 0; min-height: 0; overflow-y: auto; padding: var(--crisp-space-6) var(--crisp-space-8); }
3497
4027
  [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
3498
4028
  }
@@ -3600,7 +4130,8 @@
3600
4130
  & [data-swatch="light"] .crisp-themepicker-preview-bar:not(:nth-child(1)), & [data-swatch="system"] .crisp-themepicker-preview-bar:not(:nth-child(1)) { background: #eeeff1; }
3601
4131
  & [data-swatch="light"] .crisp-themepicker-preview-main, & [data-swatch="system"] .crisp-themepicker-preview-main { background: #ffffff; }
3602
4132
  & [data-swatch="light"] .crisp-themepicker-preview-main .crisp-themepicker-preview-bar, & [data-swatch="system"] .crisp-themepicker-preview-main .crisp-themepicker-preview-bar { background: #eeeff1; }
3603
- /* Dark swatch colours -- always for "dark", and for "system" only when the OS itself reports dark. */
4133
+ /* Dark swatch colours -- always for "dark", and for "system" only
4134
+ when the OS itself reports dark. */
3604
4135
  & .crisp-themepicker-preview[data-swatch="dark"] { border: 1px solid #1c1d1f; background: #0e0f10; }
3605
4136
  & [data-swatch="dark"] .crisp-themepicker-preview-bar:not(:nth-child(1)) { background: #1c1d1f; }
3606
4137
  & [data-swatch="dark"] .crisp-themepicker-preview-main { background: #0a0a0a; }
@@ -3645,3 +4176,34 @@
3645
4176
  transition-duration: 0.01ms !important;
3646
4177
  }
3647
4178
  }
4179
+
4180
+ .crisp-workspacegeneralsettings {
4181
+ & .crisp-workspacegeneralsettings-page { display: flex; flex-direction: column; gap: var(--crisp-space-6); font-family: var(--crisp-font-sans); }
4182
+ & .crisp-workspacegeneralsettings-heading { display: flex; flex-direction: column; gap: var(--crisp-space-0-5); }
4183
+ & .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; }
4184
+ & .crisp-workspacegeneralsettings-desc { font-size: var(--crisp-text-lg); line-height: 20px; color: var(--crisp-fg-muted); margin: 0; }
4185
+ & .crisp-workspacegeneralsettings-section { display: flex; flex-direction: column; gap: var(--crisp-space-4); }
4186
+ & .crisp-workspacegeneralsettings-sectiontitle { font-size: 16px; line-height: 20px; font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-primary); margin: 0; }
4187
+ & .crisp-workspacegeneralsettings-sectionhead { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--crisp-space-4); }
4188
+ & .crisp-workspacegeneralsettings-sectionhead-text { display: flex; flex-direction: column; gap: var(--crisp-space-1); }
4189
+ /* Logo row: a tile + helper text + "Upload logo" button. */
4190
+ & .crisp-workspacegeneralsettings-logorow { display: flex; align-items: center; gap: var(--crisp-space-4); }
4191
+ & .crisp-workspacegeneralsettings-logohelp { font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); }
4192
+ & .crisp-workspacegeneralsettings-logotext { display: flex; flex-direction: column; align-items: flex-start; gap: var(--crisp-space-1); }
4193
+ /* Name + Slug sit side by side, each ~half the content width. */
4194
+ & .crisp-workspacegeneralsettings-fieldrow { display: flex; gap: var(--crisp-space-4); }
4195
+ & .crisp-workspacegeneralsettings-fieldrow > * { flex: 1; min-width: 0; }
4196
+ & .crisp-workspacegeneralsettings-slugfield { display: flex; align-items: center; gap: var(--crisp-space-1-5); }
4197
+ & .crisp-workspacegeneralsettings-slugfield > :first-child { flex: 1; min-width: 0; }
4198
+ & .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; }
4199
+ & .crisp-workspacegeneralsettings-copybtn:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
4200
+ /* Export-data empty state -- measured: 14/500/63%-opacity, centred in a
4201
+ 40px row, no border of its own (the Table wrapper already draws it). */
4202
+ & .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; }
4203
+ /* Danger zone: a red-tinted bordered row, distinct from every other
4204
+ card on the page. */
4205
+ & .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); }
4206
+ & .crisp-workspacegeneralsettings-dangertext { display: flex; flex-direction: column; gap: var(--crisp-space-1); }
4207
+ & .crisp-workspacegeneralsettings-rowtitle { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
4208
+ & .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); }
4209
+ }