import React from 'react'; import {AbsoluteFill,Audio,Img,interpolate,staticFile,useCurrentFrame} from 'remotion'; import {Background} from '../components/Stage'; import {S,C,FONT} from '../lib/tokens'; import {cl,easeInOut,sec} from '../lib/anim'; import {FormatCtx, Format, Platform} from '../lib/format-context'; import {geometry} from '../lib/format.mjs'; import {McBox,DebugCtx} from '../lib/debug'; export type TutorialStep={title:string; caption:string; from:number; to:number; x:number; y:number; zoom?:number}; export type TutorialProps={handle:string; title:string; captureDir?:string; captureCount?:number; captureFps?:number; sourceCredit?:string; steps:TutorialStep[]; duration?:number; audio?:string; mcDebug?:boolean;format?:Format;platform?:Platform}; export const Tutorial:React.FC=({handle,title,captureDir,captureCount=0,captureFps=30,sourceCredit,steps,audio,mcDebug=false,format='9:16',platform='tiktok'})=>{ const f=useCurrentFrame();const g=geometry(format,platform);const sx=g.safe.x,sy=g.safe.y,sw=g.safe.width,sh=g.safe.height; const active=steps.find(s=>f>=sec(s.from)&&f0&&!!sourceCredit; const t=active?Math.min(1,Math.max(0,(f-sec(active.from))/12)):0; const z=active?1+(Math.min(1.6,Math.max(1,active.zoom||1.24))-1)*interpolate(t,[0,1],[0,1],{...cl,easing:easeInOut}):1; const px=active?Math.min(.88,Math.max(.12,active.x)):.5,py=active?Math.min(.85,Math.max(.12,active.y)):.5; const panX=active?interpolate(t,[0,1],[.5,px],{...cl,easing:easeInOut}):.5; const panY=active?interpolate(t,[0,1],[.5,py],{...cl,easing:easeInOut}):.5; const idx=Math.max(0,Math.min(captureCount-1,Math.floor(f*captureFps/30))); return {!mcDebug?:}
{title}
{ready?:
SCREEN RECORDING NEEDED
Add recording, frame count and source credit
}
{ready&&active&&
}
{/* Bottom captions are outside the capture viewport; they never hide UI controls. */}
{active&&<>STEP {steps.indexOf(active)+1}: {active.title}{active.caption}}
{ready?`Capture: ${sourceCredit}`:'CAPTURE NOT VERIFIED - DO NOT PUBLISH'}{handle}
{audio&&!mcDebug&&