lensfx 0.1.0 → 0.1.2

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.
package/dist/index.d.ts CHANGED
@@ -1,44 +1,79 @@
1
1
  import React from 'react';
2
2
 
3
3
  /**
4
- * Aave Glass Rendering System - Types and Constants
5
- * Reverse-engineered directly from aave.com/design/building-glass-for-the-web
4
+ * lensfx — Engine Layer: Types & Constants
5
+ *
6
+ * Pure TypeScript — no React, no DOM. Can be imported in any environment.
6
7
  */
8
+ /** All optical parameters that control how a single lens looks and distorts. */
7
9
  interface LensParams {
10
+ /** Lens width in pixels. */
8
11
  lensW: number;
12
+ /** Lens height in pixels. */
9
13
  lensH: number;
14
+ /** Lens corner radius in pixels. */
10
15
  borderRadius: number;
16
+ /** Refraction depth/strength — higher = more distortion. Range: 0–10. */
11
17
  depth: number;
18
+ /** Chromatic aberration split amount. 0 = monochromatic, 1 = full RGB split. */
12
19
  chromaAmount: number;
20
+ /** Horizontal displacement scale. 0 = no lateral displacement. */
13
21
  scaleX: number;
22
+ /** Vertical displacement scale. 0 = no vertical displacement. */
14
23
  scaleY: number;
24
+ /** Resolution of the displacement map canvas (power-of-two recommended). */
15
25
  mapSize: number;
26
+ /** Gaussian blur radius applied to the background behind the lens (px). */
16
27
  blurAmount: number;
28
+ /** If true, clips displacement strictly to the lens SDF boundary. */
17
29
  sdfBoundary: boolean;
30
+ /** If true, applies an erf-smoothed falloff at the lens edge. */
18
31
  edgeFalloff: boolean;
32
+ /** Additive brightness overlay inside the lens. Positive = lighter, negative = darker. Range: -1 to 1. */
19
33
  brightness: number;
34
+ /** Intensity of the specular highlight. Range: 0–3. */
20
35
  specularStrength: number;
36
+ /** Angle of the specular directional light in degrees. */
21
37
  specularRotation: number;
38
+ /** If true, uses dark (shadow) specular mode instead of bright. */
22
39
  specularDark: boolean;
40
+ /** Uniform tint applied inside the lens. Range: -1 (black) to 1 (white). */
23
41
  tint: number;
42
+ /** Strength of the inner glow band. Range: 0–1. */
24
43
  glowStrength: number;
44
+ /** Angular spread of the glow band, normalised. Range: 0–1. */
25
45
  glowSpread: number;
46
+ /** Exponent controlling glow falloff curve. Higher = sharper band. */
26
47
  glowExponent: number;
48
+ /** Strength of the edge specular ring (separate from inner glow). Range: 0–1. */
27
49
  edgeStrength: number;
50
+ /** Thickness of the edge specular ring in pixels. */
28
51
  edgeWidth: number;
52
+ /** Exponent controlling edge ring falloff curve. */
29
53
  edgeExponent: number;
54
+ /** Height of the ellipsoid dome above the lens surface in pixels. */
30
55
  domeDepth: number;
56
+ /** How much to reduce displacement magnitude at corners (0 = no splay, 1 = full splay). */
31
57
  splayAmount: number;
58
+ /** Box shadow applied to the lens container (outer drop shadow). */
32
59
  edgeShadow?: string;
60
+ /** Inset box shadow applied to the lens container. */
33
61
  edgeInsetShadow?: string;
62
+ /** Secondary rest-state shadow (e.g. when the slider thumb is at rest). */
34
63
  restEdgeShadow?: string;
35
64
  }
65
+ /** Precomputed geometric constants for dome curvature. */
36
66
  interface DomeConstants {
67
+ /** Horizontal sphere radius for dome curvature. */
37
68
  Rx: number;
69
+ /** Vertical sphere radius for dome curvature. */
38
70
  Ry: number;
71
+ /** Normalised scale factor for the X gradient (trapezoidal integration). */
39
72
  scaleX: number;
73
+ /** Normalised scale factor for the Y gradient (trapezoidal integration). */
40
74
  scaleY: number;
41
75
  }
76
+ /** Input to the synchronous displacement map generator. */
42
77
  interface MapGeneratorInput {
43
78
  lensHalfWidth: number;
44
79
  lensHalfHeight: number;
@@ -56,102 +91,106 @@ interface MapGeneratorInput {
56
91
  domeDepth: number;
57
92
  splayAmount: number;
58
93
  }
94
+ /** Output from the displacement map generator. */
59
95
  interface MapGeneratorOutput {
96
+ /** PNG data URL of the generated displacement map. */
60
97
  dataUrl: string;
98
+ /** Time taken for the pixel loop, in milliseconds. */
61
99
  loopMs: number;
100
+ /** Time taken for canvas.toDataURL encoding, in milliseconds. */
62
101
  encodeMs: number;
63
102
  }
64
- interface MultiLensSlot {
65
- filterEl: SVGFilterElement | null;
66
- feDispEl: SVGFEDisplacementMapElement | null;
67
- subSlots: SubSlot[];
68
- assignedTo: HTMLElement | null;
69
- version: number;
70
- lastDispScale: string;
71
- lastFilterW: string;
72
- lastFilterH: string;
73
- }
74
- interface SubSlot {
75
- feImageEl: SVGFEImageElement | null;
76
- feColorMatrixEl: SVGFEColorMatrixElement | null;
77
- feMaskEl: SVGFEImageElement | null;
78
- feMaskColorMatrixEl: SVGFEColorMatrixElement | null;
79
- assignedLensId: string | null;
80
- lastHref: string;
81
- lastX: string;
82
- lastY: string;
83
- lastW: string;
84
- lastH: string;
85
- lastColorMatrix: string;
86
- lastMaskKey: string;
87
- lastMaskColorMatrix: string;
88
- }
89
- interface SingleLensSlot {
90
- filterEl: SVGFilterElement | null;
91
- lensEls: SVGElement[];
92
- dispEls: SVGFEDisplacementMapElement[];
93
- mapMatrixEl: SVGFEColorMatrixElement | null;
94
- feImageEl: SVGFEImageElement | null;
95
- version: number;
96
- assignedTo: HTMLElement | null;
97
- assignedRect: DOMRect | null;
98
- }
103
+ /** Zero-refraction baseline — all effects disabled. */
99
104
  declare const DEFAULT_LENS_PARAMS: LensParams;
100
- declare const AAVE_GLASS_SELECTOR: {
101
- readonly container: "[data-aave-glass-container]";
102
- readonly target: "[data-refraction-target]";
103
- };
105
+ /** LensSwitch base optical parameters. */
104
106
  declare const SWITCH_GLASS_DEFAULTS: Partial<LensParams>;
105
107
  declare const SWITCH_DARK_OVERRIDES: Partial<LensParams>;
106
108
  declare const SWITCH_LIGHT_OVERRIDES: Partial<LensParams>;
109
+ /** LensSlider base optical parameters. */
107
110
  declare const SLIDER_GLASS_DEFAULTS: Partial<LensParams>;
108
111
  declare const SLIDER_DARK_OVERRIDES: Partial<LensParams>;
109
112
  declare const SLIDER_LIGHT_OVERRIDES: Partial<LensParams>;
110
113
  declare const SLIDER_SAFARI_OVERRIDES: Partial<LensParams>;
111
114
 
112
115
  /**
113
- * Aave Glass Rendering System - Core Math & Displacement Map Generators
114
- * Reverse-engineered directly from aave.com/design/building-glass-for-the-web
116
+ * lensfx — Engine Layer: Core Mathematics & Displacement Map Generation
117
+ *
118
+ * Pure TypeScript — no React, no DOM references at module load time.
119
+ * DOM (canvas) is accessed only inside function bodies, never at import time,
120
+ * making this SSR-safe.
115
121
  */
116
122
 
117
123
  /**
118
- * Approximation of Gauss error function using hyperbolic tangent.
119
- * sqrt(pi) approx 1.7724538509
124
+ * Approximation of the Gauss error function using hyperbolic tangent.
125
+ *
126
+ * erf(x) ≈ tanh(√π · x)
127
+ *
128
+ * The constant √π ≈ 1.7724538509 ensures that the derivative at 0 equals
129
+ * 2/√π (matching the exact erf), giving a C∞ smooth clamping function that
130
+ * avoids branching and is monotone on (−∞, +∞).
120
131
  */
121
132
  declare function erf(x: number): number;
122
133
  /**
123
- * Computes dome curvature geometry constants for horizontal (Rx) and vertical (Ry) axes.
134
+ * Computes ellipsoid dome curvature geometry constants for the X and Y axes.
135
+ *
136
+ * The dome is modelled as a sphere of radius R that just touches both the
137
+ * centre and the edge of the lens: R = (halfDim² + depth²) / (2·depth).
138
+ *
139
+ * @param domeDepth - maximum height of the dome above the lens plane (px)
140
+ * @param halfW - half-width of the lens (px)
141
+ * @param halfH - half-height of the lens (px)
124
142
  */
125
143
  declare function computeDomeConstants(domeDepth: number, halfW: number, halfH: number): DomeConstants;
126
144
  /**
127
- * Evaluates the gradient of the dome surface at a given offset from center.
145
+ * Evaluates the gradient (surface slope) of the dome at a given offset
146
+ * from the lens centre. Returns a value in [0, 1] normalised by `scale`.
128
147
  */
129
148
  declare function domeGradient(pos: number, R: number, scale: number): number;
130
149
  /**
131
- * Generates the SVG feColorMatrix 4x5 transformation string to scale the displacement R and G channels.
132
- * Scales displacement around the neutral center (128 / 255 = 0.5).
150
+ * Builds an SVG feColorMatrix 4×5 matrix string that rescales the
151
+ * displacement map's R and G channels around the neutral midpoint (128/255 = 0.5).
152
+ *
153
+ * When scaleX ≠ scaleY the lens has asymmetric horizontal/vertical displacement
154
+ * and we normalise the larger axis to 1 so both axes are within [0, 255].
133
155
  */
134
156
  declare function colorMatrixValues(ratioX: number, ratioY: number): string;
135
157
  /**
136
- * Generates an SVG data URI of a rounded rectangle used as a CSS mask-image for lens blur/tint layers.
158
+ * Returns a memoised SVG rounded-rectangle mask as a data URI.
159
+ * Used as CSS `mask-image` on the backdrop-blur and tint layers to
160
+ * clip them to the lens shape without touching the filter graph.
137
161
  */
138
162
  declare function getSvgMaskDataUri(width: number, height: number, radius: number): {
139
163
  uri: string;
140
164
  key: string;
141
165
  };
142
166
  /**
143
- * Fast synchronous displacement map generator using 4-fold quadrant symmetry.
144
- * Computes top-left quadrant and writes into all 4 quadrants in parallel.
145
- * Reuses canvas and typed array memory across frames.
167
+ * Creates a stateful, synchronous displacement map generator with:
168
+ * • 4-fold quadrant symmetry (75 % fewer pixel evaluations)
169
+ * • Reusable canvas & ImageData to avoid GC pressure
170
+ * • Cached dome gradient lookup table (recalculated only when geometry changes)
171
+ *
172
+ * Use for animated lenses where the map must update every frame in sync with
173
+ * Framer Motion values. For static lenses, prefer `generateDisplacementMapAsync`.
174
+ *
175
+ * @param mapSize - side length of the square displacement map canvas (px)
146
176
  */
147
177
  declare function createDisplacementMapGeneratorSync(mapSize: number): {
178
+ /**
179
+ * Generate a new displacement map and return a PNG data URL.
180
+ * Reuses internal canvas memory; safe to call every animation frame.
181
+ */
148
182
  generate(input: MapGeneratorInput): MapGeneratorOutput;
183
+ /** Release canvas memory. Call in React cleanup (useEffect return). */
149
184
  dispose(): void;
150
185
  };
151
186
  /**
152
- * Asynchronous displacement map generator for static / resting lenses.
153
- * Processes 64 rows per frame, yielding via setTimeout(0) to avoid locking UI thread.
154
- * Returns a PNG Blob URL.
187
+ * Generates a displacement map asynchronously, processing 64 rows per
188
+ * animation frame via `setTimeout(0)` to avoid blocking the main thread.
189
+ *
190
+ * Returns a Blob Object URL (PNG). Caller is responsible for calling
191
+ * `URL.revokeObjectURL` when the Lens unmounts.
192
+ *
193
+ * Use for static lenses where the map does not need to update every frame.
155
194
  */
156
195
  declare function generateDisplacementMapAsync(canvas: HTMLCanvasElement, params: {
157
196
  canvasSize: number;
@@ -172,6 +211,162 @@ declare function generateDisplacementMapAsync(canvas: HTMLCanvasElement, params:
172
211
  splayAmount?: number;
173
212
  }): Promise<string | null>;
174
213
 
214
+ /**
215
+ * lensfx — WebGL Layer: VideoRefractionRenderer
216
+ *
217
+ * Full WebGL 1 video refraction engine — Safari fallback for live <video> frames
218
+ * which cannot be filtered by SVG feDisplacementMap in WebKit.
219
+ *
220
+ * Features:
221
+ * - Analytical circle and rounded-rect bar SDFs
222
+ * - Multi-control dominant lens tracking with press scaling
223
+ * - Separable 9-tap Gaussian blur pass (two-pass half-res FBO)
224
+ * - Chromatic aberration (R, G, B pass)
225
+ * - Adaptive specular & Apple-style vibrancy luminance correction
226
+ */
227
+ /** Options for constructing a VideoRefractionRenderer. */
228
+ interface VideoRendererOptions {
229
+ /** Canvas element to render into. */
230
+ canvas: HTMLCanvasElement;
231
+ /** Source video element to sample frames from. */
232
+ video: HTMLVideoElement;
233
+ /** Lens depth strength. Default: 2. */
234
+ depth?: number;
235
+ /** Chromatic aberration amount (0–1). Default: 0.5. */
236
+ chromaAmount?: number;
237
+ /** Angle of specular directional light (degrees). Default: 45. */
238
+ specularRotation?: number;
239
+ /** Specular highlight intensity. Default: 1. */
240
+ specularStrength?: number;
241
+ }
242
+ interface VideoLensConfig {
243
+ circles: Array<{
244
+ cx: number;
245
+ cy: number;
246
+ radius: number;
247
+ scale: number;
248
+ baseScale: number;
249
+ ratioX: number;
250
+ ratioY: number;
251
+ }>;
252
+ bar?: {
253
+ cx: number;
254
+ cy: number;
255
+ w: number;
256
+ h: number;
257
+ radius: number;
258
+ baseScale: number;
259
+ ratioX: number;
260
+ ratioY: number;
261
+ };
262
+ bbox: {
263
+ x: number;
264
+ y: number;
265
+ w: number;
266
+ h: number;
267
+ };
268
+ bboxSize: {
269
+ w: number;
270
+ h: number;
271
+ };
272
+ chromaAmount: number;
273
+ specStrength: number;
274
+ adaptStrength: number;
275
+ specLumaLow: number;
276
+ specLumaHigh: number;
277
+ hasBlur: boolean;
278
+ }
279
+ declare class VideoRefractionRenderer {
280
+ private gl;
281
+ private quadBuffer;
282
+ private blurProgram;
283
+ private refractionProgram;
284
+ private videoTexture;
285
+ private mapTexture;
286
+ private blurFboA;
287
+ private blurTexA;
288
+ private blurFboB;
289
+ private blurTexB;
290
+ private fboWidth;
291
+ private fboHeight;
292
+ constructor(canvas: HTMLCanvasElement);
293
+ setDisplacementMap(image: HTMLImageElement | HTMLCanvasElement): void;
294
+ private initBlurFBO;
295
+ render(video: HTMLVideoElement, config: VideoLensConfig): void;
296
+ dispose(): void;
297
+ }
298
+
299
+ /**
300
+ * lensfx — WebGL Layer: QRRefractionRenderer
301
+ *
302
+ * Full WebGL 2 (#version 300 es) interactive QR code refraction engine.
303
+ * Uses an O(1) uniform texture occupancy grid for real-time liquid distortion
304
+ * through a 33×33 QR matrix.
305
+ *
306
+ * Features:
307
+ * - WebGL 2 (#version 300 es)
308
+ * - O(1) dot occupancy texture lookup
309
+ * - Analytical eye rounded-rect SDFs with hover scaling
310
+ * - Attenuated displacement for eye corner anchors
311
+ * - Chromatic aberration (scaleR = 1 + 2*chroma, scaleG = 1 + 1*chroma, scaleB = 1)
312
+ */
313
+ /** Options for constructing a QRRefractionRenderer. */
314
+ interface QRRendererOptions {
315
+ /** Canvas element to render into. */
316
+ canvas: HTMLCanvasElement;
317
+ /** QR matrix data (boolean 33×33 grid, row-major). */
318
+ qrMatrix: boolean[][];
319
+ /** Lens depth strength. Default: 2. */
320
+ depth?: number;
321
+ /** Chromatic aberration amount (0–1). Default: 0.3. */
322
+ chromaAmount?: number;
323
+ /** Dot radius as a fraction of cell size. Default: 0.4. */
324
+ dotRadius?: number;
325
+ }
326
+ interface QREyeConfig {
327
+ center: [number, number];
328
+ half: [number, number, number];
329
+ radius: [number, number, number];
330
+ color: [number, number, number];
331
+ scale: number;
332
+ }
333
+ interface QRRendererInit {
334
+ canvas: HTMLCanvasElement;
335
+ size: number;
336
+ matrixLength: number;
337
+ occupancyMatrix: Uint8Array;
338
+ gridOriginUV: number;
339
+ cellUV: number;
340
+ dotRadius: number;
341
+ backgroundColor?: [number, number, number];
342
+ }
343
+ declare class QRRefractionRenderer {
344
+ private gl;
345
+ private program;
346
+ private quadBuffer;
347
+ private occupancyTexture;
348
+ private displacementTexture;
349
+ private paintingTexture;
350
+ private paintingColorTexture;
351
+ private matrixLength;
352
+ private gridOriginUV;
353
+ private cellUV;
354
+ private dotRadius;
355
+ private backgroundColor;
356
+ constructor(init: QRRendererInit);
357
+ setDisplacementMap(image: HTMLImageElement | HTMLCanvasElement): void;
358
+ render(params: {
359
+ displacementActive: boolean;
360
+ lensOrigin: [number, number];
361
+ lensSize: [number, number];
362
+ displacementScale: [number, number];
363
+ chromaAmount: number;
364
+ eyes: QREyeConfig[];
365
+ eyeRefractionScale?: number;
366
+ }): void;
367
+ dispose(): void;
368
+ }
369
+
175
370
  interface AaveGlassProps {
176
371
  children?: React.ReactNode;
177
372
  lens?: Partial<LensParams>;
@@ -328,141 +523,4 @@ interface GlassToggleGroupProps {
328
523
  }
329
524
  declare function GlassToggleGroup({ options, value: controlledValue, defaultValue, onValueChange, lens, theme, className, }: GlassToggleGroupProps): React.JSX.Element;
330
525
 
331
- interface GlassPlaygroundProps {
332
- initialParams?: Partial<LensParams>;
333
- theme?: "light" | "dark";
334
- className?: string;
335
- }
336
- declare function GlassPlayground({ initialParams, theme, className, }: GlassPlaygroundProps): React.JSX.Element;
337
-
338
- /**
339
- * Aave Video Player WebGL Refraction Engine
340
- * Reverse-engineered directly from aave.com/design/building-glass-for-the-web
341
- *
342
- * Implements GPU video refraction fallback for Safari and high-performance video controls.
343
- * Features:
344
- * - Analytical circle and rounded-rect bar SDFs
345
- * - Multi-control dominant lens tracking with press scaling
346
- * - Separable 9-tap Gaussian blur pass (two-pass half-res FBO)
347
- * - Chromatic aberration (R, G, B pass)
348
- * - Adaptive specular & Apple-style vibrancy luminance correction
349
- */
350
- declare const VERTEX_SHADER = "\nattribute vec2 a_pos;\nvarying vec2 v_uv;\nvoid main() {\n // Flip Y so UV (0,0) = top-left, matching HTML/CSS coordinates.\n v_uv = vec2(a_pos.x * 0.5 + 0.5, 0.5 - a_pos.y * 0.5);\n gl_Position = vec4(a_pos, 0.0, 1.0);\n}\n";
351
- declare const BLUR_FRAGMENT_SHADER = "\nprecision highp float;\nuniform sampler2D u_source;\nuniform vec2 u_dir;\nvarying vec2 v_uv;\n\nvoid main() {\n vec4 c = texture2D(u_source, v_uv) * 0.2042;\n c += (texture2D(u_source, v_uv + 1.0 * u_dir) + texture2D(u_source, v_uv - 1.0 * u_dir)) * 0.1801;\n c += (texture2D(u_source, v_uv + 2.0 * u_dir) + texture2D(u_source, v_uv - 2.0 * u_dir)) * 0.1240;\n c += (texture2D(u_source, v_uv + 3.0 * u_dir) + texture2D(u_source, v_uv - 3.0 * u_dir)) * 0.0663;\n c += (texture2D(u_source, v_uv + 4.0 * u_dir) + texture2D(u_source, v_uv - 4.0 * u_dir)) * 0.0276;\n gl_FragColor = c;\n}\n";
352
- declare const REFRACTION_FRAGMENT_SHADER = "\n#extension GL_OES_standard_derivatives : enable\nprecision highp float;\n\nuniform sampler2D u_video;\nuniform sampler2D u_map;\nuniform sampler2D u_blurred;\nuniform float u_baseScale[3];\nuniform float u_ratioX[3];\nuniform float u_ratioY[3];\nuniform float u_chromaAmount;\nuniform float u_specStrength;\nuniform float u_adaptStrength;\nuniform float u_specLumaLow;\nuniform float u_specLumaHigh;\nuniform float u_hasBlur;\nuniform vec4 u_bbox; // (x, y, w, h) normalised, y=0 at top\nuniform vec3 u_circles[3]; // (cx, cy, radius) in bbox pixel space\nuniform float u_scale[3]; // per-circle geometric press scale (1 = unscaled)\nuniform vec4 u_bar; // (cx, cy, w, h) in bbox pixel space; w<=0 means inactive\nuniform float u_barRadius; // corner radius for the rounded-rect bar lens\nuniform float u_barBaseScale;\nuniform float u_barRatioX;\nuniform float u_barRatioY;\nuniform vec2 u_bboxSize; // bbox (width, height) in pixels\n\nvarying vec2 v_uv;\n\nvoid main() {\n vec2 uv = v_uv;\n vec2 bMin = u_bbox.xy;\n vec2 bSize = u_bbox.zw;\n vec2 bMax = bMin + bSize;\n\n // Outside bounding box -> pass-through video\n if (uv.x < bMin.x || uv.x > bMax.x || uv.y < bMin.y || uv.y > bMax.y) {\n gl_FragColor = texture2D(u_video, uv);\n return;\n }\n\n // Map fragment UV -> displacement texture UV\n vec2 mUV = (uv - bMin) / bSize;\n\n // Analytical SDF mask (pixel-perfect at any resolution). Tracks the\n // dominant region so per-region scale/ratio can be picked up via the\n // loop-constant index (safe in WebGL 1). After the circle loop we test the\n // rounded-rect \"bar\" region (when active) and take whichever has the\n // larger mask value. domCenter/domScale track the dominant lens so the\n // displacement map is sampled in its (press-)scaled space below.\n vec2 pxPos = mUV * u_bboxSize;\n float mask = 0.0;\n float baseScale = 0.0;\n float ratioX = 0.0;\n float ratioY = 0.0;\n\n // Map-sample scale assignment, tracked SEPARATELY from the tight SDF mask: a\n // pixel adopts a circle's press scale across the union of its original and\n // scaled radius (R * max(1, s)). That way the annulus a shrunk lens vacates\n // samples empty space instead of leaving the original rim stuck behind.\n // domScale = 1 (bar + background) -> identity sample.\n vec2 domCenter = pxPos;\n float domScale = 1.0;\n float domDist = 1.0; // nearest normalised assignment distance (<1 = inside)\n\n for (int i = 0; i < 3; i++) {\n if (u_circles[i].z < 0.1) continue; // skip inactive circles\n float s = max(u_scale[i], 0.001);\n float R = u_circles[i].z;\n float rho = length(pxPos - u_circles[i].xy);\n\n // Visible lens SDF at the scaled radius -> blur / specular gating.\n float dist = rho - R * s;\n float aa = fwidth(dist);\n float m = 1.0 - smoothstep(-aa, aa, dist);\n if (m > mask) {\n mask = m;\n baseScale = u_baseScale[i];\n ratioX = u_ratioX[i];\n ratioY = u_ratioY[i];\n }\n\n // Scale assignment over max(original, scaled) radius, PLUS a small FIXED\n // pixel margin so the region boundary clears the map's hard content edge at\n // r = R. A *fractional* margin scales with R, so a big lens (play) would\n // swallow its neighbours' regions and drag their rings in. 4px clears the\n // edge's AA band without ever reaching the gap to the next control.\n float nd = rho / (R * max(1.0, s) + 4.0);\n if (nd < domDist) {\n domDist = nd;\n domCenter = u_circles[i].xy;\n domScale = s;\n }\n }\n\n // Bar (rounded-rect) SDF - only when u_bar.zw > 0. The bar never scales, so\n // it keeps the identity map sample (domScale stays 1 for bar + background).\n if (u_bar.z > 0.1 && u_bar.w > 0.1) {\n vec2 q = abs(pxPos - u_bar.xy) - u_bar.zw * 0.5 + vec2(u_barRadius);\n float barDist =\n length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - u_barRadius;\n float aaBar = fwidth(barDist);\n float mBar = 1.0 - smoothstep(-aaBar, aaBar, barDist);\n if (mBar > mask) {\n mask = mBar;\n baseScale = u_barBaseScale;\n ratioX = u_barRatioX;\n ratioY = u_barRatioY;\n }\n }\n\n // Sample the displacement map at the dominant lens's scaled coordinate, so a\n // per-lens press scale shrinks the baked pattern around its center in\n // lockstep with the SDF radius above. domScale = 1 -> identity (unscaled).\n vec2 scaledPx = domCenter + (pxPos - domCenter) / domScale;\n vec4 d = texture2D(u_map, scaledPx / u_bboxSize);\n\n // mapMatrix (feColorMatrix equivalent)\n float sr = d.r * ratioX + 0.5 * (1.0 - ratioX);\n float sg = d.g * ratioY + 0.5 * (1.0 - ratioY);\n\n // Displacement offset in full-video UV space\n vec2 off = vec2(sr - 0.5, sg - 0.5) * baseScale;\n\n // Choose source: blurred or raw video\n // Inside lens -> blurred (if blur active), outside lens -> raw\n // Blend by mask so the transition at edges is clean\n vec4 color;\n if (u_chromaAmount > 0.001) {\n float sR = 1.0 + u_chromaAmount * 0.2;\n float sG = 1.0 + u_chromaAmount * 0.1;\n if (u_hasBlur > 0.5) {\n color.r = mix(\n texture2D(u_video, clamp(uv + off * sR, 0.0, 1.0)).r,\n texture2D(u_blurred, clamp(uv + off * sR, 0.0, 1.0)).r,\n mask\n );\n color.g = mix(\n texture2D(u_video, clamp(uv + off * sG, 0.0, 1.0)).g,\n texture2D(u_blurred, clamp(uv + off * sG, 0.0, 1.0)).g,\n mask\n );\n color.b = mix(\n texture2D(u_video, clamp(uv + off, 0.0, 1.0)).b,\n texture2D(u_blurred, clamp(uv + off, 0.0, 1.0)).b,\n mask\n );\n } else {\n color.r = texture2D(u_video, clamp(uv + off * sR, 0.0, 1.0)).r;\n color.g = texture2D(u_video, clamp(uv + off * sG, 0.0, 1.0)).g;\n color.b = texture2D(u_video, clamp(uv + off, 0.0, 1.0)).b;\n }\n color.a = 1.0;\n } else {\n vec2 duv = clamp(uv + off, 0.0, 1.0);\n if (u_hasBlur > 0.5) {\n color = mix(texture2D(u_video, duv), texture2D(u_blurred, duv), mask);\n } else {\n color = texture2D(u_video, duv);\n }\n }\n\n // Adaptive specular + brightness (Apple-style vibrancy)\n // Compute perceptual luminance of the displaced video sample\n float luma = dot(color.rgb, vec3(0.299, 0.587, 0.114));\n\n // Specular: blend between additive (dark bg) and multiplicative (bright bg)\n float spec = d.b - 0.502;\n float lumaLo = min(u_specLumaLow, u_specLumaHigh);\n float lumaHi = max(u_specLumaLow, u_specLumaHigh);\n float darkBlend = smoothstep(lumaLo, lumaHi, luma);\n vec3 specAdd = color.rgb + spec * u_specStrength;\n vec3 specMul = color.rgb * (1.0 - spec * u_specStrength);\n color.rgb = mix(specAdd, specMul, darkBlend);\n color.rgb = max(color.rgb, vec3(0.0));\n\n // Brightness: pull toward mid-gray - darkens bright areas, brightens dark\n float correction = (0.5 - luma) * u_adaptStrength * mask;\n color.rgb += correction;\n\n gl_FragColor = color;\n}\n";
353
- interface VideoLensConfig {
354
- circles: Array<{
355
- cx: number;
356
- cy: number;
357
- radius: number;
358
- scale: number;
359
- baseScale: number;
360
- ratioX: number;
361
- ratioY: number;
362
- }>;
363
- bar?: {
364
- cx: number;
365
- cy: number;
366
- w: number;
367
- h: number;
368
- radius: number;
369
- baseScale: number;
370
- ratioX: number;
371
- ratioY: number;
372
- };
373
- bbox: {
374
- x: number;
375
- y: number;
376
- w: number;
377
- h: number;
378
- };
379
- bboxSize: {
380
- w: number;
381
- h: number;
382
- };
383
- chromaAmount: number;
384
- specStrength: number;
385
- adaptStrength: number;
386
- specLumaLow: number;
387
- specLumaHigh: number;
388
- hasBlur: boolean;
389
- }
390
- declare class VideoRefractionRenderer {
391
- private gl;
392
- private quadBuffer;
393
- private blurProgram;
394
- private refractionProgram;
395
- private videoTexture;
396
- private mapTexture;
397
- private blurFboA;
398
- private blurTexA;
399
- private blurFboB;
400
- private blurTexB;
401
- private fboWidth;
402
- private fboHeight;
403
- constructor(canvas: HTMLCanvasElement);
404
- setDisplacementMap(image: HTMLImageElement | HTMLCanvasElement): void;
405
- private initBlurFBO;
406
- render(video: HTMLVideoElement, config: VideoLensConfig): void;
407
- dispose(): void;
408
- }
409
-
410
- /**
411
- * Aave QR Code WebGL 2 Interactive Refraction Engine
412
- * Reverse-engineered directly from aave.com/design/building-glass-for-the-web
413
- *
414
- * Implements GPU canvas-drawn QR code refraction on tap.
415
- * Features:
416
- * - WebGL 2 (#version 300 es)
417
- * - O(1) dot occupancy texture lookup
418
- * - Analytical eye rounded-rect SDFs with hover scaling
419
- * - Attenuated displacement for eye corner anchors
420
- * - Chromatic aberration (scaleR = 1 + 2*chroma, scaleG = 1 + 1*chroma, scaleB = 1)
421
- */
422
- declare const QR_VERTEX_SHADER = "#version 300 es\nin vec2 a_position;\nout vec2 v_uv;\n\nvoid main() {\n v_uv = (a_position * 0.5) + 0.5;\n gl_Position = vec4(a_position, 0.0, 1.0);\n}\n";
423
- declare const QR_FRAGMENT_SHADER = "#version 300 es\nprecision highp float;\nprecision highp sampler2D;\n\nin vec2 v_uv;\nout vec4 fragColor;\n\nuniform float u_dotRadius;\nuniform vec3 u_backgroundColor;\nuniform sampler2D u_PaintingTexture;\n\n// QR dot grid for the O(1) occupancy lookup (replaces a per-pixel scan over\n// every dot). Occupancy is matrixLength x matrixLength, R8, 0/1 per module.\nuniform sampler2D u_occupancyTexture;\nuniform float u_gridOriginUV; // padding / size, in UV\nuniform float u_cellUV; // cellSize / size, in UV\nuniform float u_invCellUV; // 1 / cellUV - avoids a per-pixel divide\nuniform int u_matrixLength;\n\nuniform sampler2D u_displacementMap;\nuniform sampler2D u_paintingColorTexture;\nuniform int u_displacementActive;\nuniform vec2 u_lensOrigin;\nuniform vec2 u_lensSize;\nuniform vec2 u_displacementScale;\nuniform float u_chromaAmount;\n\n// Eye uniforms (3 corner finder groups)\nuniform vec2 u_eyeCenter[3];\nuniform vec3 u_eyeHalf[3]; // x=outer, y=mid, z=inner half-size\nuniform vec3 u_eyeRadius[3]; // x=outer, y=mid, z=inner corner radius\nuniform vec3 u_eyeColor[3]; // fill color per group (animated)\nuniform float u_eyeScale[3]; // hover scale per group\nuniform float u_eyeRefractionScale; // 0-1 multiplier for eye displacement\n\n// QR dots sit on a uniform grid, and the radius (~0.35 of a cell) never\n// spills past a cell boundary, so the nearest dot is always the one in pos's\n// own cell. Map pos -> module (i,j), check occupancy, then test distance to\n// that module's center.\nfloat testDot(vec2 pos, float r2) {\n int i = int(floor((pos.x - u_gridOriginUV) * u_invCellUV));\n int j = int(floor((pos.y - u_gridOriginUV) * u_invCellUV));\n if (i < 0 || i >= u_matrixLength || j < 0 || j >= u_matrixLength)\n return 1.0;\n if (texelFetch(u_occupancyTexture, ivec2(i, j), 0).r < 0.5)\n return 1.0;\n vec2 c = vec2(u_gridOriginUV) + (vec2(float(i), float(j)) + 0.5) * u_cellUV;\n vec2 d = pos - c;\n return dot(d, d) < r2 ? 0.0 : 1.0;\n}\n\nfloat roundedRectSDF(vec2 p, float half_, float radius) {\n vec2 d = abs(p) - vec2(half_) + vec2(radius);\n return length(max(d, vec2(0.0))) + min(max(d.x, d.y), 0.0) - radius;\n}\n\n// Test all eye groups. Returns vec4(color, 1.0) if inside an eye, vec4(0) if not.\nvec4 testEyes(vec2 pos) {\n for (int g = 0; g < 3; g++) {\n vec2 local = (pos - u_eyeCenter[g]) / u_eyeScale[g];\n if (roundedRectSDF(local, u_eyeHalf[g].z, u_eyeRadius[g].z) < 0.0)\n return vec4(u_eyeColor[g], 1.0);\n if (roundedRectSDF(local, u_eyeHalf[g].y, u_eyeRadius[g].y) < 0.0)\n return vec4(u_backgroundColor, 1.0);\n if (roundedRectSDF(local, u_eyeHalf[g].x, u_eyeRadius[g].x) < 0.0)\n return vec4(u_eyeColor[g], 1.0);\n }\n return vec4(0.0);\n}\n\n// Test scene (eyes + dots) at a position, returning color for a non-displaced pixel.\n// Returns vec4(color, 1.0) for solid, vec4(0, 0, 0, -1.0) for dot hole (discard).\nvec4 sampleStatic(vec2 pos, float r2) {\n vec4 eye = testEyes(pos);\n if (eye.a > 0.5) return eye;\n float hole = testDot(pos, r2);\n if (hole < 0.5) return vec4(0.0, 0.0, 0.0, -1.0);\n return vec4(u_backgroundColor, 1.0);\n}\n\nvoid main() {\n vec2 uv = vec2(v_uv.x, 1.0 - v_uv.y);\n float paint = texture(u_PaintingTexture, uv).r;\n float r2 = pow(u_dotRadius * (1.0 - paint), 2.0);\n\n if (u_displacementActive == 0) {\n vec4 s = sampleStatic(uv, r2);\n if (s.a < 0.0) discard;\n fragColor = s;\n return;\n }\n\n vec2 lensUV = (uv - u_lensOrigin) / u_lensSize;\n bool insideLens = lensUV.x >= 0.0 && lensUV.x <= 1.0 &&\n lensUV.y >= 0.0 && lensUV.y <= 1.0;\n\n if (!insideLens) {\n vec4 s = sampleStatic(uv, r2);\n if (s.a < 0.0) discard;\n fragColor = s;\n return;\n }\n\n vec4 dispSample = texture(u_displacementMap, lensUV);\n if (dispSample.a < 0.01) {\n vec4 s = sampleStatic(uv, r2);\n if (s.a < 0.0) discard;\n fragColor = s;\n return;\n }\n vec2 disp = (dispSample.rg - 0.5) * u_displacementScale;\n\n float scaleR = 1.0 + u_chromaAmount * 2.0;\n float scaleG = 1.0 + u_chromaAmount * 1.0;\n\n vec2 uvR = uv + disp * scaleR;\n vec2 uvG = uv + disp * scaleG;\n vec2 uvB = uv + disp;\n\n // Attenuated displacement for eyes\n vec2 eyeDisp = disp * u_eyeRefractionScale;\n vec2 eyeUvR = uv + eyeDisp * scaleR;\n vec2 eyeUvG = uv + eyeDisp * scaleG;\n vec2 eyeUvB = uv + eyeDisp;\n\n // Per-channel: test eyes at attenuated displacement, dots at full displacement\n vec4 eyeR = testEyes(eyeUvR);\n vec4 eyeG = testEyes(eyeUvG);\n vec4 eyeB = testEyes(eyeUvB);\n\n float r, g, b;\n\n if (eyeR.a > 0.5) {\n r = eyeR.r;\n } else {\n float holeR = testDot(uvR, r2);\n float paintR = texture(u_paintingColorTexture, uvR).r;\n r = mix(paintR, u_backgroundColor.r, holeR);\n }\n\n if (eyeG.a > 0.5) {\n g = eyeG.g;\n } else {\n float holeG = testDot(uvG, r2);\n float paintG = texture(u_paintingColorTexture, uvG).g;\n g = mix(paintG, u_backgroundColor.g, holeG);\n }\n\n if (eyeB.a > 0.5) {\n b = eyeB.b;\n } else {\n float holeB = testDot(uvB, r2);\n float paintB = texture(u_paintingColorTexture, uvB).b;\n b = mix(paintB, u_backgroundColor.b, holeB);\n }\n\n fragColor = vec4(r, g, b, 1.0);\n}\n";
424
- interface QREyeConfig {
425
- center: [number, number];
426
- half: [number, number, number];
427
- radius: [number, number, number];
428
- color: [number, number, number];
429
- scale: number;
430
- }
431
- interface QRRendererInit {
432
- canvas: HTMLCanvasElement;
433
- size: number;
434
- matrixLength: number;
435
- occupancyMatrix: Uint8Array;
436
- gridOriginUV: number;
437
- cellUV: number;
438
- dotRadius: number;
439
- backgroundColor?: [number, number, number];
440
- }
441
- declare class QRRefractionRenderer {
442
- private gl;
443
- private program;
444
- private quadBuffer;
445
- private occupancyTexture;
446
- private displacementTexture;
447
- private paintingTexture;
448
- private paintingColorTexture;
449
- private matrixLength;
450
- private gridOriginUV;
451
- private cellUV;
452
- private dotRadius;
453
- private backgroundColor;
454
- constructor(init: QRRendererInit);
455
- setDisplacementMap(image: HTMLImageElement | HTMLCanvasElement): void;
456
- render(params: {
457
- displacementActive: boolean;
458
- lensOrigin: [number, number];
459
- lensSize: [number, number];
460
- displacementScale: [number, number];
461
- chromaAmount: number;
462
- eyes: QREyeConfig[];
463
- eyeRefractionScale?: number;
464
- }): void;
465
- dispose(): void;
466
- }
467
-
468
- export { AAVE_GLASS_SELECTOR, AaveGlass, type AaveGlassProps, BLUR_FRAGMENT_SHADER, DEFAULT_LENS_PARAMS, type DomeConstants, GlassPlayground, type GlassPlaygroundProps, GlassSlider, type GlassSliderProps, GlassSwitch, type GlassSwitchProps, GlassToggleGroup, type GlassToggleGroupProps, type LensParams, type MapGeneratorInput, type MapGeneratorOutput, type MultiLensSlot, type QREyeConfig, QRRefractionRenderer, type QRRendererInit, QR_FRAGMENT_SHADER, QR_VERTEX_SHADER, REFRACTION_FRAGMENT_SHADER, SLIDER_DARK_OVERRIDES, SLIDER_GLASS_DEFAULTS, SLIDER_LIGHT_OVERRIDES, SLIDER_SAFARI_OVERRIDES, SWITCH_DARK_OVERRIDES, SWITCH_GLASS_DEFAULTS, SWITCH_LIGHT_OVERRIDES, type SingleLensSlot, type SubSlot, type ToggleOption, VERTEX_SHADER, type VideoLensConfig, VideoRefractionRenderer, colorMatrixValues, computeDomeConstants, createDisplacementMapGeneratorSync, domeGradient, erf, generateDisplacementMapAsync, getSvgMaskDataUri };
526
+ export { DEFAULT_LENS_PARAMS, type DomeConstants, AaveGlass as Lens, type LensParams, type AaveGlassProps as LensProps, GlassSlider as LensSlider, type GlassSliderProps as LensSliderProps, GlassSwitch as LensSwitch, type GlassSwitchProps as LensSwitchProps, GlassToggleGroup as LensToggleGroup, type GlassToggleGroupProps as LensToggleGroupProps, type MapGeneratorInput, type MapGeneratorOutput, QRRefractionRenderer, type QRRendererOptions, SLIDER_DARK_OVERRIDES, SLIDER_GLASS_DEFAULTS, SLIDER_LIGHT_OVERRIDES, SLIDER_SAFARI_OVERRIDES, SWITCH_DARK_OVERRIDES, SWITCH_GLASS_DEFAULTS, SWITCH_LIGHT_OVERRIDES, type ToggleOption, VideoRefractionRenderer, type VideoRendererOptions, colorMatrixValues, computeDomeConstants, createDisplacementMapGeneratorSync, domeGradient, erf, generateDisplacementMapAsync, getSvgMaskDataUri };