@rafaelaugusto/jolt 0.1.0-beta.0 → 0.1.0-beta.10

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 (307) hide show
  1. package/README.md +441 -25
  2. package/dist/animate.d.ts +21 -5
  3. package/dist/animate.d.ts.map +1 -1
  4. package/dist/animate.js +205 -25
  5. package/dist/animate.js.map +1 -1
  6. package/dist/color.d.ts +11 -0
  7. package/dist/color.d.ts.map +1 -0
  8. package/dist/color.js +73 -0
  9. package/dist/color.js.map +1 -0
  10. package/dist/config.d.ts +31 -0
  11. package/dist/config.d.ts.map +1 -0
  12. package/dist/config.js +73 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/decay.d.ts +11 -0
  15. package/dist/decay.d.ts.map +1 -0
  16. package/dist/decay.js +22 -0
  17. package/dist/decay.js.map +1 -0
  18. package/dist/decoration.d.ts +18 -0
  19. package/dist/decoration.d.ts.map +1 -0
  20. package/dist/decoration.js +155 -0
  21. package/dist/decoration.js.map +1 -0
  22. package/dist/drag-input.d.ts +15 -0
  23. package/dist/drag-input.d.ts.map +1 -0
  24. package/dist/drag-input.js +66 -0
  25. package/dist/drag-input.js.map +1 -0
  26. package/dist/drag-physics.d.ts +20 -0
  27. package/dist/drag-physics.d.ts.map +1 -0
  28. package/dist/drag-physics.js +75 -0
  29. package/dist/drag-physics.js.map +1 -0
  30. package/dist/drag.d.ts +58 -0
  31. package/dist/drag.d.ts.map +1 -0
  32. package/dist/drag.js +324 -0
  33. package/dist/drag.js.map +1 -0
  34. package/dist/driver/android-color.d.ts +20 -0
  35. package/dist/driver/android-color.d.ts.map +1 -0
  36. package/dist/driver/android-color.js +136 -0
  37. package/dist/driver/android-color.js.map +1 -0
  38. package/dist/driver/android-decoration.d.ts +22 -0
  39. package/dist/driver/android-decoration.d.ts.map +1 -0
  40. package/dist/driver/android-decoration.js +139 -0
  41. package/dist/driver/android-decoration.js.map +1 -0
  42. package/dist/driver/android-playback.d.ts +13 -0
  43. package/dist/driver/android-playback.d.ts.map +1 -0
  44. package/dist/driver/android-playback.js +141 -0
  45. package/dist/driver/android-playback.js.map +1 -0
  46. package/dist/driver/android.d.ts +1 -1
  47. package/dist/driver/android.d.ts.map +1 -1
  48. package/dist/driver/android.js +165 -13
  49. package/dist/driver/android.js.map +1 -1
  50. package/dist/driver/index.d.ts +25 -2
  51. package/dist/driver/index.d.ts.map +1 -1
  52. package/dist/driver/index.js.map +1 -1
  53. package/dist/driver/ios-color.d.ts +16 -0
  54. package/dist/driver/ios-color.d.ts.map +1 -0
  55. package/dist/driver/ios-color.js +392 -0
  56. package/dist/driver/ios-color.js.map +1 -0
  57. package/dist/driver/ios-decoration.d.ts +17 -0
  58. package/dist/driver/ios-decoration.d.ts.map +1 -0
  59. package/dist/driver/ios-decoration.js +271 -0
  60. package/dist/driver/ios-decoration.js.map +1 -0
  61. package/dist/driver/ios-layout.d.ts +9 -0
  62. package/dist/driver/ios-layout.d.ts.map +1 -0
  63. package/dist/driver/ios-layout.js +52 -0
  64. package/dist/driver/ios-layout.js.map +1 -0
  65. package/dist/driver/ios-playback.d.ts +10 -0
  66. package/dist/driver/ios-playback.d.ts.map +1 -0
  67. package/dist/driver/ios-playback.js +47 -0
  68. package/dist/driver/ios-playback.js.map +1 -0
  69. package/dist/driver/ios-shadow.d.ts +6 -0
  70. package/dist/driver/ios-shadow.d.ts.map +1 -0
  71. package/dist/driver/ios-shadow.js +52 -0
  72. package/dist/driver/ios-shadow.js.map +1 -0
  73. package/dist/driver/ios.d.ts.map +1 -1
  74. package/dist/driver/ios.js +164 -20
  75. package/dist/driver/ios.js.map +1 -1
  76. package/dist/engine.d.ts +76 -5
  77. package/dist/engine.d.ts.map +1 -1
  78. package/dist/engine.js +303 -23
  79. package/dist/engine.js.map +1 -1
  80. package/dist/haptics.d.ts +21 -0
  81. package/dist/haptics.d.ts.map +1 -0
  82. package/dist/haptics.js +120 -0
  83. package/dist/haptics.js.map +1 -0
  84. package/dist/image-effect-native.d.ts +26 -0
  85. package/dist/image-effect-native.d.ts.map +1 -0
  86. package/dist/image-effect-native.js +73 -0
  87. package/dist/image-effect-native.js.map +1 -0
  88. package/dist/image-effect.d.ts +50 -0
  89. package/dist/image-effect.d.ts.map +1 -0
  90. package/dist/image-effect.js +290 -0
  91. package/dist/image-effect.js.map +1 -0
  92. package/dist/image-owner.d.ts +3 -0
  93. package/dist/image-owner.d.ts.map +1 -0
  94. package/dist/image-owner.js +11 -0
  95. package/dist/image-owner.js.map +1 -0
  96. package/dist/index.d.ts +45 -4
  97. package/dist/index.d.ts.map +1 -1
  98. package/dist/index.js +21 -2
  99. package/dist/index.js.map +1 -1
  100. package/dist/interaction-surface.d.ts +12 -0
  101. package/dist/interaction-surface.d.ts.map +1 -0
  102. package/dist/interaction-surface.js +36 -0
  103. package/dist/interaction-surface.js.map +1 -0
  104. package/dist/items.d.ts +20 -0
  105. package/dist/items.d.ts.map +1 -0
  106. package/dist/items.js +109 -0
  107. package/dist/items.js.map +1 -0
  108. package/dist/keyboard.d.ts +41 -0
  109. package/dist/keyboard.d.ts.map +1 -0
  110. package/dist/keyboard.js +256 -0
  111. package/dist/keyboard.js.map +1 -0
  112. package/dist/label.d.ts +7 -0
  113. package/dist/label.d.ts.map +1 -0
  114. package/dist/label.js +60 -0
  115. package/dist/label.js.map +1 -0
  116. package/dist/layout.d.ts +13 -0
  117. package/dist/layout.d.ts.map +1 -0
  118. package/dist/layout.js +348 -0
  119. package/dist/layout.js.map +1 -0
  120. package/dist/liquid-grid.d.ts +3 -0
  121. package/dist/liquid-grid.d.ts.map +1 -0
  122. package/dist/liquid-grid.js +24 -0
  123. package/dist/liquid-grid.js.map +1 -0
  124. package/dist/liquid.d.ts +18 -0
  125. package/dist/liquid.d.ts.map +1 -0
  126. package/dist/liquid.js +234 -0
  127. package/dist/liquid.js.map +1 -0
  128. package/dist/loader.d.ts +19 -0
  129. package/dist/loader.d.ts.map +1 -0
  130. package/dist/loader.js +173 -0
  131. package/dist/loader.js.map +1 -0
  132. package/dist/motion-scope.d.ts +39 -0
  133. package/dist/motion-scope.d.ts.map +1 -0
  134. package/dist/motion-scope.js +238 -0
  135. package/dist/motion-scope.js.map +1 -0
  136. package/dist/motion-states.d.ts +36 -0
  137. package/dist/motion-states.d.ts.map +1 -0
  138. package/dist/motion-states.js +165 -0
  139. package/dist/motion-states.js.map +1 -0
  140. package/dist/particles.d.ts +39 -0
  141. package/dist/particles.d.ts.map +1 -0
  142. package/dist/particles.js +265 -0
  143. package/dist/particles.js.map +1 -0
  144. package/dist/path.d.ts +12 -0
  145. package/dist/path.d.ts.map +1 -0
  146. package/dist/path.js +90 -0
  147. package/dist/path.js.map +1 -0
  148. package/dist/playback-group.d.ts +6 -0
  149. package/dist/playback-group.d.ts.map +1 -0
  150. package/dist/playback-group.js +141 -0
  151. package/dist/playback-group.js.map +1 -0
  152. package/dist/playback.d.ts +25 -0
  153. package/dist/playback.d.ts.map +1 -0
  154. package/dist/playback.js +35 -0
  155. package/dist/playback.js.map +1 -0
  156. package/dist/presets.d.ts +16 -1
  157. package/dist/presets.d.ts.map +1 -1
  158. package/dist/presets.js +24 -6
  159. package/dist/presets.js.map +1 -1
  160. package/dist/press.d.ts.map +1 -1
  161. package/dist/press.js +25 -7
  162. package/dist/press.js.map +1 -1
  163. package/dist/properties.d.ts +6 -3
  164. package/dist/properties.d.ts.map +1 -1
  165. package/dist/properties.js +26 -2
  166. package/dist/properties.js.map +1 -1
  167. package/dist/reveal.d.ts +25 -0
  168. package/dist/reveal.d.ts.map +1 -0
  169. package/dist/reveal.js +294 -0
  170. package/dist/reveal.js.map +1 -0
  171. package/dist/roll-diff.d.ts +22 -0
  172. package/dist/roll-diff.d.ts.map +1 -0
  173. package/dist/roll-diff.js +36 -0
  174. package/dist/roll-diff.js.map +1 -0
  175. package/dist/roll.d.ts +29 -0
  176. package/dist/roll.d.ts.map +1 -0
  177. package/dist/roll.js +442 -0
  178. package/dist/roll.js.map +1 -0
  179. package/dist/scroll-linked.d.ts +10 -0
  180. package/dist/scroll-linked.d.ts.map +1 -0
  181. package/dist/scroll-linked.js +58 -0
  182. package/dist/scroll-linked.js.map +1 -0
  183. package/dist/sequence.d.ts +13 -7
  184. package/dist/sequence.d.ts.map +1 -1
  185. package/dist/sequence.js +214 -19
  186. package/dist/sequence.js.map +1 -1
  187. package/dist/shimmer.d.ts +12 -0
  188. package/dist/shimmer.d.ts.map +1 -0
  189. package/dist/shimmer.js +161 -0
  190. package/dist/shimmer.js.map +1 -0
  191. package/dist/svg-path.d.ts +14 -0
  192. package/dist/svg-path.d.ts.map +1 -0
  193. package/dist/svg-path.js +281 -0
  194. package/dist/svg-path.js.map +1 -0
  195. package/dist/symbol-effect.d.ts +23 -0
  196. package/dist/symbol-effect.d.ts.map +1 -0
  197. package/dist/symbol-effect.js +187 -0
  198. package/dist/symbol-effect.js.map +1 -0
  199. package/dist/text-layout.d.ts +30 -0
  200. package/dist/text-layout.d.ts.map +1 -0
  201. package/dist/text-layout.js +185 -0
  202. package/dist/text-layout.js.map +1 -0
  203. package/dist/text.d.ts +17 -0
  204. package/dist/text.d.ts.map +1 -0
  205. package/dist/text.js +263 -0
  206. package/dist/text.js.map +1 -0
  207. package/dist/track.d.ts +3 -1
  208. package/dist/track.d.ts.map +1 -1
  209. package/dist/track.js +266 -13
  210. package/dist/track.js.map +1 -1
  211. package/dist/transition.d.ts +17 -1
  212. package/dist/transition.d.ts.map +1 -1
  213. package/dist/transition.js +41 -6
  214. package/dist/transition.js.map +1 -1
  215. package/dist/types.d.ts +50 -1
  216. package/dist/types.d.ts.map +1 -1
  217. package/dist/vue/index.d.ts +95 -0
  218. package/dist/vue/index.d.ts.map +1 -0
  219. package/dist/vue/index.js +163 -0
  220. package/dist/vue/index.js.map +1 -0
  221. package/dist/zoom-native.d.ts +30 -0
  222. package/dist/zoom-native.d.ts.map +1 -0
  223. package/dist/zoom-native.js +56 -0
  224. package/dist/zoom-native.js.map +1 -0
  225. package/dist/zoomable.d.ts +48 -0
  226. package/dist/zoomable.d.ts.map +1 -0
  227. package/dist/zoomable.js +249 -0
  228. package/dist/zoomable.js.map +1 -0
  229. package/package.json +33 -5
  230. package/platforms/android/include.gradle +7 -0
  231. package/platforms/android/java/org/rafaelaugusto/jolt/ColorTarget.java +102 -0
  232. package/platforms/android/java/org/rafaelaugusto/jolt/DecorationTarget.java +154 -0
  233. package/platforms/android/java/org/rafaelaugusto/jolt/ImageEffect.java +288 -0
  234. package/platforms/android/java/org/rafaelaugusto/jolt/KeyboardFollower.java +91 -0
  235. package/platforms/android/java/org/rafaelaugusto/jolt/KeyboardObserver.java +97 -0
  236. package/platforms/android/java/org/rafaelaugusto/jolt/NumberRoll.java +190 -0
  237. package/platforms/android/java/org/rafaelaugusto/jolt/PhotoZoom.java +250 -0
  238. package/platforms/android/java/org/rafaelaugusto/jolt/TextReveal.java +180 -0
  239. package/platforms/android/proguard-rules.pro +19 -0
  240. package/platforms/ios/build.xcconfig +1 -0
  241. package/platforms/ios/src/JoltImageEffect.h +25 -0
  242. package/platforms/ios/src/JoltImageEffect.mm +242 -0
  243. package/platforms/ios/src/JoltInteractiveAnimation.h +18 -0
  244. package/platforms/ios/src/JoltInteractiveAnimation.m +123 -0
  245. package/platforms/ios/src/JoltPhotoZoom.h +23 -0
  246. package/platforms/ios/src/JoltPhotoZoom.m +229 -0
  247. package/platforms/ios/src/module.modulemap +6 -0
  248. package/src/android-plugin.d.ts +155 -0
  249. package/src/animate.ts +278 -38
  250. package/src/color.ts +101 -0
  251. package/src/config.ts +84 -0
  252. package/src/decay.ts +45 -0
  253. package/src/decoration.ts +180 -0
  254. package/src/drag-input.ts +74 -0
  255. package/src/drag-physics.ts +110 -0
  256. package/src/drag.ts +395 -0
  257. package/src/driver/android-color.ts +170 -0
  258. package/src/driver/android-decoration.ts +182 -0
  259. package/src/driver/android-playback.ts +157 -0
  260. package/src/driver/android.ts +208 -20
  261. package/src/driver/index.ts +37 -2
  262. package/src/driver/ios-color.ts +441 -0
  263. package/src/driver/ios-decoration.ts +332 -0
  264. package/src/driver/ios-layout.ts +63 -0
  265. package/src/driver/ios-playback.ts +51 -0
  266. package/src/driver/ios-shadow.ts +50 -0
  267. package/src/driver/ios.ts +194 -25
  268. package/src/engine.ts +329 -27
  269. package/src/haptics.ts +148 -0
  270. package/src/image-effect-native.ts +129 -0
  271. package/src/image-effect.ts +336 -0
  272. package/src/image-owner.ts +12 -0
  273. package/src/index.ts +61 -4
  274. package/src/interaction-surface.ts +41 -0
  275. package/src/ios-plugin.d.ts +81 -0
  276. package/src/items.ts +140 -0
  277. package/src/keyboard.ts +291 -0
  278. package/src/label.ts +76 -0
  279. package/src/layout.ts +408 -0
  280. package/src/liquid-grid.ts +28 -0
  281. package/src/liquid.ts +287 -0
  282. package/src/loader.ts +202 -0
  283. package/src/motion-scope.ts +279 -0
  284. package/src/motion-states.ts +215 -0
  285. package/src/particles.ts +323 -0
  286. package/src/path.ts +124 -0
  287. package/src/playback-group.ts +143 -0
  288. package/src/playback.ts +62 -0
  289. package/src/presets.ts +31 -7
  290. package/src/press.ts +32 -10
  291. package/src/properties.ts +32 -6
  292. package/src/reveal.ts +374 -0
  293. package/src/roll-diff.ts +57 -0
  294. package/src/roll.ts +515 -0
  295. package/src/scroll-linked.ts +73 -0
  296. package/src/sequence.ts +212 -19
  297. package/src/shimmer.ts +180 -0
  298. package/src/svg-path.ts +355 -0
  299. package/src/symbol-effect.ts +226 -0
  300. package/src/text-layout.ts +228 -0
  301. package/src/text.ts +279 -0
  302. package/src/track.ts +347 -12
  303. package/src/transition.ts +63 -6
  304. package/src/types.ts +51 -1
  305. package/src/vue/index.ts +231 -0
  306. package/src/zoom-native.ts +124 -0
  307. package/src/zoomable.ts +330 -0
package/README.md CHANGED
@@ -1,6 +1,23 @@
1
- # Jolt
2
-
3
- Native animations for [NativeScript](https://nativescript.org): presets, springs, keyframes and stagger, one call each.
1
+ <p align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/rafaelaugustos/jolt/main/.github/assets/logo-dark.png">
4
+ <img src="https://raw.githubusercontent.com/rafaelaugustos/jolt/main/.github/assets/logo.png" alt="Jolt" width="280">
5
+ </picture>
6
+ </p>
7
+
8
+ <p align="center">
9
+ Native animations for <a href="https://nativescript.org">NativeScript</a>: presets, springs, keyframes and stagger, one call each.
10
+ </p>
11
+
12
+ <p align="center">
13
+ <a href="https://www.npmjs.com/package/@rafaelaugusto/jolt"><img src="https://img.shields.io/npm/v/@rafaelaugusto/jolt/next?label=npm%40next" alt="npm"></a>
14
+ <a href="https://github.com/rafaelaugustos/jolt/actions/workflows/ci.yml"><img src="https://github.com/rafaelaugustos/jolt/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
15
+ <a href="https://github.com/rafaelaugustos/jolt/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@rafaelaugusto/jolt" alt="MIT license"></a>
16
+ </p>
17
+
18
+ <p align="center">
19
+ <a href="https://jolt.rafaelaugusto.dev">Documentation</a> · <a href="https://jolt.rafaelaugusto.dev/pt-BR/">Documentação em português</a>
20
+ </p>
4
21
 
5
22
  ```ts
6
23
  import { animate } from '@rafaelaugusto/jolt'
@@ -14,6 +31,42 @@ NativeScript's `view.animate()` covers the basics and stops there: one curve for
14
31
 
15
32
  Jolt computes the motion once, in JavaScript, when the animation starts, and hands it to the platform as keyframes: Core Animation on iOS, `ObjectAnimator` on Android. Nothing runs in JavaScript while it plays. Springs follow real physics, and an animation that interrupts another one starts from where the view is, at the speed it was going.
16
33
 
34
+ ## Features
35
+
36
+ - [25 presets](#presets) to enter, exit and draw attention: `fadeInUp`, `popIn`, `shake`, `heartbeat`...
37
+ - [Springs](#springs) described by feel (`bouncy`), by `bounce` and `duration`, or by stiffness, damping and mass.
38
+ - [Corners, borders and shadows](#corners-borders-and-shadows) with native playback and selective stopping.
39
+ - [Motion paths](#motion-along-a-path) with optional tangent orientation on iOS and Android.
40
+ - [Colors](#colors) for backgrounds, borders and text, interpolated in OKLab on iOS and Android.
41
+ - [Motion scopes and states](https://jolt.rafaelaugusto.dev/guide/motion-controllers) for component cleanup and coordinated visual states.
42
+ - [Animated touch targets](https://jolt.rafaelaugusto.dev/guide/interaction-surface) with an opt-in native interaction surface.
43
+ - [Layout changes](https://jolt.rafaelaugusto.dev/guide/layout): insertion, removal, reorder and resize of container children.
44
+ - [Drag](https://jolt.rafaelaugusto.dev/guide/drag) with bounds, snap points, rubberband and native release physics.
45
+ - [Decay](#decay) from a release velocity, with optional snap points.
46
+ - [Keyframes and tweens](#tweens) with an easing per segment and exact keyframe times.
47
+ - [Stagger](#several-views) across several views, from the first, the last or the center.
48
+ - [List rows](#list-rows) that play an entrance as they scroll into view, every time or once per item.
49
+ - [Sequences](#sequences) with overlapping arrivals, shared starts and absolute timeline positions.
50
+ - [Press effects](#press) that follow the finger and let go when a scroll takes over.
51
+ - [Vue directives and a transition component](#vue) from the optional `@rafaelaugusto/jolt/vue` entry.
52
+ - [Text entrances](#text) by letter or word on iOS and Android, preserving the Label’s layout and accessibility.
53
+ - [Rolling numbers](#rolling-numbers) that slide only the changed digits in and out, on iOS and Android.
54
+ - [SF Symbol effects](#sf-symbols) and animated icon replacement on iOS 17+.
55
+ - [Shimmer](#shimmer) for loading placeholders on iOS.
56
+ - [Liquid images](#liquid-images) with native texture deformation on iOS.
57
+ - [Image effects](https://jolt.rafaelaugusto.dev/guide/image-effects): ripple, touch distortion and dissolve with native Metal/AGSL rendering and explicit fallback.
58
+ - [Photo zoom and pan](https://jolt.rafaelaugusto.dev/guide/photo-zoom): native pinch, double tap, bounds and vertical dismissal intents.
59
+ - [Loaders](#loaders) with dots, bars and ring styles on iOS.
60
+ - [Circular reveal](#circular-reveal) to show or hide content from a point on iOS.
61
+ - [Haptics](#haptics) at arrival or at marked points in an animation on iOS.
62
+ - [Particles and confetti](#particles-and-confetti) on iOS.
63
+ - [Scroll-linked animations](#scroll-linked-animations) for parallax and collapsing headers.
64
+ - [Keyboard following](#follow-the-keyboard) for composers that rise with the keyboard, at its speed and curve.
65
+ - [Playback controls](#pause-play-and-scrub) to pause, continue and scrub a finite timeline.
66
+ - [Time scale and reduced motion](#time-scale-and-reduced-motion): slow everything down while tuning, and follow the platform's reduced-motion setting by default.
67
+ - [Interruptions](#interrupting-and-stopping) that pick up the value and velocity on screen, with no jump.
68
+ - [Native playback](#how-it-works): Core Animation on iOS, `ObjectAnimator` on Android, nothing in JavaScript while it plays.
69
+
17
70
  ## Install
18
71
 
19
72
  ```sh
@@ -22,6 +75,8 @@ npm install @rafaelaugusto/jolt@next
22
75
 
23
76
  It's in beta for now, published under the `next` tag. Requires `@nativescript/core` 8.9 or later. It doesn't depend on a framework: it takes a view, or an element that holds one.
24
77
 
78
+ On iOS 15+, Jolt requests up to 120 Hz on ProMotion displays. Set `CADisableMinimumFrameDurationOnPhone` to `true` in the app's `App_Resources/iOS/Info.plist` and rebuild to enable higher rates on iPhone. See the [iOS setup](https://jolt.rafaelaugusto.dev/guide/install#promotion-on-ios); the system still chooses the actual refresh rate.
79
+
25
80
  ## Animating
26
81
 
27
82
  `animate(target, keyframes, transition?)` takes a view and where to take it:
@@ -30,14 +85,17 @@ It's in beta for now, published under the `next` tag. Requires `@nativescript/co
30
85
  animate(card, { opacity: 0.5, y: -20 })
31
86
  ```
32
87
 
33
- | Property | Unit |
34
- | -------------------- | ---------------------------------------- |
35
- | `opacity` | |
36
- | `x`, `y` | DIPs, same as `translateX`, `translateY` |
37
- | `scale` | sets `scaleX` and `scaleY` together |
38
- | `scaleX`, `scaleY` | |
39
- | `rotate` | degrees |
40
- | `rotateX`, `rotateY` | degrees, with perspective |
88
+ | Property | Unit |
89
+ | ----------------------------------------- | ---------------------------------------- |
90
+ | `opacity` | |
91
+ | `x`, `y` | DIPs, same as `translateX`, `translateY` |
92
+ | `scale` | sets `scaleX` and `scaleY` together |
93
+ | `scaleX`, `scaleY` | |
94
+ | `rotate` | degrees |
95
+ | `rotateX`, `rotateY` | degrees, with perspective |
96
+ | `backgroundColor`, `borderColor`, `color` | NativeScript color string or `Color` |
97
+
98
+ 3D rotations and flip presets use the view's `perspective` on both platforms (NativeScript defaults to 1000 DIPs). Set `card.perspective = 600` for stronger depth. Android applies the display density when configuring its camera distance; it keeps the perspective when a rotation ends or stops at an angle.
41
99
 
42
100
  A single value animates from wherever the view is. An array is a list of keyframes, and `null` stands for the current value:
43
101
 
@@ -46,6 +104,15 @@ animate(card, { x: [0, 100] }) // from 0, whatever x is now
46
104
  animate(card, { x: [null, 20, -20, null] }) // out and back to where it was
47
105
  ```
48
106
 
107
+ With `relative: true`, translation and rotation keyframes are offsets from the current value. Scale and opacity keyframes multiply it instead. Each scale axis keeps its own base, and `null` still means the current value.
108
+
109
+ ```ts
110
+ animate(card, { x: [0, -10, 10, 0] }, { relative: true }) // shake around its current x
111
+ animate(card, { scale: [1, 1.06, 1] }, { relative: true }) // grow by 6% and return
112
+ ```
113
+
114
+ The base is captured once when the view is ready and its tracks are created, before any delay or stagger. Repeats reuse that base. A relative step in a sequence resolves when the step starts; an unloaded view waits until it loads.
115
+
49
116
  The target can be a view, or anything that holds one: a NativeScript-Vue template ref, an Angular `ElementRef`.
50
117
 
51
118
  ```vue
@@ -59,11 +126,69 @@ const open = () => animate(card.value, 'popIn')
59
126
  </template>
60
127
  ```
61
128
 
62
- A view that isn't loaded yet takes its first keyframe right away and plays once it's on screen. Calling `animate(title, 'fadeInUp')` in `onMounted` doesn't flash the title before it fades in.
129
+ A view that isn't loaded yet takes its first absolute keyframe right away and plays once it's on screen. Calling `animate(title, 'fadeInUp')` in `onMounted` doesn't flash the title before it fades in. Relative animations leave the view's values alone until it loads.
130
+
131
+ ## Vue
132
+
133
+ ```vue
134
+ <script setup lang="ts">
135
+ import { JoltTransition, vAnimate, vPress } from '@rafaelaugusto/jolt/vue'
136
+ </script>
137
+
138
+ <template>
139
+ <Label v-animate="'fadeInUp'" text="Hello" />
140
+ <GridLayout v-press="{ scale: 0.95 }" />
141
+ <JoltTransition enter="popIn" leave="fadeOut">
142
+ <Label v-if="saved" text="Saved" />
143
+ </JoltTransition>
144
+ </template>
145
+ ```
146
+
147
+ `@rafaelaugusto/jolt/vue` is a separate entry point for NativeScript-Vue 3, so the core stays framework free. `v-animate` plays when the element mounts and again when its value changes, `v-press` is `press()` for templates, and `JoltTransition` wraps Vue's `BaseTransition` so a view can animate out before `v-if` removes it. Register everything with `app.use(Jolt)` or import what a component needs. See the [Vue guide](https://jolt.rafaelaugusto.dev/guide/vue).
148
+
149
+ ## Motion along a path
150
+
151
+ ```ts
152
+ animate(
153
+ plane,
154
+ { path: 'M0 0 C80 -120 200 -120 280 0', orient: true },
155
+ { duration: 1200, ease: 'linear' },
156
+ )
157
+ ```
158
+
159
+ SVG coordinates are translations in DIPs. `orient: true` aligns artwork facing +X with the forward tangent. One shared progression drives native `x`, `y` and optional `rotate` tracks, with arc-length sampling, tweens, springs, delay, repeats, stagger, relative offsets and playback controls. Paths own those coordinates; use a `path` transition override to time them together. Parsing and sampling happen once; no JavaScript runs per frame. See the [path guide](https://jolt.rafaelaugusto.dev/guide/paths) for SVG commands, orientation and geometry limits.
160
+
161
+ ## Corners, borders and shadows
162
+
163
+ ```ts
164
+ animate(card, { borderRadius: 40, borderWidth: 6 }, { duration: 800 })
165
+ ```
166
+
167
+ Uniform circular borders work on iOS and Android. On iOS, configure one outer `boxShadow` and animate `shadowOpacity`, `shadowRadius` (half Core's CSS blur radius), `shadowOffsetX` and `shadowOffsetY`. On Android, animate `elevation` and `translationZ` for native shadows. Dimensions use DIPs; interruption, springs, repeats and selective stop work as usual. Border width animates the stroke while content uses the final layout. Clip paths, nonuniform geometry and Android radius changes with CSS shadows are rejected. Rebuild Android after upgrading. See the [guide and platform limits](https://jolt.rafaelaugusto.dev/guide/decorations).
168
+
169
+ ## Colors
170
+
171
+ ```ts
172
+ animate(
173
+ card,
174
+ {
175
+ backgroundColor: '#263b27',
176
+ borderColor: ['#b6ee64', '#e5a1ff'],
177
+ color: '#f4caff',
178
+ },
179
+ { duration: 800 },
180
+ )
181
+ ```
182
+
183
+ Color strings and NativeScript `Color` objects work as targets or keyframes; `null` starts from the visible color. Colors interpolate in premultiplied OKLab and stay absolute with `relative: true`. Interruptions, repeats and `stop(card, ['backgroundColor'])` use the same API as transforms. Colors default to a 300 ms tween.
184
+
185
+ `borderColor` requires the same starting color on every edge; widths and corner radii may differ. Text color supports Label and Button on iOS, and TextBase on Android. Explicit formatted-span colors stay intact. On iOS, text uses a native alpha-mask layer preserving UILabel's layout; emoji, attachments, text shadows and masks above four million pixels keep the final model color and resolve `false`. Edits, restyling and unloading cancel text playback and restore the current text. Gradients and images themselves are not interpolated. iOS background-color playback on an image background is skipped to preserve Core’s rasterized image; the final model color is applied. See the [color guide](https://jolt.rafaelaugusto.dev/guide/colors).
186
+
187
+ Android includes a Java animation target; rebuild the native app after installing or upgrading Jolt.
63
188
 
64
189
  ## Transitions
65
190
 
66
- The third argument says how to get there. Without one, transforms move with a spring and `opacity` eases over 300ms. Keyframes tween.
191
+ The third argument says how to get there. Without one, transforms move with a spring and opacity and colors ease over 300ms. Keyframes tween.
67
192
 
68
193
  ### Springs
69
194
 
@@ -94,6 +219,18 @@ animate(
94
219
 
95
220
  Tweens take 300ms and `easeOut` unless told otherwise. `ease` takes `linear`, `ease`, `easeIn`, `easeOut`, `easeInOut`, `circIn`, `circOut`, `circInOut`, `backIn`, `backOut`, `backInOut`, `anticipate`, a cubic bezier (`[0.2, 0, 0, 1]`) or any function from 0..1 to a value. With keyframes, pass one per segment. `times` places each keyframe between 0 and 1.
96
221
 
222
+ With `easeTrack: true`, one `ease` runs over the whole track instead, and the value moves in straight lines between keyframes, the way React Native's `interpolate` places its output range on an eased value:
223
+
224
+ ```ts
225
+ animate(
226
+ spark,
227
+ { y: [0, -30, -12, 0] },
228
+ { duration: 900, ease: 'easeOut', easeTrack: true, times: [0, 0.4, 0.7, 1] },
229
+ )
230
+ ```
231
+
232
+ An easing that overshoots, like `backOut`, extends the first or last segment, as `interpolate` does with `extrapolate: 'extend'`. `easeTrack` takes a single `ease` and applies to tweens only.
233
+
97
234
  Custom easing functions run once, when the animation starts, like springs do. They don't cost anything per frame.
98
235
 
99
236
  ### Per property
@@ -120,6 +257,31 @@ animate(
120
257
 
121
258
  `delay` is in milliseconds, and the view takes the first keyframe during it. `repeat` is how many more times to play, `Infinity` until stopped. `repeatType: 'reverse'` plays every other repeat backwards. Repeats play natively too.
122
259
 
260
+ `repeatDelay` holds the end value for that many milliseconds before each repeat. The first play doesn't wait, and neither does the end of the last one. With `'reverse'` it waits at both ends:
261
+
262
+ ```ts
263
+ animate(
264
+ z,
265
+ { y: [0, -40], opacity: [0, 1] },
266
+ { duration: 1200, repeat: Infinity, repeatDelay: 1800 },
267
+ )
268
+ ```
269
+
270
+ The wait is part of the native keyframes, so it costs no JavaScript while it plays. Stopping during the wait leaves the view at the held value.
271
+
272
+ ## Decay
273
+
274
+ ```ts
275
+ import { decay } from '@rafaelaugusto/jolt'
276
+
277
+ decay(card, { x: 600 }) // DIPs per second
278
+ decay(card, { x: 600 }, { friction: 0.998, snap: [0, 320] })
279
+ ```
280
+
281
+ Starts from the current translation and slows down exponentially. `friction` multiplies velocity per millisecond; smaller values stop sooner. `snap` projects the landing and springs directly to the closest stop, preserving the supplied velocity. Use `{ snap: { x: [0, 320], y: [-200, 0] } }` for independent axes.
282
+
283
+ Shares interruption, `stop()` and awaitable completion with `animate()`. Both platforms use native keyframes with no JavaScript per frame. See the [decay guide](https://jolt.rafaelaugusto.dev/guide/decay).
284
+
123
285
  ## Presets
124
286
 
125
287
  ```ts
@@ -134,7 +296,9 @@ animate(spinner, 'spin', { repeat: Infinity })
134
296
  | Exit | `fadeOut`, `fadeOutUp`, `fadeOutDown`, `fadeOutLeft`, `fadeOutRight`, `zoomOut`, `popOut` |
135
297
  | Attention | `pulse`, `bounce`, `shake`, `wiggle`, `heartbeat`, `jelly`, `flash`, `tada`, `spin` |
136
298
 
137
- Directions follow animate.css: `fadeInUp` comes up from below, `fadeOutUp` leaves upwards. Every preset is plain keyframes and a transition, listed in [`src/presets.ts`](./src/presets.ts) and exported as `presets`.
299
+ Directions follow animate.css: `fadeInUp` comes up from below, `fadeOutUp` leaves upwards. Each built-in preset is plain keyframes and a transition, listed in [`src/presets.ts`](./src/presets.ts) and exported as `presets`.
300
+
301
+ Attention presets use `relative: true`: `shake` around `x: 40` returns to 40, `pulse` preserves the current scale, and `flash` returns to the current opacity. `spin` adds a full turn to the current rotation. Pass `{ relative: false }` to use their absolute keyframes instead. Enter and exit presets remain absolute.
138
302
 
139
303
  Options with timing only (`delay`, `repeat`, `stagger`) keep the preset's curves. Options with a curve (`duration`, `ease`, a spring) replace them:
140
304
 
@@ -144,6 +308,29 @@ animate(card, 'fadeInUp', { duration: 600 }) // a 600ms tween instead
144
308
  animate(card, 'fadeInUp', 'bouncy') // a bouncy spring instead
145
309
  ```
146
310
 
311
+ Register your own keyframes and transition with `definePreset`:
312
+
313
+ ```ts
314
+ import { animate, definePreset } from '@rafaelaugusto/jolt'
315
+
316
+ declare module '@rafaelaugusto/jolt' {
317
+ interface Presets {
318
+ cardIn: true
319
+ }
320
+ }
321
+
322
+ definePreset('cardIn', {
323
+ keyframes: { opacity: [0, 1], y: [40, 0] },
324
+ transition: { type: 'spring', bounce: 0.2, opacity: { duration: 150 } },
325
+ })
326
+
327
+ animate(card, 'cardIn')
328
+ ```
329
+
330
+ Register presets during app startup, before using them in `animate()` or `sequence()`. The `Presets` declaration adds the name to TypeScript; it does not register anything at runtime. Custom presets follow the same timing and curve overrides as built-ins, including per-property options and `stagger`.
331
+
332
+ Registering a custom name again replaces its definition for future animations. Built-in names cannot be replaced through `definePreset`, and the exported `presets` object continues to contain only the built-ins.
333
+
147
334
  ## Several views
148
335
 
149
336
  Pass an array to animate them together, and `stagger` to start them one after another:
@@ -155,6 +342,15 @@ animate(tiles, 'popIn', { stagger: { each: 30, from: 'center' } })
155
342
 
156
343
  `from` is `'first'` (the default), `'last'`, `'center'` or an index.
157
344
 
345
+ ## List rows
346
+
347
+ ```ts
348
+ const unlink = animateItems(listView, 'fadeInUp')
349
+ animateItems(listView, 'fadeInUp', { once: true })
350
+ ```
351
+
352
+ Each row of a `ListView` plays the entrance as the list hands it out. Rows are recycled while you scroll, so a row reused for another item starts the entrance again, or settles at the end values when that item was already shown and `once` is set. Items are tracked by object identity, or by index for primitives. The return value removes the effect and settles every row; unloading the list does the same. See the [list guide](https://jolt.rafaelaugusto.dev/guide/lists) for CollectionView and sectioned lists.
353
+
158
354
  ## Sequences
159
355
 
160
356
  ```ts
@@ -167,7 +363,20 @@ await sequence([
167
363
 
168
364
  Each step takes the same arguments as `animate`, or is a function that starts an animation. A step starts once the previous one looks done: a spring that's close to its target and staying there counts, even if it's still settling, so steps don't wait on the last pixel. The whole sequence finishes once every step has.
169
365
 
170
- Views in later steps take their first keyframe when the sequence starts, so the title above stays hidden until its turn instead of showing up and disappearing.
366
+ Views in later steps take their first absolute keyframe when the sequence starts, so the title above stays hidden until its turn instead of showing up and disappearing. Relative steps and `null` keyframes wait until their step starts to read the current value.
367
+
368
+ Use `at` to change the timing:
369
+
370
+ ```ts
371
+ sequence([
372
+ [logo, 'popIn'],
373
+ [title, 'fadeInUp', { at: '-150' }],
374
+ [buttons, 'fadeIn', { at: '<' }],
375
+ [footer, 'fadeIn', { at: 900 }],
376
+ ])
377
+ ```
378
+
379
+ A signed string offsets the previous arrival (`'-150'` before, `'+100'` after), `'<'` shares its activation, and a number selects absolute milliseconds. Each step's own delay still applies. Negative offsets use loaded tracks' spring/stagger arrival forecasts; absolute steps are independent. `stop()` cancels future launches and stops every started step. Position timers respect Android animator duration scale. JavaScript runs at step boundaries, so blocked JS can delay a launch. See [timelines](https://jolt.rafaelaugusto.dev/guide/sequences) for callback forecasts, interruption and loading behavior.
171
380
 
172
381
  ## Press
173
382
 
@@ -179,6 +388,166 @@ press(tile, { scale: 0.9, rotate: -4 }, 'bouncy')
179
388
 
180
389
  Animates to the given values while a finger is down, and back to where the view was once it lifts, or once the touch is cancelled by a scroll taking over. Views with `isEnabled` set to `false` don't react. It returns a function that removes the effect.
181
390
 
391
+ ## SF Symbols
392
+
393
+ ```ts
394
+ import { symbolEffect } from '@rafaelaugusto/jolt'
395
+
396
+ symbolEffect(bell, 'bounce')
397
+ symbolEffect(favorite, { replace: 'sys://heart.fill' })
398
+ ```
399
+
400
+ Takes a NativeScript `Image` with a `sys://` source, or a ref holding one. Supports `bounce`, `pulse`, `scale` and `variableColor`, with native playback, completion and cancellation. Replacement keeps the Image's source in sync. Scale holds until stopped; bounce, pulse and variable color can repeat with `{ repeat: true }`.
401
+
402
+ Effects require iOS 17+. Earlier iOS versions and Reduce Motion skip decorative effects and replace icons immediately. Android calls resolve with `false` without changing the image. See [SF Symbol effects](https://jolt.rafaelaugusto.dev/guide/symbol-effects) for options and lifecycle behavior.
403
+
404
+ ## Shimmer
405
+
406
+ ```ts
407
+ import { shimmer } from '@rafaelaugusto/jolt'
408
+
409
+ const stopShimmer = shimmer(placeholder, { duration: 1500 })
410
+ stopShimmer() // when the content is ready
411
+ ```
412
+
413
+ A native gradient mask sweeps across the placeholder and its children. It follows layout changes, preserves existing masks and releases native resources while the view is unloaded, resuming on load. Reduce Motion keeps the placeholder static. iOS only for now; Android calls return a no-op cleanup function. See the [shimmer guide](https://jolt.rafaelaugusto.dev/guide/shimmer).
414
+
415
+ ## Liquid images
416
+
417
+ ```ts
418
+ import { liquid } from '@rafaelaugusto/jolt'
419
+
420
+ const flow = liquid(orb, { intensity: 0.9, duration: 7200 })
421
+ flow.pause()
422
+ flow.resume()
423
+ flow.stop()
424
+ ```
425
+
426
+ Parts of the image move relative to each other through a native repeating texture warp. Transparency and stretch are preserved; pause freezes the current shape and stop restores the original image. Reduce Motion keeps the image static. iOS only; Android returns an inert handle. See the [liquid image guide](https://jolt.rafaelaugusto.dev/guide/liquid).
427
+
428
+ ## Loaders
429
+
430
+ ```ts
431
+ import { loader } from '@rafaelaugusto/jolt'
432
+
433
+ const stopLoading = loader(slot, { style: 'dots', color: '#71717a', size: 48 })
434
+ stopLoading() // when the content is ready
435
+ ```
436
+
437
+ Choose `dots`, `bars` or `ring`. Each indicator uses one native animation replicated across a layer, fits inside its target and stays centered as the target resizes. Reduce Motion shows a static indicator. Unloading releases native resources; loading resumes until explicitly stopped. iOS only for now. See the [loaders guide](https://jolt.rafaelaugusto.dev/guide/loaders).
438
+
439
+ ## Circular reveal
440
+
441
+ ```ts
442
+ import { reveal, conceal } from '@rafaelaugusto/jolt'
443
+
444
+ await reveal(details, { from: { x: 40, y: 40 }, duration: 500 })
445
+ await conceal(details, { to: { x: 40, y: 40 } })
446
+ ```
447
+
448
+ A native circle reveals the view's content or contracts to hide it. Reversing continues from the circle on screen. Existing masks are restored when the effect ends; conceal finishes with `visibility = 'hidden'`, preserving layout space. Reduce Motion applies the final visibility immediately. iOS only for now. See the [circular reveal guide](https://jolt.rafaelaugusto.dev/guide/reveal) for cancellation and layout behavior.
449
+
450
+ ## Text
451
+
452
+ ```ts
453
+ import { animateText } from '@rafaelaugusto/jolt'
454
+
455
+ await animateText(title)
456
+ await animateText(title, { by: 'word', stagger: 100 })
457
+ ```
458
+
459
+ Reveal a Label letter by letter or a word at a time. On iOS, TextKit keeps the complete text layout, including wrapped lines, attributed styles and composed emoji, and Core Animation plays temporary glyph images. On Android, spans that only affect drawing fade and lift each piece under one native animator, keeping the TextView's text, line breaks and truncation. The original Label remains the single accessibility element. Stop restores the full text; Reduce Motion and Android's Remove animations skip the entrance. Rebuild Android apps after upgrading to include the native class. See the [text guide](https://jolt.rafaelaugusto.dev/guide/text) for options and limits.
460
+
461
+ ## Rolling numbers
462
+
463
+ ```ts
464
+ import { rollTo } from '@rafaelaugusto/jolt'
465
+
466
+ await rollTo(balance, 1249.9, { format: (value) => value.toFixed(2) })
467
+ ```
468
+
469
+ Change a Label's number and only the digits that differ roll: each one slides out while its replacement slides in on a spring. Positions align on the units digit, so `99` becomes `100` by rolling two digits and adding one. The label holds the final text from the first frame; glyph layers on iOS and a native span animator on Android do the drawing, and tabular figures keep the width stable. Rebuild Android after upgrading. See the [rolling numbers guide](https://jolt.rafaelaugusto.dev/guide/rolling) for options and limits.
470
+
471
+ ## Haptics
472
+
473
+ ```ts
474
+ animate(card, { y: 0 }, { type: 'spring', haptic: 'light' })
475
+ animate(button, 'bounce', { haptic: { style: 'light', times: [0.6, 1] } })
476
+ ```
477
+
478
+ Choose `light`, `medium` or `heavy`. By default, one impact fires when the animation visually arrives. Explicit points can mark each landing of a bounce. Delays and stagger follow the animation; interruptions, stopping and unloading cancel pending feedback. iOS only for now. See the [haptics guide](https://jolt.rafaelaugusto.dev/guide/haptics) for timing and device behavior.
479
+
480
+ ## Particles and confetti
481
+
482
+ ```ts
483
+ import { confetti, particles } from '@rafaelaugusto/jolt'
484
+
485
+ confetti(button, { count: 80, colors: ['#facc15', '#ef4444', '#3b82f6'] })
486
+ const stopSnow = particles(scene, { source: 'top', angle: 90, velocity: 45 })
487
+ stopSnow() // remove the emitter and its particles
488
+ ```
489
+
490
+ Confetti bursts into the target's window and cleans itself up. `particles()` adds an emitter to the view for snow, sparks, bubbles or other effects. Both use Core Animation without JavaScript per frame, respect Reduce Motion and stop on unload. iOS only for now. See the [particles guide](https://jolt.rafaelaugusto.dev/guide/particles).
491
+
492
+ ## Scroll-linked animations
493
+
494
+ ```ts
495
+ import { scrollLinked } from '@rafaelaugusto/jolt'
496
+
497
+ const unlink = scrollLinked(
498
+ scrollView,
499
+ header,
500
+ { y: [0, -60], opacity: [1, 0] },
501
+ { range: [0, 120] },
502
+ )
503
+ ```
504
+
505
+ A paused native animation follows the ScrollView offset in DIPs, with linear interpolation and clamped progress. Scrolling backward restores earlier frames. Use `axis: 'x'` for horizontal scrolling; descending ranges reverse the mapping. `unlink()` freezes the current frame and removes the binding; unloading either view also cleans it up. Scroll events update progress through JavaScript, so this binding needs the main thread to remain available. See the [guide](https://jolt.rafaelaugusto.dev/guide/scroll-linked).
506
+
507
+ ## Follow the keyboard
508
+
509
+ ```ts
510
+ import { followKeyboard } from '@rafaelaugusto/jolt'
511
+
512
+ const release = followKeyboard(composer)
513
+ ```
514
+
515
+ The view rises by exactly the distance the keyboard would cover it, with the keyboard's own duration and curve, and settles back when it hides. On iOS the keyboard notifications drive a `UIView` animation; on Android 11+ a native `WindowInsetsAnimation` callback moves the view on every frame, with and without `adjustResize`. The view's `translateY` is written so later animations start from the position on screen. Rebuild Android after upgrading. See the [guide](https://jolt.rafaelaugusto.dev/guide/keyboard).
516
+
517
+ To react in other ways, `onKeyboard()` reports how much of the window the keyboard covers, with a transition that moves in step with it:
518
+
519
+ ```ts
520
+ const off = onKeyboard(({ visible, transition }) => {
521
+ animate(header, { scale: visible ? 0.8 : 1 }, transition)
522
+ })
523
+ ```
524
+
525
+ `useKeyboard()` from `/vue` keeps a ref in sync until unmount.
526
+
527
+ ## Pause, play and scrub
528
+
529
+ ```ts
530
+ const animation = animate(card, 'flipInY')
531
+ animation.pause()
532
+ animation.progress = 0.4
533
+ animation.play()
534
+ ```
535
+
536
+ `animate()` and `decay()` return a `PlaybackAnimation` with `pause()`, `play()`, `progress`, `paused` and `duration`. Progress runs from 0 to 1 across delays, repeats and stagger. It preserves the paused/playing state; a paused endpoint can scrub backward. Other animations on the same view keep moving. Infinite loops support pause/play but reject progress. Completed and stopped handles remain terminal. Sequence and effect handles keep their existing controls. See [playback](https://jolt.rafaelaugusto.dev/guide/playback) for lifecycle, haptics and gesture examples.
537
+
538
+ ## Time scale and reduced motion
539
+
540
+ ```ts
541
+ import { configure } from '@rafaelaugusto/jolt'
542
+
543
+ configure({ timeScale: 0.2 }) // everything five times slower
544
+ configure({ reducedMotion: 'always' }) // transforms jump, opacity still fades
545
+ ```
546
+
547
+ Tuning a spring at full speed is guesswork. The scale applies to every engine animation started after the call, on iOS and Android alike: durations, delays, stagger, sequence positions and spring time stretch together, keyframes stay dense, and an interruption still picks up the value and velocity on screen. Native loops such as shimmer and loaders keep their own timing.
548
+
549
+ With Reduce Motion on iOS or Remove animations on Android, translations, scale, rotation, paths, decorations and decay jump to their end values while opacity and colors still animate. `'user'` is the default; `'always'` and `'never'` override the platform for the whole library. `reducedMotion()` reads the result, `onReducedMotion(callback)` watches it, and `useReducedMotion()` from `/vue` keeps a ref in sync. See the [guide](https://jolt.rafaelaugusto.dev/guide/configure).
550
+
182
551
  ## Interrupting and stopping
183
552
 
184
553
  Starting an animation on a property that's already animating takes it over: the new one starts from the value on screen, with the same velocity, so a card can change its mind halfway without a jump. Other properties of the previous animation keep going.
@@ -190,38 +559,79 @@ const animation = animate(card, { x: 300 })
190
559
  animation.stop() // the card stays where it is
191
560
 
192
561
  stop(card) // stops everything running on the card
562
+ stop(card, ['x', 'scale']) // stops translation on x and both scale axes
563
+ ```
564
+
565
+ Selected properties stay at their current values; the others keep animating. `scaleX` and `scaleY` can also be stopped separately. Pass an empty array to stop nothing, or omit it to stop everything. This works with arrays of targets too.
566
+
567
+ If part of an animation is stopped, its `finished` promise resolves with `false` once its remaining properties finish.
568
+
569
+ `set()` places a view without animating it, and stops whatever animates those properties:
570
+
571
+ ```ts
572
+ set(logo, { opacity: 1, scale: 1, y: 0 })
193
573
  ```
194
574
 
575
+ It takes one value per property, the same properties as `animate()`. The next animation starts from there. See [setting values](https://jolt.rafaelaugusto.dev/guide/interrupting#setting-values-without-animating).
576
+
195
577
  ## How it works
196
578
 
197
- When an animation starts, Jolt resolves where each property starts from (the value on screen, or the keyframes you gave), and computes the whole curve: easing, keyframes, spring physics. It samples the curve 60 times per second of motion, and keeps keyframe times exact. Linear segments aren't sampled at all.
579
+ When an animation starts, Jolt resolves where each property starts from (the value on screen, or the keyframes you gave), and computes the whole curve: easing, keyframes, spring physics and exponential decay. It samples the curve 60 times per second of motion, and keeps keyframe times exact. Linear numeric segments use only their endpoints. Long tween segments and free decays cap sampling at 6,001 keyframes. Color tracks add intermediate OKLab samples even with linear timing.
198
580
 
199
581
  The samples go to the platform in one go:
200
582
 
201
583
  - **iOS**: a `CAKeyframeAnimation` per property on the view's layer (`opacity`, `transform.translation.x`, `transform.scale.y`, `transform.rotation.z`...). Core Animation composes them, so `x` and `scale` can belong to different animations, and plays them in the render server.
202
584
  - **Android**: an `ObjectAnimator` per property with `Keyframe`s and a linear interpolator, on the view's `alpha`, `translationX`, `scaleY`, `rotation`...
203
585
 
586
+ Solid [colors](#colors) use CGColor keyframes on iOS and int keyframes through a native Java target on Android. iOS text uses a UILabel alpha mask so glyph positions stay fixed while the color layer animates.
587
+
204
588
  Before playing, the final values are set on the NativeScript view, like `view.animate()` does, so `view.translateX` already holds the target while the animation runs. Stopping writes back the values on screen.
205
589
 
206
- Each running property remembers its curve and start time, so Jolt knows the value and velocity of any property at any moment without asking the platform. That's what lets an interruption pick up the motion where it was. On Android, where an animator starts on the next frame, it reads the value on screen instead.
590
+ Native playback does not isolate Android View animations from a blocked UI thread. Main-runtime JavaScript shares that thread, and `withLayer()` only caches drawing. Move suitable CPU work to a Worker and keep view updates small. See the [busy JavaScript guide](https://jolt.rafaelaugusto.dev/guide/android-performance) and [Android diagnostic](./benchmarks/android-busy.md).
591
+
592
+ On Android, opacity animations temporarily cache hardware-accelerated containers with children in a hardware layer. Jolt waits for the fade to start and restores the layer on completion, stop, replacement or unload. Existing app-configured layers are preserved; empty, unmeasured and larger-than-screen containers are skipped. See the [fade-layer benchmark](./benchmarks/android-fade.md) for measurement and memory tradeoffs.
593
+
594
+ Each running property remembers its curve and start time. On Android, interruptions read the displayed value and the animator's current play time, so inherited velocity follows the system's animator duration scale. Early spring arrivals use a native timing marker with completion callbacks only; other sequences advance on native completion. Delays, repeats and disabled animations follow the same platform clock, without JavaScript running per frame.
207
595
 
208
596
  ## Not there yet
209
597
 
210
- - Only transforms and opacity. Colors, size and layout changes aren't animated yet.
211
- - No gestures: dragging something and letting it fly with its velocity is next.
212
- - No layout or shared element transitions.
598
+ - `animate()` supports transforms, opacity, colors, uniform corners and borders, and platform shadows. Size and layout changes ([#21](https://github.com/rafaelaugustos/jolt/issues/21)) aren't animated yet.
599
+ - Gesture composition and nested-scroll handoff remain separate work ([#51](https://github.com/rafaelaugustos/jolt/issues/51)).
600
+ - No layout or shared element transitions ([#21](https://github.com/rafaelaugustos/jolt/issues/21), [#24](https://github.com/rafaelaugustos/jolt/issues/24)).
213
601
 
214
602
  ## API
215
603
 
216
- **Animating**: `animate(target, keyframes | preset, transition?)`, `stop(target)`, `sequence(steps)`, `press(target, pressed?, transition?)`.
604
+ **Animating**: `animate(target, keyframes | preset, transition?)`, `decay(target, velocity, options?)`, `stop(target, properties?)`, `sequence(steps)`, `press(target, pressed?, transition?)`, `drag(target, options?)`, `symbolEffect(image, effect, options?)`, `shimmer(target, options?)`, `liquid(image, options?)`, `loader(target, options?)`, `reveal(target, options?)`, `conceal(target, options?)`, `confetti(target, options?)`, `particles(target, options?)`.
605
+
606
+ `animateLayout(container, change?, options?)` animates the next layout of direct children; `motionScope().animateLayout()` owns it through component lifecycle. Cancellation settles temporary offsets into the final layout.
607
+
608
+ `interactionSurface(target)` opts a view/subtree into native touch geometry during animation; returns `{ disposed, dispose() }`. Register before playback; rebuild iOS after upgrading.
217
609
 
218
- **Animations** returned by them: `await`, `finished`, `stop()`.
610
+ `drag()` returns a controller with `position`, `dragging`, `settling`, `cancel()`, `pause()`, `play()` and `dispose()`. `motionScope().drag()` owns it through component lifecycle.
219
611
 
220
- **Helpers**: `presets`, `springs`, `cubicBezier`, plus the types (`Keyframes`, `Transition`, `Spring`, `Tween`, `PresetName`, `SpringName`, `Easing`, `Animation`, `Target`...).
612
+ `liquid()` returns a `LiquidEffect` with `pause()`, `resume()` and `stop()`.
613
+
614
+ **Animations** from `animate()`, `decay()`, `sequence()`, `symbolEffect()`, `reveal()` and `conceal()`: `await`, `finished`, `stop()`. `press()`, `shimmer()`, `loader()`, `confetti()` and `particles()` return cleanup functions.
615
+
616
+ **Helpers**: `definePreset`, `presets`, `springs`, `cubicBezier`, plus the types (`Keyframes`, `Transition`, `Spring`, `Tween`, `Preset`, `Presets`, `PresetName`, `SpringName`, `Easing`, `Animation`, `Target`...).
221
617
 
222
618
  ## Demo
223
619
 
224
- [`demo/`](./demo) is a NativeScript-Vue app with every preset, the three springs on a ball you can interrupt, stagger, press effects, loops and a sequence on launch.
620
+ [`demo/`](./demo) opens an interactive assistant on iOS and Android: a fan of document tools, a voice screen with letter-by-letter text, and a staged conversation with document previews and a rolling countdown, with the screens cross-fading through the Vue entry. Open **Help → Watch demo** to replay the flow. The orb is an animated illustration; speech, documents and audio are sample data.
621
+
622
+ | Assistant | Presets | Springs | Stagger |
623
+ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------: |
624
+ | <img src="https://raw.githubusercontent.com/rafaelaugustos/jolt/main/.github/assets/demo-assistant.gif" width="200" alt="The assistant replaying its flow on iOS"> | <img src="https://raw.githubusercontent.com/rafaelaugustos/jolt/main/.github/assets/demo-presets.gif" width="200" alt="fadeInUp, popIn and shake"> | <img src="https://raw.githubusercontent.com/rafaelaugustos/jolt/main/.github/assets/demo-springs.gif" width="200" alt="smooth, snappy and bouncy springs on an interruptible ball"> | <img src="https://raw.githubusercontent.com/rafaelaugustos/jolt/main/.github/assets/demo-stagger.gif" width="200" alt="Rows entering with a stagger"> |
625
+
626
+ | Sequences | Press (Android) | Rolling numbers | Text (Android) |
627
+ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------: |
628
+ | <img src="https://raw.githubusercontent.com/rafaelaugustos/jolt/main/.github/assets/demo-sequences.gif" width="200" alt="Overlapping steps and steps in order"> | <img src="https://raw.githubusercontent.com/rafaelaugustos/jolt/main/.github/assets/demo-press-android.gif" width="200" alt="Three press effects"> | <img src="https://raw.githubusercontent.com/rafaelaugustos/jolt/main/.github/assets/demo-rolling.gif" width="200" alt="Digits rolling up and down"> | <img src="https://raw.githubusercontent.com/rafaelaugustos/jolt/main/.github/assets/demo-text-android.gif" width="200" alt="Letter and word entrances"> |
629
+
630
+ | Layout (iOS) | Drag and snap (iOS) | Image effects (iOS) | Photo zoom (iOS) |
631
+ | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
632
+ | <img src="https://raw.githubusercontent.com/rafaelaugustos/jolt/main/.github/assets/demo-layout-ios.gif" width="200" alt="Native cards insert, reorder and expand"> | <img src="https://raw.githubusercontent.com/rafaelaugustos/jolt/main/.github/assets/demo-drag-ios.gif" width="200" alt="A card and panel follow real drags and settle within bounds"> | <img src="https://raw.githubusercontent.com/rafaelaugustos/jolt/main/.github/assets/demo-image-effects-ios.gif" width="200" alt="Ripple, touch distortion and dissolve on a raster image"> | <img src="https://raw.githubusercontent.com/rafaelaugustos/jolt/main/.github/assets/demo-photo-zoom-ios.gif" width="200" alt="A photo responds to pinch, pan and double tap"> |
633
+
634
+ The animation playground opens with `--playground` on iOS and `--es playground true` on Android, with presets, interruptible springs, decay, text, press effects, list rows, the keyboard composer and the native iOS helpers. See the [demo guide](./docs/guide/demo.md) for preview arguments and implementation notes. Every visual feature guide in the [documentation](https://jolt.rafaelaugusto.dev) includes a recording of the real native demo, in English and Portuguese.
225
635
 
226
636
  With the [NativeScript CLI](https://docs.nativescript.org/setup/) installed:
227
637
 
@@ -240,10 +650,16 @@ ns run ios # or: ns run android
240
650
  npm test # unit tests
241
651
  npm run typecheck # including the iOS and Android drivers, against the platform typings
242
652
  npm run build
653
+ npm run docs:check-media
654
+ npm run docs:build
243
655
  ```
244
656
 
657
+ Visual changes require reviewed recordings before completion or release. See [Contributing](./CONTRIBUTING.md).
658
+
245
659
  The tests run the engine against a fake platform driver ([`test/helpers.ts`](./test/helpers.ts)) with its own clock: it records the keyframes it would play and ends them when the clock passes their end.
246
660
 
661
+ For on-device measurements, see the [iOS animation startup benchmark](./benchmarks/README.md).
662
+
247
663
  ### Releasing
248
664
 
249
665
  1. Bump `version` in `package.json` (`npm version <version> --no-git-tag-version`) and move the changes under it in `CHANGELOG.md`.
@@ -254,4 +670,4 @@ Publishing authenticates with the `NPM_TOKEN` secret, or with [trusted publishin
254
670
 
255
671
  ## License
256
672
 
257
- [MIT](./LICENSE)
673
+ [MIT](./LICENSE). Maintained by Rafael Augusto and [Cortex Labs](https://getcortexlabs.com/).