@heyramzi/ui 0.0.0-stage → 1.53.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 (564) hide show
  1. package/COMPONENTS.md +133 -0
  2. package/README.md +136 -2
  3. package/package.json +217 -3
  4. package/scripts/lib/sync-core.mjs +113 -0
  5. package/scripts/relative-imports.mjs +39 -0
  6. package/scripts/sf-symbol.mjs +93 -0
  7. package/scripts/sync-shadcn.mjs +354 -0
  8. package/scripts/sync-transitions.mjs +146 -0
  9. package/scripts/validate-apple-symbol-exports.mjs +45 -0
  10. package/src/agents/agent-avatar.svelte +592 -0
  11. package/src/agents/agent-cast.svelte +197 -0
  12. package/src/agents/demo.svelte +125 -0
  13. package/src/agents/helpers.ts +58 -0
  14. package/src/agents/index.ts +11 -0
  15. package/src/agents/page-state.ts +70 -0
  16. package/src/agents/types.ts +74 -0
  17. package/src/agents/vendor/LICENSE +21 -0
  18. package/src/agents/vendor/color.ts +155 -0
  19. package/src/agents/vendor/draw.ts +840 -0
  20. package/src/agents/vendor/engine.ts +845 -0
  21. package/src/agents/vendor/mesh.ts +838 -0
  22. package/src/agents/vendor/plastic.ts +4958 -0
  23. package/src/agents/vendor/presets.ts +103 -0
  24. package/src/agents/vendor/shapes.ts +41 -0
  25. package/src/agents/vendor/ticker.ts +67 -0
  26. package/src/agents/vendor/types.ts +261 -0
  27. package/src/agents/vendor/wear.ts +1699 -0
  28. package/src/ai/ai-button/AiButton.svelte +86 -0
  29. package/src/ai/ai-button/index.ts +4 -0
  30. package/src/ai/confirmation/Confirmation.svelte +54 -0
  31. package/src/ai/confirmation/ConfirmationAccepted.svelte +23 -0
  32. package/src/ai/confirmation/ConfirmationAction.svelte +53 -0
  33. package/src/ai/confirmation/ConfirmationActions.svelte +24 -0
  34. package/src/ai/confirmation/ConfirmationFailed.svelte +23 -0
  35. package/src/ai/confirmation/ConfirmationRejected.svelte +23 -0
  36. package/src/ai/confirmation/ConfirmationRequest.svelte +25 -0
  37. package/src/ai/confirmation/ConfirmationTitle.svelte +25 -0
  38. package/src/ai/confirmation/confirmation-context.svelte.ts +22 -0
  39. package/src/ai/confirmation/index.ts +50 -0
  40. package/src/ai/generation-progress/GenerationProgress.svelte +354 -0
  41. package/src/ai/generation-progress/index.ts +7 -0
  42. package/src/ai/index.ts +111 -0
  43. package/src/ai/loading-state/PixelLoader.svelte +138 -0
  44. package/src/ai/loading-state/index.ts +5 -0
  45. package/src/ai/prompt-input/PromptInput.svelte +56 -0
  46. package/src/ai/prompt-input/PromptInputBody.svelte +19 -0
  47. package/src/ai/prompt-input/PromptInputSubmit.svelte +79 -0
  48. package/src/ai/prompt-input/PromptInputTextarea.svelte +74 -0
  49. package/src/ai/prompt-input/PromptInputToolbar.svelte +19 -0
  50. package/src/ai/prompt-input/index.ts +34 -0
  51. package/src/ai/reasoning/Reasoning.svelte +50 -0
  52. package/src/ai/reasoning/ReasoningContent.svelte +27 -0
  53. package/src/ai/reasoning/ReasoningTrigger.svelte +48 -0
  54. package/src/ai/reasoning/index.ts +22 -0
  55. package/src/ai/reasoning/reasoning-context.svelte.ts +20 -0
  56. package/src/ai/response/Response.css +32 -0
  57. package/src/ai/response/Response.svelte +130 -0
  58. package/src/ai/response/index.ts +5 -0
  59. package/src/ai/suggestion/Suggestion.svelte +43 -0
  60. package/src/ai/suggestion/Suggestions.svelte +25 -0
  61. package/src/ai/suggestion/index.ts +14 -0
  62. package/src/ai/thinking-dots/ThinkingDots.svelte +42 -0
  63. package/src/ai/thinking-dots/index.ts +5 -0
  64. package/src/ai/typing-dots/TypingDots.css +39 -0
  65. package/src/ai/typing-dots/TypingDots.svelte +27 -0
  66. package/src/ai/typing-dots/index.ts +10 -0
  67. package/src/ai/voice-waveform/VoiceWaveform.css +40 -0
  68. package/src/ai/voice-waveform/VoiceWaveform.svelte +46 -0
  69. package/src/ai/voice-waveform/index.ts +5 -0
  70. package/src/ambient.d.ts +4 -0
  71. package/src/animations/README.md +84 -0
  72. package/src/animations/avatar-group-hover.css +47 -0
  73. package/src/animations/card-tilt.css +74 -0
  74. package/src/animations/digit-pop-in.css +78 -0
  75. package/src/animations/dropdown.css +121 -0
  76. package/src/animations/index.css +23 -0
  77. package/src/animations/input-clear-dissolve.css +86 -0
  78. package/src/animations/input-shake.css +114 -0
  79. package/src/animations/learn-more-hover.css +48 -0
  80. package/src/animations/like-button.css +93 -0
  81. package/src/animations/modal.css +75 -0
  82. package/src/animations/page-side-by-side.css +63 -0
  83. package/src/animations/plus-menu-morph.css +97 -0
  84. package/src/animations/resize.css +20 -0
  85. package/src/animations/shimmer-text.css +72 -0
  86. package/src/animations/spinning-counter.css +52 -0
  87. package/src/animations/success-check.css +120 -0
  88. package/src/animations/text-swap.css +40 -0
  89. package/src/animations/text-swap.ts +40 -0
  90. package/src/animations/thinking-states.css +65 -0
  91. package/src/animations/upstream-coverage.test.ts +65 -0
  92. package/src/components/copy/clipboard.test.ts +206 -0
  93. package/src/components/copy/clipboard.ts +49 -0
  94. package/src/components/copy/copied.svelte.ts +39 -0
  95. package/src/components/copy/copy-button.svelte +175 -0
  96. package/src/components/copy/copy-button.test.ts +110 -0
  97. package/src/components/copy/index.ts +3 -0
  98. package/src/components/data-table/data-table.test.ts +134 -0
  99. package/src/components/data-table/data-table.ts +77 -0
  100. package/src/components/data-table/index.ts +20 -0
  101. package/src/components/data-table/infinite-scroll.svelte +69 -0
  102. package/src/components/data-table/pagination.svelte +62 -0
  103. package/src/components/data-table/row-arrow.svelte +28 -0
  104. package/src/components/data-table/row-context-menu.svelte +106 -0
  105. package/src/components/data-table/row-context-menu.test.ts +18 -0
  106. package/src/components/data-table/sortable-head.svelte +56 -0
  107. package/src/components/data-table/table-skeleton.svelte +83 -0
  108. package/src/components/hold-to-confirm/hold-to-confirm.svelte +232 -0
  109. package/src/components/hold-to-confirm/index.ts +1 -0
  110. package/src/components/icon/icon.svelte +23 -0
  111. package/src/components/icon/index.ts +1 -0
  112. package/src/components/language-flag/index.ts +1 -0
  113. package/src/components/language-flag/language-flag.svelte +60 -0
  114. package/src/components/list-page/detail-empty.svelte +50 -0
  115. package/src/components/list-page/detail-header.svelte +107 -0
  116. package/src/components/list-page/detail-panel.svelte +90 -0
  117. package/src/components/list-page/detail-row.svelte +36 -0
  118. package/src/components/list-page/detail-stack.svelte +33 -0
  119. package/src/components/list-page/empty-state.svelte +91 -0
  120. package/src/components/list-page/filter-item.svelte +49 -0
  121. package/src/components/list-page/filter-popover.svelte +88 -0
  122. package/src/components/list-page/filter-section.ts +12 -0
  123. package/src/components/list-page/grouped-list.svelte +116 -0
  124. package/src/components/list-page/index.ts +28 -0
  125. package/src/components/list-page/list-page-shell.svelte +195 -0
  126. package/src/components/list-page/list-table.ts +19 -0
  127. package/src/components/list-page/master-detail.svelte +206 -0
  128. package/src/components/list-page/master-detail.test.ts +37 -0
  129. package/src/components/list-page/notice-bar.svelte +68 -0
  130. package/src/components/list-page/prev-next.svelte +104 -0
  131. package/src/components/list-page/queue-rail.ts +25 -0
  132. package/src/components/list-page/rail-body.svelte +29 -0
  133. package/src/components/list-page/rail-empty.svelte +51 -0
  134. package/src/components/list-page/rail-head.svelte +49 -0
  135. package/src/components/list-page/rail-row.svelte +163 -0
  136. package/src/components/list-page/section-host.ts +24 -0
  137. package/src/components/list-page/section-label.svelte +39 -0
  138. package/src/components/list-page/step.test.ts +21 -0
  139. package/src/components/list-page/step.ts +10 -0
  140. package/src/components/list-page/toolbar-search.svelte +63 -0
  141. package/src/components/list-page/toolbar-trigger.ts +23 -0
  142. package/src/components/list-page/types.ts +1 -0
  143. package/src/components/locale-switch/index.ts +1 -0
  144. package/src/components/locale-switch/locale-switch.svelte +59 -0
  145. package/src/components/mark/index.ts +1 -0
  146. package/src/components/mark/mark.svelte +70 -0
  147. package/src/components/open-in/index.ts +6 -0
  148. package/src/components/open-in/open-in.svelte +106 -0
  149. package/src/components/picker/index.ts +4 -0
  150. package/src/components/picker/mark-picker.svelte +84 -0
  151. package/src/components/picker/picker.svelte +143 -0
  152. package/src/components/picker/trigger.ts +42 -0
  153. package/src/components/picker/types.ts +26 -0
  154. package/src/components/segmented/animated-tabs.svelte +175 -0
  155. package/src/components/segmented/collapse.test.ts +49 -0
  156. package/src/components/segmented/collapse.ts +39 -0
  157. package/src/components/segmented/index.ts +41 -0
  158. package/src/components/segmented/section-runs.svelte +79 -0
  159. package/src/components/segmented/segment.svelte +106 -0
  160. package/src/components/segmented/segmented-filter.svelte +125 -0
  161. package/src/components/segmented/segmented-nav.svelte +82 -0
  162. package/src/components/segmented/segmented-overflow.svelte +85 -0
  163. package/src/components/segmented/segmented-track.svelte +181 -0
  164. package/src/components/segmented/segmented.test.ts +20 -0
  165. package/src/components/segmented/segmented.ts +80 -0
  166. package/src/components/star-rating-input/index.ts +9 -0
  167. package/src/components/star-rating-input/star-rating-input.svelte +256 -0
  168. package/src/components/svg/index.ts +1 -0
  169. package/src/components/svg/svg.svelte +9 -0
  170. package/src/design/index.css +270 -0
  171. package/src/design/polish.css +159 -0
  172. package/src/effects/animated-beam/AnimatedBeam.svelte +132 -0
  173. package/src/effects/animated-beam/diagrams/BeamSvg.svelte +91 -0
  174. package/src/effects/animated-beam/index.ts +2 -0
  175. package/src/effects/animated-beam/path-calculator.svelte.ts +70 -0
  176. package/src/effects/animated-beam/types.ts +23 -0
  177. package/src/effects/border-beam/BorderBeam.svelte +223 -0
  178. package/src/effects/border-beam/css.test.ts +46 -0
  179. package/src/effects/border-beam/css.ts +574 -0
  180. package/src/effects/border-beam/presets.ts +699 -0
  181. package/src/effects/border-beam/register-properties.test.ts +74 -0
  182. package/src/effects/border-beam/register-properties.ts +73 -0
  183. package/src/effects/border-beam/types.ts +38 -0
  184. package/src/effects/image-comparison/ImageComparison.css +100 -0
  185. package/src/effects/image-comparison/ImageComparison.svelte +209 -0
  186. package/src/effects/image-comparison/compare.test.ts +17 -0
  187. package/src/effects/image-comparison/compare.ts +5 -0
  188. package/src/effects/image-reveal/ImageReveal.css +10 -0
  189. package/src/effects/image-reveal/ImageReveal.svelte +357 -0
  190. package/src/effects/image-reveal/index.ts +2 -0
  191. package/src/effects/image-reveal/types.ts +35 -0
  192. package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +338 -0
  193. package/src/effects/magic-card/MagicCard.css +47 -0
  194. package/src/effects/magic-card/MagicCard.svelte +57 -0
  195. package/src/effects/magnetic/magnetic.ts +81 -0
  196. package/src/effects/marquee/Marquee.css +78 -0
  197. package/src/effects/marquee/Marquee.svelte +160 -0
  198. package/src/effects/progressive-blur/ProgressiveBlur.css +9 -0
  199. package/src/effects/progressive-blur/ProgressiveBlur.svelte +63 -0
  200. package/src/effects/reveal/AnimatedGroup.svelte +101 -0
  201. package/src/effects/reveal/InView.svelte +77 -0
  202. package/src/effects/reveal/TextEffect.svelte +142 -0
  203. package/src/effects/reveal/index.ts +11 -0
  204. package/src/effects/reveal/observe.test.ts +60 -0
  205. package/src/effects/reveal/observe.ts +65 -0
  206. package/src/effects/reveal/reveal.css +141 -0
  207. package/src/effects/reveal/types.ts +32 -0
  208. package/src/effects/shader-gradient/ShaderGradient.svelte +227 -0
  209. package/src/effects/shader-gradient/color.ts +44 -0
  210. package/src/effects/shader-gradient/index.ts +5 -0
  211. package/src/effects/shader-gradient/renderer.ts +456 -0
  212. package/src/effects/shader-gradient/shaders.ts +324 -0
  213. package/src/effects/shader-gradient/types.ts +68 -0
  214. package/src/effects/spotlight/Spotlight.css +14 -0
  215. package/src/effects/spotlight/Spotlight.svelte +61 -0
  216. package/src/effects/theme-toggle/ThemeToggle.css +214 -0
  217. package/src/effects/theme-toggle/ThemeToggle.svelte +126 -0
  218. package/src/gallery.test.ts +52 -0
  219. package/src/icons/TradeIcon.svelte +16 -0
  220. package/src/icons/apple-symbol-exports.ts +135 -0
  221. package/src/icons/sf-symbols.ts +527 -0
  222. package/src/icons/trades.ts +89 -0
  223. package/src/index.ts +53 -0
  224. package/src/lib/arrive.ts +26 -0
  225. package/src/lib/icon-types.ts +17 -0
  226. package/src/lib/layout.ts +8 -0
  227. package/src/lib/panel.ts +21 -0
  228. package/src/lib/sliding-pill.ts +68 -0
  229. package/src/lib/theme-transition.ts +79 -0
  230. package/src/lib/tone.ts +48 -0
  231. package/src/shadcn/accordion/accordion-content.svelte +22 -0
  232. package/src/shadcn/accordion/accordion-item.svelte +17 -0
  233. package/src/shadcn/accordion/accordion-trigger.svelte +44 -0
  234. package/src/shadcn/accordion/accordion.svelte +19 -0
  235. package/src/shadcn/accordion/index.ts +16 -0
  236. package/src/shadcn/alert/alert-description.svelte +23 -0
  237. package/src/shadcn/alert/alert-title.svelte +20 -0
  238. package/src/shadcn/alert/alert.svelte +46 -0
  239. package/src/shadcn/alert/index.ts +14 -0
  240. package/src/shadcn/alert-dialog/alert-dialog-action.svelte +18 -0
  241. package/src/shadcn/alert-dialog/alert-dialog-cancel.svelte +18 -0
  242. package/src/shadcn/alert-dialog/alert-dialog-content.svelte +29 -0
  243. package/src/shadcn/alert-dialog/alert-dialog-description.svelte +17 -0
  244. package/src/shadcn/alert-dialog/alert-dialog-footer.svelte +20 -0
  245. package/src/shadcn/alert-dialog/alert-dialog-header.svelte +20 -0
  246. package/src/shadcn/alert-dialog/alert-dialog-overlay.svelte +17 -0
  247. package/src/shadcn/alert-dialog/alert-dialog-portal.svelte +7 -0
  248. package/src/shadcn/alert-dialog/alert-dialog-title.svelte +17 -0
  249. package/src/shadcn/alert-dialog/alert-dialog-trigger.svelte +7 -0
  250. package/src/shadcn/alert-dialog/alert-dialog.svelte +7 -0
  251. package/src/shadcn/alert-dialog/index.ts +37 -0
  252. package/src/shadcn/attachment/attachment-action.svelte +23 -0
  253. package/src/shadcn/attachment/attachment-actions.svelte +23 -0
  254. package/src/shadcn/attachment/attachment-content.svelte +23 -0
  255. package/src/shadcn/attachment/attachment-description.svelte +23 -0
  256. package/src/shadcn/attachment/attachment-group.svelte +23 -0
  257. package/src/shadcn/attachment/attachment-media.svelte +44 -0
  258. package/src/shadcn/attachment/attachment-title.svelte +23 -0
  259. package/src/shadcn/attachment/attachment-trigger.svelte +29 -0
  260. package/src/shadcn/attachment/attachment.svelte +55 -0
  261. package/src/shadcn/attachment/index.ts +40 -0
  262. package/src/shadcn/avatar/avatar-badge.svelte +26 -0
  263. package/src/shadcn/avatar/avatar-fallback.svelte +20 -0
  264. package/src/shadcn/avatar/avatar-group-count.svelte +23 -0
  265. package/src/shadcn/avatar/avatar-group.svelte +23 -0
  266. package/src/shadcn/avatar/avatar-image.svelte +17 -0
  267. package/src/shadcn/avatar/avatar.svelte +26 -0
  268. package/src/shadcn/avatar/index.ts +22 -0
  269. package/src/shadcn/badge/badge.svelte +54 -0
  270. package/src/shadcn/badge/index.ts +2 -0
  271. package/src/shadcn/breadcrumb/breadcrumb-ellipsis.svelte +23 -0
  272. package/src/shadcn/breadcrumb/breadcrumb-item.svelte +20 -0
  273. package/src/shadcn/breadcrumb/breadcrumb-link.svelte +31 -0
  274. package/src/shadcn/breadcrumb/breadcrumb-list.svelte +23 -0
  275. package/src/shadcn/breadcrumb/breadcrumb-page.svelte +23 -0
  276. package/src/shadcn/breadcrumb/breadcrumb-separator.svelte +27 -0
  277. package/src/shadcn/breadcrumb/breadcrumb.svelte +21 -0
  278. package/src/shadcn/breadcrumb/index.ts +25 -0
  279. package/src/shadcn/bubble/bubble-content.svelte +31 -0
  280. package/src/shadcn/bubble/bubble-group.svelte +20 -0
  281. package/src/shadcn/bubble/bubble-reactions.svelte +52 -0
  282. package/src/shadcn/bubble/bubble.svelte +58 -0
  283. package/src/shadcn/bubble/index.ts +25 -0
  284. package/src/shadcn/button/button.svelte +121 -0
  285. package/src/shadcn/button/index.ts +17 -0
  286. package/src/shadcn/calendar/calendar-caption.svelte +76 -0
  287. package/src/shadcn/calendar/calendar-cell.svelte +19 -0
  288. package/src/shadcn/calendar/calendar-day.svelte +35 -0
  289. package/src/shadcn/calendar/calendar-grid-body.svelte +12 -0
  290. package/src/shadcn/calendar/calendar-grid-head.svelte +12 -0
  291. package/src/shadcn/calendar/calendar-grid-row.svelte +12 -0
  292. package/src/shadcn/calendar/calendar-grid.svelte +16 -0
  293. package/src/shadcn/calendar/calendar-head-cell.svelte +19 -0
  294. package/src/shadcn/calendar/calendar-header.svelte +19 -0
  295. package/src/shadcn/calendar/calendar-heading.svelte +16 -0
  296. package/src/shadcn/calendar/calendar-month-select.svelte +44 -0
  297. package/src/shadcn/calendar/calendar-month.svelte +15 -0
  298. package/src/shadcn/calendar/calendar-months.svelte +19 -0
  299. package/src/shadcn/calendar/calendar-nav.svelte +19 -0
  300. package/src/shadcn/calendar/calendar-next-button.svelte +31 -0
  301. package/src/shadcn/calendar/calendar-prev-button.svelte +31 -0
  302. package/src/shadcn/calendar/calendar-year-select.svelte +43 -0
  303. package/src/shadcn/calendar/calendar.svelte +124 -0
  304. package/src/shadcn/calendar/index.ts +40 -0
  305. package/src/shadcn/card/card-action.svelte +23 -0
  306. package/src/shadcn/card/card-content.svelte +20 -0
  307. package/src/shadcn/card/card-description.svelte +20 -0
  308. package/src/shadcn/card/card-footer.svelte +23 -0
  309. package/src/shadcn/card/card-header.svelte +23 -0
  310. package/src/shadcn/card/card-title.svelte +29 -0
  311. package/src/shadcn/card/card.svelte +25 -0
  312. package/src/shadcn/card/index.ts +25 -0
  313. package/src/shadcn/checkbox/checkbox.svelte +38 -0
  314. package/src/shadcn/checkbox/index.ts +6 -0
  315. package/src/shadcn/code-block/code-block-actions.svelte +23 -0
  316. package/src/shadcn/code-block/code-block-content.svelte +55 -0
  317. package/src/shadcn/code-block/code-block-copy-button.svelte +10 -0
  318. package/src/shadcn/code-block/code-block-footer.svelte +20 -0
  319. package/src/shadcn/code-block/code-block-header.svelte +23 -0
  320. package/src/shadcn/code-block/code-block-line.svelte +71 -0
  321. package/src/shadcn/code-block/code-block-title.svelte +23 -0
  322. package/src/shadcn/code-block/code-block.svelte +47 -0
  323. package/src/shadcn/code-block/index.ts +42 -0
  324. package/src/shadcn/collapsible/collapsible-content.svelte +7 -0
  325. package/src/shadcn/collapsible/collapsible-trigger.svelte +7 -0
  326. package/src/shadcn/collapsible/collapsible.svelte +11 -0
  327. package/src/shadcn/collapsible/index.ts +13 -0
  328. package/src/shadcn/command/command-dialog.svelte +66 -0
  329. package/src/shadcn/command/command-empty.svelte +17 -0
  330. package/src/shadcn/command/command-group.svelte +40 -0
  331. package/src/shadcn/command/command-input.svelte +64 -0
  332. package/src/shadcn/command/command-item.svelte +37 -0
  333. package/src/shadcn/command/command-link-item.svelte +20 -0
  334. package/src/shadcn/command/command-list.svelte +20 -0
  335. package/src/shadcn/command/command-loading.svelte +7 -0
  336. package/src/shadcn/command/command-separator.svelte +17 -0
  337. package/src/shadcn/command/command-shortcut.svelte +23 -0
  338. package/src/shadcn/command/command.svelte +28 -0
  339. package/src/shadcn/command/index.ts +40 -0
  340. package/src/shadcn/context-menu/context-menu-checkbox-item.svelte +41 -0
  341. package/src/shadcn/context-menu/context-menu-content.svelte +28 -0
  342. package/src/shadcn/context-menu/context-menu-group-heading.svelte +21 -0
  343. package/src/shadcn/context-menu/context-menu-group.svelte +7 -0
  344. package/src/shadcn/context-menu/context-menu-item.svelte +27 -0
  345. package/src/shadcn/context-menu/context-menu-label.svelte +27 -0
  346. package/src/shadcn/context-menu/context-menu-portal.svelte +7 -0
  347. package/src/shadcn/context-menu/context-menu-radio-group.svelte +16 -0
  348. package/src/shadcn/context-menu/context-menu-radio-item.svelte +35 -0
  349. package/src/shadcn/context-menu/context-menu-separator.svelte +17 -0
  350. package/src/shadcn/context-menu/context-menu-shortcut.svelte +23 -0
  351. package/src/shadcn/context-menu/context-menu-sub-content.svelte +25 -0
  352. package/src/shadcn/context-menu/context-menu-sub-trigger.svelte +29 -0
  353. package/src/shadcn/context-menu/context-menu-sub.svelte +7 -0
  354. package/src/shadcn/context-menu/context-menu-trigger.svelte +17 -0
  355. package/src/shadcn/context-menu/context-menu.svelte +7 -0
  356. package/src/shadcn/context-menu/index.ts +52 -0
  357. package/src/shadcn/dialog/dialog-close.svelte +11 -0
  358. package/src/shadcn/dialog/dialog-content.svelte +57 -0
  359. package/src/shadcn/dialog/dialog-description.svelte +20 -0
  360. package/src/shadcn/dialog/dialog-footer.svelte +32 -0
  361. package/src/shadcn/dialog/dialog-header.svelte +20 -0
  362. package/src/shadcn/dialog/dialog-overlay.svelte +20 -0
  363. package/src/shadcn/dialog/dialog-portal.svelte +7 -0
  364. package/src/shadcn/dialog/dialog-title.svelte +17 -0
  365. package/src/shadcn/dialog/dialog-trigger.svelte +11 -0
  366. package/src/shadcn/dialog/dialog.svelte +7 -0
  367. package/src/shadcn/dialog/index.ts +34 -0
  368. package/src/shadcn/dropdown-menu/dropdown-menu-checkbox-group.svelte +16 -0
  369. package/src/shadcn/dropdown-menu/dropdown-menu-checkbox-item.svelte +43 -0
  370. package/src/shadcn/dropdown-menu/dropdown-menu-content.svelte +31 -0
  371. package/src/shadcn/dropdown-menu/dropdown-menu-group-heading.svelte +22 -0
  372. package/src/shadcn/dropdown-menu/dropdown-menu-group.svelte +7 -0
  373. package/src/shadcn/dropdown-menu/dropdown-menu-item.svelte +29 -0
  374. package/src/shadcn/dropdown-menu/dropdown-menu-label.svelte +27 -0
  375. package/src/shadcn/dropdown-menu/dropdown-menu-portal.svelte +7 -0
  376. package/src/shadcn/dropdown-menu/dropdown-menu-radio-group.svelte +16 -0
  377. package/src/shadcn/dropdown-menu/dropdown-menu-radio-item.svelte +34 -0
  378. package/src/shadcn/dropdown-menu/dropdown-menu-separator.svelte +17 -0
  379. package/src/shadcn/dropdown-menu/dropdown-menu-shortcut.svelte +23 -0
  380. package/src/shadcn/dropdown-menu/dropdown-menu-sub-content.svelte +20 -0
  381. package/src/shadcn/dropdown-menu/dropdown-menu-sub-trigger.svelte +29 -0
  382. package/src/shadcn/dropdown-menu/dropdown-menu-sub.svelte +7 -0
  383. package/src/shadcn/dropdown-menu/dropdown-menu-trigger.svelte +7 -0
  384. package/src/shadcn/dropdown-menu/dropdown-menu.svelte +7 -0
  385. package/src/shadcn/dropdown-menu/index.ts +56 -0
  386. package/src/shadcn/dropdown-menu/menu-item.ts +43 -0
  387. package/src/shadcn/empty/empty-content.svelte +23 -0
  388. package/src/shadcn/empty/empty-description.svelte +23 -0
  389. package/src/shadcn/empty/empty-header.svelte +20 -0
  390. package/src/shadcn/empty/empty-media.svelte +41 -0
  391. package/src/shadcn/empty/empty-title.svelte +20 -0
  392. package/src/shadcn/empty/empty.svelte +23 -0
  393. package/src/shadcn/empty/index.ts +22 -0
  394. package/src/shadcn/field/field-content.svelte +20 -0
  395. package/src/shadcn/field/field-description.svelte +25 -0
  396. package/src/shadcn/field/field-error.svelte +58 -0
  397. package/src/shadcn/field/field-group.svelte +23 -0
  398. package/src/shadcn/field/field-label.svelte +26 -0
  399. package/src/shadcn/field/field-legend.svelte +29 -0
  400. package/src/shadcn/field/field-separator.svelte +35 -0
  401. package/src/shadcn/field/field-set.svelte +24 -0
  402. package/src/shadcn/field/field-title.svelte +23 -0
  403. package/src/shadcn/field/field.svelte +53 -0
  404. package/src/shadcn/field/index.ts +36 -0
  405. package/src/shadcn/field/simple-field.svelte +71 -0
  406. package/src/shadcn/field/simple-field.test.ts +47 -0
  407. package/src/shadcn/hooks/highlighter.ts +132 -0
  408. package/src/shadcn/hooks/is-mobile.svelte.ts +9 -0
  409. package/src/shadcn/hooks/use-highlighted-code.svelte.ts +98 -0
  410. package/src/shadcn/hover-card/hover-card-content.svelte +31 -0
  411. package/src/shadcn/hover-card/hover-card-portal.svelte +7 -0
  412. package/src/shadcn/hover-card/hover-card-trigger.svelte +7 -0
  413. package/src/shadcn/hover-card/hover-card.svelte +7 -0
  414. package/src/shadcn/hover-card/index.ts +15 -0
  415. package/src/shadcn/input/index.ts +9 -0
  416. package/src/shadcn/input/input.svelte +48 -0
  417. package/src/shadcn/input/secret-input.svelte +75 -0
  418. package/src/shadcn/input-otp/index.ts +15 -0
  419. package/src/shadcn/input-otp/input-otp-group.svelte +20 -0
  420. package/src/shadcn/input-otp/input-otp-separator.svelte +25 -0
  421. package/src/shadcn/input-otp/input-otp-slot.svelte +26 -0
  422. package/src/shadcn/input-otp/input-otp.svelte +22 -0
  423. package/src/shadcn/kbd/index.ts +10 -0
  424. package/src/shadcn/kbd/kbd-group.svelte +20 -0
  425. package/src/shadcn/kbd/kbd.svelte +23 -0
  426. package/src/shadcn/label/index.ts +7 -0
  427. package/src/shadcn/label/label.svelte +20 -0
  428. package/src/shadcn/marker/index.ts +15 -0
  429. package/src/shadcn/marker/marker-content.svelte +23 -0
  430. package/src/shadcn/marker/marker-icon.svelte +21 -0
  431. package/src/shadcn/marker/marker.svelte +52 -0
  432. package/src/shadcn/message/index.ts +22 -0
  433. package/src/shadcn/message/message-avatar.svelte +23 -0
  434. package/src/shadcn/message/message-content.svelte +23 -0
  435. package/src/shadcn/message/message-footer.svelte +23 -0
  436. package/src/shadcn/message/message-group.svelte +20 -0
  437. package/src/shadcn/message/message-header.svelte +23 -0
  438. package/src/shadcn/message/message.svelte +27 -0
  439. package/src/shadcn/message-scroller/index.ts +28 -0
  440. package/src/shadcn/message-scroller/message-scroller-button.svelte +52 -0
  441. package/src/shadcn/message-scroller/message-scroller-content.svelte +17 -0
  442. package/src/shadcn/message-scroller/message-scroller-item.svelte +22 -0
  443. package/src/shadcn/message-scroller/message-scroller-provider.svelte +7 -0
  444. package/src/shadcn/message-scroller/message-scroller-viewport.svelte +20 -0
  445. package/src/shadcn/message-scroller/message-scroller.svelte +20 -0
  446. package/src/shadcn/popover/index.ts +28 -0
  447. package/src/shadcn/popover/popover-close.svelte +7 -0
  448. package/src/shadcn/popover/popover-content.svelte +38 -0
  449. package/src/shadcn/popover/popover-description.svelte +20 -0
  450. package/src/shadcn/popover/popover-header.svelte +20 -0
  451. package/src/shadcn/popover/popover-portal.svelte +7 -0
  452. package/src/shadcn/popover/popover-title.svelte +15 -0
  453. package/src/shadcn/popover/popover-trigger.svelte +17 -0
  454. package/src/shadcn/popover/popover.svelte +7 -0
  455. package/src/shadcn/progress/index.ts +7 -0
  456. package/src/shadcn/progress/progress.svelte +30 -0
  457. package/src/shadcn/radio-group/index.ts +10 -0
  458. package/src/shadcn/radio-group/radio-group-item.svelte +32 -0
  459. package/src/shadcn/radio-group/radio-group.svelte +19 -0
  460. package/src/shadcn/scroll-area/index.ts +10 -0
  461. package/src/shadcn/scroll-area/scroll-area-scrollbar.svelte +30 -0
  462. package/src/shadcn/scroll-area/scroll-area.svelte +43 -0
  463. package/src/shadcn/select/index.ts +37 -0
  464. package/src/shadcn/select/select-content.svelte +45 -0
  465. package/src/shadcn/select/select-group-heading.svelte +21 -0
  466. package/src/shadcn/select/select-group.svelte +17 -0
  467. package/src/shadcn/select/select-item.svelte +38 -0
  468. package/src/shadcn/select/select-label.svelte +20 -0
  469. package/src/shadcn/select/select-portal.svelte +7 -0
  470. package/src/shadcn/select/select-scroll-down-button.svelte +23 -0
  471. package/src/shadcn/select/select-scroll-up-button.svelte +23 -0
  472. package/src/shadcn/select/select-separator.svelte +18 -0
  473. package/src/shadcn/select/select-trigger.svelte +33 -0
  474. package/src/shadcn/select/select.svelte +11 -0
  475. package/src/shadcn/separator/index.ts +7 -0
  476. package/src/shadcn/separator/separator.svelte +23 -0
  477. package/src/shadcn/sheet/index.ts +34 -0
  478. package/src/shadcn/sheet/sheet-close.svelte +7 -0
  479. package/src/shadcn/sheet/sheet-content.svelte +73 -0
  480. package/src/shadcn/sheet/sheet-description.svelte +17 -0
  481. package/src/shadcn/sheet/sheet-footer.svelte +20 -0
  482. package/src/shadcn/sheet/sheet-header.svelte +20 -0
  483. package/src/shadcn/sheet/sheet-overlay.svelte +20 -0
  484. package/src/shadcn/sheet/sheet-portal.svelte +7 -0
  485. package/src/shadcn/sheet/sheet-title.svelte +17 -0
  486. package/src/shadcn/sheet/sheet-trigger.svelte +7 -0
  487. package/src/shadcn/sheet/sheet.css +48 -0
  488. package/src/shadcn/sheet/sheet.svelte +7 -0
  489. package/src/shadcn/sidebar/constants.ts +6 -0
  490. package/src/shadcn/sidebar/context.svelte.ts +81 -0
  491. package/src/shadcn/sidebar/index.ts +75 -0
  492. package/src/shadcn/sidebar/sidebar-content.svelte +24 -0
  493. package/src/shadcn/sidebar/sidebar-footer.svelte +21 -0
  494. package/src/shadcn/sidebar/sidebar-group-action.svelte +33 -0
  495. package/src/shadcn/sidebar/sidebar-group-content.svelte +21 -0
  496. package/src/shadcn/sidebar/sidebar-group-label.svelte +33 -0
  497. package/src/shadcn/sidebar/sidebar-group.svelte +21 -0
  498. package/src/shadcn/sidebar/sidebar-header.svelte +21 -0
  499. package/src/shadcn/sidebar/sidebar-input.svelte +21 -0
  500. package/src/shadcn/sidebar/sidebar-inset.svelte +23 -0
  501. package/src/shadcn/sidebar/sidebar-menu-action.svelte +37 -0
  502. package/src/shadcn/sidebar/sidebar-menu-badge.svelte +24 -0
  503. package/src/shadcn/sidebar/sidebar-menu-button.svelte +103 -0
  504. package/src/shadcn/sidebar/sidebar-menu-item.svelte +21 -0
  505. package/src/shadcn/sidebar/sidebar-menu-skeleton.svelte +36 -0
  506. package/src/shadcn/sidebar/sidebar-menu-sub-button.svelte +39 -0
  507. package/src/shadcn/sidebar/sidebar-menu-sub-item.svelte +21 -0
  508. package/src/shadcn/sidebar/sidebar-menu-sub.svelte +24 -0
  509. package/src/shadcn/sidebar/sidebar-menu.svelte +21 -0
  510. package/src/shadcn/sidebar/sidebar-provider.svelte +62 -0
  511. package/src/shadcn/sidebar/sidebar-rail.svelte +36 -0
  512. package/src/shadcn/sidebar/sidebar-separator.svelte +19 -0
  513. package/src/shadcn/sidebar/sidebar-trigger.svelte +37 -0
  514. package/src/shadcn/sidebar/sidebar.svelte +109 -0
  515. package/src/shadcn/skeleton/index.ts +7 -0
  516. package/src/shadcn/skeleton/skeleton.svelte +17 -0
  517. package/src/shadcn/slider/index.ts +7 -0
  518. package/src/shadcn/slider/slider.svelte +52 -0
  519. package/src/shadcn/sonner/index.ts +1 -0
  520. package/src/shadcn/sonner/sonner.svelte +31 -0
  521. package/src/shadcn/spinner/index.ts +1 -0
  522. package/src/shadcn/spinner/spinner.svelte +26 -0
  523. package/src/shadcn/switch/index.ts +7 -0
  524. package/src/shadcn/switch/switch.svelte +31 -0
  525. package/src/shadcn/table/index.ts +28 -0
  526. package/src/shadcn/table/table-body.svelte +20 -0
  527. package/src/shadcn/table/table-caption.svelte +20 -0
  528. package/src/shadcn/table/table-cell.svelte +20 -0
  529. package/src/shadcn/table/table-footer.svelte +20 -0
  530. package/src/shadcn/table/table-head.svelte +23 -0
  531. package/src/shadcn/table/table-header.svelte +20 -0
  532. package/src/shadcn/table/table-row.svelte +23 -0
  533. package/src/shadcn/table/table.svelte +25 -0
  534. package/src/shadcn/tabs/index.ts +16 -0
  535. package/src/shadcn/tabs/tabs-content.svelte +17 -0
  536. package/src/shadcn/tabs/tabs-list.svelte +36 -0
  537. package/src/shadcn/tabs/tabs-trigger.svelte +20 -0
  538. package/src/shadcn/tabs/tabs.svelte +19 -0
  539. package/src/shadcn/textarea/index.ts +7 -0
  540. package/src/shadcn/textarea/textarea.svelte +23 -0
  541. package/src/shadcn/toggle/index.ts +13 -0
  542. package/src/shadcn/toggle/toggle.svelte +53 -0
  543. package/src/shadcn/toggle-group/index.ts +10 -0
  544. package/src/shadcn/toggle-group/toggle-group-item.svelte +35 -0
  545. package/src/shadcn/toggle-group/toggle-group.svelte +66 -0
  546. package/src/shadcn/tooltip/context.ts +16 -0
  547. package/src/shadcn/tooltip/index.ts +19 -0
  548. package/src/shadcn/tooltip/tooltip-content.svelte +38 -0
  549. package/src/shadcn/tooltip/tooltip-portal.svelte +7 -0
  550. package/src/shadcn/tooltip/tooltip-provider.svelte +16 -0
  551. package/src/shadcn/tooltip/tooltip-trigger.svelte +7 -0
  552. package/src/shadcn/tooltip/tooltip.svelte +19 -0
  553. package/src/shadcn/trace/index.ts +29 -0
  554. package/src/shadcn/trace/trace-collapsible-content.svelte +29 -0
  555. package/src/shadcn/trace/trace-collapsible-trigger.svelte +29 -0
  556. package/src/shadcn/trace/trace-collapsible.svelte +20 -0
  557. package/src/shadcn/trace/trace-description.svelte +20 -0
  558. package/src/shadcn/trace/trace-icon.svelte +24 -0
  559. package/src/shadcn/trace/trace-line.svelte +31 -0
  560. package/src/shadcn/trace/trace-title.svelte +20 -0
  561. package/src/shadcn/trace/trace.svelte +23 -0
  562. package/src/shadcn/utilities.css +549 -0
  563. package/src/test-setup.ts +34 -0
  564. package/src/tokens.css +993 -0
@@ -0,0 +1,838 @@
1
+ // @ts-nocheck WHY: vendored engine, kept as upstream wrote it so a re-sync stays a clean diff. Strict flags in consumers (review) would flag ~870 spots.
2
+ /* mesh.ts — a small surface renderer for the things the bots wear.
3
+
4
+ A worn object is a handful of parametric surfaces (a dome, a band, a
5
+ cone, a tube along a curve, a revolved profile), each sampled into a
6
+ grid in its own space once and kept; every frame the grid is projected
7
+ through the head's rotation and lit from the body's light with a
8
+ material: felt, knit, paper, gold, silver, velvet, satin, soft plastic,
9
+ leather, pearl, a cut gem.
10
+
11
+ It is painted a strip at a time: the visible stretch of each row of the
12
+ grid is one shape, filled with a gradient that carries the light along
13
+ the row from the corners' own normals, so a dome is a dozen fills rather
14
+ than hundreds of quads and its light runs smoothly round it; the rows
15
+ step it the other way, finely enough not to show. Strips go back to
16
+ front across all the surfaces of an object. Cut facets are painted a
17
+ cell at a time, flat. Small spheres — pearls, beads, rivets — are
18
+ single radial fills. Canvas 2D only. */
19
+
20
+ export type V3 = [number, number, number];
21
+ export type P2 = [number, number];
22
+ export type RGB = [number, number, number];
23
+
24
+ /** How the worn object is seen: a linear map from the object's space
25
+ (about the origin `o`) to view space — rows for screen x (right), screen
26
+ y (down) and depth (toward the viewer) — the light, and the scale. */
27
+ export interface View {
28
+ m: number[];
29
+ o: V3;
30
+ toScreen(p: V3): P2;
31
+ depth(p: V3): number;
32
+ /** a direction in the object's space, turned into view space, unit length */
33
+ dir(v: V3): V3;
34
+ /** toward the light, and the half vector with the view, in view space */
35
+ L: V3;
36
+ H: V3;
37
+ /** device pixels per unit of this space */
38
+ px: number;
39
+ }
40
+
41
+ export function makeView(m: number[], o: V3, L: V3, H: V3, px: number): View {
42
+ return {
43
+ m,
44
+ o,
45
+ L,
46
+ H,
47
+ px,
48
+ toScreen: (p) => {
49
+ const x = p[0] - o[0],
50
+ y = p[1] - o[1],
51
+ z = p[2] - o[2];
52
+ return [m[0] * x + m[1] * y + m[2] * z, m[3] * x + m[4] * y + m[5] * z];
53
+ },
54
+ depth: (p) => m[6] * (p[0] - o[0]) + m[7] * (p[1] - o[1]) + m[8] * (p[2] - o[2]),
55
+ dir: (v) =>
56
+ norm([
57
+ m[0] * v[0] + m[1] * v[1] + m[2] * v[2],
58
+ m[3] * v[0] + m[4] * v[1] + m[5] * v[2],
59
+ m[6] * v[0] + m[7] * v[1] + m[8] * v[2],
60
+ ]),
61
+ };
62
+ }
63
+
64
+ /** A material: a normal (view space) to a colour. */
65
+ export type Shade = (n: V3) => RGB;
66
+
67
+ export const sub = (a: V3, b: V3): V3 => [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
68
+ export const cross = (a: V3, b: V3): V3 => [
69
+ a[1] * b[2] - a[2] * b[1],
70
+ a[2] * b[0] - a[0] * b[2],
71
+ a[0] * b[1] - a[1] * b[0],
72
+ ];
73
+ export const dot = (a: V3, b: V3) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
74
+ export const norm = (v: V3): V3 => {
75
+ const l = Math.hypot(v[0], v[1], v[2]) || 1;
76
+ return [v[0] / l, v[1] / l, v[2] / l];
77
+ };
78
+ const TAU = Math.PI * 2;
79
+
80
+ /** How many segments to cut a length into for this view: about one per
81
+ `quad` device pixels, within `lo … hi`. */
82
+ export function segs(view: View, len: number, lo: number, hi: number, quad = 11): number {
83
+ return Math.max(lo, Math.min(hi, Math.round((len * view.px) / quad)));
84
+ }
85
+
86
+ /** A surface sampled on a grid, in its object's space: positions, the
87
+ cells' normals and (for smooth shading) the corners'. Built once and
88
+ kept; each frame only projects it. */
89
+ export interface Mesh {
90
+ nu: number;
91
+ nv: number;
92
+ /** closed round u: its first and last columns are one */
93
+ closed: boolean;
94
+ pos: Float64Array;
95
+ fn: Float64Array;
96
+ vn: Float64Array | null;
97
+ }
98
+
99
+ /**
100
+ * Sample `surf(u, v)` (u, v ∈ [0, 1]) on an `nu × nv` grid. `inside` is a
101
+ * point inside the object, or for a tube the point on its centre line:
102
+ * each normal is turned to face away from it, so a surface needs no care
103
+ * over its winding. With `smooth`, every corner gets the mean of its
104
+ * cells' normals — a surface closed round u, or pinched to a pole, is
105
+ * joined up — for the gradient fill; without, the cells stay faceted.
106
+ */
107
+ export function makeMesh(
108
+ surf: (u: number, v: number) => V3,
109
+ nu: number,
110
+ nv: number,
111
+ inside: V3 | ((u: number, v: number) => V3),
112
+ smooth = true,
113
+ ): Mesh {
114
+ const W = nu + 1,
115
+ N = W * (nv + 1);
116
+ const pos = new Float64Array(N * 3);
117
+ let ext = 0;
118
+ for (let j = 0, k = 0; j <= nv; j++) {
119
+ for (let i = 0; i <= nu; i++, k++) {
120
+ const p = surf(i / nu, j / nv);
121
+ pos[k * 3] = p[0];
122
+ pos[k * 3 + 1] = p[1];
123
+ pos[k * 3 + 2] = p[2];
124
+ ext = Math.max(
125
+ ext,
126
+ Math.abs(p[0] - pos[0]) + Math.abs(p[1] - pos[1]) + Math.abs(p[2] - pos[2]),
127
+ );
128
+ }
129
+ }
130
+ const P = (k: number): V3 => [pos[k * 3], pos[k * 3 + 1], pos[k * 3 + 2]];
131
+ const fn = new Float64Array(nu * nv * 3);
132
+ for (let j = 0; j < nv; j++) {
133
+ for (let i = 0; i < nu; i++) {
134
+ const a = j * W + i;
135
+ const A = P(a),
136
+ B = P(a + 1),
137
+ C = P(a + W + 1),
138
+ D = P(a + W);
139
+ /* the diagonals' cross product survives a quad that pinches to a
140
+ point at a pole */
141
+ let n = cross(sub(C, A), sub(D, B));
142
+ const mid: V3 = [
143
+ (A[0] + B[0] + C[0] + D[0]) / 4,
144
+ (A[1] + B[1] + C[1] + D[1]) / 4,
145
+ (A[2] + B[2] + C[2] + D[2]) / 4,
146
+ ];
147
+ const ins = typeof inside === "function" ? inside((i + 0.5) / nu, (j + 0.5) / nv) : inside;
148
+ if (dot(n, sub(mid, ins)) < 0) n = [-n[0], -n[1], -n[2]];
149
+ n = norm(n);
150
+ const f = (j * nu + i) * 3;
151
+ fn[f] = n[0];
152
+ fn[f + 1] = n[1];
153
+ fn[f + 2] = n[2];
154
+ }
155
+ }
156
+ const eps = 1e-6 + ext * 1e-5;
157
+ const same = (p: number, q: number) =>
158
+ Math.abs(pos[p * 3] - pos[q * 3]) +
159
+ Math.abs(pos[p * 3 + 1] - pos[q * 3 + 1]) +
160
+ Math.abs(pos[p * 3 + 2] - pos[q * 3 + 2]) <
161
+ eps;
162
+ let closed = true;
163
+ for (let j = 0; j <= nv && closed; j++) closed = same(j * W, j * W + nu);
164
+ let vn: Float64Array | null = null;
165
+ if (smooth) {
166
+ const acc = new Float64Array(N * 3);
167
+ for (let j = 0; j < nv; j++) {
168
+ for (let i = 0; i < nu; i++) {
169
+ const f = (j * nu + i) * 3;
170
+ const a = j * W + i;
171
+ for (const k of [a, a + 1, a + W + 1, a + W]) {
172
+ acc[k * 3] += fn[f];
173
+ acc[k * 3 + 1] += fn[f + 1];
174
+ acc[k * 3 + 2] += fn[f + 2];
175
+ }
176
+ }
177
+ }
178
+ const join = (ks: number[]) => {
179
+ let x = 0,
180
+ y = 0,
181
+ z = 0;
182
+ for (const k of ks) {
183
+ x += acc[k * 3];
184
+ y += acc[k * 3 + 1];
185
+ z += acc[k * 3 + 2];
186
+ }
187
+ for (const k of ks) {
188
+ acc[k * 3] = x;
189
+ acc[k * 3 + 1] = y;
190
+ acc[k * 3 + 2] = z;
191
+ }
192
+ };
193
+ if (closed) for (let j = 0; j <= nv; j++) join([j * W, j * W + nu]);
194
+ for (const j of [0, nv]) {
195
+ let pole = true;
196
+ for (let i = 1; i <= nu && pole; i++) pole = same(j * W, j * W + i);
197
+ if (pole) join(Array.from({ length: W }, (_, i) => j * W + i));
198
+ }
199
+ for (let k = 0; k < N; k++) {
200
+ const x = acc[k * 3],
201
+ y = acc[k * 3 + 1],
202
+ z = acc[k * 3 + 2];
203
+ const l = Math.hypot(x, y, z) || 1;
204
+ acc[k * 3] = x / l;
205
+ acc[k * 3 + 1] = y / l;
206
+ acc[k * 3 + 2] = z / l;
207
+ }
208
+ vn = acc;
209
+ }
210
+ return { nu, nv, closed, pos, fn, vn };
211
+ }
212
+
213
+ /**
214
+ * A tube along a curve: `path(u)` its centre line, `up` the direction its
215
+ * cross-section is squared to (a band's depth axis, a rim's face normal),
216
+ * `prof(u, φ)` the cross-section's offset at angle φ — along the up
217
+ * direction and across it. Rows of the grid run along the tube.
218
+ */
219
+ export function makeTube(
220
+ path: (u: number) => V3,
221
+ up: V3,
222
+ prof: (u: number, ph: number) => [number, number],
223
+ nu: number,
224
+ nv: number,
225
+ closed = false,
226
+ ): Mesh {
227
+ const e = 1e-3;
228
+ const at = (u: number) => path(closed ? ((u % 1) + 1) % 1 : Math.max(0, Math.min(1, u)));
229
+ return makeMesh(
230
+ (u, v) => {
231
+ const c = path(u);
232
+ const T = norm(sub(at(u + e), at(u - e)));
233
+ const k = dot(up, T);
234
+ const Nn = norm([up[0] - T[0] * k, up[1] - T[1] * k, up[2] - T[2] * k]);
235
+ const Bn = cross(T, Nn);
236
+ const [a, b] = prof(u, v * TAU);
237
+ return [
238
+ c[0] + Nn[0] * a + Bn[0] * b,
239
+ c[1] + Nn[1] * a + Bn[1] * b,
240
+ c[2] + Nn[2] * a + Bn[2] * b,
241
+ ];
242
+ },
243
+ nu,
244
+ nv,
245
+ (u) => path(u),
246
+ );
247
+ }
248
+
249
+ /** A cross-section: an ellipse (`p` = 1) squared toward a rounded box as
250
+ `p` falls, `a` along the up direction and `b` across it. */
251
+ export const section =
252
+ (a: number, b: number, p = 1) =>
253
+ (_u: number, ph: number): [number, number] => {
254
+ const c = Math.cos(ph),
255
+ s = Math.sin(ph);
256
+ return [
257
+ a * Math.sign(c) * Math.pow(Math.abs(c), p),
258
+ b * Math.sign(s) * Math.pow(Math.abs(s), p),
259
+ ];
260
+ };
261
+
262
+ /** A mesh as this frame sees it: every corner on screen with its depth
263
+ and its normal in view space, and every cell's normal and depth. */
264
+ export interface Proj {
265
+ nu: number;
266
+ nv: number;
267
+ closed: boolean;
268
+ x: Float64Array;
269
+ y: Float64Array;
270
+ d: Float64Array;
271
+ /** the corners' normals, 3 a corner; null for a faceted mesh */
272
+ n: Float64Array | null;
273
+ /** the cells' normals, 3 a cell, and their mean depths */
274
+ f: Float64Array;
275
+ cd: Float64Array;
276
+ }
277
+
278
+ export function projectMesh(view: View, mesh: Mesh): Proj {
279
+ const { nu, nv, pos, fn, vn } = mesh;
280
+ const W = nu + 1,
281
+ N = W * (nv + 1),
282
+ C = nu * nv;
283
+ const [m0, m1, m2, m3, m4, m5, m6, m7, m8] = view.m;
284
+ const [ox, oy, oz] = view.o;
285
+ const x = new Float64Array(N),
286
+ y = new Float64Array(N),
287
+ d = new Float64Array(N);
288
+ for (let k = 0; k < N; k++) {
289
+ const X = pos[k * 3] - ox,
290
+ Y = pos[k * 3 + 1] - oy,
291
+ Z = pos[k * 3 + 2] - oz;
292
+ x[k] = m0 * X + m1 * Y + m2 * Z;
293
+ y[k] = m3 * X + m4 * Y + m5 * Z;
294
+ d[k] = m6 * X + m7 * Y + m8 * Z;
295
+ }
296
+ const turn = (src: Float64Array, dst: Float64Array, count: number) => {
297
+ for (let k = 0; k < count; k++) {
298
+ const a = src[k * 3],
299
+ b = src[k * 3 + 1],
300
+ c = src[k * 3 + 2];
301
+ const X = m0 * a + m1 * b + m2 * c,
302
+ Y = m3 * a + m4 * b + m5 * c,
303
+ Z = m6 * a + m7 * b + m8 * c;
304
+ const l = Math.hypot(X, Y, Z) || 1;
305
+ dst[k * 3] = X / l;
306
+ dst[k * 3 + 1] = Y / l;
307
+ dst[k * 3 + 2] = Z / l;
308
+ }
309
+ };
310
+ let n: Float64Array | null = null;
311
+ if (vn) turn(vn, (n = new Float64Array(N * 3)), N);
312
+ const f = new Float64Array(C * 3),
313
+ cd = new Float64Array(C);
314
+ turn(fn, f, C);
315
+ for (let j = 0, c = 0; j < nv; j++) {
316
+ for (let i = 0; i < nu; i++, c++) {
317
+ const a = j * W + i;
318
+ cd[c] = (d[a] + d[a + 1] + d[a + W] + d[a + W + 1]) / 4;
319
+ }
320
+ }
321
+ return { nu, nv, closed: mesh.closed, x, y, d, n, f, cd };
322
+ }
323
+
324
+ /** The screen bounds of some projected meshes. */
325
+ export function boundsOf(ps: Proj[]) {
326
+ let x0 = Infinity,
327
+ y0 = Infinity,
328
+ x1 = -Infinity,
329
+ y1 = -Infinity;
330
+ for (const p of ps) {
331
+ for (let k = 0; k < p.x.length; k++) {
332
+ if (p.x[k] < x0) x0 = p.x[k];
333
+ if (p.x[k] > x1) x1 = p.x[k];
334
+ if (p.y[k] < y0) y0 = p.y[k];
335
+ if (p.y[k] > y1) y1 = p.y[k];
336
+ }
337
+ }
338
+ return { x0, y0, x1, y1, cx: (x0 + x1) / 2, cy: (y0 + y1) / 2, w: x1 - x0, h: y1 - y0 };
339
+ }
340
+
341
+ /* ── painting ──────────────────────────────────────────────────────── */
342
+
343
+ export interface Layer {
344
+ parts: Proj[];
345
+ shade: Shade;
346
+ /** keep the cells facing away (thin plates seen from behind) */
347
+ cull?: boolean;
348
+ /** a cell at a time, one colour each: cut facets */
349
+ flat?: boolean;
350
+ /** a nudge toward the viewer in the depth sort: a part set into another
351
+ (a ring round a plate) wins where they meet */
352
+ bias?: number;
353
+ /** keep only the cells whose depth passes: one side of a split */
354
+ keep?: (d: number) => boolean;
355
+ }
356
+
357
+ interface Piece {
358
+ d: number;
359
+ l: Layer;
360
+ p: Proj;
361
+ j: number;
362
+ /** the strip's columns (corner indices along its row) and its cells; one
363
+ cell alone for a flat piece */
364
+ cols: number[];
365
+ cells: number[];
366
+ }
367
+
368
+ /* is the run of column midpoints s … e one way along its chord */
369
+ function monotone(mx: number[], my: number[], s: number, e: number): boolean {
370
+ const ax = mx[e] - mx[s],
371
+ ay = my[e] - my[s];
372
+ let prev = -Infinity;
373
+ for (let k = s; k <= e; k++) {
374
+ const t = (mx[k] - mx[s]) * ax + (my[k] - my[s]) * ay;
375
+ if (t < prev - 1e-9) return false;
376
+ prev = t;
377
+ }
378
+ return true;
379
+ }
380
+
381
+ const clampByte = (v: number) => (v < 0 ? 0 : v > 255 ? 255 : Math.round(v));
382
+ export const css = (c: RGB) => `rgb(${clampByte(c[0])},${clampByte(c[1])},${clampByte(c[2])})`;
383
+
384
+ /** Paint several surfaces as one, back to front, the parts facing away
385
+ dropped. `px` is the view's scale. Returns the corners painted, for an
386
+ outline. */
387
+ export function paintLayers(ctx: CanvasRenderingContext2D, layers: Layer[], px: number): P2[] {
388
+ const pieces: Piece[] = [];
389
+ const corners: P2[] = [];
390
+ for (const l of layers) {
391
+ const bias = l.bias ?? 0,
392
+ keep = l.keep,
393
+ cull = l.cull !== false;
394
+ for (const P of l.parts) {
395
+ const { nu, nv, f, cd, x, y } = P,
396
+ W = nu + 1;
397
+ const vis = (c: number) => (!cull || f[c * 3 + 2] > -0.04) && (!keep || keep(cd[c]));
398
+ for (let j = 0; j < nv; j++) {
399
+ if (l.flat) {
400
+ for (let i = 0; i < nu; i++) {
401
+ const c = j * nu + i;
402
+ if (!vis(c)) continue;
403
+ pieces.push({ d: cd[c] + bias, l, p: P, j, cols: [i, i + 1], cells: [c] });
404
+ corners.push(
405
+ [x[j * W + i], y[j * W + i]],
406
+ [x[(j + 1) * W + i + 1], y[(j + 1) * W + i + 1]],
407
+ );
408
+ }
409
+ continue;
410
+ }
411
+ /* the row's visible runs, joined across the seam of a closed grid */
412
+ const runs: number[][] = [];
413
+ let cur: number[] | null = null;
414
+ for (let i = 0; i < nu; i++) {
415
+ if (vis(j * nu + i)) {
416
+ if (!cur) runs.push((cur = []));
417
+ cur.push(i);
418
+ } else cur = null;
419
+ }
420
+ if (
421
+ P.closed &&
422
+ runs.length > 1 &&
423
+ runs[0][0] === 0 &&
424
+ runs[runs.length - 1][runs[runs.length - 1].length - 1] === nu - 1
425
+ ) {
426
+ runs[0] = runs.pop()!.concat(runs[0]);
427
+ }
428
+ for (const run of runs) {
429
+ const cols = run.concat(run[run.length - 1] + 1);
430
+ const mx: number[] = [],
431
+ my: number[] = [];
432
+ for (let k = 0; k < cols.length; k++) {
433
+ const t = j * W + cols[k],
434
+ b = t + W;
435
+ mx.push((x[t] + x[b]) / 2);
436
+ my.push((y[t] + y[b]) / 2);
437
+ /* the outline needs only the silhouette: a run's ends, and the
438
+ grid's first and last rows */
439
+ if (k === 0 || k === cols.length - 1) corners.push([x[t], y[t]], [x[b], y[b]]);
440
+ else if (j === 0) corners.push([x[t], y[t]]);
441
+ else if (j === nv - 1) corners.push([x[b], y[b]]);
442
+ }
443
+ /* split where the row turns back on itself on screen, so each
444
+ strip can take one linear gradient */
445
+ let s = 0;
446
+ while (s < cols.length - 1) {
447
+ let e = s + 1;
448
+ while (e + 1 < cols.length && monotone(mx, my, s, e + 1)) e++;
449
+ let dd = 0;
450
+ for (let k = s; k < e; k++) dd += cd[j * nu + run[k]];
451
+ pieces.push({
452
+ d: dd / (e - s) + bias,
453
+ l,
454
+ p: P,
455
+ j,
456
+ cols: cols.slice(s, e + 1),
457
+ cells: run.slice(s, e),
458
+ });
459
+ s = e;
460
+ }
461
+ }
462
+ }
463
+ }
464
+ }
465
+ pieces.sort((a, b) => a.d - b.d);
466
+ const grow = 0.5 / px;
467
+ for (const pc of pieces) {
468
+ const { p: P, l, j, cols, cells } = pc;
469
+ const { x, y, n, f } = P,
470
+ W = P.nu + 1;
471
+ const nc = cols.length;
472
+ /* the strip's outline, pushed out a little all round so neighbours
473
+ overlap and no seam of background shows */
474
+ const tx: number[] = [],
475
+ ty: number[] = [],
476
+ bx: number[] = [],
477
+ by: number[] = [];
478
+ const t0 = j * W + cols[0],
479
+ tn = j * W + cols[nc - 1];
480
+ let ax = (x[tn] + x[tn + W]) / 2 - (x[t0] + x[t0 + W]) / 2,
481
+ ay = (y[tn] + y[tn + W]) / 2 - (y[t0] + y[t0 + W]) / 2;
482
+ const al = Math.hypot(ax, ay);
483
+ const ux = al > 1e-9 ? ax / al : 0,
484
+ uy = al > 1e-9 ? ay / al : 0;
485
+ for (let k = 0; k < nc; k++) {
486
+ const t = j * W + cols[k],
487
+ b = t + W;
488
+ let dx = x[t] - x[b],
489
+ dy = y[t] - y[b];
490
+ const dl = Math.hypot(dx, dy);
491
+ if (dl > 1e-9) ((dx /= dl), (dy /= dl));
492
+ else dx = dy = 0;
493
+ const e = k === 0 ? -grow : k === nc - 1 ? grow : 0;
494
+ tx.push(x[t] + dx * grow + ux * e);
495
+ ty.push(y[t] + dy * grow + uy * e);
496
+ bx.push(x[b] - dx * grow + ux * e);
497
+ by.push(y[b] - dy * grow + uy * e);
498
+ }
499
+ /* the colour at each column: from the corners' normals, or the cells'
500
+ beside it on a faceted mesh; stops only where the colour has moved */
501
+ const colour = (k: number): RGB => {
502
+ if (n && !l.flat) {
503
+ const t = (j * W + cols[k]) * 3,
504
+ b = t + W * 3;
505
+ return l.shade(norm([n[t] + n[b], n[t + 1] + n[b + 1], n[t + 2] + n[b + 2]]));
506
+ }
507
+ const c0 = cells[Math.max(0, Math.min(cells.length - 1, k - 1))] * 3,
508
+ c1 = cells[Math.min(cells.length - 1, k)] * 3;
509
+ return l.shade(
510
+ l.flat
511
+ ? [f[c1], f[c1 + 1], f[c1 + 2]]
512
+ : norm([f[c0] + f[c1], f[c0 + 1] + f[c1 + 1], f[c0 + 2] + f[c1 + 2]]),
513
+ );
514
+ };
515
+ let style: string | CanvasGradient;
516
+ if (l.flat || al < 1e-6) style = css(colour(0));
517
+ else {
518
+ const g = ctx.createLinearGradient(
519
+ (x[t0] + x[t0 + W]) / 2,
520
+ (y[t0] + y[t0 + W]) / 2,
521
+ (x[tn] + x[tn + W]) / 2,
522
+ (y[tn] + y[tn + W]) / 2,
523
+ );
524
+ let last: RGB | null = null;
525
+ const m0x = (x[t0] + x[t0 + W]) / 2,
526
+ m0y = (y[t0] + y[t0 + W]) / 2;
527
+ for (let k = 0; k < nc; k++) {
528
+ const c = colour(k);
529
+ if (
530
+ last &&
531
+ k < nc - 1 &&
532
+ Math.abs(c[0] - last[0]) + Math.abs(c[1] - last[1]) + Math.abs(c[2] - last[2]) < 5
533
+ )
534
+ continue;
535
+ const t = j * W + cols[k];
536
+ const off =
537
+ (((x[t] + x[t + W]) / 2 - m0x) * ax + ((y[t] + y[t + W]) / 2 - m0y) * ay) / (al * al);
538
+ g.addColorStop(off < 0 ? 0 : off > 1 ? 1 : off, css(c));
539
+ last = c;
540
+ }
541
+ style = g;
542
+ }
543
+ ctx.fillStyle = style;
544
+ ctx.beginPath();
545
+ ctx.moveTo(tx[0], ty[0]);
546
+ for (let k = 1; k < nc; k++) ctx.lineTo(tx[k], ty[k]);
547
+ for (let k = nc - 1; k >= 0; k--) ctx.lineTo(bx[k], by[k]);
548
+ ctx.closePath();
549
+ ctx.fill();
550
+ }
551
+ return corners;
552
+ }
553
+
554
+ export function hull(pts: P2[]): P2[] {
555
+ const p = pts.slice().sort((a, b) => a[0] - b[0] || a[1] - b[1]);
556
+ if (p.length < 3) return p;
557
+ const cr = (o: P2, a: P2, b: P2) => (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
558
+ const lo: P2[] = [],
559
+ up: P2[] = [];
560
+ for (const q of p) {
561
+ while (lo.length >= 2 && cr(lo[lo.length - 2], lo[lo.length - 1], q) <= 0) lo.pop();
562
+ lo.push(q);
563
+ }
564
+ for (let i = p.length - 1; i >= 0; i--) {
565
+ const q = p[i];
566
+ while (up.length >= 2 && cr(up[up.length - 2], up[up.length - 1], q) <= 0) up.pop();
567
+ up.push(q);
568
+ }
569
+ lo.pop();
570
+ up.pop();
571
+ return lo.concat(up);
572
+ }
573
+
574
+ /* ── materials: a view and a base colour to a shade ────────────────── */
575
+
576
+ const clamp01 = (x: number) => (x < 0 ? 0 : x > 1 ? 1 : x);
577
+ export const mix = (a: RGB, b: RGB, t: number): RGB => [
578
+ a[0] + (b[0] - a[0]) * t,
579
+ a[1] + (b[1] - a[1]) * t,
580
+ a[2] + (b[2] - a[2]) * t,
581
+ ];
582
+ export const scale = (a: RGB, k: number): RGB => [a[0] * k, a[1] * k, a[2] * k];
583
+ const WHITE: RGB = [255, 255, 255];
584
+
585
+ /** Felt and knit: matte, the light wrapping softly round, a faint sheen
586
+ where the fibres lie side-on; a dark felt keeps a floor of light so it
587
+ reads charcoal on its lit side. `k` darkens it (a fold, an underside). */
588
+ export function felt(view: View, base: RGB, k = 1): Shade {
589
+ const L = view.L;
590
+ return (n) => {
591
+ const nl = dot(n, L);
592
+ const dif = Math.pow(clamp01((nl + 0.4) / 1.4), 1.35);
593
+ const graze = Math.pow(1 - clamp01(n[2]), 2);
594
+ const lit = (0.22 + 0.95 * dif + 0.22 * graze * clamp01(nl + 0.6)) * k;
595
+ const floor = 26 * dif * k;
596
+ return [base[0] * lit + floor, base[1] * lit + floor, base[2] * lit + floor * 1.05];
597
+ };
598
+ }
599
+
600
+ /** Velvet: deep in its folds, its colour lifting toward the silhouette. */
601
+ export function velvet(view: View, base: RGB): Shade {
602
+ const L = view.L,
603
+ sheen = mix(base, WHITE, 0.3);
604
+ return (n) => {
605
+ const nl = dot(n, L);
606
+ const dif = clamp01((nl + 0.3) / 1.3);
607
+ const graze = Math.pow(1 - clamp01(n[2]), 1.6);
608
+ return mix(scale(base, 0.2 + 0.7 * dif), sheen, 0.6 * graze * (0.4 + 0.6 * dif));
609
+ };
610
+ }
611
+
612
+ /** Paper: Lambert with a soft wide sheen. */
613
+ export function paper(view: View, base: RGB): Shade {
614
+ const L = view.L,
615
+ H = view.H;
616
+ return (n) => {
617
+ const dif = clamp01((dot(n, L) + 0.2) / 1.2);
618
+ const spec = Math.pow(clamp01(dot(n, H)), 22) * 0.3;
619
+ return mix(scale(base, 0.36 + 0.7 * dif), WHITE, spec);
620
+ };
621
+ }
622
+
623
+ /** Soft plastic and leather: diffuse, a broad sheen and a tighter hot
624
+ spot, a thin rim of light at the silhouette. */
625
+ export function plastic(view: View, base: RGB, gloss = 1): Shade {
626
+ const L = view.L,
627
+ H = view.H;
628
+ return (n) => {
629
+ const dif = clamp01((dot(n, L) + 0.25) / 1.25);
630
+ const nh = clamp01(dot(n, H));
631
+ const spec = (Math.pow(nh, 16) * 0.2 + Math.pow(nh, 80) * 0.6) * gloss;
632
+ const rim = Math.pow(1 - clamp01(n[2]), 3) * 0.14 * gloss;
633
+ return mix(scale(base, 0.28 + 0.78 * dif), WHITE, clamp01(spec + rim));
634
+ };
635
+ }
636
+
637
+ /** Satin: a silk ribbon's light — a wide soft sheen that runs along the
638
+ folds and a deep, saturated shade between them. */
639
+ export function satin(view: View, base: RGB): Shade {
640
+ const L = view.L,
641
+ H = view.H,
642
+ deep = scale(base, 0.42),
643
+ sheen = mix(base, WHITE, 0.55);
644
+ return (n) => {
645
+ const dif = clamp01((dot(n, L) + 0.35) / 1.35);
646
+ const nh = clamp01(dot(n, H));
647
+ const s = Math.pow(nh, 6) * 0.5 + Math.pow(nh, 40) * 0.35;
648
+ return mix(mix(deep, base, dif), sheen, clamp01(s));
649
+ };
650
+ }
651
+
652
+ /** Metal: the colour of what it reflects — a bright sky above, a dark
653
+ floor below, a bright horizon between — tinted, with a hot glint. */
654
+ export function metal(view: View, tint: RGB, dark: RGB, glint: RGB): Shade {
655
+ const H = view.H,
656
+ L = view.L;
657
+ return (n) => {
658
+ /* the reflection of the view about the normal; up is −y */
659
+ const rz = 2 * n[2] * n[2] - 1,
660
+ ry = 2 * n[2] * n[1];
661
+ const up = -ry;
662
+ const sky = clamp01(0.45 + up * 0.85 + rz * 0.1);
663
+ const horizon = Math.exp(-Math.pow((up + 0.12) / 0.16, 2)) * 0.55;
664
+ const nl = clamp01(dot(n, L));
665
+ const c = mix(dark, tint, clamp01(0.1 + sky * 0.72 + nl * 0.3));
666
+ const spec = Math.pow(clamp01(dot(n, H)), 70) + horizon * 0.4;
667
+ return mix(c, glint, clamp01(spec));
668
+ };
669
+ }
670
+ export const gold = (view: View) => metal(view, [255, 200, 88], [86, 50, 8], [255, 247, 220]);
671
+ export const silver = (view: View) => metal(view, [232, 236, 242], [58, 62, 72], [255, 255, 255]);
672
+
673
+ /** A pearl: soft white with a pink-blue play and a small bright glint. */
674
+ export function pearl(view: View): Shade {
675
+ const L = view.L,
676
+ H = view.H;
677
+ return (n) => {
678
+ const dif = clamp01((dot(n, L) + 0.4) / 1.4);
679
+ const base = mix([238, 228, 228], [212, 222, 242], 1 - clamp01(n[2]));
680
+ return mix(scale(base, 0.62 + 0.42 * dif), WHITE, Math.pow(clamp01(dot(n, H)), 40) * 0.8);
681
+ };
682
+ }
683
+
684
+ /** A cut gem's facet: its colour deepening toward the edges, the facets
685
+ turned to the light bright, a white fire on the one that catches it. */
686
+ export function gem(view: View, base: RGB): Shade {
687
+ const L = view.L,
688
+ H = view.H,
689
+ deep = scale(base, 0.22),
690
+ clear = mix(base, WHITE, 0.3);
691
+ return (n) => {
692
+ const c = mix(deep, clear, clamp01(clamp01(n[2]) * 0.75 + dot(n, L) * 0.4));
693
+ return mix(c, WHITE, Math.pow(clamp01(dot(n, H)), 24) * 0.95);
694
+ };
695
+ }
696
+
697
+ /* ── single fills: spheres and cut stones ──────────────────────────── */
698
+
699
+ export type SpriteKind = "gold" | "silver" | "pearl";
700
+ const SPRITE: Record<SpriteKind, [number, string][]> = {
701
+ gold: [
702
+ [0, "#fffbe8"],
703
+ [0.08, "#ffe7a0"],
704
+ [0.3, "#eeb546"],
705
+ [0.62, "#ad7214"],
706
+ [0.87, "#5a3806"],
707
+ [1, "#8c5c18"],
708
+ ],
709
+ silver: [
710
+ [0, "#ffffff"],
711
+ [0.08, "#f3f5f9"],
712
+ [0.32, "#c6ccd6"],
713
+ [0.64, "#7a808c"],
714
+ [0.88, "#3d4149"],
715
+ [1, "#6b717c"],
716
+ ],
717
+ pearl: [
718
+ [0, "#ffffff"],
719
+ [0.1, "#fdf9f7"],
720
+ [0.45, "#ebe4e6"],
721
+ [0.8, "#c6bccb"],
722
+ [1, "#a89db5"],
723
+ ],
724
+ };
725
+
726
+ /** A small sphere as one radial fill: its hot spot where the normal meets
727
+ the half vector, falling to a dark edge with a little reflected light. */
728
+ export function sphereSprite(
729
+ ctx: CanvasRenderingContext2D,
730
+ view: View,
731
+ c: V3,
732
+ rad: number,
733
+ kind: SpriteKind | RGB,
734
+ ) {
735
+ const s = view.toScreen(c);
736
+ const H = view.H;
737
+ const g = ctx.createRadialGradient(
738
+ s[0] + H[0] * rad * 0.85,
739
+ s[1] + H[1] * rad * 0.85,
740
+ 0,
741
+ s[0],
742
+ s[1],
743
+ rad,
744
+ );
745
+ if (typeof kind === "string") for (const [t, col] of SPRITE[kind]) g.addColorStop(t, col);
746
+ else {
747
+ g.addColorStop(0, css(mix(kind, WHITE, 0.8)));
748
+ g.addColorStop(0.1, css(mix(kind, WHITE, 0.3)));
749
+ g.addColorStop(0.35, css(kind));
750
+ g.addColorStop(0.85, css(scale(kind, 0.5)));
751
+ g.addColorStop(1, css(scale(kind, 0.62)));
752
+ }
753
+ ctx.fillStyle = g;
754
+ ctx.beginPath();
755
+ ctx.arc(s[0], s[1], rad, 0, TAU);
756
+ ctx.fill();
757
+ }
758
+
759
+ /**
760
+ * A cut stone set in a surface: `c` its centre, `N` the surface's outward
761
+ * normal, `T` a tangent (object space, unit). An eight-sided girdle, a
762
+ * table on top and the crown facets between, each lit by its own normal.
763
+ */
764
+ export function cutGem(
765
+ ctx: CanvasRenderingContext2D,
766
+ view: View,
767
+ c: V3,
768
+ N: V3,
769
+ T: V3,
770
+ rad: number,
771
+ col: RGB,
772
+ ) {
773
+ const B = cross(N, T);
774
+ const at = (r: number, a: number, h: number): V3 => {
775
+ const x = r * Math.cos(a),
776
+ y = r * Math.sin(a);
777
+ return [
778
+ c[0] + x * T[0] + y * B[0] + h * N[0],
779
+ c[1] + x * T[1] + y * B[1] + h * N[1],
780
+ c[2] + x * T[2] + y * B[2] + h * N[2],
781
+ ];
782
+ };
783
+ const K = 8,
784
+ tr = 0.55,
785
+ th = 0.36 * rad;
786
+ const girdle: V3[] = [],
787
+ table: V3[] = [];
788
+ for (let k = 0; k < K; k++) {
789
+ const a = ((k + 0.5) / K) * TAU;
790
+ girdle.push(at(rad, a, 0));
791
+ table.push(at(rad * tr, a, th));
792
+ }
793
+ const shade = gem(view, col);
794
+ const poly = (pts: V3[], n: V3) => {
795
+ const nv = view.dir(n);
796
+ if (nv[2] <= 0.02) return;
797
+ ctx.fillStyle = css(shade(nv));
798
+ ctx.beginPath();
799
+ pts.forEach((p, i) => {
800
+ const s = view.toScreen(p);
801
+ if (i) ctx.lineTo(s[0], s[1]);
802
+ else ctx.moveTo(s[0], s[1]);
803
+ });
804
+ ctx.closePath();
805
+ ctx.fill();
806
+ ctx.strokeStyle = ctx.fillStyle;
807
+ ctx.stroke();
808
+ };
809
+ ctx.lineWidth = Math.min(0.3, 1 / view.px);
810
+ ctx.lineJoin = "round";
811
+ for (let k = 0; k < K; k++) {
812
+ const g0 = girdle[k],
813
+ g1 = girdle[(k + 1) % K],
814
+ t1 = table[(k + 1) % K],
815
+ t0 = table[k];
816
+ let n = cross(sub(t1, g0), sub(t0, g1));
817
+ if (dot(n, N) < 0) n = [-n[0], -n[1], -n[2]];
818
+ poly([g0, g1, t1, t0], norm(n));
819
+ }
820
+ poly(table, N);
821
+ /* the table's inner light: a lighter kite toward the light */
822
+ const nv = view.dir(N);
823
+ if (nv[2] > 0.1) {
824
+ const s0 = view.toScreen(at(0, 0, th));
825
+ const L = view.L;
826
+ ctx.fillStyle = "rgba(255,255,255,0.3)";
827
+ ctx.beginPath();
828
+ const s1 = view.toScreen(at(rad * tr * 0.8, Math.atan2(-L[1], -L[0]) + 2.2, th));
829
+ const s2 = view.toScreen(at(rad * tr * 0.9, Math.atan2(-L[1], -L[0]) + Math.PI, th));
830
+ const s3 = view.toScreen(at(rad * tr * 0.8, Math.atan2(-L[1], -L[0]) + Math.PI + 0.94, th));
831
+ ctx.moveTo(s0[0], s0[1]);
832
+ ctx.lineTo(s1[0], s1[1]);
833
+ ctx.lineTo(s2[0], s2[1]);
834
+ ctx.lineTo(s3[0], s3[1]);
835
+ ctx.closePath();
836
+ ctx.fill();
837
+ }
838
+ }