@heyramzi/ui 0.0.0-stage → 1.53.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +590 -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 +112 -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 +186 -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 +263 -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,845 @@
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
+ /* The rig. A pose is a handful of numbers — head yaw / pitch / roll, a
3
+ position, squash, how open the eyes are, where they look — and blend
4
+ weights for the three states. A Sim advances a pose through time: each
5
+ state sets targets and wanders around them, runs its own events (a
6
+ flip, a hop, a nod, a blink), and a state change eases from one set of
7
+ targets to the next on a timed curve, so it starts and ends softly. */
8
+
9
+ import type { BotAvatarState, BotAvatarSquashEase } from "./types";
10
+
11
+ export const STATES: BotAvatarState[] = ["default", "working", "sleeping"];
12
+ /* the working state's hop: its period, and the height of the spinning one */
13
+ const HOP_T = 0.68;
14
+ const HOP_SPIN_H = 26;
15
+ /* a flip is that spinning hop on its own: a crouch before it (the squash
16
+ that, in the working state, the previous landing leaves), the hop, and
17
+ the landing's recovery after — measured in hop periods */
18
+ const FLIP_PRE = 0.2;
19
+ const FLIP_POST = 0.66;
20
+ /** The jump's numbers: an idle flip's and a click's. */
21
+ export interface JumpConfig {
22
+ /** how high, in body units (the body is 100 tall) */
23
+ height: number;
24
+ /** seconds in the air */
25
+ time: number;
26
+ /** how much the body stretches in the air, 0–2 */
27
+ stretch: number;
28
+ /** how much it squashes on the ground, before take-off and on landing, 0–2 */
29
+ squash: number;
30
+ /** seconds the landing squash takes, contact to recovered */
31
+ squashTime: number;
32
+ /** the landing squash's shape */
33
+ squashEase: BotAvatarSquashEase;
34
+ /** seconds the body holds its deepest squash on the ground */
35
+ groundTime: number;
36
+ /** how the weight settles through that hold */
37
+ groundEase: BotAvatarSquashEase;
38
+ /** seconds the body takes to rise from its deepest squash back to shape */
39
+ riseTime: number;
40
+ /** how it rises */
41
+ riseEase: BotAvatarSquashEase;
42
+ /** seconds a click's jump takes for its landing squash */
43
+ clickSquashTime: number;
44
+ /** whole turns in the air */
45
+ spin: number;
46
+ /** degrees of lean into it */
47
+ lean: number;
48
+ /** seconds between idle jumps, roughly (±40 %); 0 for none */
49
+ every: number;
50
+ /** when the landing squash begins: seconds before (negative) or after
51
+ touch-down; 0 is the moment of contact */
52
+ land: number;
53
+ }
54
+ export const JUMP_DEFAULTS: JumpConfig = {
55
+ height: HOP_SPIN_H,
56
+ time: HOP_T,
57
+ stretch: 1,
58
+ squash: 1.15,
59
+ squashTime: 0.37,
60
+ squashEase: "pulse",
61
+ groundTime: 0.11,
62
+ groundEase: "pulse",
63
+ riseTime: 0.33,
64
+ riseEase: "pulse",
65
+ clickSquashTime: 0.24,
66
+ spin: 1,
67
+ lean: 6,
68
+ every: 8,
69
+ land: 0,
70
+ };
71
+ /* a click's jump crouches for its squash time before take-off and lands
72
+ with a squash of the same length; an idle jump's crouch is the working
73
+ hop's short one */
74
+ const flipPre = (j: JumpConfig, poked: boolean) => (poked ? j.clickSquashTime : FLIP_PRE * j.time);
75
+ const flipDuration = (j: JumpConfig, poked: boolean) =>
76
+ flipPre(j, poked) +
77
+ j.time +
78
+ SQUASH_PEAK[j.squashEase] * (poked ? j.clickSquashTime : j.squashTime) +
79
+ Math.max(0, j.groundTime) +
80
+ j.riseTime +
81
+ Math.max(0, j.land) +
82
+ 0.05;
83
+ /* where in its time each easing reaches the deepest squash */
84
+ const SQUASH_PEAK: Record<BotAvatarSquashEase, number> = {
85
+ sharp: 0,
86
+ pulse: 2 / 7,
87
+ soft: 0.5,
88
+ bouncy: 0.144,
89
+ };
90
+ /* the rise: how the body comes back from its deepest squash to its own
91
+ shape, 1 → 0 over the rise time. `sharp` lets go at once and eases in
92
+ to rest, `pulse` leaves quickly with a long settle, `soft` eases out of
93
+ the squash and into rest, `bouncy` passes rest into a slight stretch
94
+ and settles back. */
95
+ const risePulse = (v: number, ease: BotAvatarSquashEase) => {
96
+ if (v <= 0) return 1;
97
+ if (v >= 1) return 0;
98
+ switch (ease) {
99
+ case "sharp":
100
+ return (1 - v) * (1 - v);
101
+ case "soft":
102
+ return 0.5 + 0.5 * Math.cos(Math.PI * v);
103
+ case "bouncy":
104
+ return Math.exp(-3.2 * v) * Math.cos(5.4 * v) - v * v * v * 0.026;
105
+ default: {
106
+ const k = 4.2 * v;
107
+ return (1 + k) * Math.exp(-k) - v * v * v * 0.078;
108
+ }
109
+ }
110
+ };
111
+
112
+ /* the weight settling through the hold, as a gain on the deepest squash:
113
+ the same four curves as the landing squash, pressing up to a quarter
114
+ deeper and back to the held depth, so the hold's ends never step */
115
+ const groundShape = (v: number, ease: BotAvatarSquashEase) => 1 + 0.25 * squashPulse(v, ease);
116
+ /* the landing squash over its time, 0–1 → its depth, 0–1 at the peak:
117
+ sharp is all there at contact and eases off; pulse presses quickly and
118
+ recovers without a wobble (a critically damped spring under a short
119
+ push); soft eases in and out; bouncy overshoots into a stretch and
120
+ settles. Each is faded out over the last part so it ends at nothing. */
121
+ const squashPulse = (u: number, ease: BotAvatarSquashEase) => {
122
+ if (u <= 0 || u >= 1) return 0;
123
+ let x: number;
124
+ switch (ease) {
125
+ case "sharp":
126
+ x = (1 - u) * (1 - u);
127
+ break;
128
+ case "soft":
129
+ x = Math.sin(Math.PI * u) ** 2;
130
+ break;
131
+ case "bouncy":
132
+ x = (Math.exp(-3.15 * u) * Math.sin(8.43 * u)) / 0.596;
133
+ break;
134
+ default: {
135
+ const k = 7 * u;
136
+ x = (k * k * Math.exp(2 - k)) / 4;
137
+ }
138
+ }
139
+ const tail = u > 0.85 ? 1 - (u - 0.85) / 0.15 : 1;
140
+ return x * tail * tail * (3 - 2 * tail);
141
+ };
142
+ /* the hop's shaping: squash on the ground, stretch at the top */
143
+ const hopSquash = (a: number) =>
144
+ Math.exp(-Math.pow(Math.min(Math.abs(a), Math.abs(a - 1)) / 0.11, 2));
145
+
146
+ export interface Pose {
147
+ /** radians; yaw > 0 turns the face to the viewer's right, pitch > 0 looks up */
148
+ yaw: number;
149
+ pitch: number;
150
+ roll: number;
151
+ /** body-box units (the 100×100 design space) */
152
+ x: number;
153
+ y: number;
154
+ sx: number;
155
+ sy: number;
156
+ /** 0 shut … 1 open, before blinks */
157
+ eyeOpen: number;
158
+ /** how far each lid is down right now, 0 … 1 */
159
+ blinkL: number;
160
+ blinkR: number;
161
+ lookX: number;
162
+ lookY: number;
163
+ /** the breathing cycle, −1 … 1 */
164
+ breath: number;
165
+ /** working only: how far the eyes have closed into a laugh, 0 … 1 */
166
+ laugh: number;
167
+ /** the cartoon whirl round a spinning body: strength 0 … 1, and where
168
+ its head is, in radians round the ring */
169
+ whirl: number;
170
+ whirlAngle: number;
171
+ /** blend weights: default, working, sleeping — they sum to 1 */
172
+ w: [number, number, number];
173
+ }
174
+
175
+ const DEG = Math.PI / 180;
176
+ const TAU = Math.PI * 2;
177
+ /* how long a state change takes: dozing off is slow, waking a little
178
+ quicker, the rest brisk */
179
+ /* How long a switch takes. Settling down takes longer than getting to
180
+ work: coming back to idle the body has to let go of the hops, so it
181
+ eases out over more than a second. */
182
+ const SWITCH_TO: Record<BotAvatarState, number> = { default: 1.2, working: 0.7, sleeping: 1.4 };
183
+ const SWITCH_FROM_SLEEP = 1;
184
+
185
+ /* Deterministic per-instance randomness (mulberry32). */
186
+ function rng(seed: number): () => number {
187
+ let a = (seed * 0x9e3779b1) >>> 0 || 1;
188
+ return () => {
189
+ a = (a + 0x6d2b79f5) >>> 0;
190
+ let t = a;
191
+ t = Math.imul(t ^ (t >>> 15), t | 1);
192
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
193
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
194
+ };
195
+ }
196
+
197
+ /** Exponential approach: `rate` per second, frame-rate independent. */
198
+ function approach(cur: number, target: number, rate: number, dt: number): number {
199
+ return cur + (target - cur) * (1 - Math.exp(-rate * dt));
200
+ }
201
+
202
+ const easeInOut = (p: number) => (p < 0.5 ? 4 * p * p * p : 1 - Math.pow(-2 * p + 2, 3) / 2);
203
+ /* gentler at both ends than the cubic: for drifting off and waking */
204
+ const easeSine = (p: number) => 0.5 - 0.5 * Math.cos(Math.PI * p);
205
+
206
+ /* A value that drifts: picks a new target inside its range every hold,
207
+ and eases toward it. Ranges change with the state; the value never
208
+ jumps. */
209
+ class Wander {
210
+ value = 0;
211
+ private vel = 0;
212
+ private target = 0;
213
+ private next = 0;
214
+ /* A channel that picks a new target now and then and moves to it. The
215
+ head channels move as a lightly damped spring — the turn starts and
216
+ ends softly, the way a head does — while the eyes dart with an
217
+ exponential approach, the way eyes do. */
218
+ constructor(
219
+ private rand: () => number,
220
+ public amp: number,
221
+ public holdMin: number,
222
+ public holdMax: number,
223
+ public rate: number,
224
+ private spring = false,
225
+ ) {}
226
+ update(t: number, dt: number) {
227
+ if (t >= this.next) {
228
+ this.target = (this.rand() * 2 - 1) * this.amp;
229
+ this.next = t + this.holdMin + this.rand() * (this.holdMax - this.holdMin);
230
+ }
231
+ if (this.spring) {
232
+ const w = this.rate * 1.6,
233
+ z = 0.9;
234
+ this.vel += (w * w * (this.target - this.value) - 2 * z * w * this.vel) * dt;
235
+ this.value += this.vel * dt;
236
+ } else this.value = approach(this.value, this.target, this.rate, dt);
237
+ }
238
+ /** aim at a value and stay there: the rig picks the next one */
239
+ aim(v: number) {
240
+ this.target = v;
241
+ this.next = Infinity;
242
+ }
243
+ set(amp: number, holdMin: number, holdMax: number, rate: number) {
244
+ this.amp = amp;
245
+ this.holdMin = holdMin;
246
+ this.holdMax = holdMax;
247
+ this.rate = rate;
248
+ this.next = 0;
249
+ }
250
+ }
251
+
252
+ /* A one-shot event: progress 0 → 1 over its duration, then idle at -1. */
253
+ class Event {
254
+ p = -1;
255
+ constructor(public duration: number) {}
256
+ fire() {
257
+ this.p = 0;
258
+ }
259
+ get active() {
260
+ return this.p >= 0;
261
+ }
262
+ update(dt: number) {
263
+ if (this.p < 0) return;
264
+ this.p += dt / this.duration;
265
+ if (this.p >= 1) this.p = -1;
266
+ }
267
+ }
268
+
269
+ /* The idle gaze: how far the head swings, and how long it stays. The
270
+ sideways reach is the big one — a look to a corner reads as a turn of
271
+ the head, with the rise and a slight tilt going along. */
272
+ const GAZE_YAW = 35 * DEG;
273
+ const GAZE_PITCH = 14 * DEG;
274
+ const GAZE_ROLL = 3.2 * DEG;
275
+ const GAZE_HOLD_MIN = 2.6;
276
+ const GAZE_HOLD_MAX = 4.4;
277
+
278
+ /* Resting targets per state. Everything the wanderers and events add sits
279
+ on top of these. */
280
+ interface Rest {
281
+ pitch: number;
282
+ roll: number;
283
+ y: number;
284
+ lookX: number;
285
+ lookY: number;
286
+ }
287
+ const REST: Record<BotAvatarState, Rest> = {
288
+ default: { pitch: 0, roll: 0, y: 0, lookX: 0, lookY: 0 },
289
+ working: { pitch: 5 * DEG, roll: 0, y: 0, lookX: 0, lookY: 0 },
290
+ sleeping: { pitch: -16 * DEG, roll: 6 * DEG, y: 3, lookX: 0, lookY: 1 },
291
+ };
292
+
293
+ export class Sim {
294
+ readonly pose: Pose = {
295
+ yaw: 0,
296
+ pitch: 0,
297
+ roll: 0,
298
+ x: 0,
299
+ y: 0,
300
+ sx: 1,
301
+ sy: 1,
302
+ eyeOpen: 1,
303
+ blinkL: 0,
304
+ blinkR: 0,
305
+ lookX: 0,
306
+ lookY: 0,
307
+ breath: 0,
308
+ laugh: 0,
309
+ whirl: 0,
310
+ whirlAngle: 0,
311
+ w: [1, 0, 0],
312
+ };
313
+ state: BotAvatarState = "default";
314
+
315
+ private rand: () => number;
316
+ private t = 0;
317
+ /* a state change: the weights it started from and its progress */
318
+ private wFrom: [number, number, number] = [1, 0, 0];
319
+ private tr = 1;
320
+ private trDuration = 1.2;
321
+ private yawW: Wander;
322
+ private pitchW: Wander;
323
+ private rollW: Wander;
324
+ private lookXW: Wander;
325
+ private lookYW: Wander;
326
+ private blink = new Event(0.17);
327
+ private blinkAt: number;
328
+ private blinkAgain = false;
329
+ /* −1 left eye only, 1 right eye only, 0 both */
330
+ private dart = new Event(0.12);
331
+ private dartAt: number;
332
+ private dartX = 0;
333
+ private dartY = 0;
334
+ private flip = new Event(flipDuration(JUMP_DEFAULTS, false));
335
+ private flipPoked = false;
336
+ private jump: JumpConfig = { ...JUMP_DEFAULTS };
337
+ private flipAt: number;
338
+ private flipSide = 1;
339
+ private nod = new Event(1.7);
340
+ private nodAt: number;
341
+ private hopPhase = 0;
342
+ private hopCount = 0;
343
+ /** the hops' gain: the working weight while in the state, then held so
344
+ a hop under way finishes whole when the state is left */
345
+ private hopGain = 0;
346
+ private laughEv = new Event(0.8);
347
+ private laughAt: number;
348
+ /* the jelly: a damped spring driven by how fast the head turns, so a
349
+ sweep stretches the body and it wobbles back */
350
+ private prevYaw = 0;
351
+ private jelly = 0;
352
+ private jellyV = 0;
353
+ /** how far the eyes run ahead of a head turn */
354
+ private gazeLead = 0;
355
+ /** the idle gaze: where the head is looking, and when it moves on */
356
+ private gazeDir: [number, number] = [0, 0];
357
+ private gazeAt = 0;
358
+ /** how far it turns to the side, 1 as the gaze has it */
359
+ private turnK = 1;
360
+ /** the breathing cycle's phase, in turns */
361
+ private breathPhase = 0;
362
+ /* the pointer, as an offset from the head in head-widths, and how much
363
+ to follow it — both smoothed */
364
+ private ptrX = 0;
365
+ private ptrY = 0;
366
+ private ptrS = 0;
367
+ private ptrTargetX = 0;
368
+ private ptrTargetY = 0;
369
+ private ptrTargetS = 0;
370
+ /* the smoothed yaw the head is turning to on its own */
371
+ private baseYaw = 0;
372
+
373
+ constructor(seed: number, state: BotAvatarState = "default") {
374
+ this.rand = rng(Math.floor(seed * 1e6) + 1);
375
+ const r = this.rand;
376
+ this.yawW = new Wander(r, 36 * DEG, 1.1, 2.6, 3, true);
377
+ this.pitchW = new Wander(r, 10 * DEG, 1.1, 2.6, 2.6, true);
378
+ this.rollW = new Wander(r, 5 * DEG, 1.6, 3.2, 2, true);
379
+ this.lookXW = new Wander(r, 3.6, 0.5, 2, 14);
380
+ this.lookYW = new Wander(r, 2.4, 0.5, 2, 14);
381
+ /* every instance starts somewhere else in its loops */
382
+ this.t = r() * 10;
383
+ this.hopPhase = r();
384
+ this.breathPhase = r();
385
+ /* event timers count from that start, so nothing fires on the first tick */
386
+ this.blinkAt = this.t + 1 + r() * 3;
387
+ this.flipAt = this.nextFlip(this.t, 1);
388
+ this.nodAt = this.t + 3 + r() * 4;
389
+ this.dartAt = this.t + 1 + r() * 2;
390
+ this.laughAt = this.t + 0.6 + r() * 1.5;
391
+ this.setState(state, true);
392
+ }
393
+
394
+ setState(next: BotAvatarState, immediate = false) {
395
+ if (next === this.state && !immediate) return;
396
+ const from = this.state;
397
+ this.state = next;
398
+ const w = this.pose.w;
399
+ if (immediate) {
400
+ for (let i = 0; i < 3; i++) w[i] = STATES[i] === next ? 1 : 0;
401
+ this.tr = 1;
402
+ } else {
403
+ this.wFrom = [w[0], w[1], w[2]];
404
+ this.tr = 0;
405
+ this.trDuration = from === "sleeping" ? SWITCH_FROM_SLEEP : SWITCH_TO[next];
406
+ }
407
+ switch (next) {
408
+ case "default":
409
+ /* the head takes its time: it holds a place, then swings to the
410
+ next over about a second — no short quick jabs. The gaze picks
411
+ where, so these carry the motion and their amplitude is unused. */
412
+ this.yawW.set(GAZE_YAW, 2.6, 5.4, 2);
413
+ this.pitchW.set(GAZE_PITCH, 2.8, 5.8, 1.8);
414
+ this.rollW.set(GAZE_ROLL, 3.4, 6.6, 1.5);
415
+ this.gazeAt = 0;
416
+ this.gazeDir = [0, 0];
417
+ /* the eyes keep their quickness: they dart, the head follows */
418
+ this.lookXW.set(3.6, 0.6, 2.2, 13);
419
+ this.lookYW.set(2.4, 0.6, 2.2, 13);
420
+ this.flipAt = this.nextFlip(this.t, 0.6);
421
+ break;
422
+ case "working":
423
+ this.yawW.set(16 * DEG, 0.9, 1.8, 4);
424
+ this.pitchW.set(3 * DEG, 1.2, 2.4, 3);
425
+ this.rollW.set(0, 1, 2, 3);
426
+ this.lookXW.set(2, 0.5, 1.2, 12);
427
+ this.lookYW.set(1, 0.5, 1.2, 12);
428
+ this.hopPhase = 0;
429
+ this.hopCount = 0;
430
+ this.laughAt = this.t + 0.5 + this.rand() * 1.2;
431
+ break;
432
+ case "sleeping":
433
+ this.yawW.set(7 * DEG, 3, 6, 0.7);
434
+ this.pitchW.set(3 * DEG, 3, 6, 0.7);
435
+ this.rollW.set(2 * DEG, 3, 6, 0.6);
436
+ this.lookXW.set(0, 2, 4, 2);
437
+ this.lookYW.set(0, 2, 4, 2);
438
+ this.nodAt = this.t + 2.5 + this.rand() * 4;
439
+ break;
440
+ }
441
+ }
442
+
443
+ /** Where the pointer is, relative to the head (−1 … 1 across a head
444
+ width), and how strongly to follow it (0 lets go). */
445
+ setPointer(x: number, y: number, strength: number) {
446
+ this.ptrTargetX = Math.max(-1.2, Math.min(1.2, x));
447
+ this.ptrTargetY = Math.max(-1.2, Math.min(1.2, y));
448
+ this.ptrTargetS = Math.max(0, Math.min(1, strength));
449
+ }
450
+
451
+ /** A hop and a full turn, right now, whatever the state. */
452
+ poke() {
453
+ if (this.flip.active && this.flip.p < 0.6) return;
454
+ this.flipPoked = true;
455
+ this.flip.duration = flipDuration(this.jump, true);
456
+ this.flipSide = this.rand() < 0.5 ? -1 : 1;
457
+ this.flip.fire();
458
+ this.flipAt = this.nextFlip(this.t, 1.1);
459
+ }
460
+
461
+ /** How far the head turns to the side while idle: 1 as the gaze has
462
+ it, 0 keeps it facing forward. */
463
+ setTurn(k: number) {
464
+ const next = Math.max(0, k);
465
+ if (next === this.turnK) return;
466
+ this.turnK = next;
467
+ /* take the new reach at once rather than at the next place */
468
+ if (this.state === "default") this.gazeAt = 0;
469
+ }
470
+
471
+ /** The jump's numbers; any subset. */
472
+ setJump(j: Partial<JumpConfig>) {
473
+ const every = this.jump.every;
474
+ Object.assign(this.jump, j);
475
+ /* a new interval takes effect at once, so `every: 0` stops the next
476
+ jump that was already due and a first value starts the clock */
477
+ if (j.every !== undefined && j.every !== every) this.flipAt = this.nextFlip(this.t, 1);
478
+ }
479
+
480
+ /* Where the head looks next. From a corner it mostly swings straight
481
+ across to the opposite one — top right, stay, bottom left — now and
482
+ then only sideways, or back to the middle for a beat. */
483
+ private nextGaze(): [number, number] {
484
+ const r = this.rand;
485
+ const [px, py] = this.gazeDir;
486
+ if (px !== 0 || py !== 0) {
487
+ const p = r();
488
+ if (p < 0.66) return [-px, -py];
489
+ if (p < 0.85) return [-px, py];
490
+ return [0, 0];
491
+ }
492
+ const corners: Array<[number, number]> = [
493
+ [1, -1],
494
+ [-1, 1],
495
+ [-1, -1],
496
+ [1, 1],
497
+ ];
498
+ return corners[Math.floor(r() * corners.length)];
499
+ }
500
+
501
+ /** when the next idle jump is due: `every` seconds, give or take 40 % */
502
+ private nextFlip(t: number, k: number) {
503
+ const every = this.jump.every;
504
+ return every > 0 ? t + every * k * (0.625 + this.rand() * 0.75) : Infinity;
505
+ }
506
+
507
+ /** Advance by `dt` seconds (already scaled by the speed). */
508
+ update(dt: number) {
509
+ dt = Math.min(dt, 0.05);
510
+ this.t += dt;
511
+ const t = this.t;
512
+ const p = this.pose;
513
+ const w = p.w;
514
+
515
+ /* the state change eases from the weights it started with to the new
516
+ state's on an S-curve: soft start, soft finish, no creeping tail */
517
+ if (this.tr < 1) {
518
+ this.tr = Math.min(1, this.tr + dt / this.trDuration);
519
+ /* a sine ease: no kick at either end of a switch */
520
+ const e = easeSine(this.tr);
521
+ for (let i = 0; i < 3; i++) {
522
+ const target = STATES[i] === this.state ? 1 : 0;
523
+ w[i] = this.wFrom[i] + (target - this.wFrom[i]) * e;
524
+ }
525
+ }
526
+ const [wd, ww, ws] = w;
527
+
528
+ /* rest targets, blended */
529
+ const rest = { pitch: 0, roll: 0, y: 0, lookX: 0, lookY: 0 };
530
+ for (let i = 0; i < 3; i++) {
531
+ const r = REST[STATES[i]];
532
+ rest.pitch += r.pitch * w[i];
533
+ rest.roll += r.roll * w[i];
534
+ rest.y += r.y * w[i];
535
+ rest.lookX += r.lookX * w[i];
536
+ rest.lookY += r.lookY * w[i];
537
+ }
538
+
539
+ /* the idle gaze: a place to look, a while to stay, then the swing to
540
+ the next — the wanderers carry it on their own springs */
541
+ if (this.state === "default" && t >= this.gazeAt) {
542
+ const [gx, gy] = this.nextGaze();
543
+ this.gazeDir = [gx, gy];
544
+ const reach = 0.84 + this.rand() * 0.16;
545
+ this.yawW.aim(gx * GAZE_YAW * reach * this.turnK);
546
+ this.pitchW.aim(gy * GAZE_PITCH * reach);
547
+ /* the tilt goes with the turn, so it follows it */
548
+ this.rollW.aim(gx * GAZE_ROLL * reach * this.turnK);
549
+ this.gazeAt = t + GAZE_HOLD_MIN + this.rand() * (GAZE_HOLD_MAX - GAZE_HOLD_MIN);
550
+ }
551
+
552
+ /* wander */
553
+ this.yawW.update(t, dt);
554
+ this.pitchW.update(t, dt);
555
+ this.rollW.update(t, dt);
556
+ this.lookXW.update(t, dt);
557
+ this.lookYW.update(t, dt);
558
+
559
+ /* following the pointer: the eyes lead, the head turns after them,
560
+ and the wander quietens while it lasts */
561
+ this.ptrS = approach(this.ptrS, this.ptrTargetS, 8, dt);
562
+ this.ptrX = approach(this.ptrX, this.ptrTargetX, 14, dt);
563
+ this.ptrY = approach(this.ptrY, this.ptrTargetY, 14, dt);
564
+ const ps = this.ptrS;
565
+ const quiet = 1 - 0.75 * ps;
566
+
567
+ /* the base pose: the blended rest plus the smoothed wander and the
568
+ pointer's pull — every term is already continuous */
569
+ this.baseYaw = approach(
570
+ this.baseYaw,
571
+ this.yawW.value * quiet + 22 * DEG * this.ptrX * ps,
572
+ 5,
573
+ dt,
574
+ );
575
+ const basePitch = rest.pitch + this.pitchW.value * quiet - 12 * DEG * this.ptrY * ps;
576
+ const baseRoll = rest.roll + this.rollW.value * quiet;
577
+ const baseY = rest.y;
578
+ const baseLookX = rest.lookX + this.lookXW.value * quiet + 4.5 * this.ptrX * ps;
579
+ const baseLookY = rest.lookY + this.lookYW.value * quiet + 3 * this.ptrY * ps;
580
+
581
+ /* ── events ── */
582
+ let spin = 0,
583
+ hopY = 0,
584
+ sx = 1,
585
+ sy = 1,
586
+ pitchAdd = 0,
587
+ rollAdd = 0,
588
+ blinkClose = 0,
589
+ lookXAdd = 0,
590
+ lookYAdd = 0,
591
+ laugh = 0;
592
+ let whirl = 0,
593
+ whirlAngle = 0;
594
+ /* the whirl: not there until the turn is visibly under way, gone
595
+ before the landing */
596
+ const smooth = (a: number, b: number, v: number) => {
597
+ const x = Math.min(1, Math.max(0, (v - a) / (b - a)));
598
+ return x * x * (3 - 2 * x);
599
+ };
600
+ const envelope = (q: number) => smooth(0.1, 0.26, q) * (1 - smooth(0.66, 0.9, q));
601
+ /* the ring's own travel: fast and steady from the first frame, with a
602
+ little of the body's own easing on top, so it is moving the moment
603
+ it shows */
604
+ const ringAngle = (q: number) => TAU * (1.5 * q + 0.9 * easeInOut(q));
605
+
606
+ /* blinks: idle and working blink; a double blink now and then */
607
+ if (t >= this.blinkAt && !this.blink.active && wd + ww > 0.5) {
608
+ this.blink.fire();
609
+ this.blinkAgain = !this.blinkAgain && this.rand() < 0.22;
610
+ this.blinkAt = t + (this.blinkAgain ? 0.28 : 2.2 + this.rand() * 2.6);
611
+ }
612
+ this.blink.update(dt);
613
+ if (this.blink.active) blinkClose = Math.sin(Math.PI * this.blink.p);
614
+
615
+ /* eye darts: a quick glance to the side and back, between the slower
616
+ looks — the eyes have a life of their own */
617
+ if (t >= this.dartAt && !this.dart.active && wd + ww > 0.5) {
618
+ this.dart.fire();
619
+ this.dartX = (this.rand() * 2 - 1) * 4;
620
+ this.dartY = (this.rand() * 2 - 1) * 2;
621
+ this.dart.duration = 0.25 + this.rand() * 0.45;
622
+ this.dartAt = t + 1.2 + this.rand() * 2.6;
623
+ }
624
+ this.dart.update(dt);
625
+ if (this.dart.active) {
626
+ const q = this.dart.p;
627
+ /* snap out, hold, snap back */
628
+ const hold = q < 0.15 ? q / 0.15 : q > 0.8 ? (1 - q) / 0.2 : 1;
629
+ lookXAdd += this.dartX * hold * (wd + ww);
630
+ lookYAdd += this.dartY * hold * (wd + ww);
631
+ }
632
+
633
+ /* idle: a full turn now and then, with a jump */
634
+ if (this.state === "default" && t >= this.flipAt && !this.flip.active) {
635
+ this.flipPoked = false;
636
+ this.flip.duration = flipDuration(this.jump, false);
637
+ this.flipSide = this.rand() < 0.5 ? -1 : 1;
638
+ this.flip.fire();
639
+ this.flipAt = this.nextFlip(t, 1);
640
+ }
641
+ this.flip.update(dt);
642
+ if (this.flip.active) {
643
+ const J = this.jump;
644
+ /* the hop's own phase: below 0 the crouch, above 1 the recovery */
645
+ const preS = flipPre(J, this.flipPoked);
646
+ const a = (this.flip.p * this.flip.duration - preS) / J.time;
647
+ const q = Math.min(1, Math.max(0, a));
648
+ const arc = Math.sin(Math.PI * q);
649
+ /* whole turns, eased in and out, no overshoot to snap back from */
650
+ spin += TAU * J.spin * easeInOut(q);
651
+ hopY -= J.height * arc;
652
+ /* the crouch before take-off; then the landing squash from `land`
653
+ seconds round the moment of contact — before it, the body braces
654
+ for the ground; after it, it gives late */
655
+ const tl = (a - 1) * J.time - J.land;
656
+ const squashTime = this.flipPoked ? J.clickSquashTime : J.squashTime;
657
+ /* the crouch: a click's builds over its squash time, an idle jump's
658
+ is the hop's short bump; then the hop, then the landing squash */
659
+ const crouch = (u: number) => (this.flipPoked ? u * u * (3 - 2 * u) : hopSquash(u - 1));
660
+ /* the landing in three parts: the press down to the deepest squash
661
+ in the squash easing's own shape, the hold on the ground, then
662
+ the rise back to the body's shape — each with its own time and
663
+ easing, and all three meeting at the same depth, so nothing steps */
664
+ const hold = Math.max(0, J.groundTime);
665
+ const peakT = SQUASH_PEAK[J.squashEase] * squashTime;
666
+ const rise = tl - peakT - hold;
667
+ const depth =
668
+ tl <= peakT
669
+ ? squashPulse(tl / squashTime, J.squashEase)
670
+ : rise <= 0
671
+ ? groundShape((tl - peakT) / hold, J.groundEase)
672
+ : risePulse(rise / J.riseTime, J.riseEase);
673
+ const land =
674
+ (a < 0
675
+ ? crouch(Math.max(0, 1 + (a * J.time) / preS))
676
+ : tl > 0
677
+ ? depth
678
+ : a < 0.2
679
+ ? hopSquash(a)
680
+ : 0) * J.squash;
681
+ sx += 0.16 * land - 0.06 * arc * J.stretch;
682
+ sy += -0.18 * land + 0.09 * arc * J.stretch;
683
+ /* lean into it; eyes shut for a turn */
684
+ rollAdd += this.flipSide * J.lean * DEG * arc;
685
+ if (J.spin > 0) {
686
+ laugh = Math.max(laugh, arc);
687
+ whirl = Math.max(whirl, envelope(q));
688
+ whirlAngle = ringAngle(q);
689
+ }
690
+ }
691
+
692
+ /* working: hops all the time; every third one spins. Leaving the
693
+ state, the hop under way keeps the strength it had — it flies its
694
+ whole arc, finishes its turn and lands — and that last landing is
695
+ not the rhythm's quick bump but a jump's proper one: the hold on
696
+ the ground and the rise back, from the depth the hop reached. */
697
+ const J = this.jump;
698
+ const exitHold = Math.max(0, J.groundTime);
699
+ const exitFor = exitHold + J.riseTime;
700
+ if (this.state === "working") this.hopGain = ww;
701
+ if (this.hopGain > 0.02 && (this.state === "working" || this.hopPhase > 0)) {
702
+ this.hopPhase += dt / HOP_T;
703
+ if (this.hopPhase >= 1) {
704
+ if (this.state === "working") {
705
+ this.hopPhase -= 1;
706
+ this.hopCount += 1;
707
+ } else if ((this.hopPhase - 1) * HOP_T >= exitFor) {
708
+ /* the last hop has landed and settled */
709
+ this.hopPhase = 0;
710
+ this.hopGain = 0;
711
+ }
712
+ }
713
+ const g = this.hopGain;
714
+ /* past 1 the body is down and only the landing is still playing */
715
+ const q = Math.min(1, this.hopPhase);
716
+ const arc = Math.sin(Math.PI * q);
717
+ const spinning = this.hopCount % 3 === 2;
718
+ const h = spinning ? HOP_SPIN_H : 18;
719
+ hopY -= h * arc * g;
720
+ /* squash on landing, stretch at the top */
721
+ const exitT =
722
+ this.state !== "working" && this.hopPhase > 1 ? (this.hopPhase - 1) * HOP_T : -1;
723
+ const land =
724
+ exitT < 0
725
+ ? hopSquash(this.hopPhase)
726
+ : exitT < exitHold
727
+ ? groundShape(exitT / exitHold, J.groundEase)
728
+ : risePulse((exitT - exitHold) / J.riseTime, J.riseEase);
729
+ sx += (0.16 * land - 0.06 * arc) * g;
730
+ sy += (-0.18 * land + 0.09 * arc) * g;
731
+ if (spinning) {
732
+ spin += TAU * easeInOut(q) * g;
733
+ /* eyes shut for the spin */
734
+ laugh = Math.max(laugh, arc * g);
735
+ if (envelope(q) * g > whirl) {
736
+ whirl = envelope(q) * g;
737
+ whirlAngle = ringAngle(q);
738
+ }
739
+ }
740
+ /* lean into each hop, alternating sides */
741
+ rollAdd += (this.hopCount % 2 === 0 ? 1 : -1) * 6 * DEG * arc * g;
742
+ }
743
+
744
+ /* working: now and then a laugh shuts the eyes into arcs, then they
745
+ open again */
746
+ if (this.state === "working" && t >= this.laughAt && !this.laughEv.active) {
747
+ this.laughEv.fire();
748
+ this.laughEv.duration = 0.6 + this.rand() * 0.5;
749
+ this.laughAt = t + 1.6 + this.rand() * 2.2;
750
+ }
751
+ this.laughEv.update(dt);
752
+ if (this.laughEv.active) {
753
+ const q = this.laughEv.p;
754
+ /* quick shut, hold, quick open */
755
+ laugh = Math.max(laugh, q < 0.18 ? q / 0.18 : q > 0.78 ? (1 - q) / 0.22 : 1);
756
+ }
757
+
758
+ /* sleeping: the head drops, then jerks back up */
759
+ if (this.state === "sleeping" && t >= this.nodAt && !this.nod.active) {
760
+ this.nod.fire();
761
+ this.nodAt = t + 4 + this.rand() * 4;
762
+ }
763
+ this.nod.update(dt);
764
+ if (this.nod.active) {
765
+ const q = this.nod.p;
766
+ /* slow slide down, quick recovery */
767
+ const dip = q < 0.72 ? easeInOut(q / 0.72) : 1 - easeInOut((q - 0.72) / 0.28);
768
+ pitchAdd -= 13 * DEG * dip * ws;
769
+ }
770
+
771
+ /* breathing, always, deeper and slower asleep. The phase is integrated
772
+ so the period can change with the state without the cycle jumping */
773
+ this.breathPhase += dt / (3.6 + 1.2 * ws);
774
+ const breath = Math.sin(this.breathPhase * TAU);
775
+ p.breath = breath;
776
+ sx += breath * (0.008 + 0.014 * ws);
777
+ sy += breath * (0.012 + 0.02 * ws);
778
+ const bob = Math.sin((t * TAU) / 3.4) * 2 * (1 - ws);
779
+
780
+ /* ── compose ── */
781
+ p.yaw = this.baseYaw + spin;
782
+
783
+ /* the jelly: the faster the head turns, the more the body stretches
784
+ along the turn, on a spring that overshoots and settles. A flip's
785
+ spin is left out: that is a jump, and the landing spring handles it. */
786
+ let dyaw = this.baseYaw - this.prevYaw;
787
+ dyaw = ((((dyaw + Math.PI) % TAU) + TAU) % TAU) - Math.PI;
788
+ this.prevYaw = this.baseYaw;
789
+ const rate = dt > 0 ? Math.abs(dyaw) / dt : 0;
790
+ /* the eyes lead a head turn, the way a gaze shift goes: they run
791
+ ahead of it and come back as the head settles */
792
+ const leadTarget = dt > 0 ? Math.max(-2.2, Math.min(2.2, (dyaw / dt) * 2.4)) : 0;
793
+ this.gazeLead = approach(this.gazeLead, leadTarget, 9, dt);
794
+ const jellyTarget = Math.min(0.22, 0.055 * rate);
795
+ const omega = 16,
796
+ zeta = 0.45;
797
+ this.jellyV +=
798
+ (omega * omega * (jellyTarget - this.jelly) - 2 * zeta * omega * this.jellyV) * dt;
799
+ this.jelly += this.jellyV * dt;
800
+ const jelly = Math.max(-0.08, Math.min(0.28, this.jelly)) * 0.6;
801
+ sx *= 1 + jelly;
802
+ sy *= 1 - 0.55 * jelly;
803
+
804
+ p.pitch = basePitch + pitchAdd;
805
+ p.roll = baseRoll + rollAdd;
806
+ p.x = 0;
807
+ p.y = baseY + hopY + bob;
808
+ p.sx = sx;
809
+ p.sy = sy;
810
+ /* the lids: the sleeping state closes them through its own weight in
811
+ the renderer, so here the eyes stay open apart from blinks */
812
+ p.eyeOpen = 1;
813
+ p.laugh = approach(p.laugh, laugh, 30, dt);
814
+ p.blinkL = blinkClose;
815
+ p.blinkR = blinkClose;
816
+ p.lookX = baseLookX + lookXAdd + this.gazeLead;
817
+ p.lookY = baseLookY + lookYAdd;
818
+ p.whirl = whirl;
819
+ p.whirlAngle = whirlAngle;
820
+ }
821
+ }
822
+
823
+ /** The still pose of a state, for reduced motion and the first paint. */
824
+ export function restPose(state: BotAvatarState): Pose {
825
+ const r = REST[state];
826
+ return {
827
+ yaw: 0,
828
+ pitch: r.pitch,
829
+ roll: r.roll,
830
+ x: 0,
831
+ y: r.y,
832
+ sx: 1,
833
+ sy: 1,
834
+ eyeOpen: 1,
835
+ blinkL: 0,
836
+ blinkR: 0,
837
+ lookX: r.lookX,
838
+ lookY: r.lookY,
839
+ breath: 0,
840
+ laugh: 0,
841
+ whirl: 0,
842
+ whirlAngle: 0,
843
+ w: STATES.map((s) => (s === state ? 1 : 0)) as [number, number, number],
844
+ };
845
+ }