liquid-gl 1.0.6 → 2.0.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.
Files changed (3) hide show
  1. package/README.md +117 -40
  2. package/liquidGL.js +519 -136
  3. package/package.json +3 -10
package/liquidGL.js CHANGED
@@ -4,6 +4,7 @@
4
4
  *
5
5
  * Author: NaughtyDuk© – https://liquidgl.naughtyduk.com
6
6
  * Licence: MIT
7
+ * Version: v2.0.0
7
8
  */
8
9
 
9
10
  import html2canvas from "html2canvas";
@@ -11,6 +12,8 @@ import html2canvas from "html2canvas";
11
12
  const liquidGL = (() => {
12
13
  "use strict";
13
14
 
15
+ const RECAPTURE_INTERVAL_MS = 250;
16
+
14
17
  /* --------------------------------------------------
15
18
  * Utilities
16
19
  * ------------------------------------------------*/
@@ -138,7 +141,7 @@ const liquidGL = (() => {
138
141
  this._resizeCanvas();
139
142
  this.lenses.forEach((l) => l.updateMetrics());
140
143
  this.captureSnapshot();
141
- }, 250);
144
+ }, RECAPTURE_INTERVAL_MS);
142
145
  window.addEventListener("resize", onResize, { passive: true });
143
146
 
144
147
  if ("ResizeObserver" in window) {
@@ -157,27 +160,30 @@ const liquidGL = (() => {
157
160
  document.head.appendChild(styleEl);
158
161
  this._dynamicStyleSheet = styleEl.sheet;
159
162
 
163
+ this._snapshotResolution = Math.max(
164
+ 0.1,
165
+ Math.min(3.0, snapshotResolution),
166
+ );
167
+ this._pendingReveal = [];
168
+
160
169
  this._resizeCanvas();
161
170
  this.captureSnapshot();
162
171
 
163
- this._pendingReveal = [];
164
-
165
172
  /* --------------------------------------------------
166
173
  * Dynamic media (video) support
167
174
  * ------------------------------------------------*/
168
175
  this._videoNodes = Array.from(
169
- this.snapshotTarget.querySelectorAll("video")
176
+ this.snapshotTarget.querySelectorAll("video"),
170
177
  );
171
178
  this._videoNodes = this._videoNodes.filter((v) => !this._isIgnored(v));
172
179
  this._tmpCanvas = document.createElement("canvas");
173
180
  this._tmpCtx = this._tmpCanvas.getContext("2d");
174
181
 
175
- this.canvas.style.opacity = "0";
182
+ this._videoFrameState = new WeakMap();
176
183
 
177
- this._snapshotResolution = Math.max(
178
- 0.1,
179
- Math.min(3.0, snapshotResolution)
180
- );
184
+ this._videoAlphaState = new WeakMap();
185
+
186
+ this.canvas.style.opacity = "0";
181
187
 
182
188
  this.useExternalTicker = false;
183
189
 
@@ -227,7 +233,7 @@ const liquidGL = (() => {
227
233
  y,
228
234
  gl.RGBA,
229
235
  gl.UNSIGNED_BYTE,
230
- bmp
236
+ bmp,
231
237
  );
232
238
  };
233
239
  }
@@ -244,13 +250,18 @@ const liquidGL = (() => {
244
250
  }`;
245
251
 
246
252
  const fsSource = `
253
+ #ifdef GL_FRAGMENT_PRECISION_HIGH
254
+ precision highp float;
255
+ #else
247
256
  precision mediump float;
257
+ #endif
248
258
  varying vec2 v_uv;
249
259
  uniform sampler2D u_tex;
250
260
  uniform vec2 u_resolution;
251
261
  uniform vec2 u_textureResolution;
252
262
  uniform vec4 u_bounds;
253
263
  uniform float u_refraction;
264
+ uniform float u_aberration;
254
265
  uniform float u_bevelDepth;
255
266
  uniform float u_bevelWidth;
256
267
  uniform float u_frost;
@@ -267,6 +278,17 @@ const liquidGL = (() => {
267
278
  return length(max(abs(p)-b+r,0.0))-r;
268
279
  }
269
280
 
281
+ vec2 cornerNormal( vec2 p, vec2 b, float r, vec2 fallback ) {
282
+ vec2 q = abs(p) - b + r;
283
+ vec2 m = max(q, 0.0);
284
+ float l = length(m);
285
+ if (l <= 0.0) return fallback;
286
+ float w = smoothstep(0.0, max(r * 0.5, 1.0), min(m.x, m.y));
287
+ if (w <= 0.0) return fallback;
288
+ vec2 s = vec2(p.x < 0.0 ? -1.0 : 1.0, p.y < 0.0 ? -1.0 : 1.0);
289
+ return normalize(mix(fallback, s * (m / l), w));
290
+ }
291
+
270
292
  float random(vec2 st) {
271
293
  return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123);
272
294
  }
@@ -286,7 +308,13 @@ const liquidGL = (() => {
286
308
  float min_dimension = min(u_resolution.x, u_resolution.y);
287
309
  float offsetAmt = (edge * u_refraction + pow(edge, 10.0) * u_bevelDepth);
288
310
  float centreBlend = smoothstep(0.15, 0.45, length(p));
289
- vec2 offset = normalize(p) * offsetAmt * centreBlend;
311
+ vec2 refractDir = cornerNormal(
312
+ (v_uv - 0.5) * u_resolution,
313
+ 0.5 * u_resolution,
314
+ u_radius,
315
+ normalize(p)
316
+ );
317
+ vec2 offset = refractDir * offsetAmt * centreBlend;
290
318
 
291
319
  float tiltRefractionScale = 0.05;
292
320
  vec2 tiltOffset = vec2(tan(radians(u_tiltY)), -tan(radians(u_tiltX))) * tiltRefractionScale;
@@ -326,6 +354,12 @@ const liquidGL = (() => {
326
354
  refrCol /= 5.0;
327
355
  }
328
356
 
357
+ if (u_aberration > 0.0) {
358
+ vec2 chroma = offset * u_aberration;
359
+ refrCol.r = texture2D(u_tex, sampleUV - chroma).r;
360
+ refrCol.b = texture2D(u_tex, sampleUV + chroma).b;
361
+ }
362
+
329
363
  if (refrCol.a < 0.1) {
330
364
  refrCol = baseCol;
331
365
  }
@@ -339,7 +373,7 @@ const liquidGL = (() => {
339
373
  vec2 p_px = (v_uv - 0.5) * u_resolution;
340
374
  vec2 b_px = 0.5 * u_resolution;
341
375
  float dmask = udRoundBox(p_px, b_px, u_radius);
342
- float inShape = 1.0 - step(0.0, dmask);
376
+ float inShape = clamp(0.5 - dmask, 0.0, 1.0);
343
377
 
344
378
  if (u_specular) {
345
379
  vec2 lp1 = vec2(sin(u_time*0.2), cos(u_time*0.3))*0.6 + 0.5;
@@ -370,22 +404,26 @@ const liquidGL = (() => {
370
404
  gl.bufferData(
371
405
  gl.ARRAY_BUFFER,
372
406
  new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]),
373
- gl.STATIC_DRAW
407
+ gl.STATIC_DRAW,
374
408
  );
375
409
 
376
410
  const posLoc = gl.getAttribLocation(this.program, "a_position");
377
411
  gl.enableVertexAttribArray(posLoc);
378
412
  gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);
379
413
 
414
+ this._posBuf = posBuf;
415
+ this._posLoc = posLoc;
416
+
380
417
  this.u = {
381
418
  tex: gl.getUniformLocation(this.program, "u_tex"),
382
419
  res: gl.getUniformLocation(this.program, "u_resolution"),
383
420
  textureResolution: gl.getUniformLocation(
384
421
  this.program,
385
- "u_textureResolution"
422
+ "u_textureResolution",
386
423
  ),
387
424
  bounds: gl.getUniformLocation(this.program, "u_bounds"),
388
425
  refraction: gl.getUniformLocation(this.program, "u_refraction"),
426
+ aberration: gl.getUniformLocation(this.program, "u_aberration"),
389
427
  bevelDepth: gl.getUniformLocation(this.program, "u_bevelDepth"),
390
428
  bevelWidth: gl.getUniformLocation(this.program, "u_bevelWidth"),
391
429
  frost: gl.getUniformLocation(this.program, "u_frost"),
@@ -420,7 +458,7 @@ const liquidGL = (() => {
420
458
  const attemptCapture = async (
421
459
  attempt = 1,
422
460
  maxAttempts = 3,
423
- delayMs = 500
461
+ delayMs = 500,
424
462
  ) => {
425
463
  try {
426
464
  const fullW = this.snapshotTarget.scrollWidth;
@@ -432,13 +470,24 @@ const liquidGL = (() => {
432
470
  let scale = Math.min(
433
471
  this._snapshotResolution,
434
472
  maxTex / fullW,
435
- maxTex / fullH
473
+ maxTex / fullH,
436
474
  );
437
475
 
438
476
  if (isMobileSafari) {
439
477
  const over = (Math.max(fullW, fullH) * scale) / MAX_MOBILE_DIM;
440
478
  if (over > 1) scale = scale / over;
441
479
  }
480
+
481
+ const maxArea = isMobileSafari ? 4096 * 4096 : 16384 * 16384;
482
+ if (fullW * fullH * scale * scale > maxArea) {
483
+ scale = Math.sqrt(maxArea / (fullW * fullH));
484
+ console.warn(
485
+ `liquidGL: snapshot area capped, resolution reduced to ${scale.toFixed(
486
+ 3,
487
+ )} for a ${fullW}x${fullH} document.`,
488
+ );
489
+ }
490
+
442
491
  this.scaleFactor = Math.max(0.1, scale);
443
492
 
444
493
  this.canvas.style.visibility = "hidden";
@@ -448,9 +497,16 @@ const liquidGL = (() => {
448
497
  .flatMap((lens) => [lens.el, lens._shadowEl])
449
498
  .filter(Boolean);
450
499
 
500
+ lensElements.forEach((el) => {
501
+ el.setAttribute("data-liquidgl-hide", "");
502
+ undos.push(() => {
503
+ el.removeAttribute("data-liquidgl-hide");
504
+ });
505
+ });
506
+
451
507
  const ignoreElementsFunc = (element) => {
452
508
  if (!element || !element.hasAttribute) return false;
453
- if (element === this.canvas || lensElements.includes(element)) {
509
+ if (element === this.canvas) {
454
510
  return true;
455
511
  }
456
512
  const style = window.getComputedStyle(element);
@@ -474,15 +530,24 @@ const liquidGL = (() => {
474
530
  scrollY: 0,
475
531
  scale: scale,
476
532
  ignoreElements: ignoreElementsFunc,
533
+ onclone: (clonedDoc) => {
534
+ clonedDoc
535
+ .querySelectorAll("[data-liquidgl-hide]")
536
+ .forEach((el) => {
537
+ el.style.visibility = "hidden";
538
+ });
539
+ },
477
540
  });
478
541
 
479
- this._uploadTexture(snapCanvas);
542
+ if (!this._uploadTexture(snapCanvas)) {
543
+ throw new Error("liquidGL: snapshot could not be uploaded.");
544
+ }
480
545
  return true;
481
546
  } catch (e) {
482
547
  console.error("liquidGL snapshot failed on attempt " + attempt, e);
483
548
  if (attempt < maxAttempts) {
484
549
  console.log(
485
- `Retrying snapshot capture (${attempt + 1}/${maxAttempts})...`
550
+ `Retrying snapshot capture (${attempt + 1}/${maxAttempts})...`,
486
551
  );
487
552
  await new Promise((resolve) => setTimeout(resolve, delayMs));
488
553
  return await attemptCapture(attempt + 1, maxAttempts, delayMs);
@@ -503,27 +568,39 @@ const liquidGL = (() => {
503
568
 
504
569
  /* ----------------------------- */
505
570
  _uploadTexture(srcCanvas) {
506
- if (!srcCanvas) return;
571
+ if (!srcCanvas) {
572
+ console.error("liquidGL: snapshot produced no canvas.");
573
+ return false;
574
+ }
507
575
 
508
576
  if (!(srcCanvas instanceof HTMLCanvasElement)) {
509
577
  const tmp = document.createElement("canvas");
510
578
  tmp.width = srcCanvas.width || 0;
511
579
  tmp.height = srcCanvas.height || 0;
512
- if (tmp.width === 0 || tmp.height === 0) return;
580
+ if (tmp.width === 0 || tmp.height === 0) {
581
+ console.error("liquidGL: snapshot canvas has zero dimensions.");
582
+ return false;
583
+ }
513
584
  try {
514
585
  const ctx = tmp.getContext("2d");
515
586
  ctx.drawImage(srcCanvas, 0, 0);
516
587
  srcCanvas = tmp;
517
588
  } catch (e) {
518
- console.warn(
589
+ console.error(
519
590
  "liquidGL: Unable to convert OffscreenCanvas for upload",
520
- e
591
+ e,
521
592
  );
522
- return;
593
+ return false;
523
594
  }
524
595
  }
525
596
 
526
- if (srcCanvas.width === 0 || srcCanvas.height === 0) return;
597
+ if (srcCanvas.width === 0 || srcCanvas.height === 0) {
598
+ console.error(
599
+ "liquidGL: snapshot canvas has zero dimensions, requested area " +
600
+ `${srcCanvas.width}x${srcCanvas.height} exceeds a browser limit.`,
601
+ );
602
+ return false;
603
+ }
527
604
  this.staticSnapshotCanvas = srcCanvas;
528
605
  const gl = this.gl;
529
606
  if (!this.texture) this.texture = gl.createTexture();
@@ -535,7 +612,7 @@ const liquidGL = (() => {
535
612
  gl.RGBA,
536
613
  gl.RGBA,
537
614
  gl.UNSIGNED_BYTE,
538
- srcCanvas
615
+ srcCanvas,
539
616
  );
540
617
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
541
618
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
@@ -545,12 +622,18 @@ const liquidGL = (() => {
545
622
  this.textureWidth = srcCanvas.width;
546
623
  this.textureHeight = srcCanvas.height;
547
624
 
625
+ if (this._videoFrameState) this._videoFrameState = new WeakMap();
626
+
627
+ this._vFboTexture = null;
628
+
548
629
  this.render();
549
630
 
550
631
  if (this._pendingReveal.length) {
551
632
  this._pendingReveal.forEach((ln) => ln._reveal());
552
633
  this._pendingReveal.length = 0;
553
634
  }
635
+
636
+ return true;
554
637
  }
555
638
 
556
639
  /* ----------------------------- */
@@ -594,6 +677,8 @@ const liquidGL = (() => {
594
677
 
595
678
  this._updateDynamicNodes();
596
679
 
680
+ this._frameSnapRect = this.snapshotTarget.getBoundingClientRect();
681
+
597
682
  this.lenses.forEach((lens) => {
598
683
  lens.updateMetrics();
599
684
  if (lens._mirrorActive && lens._mirrorClipUpdater) {
@@ -624,15 +709,15 @@ const liquidGL = (() => {
624
709
  const x = Math.max(0, Math.round(left * dpr) - expand);
625
710
  const y = Math.max(
626
711
  0,
627
- Math.round(this.canvas.height - (top + height) * dpr) - expand
712
+ Math.round(this.canvas.height - (top + height) * dpr) - expand,
628
713
  );
629
714
  const w = Math.min(
630
715
  this.canvas.width - x,
631
- Math.round(width * dpr) + expand * 2
716
+ Math.round(width * dpr) + expand * 2,
632
717
  );
633
718
  const h = Math.min(
634
719
  this.canvas.height - y,
635
- Math.round(height * dpr) + expand * 2
720
+ Math.round(height * dpr) + expand * 2,
636
721
  );
637
722
  if (w > 0 && h > 0) {
638
723
  gl.enable(gl.SCISSOR_TEST);
@@ -656,22 +741,27 @@ const liquidGL = (() => {
656
741
  let overscrollY = 0;
657
742
  let overscrollX = 0;
658
743
 
659
- if (window.visualViewport) {
660
- overscrollX = window.visualViewport.offsetLeft;
661
- overscrollY = window.visualViewport.offsetTop;
744
+ const vv = window.visualViewport;
745
+ if (vv && Math.abs(vv.scale - 1) < 0.01) {
746
+ overscrollX = vv.offsetLeft;
747
+ overscrollY = vv.offsetTop;
662
748
  }
663
749
 
664
- const x = (rect.left + overscrollX) * dpr;
665
- const y =
666
- this.canvas.height - (rect.top + overscrollY + rect.height) * dpr;
667
- const w = rect.width * dpr;
668
- const h = rect.height * dpr;
750
+ const leftPx = (rect.left + overscrollX) * dpr;
751
+ const topPx = (rect.top + overscrollY) * dpr;
752
+ const x = Math.round(leftPx);
753
+ const yTop = Math.round(topPx);
754
+ const w = Math.round(leftPx + rect.width * dpr) - x;
755
+ const h = Math.round(topPx + rect.height * dpr) - yTop;
756
+ const y = this.canvas.height - (yTop + h);
669
757
 
670
758
  gl.viewport(x, y, w, h);
671
759
  gl.uniform2f(this.u.res, w, h);
672
760
 
673
- const docX = rect.left - this.snapshotTarget.getBoundingClientRect().left;
674
- const docY = rect.top - this.snapshotTarget.getBoundingClientRect().top;
761
+ const snapRect =
762
+ this._frameSnapRect || this.snapshotTarget.getBoundingClientRect();
763
+ const docX = rect.left - snapRect.left;
764
+ const docY = rect.top - snapRect.top;
675
765
  const leftUV = (docX * this.scaleFactor) / this.textureWidth;
676
766
  const topUV = (docY * this.scaleFactor) / this.textureHeight;
677
767
  const wUV = (rect.width * this.scaleFactor) / this.textureWidth;
@@ -681,9 +771,10 @@ const liquidGL = (() => {
681
771
  gl.uniform2f(
682
772
  this.u.textureResolution,
683
773
  this.textureWidth,
684
- this.textureHeight
774
+ this.textureHeight,
685
775
  );
686
776
  gl.uniform1f(this.u.refraction, lens.options.refraction);
777
+ gl.uniform1f(this.u.aberration, lens.options.aberration || 0);
687
778
  gl.uniform1f(this.u.bevelDepth, lens.options.bevelDepth);
688
779
  gl.uniform1f(this.u.bevelWidth, lens.options.bevelWidth);
689
780
  gl.uniform1f(this.u.frost, lens.options.frost);
@@ -696,8 +787,8 @@ const liquidGL = (() => {
696
787
  0.001,
697
788
  Math.min(
698
789
  3.0,
699
- lens.options.magnify !== undefined ? lens.options.magnify : 1.0
700
- )
790
+ lens.options.magnify !== undefined ? lens.options.magnify : 1.0,
791
+ ),
701
792
  );
702
793
  gl.uniform1f(this.u.magnify, mag);
703
794
 
@@ -722,6 +813,166 @@ const liquidGL = (() => {
722
813
  ctx.closePath();
723
814
  }
724
815
 
816
+ _initVideoBlit() {
817
+ if (this._vBlitReady !== undefined) return this._vBlitReady;
818
+
819
+ const gl = this.gl;
820
+
821
+ const vs = `
822
+ attribute vec2 a_position;
823
+ varying vec2 v_uv;
824
+ void main(){
825
+ v_uv = (a_position + 1.0) * 0.5;
826
+ gl_Position = vec4(a_position, 0.0, 1.0);
827
+ }`;
828
+
829
+ const fs = `
830
+ precision mediump float;
831
+ varying vec2 v_uv;
832
+ uniform sampler2D u_src;
833
+ uniform vec4 u_srcRect;
834
+ void main(){
835
+ gl_FragColor = texture2D(u_src, u_srcRect.xy + v_uv * u_srcRect.zw);
836
+ }`;
837
+
838
+ const prog = createProgram(gl, vs, fs);
839
+ if (!prog) {
840
+ this._vBlitReady = false;
841
+ return false;
842
+ }
843
+
844
+ this._vProg = prog;
845
+ this._vPosLoc = gl.getAttribLocation(prog, "a_position");
846
+ this._vU = {
847
+ src: gl.getUniformLocation(prog, "u_src"),
848
+ srcRect: gl.getUniformLocation(prog, "u_srcRect"),
849
+ };
850
+
851
+ this._vTex = gl.createTexture();
852
+ gl.bindTexture(gl.TEXTURE_2D, this._vTex);
853
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
854
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
855
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
856
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
857
+ gl.bindTexture(gl.TEXTURE_2D, this.texture);
858
+
859
+ this._vFbo = gl.createFramebuffer();
860
+ this._vFboTexture = null;
861
+
862
+ this._vBlitReady = true;
863
+ return true;
864
+ }
865
+
866
+ _videoIsOpaque(vid) {
867
+ if (!vid.videoWidth || !vid.videoHeight) return false;
868
+
869
+ const key = vid.videoWidth + "x" + vid.videoHeight;
870
+ const cached = this._videoAlphaState.get(vid);
871
+ if (cached && cached.key === key) return cached.opaque;
872
+
873
+ let opaque = false;
874
+ try {
875
+ const probe =
876
+ this._alphaProbeCanvas ||
877
+ (this._alphaProbeCanvas = document.createElement("canvas"));
878
+ const size = 32;
879
+ probe.width = size;
880
+ probe.height = size;
881
+ const ctx = probe.getContext("2d", { willReadFrequently: true });
882
+ ctx.clearRect(0, 0, size, size);
883
+ ctx.drawImage(vid, 0, 0, size, size);
884
+ const data = ctx.getImageData(0, 0, size, size).data;
885
+ opaque = true;
886
+ for (let i = 3; i < data.length; i += 4) {
887
+ if (data[i] !== 255) {
888
+ opaque = false;
889
+ break;
890
+ }
891
+ }
892
+ } catch (e) {
893
+ opaque = false;
894
+ }
895
+
896
+ this._videoAlphaState.set(vid, { key, opaque });
897
+ return opaque;
898
+ }
899
+
900
+ _blitVideoToTexture(vid, dstX, dstY, dstW, dstH, srcRect) {
901
+ const gl = this.gl;
902
+
903
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this._vFbo);
904
+
905
+ if (this._vFboTexture !== this.texture) {
906
+ gl.framebufferTexture2D(
907
+ gl.FRAMEBUFFER,
908
+ gl.COLOR_ATTACHMENT0,
909
+ gl.TEXTURE_2D,
910
+ this.texture,
911
+ 0,
912
+ );
913
+ if (
914
+ gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE
915
+ ) {
916
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
917
+ this._vBlitReady = false;
918
+ return false;
919
+ }
920
+ this._vFboTexture = this.texture;
921
+ }
922
+
923
+ gl.activeTexture(gl.TEXTURE0);
924
+ gl.bindTexture(gl.TEXTURE_2D, this._vTex);
925
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
926
+
927
+ try {
928
+ gl.texImage2D(
929
+ gl.TEXTURE_2D,
930
+ 0,
931
+ gl.RGBA,
932
+ gl.RGBA,
933
+ gl.UNSIGNED_BYTE,
934
+ vid,
935
+ );
936
+ } catch (e) {
937
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
938
+ this._restoreLensProgramState();
939
+ return false;
940
+ }
941
+
942
+ gl.useProgram(this._vProg);
943
+ gl.bindBuffer(gl.ARRAY_BUFFER, this._posBuf);
944
+ gl.enableVertexAttribArray(this._vPosLoc);
945
+ gl.vertexAttribPointer(this._vPosLoc, 2, gl.FLOAT, false, 0, 0);
946
+
947
+ gl.uniform1i(this._vU.src, 0);
948
+ gl.uniform4f(
949
+ this._vU.srcRect,
950
+ srcRect.u,
951
+ srcRect.v,
952
+ srcRect.uw,
953
+ srcRect.vh,
954
+ );
955
+
956
+ gl.viewport(dstX, dstY, dstW, dstH);
957
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
958
+
959
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
960
+ this._restoreLensProgramState();
961
+
962
+ return true;
963
+ }
964
+
965
+ _restoreLensProgramState() {
966
+ const gl = this.gl;
967
+ gl.useProgram(this.program);
968
+ gl.bindBuffer(gl.ARRAY_BUFFER, this._posBuf);
969
+ gl.enableVertexAttribArray(this._posLoc);
970
+ gl.vertexAttribPointer(this._posLoc, 2, gl.FLOAT, false, 0, 0);
971
+ gl.activeTexture(gl.TEXTURE0);
972
+ gl.bindTexture(gl.TEXTURE_2D, this.texture);
973
+ gl.uniform1i(this.u.tex, 0);
974
+ }
975
+
725
976
  /* ----------------------------- */
726
977
  _updateDynamicVideos() {
727
978
  if (this._isScrolling && this._scrollUpdateCounter % 2 !== 0) return;
@@ -755,6 +1006,76 @@ const liquidGL = (() => {
755
1006
 
756
1007
  if (drawW <= 0 || drawH <= 0) return;
757
1008
 
1009
+ const geomKey =
1010
+ Math.round(texX) + "," + Math.round(texY) + "," + drawW + "," + drawH;
1011
+ const prevState = this._videoFrameState.get(vid);
1012
+ if (
1013
+ prevState &&
1014
+ prevState.time === vid.currentTime &&
1015
+ prevState.geom === geomKey
1016
+ ) {
1017
+ return;
1018
+ }
1019
+ this._videoFrameState.set(vid, {
1020
+ time: vid.currentTime,
1021
+ geom: geomKey,
1022
+ });
1023
+
1024
+ const drawX = Math.round(texX);
1025
+ const drawY = Math.round(texY);
1026
+
1027
+ const maxW = this.textureWidth;
1028
+ const maxH = this.textureHeight;
1029
+ let dstX = drawX;
1030
+ let dstY = drawY;
1031
+ let srcX = 0,
1032
+ srcY = 0,
1033
+ updW = drawW,
1034
+ updH = drawH;
1035
+
1036
+ if (dstX < 0) {
1037
+ srcX = -dstX;
1038
+ updW += dstX;
1039
+ dstX = 0;
1040
+ }
1041
+ if (dstY < 0) {
1042
+ srcY = -dstY;
1043
+ updH += dstY;
1044
+ dstY = 0;
1045
+ }
1046
+
1047
+ if (dstX + updW > maxW) {
1048
+ updW = maxW - dstX;
1049
+ }
1050
+ if (dstY + updH > maxH) {
1051
+ updH = maxH - dstY;
1052
+ }
1053
+
1054
+ if (updW <= 0 || updH <= 0) return;
1055
+
1056
+ const style = window.getComputedStyle(vid);
1057
+ const scaledRadii = {
1058
+ tl: parseFloat(style.borderTopLeftRadius) * this.scaleFactor,
1059
+ tr: parseFloat(style.borderTopRightRadius) * this.scaleFactor,
1060
+ br: parseFloat(style.borderBottomRightRadius) * this.scaleFactor,
1061
+ bl: parseFloat(style.borderBottomLeftRadius) * this.scaleFactor,
1062
+ };
1063
+ const isRounded = Object.values(scaledRadii).some((r) => r > 0);
1064
+
1065
+ if (
1066
+ !isRounded &&
1067
+ this._initVideoBlit() &&
1068
+ this._videoIsOpaque(vid) &&
1069
+ this._blitVideoToTexture(vid, dstX, dstY, updW, updH, {
1070
+ u: srcX / drawW,
1071
+ v: srcY / drawH,
1072
+ uw: updW / drawW,
1073
+ vh: updH / drawH,
1074
+ })
1075
+ ) {
1076
+ return;
1077
+ }
1078
+
758
1079
  if (
759
1080
  this._tmpCanvas.width !== drawW ||
760
1081
  this._tmpCanvas.height !== drawH
@@ -767,20 +1088,12 @@ const liquidGL = (() => {
767
1088
  this._tmpCtx.save();
768
1089
  this._tmpCtx.clearRect(0, 0, drawW, drawH);
769
1090
 
770
- const style = window.getComputedStyle(vid);
771
- const scaledRadii = {
772
- tl: parseFloat(style.borderTopLeftRadius) * this.scaleFactor,
773
- tr: parseFloat(style.borderTopRightRadius) * this.scaleFactor,
774
- br: parseFloat(style.borderBottomRightRadius) * this.scaleFactor,
775
- bl: parseFloat(style.borderBottomLeftRadius) * this.scaleFactor,
776
- };
777
-
778
- if (Object.values(scaledRadii).some((r) => r > 0)) {
1091
+ if (isRounded) {
779
1092
  this._createRoundedRectPath(
780
1093
  this._tmpCtx,
781
1094
  drawW,
782
1095
  drawH,
783
- scaledRadii
1096
+ scaledRadii,
784
1097
  );
785
1098
  this._tmpCtx.clip();
786
1099
  }
@@ -794,7 +1107,7 @@ const liquidGL = (() => {
794
1107
  0,
795
1108
  0,
796
1109
  drawW,
797
- drawH
1110
+ drawH,
798
1111
  );
799
1112
 
800
1113
  this._tmpCtx.drawImage(vid, 0, 0, drawW, drawH);
@@ -804,40 +1117,6 @@ const liquidGL = (() => {
804
1117
  return;
805
1118
  }
806
1119
 
807
- const drawX = Math.round(texX);
808
- const drawY = Math.round(texY);
809
-
810
- if (drawW <= 0 || drawH <= 0) return;
811
-
812
- const maxW = this.textureWidth;
813
- const maxH = this.textureHeight;
814
- let dstX = drawX;
815
- let dstY = drawY;
816
- let srcX = 0,
817
- srcY = 0,
818
- updW = drawW,
819
- updH = drawH;
820
-
821
- if (dstX < 0) {
822
- srcX = -dstX;
823
- updW += dstX;
824
- dstX = 0;
825
- }
826
- if (dstY < 0) {
827
- srcY = -dstY;
828
- updH += dstY;
829
- dstY = 0;
830
- }
831
-
832
- if (dstX + updW > maxW) {
833
- updW = maxW - dstX;
834
- }
835
- if (dstY + updH > maxH) {
836
- updH = maxH - dstY;
837
- }
838
-
839
- if (updW <= 0 || updH <= 0) return;
840
-
841
1120
  gl.bindTexture(gl.TEXTURE_2D, this.texture);
842
1121
  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
843
1122
  gl.texSubImage2D(
@@ -847,7 +1126,7 @@ const liquidGL = (() => {
847
1126
  dstY,
848
1127
  gl.RGBA,
849
1128
  gl.UNSIGNED_BYTE,
850
- this._tmpCanvas
1129
+ this._tmpCanvas,
851
1130
  );
852
1131
  });
853
1132
  }
@@ -894,7 +1173,7 @@ const liquidGL = (() => {
894
1173
  xInComposite,
895
1174
  yInComposite,
896
1175
  wInComposite,
897
- hInComposite
1176
+ hInComposite,
898
1177
  );
899
1178
  }
900
1179
  });
@@ -949,7 +1228,7 @@ const liquidGL = (() => {
949
1228
  0,
950
1229
  0,
951
1230
  w,
952
- h
1231
+ h,
953
1232
  );
954
1233
  gl.bindTexture(gl.TEXTURE_2D, this.texture);
955
1234
  gl.texSubImage2D(
@@ -959,7 +1238,7 @@ const liquidGL = (() => {
959
1238
  y,
960
1239
  gl.RGBA,
961
1240
  gl.UNSIGNED_BYTE,
962
- eraseCanvas
1241
+ eraseCanvas,
963
1242
  );
964
1243
  }
965
1244
  }
@@ -1037,7 +1316,7 @@ const liquidGL = (() => {
1037
1316
  0,
1038
1317
  0,
1039
1318
  drawW,
1040
- drawH
1319
+ drawH,
1041
1320
  );
1042
1321
  compositeVideos(this._compositeCtx, rect);
1043
1322
 
@@ -1046,7 +1325,7 @@ const liquidGL = (() => {
1046
1325
  this._compositeCtx.translate(drawW / 2, drawH / 2);
1047
1326
  if (style.transform !== "none") {
1048
1327
  this._compositeCtx.transform(
1049
- ...this._parseTransform(style.transform)
1328
+ ...this._parseTransform(style.transform),
1050
1329
  );
1051
1330
  }
1052
1331
  this._compositeCtx.translate(-drawW / 2, -drawH / 2);
@@ -1062,7 +1341,7 @@ const liquidGL = (() => {
1062
1341
  dstY,
1063
1342
  gl.RGBA,
1064
1343
  gl.UNSIGNED_BYTE,
1065
- compositeCanvas
1344
+ compositeCanvas,
1066
1345
  );
1067
1346
 
1068
1347
  if (this._workerEnabled && meta._heavyAnim) {
@@ -1080,7 +1359,7 @@ const liquidGL = (() => {
1080
1359
  dstX,
1081
1360
  dstY,
1082
1361
  updW,
1083
- updH
1362
+ updH,
1084
1363
  ),
1085
1364
  createImageBitmap(meta.lastCapture),
1086
1365
  ]).then(([snapBmp, dynBmp]) => {
@@ -1092,7 +1371,7 @@ const liquidGL = (() => {
1092
1371
  snap: snapBmp,
1093
1372
  dyn: dynBmp,
1094
1373
  },
1095
- [snapBmp, dynBmp]
1374
+ [snapBmp, dynBmp],
1096
1375
  );
1097
1376
  });
1098
1377
  meta.prevDrawRect = { x: dstX, y: dstY, w: updW, h: updH };
@@ -1217,7 +1496,7 @@ const liquidGL = (() => {
1217
1496
  try {
1218
1497
  this._dynamicStyleSheet.insertRule(
1219
1498
  rule,
1220
- this._dynamicStyleSheet.cssRules.length
1499
+ this._dynamicStyleSheet.cssRules.length,
1221
1500
  );
1222
1501
  m.hoverClassName = className;
1223
1502
  el.classList.add(className);
@@ -1227,7 +1506,7 @@ const liquidGL = (() => {
1227
1506
  }
1228
1507
  setDirty();
1229
1508
  },
1230
- { passive: true }
1509
+ { passive: true },
1231
1510
  );
1232
1511
 
1233
1512
  el.addEventListener("mouseleave", handleLeave, { passive: true });
@@ -1247,7 +1526,7 @@ const liquidGL = (() => {
1247
1526
  if (
1248
1527
  meta._heavyAnim &&
1249
1528
  !meta._capturing &&
1250
- ts - meta._lastCaptureTs > 33
1529
+ ts - meta._lastCaptureTs > RECAPTURE_INTERVAL_MS
1251
1530
  ) {
1252
1531
  meta._lastCaptureTs = ts;
1253
1532
  meta.needsRecapture = true;
@@ -1293,7 +1572,7 @@ const liquidGL = (() => {
1293
1572
  if (m) m._heavyAnim = true;
1294
1573
  startRealtime();
1295
1574
  },
1296
- { passive: true }
1575
+ { passive: true },
1297
1576
  );
1298
1577
 
1299
1578
  el.addEventListener(
@@ -1305,7 +1584,7 @@ const liquidGL = (() => {
1305
1584
  if (!m._animating) startRealtime();
1306
1585
  }
1307
1586
  },
1308
- { passive: true }
1587
+ { passive: true },
1309
1588
  );
1310
1589
 
1311
1590
  const stopRealtime = () => {
@@ -1454,9 +1733,12 @@ const liquidGL = (() => {
1454
1733
  let overscrollY = 0;
1455
1734
  let overscrollX = 0;
1456
1735
 
1457
- if (window.visualViewport) {
1458
- overscrollX = -window.visualViewport.offsetLeft;
1459
- overscrollY = -window.visualViewport.offsetTop;
1736
+ const vv = window.visualViewport;
1737
+ if (vv) {
1738
+ if (Math.abs(vv.scale - 1) < 0.01) {
1739
+ overscrollX = -vv.offsetLeft;
1740
+ overscrollY = -vv.offsetTop;
1741
+ }
1460
1742
  } else {
1461
1743
  const bodyStyle = window.getComputedStyle(document.body);
1462
1744
  const htmlStyle = window.getComputedStyle(document.documentElement);
@@ -1641,23 +1923,47 @@ const liquidGL = (() => {
1641
1923
  const getMaxTilt = () =>
1642
1924
  Number.isFinite(this.options.tiltFactor) ? this.options.tiltFactor : 5;
1643
1925
 
1926
+ const getTiltEase = () =>
1927
+ Number.isFinite(this.options.tiltEase)
1928
+ ? Math.max(0, this.options.tiltEase)
1929
+ : 400;
1930
+
1931
+ const TILT_TRACK_MS = 120;
1932
+
1933
+ const setTiltTransition = (ms) => {
1934
+ const value = `transform ${ms}ms cubic-bezier(0.33,1,0.68,1)`;
1935
+ this.el.style.transition = value;
1936
+ if (this._mirror) this._mirror.style.transition = value;
1937
+ if (this._shadowEl) this._shadowEl.style.transition = value;
1938
+ };
1939
+
1940
+ this._clearTiltEnterTimer = () => {
1941
+ if (this._tiltEnterTimer) {
1942
+ clearTimeout(this._tiltEnterTimer);
1943
+ this._tiltEnterTimer = null;
1944
+ }
1945
+ };
1946
+
1644
1947
  this._applyTilt = (clientX, clientY) => {
1645
1948
  if (!this._tiltInteracting) {
1646
1949
  this._tiltInteracting = true;
1647
- this.el.style.transition =
1648
- "transform 0.12s cubic-bezier(0.33,1,0.68,1)";
1649
1950
  this._createMirrorCanvas();
1650
- if (this._mirror) {
1651
- this._mirror.style.transition =
1652
- "transform 0.12s cubic-bezier(0.33,1,0.68,1)";
1653
- }
1654
- if (this._shadowEl) {
1655
- this._shadowEl.style.transition =
1656
- "transform 0.12s cubic-bezier(0.33,1,0.68,1)";
1951
+
1952
+ const enterMs = getTiltEase();
1953
+ setTiltTransition(enterMs);
1954
+
1955
+ this._tiltEntering = enterMs > 0;
1956
+ this._clearTiltEnterTimer();
1957
+ if (this._tiltEntering) {
1958
+ this._tiltEnterTimer = setTimeout(() => {
1959
+ this._tiltEntering = false;
1960
+ this._tiltEnterTimer = null;
1961
+ setTiltTransition(TILT_TRACK_MS);
1962
+ }, enterMs);
1657
1963
  }
1658
1964
  }
1659
1965
 
1660
- const r = this._baseRect || this.el.getBoundingClientRect();
1966
+ const r = this._baseRect || this._measureBaseRect();
1661
1967
  const cx = r.left + r.width / 2;
1662
1968
  const cy = r.top + r.height / 2;
1663
1969
 
@@ -1686,8 +1992,13 @@ const liquidGL = (() => {
1686
1992
 
1687
1993
  const transformStr = `${overscrollCompensation}${baseTransform}perspective(800px) rotateX(${rotX}deg) rotateY(${rotY}deg)`;
1688
1994
 
1689
- this.tiltX = rotX;
1690
- this.tiltY = rotY;
1995
+ if (this._tiltEntering) {
1996
+ this._easeTiltTo(rotX, rotY, getTiltEase());
1997
+ } else {
1998
+ this._cancelTiltEase();
1999
+ this.tiltX = rotX;
2000
+ this.tiltY = rotY;
2001
+ }
1691
2002
 
1692
2003
  this.el.style.transformOrigin = `50% 50%`;
1693
2004
  this.el.style.transform = transformStr;
@@ -1705,8 +2016,60 @@ const liquidGL = (() => {
1705
2016
  this.renderer.render();
1706
2017
  };
1707
2018
 
2019
+ this._cancelTiltEase = () => {
2020
+ if (this._tiltEaseRaf) {
2021
+ cancelAnimationFrame(this._tiltEaseRaf);
2022
+ this._tiltEaseRaf = null;
2023
+ }
2024
+ };
2025
+
2026
+ this._easeTiltTo = (toX, toY, duration) => {
2027
+ this._cancelTiltEase();
2028
+
2029
+ const fromX = this.tiltX;
2030
+ const fromY = this.tiltY;
2031
+ if (fromX === toX && fromY === toY) return;
2032
+
2033
+ if (!duration || duration <= 0) {
2034
+ this.tiltX = toX;
2035
+ this.tiltY = toY;
2036
+ this.renderer.render();
2037
+ return;
2038
+ }
2039
+
2040
+ const start =
2041
+ typeof performance !== "undefined" ? performance.now() : Date.now();
2042
+
2043
+ const step = (now) => {
2044
+ const elapsed =
2045
+ (typeof now === "number"
2046
+ ? now
2047
+ : typeof performance !== "undefined"
2048
+ ? performance.now()
2049
+ : Date.now()) - start;
2050
+ const t = Math.min(1, elapsed / duration);
2051
+ const eased = 1 - Math.pow(1 - t, 3);
2052
+
2053
+ this.tiltX = fromX + (toX - fromX) * eased;
2054
+ this.tiltY = fromY + (toY - fromY) * eased;
2055
+ this.renderer.render();
2056
+
2057
+ if (t < 1) {
2058
+ this._tiltEaseRaf = requestAnimationFrame(step);
2059
+ } else {
2060
+ this._tiltEaseRaf = null;
2061
+ this.tiltX = toX;
2062
+ this.tiltY = toY;
2063
+ this.renderer.render();
2064
+ }
2065
+ };
2066
+
2067
+ this._tiltEaseRaf = requestAnimationFrame(step);
2068
+ };
2069
+
1708
2070
  this._smoothReset = () => {
1709
- this.el.style.transition = "transform 0.4s cubic-bezier(0.33,1,0.68,1)";
2071
+ const restMs = getTiltEase();
2072
+ setTiltTransition(restMs);
1710
2073
  this.el.style.transformOrigin = `50% 50%`;
1711
2074
  const baseRest =
1712
2075
  this._savedTransform && this._savedTransform !== "none"
@@ -1726,13 +2089,11 @@ const liquidGL = (() => {
1726
2089
 
1727
2090
  this.el.style.transform = `${overscrollCompensation}${baseRest}perspective(800px) rotateX(0deg) rotateY(0deg)`;
1728
2091
 
1729
- this.tiltX = 0;
1730
- this.tiltY = 0;
1731
- this.renderer.render();
2092
+ this._tiltEntering = false;
2093
+ this._clearTiltEnterTimer();
2094
+ this._easeTiltTo(0, 0, getTiltEase());
1732
2095
 
1733
2096
  if (this._mirror) {
1734
- this._mirror.style.transition =
1735
- "transform 0.4s cubic-bezier(0.33, 1, 0.68, 1)";
1736
2097
  this._mirror.style.transformOrigin = this._pivotOrigin || "50% 50%";
1737
2098
  this._mirror.style.transform = `${baseRest}perspective(800px) rotateX(0deg) rotateY(0deg)`;
1738
2099
  const clean = () => {
@@ -1742,12 +2103,10 @@ const liquidGL = (() => {
1742
2103
  this._mirror.addEventListener("transitionend", clean, {
1743
2104
  once: true,
1744
2105
  });
1745
- this._resetCleanupTimer = setTimeout(clean, 350);
2106
+ this._resetCleanupTimer = setTimeout(clean, restMs + 50);
1746
2107
  }
1747
2108
 
1748
2109
  if (this._shadowEl) {
1749
- this._shadowEl.style.transition =
1750
- "transform 0.4s cubic-bezier(0.33,1,0.68,1)";
1751
2110
  this._shadowEl.style.transformOrigin = `50% 50%`;
1752
2111
  this._shadowEl.style.transform = `${baseRest}perspective(800px) rotateX(0deg) rotateY(0deg)`;
1753
2112
  }
@@ -1766,7 +2125,7 @@ const liquidGL = (() => {
1766
2125
  this._tiltInteracting = false;
1767
2126
  this._createMirrorCanvas();
1768
2127
 
1769
- const r = this._baseRect || this.el.getBoundingClientRect();
2128
+ const r = this._baseRect || this._measureBaseRect();
1770
2129
  const cx = r.left + r.width / 2;
1771
2130
  const cy = r.top + r.height / 2;
1772
2131
 
@@ -1853,6 +2212,11 @@ const liquidGL = (() => {
1853
2212
 
1854
2213
  _unbindTiltHandlers() {
1855
2214
  if (!this._tiltHandlersBound) return;
2215
+ if (this._cancelTiltEase) this._cancelTiltEase();
2216
+ if (this._clearTiltEnterTimer) this._clearTiltEnterTimer();
2217
+ this._tiltEntering = false;
2218
+ this.tiltX = 0;
2219
+ this.tiltY = 0;
1856
2220
  this.el.removeEventListener("mouseenter", this._onMouseEnter.bind(this));
1857
2221
  this.el.removeEventListener("mousemove", this._onMouseMove.bind(this));
1858
2222
  document.removeEventListener("mousemove", this._boundCheckLeave);
@@ -1872,8 +2236,26 @@ const liquidGL = (() => {
1872
2236
  this.renderer.render();
1873
2237
  }
1874
2238
 
2239
+ _measureBaseRect() {
2240
+ const tilted =
2241
+ this.tiltX !== 0 || this.tiltY !== 0 || !!this._tiltEaseRaf;
2242
+ if (!tilted) return this.el.getBoundingClientRect();
2243
+
2244
+ const prevTransition = this.el.style.transition;
2245
+ const prevTransform = this.el.style.transform;
2246
+
2247
+ this.el.style.transition = "none";
2248
+ this.el.style.transform = this._savedTransform || "";
2249
+ const rect = this.el.getBoundingClientRect();
2250
+
2251
+ this.el.style.transform = prevTransform;
2252
+ this.el.style.transition = prevTransition;
2253
+
2254
+ return rect;
2255
+ }
2256
+
1875
2257
  _createMirrorCanvas() {
1876
- this._baseRect = this.el.getBoundingClientRect();
2258
+ this._baseRect = this._measureBaseRect();
1877
2259
  if (this._mirror) return;
1878
2260
  this._mirror = document.createElement("canvas");
1879
2261
  Object.assign(this._mirror.style, {
@@ -1891,9 +2273,9 @@ const liquidGL = (() => {
1891
2273
 
1892
2274
  const updateClip = () => {
1893
2275
  if (this._mirrorActive) {
1894
- this._baseRect = this._baseRect || this.el.getBoundingClientRect();
2276
+ this._baseRect = this._baseRect || this._measureBaseRect();
1895
2277
  }
1896
- const r = this._baseRect || this.el.getBoundingClientRect();
2278
+ const r = this._baseRect || this._measureBaseRect();
1897
2279
  const radius = `${this.radiusCss}px`;
1898
2280
  this._mirror.style.clipPath = `inset(${r.top}px ${
1899
2281
  innerWidth - r.right
@@ -1934,6 +2316,7 @@ const liquidGL = (() => {
1934
2316
  snapshot: "body",
1935
2317
  resolution: 2.0,
1936
2318
  refraction: 0.01,
2319
+ aberration: 0,
1937
2320
  bevelDepth: 0.08,
1938
2321
  bevelWidth: 0.15,
1939
2322
  frost: 0,
@@ -1942,6 +2325,7 @@ const liquidGL = (() => {
1942
2325
  reveal: "fade",
1943
2326
  tilt: false,
1944
2327
  tiltFactor: 5,
2328
+ tiltEase: 400,
1945
2329
  magnify: 1,
1946
2330
  on: {},
1947
2331
  };
@@ -1960,7 +2344,7 @@ const liquidGL = (() => {
1960
2344
 
1961
2345
  if (noWebGL) {
1962
2346
  console.warn(
1963
- "liquidGL: WebGL not available – falling back to CSS backdrop-filter."
2347
+ "liquidGL: WebGL not available – falling back to CSS backdrop-filter.",
1964
2348
  );
1965
2349
  const fallbackNodes = document.querySelectorAll(options.target);
1966
2350
  fallbackNodes.forEach((node) => {
@@ -1984,13 +2368,13 @@ const liquidGL = (() => {
1984
2368
  const nodeList = document.querySelectorAll(options.target);
1985
2369
  if (!nodeList || nodeList.length === 0) {
1986
2370
  console.warn(
1987
- `liquidGL: Target element(s) '${options.target}' not found.`
2371
+ `liquidGL: Target element(s) '${options.target}' not found.`,
1988
2372
  );
1989
2373
  return;
1990
2374
  }
1991
2375
 
1992
2376
  const instances = Array.from(nodeList).map((el) =>
1993
- renderer.addLens(el, options)
2377
+ renderer.addLens(el, options),
1994
2378
  );
1995
2379
 
1996
2380
  if (!renderer._rafId && !renderer.useExternalTicker) {
@@ -2023,15 +2407,15 @@ const liquidGL = (() => {
2023
2407
  const renderer = window.__liquidGLRenderer__;
2024
2408
  if (!renderer) {
2025
2409
  console.warn(
2026
- "liquidGL: Please initialize liquidGL *before* calling syncWith()."
2410
+ "liquidGL: Please initialize liquidGL *before* calling syncWith().",
2027
2411
  );
2028
2412
  return;
2029
2413
  }
2030
2414
 
2031
- const G = window.gsap;
2415
+ const G = config.gsap === false ? null : config.gsap || window.gsap;
2032
2416
  const L = window.Lenis;
2033
2417
  const LS = window.LocomotiveScroll;
2034
- const ST = G ? G.ScrollTrigger : null;
2418
+ const ST = G ? config.ScrollTrigger || G.ScrollTrigger : null;
2035
2419
 
2036
2420
  let lenis = config.lenis;
2037
2421
  let loco = config.locomotiveScroll;
@@ -2103,7 +2487,6 @@ const liquidGL = (() => {
2103
2487
 
2104
2488
  return { lenis, locomotiveScroll: loco };
2105
2489
  };
2106
-
2107
2490
  return liquidGL;
2108
2491
  })();
2109
2492