@spatika/react 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (369) hide show
  1. package/AGENTS.md +71 -0
  2. package/README.md +4 -3
  3. package/dist/composites/AccentRule.d.ts +6 -0
  4. package/dist/composites/AccentRule.d.ts.map +1 -0
  5. package/dist/composites/AccentRule.js +6 -0
  6. package/dist/composites/AvailabilityBadge.d.ts +9 -0
  7. package/dist/composites/AvailabilityBadge.d.ts.map +1 -0
  8. package/dist/composites/AvailabilityBadge.js +9 -0
  9. package/dist/composites/Card.d.ts +7 -1
  10. package/dist/composites/Card.d.ts.map +1 -1
  11. package/dist/composites/Card.js +10 -0
  12. package/dist/composites/CareerCard.d.ts +20 -0
  13. package/dist/composites/CareerCard.d.ts.map +1 -0
  14. package/dist/composites/CareerCard.js +17 -0
  15. package/dist/composites/ChartDataGrid.d.ts +33 -0
  16. package/dist/composites/ChartDataGrid.d.ts.map +1 -0
  17. package/dist/composites/ChartDataGrid.js +77 -0
  18. package/dist/composites/ContactLink.d.ts +15 -0
  19. package/dist/composites/ContactLink.d.ts.map +1 -0
  20. package/dist/composites/ContactLink.js +8 -0
  21. package/dist/composites/CoverHero.d.ts +1 -1
  22. package/dist/composites/CoverHero.d.ts.map +1 -1
  23. package/dist/composites/CoverHero.js +2 -2
  24. package/dist/composites/DisplayHeading.d.ts +9 -0
  25. package/dist/composites/DisplayHeading.d.ts.map +1 -0
  26. package/dist/composites/DisplayHeading.js +6 -0
  27. package/dist/composites/EntityMediaCard.d.ts +1 -1
  28. package/dist/composites/EntityMediaCard.d.ts.map +1 -1
  29. package/dist/composites/EntityMediaCard.js +12 -4
  30. package/dist/composites/EventCalendar.d.ts +35 -0
  31. package/dist/composites/EventCalendar.d.ts.map +1 -0
  32. package/dist/composites/EventCalendar.js +339 -0
  33. package/dist/composites/EventEditor.d.ts +22 -0
  34. package/dist/composites/EventEditor.d.ts.map +1 -0
  35. package/dist/composites/EventEditor.js +86 -0
  36. package/dist/composites/EventTimeline.d.ts +24 -0
  37. package/dist/composites/EventTimeline.d.ts.map +1 -0
  38. package/dist/composites/EventTimeline.js +184 -0
  39. package/dist/composites/FloatChip.d.ts +15 -0
  40. package/dist/composites/FloatChip.d.ts.map +1 -0
  41. package/dist/composites/FloatChip.js +7 -0
  42. package/dist/composites/GradientText.d.ts +11 -0
  43. package/dist/composites/GradientText.d.ts.map +1 -0
  44. package/dist/composites/GradientText.js +6 -0
  45. package/dist/composites/IconTile.d.ts +10 -0
  46. package/dist/composites/IconTile.d.ts.map +1 -0
  47. package/dist/composites/IconTile.js +7 -0
  48. package/dist/composites/MapChart.d.ts +24 -0
  49. package/dist/composites/MapChart.d.ts.map +1 -0
  50. package/dist/composites/MapChart.js +58 -0
  51. package/dist/composites/MetaChip.d.ts +9 -0
  52. package/dist/composites/MetaChip.d.ts.map +1 -0
  53. package/dist/composites/MetaChip.js +6 -0
  54. package/dist/composites/PresenceDot.d.ts +10 -0
  55. package/dist/composites/PresenceDot.d.ts.map +1 -0
  56. package/dist/composites/PresenceDot.js +10 -0
  57. package/dist/composites/ProfileHero.d.ts +6 -4
  58. package/dist/composites/ProfileHero.d.ts.map +1 -1
  59. package/dist/composites/ProfileHero.js +8 -8
  60. package/dist/composites/ProjectCard.d.ts +12 -0
  61. package/dist/composites/ProjectCard.d.ts.map +1 -0
  62. package/dist/composites/ProjectCard.js +9 -0
  63. package/dist/composites/ScatterWebGL.d.ts +22 -0
  64. package/dist/composites/ScatterWebGL.d.ts.map +1 -0
  65. package/dist/composites/ScatterWebGL.js +45 -0
  66. package/dist/composites/SectionHeading.d.ts +16 -0
  67. package/dist/composites/SectionHeading.d.ts.map +1 -0
  68. package/dist/composites/SectionHeading.js +12 -0
  69. package/dist/composites/SiteFooter.d.ts +18 -0
  70. package/dist/composites/SiteFooter.d.ts.map +1 -0
  71. package/dist/composites/SiteFooter.js +15 -0
  72. package/dist/composites/SiteNav.d.ts +25 -0
  73. package/dist/composites/SiteNav.d.ts.map +1 -0
  74. package/dist/composites/SiteNav.js +44 -0
  75. package/dist/composites/StatCard.d.ts +7 -1
  76. package/dist/composites/StatCard.d.ts.map +1 -1
  77. package/dist/composites/StatCard.js +5 -2
  78. package/dist/composites/StatusDot.d.ts +1 -1
  79. package/dist/composites/Tag.d.ts +11 -0
  80. package/dist/composites/Tag.d.ts.map +1 -0
  81. package/dist/composites/Tag.js +33 -0
  82. package/dist/composites/Wordmark.d.ts +19 -0
  83. package/dist/composites/Wordmark.d.ts.map +1 -0
  84. package/dist/composites/Wordmark.js +15 -0
  85. package/dist/composites/cartesian-charts.d.ts +138 -0
  86. package/dist/composites/cartesian-charts.d.ts.map +1 -0
  87. package/dist/composites/cartesian-charts.js +401 -0
  88. package/dist/composites/chart-composition.d.ts +139 -0
  89. package/dist/composites/chart-composition.d.ts.map +1 -0
  90. package/dist/composites/chart-composition.js +486 -0
  91. package/dist/composites/chart-ui.d.ts +83 -0
  92. package/dist/composites/chart-ui.d.ts.map +1 -0
  93. package/dist/composites/chart-ui.js +108 -0
  94. package/dist/composites/charts-3d.d.ts +37 -0
  95. package/dist/composites/charts-3d.d.ts.map +1 -0
  96. package/dist/composites/charts-3d.js +121 -0
  97. package/dist/composites/flow-charts.d.ts +89 -0
  98. package/dist/composites/flow-charts.d.ts.map +1 -0
  99. package/dist/composites/flow-charts.js +186 -0
  100. package/dist/composites/radial-charts.d.ts +109 -0
  101. package/dist/composites/radial-charts.d.ts.map +1 -0
  102. package/dist/composites/radial-charts.js +176 -0
  103. package/dist/composites/scheduler-ui.d.ts +43 -0
  104. package/dist/composites/scheduler-ui.d.ts.map +1 -0
  105. package/dist/composites/scheduler-ui.js +71 -0
  106. package/dist/index.d.ts +83 -7
  107. package/dist/index.d.ts.map +1 -1
  108. package/dist/index.js +74 -7
  109. package/dist/lib/chart-export.d.ts +10 -0
  110. package/dist/lib/chart-export.d.ts.map +1 -0
  111. package/dist/lib/chart-export.js +102 -0
  112. package/dist/lib/charts.d.ts +235 -0
  113. package/dist/lib/charts.d.ts.map +1 -0
  114. package/dist/lib/charts.js +744 -0
  115. package/dist/lib/geo.d.ts +39 -0
  116. package/dist/lib/geo.d.ts.map +1 -0
  117. package/dist/lib/geo.js +101 -0
  118. package/dist/lib/iso.d.ts +21 -0
  119. package/dist/lib/iso.d.ts.map +1 -0
  120. package/dist/lib/iso.js +56 -0
  121. package/dist/lib/rrule.d.ts +20 -0
  122. package/dist/lib/rrule.d.ts.map +1 -0
  123. package/dist/lib/rrule.js +236 -0
  124. package/dist/lib/scheduler.d.ts +152 -0
  125. package/dist/lib/scheduler.d.ts.map +1 -0
  126. package/dist/lib/scheduler.js +501 -0
  127. package/dist/lib/webgl-scatter.d.ts +44 -0
  128. package/dist/lib/webgl-scatter.d.ts.map +1 -0
  129. package/dist/lib/webgl-scatter.js +185 -0
  130. package/dist/primitives/Accordion.d.ts +44 -20
  131. package/dist/primitives/Accordion.d.ts.map +1 -1
  132. package/dist/primitives/Accordion.js +82 -22
  133. package/dist/primitives/AppBar.d.ts +13 -0
  134. package/dist/primitives/AppBar.d.ts.map +1 -0
  135. package/dist/primitives/AppBar.js +27 -0
  136. package/dist/primitives/Autocomplete.d.ts +28 -0
  137. package/dist/primitives/Autocomplete.d.ts.map +1 -0
  138. package/dist/primitives/Autocomplete.js +118 -0
  139. package/dist/primitives/Avatar.d.ts +13 -1
  140. package/dist/primitives/Avatar.d.ts.map +1 -1
  141. package/dist/primitives/Avatar.js +17 -2
  142. package/dist/primitives/Backdrop.d.ts +17 -0
  143. package/dist/primitives/Backdrop.d.ts.map +1 -0
  144. package/dist/primitives/Backdrop.js +17 -0
  145. package/dist/primitives/BottomNavigation.d.ts +23 -0
  146. package/dist/primitives/BottomNavigation.d.ts.map +1 -0
  147. package/dist/primitives/BottomNavigation.js +30 -0
  148. package/dist/primitives/Button.d.ts +4 -4
  149. package/dist/primitives/Button.d.ts.map +1 -1
  150. package/dist/primitives/Button.js +2 -0
  151. package/dist/primitives/ButtonBase.d.ts +14 -0
  152. package/dist/primitives/ButtonBase.d.ts.map +1 -0
  153. package/dist/primitives/ButtonBase.js +13 -0
  154. package/dist/primitives/ButtonGroup.d.ts +25 -0
  155. package/dist/primitives/ButtonGroup.d.ts.map +1 -0
  156. package/dist/primitives/ButtonGroup.js +44 -0
  157. package/dist/primitives/CircularProgress.d.ts +16 -0
  158. package/dist/primitives/CircularProgress.d.ts.map +1 -0
  159. package/dist/primitives/CircularProgress.js +17 -0
  160. package/dist/primitives/ClickAwayListener.d.ts +16 -0
  161. package/dist/primitives/ClickAwayListener.d.ts.map +1 -0
  162. package/dist/primitives/ClickAwayListener.js +33 -0
  163. package/dist/primitives/Collapse.d.ts +19 -0
  164. package/dist/primitives/Collapse.d.ts.map +1 -0
  165. package/dist/primitives/Collapse.js +14 -0
  166. package/dist/primitives/Container.d.ts +13 -0
  167. package/dist/primitives/Container.d.ts.map +1 -0
  168. package/dist/primitives/Container.js +26 -0
  169. package/dist/primitives/Fab.d.ts +22 -0
  170. package/dist/primitives/Fab.d.ts.map +1 -0
  171. package/dist/primitives/Fab.js +42 -0
  172. package/dist/primitives/FormControl.d.ts +27 -0
  173. package/dist/primitives/FormControl.d.ts.map +1 -0
  174. package/dist/primitives/FormControl.js +24 -0
  175. package/dist/primitives/FormControlLabel.d.ts +18 -0
  176. package/dist/primitives/FormControlLabel.d.ts.map +1 -0
  177. package/dist/primitives/FormControlLabel.js +19 -0
  178. package/dist/primitives/Grid.d.ts +32 -0
  179. package/dist/primitives/Grid.d.ts.map +1 -0
  180. package/dist/primitives/Grid.js +50 -0
  181. package/dist/primitives/IconButton.d.ts +22 -0
  182. package/dist/primitives/IconButton.d.ts.map +1 -0
  183. package/dist/primitives/IconButton.js +39 -0
  184. package/dist/primitives/ImageList.d.ts +31 -0
  185. package/dist/primitives/ImageList.d.ts.map +1 -0
  186. package/dist/primitives/ImageList.js +33 -0
  187. package/dist/primitives/InputAdornment.d.ts +28 -0
  188. package/dist/primitives/InputAdornment.d.ts.map +1 -0
  189. package/dist/primitives/InputAdornment.js +13 -0
  190. package/dist/primitives/Link.d.ts +17 -0
  191. package/dist/primitives/Link.d.ts.map +1 -0
  192. package/dist/primitives/Link.js +29 -0
  193. package/dist/primitives/List.d.ts +41 -0
  194. package/dist/primitives/List.d.ts.map +1 -0
  195. package/dist/primitives/List.js +22 -0
  196. package/dist/primitives/Masonry.d.ts +14 -0
  197. package/dist/primitives/Masonry.d.ts.map +1 -0
  198. package/dist/primitives/Masonry.js +15 -0
  199. package/dist/primitives/Menu.d.ts +28 -0
  200. package/dist/primitives/Menu.d.ts.map +1 -0
  201. package/dist/primitives/Menu.js +54 -0
  202. package/dist/primitives/MobileStepper.d.ts +15 -0
  203. package/dist/primitives/MobileStepper.d.ts.map +1 -0
  204. package/dist/primitives/MobileStepper.js +8 -0
  205. package/dist/primitives/Modal.d.ts +18 -0
  206. package/dist/primitives/Modal.d.ts.map +1 -0
  207. package/dist/primitives/Modal.js +29 -0
  208. package/dist/primitives/NoSsr.d.ts +15 -0
  209. package/dist/primitives/NoSsr.d.ts.map +1 -0
  210. package/dist/primitives/NoSsr.js +19 -0
  211. package/dist/primitives/Paper.d.ts +22 -0
  212. package/dist/primitives/Paper.d.ts.map +1 -0
  213. package/dist/primitives/Paper.js +33 -0
  214. package/dist/primitives/Progress.d.ts +6 -2
  215. package/dist/primitives/Progress.d.ts.map +1 -1
  216. package/dist/primitives/Progress.js +8 -3
  217. package/dist/primitives/Snackbar.d.ts +23 -0
  218. package/dist/primitives/Snackbar.d.ts.map +1 -0
  219. package/dist/primitives/Snackbar.js +33 -0
  220. package/dist/primitives/SpeedDial.d.ts +26 -0
  221. package/dist/primitives/SpeedDial.d.ts.map +1 -0
  222. package/dist/primitives/SpeedDial.js +26 -0
  223. package/dist/primitives/Stack.d.ts +15 -0
  224. package/dist/primitives/Stack.d.ts.map +1 -0
  225. package/dist/primitives/Stack.js +45 -0
  226. package/dist/primitives/StarRating.d.ts +19 -1
  227. package/dist/primitives/StarRating.d.ts.map +1 -1
  228. package/dist/primitives/StarRating.js +36 -6
  229. package/dist/primitives/Stepper.d.ts +36 -0
  230. package/dist/primitives/Stepper.d.ts.map +1 -0
  231. package/dist/primitives/Stepper.js +53 -0
  232. package/dist/primitives/TablePagination.d.ts +16 -0
  233. package/dist/primitives/TablePagination.d.ts.map +1 -0
  234. package/dist/primitives/TablePagination.js +12 -0
  235. package/dist/primitives/TextField.d.ts +24 -0
  236. package/dist/primitives/TextField.d.ts.map +1 -0
  237. package/dist/primitives/TextField.js +19 -0
  238. package/dist/primitives/TextareaAutosize.d.ts +11 -0
  239. package/dist/primitives/TextareaAutosize.d.ts.map +1 -0
  240. package/dist/primitives/TextareaAutosize.js +26 -0
  241. package/dist/primitives/ToggleButton.d.ts +28 -0
  242. package/dist/primitives/ToggleButton.d.ts.map +1 -0
  243. package/dist/primitives/ToggleButton.js +56 -0
  244. package/dist/primitives/TransferList.d.ts +21 -0
  245. package/dist/primitives/TransferList.d.ts.map +1 -0
  246. package/dist/primitives/TransferList.js +40 -0
  247. package/dist/primitives/Typography.d.ts +23 -0
  248. package/dist/primitives/Typography.d.ts.map +1 -0
  249. package/dist/primitives/Typography.js +69 -0
  250. package/dist/primitives/transitions.d.ts +29 -0
  251. package/dist/primitives/transitions.d.ts.map +1 -0
  252. package/dist/primitives/transitions.js +52 -0
  253. package/package.json +30 -5
  254. package/skills/spatika-ui/SKILL.md +96 -0
  255. package/src/composites/AccentRule.tsx +12 -0
  256. package/src/composites/AvailabilityBadge.tsx +32 -0
  257. package/src/composites/Card.tsx +51 -1
  258. package/src/composites/CareerCard.tsx +111 -0
  259. package/src/composites/ChartDataGrid.test.tsx +57 -0
  260. package/src/composites/ChartDataGrid.tsx +196 -0
  261. package/src/composites/ContactLink.tsx +66 -0
  262. package/src/composites/CoverHero.test.tsx +38 -0
  263. package/src/composites/CoverHero.tsx +29 -22
  264. package/src/composites/DisplayHeading.tsx +27 -0
  265. package/src/composites/EntityMediaCard.test.tsx +58 -0
  266. package/src/composites/EntityMediaCard.tsx +31 -13
  267. package/src/composites/EventCalendar.tsx +928 -0
  268. package/src/composites/EventEditor.tsx +268 -0
  269. package/src/composites/EventTimeline.tsx +357 -0
  270. package/src/composites/FloatChip.tsx +63 -0
  271. package/src/composites/GradientText.tsx +31 -0
  272. package/src/composites/IconTile.tsx +33 -0
  273. package/src/composites/MapChart.test.tsx +39 -0
  274. package/src/composites/MapChart.tsx +130 -0
  275. package/src/composites/MetaChip.tsx +24 -0
  276. package/src/composites/PresenceDot.tsx +42 -0
  277. package/src/composites/ProfileHero.test.tsx +33 -0
  278. package/src/composites/ProfileHero.tsx +96 -79
  279. package/src/composites/ProjectCard.tsx +65 -0
  280. package/src/composites/ScatterWebGL.test.tsx +25 -0
  281. package/src/composites/ScatterWebGL.tsx +79 -0
  282. package/src/composites/SectionHeading.tsx +61 -0
  283. package/src/composites/SiteFooter.test.tsx +37 -0
  284. package/src/composites/SiteFooter.tsx +79 -0
  285. package/src/composites/SiteNav.test.tsx +46 -0
  286. package/src/composites/SiteNav.tsx +150 -0
  287. package/src/composites/StatCard.test.tsx +30 -0
  288. package/src/composites/StatCard.tsx +36 -1
  289. package/src/composites/Tag.tsx +50 -0
  290. package/src/composites/Wordmark.test.tsx +23 -0
  291. package/src/composites/Wordmark.tsx +46 -0
  292. package/src/composites/cartesian-charts.test.tsx +52 -0
  293. package/src/composites/cartesian-charts.tsx +1287 -0
  294. package/src/composites/chart-composition.test.tsx +53 -0
  295. package/src/composites/chart-composition.tsx +898 -0
  296. package/src/composites/chart-ui.tsx +328 -0
  297. package/src/composites/charts-3d.test.tsx +25 -0
  298. package/src/composites/charts-3d.tsx +318 -0
  299. package/src/composites/charts.test.tsx +254 -0
  300. package/src/composites/chrome.test.tsx +29 -0
  301. package/src/composites/flow-charts.test.tsx +79 -0
  302. package/src/composites/flow-charts.tsx +607 -0
  303. package/src/composites/marketing.test.tsx +192 -0
  304. package/src/composites/radial-charts.test.tsx +74 -0
  305. package/src/composites/radial-charts.tsx +617 -0
  306. package/src/composites/scheduler-ui.tsx +304 -0
  307. package/src/composites/scheduler.test.tsx +194 -0
  308. package/src/index.test.ts +22 -0
  309. package/src/index.ts +300 -5
  310. package/src/lib/chart-export.test.ts +20 -0
  311. package/src/lib/chart-export.ts +99 -0
  312. package/src/lib/charts-platform.test.ts +93 -0
  313. package/src/lib/charts.test.ts +132 -0
  314. package/src/lib/charts.ts +1021 -0
  315. package/src/lib/geo.test.ts +42 -0
  316. package/src/lib/geo.ts +138 -0
  317. package/src/lib/iso.test.ts +22 -0
  318. package/src/lib/iso.ts +102 -0
  319. package/src/lib/rrule.test.ts +53 -0
  320. package/src/lib/rrule.ts +281 -0
  321. package/src/lib/scheduler.test.ts +186 -0
  322. package/src/lib/scheduler.ts +688 -0
  323. package/src/lib/tokens.test.ts +2 -2
  324. package/src/lib/webgl-scatter.test.ts +36 -0
  325. package/src/lib/webgl-scatter.ts +230 -0
  326. package/src/primitives/Accordion.tsx +218 -63
  327. package/src/primitives/AppBar.tsx +39 -0
  328. package/src/primitives/Autocomplete.tsx +236 -0
  329. package/src/primitives/Avatar.tsx +52 -1
  330. package/src/primitives/Backdrop.tsx +48 -0
  331. package/src/primitives/BottomNavigation.tsx +102 -0
  332. package/src/primitives/Button.tsx +3 -0
  333. package/src/primitives/ButtonBase.tsx +34 -0
  334. package/src/primitives/ButtonGroup.tsx +90 -0
  335. package/src/primitives/CircularProgress.tsx +104 -0
  336. package/src/primitives/ClickAwayListener.tsx +46 -0
  337. package/src/primitives/Collapse.tsx +39 -0
  338. package/src/primitives/Container.tsx +40 -0
  339. package/src/primitives/Fab.tsx +62 -0
  340. package/src/primitives/FormControl.tsx +90 -0
  341. package/src/primitives/FormControlLabel.tsx +51 -0
  342. package/src/primitives/Grid.tsx +101 -0
  343. package/src/primitives/IconButton.tsx +59 -0
  344. package/src/primitives/ImageList.test.tsx +48 -0
  345. package/src/primitives/ImageList.tsx +112 -0
  346. package/src/primitives/InputAdornment.tsx +86 -0
  347. package/src/primitives/Link.tsx +48 -0
  348. package/src/primitives/List.tsx +121 -0
  349. package/src/primitives/Masonry.tsx +32 -0
  350. package/src/primitives/Menu.tsx +112 -0
  351. package/src/primitives/MobileStepper.tsx +64 -0
  352. package/src/primitives/Modal.tsx +70 -0
  353. package/src/primitives/NoSsr.tsx +28 -0
  354. package/src/primitives/Paper.tsx +55 -0
  355. package/src/primitives/Progress.tsx +25 -7
  356. package/src/primitives/Snackbar.tsx +83 -0
  357. package/src/primitives/SpeedDial.tsx +110 -0
  358. package/src/primitives/Stack.tsx +88 -0
  359. package/src/primitives/StarRating.tsx +87 -28
  360. package/src/primitives/Stepper.tsx +184 -0
  361. package/src/primitives/TablePagination.tsx +58 -0
  362. package/src/primitives/TextField.tsx +105 -0
  363. package/src/primitives/TextareaAutosize.tsx +49 -0
  364. package/src/primitives/ToggleButton.tsx +148 -0
  365. package/src/primitives/TransferList.tsx +110 -0
  366. package/src/primitives/Typography.tsx +90 -0
  367. package/src/primitives/mui-more.test.tsx +76 -0
  368. package/src/primitives/mui.test.tsx +134 -0
  369. package/src/primitives/transitions.tsx +120 -0
@@ -0,0 +1,31 @@
1
+ import type { ReactNode } from "react";
2
+ import { cn } from "../lib/cn";
3
+
4
+ export type GradientTextProps = {
5
+ children: ReactNode;
6
+ /** `static` clips a 3-stop theme gradient; `flow` animates it. */
7
+ variant?: "static" | "flow";
8
+ className?: string;
9
+ as?: "span" | "em" | "strong";
10
+ };
11
+
12
+ /** Theme-colored gradient type for wordmarks and display names. */
13
+ export function GradientText({
14
+ children,
15
+ variant = "static",
16
+ className,
17
+ as: Tag = "span",
18
+ }: GradientTextProps) {
19
+ return (
20
+ <Tag
21
+ data-slot="gradient-text"
22
+ data-variant={variant}
23
+ className={cn(
24
+ variant === "flow" ? "spk-gradient-text" : "spk-gradient-text-static",
25
+ className,
26
+ )}
27
+ >
28
+ {children}
29
+ </Tag>
30
+ );
31
+ }
@@ -0,0 +1,33 @@
1
+ import type { ReactNode } from "react";
2
+ import { cn } from "../lib/cn";
3
+ import { Card } from "./Card";
4
+
5
+ export type IconTileProps = {
6
+ icon: ReactNode;
7
+ title: ReactNode;
8
+ description?: ReactNode;
9
+ className?: string;
10
+ };
11
+
12
+ /** Horizontal domain / feature tile: icon well + italic title + caption. */
13
+ export function IconTile({ icon, title, description, className }: IconTileProps) {
14
+ return (
15
+ <div data-slot="icon-tile">
16
+ <Card
17
+ variant="panel"
18
+ padding="md"
19
+ className={cn("group flex items-center gap-5 p-5", className)}
20
+ >
21
+ <div className="flex h-14 w-14 shrink-0 items-center justify-center rounded-2xl border border-border bg-secondary text-3xl">
22
+ {icon}
23
+ </div>
24
+ <div className="min-w-0">
25
+ <p className="text-[16px] font-[900] italic text-foreground">{title}</p>
26
+ {description ? (
27
+ <p className="mt-0.5 text-[12px] font-bold text-muted-foreground">{description}</p>
28
+ ) : null}
29
+ </div>
30
+ </Card>
31
+ </div>
32
+ );
33
+ }
@@ -0,0 +1,39 @@
1
+ import { render } from "@testing-library/react";
2
+ import { describe, expect, it } from "vitest";
3
+ import { MapChart } from "./MapChart";
4
+
5
+ describe("MapChart", () => {
6
+ it("draws choropleth paths from GeoJSON", () => {
7
+ const { container } = render(
8
+ <MapChart
9
+ width={240}
10
+ height={160}
11
+ geoData={{
12
+ type: "FeatureCollection",
13
+ features: [
14
+ {
15
+ type: "Feature",
16
+ id: "a",
17
+ properties: { name: "A" },
18
+ geometry: {
19
+ type: "Polygon",
20
+ coordinates: [
21
+ [
22
+ [0, 0],
23
+ [4, 0],
24
+ [4, 3],
25
+ [0, 3],
26
+ [0, 0],
27
+ ],
28
+ ],
29
+ },
30
+ },
31
+ ],
32
+ }}
33
+ series={[{ data: [{ id: "a", value: 12, label: "A" }] }]}
34
+ />,
35
+ );
36
+ expect(container.querySelector('[data-slot="map-chart"]')).toBeTruthy();
37
+ expect(container.querySelector('[data-slot="map-chart"] path')).toBeTruthy();
38
+ });
39
+ });
@@ -0,0 +1,130 @@
1
+ import { chartColor, formatChartNumber, type ChartMargin } from "../lib/charts";
2
+ import {
3
+ featureId,
4
+ fitFeatures,
5
+ geoPath,
6
+ type GeoJsonFeatureCollection,
7
+ type GeoProjectionKind,
8
+ } from "../lib/geo";
9
+ import { ChartFrame, useChartHover } from "./chart-ui";
10
+
11
+ export type MapShapeDatum = {
12
+ id: string | number;
13
+ value: number;
14
+ label?: string;
15
+ color?: string;
16
+ };
17
+
18
+ export type MapChartProps = {
19
+ geoData: GeoJsonFeatureCollection;
20
+ series?: Array<{ data: MapShapeDatum[] }>;
21
+ idProperty?: string;
22
+ projection?: GeoProjectionKind;
23
+ height?: number;
24
+ width?: number;
25
+ colors?: string[];
26
+ hideLegend?: boolean;
27
+ margin?: ChartMargin;
28
+ className?: string;
29
+ };
30
+
31
+ export function MapChart({
32
+ geoData,
33
+ series,
34
+ idProperty = "id",
35
+ projection = "mercator",
36
+ height = 280,
37
+ width,
38
+ colors,
39
+ hideLegend,
40
+ margin,
41
+ className,
42
+ }: MapChartProps) {
43
+ const features = geoData.features ?? [];
44
+ const data = series?.[0]?.data ?? [];
45
+ const byId = new Map(data.map((item) => [String(item.id), item]));
46
+ const values = data.map((item) => item.value);
47
+ const min = values.length ? Math.min(...values) : 0;
48
+ const max = values.length ? Math.max(...values) : 1;
49
+ const { hover, setHover, clear } = useChartHover();
50
+ const legend = hideLegend
51
+ ? undefined
52
+ : data.slice(0, 8).map((item, i) => ({
53
+ id: String(item.id),
54
+ label: item.label ?? String(item.id),
55
+ color: item.color ?? choroplethFill(item.value, min, max, i, colors),
56
+ }));
57
+
58
+ return (
59
+ <ChartFrame
60
+ slot="map-chart"
61
+ className={className}
62
+ width={width}
63
+ height={height}
64
+ margin={margin ?? { top: 8, right: 8, bottom: 8, left: 8 }}
65
+ legend={legend}
66
+ hover={hover}
67
+ aria-label="Map chart"
68
+ >
69
+ {({ width: w, height: h, m }) => {
70
+ const pad = Math.min(m.left, m.right, m.top, m.bottom);
71
+ const project = fitFeatures(features, w, h, pad, projection);
72
+ return (
73
+ <svg width={w} height={h} viewBox={`0 0 ${w} ${h}`}>
74
+ {features.map((feature, i) => {
75
+ if (!feature.geometry) return null;
76
+ const id = featureId(feature, idProperty) || String(i);
77
+ const datum = byId.get(id);
78
+ const label =
79
+ datum?.label ??
80
+ (feature.properties?.name != null ? String(feature.properties.name) : id);
81
+ const fill = datum
82
+ ? (datum.color ?? choroplethFill(datum.value, min, max, i, colors))
83
+ : "color-mix(in srgb, var(--muted) 55%, transparent)";
84
+ const d = geoPath(feature.geometry, project);
85
+ if (!d) return null;
86
+ return (
87
+ <path
88
+ key={id}
89
+ d={d}
90
+ fill={fill}
91
+ className="spk-chart-map-region spk-chart-mark"
92
+ onMouseEnter={(event) =>
93
+ setHover({
94
+ x: event.nativeEvent.offsetX,
95
+ y: event.nativeEvent.offsetY,
96
+ title: label,
97
+ items: datum
98
+ ? [
99
+ {
100
+ color: fill,
101
+ label: label,
102
+ value: formatChartNumber(datum.value),
103
+ },
104
+ ]
105
+ : [{ color: fill, label: label, value: "—" }],
106
+ })
107
+ }
108
+ onMouseLeave={clear}
109
+ />
110
+ );
111
+ })}
112
+ </svg>
113
+ );
114
+ }}
115
+ </ChartFrame>
116
+ );
117
+ }
118
+
119
+ function choroplethFill(
120
+ value: number,
121
+ min: number,
122
+ max: number,
123
+ index: number,
124
+ colors?: string[],
125
+ ): string {
126
+ const t = max === min ? 0.6 : (value - min) / (max - min);
127
+ const base = chartColor(index, colors);
128
+ const mix = Math.round(28 + t * 72);
129
+ return `color-mix(in srgb, ${base} ${mix}%, var(--muted))`;
130
+ }
@@ -0,0 +1,24 @@
1
+ import type { ReactNode } from "react";
2
+ import { cn } from "../lib/cn";
3
+
4
+ export type MetaChipProps = {
5
+ icon?: ReactNode;
6
+ children: ReactNode;
7
+ className?: string;
8
+ };
9
+
10
+ /** Compact “powered by” chip: icon + label with a quiet ring. */
11
+ export function MetaChip({ icon, children, className }: MetaChipProps) {
12
+ return (
13
+ <span
14
+ data-slot="meta-chip"
15
+ className={cn(
16
+ "inline-flex items-center gap-1.5 rounded-lg bg-secondary px-2.5 py-1.5 text-[11px] font-medium text-muted-foreground ring-1 ring-border/80",
17
+ className,
18
+ )}
19
+ >
20
+ {icon ? <span className="inline-flex h-4 w-4 shrink-0 items-center justify-center opacity-90">{icon}</span> : null}
21
+ {children}
22
+ </span>
23
+ );
24
+ }
@@ -0,0 +1,42 @@
1
+ import { cn } from "../lib/cn";
2
+
3
+ export type PresenceDotProps = {
4
+ tone?: "primary" | "success";
5
+ /** Dual-span scale ping. Defaults to true. */
6
+ ping?: boolean;
7
+ className?: string;
8
+ label?: string;
9
+ };
10
+
11
+ const toneClass = {
12
+ primary: "bg-primary",
13
+ success: "bg-[var(--accent-info)]",
14
+ } as const;
15
+
16
+ /** Live status pip with optional ping halo (availability badges, hero chips). */
17
+ export function PresenceDot({
18
+ tone = "primary",
19
+ ping = true,
20
+ className,
21
+ label,
22
+ }: PresenceDotProps) {
23
+ return (
24
+ <span
25
+ data-slot="presence-dot"
26
+ role={label ? "img" : undefined}
27
+ aria-label={label}
28
+ className={cn("relative flex h-2 w-2", className)}
29
+ >
30
+ {ping ? (
31
+ <span
32
+ className={cn(
33
+ "absolute inline-flex h-full w-full animate-ping rounded-full opacity-75",
34
+ toneClass[tone],
35
+ )}
36
+ aria-hidden
37
+ />
38
+ ) : null}
39
+ <span className={cn("relative inline-flex h-2 w-2 rounded-full", toneClass[tone])} />
40
+ </span>
41
+ );
42
+ }
@@ -0,0 +1,33 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+ import { describe, expect, it, vi } from "vitest";
4
+ import { ProfileHero } from "./ProfileHero";
5
+
6
+ describe("ProfileHero", () => {
7
+ it("renders name, alias, and a notched identity plate", () => {
8
+ const { container } = render(
9
+ <ProfileHero name="Alex Chen" alias="Product designer" coverSeed="alex" bleed={false} />,
10
+ );
11
+ expect(screen.getByRole("heading", { name: "Alex Chen" })).toBeInTheDocument();
12
+ expect(screen.getByText("Product designer")).toBeInTheDocument();
13
+ expect(container.querySelector(".profile-identity")).toBeTruthy();
14
+ expect(container.querySelector(".profile-identity__plate")).toBeTruthy();
15
+ expect(container.querySelector("[data-slot='profile-hero']")).not.toHaveAttribute(
16
+ "data-cover-bleed",
17
+ );
18
+ expect(container.querySelector("[data-slot='profile-hero']")).toHaveClass("w-full");
19
+ });
20
+
21
+ it("keeps the avatar inert until onAvatarClick is provided", async () => {
22
+ const user = userEvent.setup();
23
+ const onAvatarClick = vi.fn();
24
+ const { rerender } = render(<ProfileHero name="Alex Chen" bleed={false} />);
25
+ expect(screen.getByRole("button", { name: "Alex Chen" })).toBeDisabled();
26
+
27
+ rerender(<ProfileHero name="Alex Chen" onAvatarClick={onAvatarClick} bleed={false} />);
28
+ const avatar = screen.getByRole("button", { name: "Alex Chen" });
29
+ expect(avatar).toBeEnabled();
30
+ await user.click(avatar);
31
+ expect(onAvatarClick).toHaveBeenCalledTimes(1);
32
+ });
33
+ });
@@ -33,12 +33,14 @@ export type ProfileHeroProps = {
33
33
  coverKind?: CoverPatternKind | null;
34
34
  dark?: boolean;
35
35
  warm?: boolean;
36
+ /** When false, keep the cover inside this frame (catalog previews). */
37
+ bleed?: boolean;
36
38
  };
37
39
 
38
40
  /**
39
- * Profile / entity hero: cover bleeds into app chrome, overlapping avatar
40
- * notches the frosted identity plate. Stacked in a narrow column; left-notched
41
- * once the plate is wide enough (container query, not viewport).
41
+ * Profile / entity hero: cover fills the frame, overlapping avatar notches
42
+ * the frosted identity plate. Stacked in a narrow column; left-notched once
43
+ * the plate is wide enough (container query, not viewport).
42
44
  */
43
45
  export function ProfileHero({
44
46
  name,
@@ -63,6 +65,7 @@ export function ProfileHero({
63
65
  coverKind,
64
66
  dark,
65
67
  warm,
68
+ bleed = true,
66
69
  }: ProfileHeroProps) {
67
70
  const initials = getInitials(name);
68
71
  const seed = coverSeed ?? name;
@@ -70,92 +73,106 @@ export function ProfileHero({
70
73
  const ink = scene.headerInk;
71
74
 
72
75
  return (
73
- <div data-slot="profile-hero" className={cn("relative overflow-visible", className)}>
76
+ <div
77
+ data-slot="profile-hero"
78
+ data-cover-bleed={bleed ? "true" : undefined}
79
+ className={cn(
80
+ "relative w-full",
81
+ bleed ? "overflow-visible" : "overflow-hidden rounded-[1.35rem] sm:rounded-[1.75rem]",
82
+ className,
83
+ )}
84
+ >
74
85
  <div
75
- className={cn(
76
- "relative min-h-[9rem] overflow-hidden sm:min-h-[12rem] lg:min-h-[13rem]",
77
- coverClassName,
78
- )}
86
+ className={cn("cover-media cover-media--profile", bleed && "sm:rounded-b-[2rem]")}
87
+ aria-hidden
79
88
  >
80
- <div className="cover-media cover-media--profile sm:rounded-b-[2rem]" aria-hidden>
81
- {!hideImages && coverUrl ? (
82
- <img src={coverUrl} alt="" className="absolute inset-0 size-full object-cover" />
83
- ) : (
84
- <CoverPattern seed={seed} hue={coverHue} dark={dark} warm={warm} kind={coverKind} />
89
+ {!hideImages && coverUrl ? (
90
+ <img src={coverUrl} alt="" className="absolute inset-0 size-full object-cover" />
91
+ ) : (
92
+ <CoverPattern seed={seed} hue={coverHue} dark={dark} warm={warm} kind={coverKind} />
93
+ )}
94
+ <div className="absolute inset-x-0 top-0 h-8 bg-gradient-to-b from-black/[0.04] to-transparent dark:from-black/12" />
95
+ <div className="absolute inset-x-0 bottom-0 h-[22%] bg-gradient-to-t from-black/[0.03] via-transparent to-transparent dark:from-black/10" />
96
+ </div>
97
+
98
+ <div className="relative z-10 flex flex-col">
99
+ <div
100
+ className={cn(
101
+ "relative min-h-[8.5rem] sm:min-h-[11rem] lg:min-h-[12rem]",
102
+ coverClassName,
85
103
  )}
86
- <div className="absolute inset-x-0 top-0 h-8 bg-gradient-to-b from-black/[0.04] to-transparent dark:from-black/12" />
87
- <div className="absolute inset-x-0 bottom-0 h-[22%] bg-gradient-to-t from-black/[0.03] via-transparent to-transparent dark:from-black/10" />
88
- </div>
89
- <div className="relative z-10 flex min-h-[9rem] items-start justify-between gap-2 p-3 sm:min-h-[12rem] sm:p-4 lg:min-h-[13rem]">
90
- <div>{topLeading}</div>
91
- {topActions ? (
92
- <div
93
- data-cover-ink={ink}
94
- className="cover-floating-shell flex items-center gap-1 p-1"
95
- >
96
- {topActions}
97
- </div>
98
- ) : null}
104
+ >
105
+ <div className="absolute inset-x-0 top-0 z-30 flex items-start justify-between gap-2 p-3 sm:p-4">
106
+ <div>{topLeading}</div>
107
+ {topActions ? (
108
+ <div
109
+ data-cover-ink={ink}
110
+ className="cover-floating-shell flex items-center gap-1 p-1"
111
+ >
112
+ {topActions}
113
+ </div>
114
+ ) : null}
115
+ </div>
99
116
  </div>
100
- </div>
101
117
 
102
- <div className="relative z-10 px-3 pb-4 sm:px-4 sm:pb-5 lg:px-8">
103
- <div className={cn("profile-identity", identityClassName)}>
104
- <div
105
- aria-hidden
106
- className={cn(
107
- "pointer-events-none absolute inset-0 rounded-[1.35rem] profile-identity__plate",
108
- coverPlateClass(ink),
109
- )}
110
- />
111
- <div className="profile-identity__body">
112
- <button
113
- type="button"
114
- disabled={!onAvatarClick}
115
- onClick={onAvatarClick}
116
- aria-label={name}
117
- className="profile-identity__avatar"
118
- >
119
- <span
120
- className="flex items-center justify-center overflow-hidden rounded-full border-[3px] border-white bg-muted text-lg font-black leading-none text-primary shadow-[0_16px_40px_rgba(0,0,0,0.38)] dark:border-white/30"
121
- style={{
122
- width: "var(--profile-avatar-size)",
123
- height: "var(--profile-avatar-size)",
124
- }}
118
+ <div className="relative z-20 px-3 pb-4 sm:px-4 sm:pb-5 lg:px-6">
119
+ <div className={cn("profile-identity", identityClassName)}>
120
+ <div
121
+ aria-hidden
122
+ className={cn(
123
+ "pointer-events-none absolute inset-0 rounded-[1.35rem] profile-identity__plate",
124
+ coverPlateClass(ink),
125
+ )}
126
+ />
127
+ <div className="profile-identity__body">
128
+ <button
129
+ type="button"
130
+ disabled={!onAvatarClick}
131
+ onClick={onAvatarClick}
132
+ aria-label={name}
133
+ className="profile-identity__avatar"
125
134
  >
126
- {!hideImages && avatarUrl ? (
127
- <img src={avatarUrl} alt="" className="size-full object-cover" />
128
- ) : (
129
- initials
130
- )}
131
- </span>
132
- {avatarAccessory}
133
- </button>
134
- <div className="profile-identity__copy">
135
- <div className="profile-identity__heading">
136
- <h1
137
- className={cn(
138
- "truncate text-[1.15rem] font-black leading-tight tracking-tight sm:text-2xl",
139
- coverTitleClass(ink),
140
- )}
135
+ <span
136
+ className="flex items-center justify-center overflow-hidden rounded-full border-[3px] border-white bg-muted text-lg font-black leading-none text-primary shadow-[0_16px_40px_rgba(0,0,0,0.38)] dark:border-white/30"
137
+ style={{
138
+ width: "var(--profile-avatar-size)",
139
+ height: "var(--profile-avatar-size)",
140
+ }}
141
141
  >
142
- {name}
143
- </h1>
144
- {statusBadges}
142
+ {!hideImages && avatarUrl ? (
143
+ <img src={avatarUrl} alt="" className="size-full object-cover" />
144
+ ) : (
145
+ initials
146
+ )}
147
+ </span>
148
+ {avatarAccessory}
149
+ </button>
150
+ <div className="profile-identity__copy">
151
+ <div className="profile-identity__heading">
152
+ <h1
153
+ className={cn(
154
+ "truncate text-[1.15rem] font-black leading-tight tracking-tight sm:text-2xl",
155
+ coverTitleClass(ink),
156
+ )}
157
+ >
158
+ {name}
159
+ </h1>
160
+ {statusBadges}
161
+ </div>
162
+ {alias ? (
163
+ <p className={cn("mt-0.5 truncate text-sm font-medium", coverMutedClass(ink))}>
164
+ {alias}
165
+ </p>
166
+ ) : null}
167
+ {metaLines ? (
168
+ <div className={cn("mt-2 space-y-1 text-sm", coverMutedClass(ink))}>{metaLines}</div>
169
+ ) : null}
170
+ {stats ? <div className="mt-3">{stats}</div> : null}
171
+ {tags ? <div className="profile-identity__tags">{tags}</div> : null}
145
172
  </div>
146
- {alias ? (
147
- <p className={cn("mt-0.5 truncate text-sm font-medium", coverMutedClass(ink))}>
148
- {alias}
149
- </p>
150
- ) : null}
151
- {metaLines ? (
152
- <div className={cn("mt-2 space-y-1 text-sm", coverMutedClass(ink))}>{metaLines}</div>
153
- ) : null}
154
- {stats ? <div className="mt-3">{stats}</div> : null}
155
- {tags ? <div className="profile-identity__tags">{tags}</div> : null}
156
173
  </div>
174
+ {children ? <div className="relative z-10 px-3 pb-3 sm:px-6">{children}</div> : null}
157
175
  </div>
158
- {children ? <div className="relative z-10 px-3 pb-3 sm:px-6">{children}</div> : null}
159
176
  </div>
160
177
  </div>
161
178
  </div>
@@ -0,0 +1,65 @@
1
+ import type { ReactNode } from "react";
2
+ import { cn } from "../lib/cn";
3
+ import { Badge } from "../primitives/Badge";
4
+ import { Card, CardContent, CardHeader, CardTitle } from "./Card";
5
+ import { Tag } from "./Tag";
6
+
7
+ export type ProjectCardProps = {
8
+ title: ReactNode;
9
+ company?: ReactNode;
10
+ outcome?: ReactNode;
11
+ description?: ReactNode;
12
+ tags?: string[];
13
+ className?: string;
14
+ };
15
+
16
+ /** Marketing project tile: title, company, outcome pill, body, tech tags. */
17
+ export function ProjectCard({
18
+ title,
19
+ company,
20
+ outcome,
21
+ description,
22
+ tags = [],
23
+ className,
24
+ }: ProjectCardProps) {
25
+ return (
26
+ <Card
27
+ variant="panel"
28
+ padding="md"
29
+ className={cn(
30
+ "group flex h-full flex-col hover:border-primary/30 hover:shadow-[0_12px_32px_color-mix(in_srgb,var(--primary)_7%,transparent)]",
31
+ className,
32
+ )}
33
+ >
34
+ <CardHeader className="mb-2">
35
+ <div className="min-w-0">
36
+ <CardTitle className="text-[15px] leading-snug font-[900] group-hover:text-primary sm:text-[16px]">
37
+ {title}
38
+ </CardTitle>
39
+ {company ? (
40
+ <p className="mt-0.5 text-[11px] font-bold tracking-wider text-muted-foreground uppercase">
41
+ {company}
42
+ </p>
43
+ ) : null}
44
+ </div>
45
+ {outcome ? (
46
+ <Badge variant="warm" className="rounded-full px-2.5 py-1 text-[10px] font-black">
47
+ {outcome}
48
+ </Badge>
49
+ ) : null}
50
+ </CardHeader>
51
+ <CardContent className="flex flex-1 flex-col">
52
+ {description ? (
53
+ <p className="mb-3 flex-1 text-[13px] leading-relaxed text-muted-foreground">{description}</p>
54
+ ) : null}
55
+ {tags.length > 0 ? (
56
+ <div className="flex flex-wrap gap-1.5">
57
+ {tags.map((tag) => (
58
+ <Tag key={tag}>{tag}</Tag>
59
+ ))}
60
+ </div>
61
+ ) : null}
62
+ </CardContent>
63
+ </Card>
64
+ );
65
+ }
@@ -0,0 +1,25 @@
1
+ import { render } from "@testing-library/react";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { ScatterWebGLLayer } from "./ScatterWebGL";
4
+
5
+ describe("ScatterWebGLLayer", () => {
6
+ it("renders a canvas layer and reports when WebGL is unavailable", () => {
7
+ const onUnavailable = vi.fn();
8
+ const { container } = render(
9
+ <ScatterWebGLLayer
10
+ width={120}
11
+ height={80}
12
+ series={[{ data: [{ x: 1, y: 2 }] }]}
13
+ meta={[{ id: "a", label: "A", color: "#2563eb" }]}
14
+ hidden={new Set()}
15
+ xAt={(value) => value}
16
+ yAt={(value) => value}
17
+ sizeAt={(z) => z}
18
+ bubble={false}
19
+ onUnavailable={onUnavailable}
20
+ />,
21
+ );
22
+ expect(container.querySelector('[data-slot="scatter-webgl"]')).toBeTruthy();
23
+ expect(onUnavailable).toHaveBeenCalled();
24
+ });
25
+ });