// settings-toggle-flow —— App settings: toggles flip one by one in a stagger + save toast (mobile / screen) // Phone fades in → settings list rows blur in staggered → several switches flip one by one (thumb springs with overshoot + // track color transition), with a slider drag + option change in between → "saved" toast at the bottom slides up + lingers briefly + // fades out → the whole phone breathes on hold. Vector mockup, deterministic rendering. import React from 'react'; import { AbsoluteFill, useCurrentFrame, interpolate, spring, Easing } from 'remotion'; import { Phone } from '../../_fixtures/PhoneFixtures'; const CL = { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' } as const; export const SETTINGS_TOGGLE_FLOW_DURATION = 222; // 7.4s @ 30fps // Timeline (222f) const PHONE_IN = 4; const ROW_IN = 18; // settings rows blur-in stagger begins const T1 = 40; // toggle 1 flips const T2 = 74; // toggle 2 flips const SLIDER = 100; // slider drag const T3 = 130; // toggle 3 flips const TOAST = 152; // save toast slides up const TOAST_OUT = 182; // toast fades out const HOLD = 188; // settled → breathing export const SettingsToggleFlow: React.FC = () => { const f = useCurrentFrame(); const phoneIn = interpolate(f, [PHONE_IN, PHONE_IN + 16], [0.85, 1], { easing: Easing.out(Easing.cubic), ...CL, }); const phoneY = (1 - phoneIn) * 20; // Rows blur in (staggered 4f per row) const rowIn = (i: number) => interpolate(f, [ROW_IN + i * 4, ROW_IN + i * 4 + 14], [0, 1], { easing: Easing.out(Easing.cubic), ...CL, }); // switch thumb: springs to the right (0.5 → 22px, with overshoot) const thumbX = (start: number) => 2 + 20 * spring({ frame: Math.max(0, f - start), fps: 30, config: { damping: 13, stiffness: 150 }, }); const trackOn = (start: number) => interpolate(f, [start, start + 8], [0, 1], CL); // Slider drag progress (f100–124) const sliderW = interpolate(f, [SLIDER, SLIDER + 24], [30, 130], { easing: Easing.out(Easing.cubic), ...CL }); // toast: slides up + lingers + fades out const toastIn = interpolate(f, [TOAST, TOAST + 12], [0, 1], { easing: Easing.out(Easing.cubic), ...CL }); const toastY = (1 - toastIn) * 24; const toastOut = interpolate(f, [TOAST_OUT, TOAST_OUT + 10], [1, 0], CL); const breathe = f < HOLD ? 1 : 1 + 0.008 * Math.sin(((f - HOLD) / 48) * Math.PI * 2); const switchRow = (label: string, start: number, i: number) => (
{label}
0.5 ? '#18181b' : '#e2e2e6', position: 'relative', transition: 'none' }}>
); return (
Settings
PREFERENCES
{switchRow('Notifications', T1, 0)} {switchRow('Dark mode', T2, 1)} {/* Slider row */}
Volume
ACCOUNT
{switchRow('Two-factor auth', T3, 3)}
{/* Save toast */} {f >= TOAST && (
Settings saved
)}
); };