@dazecoop/liquid-glass 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/README.md CHANGED
@@ -4,7 +4,7 @@ A "liquid glass" lens that refracts, frosts and splits the light of real HTML be
4
4
 
5
5
  Built with vanilla JavaScript, SVG filters, [Vite](https://vite.dev) and [Tailwind CSS](https://tailwindcss.com).
6
6
 
7
- ![Liquid Glass lens over a night-sky lake scene, with the tuning controls below](docs/animation.gif)
7
+ ![Liquid Glass lens over a night-sky lake scene, with the tuning controls below](https://raw.githubusercontent.com/dazecoop/liquid-glass/main/docs/animation.gif)
8
8
 
9
9
  > [!NOTE]
10
10
  > Live demo: [glass.dazemx.com](https://glass.dazemx.com/)
@@ -144,6 +144,8 @@ You can also tune the lens on the [demo](https://glass.dazemx.com/) and copy the
144
144
 
145
145
  ## UI components
146
146
 
147
+ ![The switch, segmented switch and tab bar dissolving into glass as they're pressed and dragged, in light and dark](https://raw.githubusercontent.com/dazecoop/liquid-glass/main/docs/animation-ui.gif)
148
+
147
149
  A switch, a segmented switch, a floating tab bar and a glass button, after iOS 26. Import `style.css` once. In React, use the components above. In HTML, write the markup and call `initComponents()`:
148
150
 
149
151
  ```html
@@ -194,6 +196,8 @@ The dark look is the default. Put `data-theme="light"` on any ancestor for the l
194
196
  | `show(visible, { pop })` | Fades the lens in or out. With `pop: true` it bursts like a bubble on the way out and swells back in. `moveTo` shows it again. |
195
197
  | `destroy()` | Removes the glass and everything it added to the page, and stops its listeners and observers. Safe to call twice. The components have `destroy()` too. |
196
198
 
199
+ The glass re-copies its source whenever the source changes. Put `data-glass-skip` on anything inside it that updates every frame (a counter, a clock, an animated style) and its changes won't trigger a re-copy; the copy shows it as it was at the last one.
200
+
197
201
  | Parameter | Default | Description |
198
202
  | --- | --- | --- |
199
203
  | `width`, `height` | `280`, `180` | Lens size in CSS pixels (anchored glass takes its anchor's size) |
package/lib/index.cjs CHANGED
@@ -7,7 +7,9 @@ const ARC = 0.94;
7
7
  const ARC_DROP = 1 - Math.sqrt(1 - ARC * ARC);
8
8
  const UI_SPRING = { stiffness: 440, damping: 42 };
9
9
  const CLONE_CSS = `
10
- [data-glass-copy], [data-glass-copy] * {
10
+ [data-glass-copy], [data-glass-copy] *,
11
+ [data-glass-copy]::before, [data-glass-copy]::after,
12
+ [data-glass-copy] *::before, [data-glass-copy] *::after {
11
13
  backdrop-filter: none !important;
12
14
  -webkit-backdrop-filter: none !important;
13
15
  will-change: auto !important;
@@ -61,6 +63,7 @@ function follow(root, path) {
61
63
  return n;
62
64
  }
63
65
  const insideGlass = (node) => (node.nodeType === 1 ? node : node.parentElement)?.closest("[data-glass]");
66
+ const insideSkip = (node) => (node.nodeType === 1 ? node : node.parentElement)?.closest("[data-glass-skip]");
64
67
  class LiquidGlass {
65
68
  // Starting looks. Each is plain parameters: glass.set(LiquidGlass.presets.frosted).
66
69
  // Dome rounds the bevel all the way to the middle, so it reads best on a circle.
@@ -116,6 +119,7 @@ class LiquidGlass {
116
119
  #wake() {
117
120
  if (this.#awake || this.destroyed) return;
118
121
  this.#awake = true;
122
+ if (this.el && !this.anchor) this.el.style.willChange = "transform";
119
123
  this.last = performance.now();
120
124
  this.#raf = requestAnimationFrame((t) => this.#frame(t));
121
125
  }
@@ -138,7 +142,6 @@ class LiquidGlass {
138
142
  this.el = document.createElement("div");
139
143
  this.el.dataset.glass = "";
140
144
  this.el.setAttribute("aria-hidden", "true");
141
- this.el.inert = true;
142
145
  this.el.style.cssText = "pointer-events:none;overflow:hidden;box-shadow:0 12px 34px -6px rgb(0 0 0 / 0.35)";
143
146
  if (this.anchor) {
144
147
  if (getComputedStyle(this.anchor).position === "static") this.#style(this.anchor, "position", "relative");
@@ -146,7 +149,7 @@ class LiquidGlass {
146
149
  this.el.style.cssText += ";position:absolute;inset:0;z-index:-1;border-radius:inherit";
147
150
  } else {
148
151
  const position = this.container ? "absolute;z-index:1" : "fixed;z-index:2147483000";
149
- this.el.style.cssText += `;position:${position};left:0;top:0;will-change:transform;opacity:0;transition:opacity .3s`;
152
+ this.el.style.cssText += `;position:${position};left:0;top:0;opacity:0;transition:opacity .3s`;
150
153
  if (this.container && getComputedStyle(this.container).position === "static") this.#style(this.container, "position", "relative");
151
154
  }
152
155
  this.frost = document.createElement("div");
@@ -154,6 +157,7 @@ class LiquidGlass {
154
157
  this.view = document.createElement("div");
155
158
  this.view.style.cssText = `position:absolute;inset:0;overflow:hidden;filter:url(#${id})`;
156
159
  this.copyHost = document.createElement("div");
160
+ this.copyHost.inert = true;
157
161
  this.copyHost.style.cssText = "position:absolute;left:0;top:0";
158
162
  this.rim = document.createElement("canvas");
159
163
  this.rim.style.cssText = "position:absolute;inset:0;width:100%;height:100%";
@@ -207,7 +211,7 @@ class LiquidGlass {
207
211
  if (this.#shown) this.#wake();
208
212
  };
209
213
  const mutations = new MutationObserver((records) => {
210
- const counts = (r) => !insideGlass(r.target) && !(r.type === "attributes" && r.target.closest?.("[data-glass-skip]"));
214
+ const counts = (r) => !insideGlass(r.target) && !insideSkip(r.target);
211
215
  if (records.some(counts)) dirty();
212
216
  });
213
217
  mutations.observe(this.source, { subtree: true, childList: true, attributes: true, characterData: true });
@@ -261,6 +265,10 @@ class LiquidGlass {
261
265
  left: "0",
262
266
  top: "0",
263
267
  margin: "0",
268
+ transform: "none",
269
+ translate: "none",
270
+ rotate: "none",
271
+ scale: "none",
264
272
  width: `${rect.width}px`,
265
273
  height: `${rect.height}px`
266
274
  });
@@ -626,7 +634,10 @@ class LiquidGlass {
626
634
  const buttonMoving = this.button && (!this.hovered.settled || !this.pressed.settled);
627
635
  const waiting = this.shapeDirty || this.rectDirty || this.placeDirty || this.copyDirty && this.#shown || !this.inner;
628
636
  if (animating || buttonMoving || waiting) this.#raf = requestAnimationFrame((t) => this.#frame(t));
629
- else this.#awake = false;
637
+ else {
638
+ this.#awake = false;
639
+ this.el.style.willChange = "auto";
640
+ }
630
641
  }
631
642
  }
632
643
  const clamp = (v, lo, hi) => Math.min(Math.max(v, lo), hi);
@@ -641,7 +652,7 @@ const springs = (lens, move = MOVE) => {
641
652
  lens.x.damping = lens.y.damping = move.damping;
642
653
  };
643
654
  const arrived = (lens) => Math.abs(lens.x.value - lens.x.target) <= 0.5 && Math.abs(lens.x.velocity) <= 30;
644
- const CLEAR = { frost: 1, fresnel: 0.15, saturation: 0, tint: "", rim: 0.45, body: 0, falloff: 0, lightAngle: -123.5 };
655
+ const CLEAR = { frost: 0, fresnel: 0.15, saturation: 0, tint: "", rim: 0.45, body: 0, falloff: 0, lightAngle: -123.5 };
645
656
  const LOOKS = {
646
657
  switch: {
647
658
  dark: { ...CLEAR, refraction: 25, bevel: 6, chroma: 0.21, zoom: 1.12, specular: 0.25, brightness: 0.03 },
@@ -652,10 +663,11 @@ const LOOKS = {
652
663
  dark: { ...CLEAR, refraction: 28, bevel: 5, falloff: 4.5, chroma: 0.1, zoom: 1.1, specular: 0.25, brightness: 0.03, lightAngle: -140 },
653
664
  light: { ...CLEAR, refraction: 28, bevel: 5, falloff: 4.5, chroma: 0.1, zoom: 1.1, specular: 0.55, brightness: 0, lightAngle: -140 }
654
665
  },
655
- // A little milk and a brighter rim, so a resting button reads on a flat background
666
+ // A little milk, a little frost and a brighter rim, so a resting button reads on a flat
667
+ // background
656
668
  button: {
657
- dark: { ...CLEAR, refraction: 28, bevel: 5, chroma: 0.12, zoom: 1.08, specular: 0.25, brightness: 0.03, rim: 0.6, body: 0.12 },
658
- light: { ...CLEAR, refraction: 28, bevel: 5, chroma: 0.12, zoom: 1.08, specular: 0.55, brightness: 0, rim: 0.6, body: 0.12 }
669
+ dark: { ...CLEAR, frost: 1, refraction: 28, bevel: 5, chroma: 0.12, zoom: 1.08, specular: 0.25, brightness: 0.03, rim: 0.6, body: 0.12 },
670
+ light: { ...CLEAR, frost: 1, refraction: 28, bevel: 5, chroma: 0.12, zoom: 1.08, specular: 0.55, brightness: 0, rim: 0.6, body: 0.12 }
659
671
  }
660
672
  };
661
673
  const listener = (signal) => (target, type, fn) => target.addEventListener(type, fn, { signal });
package/lib/index.js CHANGED
@@ -5,7 +5,9 @@ const ARC = 0.94;
5
5
  const ARC_DROP = 1 - Math.sqrt(1 - ARC * ARC);
6
6
  const UI_SPRING = { stiffness: 440, damping: 42 };
7
7
  const CLONE_CSS = `
8
- [data-glass-copy], [data-glass-copy] * {
8
+ [data-glass-copy], [data-glass-copy] *,
9
+ [data-glass-copy]::before, [data-glass-copy]::after,
10
+ [data-glass-copy] *::before, [data-glass-copy] *::after {
9
11
  backdrop-filter: none !important;
10
12
  -webkit-backdrop-filter: none !important;
11
13
  will-change: auto !important;
@@ -59,6 +61,7 @@ function follow(root, path) {
59
61
  return n;
60
62
  }
61
63
  const insideGlass = (node) => (node.nodeType === 1 ? node : node.parentElement)?.closest("[data-glass]");
64
+ const insideSkip = (node) => (node.nodeType === 1 ? node : node.parentElement)?.closest("[data-glass-skip]");
62
65
  class LiquidGlass {
63
66
  // Starting looks. Each is plain parameters: glass.set(LiquidGlass.presets.frosted).
64
67
  // Dome rounds the bevel all the way to the middle, so it reads best on a circle.
@@ -114,6 +117,7 @@ class LiquidGlass {
114
117
  #wake() {
115
118
  if (this.#awake || this.destroyed) return;
116
119
  this.#awake = true;
120
+ if (this.el && !this.anchor) this.el.style.willChange = "transform";
117
121
  this.last = performance.now();
118
122
  this.#raf = requestAnimationFrame((t) => this.#frame(t));
119
123
  }
@@ -136,7 +140,6 @@ class LiquidGlass {
136
140
  this.el = document.createElement("div");
137
141
  this.el.dataset.glass = "";
138
142
  this.el.setAttribute("aria-hidden", "true");
139
- this.el.inert = true;
140
143
  this.el.style.cssText = "pointer-events:none;overflow:hidden;box-shadow:0 12px 34px -6px rgb(0 0 0 / 0.35)";
141
144
  if (this.anchor) {
142
145
  if (getComputedStyle(this.anchor).position === "static") this.#style(this.anchor, "position", "relative");
@@ -144,7 +147,7 @@ class LiquidGlass {
144
147
  this.el.style.cssText += ";position:absolute;inset:0;z-index:-1;border-radius:inherit";
145
148
  } else {
146
149
  const position = this.container ? "absolute;z-index:1" : "fixed;z-index:2147483000";
147
- this.el.style.cssText += `;position:${position};left:0;top:0;will-change:transform;opacity:0;transition:opacity .3s`;
150
+ this.el.style.cssText += `;position:${position};left:0;top:0;opacity:0;transition:opacity .3s`;
148
151
  if (this.container && getComputedStyle(this.container).position === "static") this.#style(this.container, "position", "relative");
149
152
  }
150
153
  this.frost = document.createElement("div");
@@ -152,6 +155,7 @@ class LiquidGlass {
152
155
  this.view = document.createElement("div");
153
156
  this.view.style.cssText = `position:absolute;inset:0;overflow:hidden;filter:url(#${id})`;
154
157
  this.copyHost = document.createElement("div");
158
+ this.copyHost.inert = true;
155
159
  this.copyHost.style.cssText = "position:absolute;left:0;top:0";
156
160
  this.rim = document.createElement("canvas");
157
161
  this.rim.style.cssText = "position:absolute;inset:0;width:100%;height:100%";
@@ -205,7 +209,7 @@ class LiquidGlass {
205
209
  if (this.#shown) this.#wake();
206
210
  };
207
211
  const mutations = new MutationObserver((records) => {
208
- const counts = (r) => !insideGlass(r.target) && !(r.type === "attributes" && r.target.closest?.("[data-glass-skip]"));
212
+ const counts = (r) => !insideGlass(r.target) && !insideSkip(r.target);
209
213
  if (records.some(counts)) dirty();
210
214
  });
211
215
  mutations.observe(this.source, { subtree: true, childList: true, attributes: true, characterData: true });
@@ -259,6 +263,10 @@ class LiquidGlass {
259
263
  left: "0",
260
264
  top: "0",
261
265
  margin: "0",
266
+ transform: "none",
267
+ translate: "none",
268
+ rotate: "none",
269
+ scale: "none",
262
270
  width: `${rect.width}px`,
263
271
  height: `${rect.height}px`
264
272
  });
@@ -624,7 +632,10 @@ class LiquidGlass {
624
632
  const buttonMoving = this.button && (!this.hovered.settled || !this.pressed.settled);
625
633
  const waiting = this.shapeDirty || this.rectDirty || this.placeDirty || this.copyDirty && this.#shown || !this.inner;
626
634
  if (animating || buttonMoving || waiting) this.#raf = requestAnimationFrame((t) => this.#frame(t));
627
- else this.#awake = false;
635
+ else {
636
+ this.#awake = false;
637
+ this.el.style.willChange = "auto";
638
+ }
628
639
  }
629
640
  }
630
641
  const clamp = (v, lo, hi) => Math.min(Math.max(v, lo), hi);
@@ -639,7 +650,7 @@ const springs = (lens, move = MOVE) => {
639
650
  lens.x.damping = lens.y.damping = move.damping;
640
651
  };
641
652
  const arrived = (lens) => Math.abs(lens.x.value - lens.x.target) <= 0.5 && Math.abs(lens.x.velocity) <= 30;
642
- const CLEAR = { frost: 1, fresnel: 0.15, saturation: 0, tint: "", rim: 0.45, body: 0, falloff: 0, lightAngle: -123.5 };
653
+ const CLEAR = { frost: 0, fresnel: 0.15, saturation: 0, tint: "", rim: 0.45, body: 0, falloff: 0, lightAngle: -123.5 };
643
654
  const LOOKS = {
644
655
  switch: {
645
656
  dark: { ...CLEAR, refraction: 25, bevel: 6, chroma: 0.21, zoom: 1.12, specular: 0.25, brightness: 0.03 },
@@ -650,10 +661,11 @@ const LOOKS = {
650
661
  dark: { ...CLEAR, refraction: 28, bevel: 5, falloff: 4.5, chroma: 0.1, zoom: 1.1, specular: 0.25, brightness: 0.03, lightAngle: -140 },
651
662
  light: { ...CLEAR, refraction: 28, bevel: 5, falloff: 4.5, chroma: 0.1, zoom: 1.1, specular: 0.55, brightness: 0, lightAngle: -140 }
652
663
  },
653
- // A little milk and a brighter rim, so a resting button reads on a flat background
664
+ // A little milk, a little frost and a brighter rim, so a resting button reads on a flat
665
+ // background
654
666
  button: {
655
- dark: { ...CLEAR, refraction: 28, bevel: 5, chroma: 0.12, zoom: 1.08, specular: 0.25, brightness: 0.03, rim: 0.6, body: 0.12 },
656
- light: { ...CLEAR, refraction: 28, bevel: 5, chroma: 0.12, zoom: 1.08, specular: 0.55, brightness: 0, rim: 0.6, body: 0.12 }
667
+ dark: { ...CLEAR, frost: 1, refraction: 28, bevel: 5, chroma: 0.12, zoom: 1.08, specular: 0.25, brightness: 0.03, rim: 0.6, body: 0.12 },
668
+ light: { ...CLEAR, frost: 1, refraction: 28, bevel: 5, chroma: 0.12, zoom: 1.08, specular: 0.55, brightness: 0, rim: 0.6, body: 0.12 }
657
669
  }
658
670
  };
659
671
  const listener = (signal) => (target, type, fn) => target.addEventListener(type, fn, { signal });
@@ -1,12 +1,14 @@
1
- var LiquidGlass=(function(){"use strict";const kt="http://www.w3.org/2000/svg",Lt=[.2126,.7152,.0722],Q=.94,Et=1-Math.sqrt(1-Q*Q),ht={stiffness:440,damping:42},Ct=`
2
- [data-glass-copy], [data-glass-copy] * {
1
+ var LiquidGlass=(function(){"use strict";const kt="http://www.w3.org/2000/svg",Lt=[.2126,.7152,.0722],Q=.94,Ct=1-Math.sqrt(1-Q*Q),ht={stiffness:440,damping:42},Et=`
2
+ [data-glass-copy], [data-glass-copy] *,
3
+ [data-glass-copy]::before, [data-glass-copy]::after,
4
+ [data-glass-copy] *::before, [data-glass-copy] *::after {
3
5
  backdrop-filter: none !important;
4
6
  -webkit-backdrop-filter: none !important;
5
7
  will-change: auto !important;
6
8
  animation: none !important;
7
9
  transition: none !important;
8
- }`;let St=0;class z{constructor(t,{stiffness:s=260,damping:e=26}={},i=null){this.wake=i,this.value=t,this.target=t,this.velocity=0,this.stiffness=s,this.damping=e}get target(){return this._target}set target(t){this._target=t,this.wake?.()}step(t){const s=(this.target-this.value)*this.stiffness-this.velocity*this.damping;this.velocity+=s*t,this.value+=this.velocity*t}snap(t){this.value=this.target=t,this.velocity=0}get settled(){return Math.abs(this.target-this.value)<.01&&Math.abs(this.velocity)<.01}}function at(l,t,s,e,i){const n=Math.abs(l)-s+i,r=Math.abs(t)-e+i;return Math.hypot(Math.max(n,0),Math.max(r,0))+Math.min(Math.max(n,r),0)-i}function Z(l,t){const s=[];for(let e=t;e&&e!==l;e=e.parentElement)s.unshift([...e.parentElement.children].indexOf(e));return s}function tt(l,t){let s=l;for(const e of t)s=s?.children[e];return s}const lt=l=>(l.nodeType===1?l:l.parentElement)?.closest("[data-glass]");class C{static presets={clear:{refraction:40,frost:2,chroma:.4,zoom:1.08,bevel:35,specular:.35,fresnel:.3,brightness:0,saturation:0,tint:"",rim:1},frosted:{refraction:24,frost:14,chroma:.15,zoom:1,bevel:30,specular:.5,fresnel:.5,brightness:.08,saturation:.3,tint:"rgb(255 255 255 / 0.08)",rim:1},dark:{refraction:32,frost:6,chroma:.2,zoom:1.04,bevel:35,specular:.3,fresnel:.2,brightness:-.18,saturation:-.2,tint:"rgb(0 0 0 / 0.25)",rim:.6},prism:{refraction:80,frost:0,chroma:1,zoom:1.12,bevel:50,specular:.25,fresnel:.4,brightness:.02,saturation:.5,tint:"",rim:.8},dome:{refraction:50,frost:0,chroma:.2,zoom:1.35,bevel:160,specular:.7,fresnel:.6,brightness:0,saturation:.1,tint:"",rim:1}};constructor(t,{anchor:s=null,container:e=null,button:i=!1,throttle:n=0,...r}={}){this.source=t,this.anchor=s,this.container=s?null:e,this.button=!!(i&&s),this.throttle=n,this.syncedAt=-1/0,this.params={width:280,height:180,radius:64,...C.presets.clear,...r},this.reducedMotion=matchMedia("(prefers-reduced-motion: reduce)").matches;const h=()=>this.#s();this.x=new z(innerWidth/2,void 0,h),this.y=new z(innerHeight/2,void 0,h),this.scale=new z(1,{stiffness:320,damping:18},h),this.stretchX=new z(1,{stiffness:180,damping:14}),this.stretchY=new z(1,{stiffness:180,damping:14}),this.springs=[this.x,this.y,this.scale,this.stretchX,this.stretchY],this.hovered=new z(0,ht,h),this.pressed=new z(0,ht,h),this.scrolled=new Set,this.popScale=1,this.#p(),this.#d(),this.shapeDirty=!0,this.copyDirty=!0,this.placeDirty=!0,this.#s()}#t=!1;#i=0;#n=new AbortController;#r=[];#a=[];#s(){this.#t||this.destroyed||(this.#t=!0,this.last=performance.now(),this.#i=requestAnimationFrame(t=>this.#c(t)))}#o(t,s,e){this.#a.push([t,s,t.style[s]]),t.style[s]=e}get#e(){return!!(this.anchor||this.visible||this.pop)}#p(){const t=`liquid-glass-${++St}`;if(this.svg=document.createElementNS(kt,"svg"),this.svg.setAttribute("aria-hidden","true"),this.svg.style.cssText="position:absolute;width:0;height:0;overflow:hidden",this.svg.innerHTML=`<style>${Ct}</style><filter id="${t}" x="0" y="0" width="100%" height="100%" color-interpolation-filters="sRGB" />`,this.filter=this.svg.querySelector("filter"),this.el=document.createElement("div"),this.el.dataset.glass="",this.el.setAttribute("aria-hidden","true"),this.el.inert=!0,this.el.style.cssText="pointer-events:none;overflow:hidden;box-shadow:0 12px 34px -6px rgb(0 0 0 / 0.35)",this.anchor)getComputedStyle(this.anchor).position==="static"&&this.#o(this.anchor,"position","relative"),this.#o(this.anchor,"isolation","isolate"),this.el.style.cssText+=";position:absolute;inset:0;z-index:-1;border-radius:inherit";else{const e=this.container?"absolute;z-index:1":"fixed;z-index:2147483000";this.el.style.cssText+=`;position:${e};left:0;top:0;will-change:transform;opacity:0;transition:opacity .3s`,this.container&&getComputedStyle(this.container).position==="static"&&this.#o(this.container,"position","relative")}this.frost=document.createElement("div"),this.frost.style.cssText="position:absolute",this.view=document.createElement("div"),this.view.style.cssText=`position:absolute;inset:0;overflow:hidden;filter:url(#${t})`,this.copyHost=document.createElement("div"),this.copyHost.style.cssText="position:absolute;left:0;top:0",this.rim=document.createElement("canvas"),this.rim.style.cssText="position:absolute;inset:0;width:100%;height:100%",this.view.append(this.copyHost),this.frost.append(this.view),this.el.append(this.frost,this.rim),document.body.append(this.svg),(this.anchor??this.container??document.body).prepend(this.el);const{signal:s}=this.#n;if(this.anchor){const e=new ResizeObserver(()=>{this.set({width:this.anchor.offsetWidth,height:this.anchor.offsetHeight,radius:parseFloat(getComputedStyle(this.anchor).borderTopLeftRadius)||0})});e.observe(this.anchor),this.#r.push(e)}if(this.button){const e=this.anchor,i=()=>this.pressed.target=0;e.addEventListener("pointerenter",()=>this.hovered.target=1,{signal:s}),e.addEventListener("pointerleave",()=>{this.hovered.target=0,i()},{signal:s}),e.addEventListener("pointerdown",()=>this.pressed.target=1,{signal:s}),e.addEventListener("pointerup",i,{signal:s}),e.addEventListener("pointercancel",i,{signal:s}),e.addEventListener("keydown",n=>{(n.key===" "||n.key==="Enter")&&(this.pressed.target=1)},{signal:s}),e.addEventListener("keyup",i,{signal:s}),e.addEventListener("blur",i,{signal:s})}}#d(){const{signal:t}=this.#n,s=()=>{this.copyDirty=!0,this.#e&&this.#s()},e=new MutationObserver(i=>{const n=r=>!lt(r.target)&&!(r.type==="attributes"&&r.target.closest?.("[data-glass-skip]"));i.some(n)&&s()});e.observe(this.source,{subtree:!0,childList:!0,attributes:!0,characterData:!0}),this.#r.push(e),this.source.addEventListener("input",s,{capture:!0,signal:t}),this.source.addEventListener("scroll",i=>{lt(i.target)||(this.scrolled.add(i.target),s())},{capture:!0,signal:t}),addEventListener("resize",s,{signal:t}),addEventListener("scroll",()=>{this.#e&&(this.rectDirty=!0,this.#s())},{passive:!0,signal:t})}#f(){const t=this.source.cloneNode(!0);t.dataset.glassCopy="";const s="input, textarea, select, canvas",e=t.querySelectorAll(s);this.source.querySelectorAll(s).forEach((r,h)=>{const a=e[h];"value"in r&&r.type!=="file"&&(a.value=r.value),"checked"in r&&(a.checked=r.checked),r instanceof HTMLCanvasElement&&a.getContext("2d")?.drawImage(r,0,0)});const i=[];for(const r of this.scrolled)this.source.contains(r)?i.push([tt(t,Z(this.source,r)),r.scrollLeft,r.scrollTop]):this.scrolled.delete(r);this.anchor&&this.source.contains(this.anchor)?tt(t,Z(this.source,this.anchor))?.remove():this.source.contains(this.el)&&tt(t,Z(this.source,this.el))?.remove();for(const r of t.querySelectorAll("[data-glass]"))for(const h of r.querySelectorAll("[data-glass]"))h.remove();t.removeAttribute("id");for(const r of t.querySelectorAll("[id]"))r.removeAttribute("id");const n=this.source.getBoundingClientRect();this.sourceRect=n,this.containerRect=this.container?.getBoundingClientRect(),Object.assign(t.style,{position:"absolute",inset:"auto",left:"0",top:"0",margin:"0",width:`${n.width}px`,height:`${n.height}px`}),this.copyHost.replaceChildren(t);for(const[r,h,a]of i)r?.scrollTo(h,a);this.copyDirty=!1}#u(){const{width:t,height:s,radius:e,bevel:i,refraction:n,frost:r,chroma:h,zoom:a,specular:o,fresnel:c,tint:u,rim:x}=this.params,m=this.params.body??1,p=Math.min(devicePixelRatio||1,2),g=t/2,b=s/2,G=Math.min(e,g,b),O=Math.max(Math.min(i,g,b),1),q=(d,f)=>at(d,f,g,b,G),it=(d,f,v)=>{const y=q(d+v,f)-q(d-v,f),R=q(d,f+v)-q(d,f-v),A=Math.hypot(y,R)||1;return[y/A,R/A]},w=a,H=g/w,B=b/w,mt=O/w,Y=n/w,X=(d,f)=>at(d,f,H,B,G/w),S=p*Math.max(w,1),M=Math.ceil(r*1.5/w);this.inner={x:H+M,y:B+M};const nt=Y*2+Math.sign(Y||1),rt=(this.params.falloff??0)/w,$=document.createElement("canvas");$.width=Math.ceil((H+M)*2*S),$.height=Math.ceil((B+M)*2*S);const yt=$.getContext("2d"),U=yt.createImageData($.width,$.height);for(let d=0;d<$.height;d++)for(let f=0;f<$.width;f++){const v=(f+.5)/S-H-M,y=(d+.5)/S-B-M;let R=0,A=0;const L=X(v,y),I=rt>0;if(L<0&&(I?L>-rt*6:L>-mt)&&n!==0){let D;if(I)D=Math.exp(L/rt)*Y;else{const J=(1+L/mt)*Q;D=(1-Math.sqrt(1-J*J))/Et*Y}const N=X(v+.5/S,y)-X(v-.5/S,y),j=X(v,y+.5/S)-X(v,y-.5/S),K=Math.hypot(N,j)||1;R=N/K*D,A=j/K*D}const E=(d*$.width+f)*4;U.data[E]=(.5-R/nt)*255,U.data[E+1]=(.5-A/nt)*255,U.data[E+3]=255}yt.putImageData(U,0,0);const k=this.rim;k.width=Math.ceil(t*p),k.height=Math.ceil(s*p);const bt=k.getContext("2d"),W=bt.createImageData(k.width,k.height),vt=(this.params.lightAngle??-123.5)*Math.PI/180,xt=Math.cos(vt),wt=Math.sin(vt),Mt=Math.sqrt(1-.5**2),At=[xt*Mt,wt*Mt,.5+1],It=Math.hypot(...At),[Dt,Ft,Ht]=At.map(d=>d/It);for(let d=0;d<k.height;d++){const f=(d+.5)/p-b,v=(1-(f+b)/s)*.05;for(let y=0;y<k.width;y++){const R=(y+.5)/p-g,A=q(R,f);if(A>0)continue;let L=(.06+v)*m;if(A>-O){const E=1+A/O,[D,N]=it(R,f,1/p),j=D*xt+N*wt,K=E**3*(.55*Math.max(j,0)+.22*Math.max(-j,0)),J=(1-Math.min(-A/1.5,1))*.35;L+=(K+J)*x;const $t=Math.sqrt(1-E*E),Pt=Math.max(D*E*Dt+N*E*Ft+$t*Ht,0)**48;L+=o*Pt*1.4+c*(1-$t)**3*.5}const I=(d*k.width+y)*4;W.data[I]=W.data[I+1]=W.data[I+2]=255,W.data[I+3]=Math.min(L,1)*255}}bt.putImageData(W,0,0),k.style.background=u;const Bt=$.toDataURL(),ot=h*.35,V=(d,f)=>`<feDisplacementMap in="SourceGraphic" in2="map" scale="${nt*d}" xChannelSelector="R" yChannelSelector="G" result="${f}" />`;this.filterBase=`<feImage href="${Bt}" result="map" preserveAspectRatio="none" />`+(ot>.001?V(1+ot,"dr")+V(1,"dg")+V(1-ot,"db")+`<feColorMatrix in="dr" values="1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" result="r" />
10
+ }`;let St=0;class z{constructor(t,{stiffness:s=260,damping:e=26}={},i=null){this.wake=i,this.value=t,this.target=t,this.velocity=0,this.stiffness=s,this.damping=e}get target(){return this._target}set target(t){this._target=t,this.wake?.()}step(t){const s=(this.target-this.value)*this.stiffness-this.velocity*this.damping;this.velocity+=s*t,this.value+=this.velocity*t}snap(t){this.value=this.target=t,this.velocity=0}get settled(){return Math.abs(this.target-this.value)<.01&&Math.abs(this.velocity)<.01}}function at(l,t,s,e,i){const n=Math.abs(l)-s+i,r=Math.abs(t)-e+i;return Math.hypot(Math.max(n,0),Math.max(r,0))+Math.min(Math.max(n,r),0)-i}function Z(l,t){const s=[];for(let e=t;e&&e!==l;e=e.parentElement)s.unshift([...e.parentElement.children].indexOf(e));return s}function tt(l,t){let s=l;for(const e of t)s=s?.children[e];return s}const lt=l=>(l.nodeType===1?l:l.parentElement)?.closest("[data-glass]"),Rt=l=>(l.nodeType===1?l:l.parentElement)?.closest("[data-glass-skip]");class E{static presets={clear:{refraction:40,frost:2,chroma:.4,zoom:1.08,bevel:35,specular:.35,fresnel:.3,brightness:0,saturation:0,tint:"",rim:1},frosted:{refraction:24,frost:14,chroma:.15,zoom:1,bevel:30,specular:.5,fresnel:.5,brightness:.08,saturation:.3,tint:"rgb(255 255 255 / 0.08)",rim:1},dark:{refraction:32,frost:6,chroma:.2,zoom:1.04,bevel:35,specular:.3,fresnel:.2,brightness:-.18,saturation:-.2,tint:"rgb(0 0 0 / 0.25)",rim:.6},prism:{refraction:80,frost:0,chroma:1,zoom:1.12,bevel:50,specular:.25,fresnel:.4,brightness:.02,saturation:.5,tint:"",rim:.8},dome:{refraction:50,frost:0,chroma:.2,zoom:1.35,bevel:160,specular:.7,fresnel:.6,brightness:0,saturation:.1,tint:"",rim:1}};constructor(t,{anchor:s=null,container:e=null,button:i=!1,throttle:n=0,...r}={}){this.source=t,this.anchor=s,this.container=s?null:e,this.button=!!(i&&s),this.throttle=n,this.syncedAt=-1/0,this.params={width:280,height:180,radius:64,...E.presets.clear,...r},this.reducedMotion=matchMedia("(prefers-reduced-motion: reduce)").matches;const h=()=>this.#s();this.x=new z(innerWidth/2,void 0,h),this.y=new z(innerHeight/2,void 0,h),this.scale=new z(1,{stiffness:320,damping:18},h),this.stretchX=new z(1,{stiffness:180,damping:14}),this.stretchY=new z(1,{stiffness:180,damping:14}),this.springs=[this.x,this.y,this.scale,this.stretchX,this.stretchY],this.hovered=new z(0,ht,h),this.pressed=new z(0,ht,h),this.scrolled=new Set,this.popScale=1,this.#p(),this.#d(),this.shapeDirty=!0,this.copyDirty=!0,this.placeDirty=!0,this.#s()}#t=!1;#i=0;#n=new AbortController;#r=[];#a=[];#s(){this.#t||this.destroyed||(this.#t=!0,this.el&&!this.anchor&&(this.el.style.willChange="transform"),this.last=performance.now(),this.#i=requestAnimationFrame(t=>this.#c(t)))}#o(t,s,e){this.#a.push([t,s,t.style[s]]),t.style[s]=e}get#e(){return!!(this.anchor||this.visible||this.pop)}#p(){const t=`liquid-glass-${++St}`;if(this.svg=document.createElementNS(kt,"svg"),this.svg.setAttribute("aria-hidden","true"),this.svg.style.cssText="position:absolute;width:0;height:0;overflow:hidden",this.svg.innerHTML=`<style>${Et}</style><filter id="${t}" x="0" y="0" width="100%" height="100%" color-interpolation-filters="sRGB" />`,this.filter=this.svg.querySelector("filter"),this.el=document.createElement("div"),this.el.dataset.glass="",this.el.setAttribute("aria-hidden","true"),this.el.style.cssText="pointer-events:none;overflow:hidden;box-shadow:0 12px 34px -6px rgb(0 0 0 / 0.35)",this.anchor)getComputedStyle(this.anchor).position==="static"&&this.#o(this.anchor,"position","relative"),this.#o(this.anchor,"isolation","isolate"),this.el.style.cssText+=";position:absolute;inset:0;z-index:-1;border-radius:inherit";else{const e=this.container?"absolute;z-index:1":"fixed;z-index:2147483000";this.el.style.cssText+=`;position:${e};left:0;top:0;opacity:0;transition:opacity .3s`,this.container&&getComputedStyle(this.container).position==="static"&&this.#o(this.container,"position","relative")}this.frost=document.createElement("div"),this.frost.style.cssText="position:absolute",this.view=document.createElement("div"),this.view.style.cssText=`position:absolute;inset:0;overflow:hidden;filter:url(#${t})`,this.copyHost=document.createElement("div"),this.copyHost.inert=!0,this.copyHost.style.cssText="position:absolute;left:0;top:0",this.rim=document.createElement("canvas"),this.rim.style.cssText="position:absolute;inset:0;width:100%;height:100%",this.view.append(this.copyHost),this.frost.append(this.view),this.el.append(this.frost,this.rim),document.body.append(this.svg),(this.anchor??this.container??document.body).prepend(this.el);const{signal:s}=this.#n;if(this.anchor){const e=new ResizeObserver(()=>{this.set({width:this.anchor.offsetWidth,height:this.anchor.offsetHeight,radius:parseFloat(getComputedStyle(this.anchor).borderTopLeftRadius)||0})});e.observe(this.anchor),this.#r.push(e)}if(this.button){const e=this.anchor,i=()=>this.pressed.target=0;e.addEventListener("pointerenter",()=>this.hovered.target=1,{signal:s}),e.addEventListener("pointerleave",()=>{this.hovered.target=0,i()},{signal:s}),e.addEventListener("pointerdown",()=>this.pressed.target=1,{signal:s}),e.addEventListener("pointerup",i,{signal:s}),e.addEventListener("pointercancel",i,{signal:s}),e.addEventListener("keydown",n=>{(n.key===" "||n.key==="Enter")&&(this.pressed.target=1)},{signal:s}),e.addEventListener("keyup",i,{signal:s}),e.addEventListener("blur",i,{signal:s})}}#d(){const{signal:t}=this.#n,s=()=>{this.copyDirty=!0,this.#e&&this.#s()},e=new MutationObserver(i=>{const n=r=>!lt(r.target)&&!Rt(r.target);i.some(n)&&s()});e.observe(this.source,{subtree:!0,childList:!0,attributes:!0,characterData:!0}),this.#r.push(e),this.source.addEventListener("input",s,{capture:!0,signal:t}),this.source.addEventListener("scroll",i=>{lt(i.target)||(this.scrolled.add(i.target),s())},{capture:!0,signal:t}),addEventListener("resize",s,{signal:t}),addEventListener("scroll",()=>{this.#e&&(this.rectDirty=!0,this.#s())},{passive:!0,signal:t})}#f(){const t=this.source.cloneNode(!0);t.dataset.glassCopy="";const s="input, textarea, select, canvas",e=t.querySelectorAll(s);this.source.querySelectorAll(s).forEach((r,h)=>{const a=e[h];"value"in r&&r.type!=="file"&&(a.value=r.value),"checked"in r&&(a.checked=r.checked),r instanceof HTMLCanvasElement&&a.getContext("2d")?.drawImage(r,0,0)});const i=[];for(const r of this.scrolled)this.source.contains(r)?i.push([tt(t,Z(this.source,r)),r.scrollLeft,r.scrollTop]):this.scrolled.delete(r);this.anchor&&this.source.contains(this.anchor)?tt(t,Z(this.source,this.anchor))?.remove():this.source.contains(this.el)&&tt(t,Z(this.source,this.el))?.remove();for(const r of t.querySelectorAll("[data-glass]"))for(const h of r.querySelectorAll("[data-glass]"))h.remove();t.removeAttribute("id");for(const r of t.querySelectorAll("[id]"))r.removeAttribute("id");const n=this.source.getBoundingClientRect();this.sourceRect=n,this.containerRect=this.container?.getBoundingClientRect(),Object.assign(t.style,{position:"absolute",inset:"auto",left:"0",top:"0",margin:"0",transform:"none",translate:"none",rotate:"none",scale:"none",width:`${n.width}px`,height:`${n.height}px`}),this.copyHost.replaceChildren(t);for(const[r,h,a]of i)r?.scrollTo(h,a);this.copyDirty=!1}#u(){const{width:t,height:s,radius:e,bevel:i,refraction:n,frost:r,chroma:h,zoom:a,specular:o,fresnel:c,tint:u,rim:x}=this.params,m=this.params.body??1,p=Math.min(devicePixelRatio||1,2),g=t/2,b=s/2,G=Math.min(e,g,b),O=Math.max(Math.min(i,g,b),1),q=(d,f)=>at(d,f,g,b,G),it=(d,f,v)=>{const y=q(d+v,f)-q(d-v,f),R=q(d,f+v)-q(d,f-v),A=Math.hypot(y,R)||1;return[y/A,R/A]},w=a,F=g/w,B=b/w,mt=O/w,Y=n/w,X=(d,f)=>at(d,f,F,B,G/w),S=p*Math.max(w,1),M=Math.ceil(r*1.5/w);this.inner={x:F+M,y:B+M};const nt=Y*2+Math.sign(Y||1),rt=(this.params.falloff??0)/w,$=document.createElement("canvas");$.width=Math.ceil((F+M)*2*S),$.height=Math.ceil((B+M)*2*S);const yt=$.getContext("2d"),U=yt.createImageData($.width,$.height);for(let d=0;d<$.height;d++)for(let f=0;f<$.width;f++){const v=(f+.5)/S-F-M,y=(d+.5)/S-B-M;let R=0,A=0;const L=X(v,y),I=rt>0;if(L<0&&(I?L>-rt*6:L>-mt)&&n!==0){let D;if(I)D=Math.exp(L/rt)*Y;else{const J=(1+L/mt)*Q;D=(1-Math.sqrt(1-J*J))/Ct*Y}const N=X(v+.5/S,y)-X(v-.5/S,y),j=X(v,y+.5/S)-X(v,y-.5/S),K=Math.hypot(N,j)||1;R=N/K*D,A=j/K*D}const C=(d*$.width+f)*4;U.data[C]=(.5-R/nt)*255,U.data[C+1]=(.5-A/nt)*255,U.data[C+3]=255}yt.putImageData(U,0,0);const k=this.rim;k.width=Math.ceil(t*p),k.height=Math.ceil(s*p);const bt=k.getContext("2d"),W=bt.createImageData(k.width,k.height),vt=(this.params.lightAngle??-123.5)*Math.PI/180,xt=Math.cos(vt),wt=Math.sin(vt),Mt=Math.sqrt(1-.5**2),At=[xt*Mt,wt*Mt,.5+1],Dt=Math.hypot(...At),[Ht,Ft,Bt]=At.map(d=>d/Dt);for(let d=0;d<k.height;d++){const f=(d+.5)/p-b,v=(1-(f+b)/s)*.05;for(let y=0;y<k.width;y++){const R=(y+.5)/p-g,A=q(R,f);if(A>0)continue;let L=(.06+v)*m;if(A>-O){const C=1+A/O,[D,N]=it(R,f,1/p),j=D*xt+N*wt,K=C**3*(.55*Math.max(j,0)+.22*Math.max(-j,0)),J=(1-Math.min(-A/1.5,1))*.35;L+=(K+J)*x;const $t=Math.sqrt(1-C*C),Gt=Math.max(D*C*Ht+N*C*Ft+$t*Bt,0)**48;L+=o*Gt*1.4+c*(1-$t)**3*.5}const I=(d*k.width+y)*4;W.data[I]=W.data[I+1]=W.data[I+2]=255,W.data[I+3]=Math.min(L,1)*255}}bt.putImageData(W,0,0),k.style.background=u;const Pt=$.toDataURL(),ot=h*.35,V=(d,f)=>`<feDisplacementMap in="SourceGraphic" in2="map" scale="${nt*d}" xChannelSelector="R" yChannelSelector="G" result="${f}" />`;this.filterBase=`<feImage href="${Pt}" result="map" preserveAspectRatio="none" />`+(ot>.001?V(1+ot,"dr")+V(1,"dg")+V(1-ot,"db")+`<feColorMatrix in="dr" values="1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" result="r" />
9
11
  <feColorMatrix in="dg" values="0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0" result="g" />
10
12
  <feColorMatrix in="db" values="0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0" result="b" />
11
13
  <feComposite in="r" in2="g" operator="arithmetic" k2="1" k3="1" result="rg" />
12
- <feComposite in="rg" in2="b" operator="arithmetic" k2="1" k3="1" />`:V(1,"out")),this.#l(),Object.assign(this.frost.style,{left:`${g-H-M}px`,top:`${b-B-M}px`,width:`${(H+M)*2}px`,height:`${(B+M)*2}px`,transform:w===1?"":`scale(${w})`}),this.#h(),this.anchor||(this.el.style.width=`${t}px`,this.el.style.height=`${s}px`,this.el.style.borderRadius=`${G}px`),this.shapeDirty=!1}#g(){const{brightness:t,saturation:s}=this.params;if(!t&&!s)return"";const e=1+s;return`<feColorMatrix type="matrix" values="${[0,1,2].map(n=>[...Lt.map((r,h)=>r*(1-e)+(n===h?e:0)),0,t].map(r=>+r.toFixed(4)).join(" ")).join(" ")} 0 0 0 1 0" />`}#l(){this.filter.innerHTML=this.filterBase+this.#g(),this.displacers=[...this.filter.querySelectorAll("feDisplacementMap")].map(t=>[t,Number(t.getAttribute("scale"))])}#h(){const{frost:t}=this.params,s=t>0?[`blur(${t/2/this.params.zoom}px)`]:[];if(this.button){const e=this.hovered.value,i=this.pressed.value;for(const[n,r]of this.displacers)n.setAttribute("scale",r*(1-.6*i));s.push(`brightness(${1+.12*e-.04*i})`),this.el.style.boxShadow=`0 ${12-8*i}px ${34-18*i}px -6px rgb(0 0 0 / ${.35+.15*i})`}this.frost.style.filter=s.join(" ")||"none"}#m(){const{width:t,height:s}=this.params,e=this.sourceRect,i=devicePixelRatio||1,n=a=>Math.round(a*i)/i;let r,h;if(this.anchor)({left:r,top:h}=this.anchor.getBoundingClientRect());else{const a=this.scale.value*this.popScale;r=n(this.x.value-t/2),h=n(this.y.value-s/2),this.el.style.transform=`translate(${r}px, ${h}px) scale(${a*this.stretchX.value}, ${a*this.stretchY.value})`,this.container&&(r+=this.containerRect.left,h+=this.containerRect.top)}this.copyHost.style.left=`${n(this.inner.x+e.left-r-t/2)}px`,this.copyHost.style.top=`${n(this.inner.y+e.top-h-s/2)}px`}moveTo(t,s){if(this.container){const{left:e,top:i}=this.container.getBoundingClientRect();t-=e,s-=i}this.reducedMotion?(this.x.snap(t),this.y.snap(s)):(this.x.target=t,this.y.target=s),this.show(!0,{pop:!0})}press(t){this.scale.target=t?1.12:1,this.reducedMotion&&this.scale.snap(this.scale.target)}set(t){const s=Object.keys(t).every(e=>e==="brightness"||e==="saturation");Object.assign(this.params,t),s&&!this.shapeDirty&&this.filterBase?(this.#l(),this.#h()):(this.shapeDirty=!0,this.#s())}destroy(){if(!this.destroyed){this.destroyed=!0,cancelAnimationFrame(this.#i),this.#t=!1,this.#n.abort(),this.#r.forEach(t=>t.disconnect()),this.el.remove(),this.svg.remove();for(const[t,s,e]of this.#a.reverse())t.style[s]=e;this.scrolled.clear()}}show(t,{pop:s=!1}={}){if(t!==this.visible){if(this.visible=t,t&&(this.rectDirty=!0),this.#s(),this.el.getAnimations().forEach(e=>e.cancel()),!s||this.reducedMotion){this.pop=null,this.popScale=1,this.el.style.transition="opacity .3s",this.el.style.opacity=t?"1":"0";return}this.el.style.transition="none",this.pop={visible:t,start:performance.now(),burst:!t}}}#y(t){const{visible:s,start:e}=this.pop,i=t-e;let n,r;if(s){const h=Math.min(i/460,1);n=.6+.4*(1+2.2*(h-1)**3+1.2*(h-1)**2),r=Math.min(i/140,1)}else{const h=Math.min(i/110,1);n=1+.14*h*h,r=i<110?1:0,i>=110&&this.pop.burst&&(this.pop.burst=!1,this.#b())}this.popScale=n,this.el.style.opacity=String(r),i>=(s?460:110)&&(this.pop=null,this.popScale=1)}#b(){const t=this.el.getBoundingClientRect();if(!t.width)return;const s=t.left+t.width/2,e=t.top+t.height/2,i=this.container??document.body,n=this.container?this.container.getBoundingClientRect():{left:0,top:0},r=this.container?"absolute":"fixed",h=this.container?2:2147483e3,a=(c,u,x)=>{const m=document.createElement("div");m.setAttribute("aria-hidden","true"),m.style.cssText=`position:${r};pointer-events:none;z-index:${h};${c}`,i.append(m),m.animate(u,{fill:"forwards",...x}).finished.then(()=>m.remove())};a(`left:${t.left-n.left}px;top:${t.top-n.top}px;width:${t.width}px;height:${t.height}px;border-radius:${this.el.style.borderRadius};box-shadow:inset 0 0 0 1px rgb(255 255 255 / .8)`,[{opacity:.8,transform:"scale(1)"},{opacity:0,transform:"scale(1.18)"}],{duration:200,easing:"cubic-bezier(.2,.7,.3,1)"});const o=22;for(let c=0;c<o;c++){const u=c/o*Math.PI*2+(Math.random()-.5)*.5,x=Math.cos(u),m=Math.sin(u),p=3+Math.random()*4,g=18+Math.random()*46,b=14+Math.random()*22,G=s+x*t.width/2-n.left-p/2,O=e+m*t.height/2-n.top-p/2,q=`translate(${x*g*.75}px, ${m*g*.75}px)`,it=`translate(${x*g}px, ${m*g+b}px) scale(.4)`;a(`left:${G}px;top:${O}px;width:${p}px;height:${p}px;border-radius:50%;background:radial-gradient(circle at 35% 35%, rgb(255 255 255 / .95), rgb(255 255 255 / .35) 65%, rgb(255 255 255 / .1))`,[{opacity:1,transform:"translate(0, 0)",easing:"cubic-bezier(.1,.8,.3,1)"},{opacity:.9,transform:q,offset:.35,easing:"cubic-bezier(.4,0,.8,.6)"},{opacity:0,transform:it}],{duration:520+Math.random()*260})}}#c(t){const s=Math.min((t-this.last)/1e3,.03333333333333333);this.last=t;let e=!1;if(!this.anchor){if(!this.reducedMotion){const a=Math.max(Math.abs(this.x.velocity)-150,0),o=Math.max(Math.abs(this.y.velocity)-150,0),c=this.params.stretch??1;this.stretchX.target=1+c*(Math.min(a/5e3,.22)-Math.min(o/9e3,.1)),this.stretchY.target=1+c*(Math.min(o/5e3,.22)-Math.min(a/9e3,.1))}e=this.springs.some(a=>!a.settled);for(const a of this.springs)a.step(s),a.settled&&a.snap(a.target);this.pop&&(this.#y(t),e=!0)}if(this.button&&(!this.hovered.settled||!this.pressed.settled)){for(const a of[this.hovered,this.pressed])this.reducedMotion?a.snap(a.target):a.step(s);this.#h()}const i=this.copyDirty&&this.#e&&t-this.syncedAt>=this.throttle,n=i||this.shapeDirty||this.rectDirty;i?(this.#f(),this.syncedAt=t):this.rectDirty&&(this.sourceRect=this.source.getBoundingClientRect(),this.containerRect=this.container?.getBoundingClientRect()),this.rectDirty=!1,this.shapeDirty&&this.params.width>=1&&this.params.height>=1&&this.#u(),this.inner&&this.sourceRect&&(e||n||this.placeDirty)&&this.#m(),this.placeDirty=e;const r=this.button&&(!this.hovered.settled||!this.pressed.settled),h=this.shapeDirty||this.rectDirty||this.placeDirty||this.copyDirty&&this.#e||!this.inner;e||r||h?this.#i=requestAnimationFrame(a=>this.#c(a)):this.#t=!1}}const T=(l,t,s)=>Math.min(Math.max(l,t),s),ct={stiffness:632,damping:50.3},Rt={stiffness:241,damping:26.4},zt={stiffness:576,damping:36},pt=220,P=(l,t=Rt)=>{l.scale.stiffness=ct.stiffness,l.scale.damping=ct.damping,l.x.stiffness=l.y.stiffness=t.stiffness,l.x.damping=l.y.damping=t.damping},dt=l=>Math.abs(l.x.value-l.x.target)<=.5&&Math.abs(l.x.velocity)<=30,F={frost:1,fresnel:.15,saturation:0,tint:"",rim:.45,body:0,falloff:0,lightAngle:-123.5},_={switch:{dark:{...F,refraction:25,bevel:6,chroma:.21,zoom:1.12,specular:.25,brightness:.03},light:{...F,refraction:20,bevel:5,chroma:.12,zoom:1.1,specular:.55,brightness:0}},tabs:{dark:{...F,refraction:28,bevel:5,falloff:4.5,chroma:.1,zoom:1.1,specular:.25,brightness:.03,lightAngle:-140},light:{...F,refraction:28,bevel:5,falloff:4.5,chroma:.1,zoom:1.1,specular:.55,brightness:0,lightAngle:-140}},button:{dark:{...F,refraction:28,bevel:5,chroma:.12,zoom:1.08,specular:.25,brightness:.03,rim:.6,body:.12},light:{...F,refraction:28,bevel:5,chroma:.12,zoom:1.08,specular:.55,brightness:0,rim:.6,body:.12}}},st=l=>(t,s,e)=>t.addEventListener(s,e,{signal:l}),et=l=>l.closest("[data-theme]")?.dataset.theme==="light"?"light":"dark";function Tt(l){const t=document.createElement("span");return t.className="lg-glow",l.el.append(t),s=>{const e=l.el.getBoundingClientRect(),i=e.width/l.el.offsetWidth;t.style.left=`${(s.clientX-e.left)/i}px`,t.style.top=`${(s.clientY-e.top)/i}px`}}class ft{constructor(t,{grow:s=1.55,press:e=1.08,lean:i=4,look:n=_.switch[et(t)]}={}){this.root=t,this.grow=s,this.press=e,this.lean=i,this.look=n,this.thumb=t.querySelector(":scope > .thumb"),this.bar=t.querySelector(":scope > .refract"),this.options=[...t.querySelectorAll(":scope > [data-opt]")],this.isSwitch=t.getAttribute("role")==="switch",this.value=this.isSwitch?+(t.getAttribute("aria-checked")==="true"):Math.max(0,this.options.findIndex(r=>r.getAttribute("aria-checked")==="true")),this.up=!1,this.hovered=!1,this.pressed=!1,this.drag=null,this.frame=0,this.abort=new AbortController,this.lens=new C(this.bar,{container:t,...n}),P(this.lens),this.glowAt=Tt(this.lens),t.classList.add("glassy"),this.size(),this.lens.scale.snap(1/(s*e)),this.put(this.centre(this.value)),this.#t()}centre(t){const s=this.options[t];return s.offsetLeft+s.offsetWidth/2}put(t){this.at=t,this.thumb.style.translate=`${t-this.thumb.offsetLeft-this.thumb.offsetWidth/2}px`,this.root.style.setProperty("--p",T((t-this.centre(0))/(this.centre(1)-this.centre(0)),0,1))}size(){const t=this.thumb.offsetWidth,s=this.thumb.offsetHeight,e=s*this.grow,i=t*this.grow,n=Math.min(i,(i+t+e-s)/2);this.shift=(i-n)/2,this.lens.set({width:n*this.press,height:e*this.press,radius:e*this.press/2});const r=e-this.bar.offsetHeight*this.look.zoom,h=(n-r)/2/this.look.zoom,a=this.lensAt(this.centre(0))-h;this.bar.style.left=`${a}px`,this.bar.style.width=`${this.lensAt(this.centre(1))+h-a}px`}lensAt(t){const s=this.centre(0),e=this.centre(1)-s;return t+this.shift*(2*T((t-s)/e,0,1)-1)}thumbAt(t){const s=this.centre(0),e=2*this.shift/(this.centre(1)-s);return T((t+this.shift+e*s)/(1+e),s,this.centre(1))}select(t){t!==this.value&&(this.value=t,this.isSwitch?this.root.setAttribute("aria-checked",String(t===1)):this.options.forEach((s,e)=>s.setAttribute("aria-checked",String(t===e))),this.lift(),this.lens.x.target=this.lensAt(this.centre(t)),this.root.dispatchEvent(new CustomEvent("change",{detail:{value:t},bubbles:!0})))}lift(){const t=this.lens;this.up||(this.up=!0,t.x.snap(this.lensAt(this.at)),t.y.snap(this.thumb.offsetTop+this.thumb.offsetHeight/2),t.show(!0),t.el.style.transition="opacity 0.18s",this.root.classList.add("lifted")),t.scale.target=this.pressed?1:1/this.press,cancelAnimationFrame(this.frame);let s=0;const e=i=>{this.put(this.thumbAt(t.x.value)),this.drag||this.hovered||this.pressed||!dt(t)?s=0:s||(s=i),s&&i-s>pt?this.drop():this.frame=requestAnimationFrame(e)};this.frame=requestAnimationFrame(e)}drop(){this.up&&(this.up=!1,this.lens.scale.target=1/(this.grow*this.press),this.lens.show(!1),this.lens.el.style.transition="opacity 0.3s 0.08s",this.root.classList.remove("lifted"))}setPressed(t,s){this.pressed=t,this.root.classList.toggle("pressing",t),t&&this.glowAt(s),(t||this.up)&&this.lift()}destroy(){cancelAnimationFrame(this.frame),this.abort.abort(),this.lens.destroy(),this.root.classList.remove("glassy","lifted","pressing"),this.root.style.removeProperty("--p"),this.thumb.style.translate="",this.bar.style.left=this.bar.style.width=""}#t(){const t=this.root,s=st(this.abort.signal);let e=!1;s(t,"click",()=>{e?e=!1:this.select(1-this.value)}),s(t,"pointerenter",i=>{i.pointerType==="mouse"&&(this.hovered=!0,this.lift())}),s(t,"pointerleave",()=>{this.hovered=!1,this.drag?this.drag.moved||(this.drag=null,this.setPressed(!1)):this.lens.x.target=this.lensAt(this.centre(this.value))}),s(t,"pointerdown",i=>{i.button===0&&(this.drag={id:i.pointerId,x:i.clientX,from:this.at,moved:!1},this.setPressed(!0,i))}),s(t,"pointermove",i=>{const n=this.drag,r=this.lens;if(this.pressed&&this.glowAt(i),!n){if(!this.hovered)return;const a=this.centre(this.value),o=i.clientX-t.getBoundingClientRect().left-t.clientLeft,c=this.options[this.value].offsetWidth/2;r.x.target=this.lensAt(a)+T((o-a)/c,-1,1)*this.lean;return}const h=i.clientX-n.x;!n.moved&&Math.abs(h)>4&&(n.moved=!0,t.setPointerCapture(n.id)),n.moved&&(r.x.target=this.lensAt(T(n.from+h,this.centre(0),this.centre(1))))}),s(t,"pointerup",()=>{const i=this.drag;if(this.drag=null,this.setPressed(!1),!i?.moved)return;e=!0,setTimeout(()=>e=!1);const n=this.lens,h=this.thumbAt(n.x.value+n.x.velocity*.12)>(this.centre(0)+this.centre(1))/2?1:0;h===this.value?(this.lift(),n.x.target=this.lensAt(this.centre(h))):this.select(h)}),s(t,"pointercancel",()=>{this.drag=null,this.setPressed(!1),this.lens.x.target=this.lensAt(this.centre(this.value))}),s(window,"resize",()=>{this.size(),this.up||this.put(this.centre(this.value))})}}class ut{constructor(t,{grow:s=1.3,press:e=1.1,look:i=_.tabs[et(t)]}={}){this.root=t,this.grow=s,this.press=e,this.tabs=[...t.querySelectorAll(":scope > a, :scope > button")],this.pill=t.querySelector(":scope > .lg-pill"),this.glow=t.querySelector(".lg-glow"),this.pressedId=null,this.frame=0,this.abort=new AbortController,this.lens=new C(t,{container:t,...i}),P(this.lens),this.placePill(),this.#t()}get current(){return this.tabs.find(t=>t.hasAttribute("aria-current"))??null}centre(t){return t.offsetLeft+t.offsetWidth/2}choose(t){t!==this.current&&(this.tabs.forEach(s=>s.removeAttribute("aria-current")),t.setAttribute("aria-current","page"),this.placePill(),this.root.dispatchEvent(new CustomEvent("change",{detail:{tab:t},bubbles:!0})))}placePill(){const t=this.current;this.pill.hidden=!t,t&&Object.assign(this.pill.style,{left:`${t.offsetLeft}px`,top:`${t.offsetTop}px`,width:`${t.offsetWidth}px`,height:`${t.offsetHeight}px`})}lift(t){const s=this.lens;cancelAnimationFrame(this.frame);const e=(this.root.offsetHeight+8)*this.press;s.set({width:(t.offsetWidth+16)*this.press,height:e,radius:e/2});const i=this.centre(t),n=t.offsetTop+t.offsetHeight/2;s.visible||(s.x.snap(i),s.y.snap(n),s.scale.snap(1/(this.grow*this.press)),s.show(!0),s.el.style.transition="opacity 0.18s",this.root.classList.add("lensing")),this.bulge(),s.x.target=i,s.y.target=n}bulge(){this.lens.scale.target=this.pressedId===null?1/this.press:1}drop(){this.root.classList.remove("lensing"),this.lens.scale.target=1/(this.grow*this.press),this.lens.show(!1),this.lens.el.style.transition="opacity 0.3s 0.08s"}settle(t){const s=this.lens;cancelAnimationFrame(this.frame);let e=0;const i=n=>{dt(s)?e||(e=n):e=0,e&&n-e>t?this.drop():this.frame=requestAnimationFrame(i)};this.frame=requestAnimationFrame(i)}destroy(){cancelAnimationFrame(this.frame),this.abort.abort(),this.lens.destroy(),this.root.classList.remove("lensing","pressing")}#t(){const t=this.root,s=st(this.abort.signal),e=o=>o.clientX-t.getBoundingClientRect().left-t.clientLeft,i=o=>T(o,this.centre(this.tabs[0]),this.centre(this.tabs.at(-1))),n=o=>this.tabs.reduce((c,u)=>Math.abs(this.centre(u)-o)<Math.abs(this.centre(c)-o)?u:c),r=o=>{const c=t.getBoundingClientRect();this.glow.style.left=`${o.clientX-c.left}px`,this.glow.style.top=`${o.clientY-c.top}px`};this.tabs.forEach(o=>s(o,"click",()=>this.choose(o))),s(t,"pointerover",o=>{const c=o.target.closest("a, button");o.pointerType==="mouse"&&this.pressedId===null&&c&&this.lift(c)}),s(t,"pointerleave",o=>{o.pointerType==="mouse"&&this.pressedId===null&&this.settle(0)});let h=null;s(t,"pointerdown",o=>{if(o.button!==0)return;o.preventDefault(),t.setPointerCapture(o.pointerId),this.pressedId=o.pointerId;const c=o.target.closest("a, button")??n(e(o));h={x:o.clientX,tab:c,dragging:!1},this.lift(c),P(this.lens,zt),r(o),t.classList.add("pressing")}),s(t,"pointermove",o=>{if(o.pointerId!==this.pressedId)return;r(o);const c=o.clientX-h.x;!h.dragging&&Math.abs(c)>6&&(h.dragging=!0),h.dragging&&(this.lens.x.target=i(this.centre(h.tab)+c))});const a=(o,c)=>{if(o.pointerId!==this.pressedId)return;if(this.pressedId=null,t.classList.remove("pressing"),P(this.lens),this.bulge(),!c)return this.settle(0);const u=h.dragging?n(this.lens.x.target):h.tab;this.lens.x.target=this.centre(u),u.click(),o.pointerType==="mouse"&&t.matches(":hover")||this.settle(pt)};s(t,"pointerup",o=>a(o,!0)),s(t,"pointercancel",o=>a(o,!1)),s(window,"resize",()=>this.placePill())}}class gt{constructor(t,{container:s=t.parentElement,source:e=s.querySelector(":scope > [data-glass-source]"),grow:i=1.1,press:n=1.08,lit:r=.95,hang:h=120,look:a=_.button[et(t)]}={}){if(!e)throw new Error("GlassButton: no source to refract. Give the button's parent a [data-glass-source] child, or pass { source }.");this.btn=t,this.container=s,this.grow=i,this.press=n,this.hang=h,this.hovered=!1,this.pressed=!1,this.up=!1,this.frame=0,this.abort=new AbortController;const o=st(this.abort.signal);t.style.setProperty("--lit-max",r);const c=document.createElement("span");c.className="lg-clip",c.setAttribute("aria-hidden","true");const u=document.createElement("span");u.className="lg-glow",c.append(u),t.prepend(c),this.clip=c,this.lens=new C(e,{container:s,...a}),this.lens.el.classList.add("lg-button-lens"),P(this.lens),this.size(),this.lens.scale.snap(1/this.full),this.lens.show(!0),this.track();const x=p=>{const g=t.getBoundingClientRect(),b=g.width/t.offsetWidth;u.style.left=`${(p.clientX-g.left)/b}px`,u.style.top=`${(p.clientY-g.top)/b}px`};o(t,"pointerenter",p=>{p.pointerType==="mouse"&&(this.hovered=!0,x(p),t.classList.add("hovering"),this.lift())}),o(t,"pointerleave",()=>{this.hovered=!1,t.classList.remove("hovering"),this.pressed=!1,this.lift(),this.settle()}),o(t,"pointerdown",p=>{p.button===0&&(this.pressed=!0,x(p),this.lift())}),o(t,"pointermove",p=>(this.pressed||this.hovered)&&x(p));const m=()=>{this.pressed&&(this.pressed=!1,this.lift(),this.settle())};o(t,"pointerup",m),o(t,"pointercancel",m),o(t,"focus",()=>t.matches(":focus-visible")&&this.lift()),o(t,"blur",()=>this.settle(0)),o(t,"keydown",p=>{p.key!==" "&&p.key!=="Enter"||(this.pressed=!0,this.lift())}),o(t,"keyup",()=>{this.pressed=!1,this.lift()}),o(window,"resize",()=>this.size())}destroy(){cancelAnimationFrame(this.frame),cancelAnimationFrame(this.settling),this.abort.abort(),this.lens.destroy(),this.clip.remove(),this.btn.classList.remove("hovering","pressing");for(const t of["--lit-max","--s","--lit"])this.btn.style.removeProperty(t)}get full(){return this.grow*this.press}size(){const t=this.btn.getBoundingClientRect(),s=this.container.getBoundingClientRect(),e=this.btn.offsetWidth*this.full,i=this.btn.offsetHeight*this.full;this.lens.set({width:e,height:i,radius:i/2}),this.lens.x.snap(t.left+t.width/2-s.left-this.container.clientLeft),this.lens.y.snap(t.top+t.height/2-s.top-this.container.clientTop)}lift(){cancelAnimationFrame(this.settling),this.up=!0,this.lens.scale.target=this.pressed?1:1/this.press,this.btn.classList.toggle("pressing",this.pressed),this.track()}settle(t=this.hang){if(!this.up||this.hovered||this.pressed||this.btn.matches(":focus-visible"))return;cancelAnimationFrame(this.settling);const s=performance.now(),e=i=>{this.hovered||this.pressed||(i-s<t?this.settling=requestAnimationFrame(e):(this.up=!1,this.lens.scale.target=1/this.full,this.track()))};this.settling=requestAnimationFrame(e)}track(){cancelAnimationFrame(this.frame);const t=()=>{const s=this.lens.scale.value*this.full;this.btn.style.setProperty("--s",s.toFixed(4)),this.btn.style.setProperty("--lit",T((s-1)/(this.grow-1||1),0,1).toFixed(4)),this.lens.scale.settled||(this.frame=requestAnimationFrame(t))};this.frame=requestAnimationFrame(t)}}function qt(l=document){return[...[...l.querySelectorAll("[data-glass-switch]")].map(t=>new ft(t)),...[...l.querySelectorAll("[data-glass-tabs]")].map(t=>new ut(t)),...[...l.querySelectorAll("[data-glass-button]")].map(t=>new gt(t))]}return Object.assign(C,{LiquidGlass:C,GlassSwitch:ft,GlassTabBar:ut,GlassButton:gt,LOOKS:_,initComponents:qt}),C})();
14
+ <feComposite in="rg" in2="b" operator="arithmetic" k2="1" k3="1" />`:V(1,"out")),this.#l(),Object.assign(this.frost.style,{left:`${g-F-M}px`,top:`${b-B-M}px`,width:`${(F+M)*2}px`,height:`${(B+M)*2}px`,transform:w===1?"":`scale(${w})`}),this.#h(),this.anchor||(this.el.style.width=`${t}px`,this.el.style.height=`${s}px`,this.el.style.borderRadius=`${G}px`),this.shapeDirty=!1}#g(){const{brightness:t,saturation:s}=this.params;if(!t&&!s)return"";const e=1+s;return`<feColorMatrix type="matrix" values="${[0,1,2].map(n=>[...Lt.map((r,h)=>r*(1-e)+(n===h?e:0)),0,t].map(r=>+r.toFixed(4)).join(" ")).join(" ")} 0 0 0 1 0" />`}#l(){this.filter.innerHTML=this.filterBase+this.#g(),this.displacers=[...this.filter.querySelectorAll("feDisplacementMap")].map(t=>[t,Number(t.getAttribute("scale"))])}#h(){const{frost:t}=this.params,s=t>0?[`blur(${t/2/this.params.zoom}px)`]:[];if(this.button){const e=this.hovered.value,i=this.pressed.value;for(const[n,r]of this.displacers)n.setAttribute("scale",r*(1-.6*i));s.push(`brightness(${1+.12*e-.04*i})`),this.el.style.boxShadow=`0 ${12-8*i}px ${34-18*i}px -6px rgb(0 0 0 / ${.35+.15*i})`}this.frost.style.filter=s.join(" ")||"none"}#m(){const{width:t,height:s}=this.params,e=this.sourceRect,i=devicePixelRatio||1,n=a=>Math.round(a*i)/i;let r,h;if(this.anchor)({left:r,top:h}=this.anchor.getBoundingClientRect());else{const a=this.scale.value*this.popScale;r=n(this.x.value-t/2),h=n(this.y.value-s/2),this.el.style.transform=`translate(${r}px, ${h}px) scale(${a*this.stretchX.value}, ${a*this.stretchY.value})`,this.container&&(r+=this.containerRect.left,h+=this.containerRect.top)}this.copyHost.style.left=`${n(this.inner.x+e.left-r-t/2)}px`,this.copyHost.style.top=`${n(this.inner.y+e.top-h-s/2)}px`}moveTo(t,s){if(this.container){const{left:e,top:i}=this.container.getBoundingClientRect();t-=e,s-=i}this.reducedMotion?(this.x.snap(t),this.y.snap(s)):(this.x.target=t,this.y.target=s),this.show(!0,{pop:!0})}press(t){this.scale.target=t?1.12:1,this.reducedMotion&&this.scale.snap(this.scale.target)}set(t){const s=Object.keys(t).every(e=>e==="brightness"||e==="saturation");Object.assign(this.params,t),s&&!this.shapeDirty&&this.filterBase?(this.#l(),this.#h()):(this.shapeDirty=!0,this.#s())}destroy(){if(!this.destroyed){this.destroyed=!0,cancelAnimationFrame(this.#i),this.#t=!1,this.#n.abort(),this.#r.forEach(t=>t.disconnect()),this.el.remove(),this.svg.remove();for(const[t,s,e]of this.#a.reverse())t.style[s]=e;this.scrolled.clear()}}show(t,{pop:s=!1}={}){if(t!==this.visible){if(this.visible=t,t&&(this.rectDirty=!0),this.#s(),this.el.getAnimations().forEach(e=>e.cancel()),!s||this.reducedMotion){this.pop=null,this.popScale=1,this.el.style.transition="opacity .3s",this.el.style.opacity=t?"1":"0";return}this.el.style.transition="none",this.pop={visible:t,start:performance.now(),burst:!t}}}#y(t){const{visible:s,start:e}=this.pop,i=t-e;let n,r;if(s){const h=Math.min(i/460,1);n=.6+.4*(1+2.2*(h-1)**3+1.2*(h-1)**2),r=Math.min(i/140,1)}else{const h=Math.min(i/110,1);n=1+.14*h*h,r=i<110?1:0,i>=110&&this.pop.burst&&(this.pop.burst=!1,this.#b())}this.popScale=n,this.el.style.opacity=String(r),i>=(s?460:110)&&(this.pop=null,this.popScale=1)}#b(){const t=this.el.getBoundingClientRect();if(!t.width)return;const s=t.left+t.width/2,e=t.top+t.height/2,i=this.container??document.body,n=this.container?this.container.getBoundingClientRect():{left:0,top:0},r=this.container?"absolute":"fixed",h=this.container?2:2147483e3,a=(c,u,x)=>{const m=document.createElement("div");m.setAttribute("aria-hidden","true"),m.style.cssText=`position:${r};pointer-events:none;z-index:${h};${c}`,i.append(m),m.animate(u,{fill:"forwards",...x}).finished.then(()=>m.remove())};a(`left:${t.left-n.left}px;top:${t.top-n.top}px;width:${t.width}px;height:${t.height}px;border-radius:${this.el.style.borderRadius};box-shadow:inset 0 0 0 1px rgb(255 255 255 / .8)`,[{opacity:.8,transform:"scale(1)"},{opacity:0,transform:"scale(1.18)"}],{duration:200,easing:"cubic-bezier(.2,.7,.3,1)"});const o=22;for(let c=0;c<o;c++){const u=c/o*Math.PI*2+(Math.random()-.5)*.5,x=Math.cos(u),m=Math.sin(u),p=3+Math.random()*4,g=18+Math.random()*46,b=14+Math.random()*22,G=s+x*t.width/2-n.left-p/2,O=e+m*t.height/2-n.top-p/2,q=`translate(${x*g*.75}px, ${m*g*.75}px)`,it=`translate(${x*g}px, ${m*g+b}px) scale(.4)`;a(`left:${G}px;top:${O}px;width:${p}px;height:${p}px;border-radius:50%;background:radial-gradient(circle at 35% 35%, rgb(255 255 255 / .95), rgb(255 255 255 / .35) 65%, rgb(255 255 255 / .1))`,[{opacity:1,transform:"translate(0, 0)",easing:"cubic-bezier(.1,.8,.3,1)"},{opacity:.9,transform:q,offset:.35,easing:"cubic-bezier(.4,0,.8,.6)"},{opacity:0,transform:it}],{duration:520+Math.random()*260})}}#c(t){const s=Math.min((t-this.last)/1e3,.03333333333333333);this.last=t;let e=!1;if(!this.anchor){if(!this.reducedMotion){const a=Math.max(Math.abs(this.x.velocity)-150,0),o=Math.max(Math.abs(this.y.velocity)-150,0),c=this.params.stretch??1;this.stretchX.target=1+c*(Math.min(a/5e3,.22)-Math.min(o/9e3,.1)),this.stretchY.target=1+c*(Math.min(o/5e3,.22)-Math.min(a/9e3,.1))}e=this.springs.some(a=>!a.settled);for(const a of this.springs)a.step(s),a.settled&&a.snap(a.target);this.pop&&(this.#y(t),e=!0)}if(this.button&&(!this.hovered.settled||!this.pressed.settled)){for(const a of[this.hovered,this.pressed])this.reducedMotion?a.snap(a.target):a.step(s);this.#h()}const i=this.copyDirty&&this.#e&&t-this.syncedAt>=this.throttle,n=i||this.shapeDirty||this.rectDirty;i?(this.#f(),this.syncedAt=t):this.rectDirty&&(this.sourceRect=this.source.getBoundingClientRect(),this.containerRect=this.container?.getBoundingClientRect()),this.rectDirty=!1,this.shapeDirty&&this.params.width>=1&&this.params.height>=1&&this.#u(),this.inner&&this.sourceRect&&(e||n||this.placeDirty)&&this.#m(),this.placeDirty=e;const r=this.button&&(!this.hovered.settled||!this.pressed.settled),h=this.shapeDirty||this.rectDirty||this.placeDirty||this.copyDirty&&this.#e||!this.inner;e||r||h?this.#i=requestAnimationFrame(a=>this.#c(a)):(this.#t=!1,this.el.style.willChange="auto")}}const T=(l,t,s)=>Math.min(Math.max(l,t),s),ct={stiffness:632,damping:50.3},zt={stiffness:241,damping:26.4},Tt={stiffness:576,damping:36},pt=220,P=(l,t=zt)=>{l.scale.stiffness=ct.stiffness,l.scale.damping=ct.damping,l.x.stiffness=l.y.stiffness=t.stiffness,l.x.damping=l.y.damping=t.damping},dt=l=>Math.abs(l.x.value-l.x.target)<=.5&&Math.abs(l.x.velocity)<=30,H={frost:0,fresnel:.15,saturation:0,tint:"",rim:.45,body:0,falloff:0,lightAngle:-123.5},_={switch:{dark:{...H,refraction:25,bevel:6,chroma:.21,zoom:1.12,specular:.25,brightness:.03},light:{...H,refraction:20,bevel:5,chroma:.12,zoom:1.1,specular:.55,brightness:0}},tabs:{dark:{...H,refraction:28,bevel:5,falloff:4.5,chroma:.1,zoom:1.1,specular:.25,brightness:.03,lightAngle:-140},light:{...H,refraction:28,bevel:5,falloff:4.5,chroma:.1,zoom:1.1,specular:.55,brightness:0,lightAngle:-140}},button:{dark:{...H,frost:1,refraction:28,bevel:5,chroma:.12,zoom:1.08,specular:.25,brightness:.03,rim:.6,body:.12},light:{...H,frost:1,refraction:28,bevel:5,chroma:.12,zoom:1.08,specular:.55,brightness:0,rim:.6,body:.12}}},st=l=>(t,s,e)=>t.addEventListener(s,e,{signal:l}),et=l=>l.closest("[data-theme]")?.dataset.theme==="light"?"light":"dark";function qt(l){const t=document.createElement("span");return t.className="lg-glow",l.el.append(t),s=>{const e=l.el.getBoundingClientRect(),i=e.width/l.el.offsetWidth;t.style.left=`${(s.clientX-e.left)/i}px`,t.style.top=`${(s.clientY-e.top)/i}px`}}class ft{constructor(t,{grow:s=1.55,press:e=1.08,lean:i=4,look:n=_.switch[et(t)]}={}){this.root=t,this.grow=s,this.press=e,this.lean=i,this.look=n,this.thumb=t.querySelector(":scope > .thumb"),this.bar=t.querySelector(":scope > .refract"),this.options=[...t.querySelectorAll(":scope > [data-opt]")],this.isSwitch=t.getAttribute("role")==="switch",this.value=this.isSwitch?+(t.getAttribute("aria-checked")==="true"):Math.max(0,this.options.findIndex(r=>r.getAttribute("aria-checked")==="true")),this.up=!1,this.hovered=!1,this.pressed=!1,this.drag=null,this.frame=0,this.abort=new AbortController,this.lens=new E(this.bar,{container:t,...n}),P(this.lens),this.glowAt=qt(this.lens),t.classList.add("glassy"),this.size(),this.lens.scale.snap(1/(s*e)),this.put(this.centre(this.value)),this.#t()}centre(t){const s=this.options[t];return s.offsetLeft+s.offsetWidth/2}put(t){this.at=t,this.thumb.style.translate=`${t-this.thumb.offsetLeft-this.thumb.offsetWidth/2}px`,this.root.style.setProperty("--p",T((t-this.centre(0))/(this.centre(1)-this.centre(0)),0,1))}size(){const t=this.thumb.offsetWidth,s=this.thumb.offsetHeight,e=s*this.grow,i=t*this.grow,n=Math.min(i,(i+t+e-s)/2);this.shift=(i-n)/2,this.lens.set({width:n*this.press,height:e*this.press,radius:e*this.press/2});const r=e-this.bar.offsetHeight*this.look.zoom,h=(n-r)/2/this.look.zoom,a=this.lensAt(this.centre(0))-h;this.bar.style.left=`${a}px`,this.bar.style.width=`${this.lensAt(this.centre(1))+h-a}px`}lensAt(t){const s=this.centre(0),e=this.centre(1)-s;return t+this.shift*(2*T((t-s)/e,0,1)-1)}thumbAt(t){const s=this.centre(0),e=2*this.shift/(this.centre(1)-s);return T((t+this.shift+e*s)/(1+e),s,this.centre(1))}select(t){t!==this.value&&(this.value=t,this.isSwitch?this.root.setAttribute("aria-checked",String(t===1)):this.options.forEach((s,e)=>s.setAttribute("aria-checked",String(t===e))),this.lift(),this.lens.x.target=this.lensAt(this.centre(t)),this.root.dispatchEvent(new CustomEvent("change",{detail:{value:t},bubbles:!0})))}lift(){const t=this.lens;this.up||(this.up=!0,t.x.snap(this.lensAt(this.at)),t.y.snap(this.thumb.offsetTop+this.thumb.offsetHeight/2),t.show(!0),t.el.style.transition="opacity 0.18s",this.root.classList.add("lifted")),t.scale.target=this.pressed?1:1/this.press,cancelAnimationFrame(this.frame);let s=0;const e=i=>{this.put(this.thumbAt(t.x.value)),this.drag||this.hovered||this.pressed||!dt(t)?s=0:s||(s=i),s&&i-s>pt?this.drop():this.frame=requestAnimationFrame(e)};this.frame=requestAnimationFrame(e)}drop(){this.up&&(this.up=!1,this.lens.scale.target=1/(this.grow*this.press),this.lens.show(!1),this.lens.el.style.transition="opacity 0.3s 0.08s",this.root.classList.remove("lifted"))}setPressed(t,s){this.pressed=t,this.root.classList.toggle("pressing",t),t&&this.glowAt(s),(t||this.up)&&this.lift()}destroy(){cancelAnimationFrame(this.frame),this.abort.abort(),this.lens.destroy(),this.root.classList.remove("glassy","lifted","pressing"),this.root.style.removeProperty("--p"),this.thumb.style.translate="",this.bar.style.left=this.bar.style.width=""}#t(){const t=this.root,s=st(this.abort.signal);let e=!1;s(t,"click",()=>{e?e=!1:this.select(1-this.value)}),s(t,"pointerenter",i=>{i.pointerType==="mouse"&&(this.hovered=!0,this.lift())}),s(t,"pointerleave",()=>{this.hovered=!1,this.drag?this.drag.moved||(this.drag=null,this.setPressed(!1)):this.lens.x.target=this.lensAt(this.centre(this.value))}),s(t,"pointerdown",i=>{i.button===0&&(this.drag={id:i.pointerId,x:i.clientX,from:this.at,moved:!1},this.setPressed(!0,i))}),s(t,"pointermove",i=>{const n=this.drag,r=this.lens;if(this.pressed&&this.glowAt(i),!n){if(!this.hovered)return;const a=this.centre(this.value),o=i.clientX-t.getBoundingClientRect().left-t.clientLeft,c=this.options[this.value].offsetWidth/2;r.x.target=this.lensAt(a)+T((o-a)/c,-1,1)*this.lean;return}const h=i.clientX-n.x;!n.moved&&Math.abs(h)>4&&(n.moved=!0,t.setPointerCapture(n.id)),n.moved&&(r.x.target=this.lensAt(T(n.from+h,this.centre(0),this.centre(1))))}),s(t,"pointerup",()=>{const i=this.drag;if(this.drag=null,this.setPressed(!1),!i?.moved)return;e=!0,setTimeout(()=>e=!1);const n=this.lens,h=this.thumbAt(n.x.value+n.x.velocity*.12)>(this.centre(0)+this.centre(1))/2?1:0;h===this.value?(this.lift(),n.x.target=this.lensAt(this.centre(h))):this.select(h)}),s(t,"pointercancel",()=>{this.drag=null,this.setPressed(!1),this.lens.x.target=this.lensAt(this.centre(this.value))}),s(window,"resize",()=>{this.size(),this.up||this.put(this.centre(this.value))})}}class ut{constructor(t,{grow:s=1.3,press:e=1.1,look:i=_.tabs[et(t)]}={}){this.root=t,this.grow=s,this.press=e,this.tabs=[...t.querySelectorAll(":scope > a, :scope > button")],this.pill=t.querySelector(":scope > .lg-pill"),this.glow=t.querySelector(".lg-glow"),this.pressedId=null,this.frame=0,this.abort=new AbortController,this.lens=new E(t,{container:t,...i}),P(this.lens),this.placePill(),this.#t()}get current(){return this.tabs.find(t=>t.hasAttribute("aria-current"))??null}centre(t){return t.offsetLeft+t.offsetWidth/2}choose(t){t!==this.current&&(this.tabs.forEach(s=>s.removeAttribute("aria-current")),t.setAttribute("aria-current","page"),this.placePill(),this.root.dispatchEvent(new CustomEvent("change",{detail:{tab:t},bubbles:!0})))}placePill(){const t=this.current;this.pill.hidden=!t,t&&Object.assign(this.pill.style,{left:`${t.offsetLeft}px`,top:`${t.offsetTop}px`,width:`${t.offsetWidth}px`,height:`${t.offsetHeight}px`})}lift(t){const s=this.lens;cancelAnimationFrame(this.frame);const e=(this.root.offsetHeight+8)*this.press;s.set({width:(t.offsetWidth+16)*this.press,height:e,radius:e/2});const i=this.centre(t),n=t.offsetTop+t.offsetHeight/2;s.visible||(s.x.snap(i),s.y.snap(n),s.scale.snap(1/(this.grow*this.press)),s.show(!0),s.el.style.transition="opacity 0.18s",this.root.classList.add("lensing")),this.bulge(),s.x.target=i,s.y.target=n}bulge(){this.lens.scale.target=this.pressedId===null?1/this.press:1}drop(){this.root.classList.remove("lensing"),this.lens.scale.target=1/(this.grow*this.press),this.lens.show(!1),this.lens.el.style.transition="opacity 0.3s 0.08s"}settle(t){const s=this.lens;cancelAnimationFrame(this.frame);let e=0;const i=n=>{dt(s)?e||(e=n):e=0,e&&n-e>t?this.drop():this.frame=requestAnimationFrame(i)};this.frame=requestAnimationFrame(i)}destroy(){cancelAnimationFrame(this.frame),this.abort.abort(),this.lens.destroy(),this.root.classList.remove("lensing","pressing")}#t(){const t=this.root,s=st(this.abort.signal),e=o=>o.clientX-t.getBoundingClientRect().left-t.clientLeft,i=o=>T(o,this.centre(this.tabs[0]),this.centre(this.tabs.at(-1))),n=o=>this.tabs.reduce((c,u)=>Math.abs(this.centre(u)-o)<Math.abs(this.centre(c)-o)?u:c),r=o=>{const c=t.getBoundingClientRect();this.glow.style.left=`${o.clientX-c.left}px`,this.glow.style.top=`${o.clientY-c.top}px`};this.tabs.forEach(o=>s(o,"click",()=>this.choose(o))),s(t,"pointerover",o=>{const c=o.target.closest("a, button");o.pointerType==="mouse"&&this.pressedId===null&&c&&this.lift(c)}),s(t,"pointerleave",o=>{o.pointerType==="mouse"&&this.pressedId===null&&this.settle(0)});let h=null;s(t,"pointerdown",o=>{if(o.button!==0)return;o.preventDefault(),t.setPointerCapture(o.pointerId),this.pressedId=o.pointerId;const c=o.target.closest("a, button")??n(e(o));h={x:o.clientX,tab:c,dragging:!1},this.lift(c),P(this.lens,Tt),r(o),t.classList.add("pressing")}),s(t,"pointermove",o=>{if(o.pointerId!==this.pressedId)return;r(o);const c=o.clientX-h.x;!h.dragging&&Math.abs(c)>6&&(h.dragging=!0),h.dragging&&(this.lens.x.target=i(this.centre(h.tab)+c))});const a=(o,c)=>{if(o.pointerId!==this.pressedId)return;if(this.pressedId=null,t.classList.remove("pressing"),P(this.lens),this.bulge(),!c)return this.settle(0);const u=h.dragging?n(this.lens.x.target):h.tab;this.lens.x.target=this.centre(u),u.click(),o.pointerType==="mouse"&&t.matches(":hover")||this.settle(pt)};s(t,"pointerup",o=>a(o,!0)),s(t,"pointercancel",o=>a(o,!1)),s(window,"resize",()=>this.placePill())}}class gt{constructor(t,{container:s=t.parentElement,source:e=s.querySelector(":scope > [data-glass-source]"),grow:i=1.1,press:n=1.08,lit:r=.95,hang:h=120,look:a=_.button[et(t)]}={}){if(!e)throw new Error("GlassButton: no source to refract. Give the button's parent a [data-glass-source] child, or pass { source }.");this.btn=t,this.container=s,this.grow=i,this.press=n,this.hang=h,this.hovered=!1,this.pressed=!1,this.up=!1,this.frame=0,this.abort=new AbortController;const o=st(this.abort.signal);t.style.setProperty("--lit-max",r);const c=document.createElement("span");c.className="lg-clip",c.setAttribute("aria-hidden","true");const u=document.createElement("span");u.className="lg-glow",c.append(u),t.prepend(c),this.clip=c,this.lens=new E(e,{container:s,...a}),this.lens.el.classList.add("lg-button-lens"),P(this.lens),this.size(),this.lens.scale.snap(1/this.full),this.lens.show(!0),this.track();const x=p=>{const g=t.getBoundingClientRect(),b=g.width/t.offsetWidth;u.style.left=`${(p.clientX-g.left)/b}px`,u.style.top=`${(p.clientY-g.top)/b}px`};o(t,"pointerenter",p=>{p.pointerType==="mouse"&&(this.hovered=!0,x(p),t.classList.add("hovering"),this.lift())}),o(t,"pointerleave",()=>{this.hovered=!1,t.classList.remove("hovering"),this.pressed=!1,this.lift(),this.settle()}),o(t,"pointerdown",p=>{p.button===0&&(this.pressed=!0,x(p),this.lift())}),o(t,"pointermove",p=>(this.pressed||this.hovered)&&x(p));const m=()=>{this.pressed&&(this.pressed=!1,this.lift(),this.settle())};o(t,"pointerup",m),o(t,"pointercancel",m),o(t,"focus",()=>t.matches(":focus-visible")&&this.lift()),o(t,"blur",()=>this.settle(0)),o(t,"keydown",p=>{p.key!==" "&&p.key!=="Enter"||(this.pressed=!0,this.lift())}),o(t,"keyup",()=>{this.pressed=!1,this.lift()}),o(window,"resize",()=>this.size())}destroy(){cancelAnimationFrame(this.frame),cancelAnimationFrame(this.settling),this.abort.abort(),this.lens.destroy(),this.clip.remove(),this.btn.classList.remove("hovering","pressing");for(const t of["--lit-max","--s","--lit"])this.btn.style.removeProperty(t)}get full(){return this.grow*this.press}size(){const t=this.btn.getBoundingClientRect(),s=this.container.getBoundingClientRect(),e=this.btn.offsetWidth*this.full,i=this.btn.offsetHeight*this.full;this.lens.set({width:e,height:i,radius:i/2}),this.lens.x.snap(t.left+t.width/2-s.left-this.container.clientLeft),this.lens.y.snap(t.top+t.height/2-s.top-this.container.clientTop)}lift(){cancelAnimationFrame(this.settling),this.up=!0,this.lens.scale.target=this.pressed?1:1/this.press,this.btn.classList.toggle("pressing",this.pressed),this.track()}settle(t=this.hang){if(!this.up||this.hovered||this.pressed||this.btn.matches(":focus-visible"))return;cancelAnimationFrame(this.settling);const s=performance.now(),e=i=>{this.hovered||this.pressed||(i-s<t?this.settling=requestAnimationFrame(e):(this.up=!1,this.lens.scale.target=1/this.full,this.track()))};this.settling=requestAnimationFrame(e)}track(){cancelAnimationFrame(this.frame);const t=()=>{const s=this.lens.scale.value*this.full;this.btn.style.setProperty("--s",s.toFixed(4)),this.btn.style.setProperty("--lit",T((s-1)/(this.grow-1||1),0,1).toFixed(4)),this.lens.scale.settled||(this.frame=requestAnimationFrame(t))};this.frame=requestAnimationFrame(t)}}function It(l=document){return[...[...l.querySelectorAll("[data-glass-switch]")].map(t=>new ft(t)),...[...l.querySelectorAll("[data-glass-tabs]")].map(t=>new ut(t)),...[...l.querySelectorAll("[data-glass-button]")].map(t=>new gt(t))]}return Object.assign(E,{LiquidGlass:E,GlassSwitch:ft,GlassTabBar:ut,GlassButton:gt,LOOKS:_,initComponents:It}),E})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dazecoop/liquid-glass",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "A liquid glass lens that refracts, frosts and splits the light of real HTML behind it, plus iOS 26 style switches, tab bars and buttons. Vanilla JS, with React bindings.",
5
5
  "keywords": [
6
6
  "liquid-glass",