export type FrameLifecycleTier = 'full' | 'contained' | 'detached'; export interface FrameLodRect { x: number; y: number; width: number; height: number; } export interface FrameLodCamera { panX: number; panY: number; zoom: number; } export interface FrameLodViewport { width: number; height: number; } export interface FrameLodConfig { /** Distance below which a contained or detached frame promotes to full. */ fullEnter: number; /** Distance above which a full frame demotes to contained. */ fullExit: number; /** Distance beyond which a contained frame may detach after the dwell. */ detachEnter: number; /** Distance below which a detached frame reattaches as contained. */ detachExit: number; /** * How long a frame must have held its contained tier before it may detach. * Counted from the tier-entry timestamp, so a pan that sweeps a frame out * and back within the dwell never pays a remount. */ dwellMs: number; /** Slack factor when testing whether the frame union fits the viewport. */ overviewFitSlack: number; /** Frames intersecting the viewport at once that force overview mode. */ overviewVisibleCount: number; } export declare const DEFAULT_FRAME_LOD_CONFIG: FrameLodConfig; export interface FrameLodFrame { id: string; rect: FrameLodRect; /** Pinned frames (active, selected, preview-intended, live) stay full. */ pinned: boolean; /** Retained snapshot weight proxy: serialized snapshot length times two. */ weightBytes: number; /** * Screenshot bitmap pixels stored per frame CSS pixel; null or absent when * the frame has no captured screenshot. Input to the magnification signal. */ screenshotDensity?: number | null; } /** * Content-sized catalog frames begin as 1x1 seeds. They need one full render * to publish intrinsic bounds; afterwards their live DOM can stay mounted * while the browser skips offscreen paint. */ export declare function contentFrameNeedsMeasurement(frame: { frameSizeMode?: string; width?: number; height?: number; }): boolean; export interface FrameLodTierState { tier: FrameLifecycleTier; /** Timestamp at which the frame entered its current tier. */ since: number; } /** * Screen-space gap between a frame and the viewport, normalized by the * viewport diagonal. A frame intersecting the viewport is at distance zero; * one whole viewport diagonal away is at distance one. */ export declare function frameViewportDistance(frameRect: FrameLodRect, camera: FrameLodCamera, viewport: FrameLodViewport): number; /** * An overview camera sees the storyboard as a whole: either the union of all * frames roughly fits the viewport, or many frames intersect it at once. In * overview every frame legitimately needs pixels, so tier demotion is off and * the imperative camera writer alone pays for the pan. */ export declare function isOverviewCamera(frames: readonly FrameLodRect[], camera: FrameLodCamera, viewport: FrameLodViewport, config?: FrameLodConfig): boolean; /** * True when the camera draws a frame above its screenshot's pixel density — * the bitmap would visibly blur, so the frame should trade its screenshot for * the frozen DOM surface. Gated on the `full` tier: at magnifying zoom levels * only a handful of frames are near the viewport, which keeps simultaneous * DOM reconstruction bounded. * * An overview camera never magnifies. Overview marks every frame `full` * because they all need pixels at once — and a low-density bitmap (a legacy * 1x capture on a 2x display) can sit "past its density" at overview zoom, * which would reconstruct the whole storyboard's frozen DOM in one pass. */ export declare function frameMagnifiedPastBitmap(tier: FrameLifecycleTier, displayDensity: number, screenshotDensity: number | null | undefined, overview: boolean): boolean; /** * Whether frame screenshots should display at their native (2x) resolution. * Below one device pixel per CSS pixel a half-scale bitmap is * indistinguishable, and an overview showing every frame at native capture * resolution keeps gigabytes of decoded bitmap resident — the canvas pans * like it is buffering. The display density is camera-global, so this is one * signal, not a per-frame one; the ±5% band keeps a zoom hovering at the * boundary from flapping between decodes. */ export declare function resolveBitmapFullSignal(previous: boolean, displayDensity: number): boolean; export declare const FRAME_LOD_BUDGET_BYTES_PER_GB: number; export declare const FRAME_LOD_BUDGET_MIN_BYTES: number; export declare const FRAME_LOD_BUDGET_MAX_BYTES: number; export declare const FRAME_LOD_BUDGET_MIN_BYTES_PER_FRAME: number; /** * Total attached-preview weight allowed before the governor demotes the * farthest unpinned frames. Scales with device memory, clamped, and never * drops below a small per-frame allowance so a large storyboard is not forced * into a wall of placeholders on a small device. */ export declare function resolveFrameLodBudget({ deviceMemoryGb, frameCount, }: { deviceMemoryGb?: number; frameCount: number; }): number; /** * Resolves the lifecycle tier for every frame. Rules, in order: an overview * camera keeps everything full; pinned frames are always full; otherwise the * hysteresis step above applies; finally the memory governor demotes the * farthest unpinned attached frames (skipping weightless ones — demoting them * frees nothing) until the attached weight fits the budget. Governor * demotion is allowed to bypass the dwell: it exists to cap memory, not to * smooth pans. */ export declare function resolveFrameLodTiers({ frames, camera, viewport, previous, now, config, budget, }: { frames: readonly FrameLodFrame[]; camera: FrameLodCamera; viewport: FrameLodViewport; previous: ReadonlyMap; now: number; config?: FrameLodConfig; budget: number; }): Map; /** * Which frames should render live, best first: the active frame, then every * frame by its screen-space gap to the viewport (intersecting frames first), * ties broken by distance from the viewport centre. The live-screen budget * takes the head of this list; the rest keep their frozen capture. */ export declare function rankLiveScreenCandidates({ frames, camera, viewport, activeId, preferredIds, }: { frames: readonly { id: string; rect: FrameLodRect; }[]; camera: FrameLodCamera; viewport: FrameLodViewport; activeId: string | null; /** Ids that outrank distance, in order: the active group's standby variants. */ preferredIds?: readonly string[]; }): string[];