Skip to content
jolt.
← Recipes

MOTION / iOS / NATIVESCRIPT VUE

A rhythm of your own.

A traced signal, a quiet title and a staggered entrance into Vena.

From Vena ↗
Native recording · iOSVena on iOS: creator-provided English recording of the opening, particle heartbeat, rhythm collection and heart focus. Original speed, 60 fps export, no audio; the first 3.25 seconds of system home and launch are omitted. Capture device unspecified; not a performance measurement. BPM readings are illustrative. BodyParts3D © DBCLS · CC BY 4.0 / MakeHuman · CC0 ↗

The motion behind it.

Jolt 0.1.0-rc.11

The complete source file from the project, pinned to the reviewed revision. Adapt it with the dependencies described below.

NativeScript-Vue 3 and Jolt RC.11. Call opening with four loaded native views; title, caption and skip start at opacity 0. The rule hosts a 190×30 vector path. WelcomeOverlay.vue shows the completion/skip guard and disposal. revealInterface takes an owned MotionScope and four mounted views. Dispose the returned opening handle and your interface scope on unload. This file animates text, the path and interface entrance; the particle heart needs HeartScene and its assets. Preserve reduced-motion handling. iOS is the recorded platform.

entrance.ts
import type { View } from '@nativescript/core'
import { motionScope, reducedMotion, set, type MotionScope } from '@rafaelaugusto/jolt'
import { shape, type Shape } from '@rafaelaugusto/jolt/shapes'

export interface EntranceTargets { title: View; caption: View; rule: View; skip: View }

export function opening(targets: EntranceTargets) {
  const scope = motionScope()
  const drawings: Shape[] = []
  if (reducedMotion()) {
    set([targets.title, targets.caption, targets.skip], { opacity: 1 })
    return { scope, finished: scope.animate(targets.title, { opacity: [1, 1] }, { duration: 180 }).finished, dispose: () => scope.dispose() }
  }
  const line = shape(targets.rule, { viewBox: [0, 0, 190, 30], d: 'M0 15 L47 15 L56 12 L62 15 L76 15 L82 21 L89 2 L97 28 L105 15 L123 15 Q134 5 144 15 L190 15', fill: 'transparent', stroke: '#FF4067', strokeWidth: 1.1, strokeEnd: 0, lineCap: 'round' })
  drawings.push(line)
  const sequence = scope.timeline([
    [targets.title, { opacity: [0, 1], y: [14, 0] }, { at: 420, duration: 800, ease: 'easeOut' }],
    { at: 700, run: () => line.animate({ strokeEnd: 1 }, { duration: 1000, ease: 'easeInOut' }) },
    [targets.caption, { opacity: [0, 1], y: [8, 0] }, { at: 1000, duration: 600, ease: 'easeOut' }],
    [targets.skip, { opacity: [0, .7] }, { at: 1200, duration: 500 }],
    [targets.title, { opacity: [1, 1] }, { at: 2100, duration: 350 }],
  ])
  return { scope, finished: sequence.finished, dispose() { scope.dispose(); drawings.forEach(drawing => drawing.dispose()) } }
}

export function revealInterface(scope: MotionScope, header: View, card: View, footer: View, hint: View) {
  return scope.timeline([
    [header, { opacity: [0, 1], y: reducedMotion() ? 0 : [-9, 0] }, { at: 0, duration: 500, ease: 'easeOut' }],
    [card, { opacity: [0, 1], y: reducedMotion() ? 0 : [38, 0] }, { at: 100, type: 'spring', duration: 850, bounce: .12 }],
    [footer, { opacity: [0, 1], y: reducedMotion() ? 0 : [12, 0] }, { at: 230, duration: 600, ease: 'easeOut' }],
    [hint, { opacity: [0, .7] }, { at: 600, duration: 600 }],
  ])
}
View full demo ↗

Read the full guide ↗