@mova-ui/react 0.4.1 → 0.4.5

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 (478) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +483 -830
  3. package/dist/styles.css +116 -1
  4. package/dist/ui/ai/ai-context-meter.d.ts +2 -1
  5. package/dist/ui/ai/ai-sources.d.ts +2 -1
  6. package/dist/ui/ai/ai-task-list.d.ts +2 -1
  7. package/dist/ui/ai/ai-tool-call.d.ts +2 -1
  8. package/package.json +1 -1
  9. package/registry/1.json +1 -1
  10. package/registry/10.json +1 -1
  11. package/registry/11.json +1 -1
  12. package/registry/12.json +1 -1
  13. package/registry/13.json +1 -1
  14. package/registry/14.json +1 -1
  15. package/registry/15.json +1 -1
  16. package/registry/16.json +1 -1
  17. package/registry/17.json +1 -1
  18. package/registry/18.json +1 -1
  19. package/registry/19.json +1 -1
  20. package/registry/2.json +1 -1
  21. package/registry/20.json +1 -1
  22. package/registry/21.json +1 -1
  23. package/registry/22.json +1 -1
  24. package/registry/23.json +1 -1
  25. package/registry/24.json +1 -1
  26. package/registry/25.json +1 -1
  27. package/registry/26.json +1 -1
  28. package/registry/27.json +1 -1
  29. package/registry/28.json +1 -1
  30. package/registry/29.json +1 -1
  31. package/registry/3.json +1 -1
  32. package/registry/30.json +1 -1
  33. package/registry/31.json +1 -1
  34. package/registry/32.json +1 -1
  35. package/registry/33.json +1 -1
  36. package/registry/34.json +1 -1
  37. package/registry/35.json +1 -1
  38. package/registry/36.json +1 -1
  39. package/registry/37.json +1 -1
  40. package/registry/4.json +1 -1
  41. package/registry/5.json +1 -1
  42. package/registry/6.json +1 -1
  43. package/registry/7.json +1 -1
  44. package/registry/8.json +1 -1
  45. package/registry/9.json +1 -1
  46. package/registry/ab-segmented-tabs.json +1 -1
  47. package/registry/accordion-blind.json +1 -1
  48. package/registry/accordion-loader.json +1 -1
  49. package/registry/ai-approval-card.json +2 -2
  50. package/registry/ai-code-block.json +2 -2
  51. package/registry/ai-context-meter.json +2 -2
  52. package/registry/ai-fine-tune-card.json +2 -2
  53. package/registry/ai-prompt-bar.json +2 -2
  54. package/registry/ai-sidebar-nav.json +2 -2
  55. package/registry/ai-sources.json +2 -2
  56. package/registry/ai-suggestions.json +1 -1
  57. package/registry/ai-task-list.json +2 -2
  58. package/registry/ai-tool-call.json +2 -2
  59. package/registry/analyzing-image.json +1 -1
  60. package/registry/animated-sidebar.json +1 -1
  61. package/registry/animated-tags.json +1 -1
  62. package/registry/app-icon-load.json +1 -1
  63. package/registry/apple-breathe.json +1 -1
  64. package/registry/apple-equalizer.json +1 -1
  65. package/registry/apple-icon-morph.json +1 -1
  66. package/registry/apple-pulse-dots.json +1 -1
  67. package/registry/apple-scale-pulse.json +1 -1
  68. package/registry/apple-sound-wave.json +1 -1
  69. package/registry/apple-text-reveal.json +1 -1
  70. package/registry/apple-unlock.json +1 -1
  71. package/registry/arc-tracer.json +1 -1
  72. package/registry/bar-cascade.json +1 -1
  73. package/registry/bar-sweep.json +1 -1
  74. package/registry/blur-text.json +1 -1
  75. package/registry/blur-up-char.json +1 -1
  76. package/registry/blur-up-word.json +1 -1
  77. package/registry/bobbing-dots.json +1 -1
  78. package/registry/bookmark-corner-peel.json +1 -1
  79. package/registry/bookmark-save-pill.json +1 -1
  80. package/registry/bookmark-toggle.json +1 -1
  81. package/registry/border-trail-loading.json +1 -1
  82. package/registry/bounce-dots.json +1 -1
  83. package/registry/bounce-in-char.json +1 -1
  84. package/registry/bouncing-bars.json +1 -1
  85. package/registry/bouncing-dots.json +1 -1
  86. package/registry/bouncing-lines.json +1 -1
  87. package/registry/bouncing-square.json +1 -1
  88. package/registry/breathe-ring.json +1 -1
  89. package/registry/breathing-glow.json +1 -1
  90. package/registry/breathing-square.json +1 -1
  91. package/registry/btn-1.json +1 -1
  92. package/registry/btn-10.json +1 -1
  93. package/registry/btn-11.json +1 -1
  94. package/registry/btn-12.json +1 -1
  95. package/registry/btn-13.json +1 -1
  96. package/registry/btn-14.json +1 -1
  97. package/registry/btn-15.json +1 -1
  98. package/registry/btn-16.json +1 -1
  99. package/registry/btn-17.json +1 -1
  100. package/registry/btn-18.json +1 -1
  101. package/registry/btn-19.json +1 -1
  102. package/registry/btn-2.json +1 -1
  103. package/registry/btn-20.json +1 -1
  104. package/registry/btn-21.json +1 -1
  105. package/registry/btn-22.json +1 -1
  106. package/registry/btn-23.json +1 -1
  107. package/registry/btn-24.json +1 -1
  108. package/registry/btn-25.json +1 -1
  109. package/registry/btn-26.json +1 -1
  110. package/registry/btn-27.json +1 -1
  111. package/registry/btn-28.json +1 -1
  112. package/registry/btn-29.json +1 -1
  113. package/registry/btn-3.json +1 -1
  114. package/registry/btn-30.json +1 -1
  115. package/registry/btn-31.json +1 -1
  116. package/registry/btn-32.json +1 -1
  117. package/registry/btn-33.json +1 -1
  118. package/registry/btn-34.json +1 -1
  119. package/registry/btn-35.json +1 -1
  120. package/registry/btn-36.json +1 -1
  121. package/registry/btn-37.json +1 -1
  122. package/registry/btn-4.json +1 -1
  123. package/registry/btn-5.json +1 -1
  124. package/registry/btn-6.json +1 -1
  125. package/registry/btn-7.json +1 -1
  126. package/registry/btn-8.json +1 -1
  127. package/registry/btn-9.json +1 -1
  128. package/registry/button-slide-arrow.json +1 -1
  129. package/registry/cacheable-bandwidth-cost.json +1 -1
  130. package/registry/card-arc-5.json +1 -1
  131. package/registry/card-arc-7.json +1 -1
  132. package/registry/card-carousel-mono.json +1 -1
  133. package/registry/card-carousel.json +1 -1
  134. package/registry/card-cascade-stagger.json +1 -1
  135. package/registry/card-corner-fan.json +1 -1
  136. package/registry/card-cover-flow-mono.json +1 -1
  137. package/registry/card-cover-flow.json +1 -1
  138. package/registry/card-deck-cascade.json +1 -1
  139. package/registry/card-glance-preview.json +1 -1
  140. package/registry/card-hover.json +1 -1
  141. package/registry/card-linear-spread.json +1 -1
  142. package/registry/card-long-arc-5.json +1 -1
  143. package/registry/card-scatter-spread.json +1 -1
  144. package/registry/card-stack-peel.json +1 -1
  145. package/registry/card-stamp-arc.json +1 -1
  146. package/registry/card-time-machine-mono.json +1 -1
  147. package/registry/card-time-machine.json +1 -1
  148. package/registry/card-wheel-fan.json +1 -1
  149. package/registry/category-select.json +1 -1
  150. package/registry/character-stagger.json +1 -1
  151. package/registry/checkbox-draw.json +1 -1
  152. package/registry/checkmark-draw.json +1 -1
  153. package/registry/circle-toggle.json +1 -1
  154. package/registry/circuit-trace-draw.json +1 -1
  155. package/registry/circular-bars.json +1 -1
  156. package/registry/circular-create-button.json +1 -1
  157. package/registry/classic-spinner.json +1 -1
  158. package/registry/classic-toggle.json +1 -1
  159. package/registry/clock-spinner.json +1 -1
  160. package/registry/comet-spinner.json +1 -1
  161. package/registry/compass-needle-deflect.json +1 -1
  162. package/registry/concentric-pulse.json +1 -1
  163. package/registry/concentric-ring.json +1 -1
  164. package/registry/concentric-squares.json +1 -1
  165. package/registry/context-menu.json +1 -1
  166. package/registry/conveyor-loop.json +1 -1
  167. package/registry/credit-score-barcode-meter.json +1 -1
  168. package/registry/cross-spinner.json +1 -1
  169. package/registry/cube-flip-spring.json +1 -1
  170. package/registry/cursor-trail.json +1 -1
  171. package/registry/dash-ring.json +1 -1
  172. package/registry/dashed-spiral.json +1 -1
  173. package/registry/date-position-selector.json +1 -1
  174. package/registry/dia-text-reveal.json +1 -1
  175. package/registry/diamond-grid.json +1 -1
  176. package/registry/diamond-rotate-spring.json +1 -1
  177. package/registry/dislike-toggle.json +1 -1
  178. package/registry/dither-book.json +1 -1
  179. package/registry/domino-cascade.json +1 -1
  180. package/registry/dot-spinner.json +1 -1
  181. package/registry/dots-ring.json +1 -1
  182. package/registry/double-bounce-toggle.json +1 -1
  183. package/registry/double-ring.json +1 -1
  184. package/registry/download-icons.json +1 -1
  185. package/registry/drop-dot.json +1 -1
  186. package/registry/drop-in-char.json +1 -1
  187. package/registry/droplet-squish.json +1 -1
  188. package/registry/dual-arc.json +1 -1
  189. package/registry/dual-magnet-dipole.json +1 -1
  190. package/registry/dynamic-island.json +1 -1
  191. package/registry/elastic-bars.json +1 -1
  192. package/registry/elastic-square.json +1 -1
  193. package/registry/elastic-tag-snap.json +1 -1
  194. package/registry/error-shake.json +1 -1
  195. package/registry/expanding-cross.json +1 -1
  196. package/registry/expanding-icon-tabs.json +1 -1
  197. package/registry/face-id-scan.json +1 -1
  198. package/registry/fade-arc.json +1 -1
  199. package/registry/fade-dots.json +1 -1
  200. package/registry/fade-down.json +1 -1
  201. package/registry/fade-in-char.json +1 -1
  202. package/registry/fade-in-text.json +1 -1
  203. package/registry/fade-in-word.json +1 -1
  204. package/registry/fade-in.json +1 -1
  205. package/registry/fade-up.json +1 -1
  206. package/registry/file-upload-dropzone.json +1 -1
  207. package/registry/filter-tag-pill.json +1 -1
  208. package/registry/flip-square.json +1 -1
  209. package/registry/flip-x-char.json +1 -1
  210. package/registry/flip-y-char.json +1 -1
  211. package/registry/float-char.json +1 -1
  212. package/registry/float-word.json +1 -1
  213. package/registry/floating-diamonds.json +1 -1
  214. package/registry/floating-label-input.json +1 -1
  215. package/registry/fluid-bars.json +1 -1
  216. package/registry/fluid-diamond.json +1 -1
  217. package/registry/fluid-dot-orbit.json +1 -1
  218. package/registry/fluid-skeleton.json +1 -1
  219. package/registry/focus-blur.json +1 -1
  220. package/registry/form-submit-button.json +1 -1
  221. package/registry/french-doors-3d.json +1 -1
  222. package/registry/gear-tooth-step.json +1 -1
  223. package/registry/gears.json +1 -1
  224. package/registry/gelatin-wobble.json +1 -1
  225. package/registry/geometric-iris-shutter.json +1 -1
  226. package/registry/glassmorphic-card.json +1 -1
  227. package/registry/glow-button.json +1 -1
  228. package/registry/glow-text.json +1 -1
  229. package/registry/gooey-popover.json +1 -1
  230. package/registry/gradient-arc.json +1 -1
  231. package/registry/gradient-shimmer.json +1 -1
  232. package/registry/grid-dots.json +1 -1
  233. package/registry/haptic-ring.json +1 -1
  234. package/registry/heartbeat.json +1 -1
  235. package/registry/hex-lattice-draw.json +1 -1
  236. package/registry/hexagon-spinner.json +1 -1
  237. package/registry/hourglass.json +1 -1
  238. package/registry/hover-lift-char.json +1 -1
  239. package/registry/hover-lift-word.json +1 -1
  240. package/registry/hover-link-card.json +1 -1
  241. package/registry/hover-scale-char.json +1 -1
  242. package/registry/hover-scale-word.json +1 -1
  243. package/registry/inertia-skid-stop.json +1 -1
  244. package/registry/infinity-path.json +1 -1
  245. package/registry/input-focus-glow.json +1 -1
  246. package/registry/inset-action-button.json +1 -1
  247. package/registry/inset-button.json +1 -1
  248. package/registry/intersecting-rings.json +1 -1
  249. package/registry/ios-spinner.json +1 -1
  250. package/registry/kinetic-metronome-tick.json +1 -1
  251. package/registry/kinetic-tension-capsule.json +1 -1
  252. package/registry/light-dark-morph.json +1 -1
  253. package/registry/like-toggle.json +1 -1
  254. package/registry/line-spinner.json +1 -1
  255. package/registry/liquid-dots.json +1 -1
  256. package/registry/mac-terminal.json +1 -1
  257. package/registry/magnetic-button.json +1 -1
  258. package/registry/magnetic-disks.json +1 -1
  259. package/registry/magnetic-dots.json +1 -1
  260. package/registry/magnetic-slide-btn.json +1 -1
  261. package/registry/manifest.json +656 -650
  262. package/registry/matrix-grid-loader.json +1 -1
  263. package/registry/minimal-triangle.json +1 -1
  264. package/registry/modular-tile-snap.json +1 -1
  265. package/registry/mono-activity-blue.json +1 -1
  266. package/registry/mono-activity-green.json +1 -1
  267. package/registry/mono-activity-purple.json +1 -1
  268. package/registry/mono-heatmap.json +1 -1
  269. package/registry/mono-rounded-area.json +1 -1
  270. package/registry/mono-rounded-bar.json +1 -1
  271. package/registry/mono-rounded-bubble.json +1 -1
  272. package/registry/mono-rounded-bullet.json +1 -1
  273. package/registry/mono-rounded-candlestick.json +1 -1
  274. package/registry/mono-rounded-composed.json +1 -1
  275. package/registry/mono-rounded-donut.json +1 -1
  276. package/registry/mono-rounded-funnel.json +1 -1
  277. package/registry/mono-rounded-gauge-arc.json +1 -1
  278. package/registry/mono-rounded-heatmap.json +1 -1
  279. package/registry/mono-rounded-kpi.json +1 -1
  280. package/registry/mono-rounded-line.json +1 -1
  281. package/registry/mono-rounded-meter.json +1 -1
  282. package/registry/mono-rounded-polar.json +1 -1
  283. package/registry/mono-rounded-pyramid.json +1 -1
  284. package/registry/mono-rounded-radar.json +1 -1
  285. package/registry/mono-rounded-radial-gauge.json +1 -1
  286. package/registry/mono-rounded-radial-group.json +1 -1
  287. package/registry/mono-rounded-range.json +1 -1
  288. package/registry/mono-rounded-sankey.json +1 -1
  289. package/registry/mono-rounded-scatter.json +1 -1
  290. package/registry/mono-rounded-sparkline.json +1 -1
  291. package/registry/mono-rounded-stacked-bar.json +1 -1
  292. package/registry/mono-rounded-step.json +1 -1
  293. package/registry/mono-rounded-stream.json +1 -1
  294. package/registry/mono-rounded-treemap.json +1 -1
  295. package/registry/mono-rounded-waterfall.json +1 -1
  296. package/registry/mono-stock.json +1 -1
  297. package/registry/morph-action-pill.json +1 -1
  298. package/registry/morph-dot-ring.json +1 -1
  299. package/registry/morph-loader.json +1 -1
  300. package/registry/morph-toggle.json +1 -1
  301. package/registry/morphing-bars.json +1 -1
  302. package/registry/morphing-infinity.json +1 -1
  303. package/registry/morphing-ring.json +1 -1
  304. package/registry/morphing-shape.json +1 -1
  305. package/registry/mouse-follow.json +1 -1
  306. package/registry/multi-select-chips.json +1 -1
  307. package/registry/multi-tab-bar.json +1 -1
  308. package/registry/neon-sign-draw.json +1 -1
  309. package/registry/nested-orbital-gimbal.json +1 -1
  310. package/registry/network-telemetry-matrix.json +1 -1
  311. package/registry/newtons-cradle.json +1 -1
  312. package/registry/noise-decibel-level.json +1 -1
  313. package/registry/obsidian-liquid-wave.json +1 -1
  314. package/registry/offset-rings.json +1 -1
  315. package/registry/orbiting-circles.json +1 -1
  316. package/registry/orbiting-dot.json +1 -1
  317. package/registry/origami-envelope-unfold.json +1 -1
  318. package/registry/origami-shape.json +1 -1
  319. package/registry/otp-input.json +1 -1
  320. package/registry/overview-bar-scrubber-card.json +1 -1
  321. package/registry/page-transition.json +1 -1
  322. package/registry/page-turn-curl.json +1 -1
  323. package/registry/password-toggle.json +1 -1
  324. package/registry/pendulum-bubble-level.json +1 -1
  325. package/registry/pendulum.json +1 -1
  326. package/registry/perspective-flip-stage.json +1 -1
  327. package/registry/perspective-flip.json +1 -1
  328. package/registry/perspective-layout-switcher.json +1 -1
  329. package/registry/physics-dock.json +1 -1
  330. package/registry/pill-tabs.json +1 -1
  331. package/registry/plus-minus-toggle.json +1 -1
  332. package/registry/presets.json +1 -1
  333. package/registry/prism-block-stack.json +1 -1
  334. package/registry/progress-indicator-piano.json +1 -1
  335. package/registry/progress-indicator.json +1 -1
  336. package/registry/progress-stepper.json +1 -1
  337. package/registry/pulsating-dots.json +1 -1
  338. package/registry/pulse-char.json +1 -1
  339. package/registry/pulse-dot.json +1 -1
  340. package/registry/pulse-dots.json +1 -1
  341. package/registry/pulse-square.json +1 -1
  342. package/registry/pulse-word.json +1 -1
  343. package/registry/pulse.json +1 -1
  344. package/registry/pumping-heart.json +1 -1
  345. package/registry/pyramid-block-build.json +1 -1
  346. package/registry/radar-sweep.json +1 -1
  347. package/registry/radial-iris-mask.json +1 -1
  348. package/registry/radial-spinner.json +1 -1
  349. package/registry/radio-scale.json +1 -1
  350. package/registry/range-slider.json +1 -1
  351. package/registry/receipt-tape-print.json +1 -1
  352. package/registry/rectangle-toggle.json +1 -1
  353. package/registry/repost-toggle.json +1 -1
  354. package/registry/reveal-text.json +1 -1
  355. package/registry/ribbon-banner-slide.json +1 -1
  356. package/registry/ring-sweep.json +1 -1
  357. package/registry/ripple-effect.json +1 -1
  358. package/registry/rise-up-word.json +1 -1
  359. package/registry/roller-blind-drop.json +1 -1
  360. package/registry/rolling-tumble.json +1 -1
  361. package/registry/rotate-in-char.json +1 -1
  362. package/registry/rotating-cross.json +1 -1
  363. package/registry/rotating-louvers.json +1 -1
  364. package/registry/rotating-triangle.json +1 -1
  365. package/registry/rudder-issue-board.json +2 -2
  366. package/registry/sales-analytics-dual-bars.json +1 -1
  367. package/registry/sales-overview-radial-dashboard.json +1 -1
  368. package/registry/sales-target-segmented-arc.json +1 -1
  369. package/registry/scale-in-char.json +1 -1
  370. package/registry/scale-in-text.json +1 -1
  371. package/registry/scale-in-word.json +1 -1
  372. package/registry/scale-in.json +1 -1
  373. package/registry/scramble-text.json +1 -1
  374. package/registry/scroll-canvas-unroll.json +1 -1
  375. package/registry/scroll-reveal.json +1 -1
  376. package/registry/search-expand.json +1 -1
  377. package/registry/segmented-arc-meter.json +1 -1
  378. package/registry/segmented-link-stretch.json +1 -1
  379. package/registry/segmented-step-bar.json +1 -1
  380. package/registry/segmented-stepper-dots.json +1 -1
  381. package/registry/select-dropdown.json +1 -1
  382. package/registry/server-performance-step-bars.json +1 -1
  383. package/registry/shape-shift-grid.json +1 -1
  384. package/registry/shimmer-line.json +1 -1
  385. package/registry/shimmer-text.json +1 -1
  386. package/registry/shutter-slide.json +1 -1
  387. package/registry/shutter-step-slices.json +1 -1
  388. package/registry/siri-wave.json +1 -1
  389. package/registry/skeleton-loader.json +1 -1
  390. package/registry/skeleton.json +1 -1
  391. package/registry/skew-x-char.json +1 -1
  392. package/registry/slide-down-char.json +1 -1
  393. package/registry/slide-down-word.json +1 -1
  394. package/registry/slide-left-char.json +1 -1
  395. package/registry/slide-left.json +1 -1
  396. package/registry/slide-right-char.json +1 -1
  397. package/registry/slide-right.json +1 -1
  398. package/registry/slide-up-char.json +1 -1
  399. package/registry/slide-up-text.json +1 -1
  400. package/registry/slide-up-word.json +1 -1
  401. package/registry/sliding-bars.json +1 -1
  402. package/registry/slinky-coil.json +1 -1
  403. package/registry/smart-card-dispenser.json +1 -1
  404. package/registry/smooth-dot-shift.json +1 -1
  405. package/registry/smooth-ring.json +1 -1
  406. package/registry/smooth-rounded-square.json +1 -1
  407. package/registry/solid-switch.json +1 -1
  408. package/registry/spatial-door-portal.json +1 -1
  409. package/registry/spinning-squares.json +1 -1
  410. package/registry/spiral-spinner.json +1 -1
  411. package/registry/split-gate-reveal.json +1 -1
  412. package/registry/spotlight.json +1 -1
  413. package/registry/spring-bars.json +1 -1
  414. package/registry/spring-dot-matrix.json +1 -1
  415. package/registry/spring-hexagon.json +1 -1
  416. package/registry/spring-ring-expand.json +1 -1
  417. package/registry/spring-text-pop.json +1 -1
  418. package/registry/spring-text.json +1 -1
  419. package/registry/square-accordion.json +1 -1
  420. package/registry/square-grid.json +1 -1
  421. package/registry/square-snake.json +1 -1
  422. package/registry/square-spinner.json +1 -1
  423. package/registry/squash-sphere.json +1 -1
  424. package/registry/stacked-bar-pulse.json +1 -1
  425. package/registry/stagger-text.json +1 -1
  426. package/registry/staggered-glass-curtain.json +1 -1
  427. package/registry/sticky-note-peel.json +1 -1
  428. package/registry/sticky-reveal.json +1 -1
  429. package/registry/stretch-x-char.json +1 -1
  430. package/registry/stretch-y-char.json +1 -1
  431. package/registry/stroke-waveform.json +1 -1
  432. package/registry/submenu-flyout.json +1 -1
  433. package/registry/success-check.json +1 -1
  434. package/registry/sudden-brake.json +1 -1
  435. package/registry/swapping-dots.json +1 -1
  436. package/registry/swing-word.json +1 -1
  437. package/registry/swirling-spinner.json +1 -1
  438. package/registry/symmetric-wave.json +1 -1
  439. package/registry/terminal-loader.json +1 -1
  440. package/registry/text-blink.json +1 -1
  441. package/registry/text-dots.json +1 -1
  442. package/registry/text-morph.json +1 -1
  443. package/registry/text-reveal.json +1 -1
  444. package/registry/text-shimmer-wave.json +1 -1
  445. package/registry/text-shimmer.json +1 -1
  446. package/registry/textarea-auto-grow.json +1 -1
  447. package/registry/theme-toggle.json +1 -1
  448. package/registry/tilt-card.json +1 -1
  449. package/registry/timer-preparation-segmented.json +1 -1
  450. package/registry/tracking-in-text.json +1 -1
  451. package/registry/tracking-out-text.json +1 -1
  452. package/registry/trailing-dots.json +1 -1
  453. package/registry/triple-dot-spinner.json +1 -1
  454. package/registry/twin-orbit.json +1 -1
  455. package/registry/typewriter-text.json +1 -1
  456. package/registry/typing-indicator.json +1 -1
  457. package/registry/typing.json +1 -1
  458. package/registry/use-mouse-position.json +1 -1
  459. package/registry/use-reduced-motion.json +1 -1
  460. package/registry/use-scroll-progress.json +1 -1
  461. package/registry/use-stagger.json +1 -1
  462. package/registry/use-web-haptics.json +1 -1
  463. package/registry/users-growth-pill-progress.json +1 -1
  464. package/registry/utils.json +1 -1
  465. package/registry/vertical-wheel-counter.json +1 -1
  466. package/registry/views-hourly-wave-chart.json +1 -1
  467. package/registry/wandering-cube.json +1 -1
  468. package/registry/wandering-eyes.json +1 -1
  469. package/registry/watch-spinner.json +1 -1
  470. package/registry/wave-dots.json +1 -1
  471. package/registry/wave-physics-loader.json +1 -1
  472. package/registry/wave-reveal.json +1 -1
  473. package/registry/waveform-loader.json +1 -1
  474. package/registry/word-reveal.json +1 -1
  475. package/registry/zig-zag-pulse.json +1 -1
  476. package/registry/zoom-in-text.json +1 -1
  477. package/registry/zoom-in.json +1 -1
  478. package/registry/zoom-out-text.json +1 -1
@@ -7,8 +7,9 @@ export type AIContextMeterProps = {
7
7
  className?: string;
8
8
  dangerAt?: number;
9
9
  limit: number;
10
+ theme?: "light" | "dark";
10
11
  used: number;
11
12
  warningAt?: number;
12
13
  };
13
- export declare function AIContextMeter({ breakdown, className, dangerAt, limit, used, warningAt, }: AIContextMeterProps): import("react").JSX.Element;
14
+ export declare function AIContextMeter({ breakdown, className, dangerAt, limit, theme, used, warningAt, }: AIContextMeterProps): import("react").JSX.Element;
14
15
  export default AIContextMeter;
@@ -11,6 +11,7 @@ export type AISourcesProps = {
11
11
  defaultOpen?: boolean;
12
12
  label?: string;
13
13
  sources: AISource[];
14
+ theme?: "light" | "dark";
14
15
  };
15
- export declare function AISources({ className, defaultOpen, label, sources }: AISourcesProps): import("react").JSX.Element;
16
+ export declare function AISources({ className, defaultOpen, label, sources, theme }: AISourcesProps): import("react").JSX.Element;
16
17
  export default AISources;
@@ -10,6 +10,7 @@ export type AITaskListProps = {
10
10
  className?: string;
11
11
  label?: string;
12
12
  tasks: AITask[];
13
+ theme?: "light" | "dark";
13
14
  };
14
- export declare function AITaskList({ className, label, tasks }: AITaskListProps): import("react").JSX.Element;
15
+ export declare function AITaskList({ className, label, tasks, theme }: AITaskListProps): import("react").JSX.Element;
15
16
  export default AITaskList;
@@ -8,6 +8,7 @@ export type AIToolCallProps = {
8
8
  result?: ReactNode;
9
9
  status?: AIToolCallStatus;
10
10
  summary?: string;
11
+ theme?: "light" | "dark";
11
12
  };
12
- export declare function AIToolCall({ args, className, defaultOpen, name, result, status, summary }: AIToolCallProps): import("react").JSX.Element;
13
+ export declare function AIToolCall({ args, className, defaultOpen, name, result, status, summary, theme }: AIToolCallProps): import("react").JSX.Element;
13
14
  export default AIToolCall;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mova-ui/react",
3
- "version": "0.4.1",
3
+ "version": "0.4.5",
4
4
  "description": "Mova. Make interfaces feel alive. React motion components and an offline source CLI.",
5
5
  "private": false,
6
6
  "type": "module",
package/registry/1.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "1",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "1.tsx",
6
6
  "registryFile": "1.json",
7
7
  "exports": [
package/registry/10.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "10",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "10.tsx",
6
6
  "registryFile": "10.json",
7
7
  "exports": [
package/registry/11.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "11",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "11.tsx",
6
6
  "registryFile": "11.json",
7
7
  "exports": [
package/registry/12.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "12",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "12.tsx",
6
6
  "registryFile": "12.json",
7
7
  "exports": [
package/registry/13.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "13",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "13.tsx",
6
6
  "registryFile": "13.json",
7
7
  "exports": [
package/registry/14.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "14",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "14.tsx",
6
6
  "registryFile": "14.json",
7
7
  "exports": [
package/registry/15.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "15",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "15.tsx",
6
6
  "registryFile": "15.json",
7
7
  "exports": [
package/registry/16.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "16",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "16.tsx",
6
6
  "registryFile": "16.json",
7
7
  "exports": [
package/registry/17.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "17",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "17.tsx",
6
6
  "registryFile": "17.json",
7
7
  "exports": [
package/registry/18.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "18",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "18.tsx",
6
6
  "registryFile": "18.json",
7
7
  "exports": [
package/registry/19.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "19",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "19.tsx",
6
6
  "registryFile": "19.json",
7
7
  "exports": [
package/registry/2.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "2",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "2.tsx",
6
6
  "registryFile": "2.json",
7
7
  "exports": [
package/registry/20.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "20",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "20.tsx",
6
6
  "registryFile": "20.json",
7
7
  "exports": [
package/registry/21.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "21",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "21.tsx",
6
6
  "registryFile": "21.json",
7
7
  "exports": [
package/registry/22.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "22",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "22.tsx",
6
6
  "registryFile": "22.json",
7
7
  "exports": [
package/registry/23.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "23",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "23.tsx",
6
6
  "registryFile": "23.json",
7
7
  "exports": [
package/registry/24.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "24",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "24.tsx",
6
6
  "registryFile": "24.json",
7
7
  "exports": [
package/registry/25.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "25",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "25.tsx",
6
6
  "registryFile": "25.json",
7
7
  "exports": [
package/registry/26.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "26",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "26.tsx",
6
6
  "registryFile": "26.json",
7
7
  "exports": [
package/registry/27.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "27",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "27.tsx",
6
6
  "registryFile": "27.json",
7
7
  "exports": [
package/registry/28.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "28",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "28.tsx",
6
6
  "registryFile": "28.json",
7
7
  "exports": [
package/registry/29.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "29",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "29.tsx",
6
6
  "registryFile": "29.json",
7
7
  "exports": [
package/registry/3.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "3",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "3.tsx",
6
6
  "registryFile": "3.json",
7
7
  "exports": [
package/registry/30.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "30",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "30.tsx",
6
6
  "registryFile": "30.json",
7
7
  "exports": [
package/registry/31.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "31",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "31.tsx",
6
6
  "registryFile": "31.json",
7
7
  "exports": [
package/registry/32.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "32",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "32.tsx",
6
6
  "registryFile": "32.json",
7
7
  "exports": [
package/registry/33.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "33",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "33.tsx",
6
6
  "registryFile": "33.json",
7
7
  "exports": [
package/registry/34.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "34",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "34.tsx",
6
6
  "registryFile": "34.json",
7
7
  "exports": [
package/registry/35.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "35",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "35.tsx",
6
6
  "registryFile": "35.json",
7
7
  "exports": [
package/registry/36.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "36",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "36.tsx",
6
6
  "registryFile": "36.json",
7
7
  "exports": [
package/registry/37.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "37",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "37.tsx",
6
6
  "registryFile": "37.json",
7
7
  "exports": [
package/registry/4.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "4",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "4.tsx",
6
6
  "registryFile": "4.json",
7
7
  "exports": [
package/registry/5.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "5",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "5.tsx",
6
6
  "registryFile": "5.json",
7
7
  "exports": [
package/registry/6.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "6",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "6.tsx",
6
6
  "registryFile": "6.json",
7
7
  "exports": [
package/registry/7.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "7",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "7.tsx",
6
6
  "registryFile": "7.json",
7
7
  "exports": [
package/registry/8.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "8",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "8.tsx",
6
6
  "registryFile": "8.json",
7
7
  "exports": [
package/registry/9.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "9",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "9.tsx",
6
6
  "registryFile": "9.json",
7
7
  "exports": [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "ab-segmented-tabs",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "ab-segmented-tabs.tsx",
6
6
  "sourcePath": "components/css-animations/yui-components/YuiUiKit2.tsx",
7
7
  "registryFile": "ab-segmented-tabs.json",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "accordion-blind",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "accordion-blind.tsx",
6
6
  "sourcePath": "components/css-animations/whimsical/WhimsicalVariations.tsx",
7
7
  "registryFile": "accordion-blind.json",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "accordion-loader",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "accordion-loader.tsx",
6
6
  "sourcePath": "ui/loading/accordion-loader.tsx",
7
7
  "registryFile": "accordion-loader.json",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "ai-approval-card",
3
3
  "tier": "free",
4
- "version": "0.4.1",
4
+ "version": "0.4.5",
5
5
  "file": "ai-approval-card.tsx",
6
6
  "sourcePath": "ui/ai/ai-approval-card.tsx",
7
7
  "registryFile": "ai-approval-card.json",
@@ -13,5 +13,5 @@
13
13
  "dependencies": [],
14
14
  "license": "MIT",
15
15
  "previewMode": "client",
16
- "content": "\"use client\";\n\"use client\";\n\n/** Adapted from Beautiful UI (https://github.com/slev12397/beautiful-ui).\n * Copyright (c) 2026 Shane Levine. MIT License; see LICENSE.third-party.\n * Original card geometry, interactions, and motion are preserved. */\nimport * as React from \"react\";\nimport type { ReactNode } from \"react\";\n\nimport { useEffect, useLayoutEffect, useRef, useState, type CSSProperties } from \"react\";\n\n/* ─────────────────────────────────────────────────────────\n * APPROVAL CARD (human-in-the-loop)\n * One question at a time. The stack slides vertically as you\n * move between questions (the card's height animates to fit),\n * the step counter rolls like an odometer, and the footer uses\n * pill actions — a quiet Skip and a dark Continue with a ⏎.\n * Single-choice answers auto-advance; multi-select waits.\n * ───────────────────────────────────────────────────────── */\n\nexport type ApprovalQuestion = {\n q: string;\n type: \"radio\" | \"check\";\n options: string[];\n};\n\nconst QUESTIONS: ApprovalQuestion[] = [\n {\n q: \"How many flavors should we launch?\",\n type: \"radio\",\n options: [\"Three (core line)\", \"Five (full case)\", \"Just one hero\"],\n },\n {\n q: \"Which mix-ins should we stock?\",\n type: \"check\",\n options: [\"Chocolate chips\", \"Waffle bits\", \"Sprinkles\"],\n },\n {\n q: \"Which market do we enter first?\",\n type: \"radio\",\n options: [\"Food trucks\", \"Grocery freezers\", \"Scoop shops\"],\n },\n];\n\nexport type ApprovalLabels = {\n skip: string;\n continue: string;\n send: string;\n customPlaceholder: string;\n sentMessage: string;\n};\n\nconst DEFAULT_LABELS: ApprovalLabels = {\n skip: \"Skip\",\n continue: \"Continue\",\n send: \"Send\",\n customPlaceholder: \"Something else…\",\n sentMessage: \"Answers sent\",\n};\n\nconst ROLL_MS = 400;\nconst SLIDE = \"360ms cubic-bezier(0.22, 1, 0.36, 1)\";\n\n/* odometer digits — each character that changes rolls up (or down) */\nfunction RollingDigits({ value }: { value: string }) {\n const prevRef = useRef(value);\n const [oldVal, setOldVal] = useState(value);\n const [newVal, setNewVal] = useState(value);\n const [rolling, setRolling] = useState(false);\n const [shifted, setShifted] = useState(false);\n const [dir, setDir] = useState<\"up\" | \"down\">(\"up\");\n\n useEffect(() => {\n if (prevRef.current === value) return;\n const from = prevRef.current;\n prevRef.current = value;\n const fromN = parseInt(from, 10);\n const toN = parseInt(value, 10);\n setDir(Number.isFinite(fromN) && Number.isFinite(toN) && toN < fromN ? \"down\" : \"up\");\n setOldVal(from);\n setNewVal(value);\n setRolling(true);\n setShifted(false);\n\n let raf2 = 0;\n const raf1 = requestAnimationFrame(() => {\n raf2 = requestAnimationFrame(() => setShifted(true));\n });\n const done = setTimeout(() => {\n setRolling(false);\n setOldVal(value);\n setShifted(false);\n }, ROLL_MS);\n\n return () => {\n cancelAnimationFrame(raf1);\n cancelAnimationFrame(raf2);\n clearTimeout(done);\n };\n }, [value]);\n\n const chars = rolling ? newVal : oldVal;\n\n return (\n <>\n {Array.from({ length: chars.length }, (_, i) => {\n const o = oldVal[i] ?? \"\";\n const n = chars[i] ?? \"\";\n if (!rolling || o === n) {\n return <span key={`${i}-${n}`}>{n}</span>;\n }\n const top = dir === \"down\" ? n : o;\n const bottom = dir === \"down\" ? o : n;\n const restY = dir === \"down\" ? \"0\" : \"-1em\";\n const startY = dir === \"down\" ? \"-1em\" : \"0\";\n return (\n <span\n key={`${i}-${o}-${n}-${dir}`}\n style={{ display: \"inline-block\", position: \"relative\", overflow: \"hidden\", height: \"1em\", lineHeight: \"1em\", verticalAlign: \"-0.05em\" }}\n >\n <span\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n transition: \"transform 350ms cubic-bezier(0.4, 0, 0.2, 1)\",\n transform: `translateY(${shifted ? restY : startY})`,\n }}\n >\n <span style={{ height: \"1em\", lineHeight: \"1em\" }}>{top}</span>\n <span style={{ height: \"1em\", lineHeight: \"1em\" }}>{bottom}</span>\n </span>\n </span>\n );\n })}\n </>\n );\n}\n\nfunction Ico({ path, size = 14, sw = 2 }: { path: React.ReactNode; size?: number; sw?: number }) {\n return (\n <svg width={size} height={size} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={sw} strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n {path}\n </svg>\n );\n}\n\nfunction ApprovalCard({\n questions = QUESTIONS,\n labels,\n onSubmitted,\n onAnswerChange,\n resettable = true,\n}: {\n questions?: ApprovalQuestion[];\n labels?: Partial<ApprovalLabels>;\n onSubmitted?: (answers: Record<number, number[]>, customAnswers: Record<number, string>) => void;\n onAnswerChange?: (questionIndex: number, answer: number[]) => void;\n resettable?: boolean;\n variant?: string;\n} = {}) {\n const t = { ...DEFAULT_LABELS, ...labels };\n const [qi, setQi] = useState(0);\n const [answers, setAnswers] = useState<Record<number, number[]>>({});\n const [custom, setCustom] = useState<Record<number, string>>({});\n const answersRef = useRef(answers);\n answersRef.current = answers;\n const customRef = useRef(custom);\n customRef.current = custom;\n const [sent, setSent] = useState(false);\n const [open, setOpen] = useState(true);\n const reopenRef = useRef<HTMLButtonElement>(null);\n const sentStatusRef = useRef<HTMLDivElement>(null);\n const previousView = useRef({ open: true, sent: false });\n\n const advanceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const questionRefs = useRef<(HTMLDivElement | null)[]>([]);\n const measured = useRef(false);\n const [viewportH, setViewportH] = useState<number | undefined>(undefined);\n const [trackY, setTrackY] = useState(0);\n const [animate, setAnimate] = useState(false);\n // Until the first question is measured, render only the active one so the\n // initial (and SSR) height is Q1's height — not all questions stacked, which\n // would flash to full height and then shrink on mount.\n const [ready, setReady] = useState(false);\n\n const last = qi >= questions.length - 1;\n const selected = answers[qi] ?? [];\n const hasAnswer = selected.length > 0 || Boolean(custom[qi]?.trim());\n\n const sync = (withAnim: boolean) => {\n const item = questionRefs.current[qi];\n if (!item) return;\n const reduce = typeof window !== \"undefined\" && window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n setViewportH(item.offsetHeight);\n setTrackY(item.offsetTop);\n setAnimate(withAnim && !reduce);\n };\n\n useLayoutEffect(() => {\n const withAnim = measured.current;\n measured.current = true;\n sync(withAnim);\n setReady(true);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [qi, answers, custom, open, sent]);\n\n useEffect(() => {\n const previous = previousView.current;\n previousView.current = { open, sent };\n if (!open) reopenRef.current?.focus({ preventScroll: true });\n else if (sent) sentStatusRef.current?.focus({ preventScroll: true });\n else if (!previous.open || previous.sent) questionRefs.current[qi]?.querySelector<HTMLButtonElement>(\"button\")?.focus({ preventScroll: true });\n }, [open, sent, qi]);\n\n useEffect(() => {\n const focused = document.activeElement;\n const previousQuestion = questionRefs.current.find((item, index) => index !== qi && item?.contains(focused));\n if (previousQuestion) questionRefs.current[qi]?.querySelector<HTMLButtonElement>(\"button\")?.focus({ preventScroll: true });\n }, [qi]);\n\n useEffect(() => {\n setQi((current) => Math.max(0, Math.min(current, questions.length - 1)));\n }, [questions.length]);\n\n useEffect(() => {\n const id = requestAnimationFrame(() => sync(measured.current));\n return () => cancelAnimationFrame(id);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [qi]);\n\n useEffect(() => {\n const item = questionRefs.current[qi];\n if (!item || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(() => sync(false));\n observer.observe(item);\n return () => observer.disconnect();\n }, [qi, open, sent, questions]);\n\n useEffect(() => () => { if (advanceTimer.current) clearTimeout(advanceTimer.current); }, []);\n\n const goTo = (next: number) => {\n if (advanceTimer.current) clearTimeout(advanceTimer.current);\n setQi(Math.max(0, Math.min(Math.max(next, 0), questions.length - 1)));\n };\n\n const send = () => {\n if (advanceTimer.current) clearTimeout(advanceTimer.current);\n setSent(true);\n onSubmitted?.(answersRef.current, customRef.current);\n };\n\n const advance = () => {\n if (last) send();\n else goTo(qi + 1);\n };\n\n const toggle = (index: number) => {\n const type = questions[qi].type;\n const current = answersRef.current;\n const picked = current[qi] ?? [];\n const next = type === \"radio\" ? [index] : picked.includes(index) ? picked.filter((item) => item !== index) : [...picked, index];\n const updated = { ...current, [qi]: next };\n answersRef.current = updated;\n setAnswers(updated);\n onAnswerChange?.(qi, next);\n if (type === \"radio\") {\n setCustom((current) => ({ ...current, [qi]: \"\" }));\n if (advanceTimer.current) clearTimeout(advanceTimer.current);\n advanceTimer.current = setTimeout(() => {\n if (last) send();\n else setQi((current) => Math.min(questions.length - 1, current + 1));\n }, 480);\n }\n };\n\n const reset = () => {\n if (advanceTimer.current) clearTimeout(advanceTimer.current);\n setQi(0);\n setAnswers({});\n setCustom({});\n setSent(false);\n setOpen(true);\n measured.current = false;\n };\n\n if (questions.length === 0) return <div role=\"status\" className=\"text-[13px]\">No questions to approve.</div>;\n\n if (!open) {\n return (\n <button ref={reopenRef} type=\"button\" onClick={() => setOpen(true)} className=\"rounded-[8px] bg-[var(--surface)] px-3 py-2 text-[12.5px] font-medium text-[var(--ink)] shadow-[var(--shadow-btn)] transition-colors duration-150 hover:bg-[var(--hover)]\">\n Open approval\n </button>\n );\n }\n\n if (sent) {\n return (\n <div ref={sentStatusRef} tabIndex={-1} role=\"status\" className=\"flex w-full max-w-80 flex-wrap items-center gap-3\" style={{ animation: \"mova-ai-approval-card-pop-in 260ms cubic-bezier(0.23,1,0.32,1) both\" }}>\n <span className=\"inline-flex items-center gap-1.5 rounded-full bg-[var(--green-tint)] py-1 pr-2.5 pl-1 text-[12.5px] font-medium text-[var(--green)]\">\n <span className=\"flex size-4.5 items-center justify-center rounded-full bg-[var(--green)] text-white\">\n <svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"3\" strokeLinecap=\"round\" strokeLinejoin=\"round\"><path d=\"M20 6L9 17l-5-5\" /></svg>\n </span>\n {t.sentMessage}\n </span>\n {resettable && (\n <button type=\"button\" onClick={reset} className=\"text-[12px] font-medium text-[var(--ink-3)] transition-colors duration-150 hover:text-[var(--ink)]\">\n Start over\n </button>\n )}\n </div>\n );\n }\n\n return (\n <div className=\"w-full max-w-80\">\n <div className=\"relative overflow-hidden rounded-[10px] bg-[var(--surface)] shadow-[var(--shadow-card)]\" style={{ animation: \"mova-ai-approval-card-fade-up 380ms cubic-bezier(0.23,1,0.32,1) both\" }}>\n <button\n type=\"button\"\n aria-label=\"Dismiss\"\n onClick={() => { if (advanceTimer.current) clearTimeout(advanceTimer.current); setOpen(false); }}\n className=\"primitive-icon-button absolute right-2.5 top-2.5 z-10 text-[var(--ink-3)] transition-colors duration-100 hover:bg-[var(--hover)] hover:text-[var(--ink)]\"\n >\n <Ico size={14} sw={2.2} path={<path d=\"M18 6L6 18M6 6l12 12\" />} />\n </button>\n <div className=\"primitive-card-pad\">\n {/* the question itself is the heading */}\n <div\n className=\"overflow-hidden\"\n style={{ height: viewportH, transition: animate ? `height ${SLIDE}` : undefined }}\n aria-live=\"polite\"\n >\n <div\n style={{\n position: \"relative\",\n display: \"flex\",\n flexDirection: \"column\",\n gap: 26,\n transform: `translate3d(0, ${-trackY}px, 0)`,\n transition: animate ? `transform ${SLIDE}` : undefined,\n willChange: \"transform\",\n }}\n >\n {questions.map((question, qIdx) => {\n const active = qIdx === qi;\n // Before the first measure, mount only the active question so the\n // card opens at its real height instead of flashing to full height.\n if (!ready && !active) return null;\n const picked = answers[qIdx] ?? [];\n const questionStyle: CSSProperties = {\n opacity: active ? 1 : 0,\n transition: animate ? `opacity ${SLIDE}` : undefined,\n pointerEvents: active ? undefined : \"none\",\n };\n return (\n <div\n key={qIdx}\n ref={(el) => { questionRefs.current[qIdx] = el; }}\n aria-hidden={active ? undefined : true}\n style={questionStyle}\n >\n <div className=\"pr-7 text-[14px] font-medium text-[var(--ink)]\">{question.q}</div>\n <GlideMenu className=\"mt-2.5 flex flex-col gap-1\" highlightClassName=\"inset-x-0 rounded-[8px] bg-[var(--hover)]\">\n {question.options.map((option, i) => {\n const on = picked.includes(i);\n return (\n <button\n key={option}\n type=\"button\"\n data-menu-row\n aria-pressed={on}\n tabIndex={active ? 0 : -1}\n onClick={() => { if (active) toggle(i); }}\n className=\"relative z-10 flex items-center gap-1.5 rounded-[8px] pl-1 pr-2 py-1 text-left transition-colors duration-100\"\n >\n <span\n className={`flex size-4 shrink-0 items-center justify-center transition-colors duration-200\n ${question.type === \"radio\" ? \"rounded-full\" : \"rounded-[5px]\"}\n ${on ? \"bg-[var(--ink)] text-[var(--canvas)]\" : \"shadow-[inset_0_0_0_1.5px_var(--line-strong)] text-transparent\"}`}\n >\n {question.type === \"radio\" ? (\n <span className=\"size-1.5 rounded-full bg-[var(--canvas)] transition-transform duration-200\" style={{ transform: on ? \"scale(1)\" : \"scale(0)\" }} />\n ) : (\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"3\" strokeLinecap=\"round\" strokeLinejoin=\"round\"><path d=\"M20 6L9 17l-5-5\" /></svg>\n )}\n </span>\n <span className={`text-[13px] leading-snug transition-colors duration-200 ${on ? \"text-[var(--ink)]\" : \"text-[var(--ink-2)]\"}`}>\n {option}\n </span>\n </button>\n );\n })}\n <label data-menu-row className=\"relative z-10 flex items-center gap-1.5 rounded-[8px] pl-1 pr-2 py-1 transition-colors duration-100\">\n <input\n value={custom[qIdx] ?? \"\"}\n tabIndex={active ? 0 : -1}\n onChange={(event) => {\n if (!active) return;\n if (advanceTimer.current) clearTimeout(advanceTimer.current);\n setCustom((current) => ({ ...current, [qIdx]: event.target.value }));\n if (question.type === \"radio\") setAnswers((current) => ({ ...current, [qIdx]: [] }));\n }}\n onKeyDown={(event) => {\n if (event.key === \"Enter\" && hasAnswer) {\n event.preventDefault();\n advance();\n }\n }}\n placeholder={t.customPlaceholder}\n aria-label=\"Custom answer\"\n className=\"min-w-0 flex-1 bg-transparent pl-1.5 text-[13px] text-[var(--ink)] outline-none placeholder:text-[var(--ink-3)]\"\n />\n </label>\n </GlideMenu>\n </div>\n );\n })}\n </div>\n </div>\n </div>\n\n {/* footer — step nav (rolling counter) + pill actions */}\n <div className=\"primitive-card-footer flex items-center justify-between gap-3\">\n <div className=\"flex items-center gap-1 text-[var(--ink-3)]\">\n <button\n type=\"button\"\n aria-label=\"Previous question\"\n disabled={qi <= 0}\n onClick={() => goTo(qi - 1)}\n className=\"flex size-[18px] items-center justify-center rounded-[5px] transition-colors duration-100 enabled:hover:text-[var(--ink)] disabled:opacity-30\"\n >\n <Ico size={14} path={<path d=\"M18 15l-6-6-6 6\" />} />\n </button>\n <span className=\"inline-flex items-center text-[12px] font-medium tabular-nums text-[var(--ink-3)]\" style={{ letterSpacing: \"-0.1px\", lineHeight: 1 }}>\n <RollingDigits value={`${qi + 1} / ${questions.length}`} />\n </span>\n <button\n type=\"button\"\n aria-label=\"Next question\"\n disabled={last}\n onClick={() => goTo(qi + 1)}\n className=\"flex size-[18px] items-center justify-center rounded-[5px] transition-colors duration-100 enabled:hover:text-[var(--ink)] disabled:opacity-30\"\n >\n <Ico size={14} path={<path d=\"M6 9l6 6 6-6\" />} />\n </button>\n </div>\n\n <div className=\"-mr-0.5 flex items-center gap-1.5\">\n <Button variant=\"ghost\" size=\"sm\" onClick={() => { if (advanceTimer.current) clearTimeout(advanceTimer.current); if (last) setOpen(false); else goTo(qi + 1); }}>\n {t.skip}\n </Button>\n <Button variant=\"accent\" size=\"sm\" disabled={!hasAnswer} onClick={advance}>\n {last ? t.send : t.continue}\n </Button>\n </div>\n </div>\n </div>\n </div>\n );\n}\n\ntype GlideMenuProps = {\n children: ReactNode;\n className?: string;\n highlightClassName?: string;\n rowSelector?: string;\n};\n\n/** A single hover layer that glides between interactive menu rows. */\nfunction GlideMenu({\n children,\n className = \"\",\n highlightClassName = \"inset-x-0 rounded-[8px] bg-[var(--hover)]\",\n rowSelector = \"[data-menu-row]\",\n}: GlideMenuProps) {\n const ref = useRef<HTMLDivElement>(null);\n const [box, setBox] = useState<{ top: number; height: number } | null>(null);\n const [visible, setVisible] = useState(false);\n\n const moveTo = (target: EventTarget | null) => {\n const container = ref.current;\n if (!(target instanceof Element) || !container) return;\n const row = target.closest(rowSelector);\n if (!(row instanceof HTMLElement) || !container.contains(row)) return;\n const containerRect = container.getBoundingClientRect();\n const rowRect = row.getBoundingClientRect();\n setBox({ top: rowRect.top - containerRect.top, height: rowRect.height });\n setVisible(true);\n };\n\n return (\n <div\n ref={ref}\n onMouseOver={(event) => moveTo(event.target)}\n onMouseLeave={() => setVisible(false)}\n onFocusCapture={(event) => moveTo(event.target)}\n onBlurCapture={(event) => {\n if (!ref.current?.contains(event.relatedTarget as Node | null)) setVisible(false);\n }}\n className={`group/glide-menu relative ${className}`}\n >\n <span\n aria-hidden\n className={`pointer-events-none absolute ${highlightClassName}`}\n style={{\n top: box?.top ?? 0,\n height: box?.height ?? 0,\n opacity: box && visible ? 1 : 0,\n transition:\n \"top 220ms cubic-bezier(0.23,1,0.32,1), height 220ms cubic-bezier(0.23,1,0.32,1), opacity 150ms ease\",\n }}\n />\n {children}\n </div>\n );\n}\n\nfunction Button({variant = \"secondary\",size = \"md\",className = \"\",...props}: React.ButtonHTMLAttributes<HTMLButtonElement> & {variant?: \"secondary\" | \"ghost\" | \"accent\";size?: \"sm\" | \"md\"}) {\n const variants = {secondary:\"bg-[var(--surface)] text-[var(--ink)] shadow-[var(--shadow-btn)] hover:bg-[var(--inset)]\", ghost:\"bg-[var(--hover-2)] text-[var(--ink)] hover:bg-[var(--line-strong)]\", accent:\"bg-[var(--accent)] text-white hover:bg-[var(--accent-ink)] shadow-[inset_0_1px_0_rgba(255,255,255,0.14)]\"};\n return <button type=\"button\" className={`inline-flex items-center justify-center font-medium select-none transition-[transform,background-color,opacity] duration-150 ease-out active:scale-[0.96] disabled:opacity-50 disabled:pointer-events-none ${variants[variant]} ${size === \"sm\" ? \"h-[27px] px-3 text-[13px] leading-none rounded-full gap-1.5\" : \"px-4 py-[9px] text-sm leading-none rounded-full gap-2\"} ${className}`} {...props}/>;\n}\n\n/* Smooth shadow values: shadow-plugin, Copyright (c) Florian Kiem, MIT. */\nconst scopedStyles = `.mova-ai-approval-card{\n --page: oklch(0.985 0.001 286.376);\n --canvas: oklch(0.961 0.002 247.84);\n --surface: oklch(1 0 0);\n --inset: oklch(0.979 0.002 247.839);\n --hover: oklch(0.97 0.002 247.839);\n --hover-2: oklch(0.933 0.003 247.86);\n\n --ink: oklch(0.247 0.006 258.361);\n --ink-2: oklch(0.506 0.01 264.477);\n --ink-3: oklch(0.695 0.009 264.505);\n\n --line: oklch(0.946 0.003 264.542);\n --line-strong: oklch(0.912 0.005 258.326);\n --line-soft: oklch(0.966 0.002 264.542);\n --grid-line: color-mix(in srgb, var(--line) 78%, transparent);\n --field: oklch(0.961 0.001 286.375);\n --stripe: oklch(0.405 0 0 / 0.075);\n --stripe-bg: oklch(0.97 0 0);\n\n --accent: oklch(0.626 0.205 254.947);\n --accent-ink: oklch(0.556 0.187 255.617);\n --accent-tint: oklch(0.96 0.019 252.878);\n\n --green: oklch(0.603 0.155 150.883);\n --green-tint: oklch(0.958 0.017 159.118);\n --orange: oklch(0.689 0.179 49.902);\n --orange-tint: oklch(0.964 0.021 67.581);\n --red: oklch(0.621 0.192 23.042);\n --red-tint: oklch(0.956 0.017 17.462);\n\n --tooltip-bg: oklch(0.272 0.008 264.435);\n --tooltip-fg: oklch(0.976 0.002 247.839);\n --tooltip-muted: oklch(0.731 0.008 260.731);\n --tooltip-border: oklch(0.356 0.007 264.474);\n\n --shadow-hairline: 0 0 0 1px var(--line);\n --shadow-btn: 0 0 0 1px var(--line-strong), var(--shadow-xs);\n --shadow-card: 0 0 0 1px var(--line), var(--shadow-sm);\n --shadow-raised: 0 0 0 1px var(--line), var(--shadow-md);\n --shadow-overlay: 0 0 0 1px var(--line), var(--shadow-lg);\n --shadow-inset-field: inset 0 1px 2px oklch(0 0 0 / 0.12);--shadow-xs: 0 0 4px 0 rgba(0, 0, 0, 0.04);--shadow-sm: 0 18px 47px 0 rgba(0, 0, 0, 0.03), 0 7.5px 19px 0 rgba(0, 0, 0, 0.02),\n 0 4px 10.5px 0 rgba(0, 0, 0, 0.02), 0 2.3px 5.8px 0 rgba(0, 0, 0, 0.01),\n 0 1.2px 3.1px 0 rgba(0, 0, 0, 0.01), 0 0.5px 1.3px 0 rgba(0, 0, 0, 0.01);--shadow-md: 0 17.54px 23.39px 0 rgba(0, 0, 0, 0.04), 0 9.4px 12.5px 0 rgba(0, 0, 0, 0.03),\n 0 5.25px 7px 0 rgba(0, 0, 0, 0.02), 0 2.79px 3.72px -2px rgba(0, 0, 0, 0.01),\n 0 1.16px 1.5px 0 rgba(0, 0, 0, 0.01);--radius-control:8px;\n width:100%;max-width:320px;min-width:0;font-family:Inter,ui-sans-serif,system-ui,sans-serif;font-size:14px;line-height:1.5;letter-spacing:-.01em;color:var(--ink);}\n.mova-ai-approval-card[data-theme=\"dark\"]{\n --page: oklch(0.209 0.004 264.477);\n --canvas: oklch(0.231 0.004 264.487);\n --surface: oklch(0.26 0.006 271.191);\n --inset: oklch(0.243 0.004 264.492);\n --hover: oklch(0.289 0.006 271.22);\n --hover-2: oklch(0.318 0.007 274.747);\n\n --ink: oklch(0.964 0.002 247.839);\n --ink-2: oklch(0.731 0.008 260.731);\n --ink-3: oklch(0.541 0.01 264.484);\n\n --line: oklch(0.308 0.006 258.354);\n --line-strong: oklch(0.356 0.007 264.474);\n --line-soft: oklch(0.278 0.006 258.354);\n --field: oklch(0.293 0.006 271.223);\n --stripe: oklch(1 0 0 / 0.055);\n --stripe-bg: oklch(0.226 0.004 264.485);\n\n --accent: oklch(0.68 0.173 253.301);\n --accent-ink: oklch(0.788 0.113 248.33);\n --accent-tint: oklch(0.68 0.173 253.301 / 0.16);\n\n --green: oklch(0.705 0.154 153.814);\n --green-tint: oklch(0.705 0.154 153.814 / 0.14);\n --orange: oklch(0.746 0.156 55.642);\n --orange-tint: oklch(0.746 0.156 55.642 / 0.14);\n --red: oklch(0.666 0.18 21.433);\n --red-tint: oklch(0.666 0.18 21.433 / 0.14);\n\n --tooltip-bg: oklch(0.182 0.004 264.459);\n --tooltip-fg: oklch(0.964 0.002 247.839);\n --tooltip-muted: oklch(0.731 0.008 260.731);\n --tooltip-border: oklch(0.308 0.006 258.354);\n\n --shadow-hairline: 0 0 0 1px var(--line);\n --shadow-btn: 0 0 0 1px oklch(1 0 0 / 0.1), 0 1px 2px oklch(0 0 0 / 0.3);\n --shadow-card:\n 0 0 0 1px oklch(1 0 0 / 0.11), 0 1px 2px oklch(0 0 0 / 0.2),\n 0 2px 6px oklch(0 0 0 / 0.2);\n --shadow-raised:\n 0 0 0 1px oklch(1 0 0 / 0.13), 0 2px 10px oklch(0 0 0 / 0.22);\n --shadow-overlay:\n 0 0 0 1px oklch(1 0 0 / 0.15), 0 8px 28px oklch(0 0 0 / 0.34);\n --shadow-inset-field: inset 0 1px 2px oklch(0 0 0 / 0.4);}\n.mova-ai-approval-card .primitive-card-pad{padding:12px} .mova-ai-approval-card .primitive-card-bar{padding:10px 12px} .mova-ai-approval-card .primitive-card-footer{padding:10px} .mova-ai-approval-card .primitive-icon-button{display:inline-flex;width:28px;height:28px;align-items:center;justify-content:center;border-radius:8px}\n.mova-ai-approval-card :is(button,input,[tabindex]):focus-visible{outline:2px solid var(--accent)!important;outline-offset:2px} .mova-ai-approval-card button:not(:disabled){cursor:pointer} .mova-ai-approval-card code{overflow-wrap:anywhere} .mova-ai-approval-card *, .mova-ai-approval-card *::before,.mova-ai-approval-card *::after{box-sizing:border-box}\n@keyframes mova-ai-approval-card-pop-in{from{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}\n@keyframes mova-ai-approval-card-fade-up{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}\n@keyframes mova-ai-approval-card-shimmer-text{from{background-position:150% center}to{background-position:-50% center}}\n@media(prefers-reduced-motion:reduce){.mova-ai-approval-card *, .mova-ai-approval-card *::before, .mova-ai-approval-card *::after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}`;\n\nexport function AIApprovalCard({theme = \"light\", ...props}: React.ComponentProps<typeof ApprovalCard> & {theme?: \"light\" | \"dark\"} = {}) {\n return <div className=\"mova-ai-approval-card\" data-theme={theme}><style>{scopedStyles}</style><ApprovalCard {...props} /></div>;\n}\n"
16
+ "content": "\"use client\";\n\"use client\";\n\n/** Adapted from Beautiful UI (https://github.com/slev12397/beautiful-ui).\n * Copyright (c) 2026 Shane Levine. MIT License; see LICENSE.third-party.\n * Original card geometry, interactions, and motion are preserved. */\nimport * as React from \"react\";\nimport type { ReactNode } from \"react\";\n\nimport { useEffect, useLayoutEffect, useRef, useState, type CSSProperties } from \"react\";\n\n/* ─────────────────────────────────────────────────────────\n * APPROVAL CARD (human-in-the-loop)\n * One question at a time. The stack slides vertically as you\n * move between questions (the card's height animates to fit),\n * the step counter rolls like an odometer, and the footer uses\n * pill actions — a quiet Skip and a dark Continue with a ⏎.\n * Single-choice answers auto-advance; multi-select waits.\n * ───────────────────────────────────────────────────────── */\n\nexport type ApprovalQuestion = {\n q: string;\n type: \"radio\" | \"check\";\n options: string[];\n};\n\nconst QUESTIONS: ApprovalQuestion[] = [\n {\n q: \"How many flavors should we launch?\",\n type: \"radio\",\n options: [\"Three (core line)\", \"Five (full case)\", \"Just one hero\"],\n },\n {\n q: \"Which mix-ins should we stock?\",\n type: \"check\",\n options: [\"Chocolate chips\", \"Waffle bits\", \"Sprinkles\"],\n },\n {\n q: \"Which market do we enter first?\",\n type: \"radio\",\n options: [\"Food trucks\", \"Grocery freezers\", \"Scoop shops\"],\n },\n];\n\nexport type ApprovalLabels = {\n skip: string;\n continue: string;\n send: string;\n customPlaceholder: string;\n sentMessage: string;\n};\n\nconst DEFAULT_LABELS: ApprovalLabels = {\n skip: \"Skip\",\n continue: \"Continue\",\n send: \"Send\",\n customPlaceholder: \"Something else…\",\n sentMessage: \"Answers sent\",\n};\n\nconst ROLL_MS = 400;\nconst SLIDE = \"360ms cubic-bezier(0.22, 1, 0.36, 1)\";\n\n/* odometer digits — each character that changes rolls up (or down) */\nfunction RollingDigits({ value }: { value: string }) {\n const prevRef = useRef(value);\n const [oldVal, setOldVal] = useState(value);\n const [newVal, setNewVal] = useState(value);\n const [rolling, setRolling] = useState(false);\n const [shifted, setShifted] = useState(false);\n const [dir, setDir] = useState<\"up\" | \"down\">(\"up\");\n\n useEffect(() => {\n if (prevRef.current === value) return;\n const from = prevRef.current;\n prevRef.current = value;\n const fromN = parseInt(from, 10);\n const toN = parseInt(value, 10);\n setDir(Number.isFinite(fromN) && Number.isFinite(toN) && toN < fromN ? \"down\" : \"up\");\n setOldVal(from);\n setNewVal(value);\n setRolling(true);\n setShifted(false);\n\n let raf2 = 0;\n const raf1 = requestAnimationFrame(() => {\n raf2 = requestAnimationFrame(() => setShifted(true));\n });\n const done = setTimeout(() => {\n setRolling(false);\n setOldVal(value);\n setShifted(false);\n }, ROLL_MS);\n\n return () => {\n cancelAnimationFrame(raf1);\n cancelAnimationFrame(raf2);\n clearTimeout(done);\n };\n }, [value]);\n\n const chars = rolling ? newVal : oldVal;\n\n return (\n <>\n {Array.from({ length: chars.length }, (_, i) => {\n const o = oldVal[i] ?? \"\";\n const n = chars[i] ?? \"\";\n if (!rolling || o === n) {\n return <span key={`${i}-${n}`}>{n}</span>;\n }\n const top = dir === \"down\" ? n : o;\n const bottom = dir === \"down\" ? o : n;\n const restY = dir === \"down\" ? \"0\" : \"-1em\";\n const startY = dir === \"down\" ? \"-1em\" : \"0\";\n return (\n <span\n key={`${i}-${o}-${n}-${dir}`}\n style={{ display: \"inline-block\", position: \"relative\", overflow: \"hidden\", height: \"1em\", lineHeight: \"1em\", verticalAlign: \"-0.05em\" }}\n >\n <span\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n transition: \"transform 350ms cubic-bezier(0.4, 0, 0.2, 1)\",\n transform: `translateY(${shifted ? restY : startY})`,\n }}\n >\n <span style={{ height: \"1em\", lineHeight: \"1em\" }}>{top}</span>\n <span style={{ height: \"1em\", lineHeight: \"1em\" }}>{bottom}</span>\n </span>\n </span>\n );\n })}\n </>\n );\n}\n\nfunction Ico({ path, size = 14, sw = 2 }: { path: React.ReactNode; size?: number; sw?: number }) {\n return (\n <svg width={size} height={size} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={sw} strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n {path}\n </svg>\n );\n}\n\nfunction ApprovalCard({\n questions = QUESTIONS,\n labels,\n onSubmitted,\n onAnswerChange,\n resettable = true,\n}: {\n questions?: ApprovalQuestion[];\n labels?: Partial<ApprovalLabels>;\n onSubmitted?: (answers: Record<number, number[]>, customAnswers: Record<number, string>) => void;\n onAnswerChange?: (questionIndex: number, answer: number[]) => void;\n resettable?: boolean;\n variant?: string;\n} = {}) {\n const t = { ...DEFAULT_LABELS, ...labels };\n const [qi, setQi] = useState(0);\n const [answers, setAnswers] = useState<Record<number, number[]>>({});\n const [custom, setCustom] = useState<Record<number, string>>({});\n const answersRef = useRef(answers);\n answersRef.current = answers;\n const customRef = useRef(custom);\n customRef.current = custom;\n const [sent, setSent] = useState(false);\n const [open, setOpen] = useState(true);\n const reopenRef = useRef<HTMLButtonElement>(null);\n const sentStatusRef = useRef<HTMLDivElement>(null);\n const previousView = useRef({ open: true, sent: false });\n\n const advanceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const questionRefs = useRef<(HTMLDivElement | null)[]>([]);\n const measured = useRef(false);\n const [viewportH, setViewportH] = useState<number | undefined>(undefined);\n const [trackY, setTrackY] = useState(0);\n const [animate, setAnimate] = useState(false);\n // Until the first question is measured, render only the active one so the\n // initial (and SSR) height is Q1's height — not all questions stacked, which\n // would flash to full height and then shrink on mount.\n const [ready, setReady] = useState(false);\n\n const last = qi >= questions.length - 1;\n const selected = answers[qi] ?? [];\n const hasAnswer = selected.length > 0 || Boolean(custom[qi]?.trim());\n\n const sync = (withAnim: boolean) => {\n const item = questionRefs.current[qi];\n if (!item) return;\n const reduce = typeof window !== \"undefined\" && window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n setViewportH(item.offsetHeight);\n setTrackY(item.offsetTop);\n setAnimate(withAnim && !reduce);\n };\n\n useLayoutEffect(() => {\n const withAnim = measured.current;\n measured.current = true;\n sync(withAnim);\n setReady(true);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [qi, answers, custom, open, sent]);\n\n useEffect(() => {\n const previous = previousView.current;\n previousView.current = { open, sent };\n if (!open) reopenRef.current?.focus({ preventScroll: true });\n else if (sent) sentStatusRef.current?.focus({ preventScroll: true });\n else if (!previous.open || previous.sent) questionRefs.current[qi]?.querySelector<HTMLButtonElement>(\"button\")?.focus({ preventScroll: true });\n }, [open, sent, qi]);\n\n useEffect(() => {\n const focused = document.activeElement;\n const previousQuestion = questionRefs.current.find((item, index) => index !== qi && item?.contains(focused));\n if (previousQuestion) questionRefs.current[qi]?.querySelector<HTMLButtonElement>(\"button\")?.focus({ preventScroll: true });\n }, [qi]);\n\n useEffect(() => {\n setQi((current) => Math.max(0, Math.min(current, questions.length - 1)));\n }, [questions.length]);\n\n useEffect(() => {\n const id = requestAnimationFrame(() => sync(measured.current));\n return () => cancelAnimationFrame(id);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [qi]);\n\n useEffect(() => {\n const item = questionRefs.current[qi];\n if (!item || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(() => sync(false));\n observer.observe(item);\n return () => observer.disconnect();\n }, [qi, open, sent, questions]);\n\n useEffect(() => () => { if (advanceTimer.current) clearTimeout(advanceTimer.current); }, []);\n\n const goTo = (next: number) => {\n if (advanceTimer.current) clearTimeout(advanceTimer.current);\n setQi(Math.max(0, Math.min(Math.max(next, 0), questions.length - 1)));\n };\n\n const send = () => {\n if (advanceTimer.current) clearTimeout(advanceTimer.current);\n setSent(true);\n onSubmitted?.(answersRef.current, customRef.current);\n };\n\n const advance = () => {\n if (last) send();\n else goTo(qi + 1);\n };\n\n const toggle = (index: number) => {\n const type = questions[qi].type;\n const current = answersRef.current;\n const picked = current[qi] ?? [];\n const next = type === \"radio\" ? [index] : picked.includes(index) ? picked.filter((item) => item !== index) : [...picked, index];\n const updated = { ...current, [qi]: next };\n answersRef.current = updated;\n setAnswers(updated);\n onAnswerChange?.(qi, next);\n if (type === \"radio\") {\n setCustom((current) => ({ ...current, [qi]: \"\" }));\n if (advanceTimer.current) clearTimeout(advanceTimer.current);\n advanceTimer.current = setTimeout(() => {\n if (last) send();\n else setQi((current) => Math.min(questions.length - 1, current + 1));\n }, 480);\n }\n };\n\n const reset = () => {\n if (advanceTimer.current) clearTimeout(advanceTimer.current);\n setQi(0);\n setAnswers({});\n setCustom({});\n setSent(false);\n setOpen(true);\n measured.current = false;\n };\n\n if (questions.length === 0) return <div role=\"status\" className=\"text-[13px]\">No questions to approve.</div>;\n\n if (!open) {\n return (\n <button ref={reopenRef} type=\"button\" onClick={() => setOpen(true)} className=\"rounded-[8px] bg-[var(--mova-ai-surface,#fff)] px-3 py-2 text-[12.5px] font-medium text-[var(--mova-ai-ink,#202020)] shadow-[var(--mova-ai-shadow-btn,none)] transition-colors duration-150 hover:bg-[var(--mova-ai-hover,#f1f1f1)]\">\n Open approval\n </button>\n );\n }\n\n if (sent) {\n return (\n <div ref={sentStatusRef} tabIndex={-1} role=\"status\" className=\"flex w-full max-w-80 flex-wrap items-center gap-3\" style={{ animation: \"mova-ai-approval-card-pop-in 260ms cubic-bezier(0.23,1,0.32,1) both\" }}>\n <span className=\"inline-flex items-center gap-1.5 rounded-full bg-[var(--mova-ai-success-tint,#e8f3ec)] py-1 pr-2.5 pl-1 text-[12.5px] font-medium text-[var(--mova-ai-success,#28734d)]\">\n <span className=\"flex size-4.5 items-center justify-center rounded-full bg-[var(--mova-ai-success,#28734d)] text-white\">\n <svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"3\" strokeLinecap=\"round\" strokeLinejoin=\"round\"><path d=\"M20 6L9 17l-5-5\" /></svg>\n </span>\n {t.sentMessage}\n </span>\n {resettable && (\n <button type=\"button\" onClick={reset} className=\"text-[12px] font-medium text-[var(--mova-ai-ink-muted,#858585)] transition-colors duration-150 hover:text-[var(--mova-ai-ink,#202020)]\">\n Start over\n </button>\n )}\n </div>\n );\n }\n\n return (\n <div className=\"w-full max-w-80\">\n <div className=\"relative overflow-hidden rounded-lg border border-[var(--mova-ai-border,#dedede)] bg-[var(--mova-ai-surface,#fff)] text-[color:var(--mova-ai-ink,#202020)] shadow-[var(--mova-ai-shadow-card,none)]\" style={{ animation: \"mova-ai-approval-card-fade-up 380ms cubic-bezier(0.23,1,0.32,1) both\" }}>\n <button\n type=\"button\"\n aria-label=\"Dismiss\"\n onClick={() => { if (advanceTimer.current) clearTimeout(advanceTimer.current); setOpen(false); }}\n className=\"primitive-icon-button absolute right-2.5 top-2.5 z-10 text-[var(--mova-ai-ink-muted,#858585)] transition-colors duration-100 hover:bg-[var(--mova-ai-hover,#f1f1f1)] hover:text-[var(--mova-ai-ink,#202020)]\"\n >\n <Ico size={14} sw={2.2} path={<path d=\"M18 6L6 18M6 6l12 12\" />} />\n </button>\n <div className=\"primitive-card-pad\">\n {/* the question itself is the heading */}\n <div\n className=\"overflow-hidden\"\n style={{ height: viewportH, transition: animate ? `height ${SLIDE}` : undefined }}\n aria-live=\"polite\"\n >\n <div\n style={{\n position: \"relative\",\n display: \"flex\",\n flexDirection: \"column\",\n gap: 26,\n transform: `translate3d(0, ${-trackY}px, 0)`,\n transition: animate ? `transform ${SLIDE}` : undefined,\n willChange: \"transform\",\n }}\n >\n {questions.map((question, qIdx) => {\n const active = qIdx === qi;\n // Before the first measure, mount only the active question so the\n // card opens at its real height instead of flashing to full height.\n if (!ready && !active) return null;\n const picked = answers[qIdx] ?? [];\n const questionStyle: CSSProperties = {\n opacity: active ? 1 : 0,\n transition: animate ? `opacity ${SLIDE}` : undefined,\n pointerEvents: active ? undefined : \"none\",\n };\n return (\n <div\n key={qIdx}\n ref={(el) => { questionRefs.current[qIdx] = el; }}\n aria-hidden={active ? undefined : true}\n style={questionStyle}\n >\n <div className=\"pr-7 text-[14px] font-medium text-[var(--mova-ai-ink,#202020)]\">{question.q}</div>\n <GlideMenu className=\"mt-2.5 flex flex-col gap-1\" highlightClassName=\"inset-x-0 rounded-[8px] bg-[var(--mova-ai-hover,#f1f1f1)]\">\n {question.options.map((option, i) => {\n const on = picked.includes(i);\n return (\n <button\n key={option}\n type=\"button\"\n data-menu-row\n aria-pressed={on}\n tabIndex={active ? 0 : -1}\n onClick={() => { if (active) toggle(i); }}\n className=\"relative z-10 flex items-center gap-1.5 rounded-[8px] pl-1 pr-2 py-1 text-left transition-colors duration-100\"\n >\n <span\n className={`flex size-4 shrink-0 items-center justify-center transition-colors duration-200\n ${question.type === \"radio\" ? \"rounded-full\" : \"rounded-[5px]\"}\n ${on ? \"bg-[var(--mova-ai-ink,#202020)] text-[var(--mova-ai-canvas,#f4f4f4)]\" : \"shadow-[inset_0_0_0_1.5px_var(--mova-ai-border-strong,#c8c8c8)] text-transparent\"}`}\n >\n {question.type === \"radio\" ? (\n <span className=\"size-1.5 rounded-full bg-[var(--mova-ai-canvas,#f4f4f4)] transition-transform duration-200\" style={{ transform: on ? \"scale(1)\" : \"scale(0)\" }} />\n ) : (\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"3\" strokeLinecap=\"round\" strokeLinejoin=\"round\"><path d=\"M20 6L9 17l-5-5\" /></svg>\n )}\n </span>\n <span className={`text-[13px] leading-snug transition-colors duration-200 ${on ? \"text-[var(--mova-ai-ink,#202020)]\" : \"text-[var(--mova-ai-ink-secondary,#666)]\"}`}>\n {option}\n </span>\n </button>\n );\n })}\n <label data-menu-row className=\"relative z-10 flex items-center gap-1.5 rounded-[8px] pl-1 pr-2 py-1 transition-colors duration-100\">\n <input\n value={custom[qIdx] ?? \"\"}\n tabIndex={active ? 0 : -1}\n onChange={(event) => {\n if (!active) return;\n if (advanceTimer.current) clearTimeout(advanceTimer.current);\n setCustom((current) => ({ ...current, [qIdx]: event.target.value }));\n if (question.type === \"radio\") setAnswers((current) => ({ ...current, [qIdx]: [] }));\n }}\n onKeyDown={(event) => {\n if (event.key === \"Enter\" && hasAnswer) {\n event.preventDefault();\n advance();\n }\n }}\n placeholder={t.customPlaceholder}\n aria-label=\"Custom answer\"\n className=\"min-w-0 flex-1 bg-transparent pl-1.5 text-[13px] text-[var(--mova-ai-ink,#202020)] outline-none placeholder:text-[var(--mova-ai-ink-muted,#858585)]\"\n />\n </label>\n </GlideMenu>\n </div>\n );\n })}\n </div>\n </div>\n </div>\n\n {/* footer — step nav (rolling counter) + pill actions */}\n <div className=\"primitive-card-footer flex items-center justify-between gap-3\">\n <div className=\"flex items-center gap-1 text-[var(--mova-ai-ink-muted,#858585)]\">\n <button\n type=\"button\"\n aria-label=\"Previous question\"\n disabled={qi <= 0}\n onClick={() => goTo(qi - 1)}\n className=\"flex size-[18px] items-center justify-center rounded-[5px] transition-colors duration-100 enabled:hover:text-[var(--mova-ai-ink,#202020)] disabled:opacity-30\"\n >\n <Ico size={14} path={<path d=\"M18 15l-6-6-6 6\" />} />\n </button>\n <span className=\"inline-flex items-center text-[12px] font-medium tabular-nums text-[var(--mova-ai-ink-muted,#858585)]\" style={{ letterSpacing: \"-0.1px\", lineHeight: 1 }}>\n <RollingDigits value={`${qi + 1} / ${questions.length}`} />\n </span>\n <button\n type=\"button\"\n aria-label=\"Next question\"\n disabled={last}\n onClick={() => goTo(qi + 1)}\n className=\"flex size-[18px] items-center justify-center rounded-[5px] transition-colors duration-100 enabled:hover:text-[var(--mova-ai-ink,#202020)] disabled:opacity-30\"\n >\n <Ico size={14} path={<path d=\"M6 9l6 6 6-6\" />} />\n </button>\n </div>\n\n <div className=\"-mr-0.5 flex items-center gap-1.5\">\n <Button variant=\"ghost\" size=\"sm\" onClick={() => { if (advanceTimer.current) clearTimeout(advanceTimer.current); if (last) setOpen(false); else goTo(qi + 1); }}>\n {t.skip}\n </Button>\n <Button variant=\"accent\" size=\"sm\" disabled={!hasAnswer} onClick={advance}>\n {last ? t.send : t.continue}\n </Button>\n </div>\n </div>\n </div>\n </div>\n );\n}\n\ntype GlideMenuProps = {\n children: ReactNode;\n className?: string;\n highlightClassName?: string;\n rowSelector?: string;\n};\n\n/** A single hover layer that glides between interactive menu rows. */\nfunction GlideMenu({\n children,\n className = \"\",\n highlightClassName = \"inset-x-0 rounded-[8px] bg-[var(--mova-ai-hover,#f1f1f1)]\",\n rowSelector = \"[data-menu-row]\",\n}: GlideMenuProps) {\n const ref = useRef<HTMLDivElement>(null);\n const [box, setBox] = useState<{ top: number; height: number } | null>(null);\n const [visible, setVisible] = useState(false);\n\n const moveTo = (target: EventTarget | null) => {\n const container = ref.current;\n if (!(target instanceof Element) || !container) return;\n const row = target.closest(rowSelector);\n if (!(row instanceof HTMLElement) || !container.contains(row)) return;\n const containerRect = container.getBoundingClientRect();\n const rowRect = row.getBoundingClientRect();\n setBox({ top: rowRect.top - containerRect.top, height: rowRect.height });\n setVisible(true);\n };\n\n return (\n <div\n ref={ref}\n onMouseOver={(event) => moveTo(event.target)}\n onMouseLeave={() => setVisible(false)}\n onFocusCapture={(event) => moveTo(event.target)}\n onBlurCapture={(event) => {\n if (!ref.current?.contains(event.relatedTarget as Node | null)) setVisible(false);\n }}\n className={`group/glide-menu relative ${className}`}\n >\n <span\n aria-hidden\n className={`pointer-events-none absolute ${highlightClassName}`}\n style={{\n top: box?.top ?? 0,\n height: box?.height ?? 0,\n opacity: box && visible ? 1 : 0,\n transition:\n \"top 220ms cubic-bezier(0.23,1,0.32,1), height 220ms cubic-bezier(0.23,1,0.32,1), opacity 150ms ease\",\n }}\n />\n {children}\n </div>\n );\n}\n\nfunction Button({variant = \"secondary\",size = \"md\",className = \"\",...props}: React.ButtonHTMLAttributes<HTMLButtonElement> & {variant?: \"secondary\" | \"ghost\" | \"accent\";size?: \"sm\" | \"md\"}) {\n const variants = {secondary:\"bg-[var(--mova-ai-surface,#fff)] text-[var(--mova-ai-ink,#202020)] shadow-[var(--mova-ai-shadow-btn,none)] hover:bg-[var(--mova-ai-inset,#f8f8f8)]\", ghost:\"bg-[var(--mova-ai-hover-strong,#e9e9e9)] text-[var(--mova-ai-ink,#202020)] hover:bg-[var(--mova-ai-border-strong,#c8c8c8)]\", accent:\"approval-accent shadow-[inset_0_1px_0_rgba(255,255,255,0.14)]\"};\n return <button type=\"button\" className={`inline-flex items-center justify-center font-medium select-none transition-[transform,background-color,color,opacity,filter] duration-150 ease-out active:scale-[0.96] active:brightness-110 disabled:opacity-50 disabled:pointer-events-none ${variants[variant]} ${size === \"sm\" ? \"h-[27px] px-3 text-[13px] leading-none rounded-full gap-1.5\" : \"px-4 py-[9px] text-sm leading-none rounded-full gap-2\"} ${className}`} {...props}/>;\n}\n\n/* Smooth shadow values: shadow-plugin, Copyright (c) Florian Kiem, MIT. */\nconst AI_THEME_STYLES = \"/* Shared public tokens for the standalone AI component family. */\\n.mova-ai-theme {\\n color-scheme: light;\\n color: var(--mova-ai-ink, #202020);\\n font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \\\"Segoe UI\\\", sans-serif;\\n font-size: 13px;\\n line-height: 1.45;\\n letter-spacing: -0.01em;\\n\\n --mova-ai-page: #f7f7f7;\\n --mova-ai-canvas: #f4f4f4;\\n --mova-ai-surface: #ffffff;\\n --mova-ai-inset: #f8f8f8;\\n --mova-ai-hover: #f1f1f1;\\n --mova-ai-hover-strong: #e9e9e9;\\n\\n --mova-ai-ink: #202020;\\n --mova-ai-ink-secondary: #666666;\\n --mova-ai-ink-muted: #858585;\\n\\n --mova-ai-border: #dedede;\\n --mova-ai-border-soft: #ededed;\\n --mova-ai-border-strong: #c8c8c8;\\n --mova-ai-field: #f6f6f6;\\n --mova-ai-grid: color-mix(in srgb, var(--mova-ai-border) 78%, transparent);\\n --mova-ai-stripe: rgb(32 35 38 / 7%);\\n --mova-ai-stripe-bg: #242424;\\n\\n --mova-ai-accent: #454545;\\n --mova-ai-accent-ink: #333333;\\n --mova-ai-accent-foreground: #ffffff;\\n --mova-ai-accent-tint: #eeeeee;\\n --mova-ai-success: #28734d;\\n --mova-ai-success-tint: #e8f3ec;\\n --mova-ai-warning: #986517;\\n --mova-ai-warning-tint: #f8f0df;\\n --mova-ai-danger: #a33c3c;\\n --mova-ai-danger-tint: #f7eaea;\\n --mova-ai-info: #496b89;\\n --mova-ai-info-tint: #eaf0f5;\\n --mova-ai-neutral-status: #69737d;\\n --mova-ai-status-on: #ffffff;\\n\\n --mova-ai-tooltip-bg: #292929;\\n --mova-ai-tooltip-fg: #f6f6f6;\\n --mova-ai-tooltip-muted: #c3c3c3;\\n --mova-ai-tooltip-border: #484848;\\n\\n --mova-ai-radius-panel: 8px;\\n --mova-ai-radius-control: 8px;\\n --mova-ai-shadow-xs: 0 1px 2px rgb(22 28 33 / 5%);\\n --mova-ai-shadow-sm: 0 2px 8px rgb(22 28 33 / 7%);\\n --mova-ai-shadow-md: 0 8px 20px rgb(22 28 33 / 11%);\\n --mova-ai-shadow-lg: 0 14px 32px rgb(22 28 33 / 15%);\\n --mova-ai-shadow-hairline: 0 0 0 1px var(--mova-ai-border);\\n --mova-ai-shadow-btn: 0 0 0 1px var(--mova-ai-border-strong), var(--mova-ai-shadow-xs);\\n --mova-ai-shadow-card: 0 0 0 1px var(--mova-ai-border), var(--mova-ai-shadow-sm);\\n --mova-ai-shadow-raised: 0 0 0 1px var(--mova-ai-border), var(--mova-ai-shadow-md);\\n --mova-ai-shadow-overlay: 0 0 0 1px var(--mova-ai-border), var(--mova-ai-shadow-lg);\\n --mova-ai-shadow-inset-field: inset 0 1px 2px rgb(22 28 33 / 12%);\\n}\\n\\n.mova-ai-theme[data-theme=\\\"dark\\\"] {\\n color-scheme: dark;\\n --mova-ai-page: #181818;\\n --mova-ai-canvas: #202020;\\n --mova-ai-surface: #272727;\\n --mova-ai-inset: #1d1d1d;\\n --mova-ai-hover: #2d2d2d;\\n --mova-ai-hover-strong: #393939;\\n\\n --mova-ai-ink: #eeeeee;\\n --mova-ai-ink-secondary: #b5b5b5;\\n --mova-ai-ink-muted: #929292;\\n\\n --mova-ai-border: #3b3b3b;\\n --mova-ai-border-soft: #303030;\\n --mova-ai-border-strong: #505050;\\n --mova-ai-field: #2c2c2c;\\n --mova-ai-grid: color-mix(in srgb, var(--mova-ai-border) 78%, transparent);\\n --mova-ai-stripe: rgb(255 255 255 / 6%);\\n --mova-ai-stripe-bg: #202528;\\n\\n --mova-ai-accent: #c2c2c2;\\n --mova-ai-accent-ink: #d5d5d5;\\n --mova-ai-accent-foreground: #202020;\\n --mova-ai-accent-tint: #353535;\\n --mova-ai-success: #88c7a0;\\n --mova-ai-success-tint: #293b30;\\n --mova-ai-warning: #d7b66f;\\n --mova-ai-warning-tint: #3c3525;\\n --mova-ai-danger: #e5928e;\\n --mova-ai-danger-tint: #3c2b2b;\\n --mova-ai-info: #9bbbd6;\\n --mova-ai-info-tint: #293541;\\n --mova-ai-neutral-status: #a0a0a0;\\n --mova-ai-status-on: #181818;\\n\\n --mova-ai-tooltip-bg: #121212;\\n --mova-ai-tooltip-fg: #eeeeee;\\n --mova-ai-tooltip-muted: #b5b5b5;\\n --mova-ai-tooltip-border: #3b3b3b;\\n\\n --mova-ai-shadow-xs: 0 1px 2px rgb(0 0 0 / 22%);\\n --mova-ai-shadow-sm: 0 2px 8px rgb(0 0 0 / 25%);\\n --mova-ai-shadow-md: 0 8px 20px rgb(0 0 0 / 30%);\\n --mova-ai-shadow-lg: 0 14px 32px rgb(0 0 0 / 38%);\\n --mova-ai-shadow-hairline: 0 0 0 1px var(--mova-ai-border);\\n --mova-ai-shadow-btn: 0 0 0 1px var(--mova-ai-border-strong), var(--mova-ai-shadow-xs);\\n --mova-ai-shadow-card: 0 0 0 1px var(--mova-ai-border), var(--mova-ai-shadow-sm);\\n --mova-ai-shadow-raised: 0 0 0 1px var(--mova-ai-border), var(--mova-ai-shadow-md);\\n --mova-ai-shadow-overlay: 0 0 0 1px var(--mova-ai-border), var(--mova-ai-shadow-lg);\\n --mova-ai-shadow-inset-field: inset 0 1px 2px rgb(0 0 0 / 42%);\\n}\\n\";\nconst scopedStyles = `.mova-ai-approval-card{--approval-action-bg:#454545;--approval-action-fg:#fff;--approval-action-hover:#333;--approval-action-disabled-bg:#e9e9e9;--approval-action-disabled-fg:#858585} .mova-ai-approval-card[data-theme=\"dark\"]{--approval-action-bg:#f5f5f5;--approval-action-fg:#202020;--approval-action-hover:#fff;--approval-action-disabled-bg:#3b3b3b;--approval-action-disabled-fg:#929292} .mova-ai-approval-card .approval-accent{background-color:var(--approval-action-bg);color:var(--approval-action-fg)} .mova-ai-approval-card .approval-accent:not(:disabled):hover{background-color:var(--approval-action-hover)} .mova-ai-approval-card .approval-accent:disabled{background-color:var(--approval-action-disabled-bg);color:var(--approval-action-disabled-fg);opacity:1}\n.mova-ai-approval-card .primitive-card-pad{padding:12px} .mova-ai-approval-card .primitive-card-bar{padding:10px 12px} .mova-ai-approval-card .primitive-card-footer{padding:10px} .mova-ai-approval-card .primitive-icon-button{display:inline-flex;width:28px;height:28px;align-items:center;justify-content:center;border-radius:8px}\n.mova-ai-approval-card :is(button,input,[tabindex]):focus-visible{outline:2px solid var(--mova-ai-accent,#454545)!important;outline-offset:2px} .mova-ai-approval-card button:not(:disabled){cursor:pointer} .mova-ai-approval-card code{overflow-wrap:anywhere} .mova-ai-approval-card *, .mova-ai-approval-card *::before,.mova-ai-approval-card *::after{box-sizing:border-box}\n@keyframes mova-ai-approval-card-pop-in{from{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}\n@keyframes mova-ai-approval-card-fade-up{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}\n@keyframes mova-ai-approval-card-shimmer-text{from{background-position:150% center}to{background-position:-50% center}}\n@media(prefers-reduced-motion:reduce){.mova-ai-approval-card *, .mova-ai-approval-card *::before, .mova-ai-approval-card *::after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}`;\n\nexport function AIApprovalCard({theme = \"light\", ...props}: React.ComponentProps<typeof ApprovalCard> & {theme?: \"light\" | \"dark\"} = {}) {\n return <div className=\"mova-ai-theme mova-ai-approval-card\" data-theme={theme}><style>{AI_THEME_STYLES}{scopedStyles}</style><ApprovalCard {...props} /></div>;\n}\n"
17
17
  }