import type {Cue, Segment, Shot} from './types'; export type EvidenceSide='visual'|'explanation'; export type Framing={position:number[];target:number[];fov:number}; export const portrait=(width:number,height:number)=>width/height<.85; /** Fit the subject horizontally without moving a camera outside the spacecraft. */ export function fitFov(width:number,height:number,distance:number,aspect:number){ return Math.min(112,Math.max(24,2*Math.atan(Math.max(height,width/aspect)/(2*distance))*180/Math.PI)); } export function portraitFrame(shot:Shot,aspect:number,side:EvidenceSide='visual',flagship=false):Framing{ if(shot==='story'){ const x=side==='visual'?-1.74:1.74; return {position:[x,1.7,1.78],target:[x,1.67,-.32],fov:fitFov(1.49,side==='visual'?1.05:1.65,2.10,aspect)}; } if(flagship&&shot==='close')return {position:[.035,1.72,1.05],target:[.085,1.695,.025],fov:fitFov(.57,.57,1.025,aspect)}; if(flagship&&shot==='medium')return {position:[.025,1.76,1.90],target:[.065,1.48,.02],fov:fitFov(.92,1.18,1.88,aspect)}; if(shot==='close')return {position:[0,1.72,1.03],target:[0,1.685,.015],fov:fitFov(.39,.51,1.02,aspect)}; if(shot==='medium')return {position:[0,1.77,2.45],target:[0,1.40,.02],fov:fitFov(1.16,1.48,2.44,aspect)}; return {position:[0,1.86,4.20],target:[0,1.53,-.22],fov:fitFov(5.15,3.1,4.42,aspect)}; } /** Portrait uses the final unlocked close-up as an explanation insert after the visual. */ export function editorialFrame(segment:Segment|undefined,cue:Cue|undefined,t:number,isPortrait:boolean):{shot:Shot;side:EvidenceSide}{ const shot=cue?.shot??'medium'; if(cue?.evidenceSide)return {shot,side:cue.evidenceSide}; if(!isPortrait||!segment?.storyId||cue?.locked)return {shot,side:'visual'}; const earlierVisual=segment.cues.some(c=>c.shot==='story'&&c.at<(cue?.at??0)); if(shot==='close'&&earlierVisual&&segment.facts.length)return {shot:'story',side:'explanation'}; return {shot,side:shot==='story'&&t-(cue?.at??0)>=7&&segment.facts.length?'explanation':'visual'}; }