@blurrow/ui 0.2.0 → 0.2.1

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 (271) hide show
  1. package/README.md +48 -36
  2. package/dist/Accordion.d.ts +17 -0
  3. package/dist/Accordion.js +138 -0
  4. package/dist/Accordion.js.map +1 -0
  5. package/dist/Alert.d.ts +13 -0
  6. package/dist/Alert.js +69 -0
  7. package/dist/Alert.js.map +1 -0
  8. package/dist/AuthorCard.d.ts +12 -0
  9. package/dist/AuthorCard.js +57 -0
  10. package/dist/AuthorCard.js.map +1 -0
  11. package/dist/Avatar.d.ts +21 -0
  12. package/dist/Avatar.js +96 -0
  13. package/dist/Avatar.js.map +1 -0
  14. package/dist/Badge.d.ts +12 -0
  15. package/dist/Badge.js +46 -0
  16. package/dist/Badge.js.map +1 -0
  17. package/dist/Banner.d.ts +15 -0
  18. package/dist/Banner.js +68 -0
  19. package/dist/Banner.js.map +1 -0
  20. package/dist/Breadcrumb.d.ts +13 -0
  21. package/dist/Breadcrumb.js +45 -0
  22. package/dist/Breadcrumb.js.map +1 -0
  23. package/dist/Button.d.ts +19 -0
  24. package/dist/Button.js +282 -0
  25. package/dist/Button.js.map +1 -0
  26. package/dist/ButtonGroup.d.ts +19 -0
  27. package/dist/ButtonGroup.js +62 -0
  28. package/dist/ButtonGroup.js.map +1 -0
  29. package/dist/Card.d.ts +40 -0
  30. package/dist/Card.js +175 -0
  31. package/dist/Card.js.map +1 -0
  32. package/dist/CardShell.d.ts +18 -0
  33. package/dist/CardShell.js +58 -0
  34. package/dist/CardShell.js.map +1 -0
  35. package/dist/Carousel.d.ts +22 -0
  36. package/dist/Carousel.js +247 -0
  37. package/dist/Carousel.js.map +1 -0
  38. package/dist/Checkbox.d.ts +15 -0
  39. package/dist/Checkbox.js +84 -0
  40. package/dist/Checkbox.js.map +1 -0
  41. package/dist/CloseButton.d.ts +12 -0
  42. package/dist/CloseButton.js +63 -0
  43. package/dist/CloseButton.js.map +1 -0
  44. package/dist/CodeBlock.d.ts +12 -0
  45. package/dist/CodeBlock.js +221 -0
  46. package/dist/CodeBlock.js.map +1 -0
  47. package/dist/ColorPicker.d.ts +24 -0
  48. package/dist/ColorPicker.js +366 -0
  49. package/dist/ColorPicker.js.map +1 -0
  50. package/dist/Combobox.d.ts +34 -0
  51. package/dist/Combobox.js +255 -0
  52. package/dist/Combobox.js.map +1 -0
  53. package/dist/CommandPalette.d.ts +25 -0
  54. package/dist/CommandPalette.js +304 -0
  55. package/dist/CommandPalette.js.map +1 -0
  56. package/dist/ContextMenu.d.ts +16 -0
  57. package/dist/ContextMenu.js +209 -0
  58. package/dist/ContextMenu.js.map +1 -0
  59. package/dist/DataList.d.ts +15 -0
  60. package/dist/DataList.js +42 -0
  61. package/dist/DataList.js.map +1 -0
  62. package/dist/DatePicker.d.ts +65 -0
  63. package/dist/DatePicker.js +391 -0
  64. package/dist/DatePicker.js.map +1 -0
  65. package/dist/DateRangePicker.d.ts +34 -0
  66. package/dist/DateRangePicker.js +180 -0
  67. package/dist/DateRangePicker.js.map +1 -0
  68. package/dist/Drawer.d.ts +17 -0
  69. package/dist/Drawer.js +132 -0
  70. package/dist/Drawer.js.map +1 -0
  71. package/dist/DropdownMenu.d.ts +23 -0
  72. package/dist/DropdownMenu.js +185 -0
  73. package/dist/DropdownMenu.js.map +1 -0
  74. package/dist/EmptyState.d.ts +13 -0
  75. package/dist/EmptyState.js +55 -0
  76. package/dist/EmptyState.js.map +1 -0
  77. package/dist/FieldMessage.d.ts +26 -0
  78. package/dist/FieldMessage.js +95 -0
  79. package/dist/FieldMessage.js.map +1 -0
  80. package/dist/FileUpload.d.ts +32 -0
  81. package/dist/FileUpload.js +253 -0
  82. package/dist/FileUpload.js.map +1 -0
  83. package/dist/Floating.d.ts +32 -0
  84. package/dist/Floating.js +95 -0
  85. package/dist/Floating.js.map +1 -0
  86. package/dist/HoverCard.d.ts +18 -0
  87. package/dist/HoverCard.js +90 -0
  88. package/dist/HoverCard.js.map +1 -0
  89. package/dist/Input.d.ts +17 -0
  90. package/dist/Input.js +96 -0
  91. package/dist/Input.js.map +1 -0
  92. package/dist/Kbd.d.ts +9 -0
  93. package/dist/Kbd.js +24 -0
  94. package/dist/Kbd.js.map +1 -0
  95. package/dist/Modal.d.ts +15 -0
  96. package/dist/Modal.js +109 -0
  97. package/dist/Modal.js.map +1 -0
  98. package/dist/NumberInput.d.ts +23 -0
  99. package/dist/NumberInput.js +162 -0
  100. package/dist/NumberInput.js.map +1 -0
  101. package/dist/OtpInput.d.ts +26 -0
  102. package/dist/OtpInput.js +173 -0
  103. package/dist/OtpInput.js.map +1 -0
  104. package/dist/Pagination.d.ts +11 -0
  105. package/dist/Pagination.js +148 -0
  106. package/dist/Pagination.js.map +1 -0
  107. package/dist/Popover.d.ts +13 -0
  108. package/dist/Popover.js +98 -0
  109. package/dist/Popover.js.map +1 -0
  110. package/dist/Progress.d.ts +12 -0
  111. package/dist/Progress.js +61 -0
  112. package/dist/Progress.js.map +1 -0
  113. package/dist/QuoteCard.d.ts +13 -0
  114. package/dist/QuoteCard.js +92 -0
  115. package/dist/QuoteCard.js.map +1 -0
  116. package/dist/Radio.d.ts +19 -0
  117. package/dist/Radio.js +161 -0
  118. package/dist/Radio.js.map +1 -0
  119. package/dist/RadioCards.d.ts +27 -0
  120. package/dist/RadioCards.js +205 -0
  121. package/dist/RadioCards.js.map +1 -0
  122. package/dist/ReactionBar.d.ts +15 -0
  123. package/dist/ReactionBar.js +92 -0
  124. package/dist/ReactionBar.js.map +1 -0
  125. package/dist/ReadingProgress.d.ts +8 -0
  126. package/dist/ReadingProgress.js +51 -0
  127. package/dist/ReadingProgress.js.map +1 -0
  128. package/dist/ReadingTime.d.ts +9 -0
  129. package/dist/ReadingTime.js +39 -0
  130. package/dist/ReadingTime.js.map +1 -0
  131. package/dist/Select.d.ts +20 -0
  132. package/dist/Select.js +244 -0
  133. package/dist/Select.js.map +1 -0
  134. package/dist/Separator.d.ts +14 -0
  135. package/dist/Separator.js +33 -0
  136. package/dist/Separator.js.map +1 -0
  137. package/dist/ShareButtons.d.ts +10 -0
  138. package/dist/ShareButtons.js +107 -0
  139. package/dist/ShareButtons.js.map +1 -0
  140. package/dist/Sidebar.d.ts +30 -0
  141. package/dist/Sidebar.js +140 -0
  142. package/dist/Sidebar.js.map +1 -0
  143. package/dist/Skeleton.d.ts +15 -0
  144. package/dist/Skeleton.js +67 -0
  145. package/dist/Skeleton.js.map +1 -0
  146. package/dist/Slider.d.ts +27 -0
  147. package/dist/Slider.js +182 -0
  148. package/dist/Slider.js.map +1 -0
  149. package/dist/Spinner.d.ts +12 -0
  150. package/dist/Spinner.js +24 -0
  151. package/dist/Spinner.js.map +1 -0
  152. package/dist/SpotlightCard.d.ts +17 -0
  153. package/dist/SpotlightCard.js +87 -0
  154. package/dist/SpotlightCard.js.map +1 -0
  155. package/dist/Stat.d.ts +19 -0
  156. package/dist/Stat.js +71 -0
  157. package/dist/Stat.js.map +1 -0
  158. package/dist/StatCard.d.ts +24 -0
  159. package/dist/StatCard.js +212 -0
  160. package/dist/StatCard.js.map +1 -0
  161. package/dist/Steps.d.ts +14 -0
  162. package/dist/Steps.js +94 -0
  163. package/dist/Steps.js.map +1 -0
  164. package/dist/Table.d.ts +21 -0
  165. package/dist/Table.js +93 -0
  166. package/dist/Table.js.map +1 -0
  167. package/dist/TableOfContents.d.ts +17 -0
  168. package/dist/TableOfContents.js +92 -0
  169. package/dist/TableOfContents.js.map +1 -0
  170. package/dist/Tabs.d.ts +17 -0
  171. package/dist/Tabs.js +123 -0
  172. package/dist/Tabs.js.map +1 -0
  173. package/dist/TagInput.d.ts +16 -0
  174. package/dist/TagInput.js +169 -0
  175. package/dist/TagInput.js.map +1 -0
  176. package/dist/Textarea.d.ts +13 -0
  177. package/dist/Textarea.js +74 -0
  178. package/dist/Textarea.js.map +1 -0
  179. package/dist/TimePicker.d.ts +28 -0
  180. package/dist/TimePicker.js +264 -0
  181. package/dist/TimePicker.js.map +1 -0
  182. package/dist/Timeline.d.ts +19 -0
  183. package/dist/Timeline.js +76 -0
  184. package/dist/Timeline.js.map +1 -0
  185. package/dist/Toast.d.ts +18 -0
  186. package/dist/Toast.js +318 -0
  187. package/dist/Toast.js.map +1 -0
  188. package/dist/Toggle.d.ts +13 -0
  189. package/dist/Toggle.js +84 -0
  190. package/dist/Toggle.js.map +1 -0
  191. package/dist/Tooltip.d.ts +13 -0
  192. package/dist/Tooltip.js +138 -0
  193. package/dist/Tooltip.js.map +1 -0
  194. package/dist/cli.js +172 -5
  195. package/dist/index.d.ts +68 -631
  196. package/dist/index.js +67 -5558
  197. package/dist/index.js.map +1 -1
  198. package/dist/registry/Accordion.tsx +6 -6
  199. package/dist/registry/Alert.tsx +13 -10
  200. package/dist/registry/AuthorCard.tsx +7 -7
  201. package/dist/registry/Avatar.tsx +5 -5
  202. package/dist/registry/Badge.tsx +12 -15
  203. package/dist/registry/Banner.tsx +15 -18
  204. package/dist/registry/Breadcrumb.tsx +3 -3
  205. package/dist/registry/Button.tsx +58 -69
  206. package/dist/registry/ButtonGroup.tsx +9 -9
  207. package/dist/registry/Card.tsx +15 -15
  208. package/dist/registry/CardShell.tsx +6 -6
  209. package/dist/registry/Carousel.tsx +19 -15
  210. package/dist/registry/Checkbox.tsx +14 -9
  211. package/dist/registry/CloseButton.tsx +2 -2
  212. package/dist/registry/CodeBlock.tsx +15 -15
  213. package/dist/registry/ColorPicker.tsx +404 -0
  214. package/dist/registry/Combobox.tsx +318 -0
  215. package/dist/registry/CommandPalette.tsx +31 -24
  216. package/dist/registry/ContextMenu.tsx +241 -0
  217. package/dist/registry/DataList.tsx +4 -4
  218. package/dist/registry/DatePicker.tsx +494 -0
  219. package/dist/registry/DateRangePicker.tsx +232 -0
  220. package/dist/registry/Drawer.tsx +17 -15
  221. package/dist/registry/DropdownMenu.tsx +39 -77
  222. package/dist/registry/EmptyState.tsx +8 -8
  223. package/dist/registry/FieldMessage.tsx +123 -0
  224. package/dist/registry/FileUpload.tsx +318 -0
  225. package/dist/registry/Floating.tsx +134 -0
  226. package/dist/registry/HoverCard.tsx +113 -0
  227. package/dist/registry/Input.tsx +54 -43
  228. package/dist/registry/Kbd.tsx +4 -5
  229. package/dist/registry/Modal.tsx +17 -15
  230. package/dist/registry/NumberInput.tsx +183 -0
  231. package/dist/registry/OtpInput.tsx +219 -0
  232. package/dist/registry/Pagination.tsx +15 -18
  233. package/dist/registry/Popover.tsx +40 -45
  234. package/dist/registry/Progress.tsx +4 -4
  235. package/dist/registry/QuoteCard.tsx +7 -7
  236. package/dist/registry/Radio.tsx +24 -21
  237. package/dist/registry/RadioCards.tsx +247 -0
  238. package/dist/registry/ReactionBar.tsx +5 -5
  239. package/dist/registry/ReadingProgress.tsx +2 -2
  240. package/dist/registry/ReadingTime.tsx +2 -2
  241. package/dist/registry/Select.tsx +28 -53
  242. package/dist/registry/Separator.tsx +52 -0
  243. package/dist/registry/ShareButtons.tsx +2 -2
  244. package/dist/registry/Sidebar.tsx +18 -18
  245. package/dist/registry/Skeleton.tsx +1 -1
  246. package/dist/registry/Slider.tsx +229 -0
  247. package/dist/registry/Spinner.tsx +32 -0
  248. package/dist/registry/SpotlightCard.tsx +6 -6
  249. package/dist/registry/Stat.tsx +4 -4
  250. package/dist/registry/StatCard.tsx +19 -19
  251. package/dist/registry/Steps.tsx +7 -7
  252. package/dist/registry/Table.tsx +12 -10
  253. package/dist/registry/TableOfContents.tsx +8 -8
  254. package/dist/registry/Tabs.tsx +6 -6
  255. package/dist/registry/TagInput.tsx +9 -9
  256. package/dist/registry/Textarea.tsx +5 -5
  257. package/dist/registry/TimePicker.tsx +315 -0
  258. package/dist/registry/Timeline.tsx +14 -14
  259. package/dist/registry/Toast.tsx +15 -12
  260. package/dist/registry/Toggle.tsx +7 -10
  261. package/dist/registry/Tooltip.tsx +21 -73
  262. package/dist/registry/styles.css +868 -62
  263. package/dist/registry/useFloatingPanel.ts +112 -0
  264. package/dist/styles.css +868 -62
  265. package/dist/useFloatingPanel.d.ts +24 -0
  266. package/dist/useFloatingPanel.js +89 -0
  267. package/dist/useFloatingPanel.js.map +1 -0
  268. package/dist/useModalBehavior.d.ts +15 -0
  269. package/dist/useModalBehavior.js +57 -0
  270. package/dist/useModalBehavior.js.map +1 -0
  271. package/package.json +1 -1
@@ -53,7 +53,7 @@ export function Accordion({ items, allowMultiple = false, className }: Accordion
53
53
  style={{ background: "var(--panel-bezel)", boxShadow: "var(--panel-shadow)" }}
54
54
  >
55
55
  <div
56
- className="bg-white rounded-[13px] overflow-hidden p-1"
56
+ className="bg-panel-face rounded-[13px] overflow-hidden p-1"
57
57
  style={{ boxShadow: "var(--panel-face-shadow)" }}
58
58
  >
59
59
  {items.map((item, index) => {
@@ -69,7 +69,7 @@ export function Accordion({ items, allowMultiple = false, className }: Accordion
69
69
  "relative",
70
70
  // Hairline between rows, inset so it doesn't touch the rounded corners
71
71
  !isLast &&
72
- "after:absolute after:left-3 after:right-3 after:bottom-0 after:h-px after:bg-neutral-100",
72
+ "after:absolute after:left-3 after:right-3 after:bottom-0 after:h-px after:bg-line",
73
73
  ]
74
74
  .filter(Boolean)
75
75
  .join(" ")}
@@ -84,15 +84,15 @@ export function Accordion({ items, allowMultiple = false, className }: Accordion
84
84
  className={[
85
85
  "group/acc w-full flex items-center justify-between gap-3 py-3 px-3 text-left text-[13.5px] font-medium cursor-pointer rounded-[10px]",
86
86
  "transition-colors duration-150",
87
- isOpen ? "text-neutral-900" : "text-neutral-700 hover:text-neutral-900 hover:bg-neutral-50",
87
+ isOpen ? "text-ink" : "text-ink-label hover:text-ink hover:bg-surface-hover",
88
88
  ].join(" ")}
89
89
  >
90
90
  <span>{item.title}</span>
91
91
  {/* Plus that turns into a minus: says "more inside", not "navigate" */}
92
92
  <span
93
93
  aria-hidden
94
- className="relative shrink-0 w-6 h-6 rounded-[7px] flex items-center justify-center text-neutral-500 group-hover/acc:text-neutral-900 transition-colors"
95
- style={{ background: "var(--face-light)", boxShadow: "var(--face-light-shadow)" }}
94
+ className="relative shrink-0 w-6 h-6 rounded-[7px] flex items-center justify-center text-ink-muted group-hover/acc:text-ink transition-colors"
95
+ style={{ background: "var(--face)", boxShadow: "var(--face-shadow)" }}
96
96
  >
97
97
  <span className="absolute w-2.5 h-[1.5px] rounded-full bg-current" />
98
98
  <span
@@ -120,7 +120,7 @@ export function Accordion({ items, allowMultiple = false, className }: Accordion
120
120
  >
121
121
  <div className="overflow-hidden">
122
122
  <div
123
- className="px-3 pb-4 pt-0.5 text-[13px] text-neutral-600 leading-relaxed"
123
+ className="px-3 pb-4 pt-0.5 text-[13px] text-ink-body leading-relaxed"
124
124
  style={{
125
125
  opacity: isOpen ? 1 : 0,
126
126
  transform: isOpen ? "translateY(0)" : "translateY(-4px)",
@@ -18,7 +18,7 @@ const variantConfig: Record<AlertVariant, {
18
18
  icon: React.ReactNode;
19
19
  }> = {
20
20
  default: {
21
- tile: "var(--face-dark)",
21
+ tile: "var(--face-inverse)",
22
22
  icon: (
23
23
  <svg width="13" height="13" fill="none" stroke="currentColor" strokeWidth="1.75" viewBox="0 0 24 24">
24
24
  <circle cx="12" cy="12" r="10" />
@@ -27,7 +27,7 @@ const variantConfig: Record<AlertVariant, {
27
27
  ),
28
28
  },
29
29
  success: {
30
- tile: "linear-gradient(180deg, #10b981 0%, #059669 100%)",
30
+ tile: "var(--tile-success)",
31
31
  icon: (
32
32
  <svg width="13" height="13" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
33
33
  <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
@@ -35,7 +35,7 @@ const variantConfig: Record<AlertVariant, {
35
35
  ),
36
36
  },
37
37
  warning: {
38
- tile: "linear-gradient(180deg, #fbbf24 0%, #f59e0b 100%)",
38
+ tile: "var(--tile-warning)",
39
39
  icon: (
40
40
  <svg width="13" height="13" fill="none" stroke="currentColor" strokeWidth="1.75" viewBox="0 0 24 24">
41
41
  <path strokeLinecap="round" strokeLinejoin="round" d="M12 9v4m0 4h.01M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z" />
@@ -43,7 +43,7 @@ const variantConfig: Record<AlertVariant, {
43
43
  ),
44
44
  },
45
45
  error: {
46
- tile: "linear-gradient(180deg, #f87171 0%, #dc2626 100%)",
46
+ tile: "var(--tile-error)",
47
47
  icon: (
48
48
  <svg width="13" height="13" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
49
49
  <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
@@ -63,25 +63,28 @@ export function Alert({ variant = "default", title, description, onDismiss, clas
63
63
  style={{ background: "var(--panel-bezel)", boxShadow: "var(--panel-shadow)" }}
64
64
  >
65
65
  <div
66
- className="bg-white rounded-[13px] pl-3 pr-2 py-3 flex items-start gap-3"
66
+ className="bg-panel-face rounded-[13px] pl-3 pr-2 py-3 flex items-start gap-3"
67
67
  style={{ boxShadow: "var(--panel-face-shadow)" }}
68
68
  >
69
69
  <div
70
70
  aria-hidden
71
- className="shrink-0 w-7 h-7 rounded-[8px] flex items-center justify-center text-white"
71
+ className={[
72
+ "shrink-0 w-7 h-7 rounded-[8px] flex items-center justify-center",
73
+ // the default tile is the inverse face; status tiles stay white-on-colour
74
+ tile === "var(--face-inverse)" ? "text-ink-inverse" : "text-white",
75
+ ].join(" ")}
72
76
  style={{
73
77
  background: tile,
74
- boxShadow:
75
- "0 0 3px rgba(0,0,0,0.212), 0 1px 2px rgba(0,0,0,0.15), inset 0 2px 3px -2px rgba(255,255,255,0.188)",
78
+ boxShadow: "var(--tile-status-shadow)",
76
79
  }}
77
80
  >
78
81
  {icon}
79
82
  </div>
80
83
 
81
84
  <div className="flex-1 min-w-0 pt-[5px]">
82
- <p className="text-[13px] font-semibold text-neutral-900 leading-tight tracking-[-0.01em]">{title}</p>
85
+ <p className="text-[13px] font-semibold text-ink leading-tight tracking-[-0.01em]">{title}</p>
83
86
  {description && (
84
- <p className="text-[12.5px] text-neutral-500 mt-1 leading-relaxed">{description}</p>
87
+ <p className="text-[12.5px] text-ink-muted mt-1 leading-relaxed">{description}</p>
85
88
  )}
86
89
  </div>
87
90
 
@@ -25,17 +25,17 @@ export function AuthorCard({
25
25
  ]
26
26
  .filter(Boolean)
27
27
  .join(" ")}
28
- style={{ background: "var(--panel-bezel)", boxShadow: "var(--card-light-shadow)" }}
28
+ style={{ background: "var(--panel-bezel)", boxShadow: "var(--card-shadow)" }}
29
29
  >
30
- <div className="relative bg-white rounded-[17px] overflow-hidden" style={{ boxShadow: "var(--panel-face-shadow)" }}>
30
+ <div className="relative bg-panel-face rounded-[17px] overflow-hidden" style={{ boxShadow: "var(--panel-face-shadow)" }}>
31
31
  {/* Cover: a sunken well, so the avatar visibly sits on top of it */}
32
32
  <div
33
33
  aria-hidden
34
34
  className="h-20 relative overflow-hidden"
35
35
  style={{
36
36
  background:
37
- "radial-gradient(130% 90% at 25% 0%, rgba(255,255,255,0.7), transparent 60%), linear-gradient(180deg, #ececeb 0%, #f4f4f3 100%)",
38
- boxShadow: "inset 0 1px 3px rgba(17,17,17,0.06), inset 0 -2px 3px -2px rgba(17,17,17,0.05)",
37
+ "var(--cover)",
38
+ boxShadow: "var(--cover-shadow)",
39
39
  }}
40
40
  />
41
41
  <div className="px-6 pb-6">
@@ -44,14 +44,14 @@ export function AuthorCard({
44
44
  src={avatarSrc}
45
45
  name={name}
46
46
  size="lg"
47
- className="ring-[3.5px] ring-white shadow-[0_3px_12px_-2px_rgba(0,0,0,0.15),0_0_0_1px_rgba(0,0,0,0.05)]"
47
+ className="ring-[3.5px] ring-panel-face shadow-[var(--avatar-lift)]"
48
48
  />
49
49
  </div>
50
50
  <div className="flex items-start justify-between gap-4">
51
51
  <div className="flex-1 min-w-0">
52
- <p className="text-[15px] font-semibold text-neutral-900 leading-snug truncate tracking-[-0.01em]">{name}</p>
52
+ <p className="text-[15px] font-semibold text-ink leading-snug truncate tracking-[-0.01em]">{name}</p>
53
53
  {bio && (
54
- <p className="text-[13px] text-neutral-500 leading-relaxed mt-1 line-clamp-2">{bio}</p>
54
+ <p className="text-[13px] text-ink-muted leading-relaxed mt-1 line-clamp-2">{bio}</p>
55
55
  )}
56
56
  </div>
57
57
  {onFollow && (
@@ -37,13 +37,13 @@ export function Avatar({
37
37
  return (
38
38
  <div
39
39
  className={[
40
- "relative shrink-0 rounded-full overflow-hidden flex items-center justify-center font-semibold text-neutral-600 select-none",
40
+ "relative shrink-0 rounded-full overflow-hidden flex items-center justify-center font-semibold text-ink-body select-none",
41
41
  sizeStyles[size],
42
42
  className,
43
43
  ]
44
44
  .filter(Boolean)
45
45
  .join(" ")}
46
- style={{ background: "var(--face-light)" }}
46
+ style={{ background: "var(--face)" }}
47
47
  >
48
48
  {src ? (
49
49
  // eslint-disable-next-line @next/next/no-img-element
@@ -55,7 +55,7 @@ export function Avatar({
55
55
  aria-hidden
56
56
  className="pointer-events-none absolute inset-0 rounded-full"
57
57
  style={{
58
- boxShadow: "inset 0 2px 3px -2px rgba(255,255,255,0.45), inset 0 0 2px rgba(17,17,17,0.12), inset 0 -2px 3px -2px rgba(17,17,17,0.04)",
58
+ boxShadow: "var(--avatar-rim)",
59
59
  }}
60
60
  />
61
61
  </div>
@@ -91,7 +91,7 @@ export function AvatarGroup({
91
91
  <div
92
92
  key={i}
93
93
  className={[
94
- "ring-2 ring-white rounded-full",
94
+ "ring-2 ring-panel-face rounded-full",
95
95
  i > 0 ? overlapStyles[size] : "",
96
96
  ].join(" ")}
97
97
  >
@@ -101,7 +101,7 @@ export function AvatarGroup({
101
101
  {overflow > 0 && (
102
102
  <div
103
103
  className={[
104
- "ring-2 ring-white rounded-full flex items-center justify-center font-semibold text-neutral-600 select-none",
104
+ "ring-2 ring-panel-face rounded-full flex items-center justify-center font-semibold text-ink-body select-none",
105
105
  overlapStyles[size],
106
106
  sizeStyles[size],
107
107
  ].join(" ")}
@@ -9,30 +9,27 @@ interface BadgeProps {
9
9
  }
10
10
 
11
11
  const variantStyles: Record<BadgeVariant, string> = {
12
- default: "text-white",
13
- muted: "text-neutral-600",
14
- accent: "text-violet-700",
12
+ default: "text-ink-inverse",
13
+ muted: "text-ink-body",
14
+ accent: "text-accent",
15
15
  };
16
16
 
17
17
  /* Badges are small raised faces, lit from the same top edge as buttons. */
18
18
  const variantSurfaces: Record<BadgeVariant, React.CSSProperties> = {
19
19
  default: {
20
- background: "var(--face-dark)",
21
- boxShadow:
22
- "0 0 3px rgba(0,0,0,0.35), 0 1px 2px rgba(0,0,0,0.2), inset 0 2px 3px -2px rgba(255,255,255,0.105)",
23
- textShadow: "0 -1px 0 rgba(0,0,0,0.4)",
20
+ background: "var(--face-inverse)",
21
+ boxShadow: "var(--badge-inverse-shadow)",
22
+ textShadow: "var(--emboss-inverse)",
24
23
  },
25
24
  muted: {
26
- background: "linear-gradient(180deg, #ffffff 0%, #f1f1f1 100%)",
27
- boxShadow:
28
- "0 0 3px rgba(17,17,17,0.119), 0 1px 1.5px rgba(17,17,17,0.07), inset 0 2px 3px -2px rgba(255,255,255,1)",
29
- textShadow: "0 1px 0 rgba(255,255,255,0.9)",
25
+ background: "var(--badge)",
26
+ boxShadow: "var(--badge-shadow)",
27
+ textShadow: "var(--emboss)",
30
28
  },
31
29
  accent: {
32
- background: "linear-gradient(180deg, #f5f0ff 0%, #ebe1ff 100%)",
33
- boxShadow:
34
- "0 0 3px rgba(124,58,237,0.255), 0 1px 1.5px rgba(124,58,237,0.12), inset 0 2px 3px -2px rgba(255,255,255,0.637)",
35
- textShadow: "0 1px 0 rgba(255,255,255,0.7)",
30
+ background: "var(--badge-accent)",
31
+ boxShadow: "var(--badge-accent-shadow)",
32
+ textShadow: "var(--emboss-soft)",
36
33
  },
37
34
  };
38
35
 
@@ -6,37 +6,34 @@ import { CloseButton } from "./CloseButton";
6
6
  type BannerVariant = "default" | "accent" | "warning";
7
7
 
8
8
  /* A banner is a dark bezel holding a dark face — the same build as the
9
- primary button, stretched to full width. */
9
+ primary button, stretched to full width. On a dark page it stays a
10
+ dark strip (the mid material) rather than a full-width pale bar. */
10
11
  const variantStyles: Record<
11
12
  BannerVariant,
12
13
  { bezel: string; bezelShadow: string; face: string; faceShadow: string; text: string; tone: "light" | "dark" }
13
14
  > = {
14
15
  default: {
15
- bezel: "var(--bezel-dark)",
16
- bezelShadow: "var(--bezel-dark-shadow)",
17
- face: "var(--face-dark)",
18
- faceShadow: "var(--face-dark-shadow)",
16
+ bezel: "var(--banner-bezel)",
17
+ bezelShadow: "var(--banner-bezel-shadow)",
18
+ face: "var(--banner-face)",
19
+ faceShadow: "var(--banner-face-shadow)",
19
20
  text: "text-white",
20
21
  tone: "dark",
21
22
  },
22
23
  accent: {
23
- bezel: "linear-gradient(180deg, #4c2a99 0%, #2e1065 100%)",
24
- bezelShadow:
25
- "0 0 0 0.5px rgba(46,16,101,0.36), 0 4px 12px -4px rgba(76,29,149,0.45), inset 0 2px 3px -2px rgba(255,255,255,0.09)",
26
- face: "linear-gradient(180deg, #7c3aed 0%, #6d28d9 100%)",
27
- faceShadow:
28
- "0 0 3px rgba(46,16,101,0.35), 0 1px 2px rgba(46,16,101,0.4), inset 0 2px 3px -2px rgba(255,255,255,0.165)",
24
+ bezel: "var(--bezel-violet)",
25
+ bezelShadow: "var(--bezel-violet-shadow)",
26
+ face: "var(--face-violet)",
27
+ faceShadow: "var(--face-violet-shadow)",
29
28
  text: "text-white",
30
29
  tone: "dark",
31
30
  },
32
31
  warning: {
33
- bezel: "linear-gradient(180deg, #fde68a 0%, #f5c84b 100%)",
34
- bezelShadow:
35
- "0 0 0 0.5px rgba(180,83,9,0.15), 0 4px 12px -4px rgba(217,119,6,0.3), inset 0 2px 3px -2px rgba(255,255,255,0.45)",
36
- face: "linear-gradient(180deg, #fffbeb 0%, #fef3c7 100%)",
37
- faceShadow:
38
- "0 0 3px rgba(180,83,9,0.255), 0 1px 2px rgba(180,83,9,0.12), inset 0 2px 3px -2px rgba(255,255,255,1)",
39
- text: "text-amber-900",
32
+ bezel: "var(--bezel-warning)",
33
+ bezelShadow: "var(--bezel-warning-shadow)",
34
+ face: "var(--face-warning)",
35
+ faceShadow: "var(--face-warning-shadow)",
36
+ text: "text-warning-ink",
40
37
  tone: "light",
41
38
  },
42
39
  };
@@ -28,7 +28,7 @@ export function Breadcrumb({ items, className = "" }: BreadcrumbProps) {
28
28
  stroke="currentColor"
29
29
  strokeWidth="2"
30
30
  viewBox="0 0 24 24"
31
- className="text-neutral-300 shrink-0"
31
+ className="text-ink-ghost shrink-0"
32
32
  >
33
33
  <path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
34
34
  </svg>
@@ -38,7 +38,7 @@ export function Breadcrumb({ items, className = "" }: BreadcrumbProps) {
38
38
  aria-current={isLast ? "page" : undefined}
39
39
  className={[
40
40
  "text-[12.5px] px-1.5 py-0.5 truncate",
41
- isLast ? "font-medium text-neutral-900" : "text-neutral-500",
41
+ isLast ? "font-medium text-ink" : "text-ink-muted",
42
42
  ].join(" ")}
43
43
  >
44
44
  {item.label}
@@ -46,7 +46,7 @@ export function Breadcrumb({ items, className = "" }: BreadcrumbProps) {
46
46
  ) : (
47
47
  <a
48
48
  href={item.href}
49
- className="text-[12.5px] text-neutral-500 hover:text-neutral-900 px-1.5 py-0.5 rounded-[6px] hover:bg-neutral-900/[0.05] transition-colors truncate"
49
+ className="text-[12.5px] text-ink-muted hover:text-ink px-1.5 py-0.5 rounded-[6px] hover:bg-ink/[0.05] transition-colors truncate"
50
50
  >
51
51
  {item.label}
52
52
  </a>
@@ -34,59 +34,52 @@ const shellConfig: Record<
34
34
  }
35
35
  > = {
36
36
  primary: {
37
- bezel: "var(--bezel-dark)",
38
- bezelShadow: "var(--bezel-dark-shadow)",
39
- face: "var(--face-dark)",
40
- faceShadow: "var(--face-dark-shadow)",
41
- faceHoverShadow:
42
- "0 0 3px rgba(0,0,0,0.35), 0 2px 3px rgba(0,0,0,0.45), 0 4px 8px -2px rgba(0,0,0,0.35), inset 0 2px 3px -2px rgba(255,255,255,0.135), inset 0 -2px 3px -2px rgba(0,0,0,0.25)",
43
- facePressedShadow: "var(--face-dark-pressed)",
44
- text: "text-white",
45
- textShadow: "0 -1px 0 rgba(0,0,0,0.45)",
46
- fill: "bg-white/[0.05]",
37
+ bezel: "var(--bezel-inverse)",
38
+ bezelShadow: "var(--bezel-inverse-shadow)",
39
+ face: "var(--face-inverse)",
40
+ faceShadow: "var(--face-inverse-shadow)",
41
+ faceHoverShadow: "var(--face-inverse-shadow-hover)",
42
+ facePressedShadow: "var(--face-inverse-pressed)",
43
+ text: "text-ink-inverse",
44
+ textShadow: "var(--emboss-inverse-deep)",
45
+ fill: "bg-ink-inverse/[0.05]",
47
46
  },
48
47
  secondary: {
49
- bezel: "var(--bezel-light)",
50
- bezelShadow: "var(--bezel-light-shadow)",
51
- face: "var(--face-light)",
52
- faceShadow: "var(--face-light-shadow)",
53
- faceHoverShadow:
54
- "0 0 3px rgba(17,17,17,0.136), 0 2px 3px rgba(17,17,17,0.1), 0 4px 8px -2px rgba(17,17,17,0.08), inset 0 2px 3px -2px rgba(255,255,255,1), inset 0 -2px 3px -2px rgba(17,17,17,0.035)",
55
- facePressedShadow: "var(--face-light-pressed)",
56
- text: "text-neutral-800",
57
- textShadow: "0 1px 0 rgba(255,255,255,0.9)",
58
- fill: "bg-neutral-900/[0.025]",
48
+ bezel: "var(--bezel)",
49
+ bezelShadow: "var(--bezel-shadow)",
50
+ face: "var(--face)",
51
+ faceShadow: "var(--face-shadow)",
52
+ faceHoverShadow: "var(--face-shadow-hover)",
53
+ facePressedShadow: "var(--face-pressed)",
54
+ text: "text-ink-soft",
55
+ textShadow: "var(--emboss)",
56
+ fill: "bg-ink/[0.025]",
59
57
  },
60
58
  outline: {
61
- bezel: "linear-gradient(180deg, rgba(255,255,255,0.5) 0%, rgba(17,17,17,0.04) 100%)",
62
- bezelShadow:
63
- "0 0 0 0.5px rgba(17,17,17,0.048), inset 0 2px 3px -2px rgba(255,255,255,0.45)",
64
- face: "var(--face-light)",
65
- faceShadow:
66
- "0 0 3px rgba(17,17,17,0.102), 0 1px 1px rgba(17,17,17,0.06), inset 0 2px 3px -2px rgba(255,255,255,1)",
67
- faceHoverShadow:
68
- "0 0 3px rgba(17,17,17,0.136), 0 1px 2px rgba(17,17,17,0.08), 0 3px 6px -2px rgba(17,17,17,0.06), inset 0 2px 3px -2px rgba(255,255,255,1)",
69
- facePressedShadow: "var(--face-light-pressed)",
70
- text: "text-neutral-700",
71
- textShadow: "0 1px 0 rgba(255,255,255,0.9)",
72
- fill: "bg-neutral-900/[0.025]",
59
+ bezel: "var(--outline-bezel)",
60
+ bezelShadow: "var(--outline-bezel-shadow)",
61
+ face: "var(--face)",
62
+ faceShadow: "var(--outline-face-shadow)",
63
+ faceHoverShadow: "var(--outline-face-shadow-hover)",
64
+ facePressedShadow: "var(--face-pressed)",
65
+ text: "text-ink-label",
66
+ textShadow: "var(--emboss)",
67
+ fill: "bg-ink/[0.025]",
73
68
  },
74
69
  };
75
70
 
76
71
  /* Secondary on a dark surface: a raised face a few steps lighter than the
77
- card — present, but in the same family of tones. */
72
+ card — present, but in the same family of tones. It's the mid material,
73
+ the same one secondary keys wear everywhere in the dark theme. */
78
74
  const darkSecondary = {
79
- bezel: "linear-gradient(180deg, #2a2a2a 0%, #161616 100%)",
80
- bezelShadow:
81
- "0 0 0 0.5px rgba(0,0,0,0.3), 0 1px 2px rgba(0,0,0,0.35), inset 0 2px 3px -2px rgba(255,255,255,0.053)",
82
- face: "linear-gradient(180deg, #353535 0%, #2a2a2a 55%, #242424 100%)",
83
- faceShadow:
84
- "0 0 3px rgba(0,0,0,0.35), 0 1px 1.5px rgba(0,0,0,0.4), inset 0 2px 3px -2px rgba(255,255,255,0.09), inset 0 -2px 3px -2px rgba(0,0,0,0.2)",
85
- faceHoverShadow:
86
- "0 0 3px rgba(0,0,0,0.35), 0 2px 3px rgba(0,0,0,0.45), 0 4px 8px -2px rgba(0,0,0,0.35), inset 0 2px 3px -2px rgba(255,255,255,0.12), inset 0 -2px 3px -2px rgba(0,0,0,0.2)",
75
+ bezel: "var(--bezel-mid)",
76
+ bezelShadow: "var(--bezel-mid-shadow)",
77
+ face: "var(--face-mid)",
78
+ faceShadow: "var(--face-mid-shadow)",
79
+ faceHoverShadow: "var(--face-mid-shadow-hover)",
87
80
  facePressedShadow: "var(--face-dark-pressed)",
88
81
  text: "text-neutral-100",
89
- textShadow: "0 -1px 0 rgba(0,0,0,0.4)",
82
+ textShadow: "var(--emboss-inverse)",
90
83
  fill: "bg-white/[0.04]",
91
84
  };
92
85
 
@@ -103,38 +96,34 @@ const flatConfig: Record<
103
96
  }
104
97
  > = {
105
98
  primary: {
106
- bg: "text-white",
107
- background: "var(--face-dark)",
108
- fill: "bg-white/[0.08]",
109
- shadow: "var(--face-dark-shadow)",
110
- hoverShadow:
111
- "0 0 3px rgba(0,0,0,0.35), 0 2px 4px rgba(0,0,0,0.4), 0 6px 14px -4px rgba(0,0,0,0.35), inset 0 2px 3px -2px rgba(255,255,255,0.135)",
112
- pressedShadow: "var(--face-dark-pressed)",
99
+ bg: "text-ink-inverse",
100
+ background: "var(--face-inverse)",
101
+ fill: "bg-ink-inverse/[0.08]",
102
+ shadow: "var(--face-inverse-shadow)",
103
+ hoverShadow: "var(--face-inverse-float-shadow)",
104
+ pressedShadow: "var(--face-inverse-pressed)",
113
105
  },
114
106
  secondary: {
115
- bg: "text-neutral-800",
116
- background: "var(--face-light)",
117
- fill: "bg-neutral-900/[0.035]",
118
- shadow: "var(--face-light-shadow)",
119
- hoverShadow:
120
- "0 0 3px rgba(17,17,17,0.136), 0 2px 4px rgba(17,17,17,0.08), 0 6px 14px -4px rgba(17,17,17,0.1), inset 0 2px 3px -2px rgba(255,255,255,1)",
121
- pressedShadow: "var(--face-light-pressed)",
107
+ bg: "text-ink-soft",
108
+ background: "var(--face)",
109
+ fill: "bg-ink/[0.035]",
110
+ shadow: "var(--face-shadow)",
111
+ hoverShadow: "var(--face-float-shadow)",
112
+ pressedShadow: "var(--face-pressed)",
122
113
  },
123
114
  ghost: {
124
- bg: "bg-transparent text-neutral-500 hover:text-neutral-800",
125
- fill: "bg-neutral-900/[0.05]",
115
+ bg: "bg-transparent text-ink-muted hover:text-ink-soft",
116
+ fill: "bg-ink/[0.05]",
126
117
  shadow: "none",
127
- hoverShadow: "inset 0 0 2px rgba(17,17,17,0.09)",
128
- pressedShadow: "inset 0 1px 2px rgba(17,17,17,0.08)",
118
+ hoverShadow: "var(--ghost-shadow-hover)",
119
+ pressedShadow: "var(--ghost-shadow-pressed)",
129
120
  },
130
121
  outline: {
131
- bg: "bg-transparent text-neutral-700",
132
- fill: "bg-neutral-900/[0.035]",
133
- shadow: "inset 0 0 2px rgba(17,17,17,0.15)",
134
- hoverShadow:
135
- "inset 0 0 2px rgba(17,17,17,0.24), 0 1px 2px rgba(17,17,17,0.05)",
136
- pressedShadow:
137
- "inset 0 0 2px rgba(17,17,17,0.24), inset 0 1px 2px rgba(17,17,17,0.06)",
122
+ bg: "bg-transparent text-ink-label",
123
+ fill: "bg-ink/[0.035]",
124
+ shadow: "var(--outline-shadow)",
125
+ hoverShadow: "var(--outline-shadow-hover)",
126
+ pressedShadow: "var(--outline-shadow-pressed)",
138
127
  },
139
128
  };
140
129
 
@@ -264,7 +253,7 @@ export function Button({
264
253
  className={[
265
254
  "group/btn relative inline-flex cursor-pointer select-none whitespace-nowrap",
266
255
  shellRadius[size],
267
- "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-900",
256
+ "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink",
268
257
  "disabled:opacity-40 disabled:pointer-events-none",
269
258
  fullWidth ? "w-full" : "",
270
259
  className,
@@ -331,7 +320,7 @@ export function Button({
331
320
  className={[
332
321
  "btn-face group/btn relative overflow-hidden inline-flex items-center justify-center gap-1.5 font-medium cursor-pointer select-none whitespace-nowrap",
333
322
  "transition-colors duration-200 ease-out",
334
- "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-900",
323
+ "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ink",
335
324
  "disabled:opacity-40 disabled:pointer-events-none",
336
325
  cfg.bg,
337
326
  flatSizeStyles[size],
@@ -24,26 +24,26 @@ export function ButtonGroup({ items, label, className = "" }: ButtonGroupProps)
24
24
  role="group"
25
25
  aria-label={label}
26
26
  className={["inline-flex w-fit rounded-[12px] p-[3px]", className].join(" ")}
27
- style={{ background: "var(--bezel-light)", boxShadow: "var(--bezel-light-shadow)" }}
27
+ style={{ background: "var(--bezel)", boxShadow: "var(--bezel-shadow)" }}
28
28
  >
29
29
  <div
30
30
  className="flex rounded-[9px] overflow-hidden"
31
- style={{ background: "var(--face-light)", boxShadow: "var(--face-light-shadow)" }}
31
+ style={{ background: "var(--face)", boxShadow: "var(--face-shadow)" }}
32
32
  >
33
33
  {items.map((item, i) => {
34
34
  const keyClass = [
35
35
  "group/key relative h-9 inline-flex items-center justify-center gap-1.5 cursor-pointer",
36
- "text-[13px] font-medium text-neutral-800 outline-none",
36
+ "text-[13px] font-medium text-ink-soft outline-none",
37
37
  "transition-[box-shadow,background] duration-150",
38
- "active:bg-neutral-900/[0.04] active:[box-shadow:inset_0_1px_2px_rgba(17,17,17,0.08)]",
39
- "focus-visible:[box-shadow:inset_0_0_0_2px_rgba(17,17,17,0.85)]",
38
+ "active:bg-ink/[0.04] active:[box-shadow:var(--ghost-shadow-pressed)]",
39
+ "focus-visible:[box-shadow:var(--focus-ring-inset)]",
40
40
  item.wide ? "px-4" : "w-10",
41
41
  ].join(" ");
42
42
  const inner = (
43
43
  <>
44
44
  <span
45
45
  aria-hidden
46
- className="pointer-events-none absolute inset-0 origin-bottom scale-y-0 bg-neutral-900/[0.025] transition-transform duration-300 ease-out group-hover/key:scale-y-100"
46
+ className="pointer-events-none absolute inset-0 origin-bottom scale-y-0 bg-ink/[0.025] transition-transform duration-300 ease-out group-hover/key:scale-y-100"
47
47
  />
48
48
  <span className="relative inline-flex items-center gap-1.5">{item.content}</span>
49
49
  </>
@@ -52,7 +52,7 @@ export function ButtonGroup({ items, label, className = "" }: ButtonGroupProps)
52
52
  "aria-label": item.label,
53
53
  title: item.wide ? undefined : item.label,
54
54
  className: keyClass,
55
- style: { textShadow: "0 1px 0 rgba(255,255,255,0.9)" },
55
+ style: { textShadow: "var(--emboss)" },
56
56
  };
57
57
 
58
58
  return (
@@ -60,8 +60,8 @@ export function ButtonGroup({ items, label, className = "" }: ButtonGroupProps)
60
60
  {i > 0 && (
61
61
  <span
62
62
  aria-hidden
63
- className="w-px self-stretch my-2 bg-neutral-900/[0.08]"
64
- style={{ boxShadow: "1px 0 0 rgba(255,255,255,0.9)" }}
63
+ className="w-px self-stretch my-2 bg-groove"
64
+ style={{ boxShadow: "var(--groove-lip)" }}
65
65
  />
66
66
  )}
67
67
  {item.href ? (