layershift 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,35 @@
1
+ /**
2
+ * JSX IntrinsicElements declaration for the <layershift-parallax> custom element.
3
+ *
4
+ * Reference this file in your tsconfig.json to get TypeScript support
5
+ * for the custom element in React/JSX projects:
6
+ *
7
+ * "compilerOptions": {
8
+ * "types": ["layershift/global"]
9
+ * }
10
+ */
11
+
12
+ import type { LayershiftProps } from './types';
13
+
14
+ declare global {
15
+ namespace JSX {
16
+ interface IntrinsicElements {
17
+ 'layershift-parallax': React.DetailedHTMLProps<
18
+ React.HTMLAttributes<HTMLElement> & {
19
+ src?: string;
20
+ 'depth-src'?: string;
21
+ 'depth-meta'?: string;
22
+ 'parallax-x'?: number | string;
23
+ 'parallax-y'?: number | string;
24
+ 'parallax-max'?: number | string;
25
+ layers?: number | string;
26
+ overscan?: number | string;
27
+ autoplay?: boolean | string;
28
+ loop?: boolean | string;
29
+ muted?: boolean | string;
30
+ },
31
+ HTMLElement
32
+ >;
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Entry point for the <layershift-parallax> Web Component.
3
+ *
4
+ * Importing this module registers the custom element with the browser.
5
+ * After registration, <layershift-parallax> can be used in any HTML document.
6
+ */
7
+ import { LayershiftElement } from './layershift-element';
8
+ export { LayershiftElement };
9
+ export type { LayershiftProps } from './types';
10
+ export type { LayershiftEventMap, LayershiftReadyDetail, LayershiftPlayDetail, LayershiftPauseDetail, LayershiftLoopDetail, LayershiftFrameDetail, LayershiftErrorDetail, } from './types';
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/layershift/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAMzD,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAC7B,YAAY,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,YAAY,EACV,kBAAkB,EAClB,qBAAqB,EACrB,oBAAoB,EACpB,qBAAqB,EACrB,oBAAoB,EACpB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,SAAS,CAAC"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * <layershift-parallax> Web Component
3
+ *
4
+ * A self-contained Custom Element that renders a depth-aware parallax
5
+ * video effect. Encapsulates the entire Three.js pipeline inside a
6
+ * Shadow DOM — consumers just drop in the tag and provide asset URLs.
7
+ *
8
+ * Usage:
9
+ * <layershift-parallax
10
+ * src="video.mp4"
11
+ * depth-src="depth-data.bin"
12
+ * depth-meta="depth-meta.json"
13
+ * ></layershift-parallax>
14
+ */
15
+ export declare class LayershiftElement extends HTMLElement {
16
+ static readonly TAG_NAME = "layershift-parallax";
17
+ static get observedAttributes(): string[];
18
+ private shadow;
19
+ private container;
20
+ private renderer;
21
+ private inputHandler;
22
+ private depthWorker;
23
+ private video;
24
+ private initialized;
25
+ private abortController;
26
+ private loopCount;
27
+ constructor();
28
+ private getAttrFloat;
29
+ private getAttrBool;
30
+ private get parallaxX();
31
+ private get parallaxY();
32
+ private get parallaxMax();
33
+ private get overscan();
34
+ private get shouldAutoplay();
35
+ private get shouldLoop();
36
+ private get shouldMute();
37
+ /**
38
+ * Dispatch a namespaced custom event that bubbles through Shadow DOM.
39
+ * All events use the `layershift-parallax:` prefix and are `composed`
40
+ * so consumers can listen on the host element from the light DOM.
41
+ */
42
+ private emit;
43
+ /**
44
+ * Attach native video event listeners and re-dispatch them
45
+ * as namespaced custom events on the host element.
46
+ */
47
+ private attachVideoEventListeners;
48
+ connectedCallback(): void;
49
+ disconnectedCallback(): void;
50
+ attributeChangedCallback(_name: string, _oldVal: string | null, _newVal: string | null): void;
51
+ private setupShadowDOM;
52
+ private init;
53
+ private createVideoElement;
54
+ private dispose;
55
+ }
56
+ //# sourceMappingURL=layershift-element.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layershift-element.d.ts","sourceRoot":"","sources":["../../../../src/components/layershift/layershift-element.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAmIH,qBAAa,iBAAkB,SAAQ,WAAW;IAChD,MAAM,CAAC,QAAQ,CAAC,QAAQ,yBAAyB;IAEjD,MAAM,KAAK,kBAAkB,IAAI,MAAM,EAAE,CAOxC;IAED,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,SAAS,CAA+B;IAChD,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,YAAY,CAAsC;IAC1D,OAAO,CAAC,WAAW,CAAwC;IAC3D,OAAO,CAAC,KAAK,CAAiC;IAC9C,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,eAAe,CAAgC;IACvD,OAAO,CAAC,SAAS,CAAK;;IAStB,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,WAAW;IASnB,OAAO,KAAK,SAAS,GAA0E;IAC/F,OAAO,KAAK,SAAS,GAA0E;IAC/F,OAAO,KAAK,WAAW,GAA8E;IACrG,OAAO,KAAK,QAAQ,GAAuE;IAC3F,OAAO,KAAK,cAAc,GAAuE;IACjG,OAAO,KAAK,UAAU,GAA+D;IACrF,OAAO,KAAK,UAAU,GAAiE;IAIvF;;;;OAIG;IACH,OAAO,CAAC,IAAI;IAUZ;;;OAGG;IACH,OAAO,CAAC,yBAAyB;IA2BjC,iBAAiB,IAAI,IAAI;IAKzB,oBAAoB,IAAI,IAAI;IAI5B,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAgB7F,OAAO,CAAC,cAAc;YAkCR,IAAI;YAsJJ,kBAAkB;IAuChC,OAAO,CAAC,OAAO;CAyBhB"}
@@ -0,0 +1,65 @@
1
+ export interface LayershiftProps {
2
+ src: string;
3
+ depthSrc: string;
4
+ depthMeta: string;
5
+ parallaxX?: number;
6
+ parallaxY?: number;
7
+ parallaxMax?: number;
8
+ layers?: number;
9
+ overscan?: number;
10
+ autoplay?: boolean;
11
+ loop?: boolean;
12
+ muted?: boolean;
13
+ className?: string;
14
+ style?: Record<string, string>;
15
+ }
16
+ /** Fired once after initialization completes successfully. */
17
+ export interface LayershiftReadyDetail {
18
+ videoWidth: number;
19
+ videoHeight: number;
20
+ duration: number;
21
+ /** Depth analysis profile (present when depth data was analyzed). */
22
+ depthProfile?: import('../../depth-analysis').DepthProfile;
23
+ /** Parameters derived from depth analysis (present when depth data was analyzed). */
24
+ derivedParams?: import('../../depth-analysis').DerivedParallaxParams;
25
+ }
26
+ /** Fired when video starts playing. */
27
+ export interface LayershiftPlayDetail {
28
+ currentTime: number;
29
+ }
30
+ /** Fired when video pauses. */
31
+ export interface LayershiftPauseDetail {
32
+ currentTime: number;
33
+ }
34
+ /** Fired when video loops back to start. */
35
+ export interface LayershiftLoopDetail {
36
+ loopCount: number;
37
+ }
38
+ /** Fired on each new video frame (via requestVideoFrameCallback when available). */
39
+ export interface LayershiftFrameDetail {
40
+ currentTime: number;
41
+ frameNumber: number;
42
+ }
43
+ /** Fired on initialization errors. */
44
+ export interface LayershiftErrorDetail {
45
+ message: string;
46
+ }
47
+ /**
48
+ * Map of all custom events dispatched by `<layershift-parallax>`.
49
+ *
50
+ * Usage with addEventListener:
51
+ * ```ts
52
+ * el.addEventListener('layershift-parallax:ready', (e) => {
53
+ * console.log(e.detail.videoWidth, e.detail.duration);
54
+ * });
55
+ * ```
56
+ */
57
+ export interface LayershiftEventMap {
58
+ 'layershift-parallax:ready': CustomEvent<LayershiftReadyDetail>;
59
+ 'layershift-parallax:play': CustomEvent<LayershiftPlayDetail>;
60
+ 'layershift-parallax:pause': CustomEvent<LayershiftPauseDetail>;
61
+ 'layershift-parallax:loop': CustomEvent<LayershiftLoopDetail>;
62
+ 'layershift-parallax:frame': CustomEvent<LayershiftFrameDetail>;
63
+ 'layershift-parallax:error': CustomEvent<LayershiftErrorDetail>;
64
+ }
65
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/components/layershift/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAMD,8DAA8D;AAC9D,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,YAAY,CAAC,EAAE,OAAO,sBAAsB,EAAE,YAAY,CAAC;IAC3D,qFAAqF;IACrF,aAAa,CAAC,EAAE,OAAO,sBAAsB,EAAE,qBAAqB,CAAC;CACtE;AAED,uCAAuC;AACvC,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,+BAA+B;AAC/B,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,4CAA4C;AAC5C,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,oFAAoF;AACpF,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,sCAAsC;AACtC,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,kBAAkB;IACjC,2BAA2B,EAAE,WAAW,CAAC,qBAAqB,CAAC,CAAC;IAChE,0BAA0B,EAAE,WAAW,CAAC,oBAAoB,CAAC,CAAC;IAC9D,2BAA2B,EAAE,WAAW,CAAC,qBAAqB,CAAC,CAAC;IAChE,0BAA0B,EAAE,WAAW,CAAC,oBAAoB,CAAC,CAAC;IAC9D,2BAA2B,EAAE,WAAW,CAAC,qBAAqB,CAAC,CAAC;IAChE,2BAA2B,EAAE,WAAW,CAAC,qBAAqB,CAAC,CAAC;CACjE"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Depth analysis and parameter derivation for adaptive parallax tuning.
3
+ *
4
+ * This module provides two pure functions:
5
+ *
6
+ * 1. `analyzeDepthFrames()` — computes a statistical DepthProfile from
7
+ * precomputed depth frames (histogram, percentiles, bimodality).
8
+ *
9
+ * 2. `deriveParallaxParams()` — maps a DepthProfile to concrete parallax
10
+ * renderer parameters using continuous functions with algebraic
11
+ * calibration guarantees.
12
+ *
13
+ * ## Calibration invariant
14
+ *
15
+ * The derivation formulas are designed so that the "average scene"
16
+ * (effectiveRange=0.50, bimodality=0.40) produces the exact current
17
+ * hardcoded defaults. This is an algebraic identity, not an approximation.
18
+ *
19
+ * ## Performance
20
+ *
21
+ * Both functions run once at initialization. analyzeDepthFrames samples
22
+ * up to 5 deterministically-chosen frames (~1.3M pixels at 512×512),
23
+ * completing in <5ms. deriveParallaxParams is O(1) arithmetic.
24
+ *
25
+ * ## Determinism
26
+ *
27
+ * Identical input always produces identical output. No randomness,
28
+ * no environment queries, no side effects.
29
+ */
30
+ /**
31
+ * Statistical profile of a video's depth distribution.
32
+ * Computed once from precomputed depth frames at initialization.
33
+ */
34
+ export interface DepthProfile {
35
+ /** Mean depth value, normalized [0, 1]. */
36
+ mean: number;
37
+ /** Standard deviation of depth [0, ~0.5]. */
38
+ stdDev: number;
39
+ /** 5th percentile depth [0, 1]. */
40
+ p5: number;
41
+ /** 25th percentile depth [0, 1]. */
42
+ p25: number;
43
+ /** Median (50th percentile) depth [0, 1]. */
44
+ median: number;
45
+ /** 75th percentile depth [0, 1]. */
46
+ p75: number;
47
+ /** 95th percentile depth [0, 1]. */
48
+ p95: number;
49
+ /** Effective depth range: p95 - p5. [0, 1]. */
50
+ effectiveRange: number;
51
+ /** Interquartile range: p75 - p25. [0, 1]. */
52
+ iqr: number;
53
+ /**
54
+ * Bimodality score [0, 1]. Higher values indicate two distinct
55
+ * depth clusters (clear foreground/background separation).
56
+ */
57
+ bimodality: number;
58
+ /** Normalized 256-bin histogram (sums to 1.0). */
59
+ histogram: Float32Array;
60
+ }
61
+ /**
62
+ * Parallax parameters derived from depth analysis.
63
+ * All values are clamped to safe bounds.
64
+ */
65
+ export interface DerivedParallaxParams {
66
+ parallaxStrength: number;
67
+ contrastLow: number;
68
+ contrastHigh: number;
69
+ verticalReduction: number;
70
+ dofStart: number;
71
+ dofStrength: number;
72
+ pomSteps: number;
73
+ overscanPadding: number;
74
+ }
75
+ /**
76
+ * Compute a statistical depth profile from precomputed depth frames.
77
+ *
78
+ * Samples up to 5 deterministically-chosen frames to build a 256-bin
79
+ * histogram, then extracts percentiles, mean, stdDev, and bimodality.
80
+ *
81
+ * @param frames - Array of Uint8Array depth frames (0=near, 255=far).
82
+ * @param width - Frame width in pixels (e.g. 512).
83
+ * @param height - Frame height in pixels (e.g. 512).
84
+ * @returns DepthProfile with all statistics. If frames is empty, returns
85
+ * a degenerate profile that will trigger rejection in deriveParallaxParams.
86
+ */
87
+ export declare function analyzeDepthFrames(frames: Uint8Array[], width: number, height: number): DepthProfile;
88
+ /**
89
+ * Derive parallax renderer parameters from a depth profile.
90
+ *
91
+ * All derivations are continuous functions of depth statistics.
92
+ * No discrete scene classification. No branching on semantic interpretation.
93
+ *
94
+ * Calibration invariant: when effectiveRange=0.50 and bimodality=0.40,
95
+ * every derived parameter equals the current production default exactly.
96
+ *
97
+ * If the depth profile indicates degenerate data (effectiveRange < 0.05
98
+ * or stdDev < 0.02), returns the exact calibrated defaults.
99
+ *
100
+ * @param profile - DepthProfile from analyzeDepthFrames().
101
+ * @returns DerivedParallaxParams with all values clamped to safe bounds.
102
+ */
103
+ export declare function deriveParallaxParams(profile: DepthProfile): DerivedParallaxParams;
104
+ //# sourceMappingURL=depth-analysis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"depth-analysis.d.ts","sourceRoot":"","sources":["../../src/depth-analysis.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAMH;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;IAEb,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAC;IAEf,mCAAmC;IACnC,EAAE,EAAE,MAAM,CAAC;IAEX,oCAAoC;IACpC,GAAG,EAAE,MAAM,CAAC;IAEZ,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAC;IAEf,oCAAoC;IACpC,GAAG,EAAE,MAAM,CAAC;IAEZ,oCAAoC;IACpC,GAAG,EAAE,MAAM,CAAC;IAEZ,+CAA+C;IAC/C,cAAc,EAAE,MAAM,CAAC;IAEvB,8CAA8C;IAC9C,GAAG,EAAE,MAAM,CAAC;IAEZ;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB,kDAAkD;IAClD,SAAS,EAAE,YAAY,CAAC;CACzB;AAED;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;CACzB;AAyBD;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,UAAU,EAAE,EACpB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,YAAY,CAgFd;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,YAAY,GAAG,qBAAqB,CAwDjF"}
@@ -0,0 +1,22 @@
1
+ export interface ParallaxInput {
2
+ x: number;
3
+ y: number;
4
+ }
5
+ export declare class InputHandler {
6
+ private readonly motionLerpFactor;
7
+ private pointerTarget;
8
+ private motionTarget;
9
+ private smoothedOutput;
10
+ private usingMotionInput;
11
+ private motionListenerAttached;
12
+ constructor(motionLerpFactor: number);
13
+ get isMotionSupported(): boolean;
14
+ get isMotionEnabled(): boolean;
15
+ enableMotionControls(): Promise<boolean>;
16
+ update(): ParallaxInput;
17
+ dispose(): void;
18
+ private readonly handleMouseMove;
19
+ private readonly resetPointerTarget;
20
+ private readonly handleDeviceOrientation;
21
+ }
22
+ //# sourceMappingURL=input-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-handler.d.ts","sourceRoot":"","sources":["../../src/input-handler.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAMD,qBAAa,YAAY;IAOX,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IAN7C,OAAO,CAAC,aAAa,CAAiC;IACtD,OAAO,CAAC,YAAY,CAAiC;IACrD,OAAO,CAAC,cAAc,CAAiC;IACvD,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,sBAAsB,CAAS;gBAEV,gBAAgB,EAAE,MAAM;IAKrD,IAAI,iBAAiB,IAAI,OAAO,CAkB/B;IAED,IAAI,eAAe,IAAI,OAAO,CAE7B;IAEK,oBAAoB,IAAI,OAAO,CAAC,OAAO,CAAC;IAsB9C,MAAM,IAAI,aAAa;IAgBvB,OAAO,IAAI,IAAI;IASf,OAAO,CAAC,QAAQ,CAAC,eAAe,CAK9B;IAEF,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAGjC;IAEF,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAOtC;CACH"}
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Parallax Renderer — GPU-accelerated depth-aware video parallax.
3
+ *
4
+ * Renders a single full-viewport plane textured with the source video
5
+ * (via THREE.VideoTexture) and a precomputed depth map. A custom
6
+ * fragment shader displaces UV coordinates per-pixel based on the
7
+ * depth value and current mouse/gyro input, creating a continuous
8
+ * parallax effect with no discrete layer banding.
9
+ *
10
+ * ## Rendering pipeline (per frame)
11
+ *
12
+ * 1. THREE.VideoTexture auto-updates from the <video> element,
13
+ * providing the color frame at native display resolution.
14
+ *
15
+ * 2. The depth interpolator produces a Uint8Array for the current
16
+ * playback time (interpolated between precomputed 5fps keyframes,
17
+ * bilateral-filtered on the CPU, then quantized to 0-255).
18
+ * This is copied into a single-channel DataTexture on the GPU.
19
+ *
20
+ * 3. The InputHandler provides a smoothed {x, y} offset in [-1, 1].
21
+ * This is passed to the shader as the uOffset uniform.
22
+ *
23
+ * 4. The fragment shader samples the depth map at each pixel's UV,
24
+ * computes a UV displacement proportional to (1 - depth) * strength,
25
+ * and samples the video texture at the displaced coordinates.
26
+ * Near objects (depth ≈ 0) move more; far objects (depth ≈ 1) less.
27
+ *
28
+ * 5. When POM is enabled, the shader ray-marches through the depth
29
+ * field to find the correct surface intersection, producing
30
+ * self-occlusion (near objects cover far objects behind them).
31
+ *
32
+ * ## Texture memory
33
+ *
34
+ * Only 2 textures per frame: 1 VideoTexture (GPU-managed, zero CPU
35
+ * upload) + 1 depth DataTexture (1024×1024 Uint8 = 1 MB, uploaded
36
+ * only when depth changes at ~5fps). This is ~5× less bandwidth
37
+ * than the old 5-layer RGBA system.
38
+ */
39
+ import type { ParallaxInput } from './input-handler';
40
+ /** Configuration subset relevant to the parallax renderer. */
41
+ export interface ParallaxRendererConfig {
42
+ parallaxStrength: number;
43
+ pomEnabled: boolean;
44
+ pomSteps: number;
45
+ overscanPadding: number;
46
+ /**
47
+ * Depth-adaptive shader parameters.
48
+ * When omitted, calibrated defaults matching the current hardcoded values
49
+ * are used. When provided, the explicit value overrides the derived value.
50
+ */
51
+ contrastLow?: number;
52
+ contrastHigh?: number;
53
+ verticalReduction?: number;
54
+ dofStart?: number;
55
+ dofStrength?: number;
56
+ }
57
+ export declare class ParallaxRenderer {
58
+ /** Debounce delay for resize events to avoid layout thrashing. */
59
+ private static readonly RESIZE_DEBOUNCE_MS;
60
+ /** Compile-time upper bound for the POM for-loop in GLSL. */
61
+ private static readonly MAX_POM_STEPS;
62
+ private readonly scene;
63
+ private readonly camera;
64
+ private readonly renderer;
65
+ private readonly container;
66
+ private videoTexture;
67
+ private depthTexture;
68
+ private mesh;
69
+ private videoAspect;
70
+ private readDepth;
71
+ private readInput;
72
+ private playbackVideo;
73
+ /**
74
+ * Optional callback invoked on each new video frame (from RVFC).
75
+ * The Web Component uses this to dispatch the 'layershift-parallax:frame' event.
76
+ */
77
+ private onVideoFrame;
78
+ private animationFrameHandle;
79
+ /** requestVideoFrameCallback handle (0 = inactive). */
80
+ private rvfcHandle;
81
+ /** Whether RVFC is supported on the current video element. */
82
+ private rvfcSupported;
83
+ private resizeObserver;
84
+ private resizeTimer;
85
+ private currentPlaneWidth;
86
+ private currentPlaneHeight;
87
+ /**
88
+ * Create the renderer and attach its canvas to the DOM.
89
+ *
90
+ * @param parent - The container element that the WebGL canvas is
91
+ * appended to. The renderer sizes itself to fill this element.
92
+ * @param config - Parallax-specific settings (strength, POM, overscan).
93
+ * Optional shader parameters are merged with calibrated defaults.
94
+ */
95
+ /** Resolved config with all optional shader params filled from defaults. */
96
+ private readonly config;
97
+ constructor(parent: HTMLElement, config: ParallaxRendererConfig);
98
+ /**
99
+ * Set up the scene: create VideoTexture, depth DataTexture, and the
100
+ * single mesh with the custom parallax ShaderMaterial.
101
+ *
102
+ * Call this once after the video element and depth data are loaded.
103
+ *
104
+ * @param video - The <video> element to sample color frames from.
105
+ * Must already have metadata loaded (videoWidth/videoHeight set).
106
+ * @param depthWidth - Width of the precomputed depth map (e.g. 512).
107
+ * @param depthHeight - Height of the precomputed depth map (e.g. 512).
108
+ */
109
+ initialize(video: HTMLVideoElement, depthWidth: number, depthHeight: number): void;
110
+ /**
111
+ * Begin the render loop.
112
+ *
113
+ * When `requestVideoFrameCallback` is available, two loops run:
114
+ * 1. RVFC loop — fires once per new video frame, handles depth update.
115
+ * 2. RAF loop — fires at display refresh rate, handles input + render.
116
+ *
117
+ * When RVFC is not available, falls back to a single RAF loop that
118
+ * does everything (the pre-RVFC behavior).
119
+ *
120
+ * @param readDepth - Called with the current video time.
121
+ * Returns a Uint8Array of depth values (0=near, 255=far) at the
122
+ * depth texture's resolution. The interpolator handles caching
123
+ * so redundant calls (same depth frame) return instantly.
124
+ * @param readInput - Returns the smoothed parallax input {x, y}
125
+ * in [-1, 1].
126
+ * @param onVideoFrame - Optional callback invoked on each new
127
+ * video frame. Receives the accurate media time and the
128
+ * browser's presented-frame counter.
129
+ */
130
+ start(video: HTMLVideoElement, readDepth: (timeSec: number) => Uint8Array, readInput: () => ParallaxInput, onVideoFrame?: (currentTime: number, frameNumber: number) => void): void;
131
+ /** Stop both render loops and release callbacks. */
132
+ stop(): void;
133
+ /** Stop rendering and release all GPU resources. */
134
+ dispose(): void;
135
+ /** Check whether requestVideoFrameCallback is available. */
136
+ private static isRVFCSupported;
137
+ /**
138
+ * RVFC callback — fires only when the browser presents a new video frame.
139
+ *
140
+ * Handles the expensive depth texture update, which only needs to happen
141
+ * when the video frame actually changes (~24-30fps, not 60-120fps).
142
+ *
143
+ * Uses `metadata.mediaTime` for more accurate depth reads than
144
+ * `video.currentTime` (which can lag behind the presented frame).
145
+ */
146
+ private readonly videoFrameLoop;
147
+ /**
148
+ * Main render loop — called every animation frame at display refresh rate.
149
+ *
150
+ * When RVFC is active, this only handles:
151
+ * 1. Updating the parallax offset uniform from input (buttery smooth).
152
+ * 2. Rendering the scene (single draw call).
153
+ *
154
+ * The depth texture is updated separately by videoFrameLoop at video
155
+ * frame rate. This separation means parallax stays smooth at 60/120fps
156
+ * even though depth only updates at 24-30fps.
157
+ *
158
+ * When RVFC is NOT supported, this falls back to the original behavior:
159
+ * depth update + input update + render all in a single RAF tick.
160
+ */
161
+ private readonly renderLoop;
162
+ /**
163
+ * Set up a ResizeObserver on the container element and a fallback
164
+ * window resize listener. Both trigger a debounced recalculation
165
+ * of the viewport layout, camera, and plane geometry.
166
+ */
167
+ private setupResizeHandling;
168
+ /** Debounce resize events to avoid expensive layout recalculations. */
169
+ private readonly scheduleResizeRecalculate;
170
+ /**
171
+ * Recalculate the WebGL canvas size, orthographic camera frustum,
172
+ * and plane geometry to match the current container dimensions.
173
+ *
174
+ * The plane is sized to "cover" the viewport (like CSS object-fit:
175
+ * cover) plus extra overscan padding so that parallax displacement
176
+ * doesn't reveal the plane edges.
177
+ */
178
+ private recalculateViewportLayout;
179
+ /** Read the container's pixel dimensions, with a minimum of 1×1. */
180
+ private getViewportSize;
181
+ /**
182
+ * Compute the plane dimensions needed to cover the viewport while
183
+ * preserving the video's aspect ratio, plus overscan padding.
184
+ *
185
+ * "Cover" means the plane is scaled so the shorter axis fills the
186
+ * viewport (the longer axis overflows). Overscan adds extra size
187
+ * proportional to the parallax strength so that maximum displacement
188
+ * never reveals the plane edge.
189
+ *
190
+ * @returns planeWidth/planeHeight in world units (= pixels, since
191
+ * the camera is set up 1:1).
192
+ */
193
+ private computeCoverPlaneSize;
194
+ /** Dispose the mesh, material, and textures from the scene. */
195
+ private disposeScene;
196
+ }
197
+ //# sourceMappingURL=parallax-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parallax-renderer.d.ts","sourceRoot":"","sources":["../../src/parallax-renderer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ export interface DepthMeta {
2
+ frameCount: number;
3
+ fps: number;
4
+ width: number;
5
+ height: number;
6
+ sourceFps: number;
7
+ }
8
+ export interface PrecomputedDepthData {
9
+ meta: DepthMeta;
10
+ frames: Uint8Array[];
11
+ }
12
+ export interface BinaryDownloadProgress {
13
+ receivedBytes: number;
14
+ totalBytes: number | null;
15
+ fraction: number;
16
+ }
17
+ /**
18
+ * Worker-backed depth frame interpolator.
19
+ *
20
+ * Offloads the bilateral filter, interpolation, and resize to a Web Worker
21
+ * so the main thread stays free for smooth rendering. Uses double-buffering:
22
+ * the main thread always has a ready-to-use depth frame while the worker
23
+ * computes the next one in parallel.
24
+ *
25
+ * ## Double-buffer strategy
26
+ *
27
+ * - `currentBuffer` — the latest completed depth frame, always available
28
+ * for synchronous reads via `sample()`.
29
+ * - When the render loop calls `sample(timeSec)`, it returns `currentBuffer`
30
+ * immediately (zero main-thread work) and posts a request to the worker
31
+ * for the new time if it differs from the last requested time.
32
+ * - When the worker responds, `currentBuffer` is swapped to the new result.
33
+ * - During the 1-2 frames of latency, the previous depth frame is shown —
34
+ * this is imperceptible since depth only changes at ~5fps.
35
+ */
36
+ export declare class WorkerDepthInterpolator {
37
+ private worker;
38
+ private currentBuffer;
39
+ private pendingTimeSec;
40
+ private workerBusy;
41
+ private disposed;
42
+ private constructor();
43
+ /**
44
+ * Create a WorkerDepthInterpolator, initializing the worker with frame data.
45
+ *
46
+ * The frame ArrayBuffers are transferred (zero-copy) to the worker.
47
+ * After this call, the original depthData.frames arrays are neutered
48
+ * and should not be accessed.
49
+ */
50
+ static create(depthData: PrecomputedDepthData, targetWidth: number, targetHeight: number): Promise<WorkerDepthInterpolator>;
51
+ /**
52
+ * Get the current depth frame — always synchronous, zero main-thread work.
53
+ *
54
+ * Returns the latest completed depth frame from the worker. If the worker
55
+ * hasn't finished processing the current time yet, returns the previous
56
+ * frame (1-2 frame latency is imperceptible at ~5fps depth changes).
57
+ *
58
+ * Automatically requests the worker to process the new time in the background.
59
+ */
60
+ sample(timeSec: number): Uint8Array;
61
+ /** Send a sample request to the worker if it's not busy. */
62
+ private requestSample;
63
+ /** Terminate the worker and release resources. */
64
+ dispose(): void;
65
+ }
66
+ export declare class DepthFrameInterpolator {
67
+ private readonly depthData;
68
+ private readonly targetWidth;
69
+ private readonly targetHeight;
70
+ private readonly interpolatedDepth;
71
+ private readonly resizedDepth;
72
+ private readonly bilateralOutput;
73
+ private readonly uint8Output;
74
+ private lastFrameIndex;
75
+ private lastNextFrameIndex;
76
+ private lastLerpFactor;
77
+ constructor(depthData: PrecomputedDepthData, targetWidth: number, targetHeight: number);
78
+ sample(timeSec: number): Uint8Array;
79
+ }
80
+ export declare function loadPrecomputedDepth(depthDataUrl: string, depthMetaUrl: string, onProgress?: (progress: BinaryDownloadProgress) => void): Promise<PrecomputedDepthData>;
81
+ //# sourceMappingURL=precomputed-depth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"precomputed-depth.d.ts","sourceRoot":"","sources":["../../src/precomputed-depth.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,SAAS;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,sBAAsB;IACrC,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,uBAAuB;IAClC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAS;IAEzB,OAAO;IAQP;;;;;;OAMG;WACU,MAAM,CACjB,SAAS,EAAE,oBAAoB,EAC/B,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,uBAAuB,CAAC;IAqEnC;;;;;;;;OAQG;IACH,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU;IAKnC,4DAA4D;IAC5D,OAAO,CAAC,aAAa;IAarB,kDAAkD;IAClD,OAAO,IAAI,IAAI;CAIhB;AAED,qBAAa,sBAAsB;IAe/B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAhB/B,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAe;IACjD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAe;IAC/C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAa;IAMzC,OAAO,CAAC,cAAc,CAAM;IAC5B,OAAO,CAAC,kBAAkB,CAAM;IAChC,OAAO,CAAC,cAAc,CAAM;gBAGT,SAAS,EAAE,oBAAoB,EAC/B,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM;IAUvC,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU;CA8DpC;AAED,wBAAsB,oBAAoB,CACxC,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,EACpB,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,sBAAsB,KAAK,IAAI,GACtD,OAAO,CAAC,oBAAoB,CAAC,CAO/B"}
@@ -0,0 +1,23 @@
1
+ export interface FrameExtractionOptions {
2
+ fps: number;
3
+ maxDurationSec: number;
4
+ workingMaxWidth: number;
5
+ }
6
+ export interface ExtractionPlan {
7
+ fps: number;
8
+ durationSec: number;
9
+ frameCount: number;
10
+ width: number;
11
+ height: number;
12
+ }
13
+ export interface ExtractedFrame {
14
+ index: number;
15
+ timeSec: number;
16
+ imageData: ImageData;
17
+ width: number;
18
+ height: number;
19
+ }
20
+ export declare function createHiddenVideoElement(url: string): Promise<HTMLVideoElement>;
21
+ export declare function createExtractionPlan(video: HTMLVideoElement, options: FrameExtractionOptions): ExtractionPlan;
22
+ export declare function extractFramesBySeeking(video: HTMLVideoElement, plan: ExtractionPlan, onFrame: (frame: ExtractedFrame, totalFrames: number) => Promise<void> | void, onProgress?: (progress: number) => void): Promise<void>;
23
+ //# sourceMappingURL=video-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video-source.d.ts","sourceRoot":"","sources":["../../src/video-source.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,sBAAsB;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,SAAS,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAID,wBAAsB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAmBrF;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,gBAAgB,EACvB,OAAO,EAAE,sBAAsB,GAC9B,cAAc,CAehB;AAED,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,gBAAgB,EACvB,IAAI,EAAE,cAAc,EACpB,OAAO,EAAE,CAAC,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,EAC7E,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,GACtC,OAAO,CAAC,IAAI,CAAC,CAmCf"}