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