/**
 * GENE Motion — Scroll Hero free-placement timeline: shared constants + pure
 * geometry. Imported by save.js and edit.js (which need the resolved defaults),
 * and by view.js (which needs only the fixed transition amounts — it reads each
 * beat's placement/window from the DOM data-* attributes save writes).
 *
 * The model: in timeline mode each heading is placed at a center-anchored
 * (x%, y%) over the pinned hero and lives inside a scroll window [in, out]
 * (fractions of the runway). It enters over a fixed lead-in, holds through the
 * window, then leaves over a fixed lead-out — all scrubbed by scroll position,
 * so scrolling back reverses it. Enter/exit length and travel are fixed here on
 * purpose (never exposed in the UI, so placements can't look janky).
 */

// Fraction of the runway spent on the enter / exit transition of each heading.
export const ENTER = 0.12;
export const EXIT = 0.12;
// Travel distance of the movement presets, in px.
export const RISE = 44;
export const SLIDE = 64;

export function num( v, d ) {
	return typeof v === 'number' && isFinite( v ) ? v : d;
}

/** Center-anchored placement for a beat (percent). Mobile falls back to desktop. */
export function beatPos( b, mobile ) {
	const x = mobile && typeof b.xM === 'number' ? b.xM : num( b.x, 50 );
	const y = mobile && typeof b.yM === 'number' ? b.yM : num( b.y, 50 );
	return { x, y };
}

/**
 * The beat's scroll window as fractions 0..1. When the author hasn't set in/out
 * we fall back to an even split by index, so a freshly placed heading already
 * has a sensible window and just works.
 */
export function beatWindow( i, n, b ) {
	const inD = n ? i / n : 0;
	const outD = n ? ( i + 1 ) / n : 1;
	let bi = typeof b.in === 'number' ? b.in / 100 : inD;
	let bo = typeof b.out === 'number' ? b.out / 100 : outD;
	bi = bi < 0 ? 0 : bi > 1 ? 1 : bi;
	bo = bo < 0 ? 0 : bo > 1 ? 1 : bo;
	if ( bo < bi ) bo = bi;
	return { in: bi, out: bo };
}
