@marcosdemik/liquidglass 2.0.3 → 2.0.5
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 +3 -1
- package/dist/index.cjs +31 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +30 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -71,6 +71,7 @@ The component includes a `"use client"` directive, so it works out of the box wi
|
|
|
71
71
|
| `intensity` | `number` | `0.7` | Refraction intensity at the glass edge (0-1) |
|
|
72
72
|
| `edgeSize` | `number` | `30` | Thickness of the glass edge refraction zone in pixels |
|
|
73
73
|
| `specularWidth` | `number` | `0.02` | Specular rim thickness relative to the smallest dimension (0-1) |
|
|
74
|
+
| `quality` | `number` | `2` | Supersampling multiplier for the displacement map (higher = smoother gradients) |
|
|
74
75
|
|
|
75
76
|
### Hover Animation
|
|
76
77
|
|
|
@@ -189,7 +190,7 @@ All standard `<button>` HTML attributes (`onClick`, `disabled`, `aria-label`, et
|
|
|
189
190
|
|
|
190
191
|
The effect is built from three layers:
|
|
191
192
|
|
|
192
|
-
1. **WebGL Displacement + Specular Maps** - A GLSL fragment shader computes a displacement map and a specular highlight map from a signed distance field (SDF) of a rounded rectangle. 3D surface normals are derived from the SDF to create realistic light refraction at the edges.
|
|
193
|
+
1. **WebGL Displacement + Specular Maps** - A GLSL fragment shader (highp precision) computes a displacement map and a specular highlight map from a signed distance field (SDF) of a rounded rectangle. 3D surface normals are derived from the SDF to create realistic light refraction at the edges. Maps are rendered at `quality`x resolution (default 2x) for smoother gradients, output as Blob URLs via `toBlob()`, and cached by parameters so multiple instances with the same props share a single set of maps.
|
|
193
194
|
|
|
194
195
|
2. **SVG Filter Chain** - The displacement map feeds into an SVG `<filter>` with `feDisplacementMap` for background refraction, `feColorMatrix` for saturation control, and `feBlend` to composite the specular highlight layer on top. A `brightness()` function in the backdrop-filter adds a subtle glow.
|
|
195
196
|
|
|
@@ -208,6 +209,7 @@ New props added:
|
|
|
208
209
|
|
|
209
210
|
- `brightness` - controls backdrop brightness (default `1.1`)
|
|
210
211
|
- `specularWidth` - controls the specular rim thickness (default `0.02`)
|
|
212
|
+
- `quality` - supersampling multiplier for smoother displacement maps (default `2`)
|
|
211
213
|
|
|
212
214
|
Changed defaults: `displacement` (35 -> 55), `blur` (2 -> 1), `saturation` (1.2 -> 150), `hoverScale` (1.05 -> 1.08), `hoverDisplacement` (65 -> 125), `hoverDuration` (0.4 -> 0.25), `glassColor` ("rgba(255,255,255,0.05)" -> "transparent").
|
|
213
215
|
|
package/dist/index.cjs
CHANGED
|
@@ -63,6 +63,7 @@ __export(index_exports, {
|
|
|
63
63
|
LiquidGlassButton: () => LiquidGlassButton,
|
|
64
64
|
cn: () => cn,
|
|
65
65
|
generateGlassMaps: () => generateGlassMaps,
|
|
66
|
+
getCachedGlassMaps: () => getCachedGlassMaps,
|
|
66
67
|
revokeGlassMaps: () => revokeGlassMaps
|
|
67
68
|
});
|
|
68
69
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -202,6 +203,25 @@ async function generateGlassMaps(opts) {
|
|
|
202
203
|
_mapCache.set(key, result);
|
|
203
204
|
return result;
|
|
204
205
|
}
|
|
206
|
+
function getCachedGlassMaps(opts) {
|
|
207
|
+
var _a;
|
|
208
|
+
const {
|
|
209
|
+
width,
|
|
210
|
+
height,
|
|
211
|
+
radius = 60,
|
|
212
|
+
edgeSize = 30,
|
|
213
|
+
intensity = 0.7,
|
|
214
|
+
specularWidth = 0.02,
|
|
215
|
+
quality = 2
|
|
216
|
+
} = opts;
|
|
217
|
+
const scale = Math.max(1, Math.round(quality));
|
|
218
|
+
const rw = width * scale;
|
|
219
|
+
const rh = height * scale;
|
|
220
|
+
const r = Math.min(radius * scale, rw / 2, rh / 2);
|
|
221
|
+
const borderSoftness = edgeSize * intensity * scale;
|
|
222
|
+
const specPx = specularWidth * Math.min(rw, rh);
|
|
223
|
+
return (_a = _mapCache.get(cacheKey(rw, rh, r, borderSoftness, specPx))) != null ? _a : null;
|
|
224
|
+
}
|
|
205
225
|
function revokeGlassMaps(maps) {
|
|
206
226
|
URL.revokeObjectURL(maps.displacement);
|
|
207
227
|
URL.revokeObjectURL(maps.specular);
|
|
@@ -259,10 +279,18 @@ var LiquidGlassButton = (0, import_react.forwardRef)(
|
|
|
259
279
|
const displacerRef = (0, import_react.useRef)(null);
|
|
260
280
|
const blurRef = (0, import_react.useRef)(null);
|
|
261
281
|
const filterId = "lg" + (0, import_react.useId)().replace(/:/g, "");
|
|
262
|
-
const
|
|
282
|
+
const mapOpts = { width, height, radius, edgeSize, intensity, specularWidth, quality };
|
|
283
|
+
const [maps, setMaps] = (0, import_react.useState)(
|
|
284
|
+
() => getCachedGlassMaps(mapOpts)
|
|
285
|
+
);
|
|
263
286
|
(0, import_react.useEffect)(() => {
|
|
287
|
+
const cached = getCachedGlassMaps(mapOpts);
|
|
288
|
+
if (cached) {
|
|
289
|
+
setMaps(cached);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
264
292
|
let cancelled = false;
|
|
265
|
-
generateGlassMaps(
|
|
293
|
+
generateGlassMaps(mapOpts).then((m) => {
|
|
266
294
|
if (!cancelled) setMaps(m);
|
|
267
295
|
});
|
|
268
296
|
return () => {
|
|
@@ -463,6 +491,7 @@ var LiquidGlassButton = (0, import_react.forwardRef)(
|
|
|
463
491
|
LiquidGlassButton,
|
|
464
492
|
cn,
|
|
465
493
|
generateGlassMaps,
|
|
494
|
+
getCachedGlassMaps,
|
|
466
495
|
revokeGlassMaps
|
|
467
496
|
});
|
|
468
497
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/liquid-glass-button.tsx","../src/utils.ts","../src/generate-displacement-map.ts"],"sourcesContent":["export { LiquidGlassButton } from \"./liquid-glass-button\";\nexport type { LiquidGlassButtonProps } from \"./liquid-glass-button\";\nexport { generateGlassMaps, revokeGlassMaps } from \"./generate-displacement-map\";\nexport type { MapOptions } from \"./generate-displacement-map\";\nexport { cn } from \"./utils\";\n","import React, { useRef, useEffect, useState, useId, forwardRef } from \"react\";\nimport gsap from \"gsap\";\nimport { cn } from \"./utils\";\nimport { generateGlassMaps, revokeGlassMaps } from \"./generate-displacement-map\";\n\nexport interface LiquidGlassButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n /** Button width in px */\n width?: number;\n /** Button height in px */\n height?: number;\n /** Border radius in px */\n radius?: number;\n /** Edge thickness of the glass refraction zone */\n edgeSize?: number;\n /** Edge refraction intensity (0-1) */\n intensity?: number;\n /** Specular rim thickness relative to size (0-1) */\n specularWidth?: number;\n /** feDisplacementMap scale - how much the background refracts */\n displacement?: number;\n /** Gaussian blur applied to the background */\n blur?: number;\n /** Saturation applied to the displaced result */\n saturation?: number;\n /** Brightness boost on the backdrop-filter (1 = normal) */\n brightness?: number;\n /** Background tint color of the glass */\n glassColor?: string;\n /** Scale multiplier on hover */\n hoverScale?: number;\n /** Displacement scale on hover */\n hoverDisplacement?: number;\n /** Blur amount on hover */\n hoverBlur?: number;\n /** Duration of hover animation in seconds */\n hoverDuration?: number;\n /** Disable all GSAP animations */\n disableAnimation?: boolean;\n /** Supersampling quality for the displacement map (default 2, higher = smoother) */\n quality?: number;\n}\n\nexport const LiquidGlassButton = forwardRef<HTMLButtonElement, LiquidGlassButtonProps>(\n function LiquidGlassButton({\n children, className, style,\n width = 300,\n height = 56,\n radius = 60,\n edgeSize = 30,\n intensity = 0.7,\n specularWidth = 0.02,\n displacement = 55,\n blur = 1,\n saturation = 150,\n brightness = 1.1,\n glassColor = \"transparent\",\n hoverScale = 1.08,\n hoverDisplacement = 125,\n hoverBlur = 4,\n hoverDuration = 0.25,\n disableAnimation = false,\n quality = 2,\n ...props\n }, ref) {\n const internalRef = useRef<HTMLButtonElement>(null);\n const buttonRef = (ref as React.RefObject<HTMLButtonElement>) ?? internalRef;\n\n const displacerRef = useRef<SVGFEDisplacementMapElement>(null);\n const blurRef = useRef<SVGFEGaussianBlurElement>(null);\n\n const filterId = \"lg\" + useId().replace(/:/g, \"\");\n\n const [maps, setMaps] = useState<{ displacement: string; specular: string } | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n\n generateGlassMaps({ width, height, radius, edgeSize, intensity, specularWidth, quality })\n .then((m) => {\n if (!cancelled) setMaps(m);\n });\n\n return () => {\n cancelled = true;\n };\n }, [width, height, radius, edgeSize, intensity, specularWidth, quality]);\n\n useEffect(() => {\n return () => {\n if (maps) revokeGlassMaps(maps);\n };\n }, [maps]);\n\n useEffect(() => {\n const button = buttonRef.current;\n const displacer = displacerRef.current;\n const blurEl = blurRef.current;\n if (!button || !displacer || !blurEl || disableAnimation) return;\n\n if (window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) return;\n\n const fx = {\n displacement: displacement,\n blur: blur,\n };\n\n const sync = () => {\n displacer.setAttribute(\"scale\", fx.displacement.toString());\n blurEl.setAttribute(\"stdDeviation\", fx.blur.toString());\n };\n\n sync();\n\n const onEnter = () => {\n gsap.killTweensOf([fx, button]);\n gsap.to(fx, {\n displacement: hoverDisplacement,\n blur: hoverBlur,\n duration: hoverDuration,\n ease: \"back.out(1.4)\",\n onUpdate: sync,\n });\n gsap.to(button, {\n scale: hoverScale,\n duration: hoverDuration,\n ease: \"back.out(1.4)\",\n });\n };\n\n const onLeave = () => {\n gsap.killTweensOf([fx, button]);\n gsap.to(fx, {\n displacement: displacement,\n blur: blur,\n duration: hoverDuration,\n ease: \"power2.out\",\n onUpdate: sync,\n });\n gsap.to(button, {\n scale: 1,\n duration: hoverDuration,\n ease: \"power2.out\",\n });\n };\n\n const onClick = () => {\n gsap.killTweensOf(button);\n const cur = gsap.getProperty(button, \"scale\") as number;\n gsap.timeline()\n .to(button, { scale: cur * 0.92, duration: 0.08, ease: \"power2.in\" })\n .to(button, { scale: hoverScale, duration: 0.25, ease: \"back.out(2)\" });\n };\n\n button.addEventListener(\"pointerenter\", onEnter);\n button.addEventListener(\"pointerleave\", onLeave);\n button.addEventListener(\"click\", onClick);\n\n return () => {\n button.removeEventListener(\"pointerenter\", onEnter);\n button.removeEventListener(\"pointerleave\", onLeave);\n button.removeEventListener(\"click\", onClick);\n gsap.killTweensOf([button, fx]);\n };\n }, [buttonRef, maps, displacement, blur, hoverScale, hoverDisplacement, hoverBlur, hoverDuration, disableAnimation]);\n\n if (!maps) return null;\n\n return (\n <>\n <button\n ref={buttonRef}\n className={cn(\"relative overflow-hidden shadow-lg cursor-pointer\", className)}\n style={{ width, height, borderRadius: radius, border: \"none\", background: glassColor, ...style }}\n {...props}\n >\n <div\n className=\"absolute inset-0\"\n style={{\n backdropFilter: `url(#${filterId}) brightness(${brightness * 100}%)`,\n WebkitBackdropFilter: `url(#${filterId}) brightness(${brightness * 100}%)`,\n borderRadius: \"inherit\",\n willChange: \"backdrop-filter\",\n transform: \"translateZ(0)\",\n }}\n />\n <div\n className=\"absolute inset-0 inline-flex items-center justify-center font-bold text-white\"\n style={{ background: \"hsl(0 100% 100% / 15%)\", borderRadius: \"inherit\" }}\n >\n {children}\n </div>\n </button>\n\n <svg\n colorInterpolationFilters=\"sRGB\"\n style={{ position: \"absolute\", width: 0, height: 0, overflow: \"hidden\" }}\n aria-hidden=\"true\"\n >\n <defs>\n <filter id={filterId}>\n <feGaussianBlur\n ref={blurRef}\n in=\"SourceGraphic\"\n stdDeviation={blur}\n result=\"blurred_source\"\n />\n <feImage\n href={maps.displacement}\n x=\"0\"\n y=\"0\"\n width={width}\n height={height}\n result=\"displacement_map\"\n />\n <feDisplacementMap\n ref={displacerRef}\n in=\"blurred_source\"\n in2=\"displacement_map\"\n scale={displacement}\n xChannelSelector=\"R\"\n yChannelSelector=\"G\"\n result=\"displaced\"\n />\n <feColorMatrix\n in=\"displaced\"\n type=\"saturate\"\n result=\"displaced_saturated\"\n values={saturation.toString()}\n />\n <feImage\n href={maps.specular}\n x=\"0\"\n y=\"0\"\n width={width}\n height={height}\n result=\"specular_layer\"\n />\n <feGaussianBlur\n in=\"specular_layer\"\n stdDeviation=\"1\"\n result=\"blurred_specular_layer\"\n />\n <feComposite\n in=\"displaced_saturated\"\n in2=\"blurred_specular_layer\"\n operator=\"in\"\n result=\"final_specular_layer\"\n />\n <feBlend\n in=\"final_specular_layer\"\n in2=\"displaced\"\n mode=\"normal\"\n />\n </filter>\n </defs>\n </svg>\n </>\n );\n }\n);\n","export function cn(...classes: (string | undefined | null | false)[]) {\n return classes.filter(Boolean).join(\" \");\n}\n","export interface MapOptions {\n width: number;\n height: number;\n radius?: number;\n edgeSize?: number;\n intensity?: number;\n specularWidth?: number;\n /** Supersampling multiplier for the displacement map (default: 2). Higher = smoother gradients. */\n quality?: number;\n}\n\nconst VERT = `attribute vec4 position; void main(){ gl_Position = position; }`;\n\nconst FRAG = `\nprecision highp float;\nuniform vec2 uRes;\nuniform float uRadius;\nuniform float uBorderSoftness;\nuniform float uSpecularWidth;\nuniform int uMode; // 0 = displacement, 1 = specular\n\nfloat sdRoundedBox(vec2 p, vec2 b, float r){\n r = min(r, min(b.x, b.y));\n vec2 q = abs(p) - b + r;\n return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;\n}\n\nvec3 calcNormal(vec2 p, vec2 b, float r){\n float e = max(0.5, min(b.x, b.y) * 0.01);\n vec2 h = vec2(e, 0.0);\n return normalize(vec3(\n sdRoundedBox(p+h.xy, b, r) - sdRoundedBox(p-h.xy, b, r),\n sdRoundedBox(p+h.yx, b, r) - sdRoundedBox(p-h.yx, b, r),\n -e * 2.0\n ));\n}\n\nvoid main(){\n vec2 p = gl_FragCoord.xy - uRes * 0.5;\n vec2 halfSize = uRes * 0.5 - 1.0;\n float d = sdRoundedBox(p, halfSize, uRadius);\n\n if(d > 0.0){ gl_FragColor = vec4(0.0); return; }\n\n if(uMode == 0){\n vec3 n = calcNormal(p, halfSize, uRadius);\n vec3 nc = n * 0.5 + 0.5;\n float border = smoothstep(-uBorderSoftness, 0.0, d);\n vec3 flat_ = vec3(0.5, 0.5, 1.0);\n gl_FragColor = vec4(mix(flat_, nc, border), 1.0);\n } else {\n float rim = smoothstep(-uSpecularWidth - 2.0, -uSpecularWidth, d)\n * (1.0 - smoothstep(-2.0, 0.0, d));\n float glow = smoothstep(-uBorderSoftness, 0.0, d) * 0.1;\n float s = clamp(rim + glow, 0.0, 1.0);\n gl_FragColor = vec4(vec3(s), s);\n }\n}\n`;\n\nfunction compile(gl: WebGLRenderingContext, type: number, src: string) {\n const s = gl.createShader(type)!;\n gl.shaderSource(s, src);\n gl.compileShader(s);\n return s;\n}\n\nlet _cachedProgram: { gl: WebGLRenderingContext; program: WebGLProgram; canvas: HTMLCanvasElement } | null = null;\n\nfunction getGL() {\n if (_cachedProgram) return _cachedProgram;\n\n const canvas = document.createElement(\"canvas\");\n const gl = canvas.getContext(\"webgl\", { preserveDrawingBuffer: true, premultipliedAlpha: false })!;\n\n const vs = compile(gl, gl.VERTEX_SHADER, VERT);\n const fs = compile(gl, gl.FRAGMENT_SHADER, FRAG);\n const program = gl.createProgram()!;\n gl.attachShader(program, vs);\n gl.attachShader(program, fs);\n gl.linkProgram(program);\n gl.useProgram(program);\n\n const buf = gl.createBuffer();\n gl.bindBuffer(gl.ARRAY_BUFFER, buf);\n gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);\n const pos = gl.getAttribLocation(program, \"position\");\n gl.enableVertexAttribArray(pos);\n gl.vertexAttribPointer(pos, 2, gl.FLOAT, false, 0, 0);\n\n _cachedProgram = { gl, program, canvas };\n return _cachedProgram;\n}\n\n// ── Cache ──────────────────────────────────────────────────────\n\nconst _mapCache = new Map<string, { displacement: string; specular: string }>();\n\nfunction cacheKey(\n w: number, h: number, r: number, bs: number, sw: number,\n): string {\n return `${w}|${h}|${r}|${bs}|${sw}`;\n}\n\n// ── Render ─────────────────────────────────────────────────────\n\nfunction renderToBlob(\n width: number,\n height: number,\n radius: number,\n borderSoftness: number,\n specularWidth: number,\n mode: number,\n): Promise<string> {\n const { gl, program, canvas } = getGL();\n canvas.width = width;\n canvas.height = height;\n gl.viewport(0, 0, width, height);\n gl.clearColor(0, 0, 0, 0);\n gl.clear(gl.COLOR_BUFFER_BIT);\n gl.useProgram(program);\n\n gl.uniform2f(gl.getUniformLocation(program, \"uRes\"), width, height);\n gl.uniform1f(gl.getUniformLocation(program, \"uRadius\"), radius);\n gl.uniform1f(gl.getUniformLocation(program, \"uBorderSoftness\"), borderSoftness);\n gl.uniform1f(gl.getUniformLocation(program, \"uSpecularWidth\"), specularWidth);\n gl.uniform1i(gl.getUniformLocation(program, \"uMode\"), mode);\n\n gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n\n return new Promise((resolve) => {\n canvas.toBlob((blob) => {\n resolve(URL.createObjectURL(blob!));\n }, \"image/png\");\n });\n}\n\n// ── Public API ─────────────────────────────────────────────────\n\nexport async function generateGlassMaps(opts: MapOptions): Promise<{\n displacement: string;\n specular: string;\n}> {\n const {\n width,\n height,\n radius = 60,\n edgeSize = 30,\n intensity = 0.7,\n specularWidth = 0.02,\n quality = 2,\n } = opts;\n\n const scale = Math.max(1, Math.round(quality));\n const rw = width * scale;\n const rh = height * scale;\n\n const r = Math.min(radius * scale, rw / 2, rh / 2);\n const borderSoftness = edgeSize * intensity * scale;\n const specPx = specularWidth * Math.min(rw, rh);\n\n const key = cacheKey(rw, rh, r, borderSoftness, specPx);\n const cached = _mapCache.get(key);\n if (cached) return cached;\n\n const [displacement, specular] = await Promise.all([\n renderToBlob(rw, rh, r, borderSoftness, specPx, 0),\n renderToBlob(rw, rh, r, borderSoftness, specPx, 1),\n ]);\n\n const result = { displacement, specular };\n _mapCache.set(key, result);\n return result;\n}\n\nexport function revokeGlassMaps(maps: { displacement: string; specular: string }) {\n URL.revokeObjectURL(maps.displacement);\n URL.revokeObjectURL(maps.specular);\n}\n"],"mappings":"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","names":["LiquidGlassButton","gsap"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/liquid-glass-button.tsx","../src/utils.ts","../src/generate-displacement-map.ts"],"sourcesContent":["export { LiquidGlassButton } from \"./liquid-glass-button\";\nexport type { LiquidGlassButtonProps } from \"./liquid-glass-button\";\nexport { generateGlassMaps, getCachedGlassMaps, revokeGlassMaps } from \"./generate-displacement-map\";\nexport type { MapOptions } from \"./generate-displacement-map\";\nexport { cn } from \"./utils\";\n","import React, { useRef, useEffect, useState, useId, forwardRef } from \"react\";\nimport gsap from \"gsap\";\nimport { cn } from \"./utils\";\nimport { generateGlassMaps, getCachedGlassMaps, revokeGlassMaps } from \"./generate-displacement-map\";\n\nexport interface LiquidGlassButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n /** Button width in px */\n width?: number;\n /** Button height in px */\n height?: number;\n /** Border radius in px */\n radius?: number;\n /** Edge thickness of the glass refraction zone */\n edgeSize?: number;\n /** Edge refraction intensity (0-1) */\n intensity?: number;\n /** Specular rim thickness relative to size (0-1) */\n specularWidth?: number;\n /** feDisplacementMap scale - how much the background refracts */\n displacement?: number;\n /** Gaussian blur applied to the background */\n blur?: number;\n /** Saturation applied to the displaced result */\n saturation?: number;\n /** Brightness boost on the backdrop-filter (1 = normal) */\n brightness?: number;\n /** Background tint color of the glass */\n glassColor?: string;\n /** Scale multiplier on hover */\n hoverScale?: number;\n /** Displacement scale on hover */\n hoverDisplacement?: number;\n /** Blur amount on hover */\n hoverBlur?: number;\n /** Duration of hover animation in seconds */\n hoverDuration?: number;\n /** Disable all GSAP animations */\n disableAnimation?: boolean;\n /** Supersampling quality for the displacement map (default 2, higher = smoother) */\n quality?: number;\n}\n\nexport const LiquidGlassButton = forwardRef<HTMLButtonElement, LiquidGlassButtonProps>(\n function LiquidGlassButton({\n children, className, style,\n width = 300,\n height = 56,\n radius = 60,\n edgeSize = 30,\n intensity = 0.7,\n specularWidth = 0.02,\n displacement = 55,\n blur = 1,\n saturation = 150,\n brightness = 1.1,\n glassColor = \"transparent\",\n hoverScale = 1.08,\n hoverDisplacement = 125,\n hoverBlur = 4,\n hoverDuration = 0.25,\n disableAnimation = false,\n quality = 2,\n ...props\n }, ref) {\n const internalRef = useRef<HTMLButtonElement>(null);\n const buttonRef = (ref as React.RefObject<HTMLButtonElement>) ?? internalRef;\n\n const displacerRef = useRef<SVGFEDisplacementMapElement>(null);\n const blurRef = useRef<SVGFEGaussianBlurElement>(null);\n\n const filterId = \"lg\" + useId().replace(/:/g, \"\");\n\n const mapOpts = { width, height, radius, edgeSize, intensity, specularWidth, quality };\n const [maps, setMaps] = useState<{ displacement: string; specular: string } | null>(\n () => getCachedGlassMaps(mapOpts),\n );\n\n useEffect(() => {\n // If already have maps from cache, skip\n const cached = getCachedGlassMaps(mapOpts);\n if (cached) {\n setMaps(cached);\n return;\n }\n\n let cancelled = false;\n generateGlassMaps(mapOpts).then((m) => {\n if (!cancelled) setMaps(m);\n });\n\n return () => { cancelled = true; };\n }, [width, height, radius, edgeSize, intensity, specularWidth, quality]);\n\n useEffect(() => {\n return () => {\n if (maps) revokeGlassMaps(maps);\n };\n }, [maps]);\n\n useEffect(() => {\n const button = buttonRef.current;\n const displacer = displacerRef.current;\n const blurEl = blurRef.current;\n if (!button || !displacer || !blurEl || disableAnimation) return;\n\n if (window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) return;\n\n const fx = {\n displacement: displacement,\n blur: blur,\n };\n\n const sync = () => {\n displacer.setAttribute(\"scale\", fx.displacement.toString());\n blurEl.setAttribute(\"stdDeviation\", fx.blur.toString());\n };\n\n sync();\n\n const onEnter = () => {\n gsap.killTweensOf([fx, button]);\n gsap.to(fx, {\n displacement: hoverDisplacement,\n blur: hoverBlur,\n duration: hoverDuration,\n ease: \"back.out(1.4)\",\n onUpdate: sync,\n });\n gsap.to(button, {\n scale: hoverScale,\n duration: hoverDuration,\n ease: \"back.out(1.4)\",\n });\n };\n\n const onLeave = () => {\n gsap.killTweensOf([fx, button]);\n gsap.to(fx, {\n displacement: displacement,\n blur: blur,\n duration: hoverDuration,\n ease: \"power2.out\",\n onUpdate: sync,\n });\n gsap.to(button, {\n scale: 1,\n duration: hoverDuration,\n ease: \"power2.out\",\n });\n };\n\n const onClick = () => {\n gsap.killTweensOf(button);\n const cur = gsap.getProperty(button, \"scale\") as number;\n gsap.timeline()\n .to(button, { scale: cur * 0.92, duration: 0.08, ease: \"power2.in\" })\n .to(button, { scale: hoverScale, duration: 0.25, ease: \"back.out(2)\" });\n };\n\n button.addEventListener(\"pointerenter\", onEnter);\n button.addEventListener(\"pointerleave\", onLeave);\n button.addEventListener(\"click\", onClick);\n\n return () => {\n button.removeEventListener(\"pointerenter\", onEnter);\n button.removeEventListener(\"pointerleave\", onLeave);\n button.removeEventListener(\"click\", onClick);\n gsap.killTweensOf([button, fx]);\n };\n }, [buttonRef, maps, displacement, blur, hoverScale, hoverDisplacement, hoverBlur, hoverDuration, disableAnimation]);\n\n if (!maps) return null;\n\n return (\n <>\n <button\n ref={buttonRef}\n className={cn(\"relative overflow-hidden shadow-lg cursor-pointer\", className)}\n style={{ width, height, borderRadius: radius, border: \"none\", background: glassColor, ...style }}\n {...props}\n >\n <div\n className=\"absolute inset-0\"\n style={{\n backdropFilter: `url(#${filterId}) brightness(${brightness * 100}%)`,\n WebkitBackdropFilter: `url(#${filterId}) brightness(${brightness * 100}%)`,\n borderRadius: \"inherit\",\n willChange: \"backdrop-filter\",\n transform: \"translateZ(0)\",\n }}\n />\n <div\n className=\"absolute inset-0 inline-flex items-center justify-center font-bold text-white\"\n style={{ background: \"hsl(0 100% 100% / 15%)\", borderRadius: \"inherit\" }}\n >\n {children}\n </div>\n </button>\n\n <svg\n colorInterpolationFilters=\"sRGB\"\n style={{ position: \"absolute\", width: 0, height: 0, overflow: \"hidden\" }}\n aria-hidden=\"true\"\n >\n <defs>\n <filter id={filterId}>\n <feGaussianBlur\n ref={blurRef}\n in=\"SourceGraphic\"\n stdDeviation={blur}\n result=\"blurred_source\"\n />\n <feImage\n href={maps.displacement}\n x=\"0\"\n y=\"0\"\n width={width}\n height={height}\n result=\"displacement_map\"\n />\n <feDisplacementMap\n ref={displacerRef}\n in=\"blurred_source\"\n in2=\"displacement_map\"\n scale={displacement}\n xChannelSelector=\"R\"\n yChannelSelector=\"G\"\n result=\"displaced\"\n />\n <feColorMatrix\n in=\"displaced\"\n type=\"saturate\"\n result=\"displaced_saturated\"\n values={saturation.toString()}\n />\n <feImage\n href={maps.specular}\n x=\"0\"\n y=\"0\"\n width={width}\n height={height}\n result=\"specular_layer\"\n />\n <feGaussianBlur\n in=\"specular_layer\"\n stdDeviation=\"1\"\n result=\"blurred_specular_layer\"\n />\n <feComposite\n in=\"displaced_saturated\"\n in2=\"blurred_specular_layer\"\n operator=\"in\"\n result=\"final_specular_layer\"\n />\n <feBlend\n in=\"final_specular_layer\"\n in2=\"displaced\"\n mode=\"normal\"\n />\n </filter>\n </defs>\n </svg>\n </>\n );\n }\n);\n","export function cn(...classes: (string | undefined | null | false)[]) {\n return classes.filter(Boolean).join(\" \");\n}\n","export interface MapOptions {\n width: number;\n height: number;\n radius?: number;\n edgeSize?: number;\n intensity?: number;\n specularWidth?: number;\n /** Supersampling multiplier for the displacement map (default: 2). Higher = smoother gradients. */\n quality?: number;\n}\n\nconst VERT = `attribute vec4 position; void main(){ gl_Position = position; }`;\n\nconst FRAG = `\nprecision highp float;\nuniform vec2 uRes;\nuniform float uRadius;\nuniform float uBorderSoftness;\nuniform float uSpecularWidth;\nuniform int uMode; // 0 = displacement, 1 = specular\n\nfloat sdRoundedBox(vec2 p, vec2 b, float r){\n r = min(r, min(b.x, b.y));\n vec2 q = abs(p) - b + r;\n return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;\n}\n\nvec3 calcNormal(vec2 p, vec2 b, float r){\n float e = max(0.5, min(b.x, b.y) * 0.01);\n vec2 h = vec2(e, 0.0);\n return normalize(vec3(\n sdRoundedBox(p+h.xy, b, r) - sdRoundedBox(p-h.xy, b, r),\n sdRoundedBox(p+h.yx, b, r) - sdRoundedBox(p-h.yx, b, r),\n -e * 2.0\n ));\n}\n\nvoid main(){\n vec2 p = gl_FragCoord.xy - uRes * 0.5;\n vec2 halfSize = uRes * 0.5 - 1.0;\n float d = sdRoundedBox(p, halfSize, uRadius);\n\n if(d > 0.0){ gl_FragColor = vec4(0.0); return; }\n\n if(uMode == 0){\n vec3 n = calcNormal(p, halfSize, uRadius);\n vec3 nc = n * 0.5 + 0.5;\n float border = smoothstep(-uBorderSoftness, 0.0, d);\n vec3 flat_ = vec3(0.5, 0.5, 1.0);\n gl_FragColor = vec4(mix(flat_, nc, border), 1.0);\n } else {\n float rim = smoothstep(-uSpecularWidth - 2.0, -uSpecularWidth, d)\n * (1.0 - smoothstep(-2.0, 0.0, d));\n float glow = smoothstep(-uBorderSoftness, 0.0, d) * 0.1;\n float s = clamp(rim + glow, 0.0, 1.0);\n gl_FragColor = vec4(vec3(s), s);\n }\n}\n`;\n\nfunction compile(gl: WebGLRenderingContext, type: number, src: string) {\n const s = gl.createShader(type)!;\n gl.shaderSource(s, src);\n gl.compileShader(s);\n return s;\n}\n\nlet _cachedProgram: { gl: WebGLRenderingContext; program: WebGLProgram; canvas: HTMLCanvasElement } | null = null;\n\nfunction getGL() {\n if (_cachedProgram) return _cachedProgram;\n\n const canvas = document.createElement(\"canvas\");\n const gl = canvas.getContext(\"webgl\", { preserveDrawingBuffer: true, premultipliedAlpha: false })!;\n\n const vs = compile(gl, gl.VERTEX_SHADER, VERT);\n const fs = compile(gl, gl.FRAGMENT_SHADER, FRAG);\n const program = gl.createProgram()!;\n gl.attachShader(program, vs);\n gl.attachShader(program, fs);\n gl.linkProgram(program);\n gl.useProgram(program);\n\n const buf = gl.createBuffer();\n gl.bindBuffer(gl.ARRAY_BUFFER, buf);\n gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);\n const pos = gl.getAttribLocation(program, \"position\");\n gl.enableVertexAttribArray(pos);\n gl.vertexAttribPointer(pos, 2, gl.FLOAT, false, 0, 0);\n\n _cachedProgram = { gl, program, canvas };\n return _cachedProgram;\n}\n\n// ── Cache ──────────────────────────────────────────────────────\n\nconst _mapCache = new Map<string, { displacement: string; specular: string }>();\n\nfunction cacheKey(\n w: number, h: number, r: number, bs: number, sw: number,\n): string {\n return `${w}|${h}|${r}|${bs}|${sw}`;\n}\n\n// ── Render ─────────────────────────────────────────────────────\n\nfunction renderToBlob(\n width: number,\n height: number,\n radius: number,\n borderSoftness: number,\n specularWidth: number,\n mode: number,\n): Promise<string> {\n const { gl, program, canvas } = getGL();\n canvas.width = width;\n canvas.height = height;\n gl.viewport(0, 0, width, height);\n gl.clearColor(0, 0, 0, 0);\n gl.clear(gl.COLOR_BUFFER_BIT);\n gl.useProgram(program);\n\n gl.uniform2f(gl.getUniformLocation(program, \"uRes\"), width, height);\n gl.uniform1f(gl.getUniformLocation(program, \"uRadius\"), radius);\n gl.uniform1f(gl.getUniformLocation(program, \"uBorderSoftness\"), borderSoftness);\n gl.uniform1f(gl.getUniformLocation(program, \"uSpecularWidth\"), specularWidth);\n gl.uniform1i(gl.getUniformLocation(program, \"uMode\"), mode);\n\n gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n\n return new Promise((resolve) => {\n canvas.toBlob((blob) => {\n resolve(URL.createObjectURL(blob!));\n }, \"image/png\");\n });\n}\n\n// ── Public API ─────────────────────────────────────────────────\n\nexport async function generateGlassMaps(opts: MapOptions): Promise<{\n displacement: string;\n specular: string;\n}> {\n const {\n width,\n height,\n radius = 60,\n edgeSize = 30,\n intensity = 0.7,\n specularWidth = 0.02,\n quality = 2,\n } = opts;\n\n const scale = Math.max(1, Math.round(quality));\n const rw = width * scale;\n const rh = height * scale;\n\n const r = Math.min(radius * scale, rw / 2, rh / 2);\n const borderSoftness = edgeSize * intensity * scale;\n const specPx = specularWidth * Math.min(rw, rh);\n\n const key = cacheKey(rw, rh, r, borderSoftness, specPx);\n const cached = _mapCache.get(key);\n if (cached) return cached;\n\n const [displacement, specular] = await Promise.all([\n renderToBlob(rw, rh, r, borderSoftness, specPx, 0),\n renderToBlob(rw, rh, r, borderSoftness, specPx, 1),\n ]);\n\n const result = { displacement, specular };\n _mapCache.set(key, result);\n return result;\n}\n\nexport function getCachedGlassMaps(opts: MapOptions): { displacement: string; specular: string } | null {\n const {\n width,\n height,\n radius = 60,\n edgeSize = 30,\n intensity = 0.7,\n specularWidth = 0.02,\n quality = 2,\n } = opts;\n\n const scale = Math.max(1, Math.round(quality));\n const rw = width * scale;\n const rh = height * scale;\n\n const r = Math.min(radius * scale, rw / 2, rh / 2);\n const borderSoftness = edgeSize * intensity * scale;\n const specPx = specularWidth * Math.min(rw, rh);\n\n return _mapCache.get(cacheKey(rw, rh, r, borderSoftness, specPx)) ?? null;\n}\n\nexport function revokeGlassMaps(maps: { displacement: string; specular: string }) {\n URL.revokeObjectURL(maps.displacement);\n URL.revokeObjectURL(maps.specular);\n}\n"],"mappings":"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","names":["LiquidGlassButton","gsap"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -52,6 +52,10 @@ declare function generateGlassMaps(opts: MapOptions): Promise<{
|
|
|
52
52
|
displacement: string;
|
|
53
53
|
specular: string;
|
|
54
54
|
}>;
|
|
55
|
+
declare function getCachedGlassMaps(opts: MapOptions): {
|
|
56
|
+
displacement: string;
|
|
57
|
+
specular: string;
|
|
58
|
+
} | null;
|
|
55
59
|
declare function revokeGlassMaps(maps: {
|
|
56
60
|
displacement: string;
|
|
57
61
|
specular: string;
|
|
@@ -59,4 +63,4 @@ declare function revokeGlassMaps(maps: {
|
|
|
59
63
|
|
|
60
64
|
declare function cn(...classes: (string | undefined | null | false)[]): string;
|
|
61
65
|
|
|
62
|
-
export { LiquidGlassButton, type LiquidGlassButtonProps, type MapOptions, cn, generateGlassMaps, revokeGlassMaps };
|
|
66
|
+
export { LiquidGlassButton, type LiquidGlassButtonProps, type MapOptions, cn, generateGlassMaps, getCachedGlassMaps, revokeGlassMaps };
|
package/dist/index.d.ts
CHANGED
|
@@ -52,6 +52,10 @@ declare function generateGlassMaps(opts: MapOptions): Promise<{
|
|
|
52
52
|
displacement: string;
|
|
53
53
|
specular: string;
|
|
54
54
|
}>;
|
|
55
|
+
declare function getCachedGlassMaps(opts: MapOptions): {
|
|
56
|
+
displacement: string;
|
|
57
|
+
specular: string;
|
|
58
|
+
} | null;
|
|
55
59
|
declare function revokeGlassMaps(maps: {
|
|
56
60
|
displacement: string;
|
|
57
61
|
specular: string;
|
|
@@ -59,4 +63,4 @@ declare function revokeGlassMaps(maps: {
|
|
|
59
63
|
|
|
60
64
|
declare function cn(...classes: (string | undefined | null | false)[]): string;
|
|
61
65
|
|
|
62
|
-
export { LiquidGlassButton, type LiquidGlassButtonProps, type MapOptions, cn, generateGlassMaps, revokeGlassMaps };
|
|
66
|
+
export { LiquidGlassButton, type LiquidGlassButtonProps, type MapOptions, cn, generateGlassMaps, getCachedGlassMaps, revokeGlassMaps };
|
package/dist/index.js
CHANGED
|
@@ -166,6 +166,25 @@ async function generateGlassMaps(opts) {
|
|
|
166
166
|
_mapCache.set(key, result);
|
|
167
167
|
return result;
|
|
168
168
|
}
|
|
169
|
+
function getCachedGlassMaps(opts) {
|
|
170
|
+
var _a;
|
|
171
|
+
const {
|
|
172
|
+
width,
|
|
173
|
+
height,
|
|
174
|
+
radius = 60,
|
|
175
|
+
edgeSize = 30,
|
|
176
|
+
intensity = 0.7,
|
|
177
|
+
specularWidth = 0.02,
|
|
178
|
+
quality = 2
|
|
179
|
+
} = opts;
|
|
180
|
+
const scale = Math.max(1, Math.round(quality));
|
|
181
|
+
const rw = width * scale;
|
|
182
|
+
const rh = height * scale;
|
|
183
|
+
const r = Math.min(radius * scale, rw / 2, rh / 2);
|
|
184
|
+
const borderSoftness = edgeSize * intensity * scale;
|
|
185
|
+
const specPx = specularWidth * Math.min(rw, rh);
|
|
186
|
+
return (_a = _mapCache.get(cacheKey(rw, rh, r, borderSoftness, specPx))) != null ? _a : null;
|
|
187
|
+
}
|
|
169
188
|
function revokeGlassMaps(maps) {
|
|
170
189
|
URL.revokeObjectURL(maps.displacement);
|
|
171
190
|
URL.revokeObjectURL(maps.specular);
|
|
@@ -223,10 +242,18 @@ var LiquidGlassButton = forwardRef(
|
|
|
223
242
|
const displacerRef = useRef(null);
|
|
224
243
|
const blurRef = useRef(null);
|
|
225
244
|
const filterId = "lg" + useId().replace(/:/g, "");
|
|
226
|
-
const
|
|
245
|
+
const mapOpts = { width, height, radius, edgeSize, intensity, specularWidth, quality };
|
|
246
|
+
const [maps, setMaps] = useState(
|
|
247
|
+
() => getCachedGlassMaps(mapOpts)
|
|
248
|
+
);
|
|
227
249
|
useEffect(() => {
|
|
250
|
+
const cached = getCachedGlassMaps(mapOpts);
|
|
251
|
+
if (cached) {
|
|
252
|
+
setMaps(cached);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
228
255
|
let cancelled = false;
|
|
229
|
-
generateGlassMaps(
|
|
256
|
+
generateGlassMaps(mapOpts).then((m) => {
|
|
230
257
|
if (!cancelled) setMaps(m);
|
|
231
258
|
});
|
|
232
259
|
return () => {
|
|
@@ -426,6 +453,7 @@ export {
|
|
|
426
453
|
LiquidGlassButton,
|
|
427
454
|
cn,
|
|
428
455
|
generateGlassMaps,
|
|
456
|
+
getCachedGlassMaps,
|
|
429
457
|
revokeGlassMaps
|
|
430
458
|
};
|
|
431
459
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/liquid-glass-button.tsx","../src/utils.ts","../src/generate-displacement-map.ts"],"sourcesContent":["import React, { useRef, useEffect, useState, useId, forwardRef } from \"react\";\nimport gsap from \"gsap\";\nimport { cn } from \"./utils\";\nimport { generateGlassMaps, revokeGlassMaps } from \"./generate-displacement-map\";\n\nexport interface LiquidGlassButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n /** Button width in px */\n width?: number;\n /** Button height in px */\n height?: number;\n /** Border radius in px */\n radius?: number;\n /** Edge thickness of the glass refraction zone */\n edgeSize?: number;\n /** Edge refraction intensity (0-1) */\n intensity?: number;\n /** Specular rim thickness relative to size (0-1) */\n specularWidth?: number;\n /** feDisplacementMap scale - how much the background refracts */\n displacement?: number;\n /** Gaussian blur applied to the background */\n blur?: number;\n /** Saturation applied to the displaced result */\n saturation?: number;\n /** Brightness boost on the backdrop-filter (1 = normal) */\n brightness?: number;\n /** Background tint color of the glass */\n glassColor?: string;\n /** Scale multiplier on hover */\n hoverScale?: number;\n /** Displacement scale on hover */\n hoverDisplacement?: number;\n /** Blur amount on hover */\n hoverBlur?: number;\n /** Duration of hover animation in seconds */\n hoverDuration?: number;\n /** Disable all GSAP animations */\n disableAnimation?: boolean;\n /** Supersampling quality for the displacement map (default 2, higher = smoother) */\n quality?: number;\n}\n\nexport const LiquidGlassButton = forwardRef<HTMLButtonElement, LiquidGlassButtonProps>(\n function LiquidGlassButton({\n children, className, style,\n width = 300,\n height = 56,\n radius = 60,\n edgeSize = 30,\n intensity = 0.7,\n specularWidth = 0.02,\n displacement = 55,\n blur = 1,\n saturation = 150,\n brightness = 1.1,\n glassColor = \"transparent\",\n hoverScale = 1.08,\n hoverDisplacement = 125,\n hoverBlur = 4,\n hoverDuration = 0.25,\n disableAnimation = false,\n quality = 2,\n ...props\n }, ref) {\n const internalRef = useRef<HTMLButtonElement>(null);\n const buttonRef = (ref as React.RefObject<HTMLButtonElement>) ?? internalRef;\n\n const displacerRef = useRef<SVGFEDisplacementMapElement>(null);\n const blurRef = useRef<SVGFEGaussianBlurElement>(null);\n\n const filterId = \"lg\" + useId().replace(/:/g, \"\");\n\n const [maps, setMaps] = useState<{ displacement: string; specular: string } | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n\n generateGlassMaps({ width, height, radius, edgeSize, intensity, specularWidth, quality })\n .then((m) => {\n if (!cancelled) setMaps(m);\n });\n\n return () => {\n cancelled = true;\n };\n }, [width, height, radius, edgeSize, intensity, specularWidth, quality]);\n\n useEffect(() => {\n return () => {\n if (maps) revokeGlassMaps(maps);\n };\n }, [maps]);\n\n useEffect(() => {\n const button = buttonRef.current;\n const displacer = displacerRef.current;\n const blurEl = blurRef.current;\n if (!button || !displacer || !blurEl || disableAnimation) return;\n\n if (window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) return;\n\n const fx = {\n displacement: displacement,\n blur: blur,\n };\n\n const sync = () => {\n displacer.setAttribute(\"scale\", fx.displacement.toString());\n blurEl.setAttribute(\"stdDeviation\", fx.blur.toString());\n };\n\n sync();\n\n const onEnter = () => {\n gsap.killTweensOf([fx, button]);\n gsap.to(fx, {\n displacement: hoverDisplacement,\n blur: hoverBlur,\n duration: hoverDuration,\n ease: \"back.out(1.4)\",\n onUpdate: sync,\n });\n gsap.to(button, {\n scale: hoverScale,\n duration: hoverDuration,\n ease: \"back.out(1.4)\",\n });\n };\n\n const onLeave = () => {\n gsap.killTweensOf([fx, button]);\n gsap.to(fx, {\n displacement: displacement,\n blur: blur,\n duration: hoverDuration,\n ease: \"power2.out\",\n onUpdate: sync,\n });\n gsap.to(button, {\n scale: 1,\n duration: hoverDuration,\n ease: \"power2.out\",\n });\n };\n\n const onClick = () => {\n gsap.killTweensOf(button);\n const cur = gsap.getProperty(button, \"scale\") as number;\n gsap.timeline()\n .to(button, { scale: cur * 0.92, duration: 0.08, ease: \"power2.in\" })\n .to(button, { scale: hoverScale, duration: 0.25, ease: \"back.out(2)\" });\n };\n\n button.addEventListener(\"pointerenter\", onEnter);\n button.addEventListener(\"pointerleave\", onLeave);\n button.addEventListener(\"click\", onClick);\n\n return () => {\n button.removeEventListener(\"pointerenter\", onEnter);\n button.removeEventListener(\"pointerleave\", onLeave);\n button.removeEventListener(\"click\", onClick);\n gsap.killTweensOf([button, fx]);\n };\n }, [buttonRef, maps, displacement, blur, hoverScale, hoverDisplacement, hoverBlur, hoverDuration, disableAnimation]);\n\n if (!maps) return null;\n\n return (\n <>\n <button\n ref={buttonRef}\n className={cn(\"relative overflow-hidden shadow-lg cursor-pointer\", className)}\n style={{ width, height, borderRadius: radius, border: \"none\", background: glassColor, ...style }}\n {...props}\n >\n <div\n className=\"absolute inset-0\"\n style={{\n backdropFilter: `url(#${filterId}) brightness(${brightness * 100}%)`,\n WebkitBackdropFilter: `url(#${filterId}) brightness(${brightness * 100}%)`,\n borderRadius: \"inherit\",\n willChange: \"backdrop-filter\",\n transform: \"translateZ(0)\",\n }}\n />\n <div\n className=\"absolute inset-0 inline-flex items-center justify-center font-bold text-white\"\n style={{ background: \"hsl(0 100% 100% / 15%)\", borderRadius: \"inherit\" }}\n >\n {children}\n </div>\n </button>\n\n <svg\n colorInterpolationFilters=\"sRGB\"\n style={{ position: \"absolute\", width: 0, height: 0, overflow: \"hidden\" }}\n aria-hidden=\"true\"\n >\n <defs>\n <filter id={filterId}>\n <feGaussianBlur\n ref={blurRef}\n in=\"SourceGraphic\"\n stdDeviation={blur}\n result=\"blurred_source\"\n />\n <feImage\n href={maps.displacement}\n x=\"0\"\n y=\"0\"\n width={width}\n height={height}\n result=\"displacement_map\"\n />\n <feDisplacementMap\n ref={displacerRef}\n in=\"blurred_source\"\n in2=\"displacement_map\"\n scale={displacement}\n xChannelSelector=\"R\"\n yChannelSelector=\"G\"\n result=\"displaced\"\n />\n <feColorMatrix\n in=\"displaced\"\n type=\"saturate\"\n result=\"displaced_saturated\"\n values={saturation.toString()}\n />\n <feImage\n href={maps.specular}\n x=\"0\"\n y=\"0\"\n width={width}\n height={height}\n result=\"specular_layer\"\n />\n <feGaussianBlur\n in=\"specular_layer\"\n stdDeviation=\"1\"\n result=\"blurred_specular_layer\"\n />\n <feComposite\n in=\"displaced_saturated\"\n in2=\"blurred_specular_layer\"\n operator=\"in\"\n result=\"final_specular_layer\"\n />\n <feBlend\n in=\"final_specular_layer\"\n in2=\"displaced\"\n mode=\"normal\"\n />\n </filter>\n </defs>\n </svg>\n </>\n );\n }\n);\n","export function cn(...classes: (string | undefined | null | false)[]) {\n return classes.filter(Boolean).join(\" \");\n}\n","export interface MapOptions {\n width: number;\n height: number;\n radius?: number;\n edgeSize?: number;\n intensity?: number;\n specularWidth?: number;\n /** Supersampling multiplier for the displacement map (default: 2). Higher = smoother gradients. */\n quality?: number;\n}\n\nconst VERT = `attribute vec4 position; void main(){ gl_Position = position; }`;\n\nconst FRAG = `\nprecision highp float;\nuniform vec2 uRes;\nuniform float uRadius;\nuniform float uBorderSoftness;\nuniform float uSpecularWidth;\nuniform int uMode; // 0 = displacement, 1 = specular\n\nfloat sdRoundedBox(vec2 p, vec2 b, float r){\n r = min(r, min(b.x, b.y));\n vec2 q = abs(p) - b + r;\n return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;\n}\n\nvec3 calcNormal(vec2 p, vec2 b, float r){\n float e = max(0.5, min(b.x, b.y) * 0.01);\n vec2 h = vec2(e, 0.0);\n return normalize(vec3(\n sdRoundedBox(p+h.xy, b, r) - sdRoundedBox(p-h.xy, b, r),\n sdRoundedBox(p+h.yx, b, r) - sdRoundedBox(p-h.yx, b, r),\n -e * 2.0\n ));\n}\n\nvoid main(){\n vec2 p = gl_FragCoord.xy - uRes * 0.5;\n vec2 halfSize = uRes * 0.5 - 1.0;\n float d = sdRoundedBox(p, halfSize, uRadius);\n\n if(d > 0.0){ gl_FragColor = vec4(0.0); return; }\n\n if(uMode == 0){\n vec3 n = calcNormal(p, halfSize, uRadius);\n vec3 nc = n * 0.5 + 0.5;\n float border = smoothstep(-uBorderSoftness, 0.0, d);\n vec3 flat_ = vec3(0.5, 0.5, 1.0);\n gl_FragColor = vec4(mix(flat_, nc, border), 1.0);\n } else {\n float rim = smoothstep(-uSpecularWidth - 2.0, -uSpecularWidth, d)\n * (1.0 - smoothstep(-2.0, 0.0, d));\n float glow = smoothstep(-uBorderSoftness, 0.0, d) * 0.1;\n float s = clamp(rim + glow, 0.0, 1.0);\n gl_FragColor = vec4(vec3(s), s);\n }\n}\n`;\n\nfunction compile(gl: WebGLRenderingContext, type: number, src: string) {\n const s = gl.createShader(type)!;\n gl.shaderSource(s, src);\n gl.compileShader(s);\n return s;\n}\n\nlet _cachedProgram: { gl: WebGLRenderingContext; program: WebGLProgram; canvas: HTMLCanvasElement } | null = null;\n\nfunction getGL() {\n if (_cachedProgram) return _cachedProgram;\n\n const canvas = document.createElement(\"canvas\");\n const gl = canvas.getContext(\"webgl\", { preserveDrawingBuffer: true, premultipliedAlpha: false })!;\n\n const vs = compile(gl, gl.VERTEX_SHADER, VERT);\n const fs = compile(gl, gl.FRAGMENT_SHADER, FRAG);\n const program = gl.createProgram()!;\n gl.attachShader(program, vs);\n gl.attachShader(program, fs);\n gl.linkProgram(program);\n gl.useProgram(program);\n\n const buf = gl.createBuffer();\n gl.bindBuffer(gl.ARRAY_BUFFER, buf);\n gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);\n const pos = gl.getAttribLocation(program, \"position\");\n gl.enableVertexAttribArray(pos);\n gl.vertexAttribPointer(pos, 2, gl.FLOAT, false, 0, 0);\n\n _cachedProgram = { gl, program, canvas };\n return _cachedProgram;\n}\n\n// ── Cache ──────────────────────────────────────────────────────\n\nconst _mapCache = new Map<string, { displacement: string; specular: string }>();\n\nfunction cacheKey(\n w: number, h: number, r: number, bs: number, sw: number,\n): string {\n return `${w}|${h}|${r}|${bs}|${sw}`;\n}\n\n// ── Render ─────────────────────────────────────────────────────\n\nfunction renderToBlob(\n width: number,\n height: number,\n radius: number,\n borderSoftness: number,\n specularWidth: number,\n mode: number,\n): Promise<string> {\n const { gl, program, canvas } = getGL();\n canvas.width = width;\n canvas.height = height;\n gl.viewport(0, 0, width, height);\n gl.clearColor(0, 0, 0, 0);\n gl.clear(gl.COLOR_BUFFER_BIT);\n gl.useProgram(program);\n\n gl.uniform2f(gl.getUniformLocation(program, \"uRes\"), width, height);\n gl.uniform1f(gl.getUniformLocation(program, \"uRadius\"), radius);\n gl.uniform1f(gl.getUniformLocation(program, \"uBorderSoftness\"), borderSoftness);\n gl.uniform1f(gl.getUniformLocation(program, \"uSpecularWidth\"), specularWidth);\n gl.uniform1i(gl.getUniformLocation(program, \"uMode\"), mode);\n\n gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n\n return new Promise((resolve) => {\n canvas.toBlob((blob) => {\n resolve(URL.createObjectURL(blob!));\n }, \"image/png\");\n });\n}\n\n// ── Public API ─────────────────────────────────────────────────\n\nexport async function generateGlassMaps(opts: MapOptions): Promise<{\n displacement: string;\n specular: string;\n}> {\n const {\n width,\n height,\n radius = 60,\n edgeSize = 30,\n intensity = 0.7,\n specularWidth = 0.02,\n quality = 2,\n } = opts;\n\n const scale = Math.max(1, Math.round(quality));\n const rw = width * scale;\n const rh = height * scale;\n\n const r = Math.min(radius * scale, rw / 2, rh / 2);\n const borderSoftness = edgeSize * intensity * scale;\n const specPx = specularWidth * Math.min(rw, rh);\n\n const key = cacheKey(rw, rh, r, borderSoftness, specPx);\n const cached = _mapCache.get(key);\n if (cached) return cached;\n\n const [displacement, specular] = await Promise.all([\n renderToBlob(rw, rh, r, borderSoftness, specPx, 0),\n renderToBlob(rw, rh, r, borderSoftness, specPx, 1),\n ]);\n\n const result = { displacement, specular };\n _mapCache.set(key, result);\n return result;\n}\n\nexport function revokeGlassMaps(maps: { displacement: string; specular: string }) {\n URL.revokeObjectURL(maps.displacement);\n URL.revokeObjectURL(maps.specular);\n}\n"],"mappings":"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","names":["LiquidGlassButton"]}
|
|
1
|
+
{"version":3,"sources":["../src/liquid-glass-button.tsx","../src/utils.ts","../src/generate-displacement-map.ts"],"sourcesContent":["import React, { useRef, useEffect, useState, useId, forwardRef } from \"react\";\nimport gsap from \"gsap\";\nimport { cn } from \"./utils\";\nimport { generateGlassMaps, getCachedGlassMaps, revokeGlassMaps } from \"./generate-displacement-map\";\n\nexport interface LiquidGlassButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n /** Button width in px */\n width?: number;\n /** Button height in px */\n height?: number;\n /** Border radius in px */\n radius?: number;\n /** Edge thickness of the glass refraction zone */\n edgeSize?: number;\n /** Edge refraction intensity (0-1) */\n intensity?: number;\n /** Specular rim thickness relative to size (0-1) */\n specularWidth?: number;\n /** feDisplacementMap scale - how much the background refracts */\n displacement?: number;\n /** Gaussian blur applied to the background */\n blur?: number;\n /** Saturation applied to the displaced result */\n saturation?: number;\n /** Brightness boost on the backdrop-filter (1 = normal) */\n brightness?: number;\n /** Background tint color of the glass */\n glassColor?: string;\n /** Scale multiplier on hover */\n hoverScale?: number;\n /** Displacement scale on hover */\n hoverDisplacement?: number;\n /** Blur amount on hover */\n hoverBlur?: number;\n /** Duration of hover animation in seconds */\n hoverDuration?: number;\n /** Disable all GSAP animations */\n disableAnimation?: boolean;\n /** Supersampling quality for the displacement map (default 2, higher = smoother) */\n quality?: number;\n}\n\nexport const LiquidGlassButton = forwardRef<HTMLButtonElement, LiquidGlassButtonProps>(\n function LiquidGlassButton({\n children, className, style,\n width = 300,\n height = 56,\n radius = 60,\n edgeSize = 30,\n intensity = 0.7,\n specularWidth = 0.02,\n displacement = 55,\n blur = 1,\n saturation = 150,\n brightness = 1.1,\n glassColor = \"transparent\",\n hoverScale = 1.08,\n hoverDisplacement = 125,\n hoverBlur = 4,\n hoverDuration = 0.25,\n disableAnimation = false,\n quality = 2,\n ...props\n }, ref) {\n const internalRef = useRef<HTMLButtonElement>(null);\n const buttonRef = (ref as React.RefObject<HTMLButtonElement>) ?? internalRef;\n\n const displacerRef = useRef<SVGFEDisplacementMapElement>(null);\n const blurRef = useRef<SVGFEGaussianBlurElement>(null);\n\n const filterId = \"lg\" + useId().replace(/:/g, \"\");\n\n const mapOpts = { width, height, radius, edgeSize, intensity, specularWidth, quality };\n const [maps, setMaps] = useState<{ displacement: string; specular: string } | null>(\n () => getCachedGlassMaps(mapOpts),\n );\n\n useEffect(() => {\n // If already have maps from cache, skip\n const cached = getCachedGlassMaps(mapOpts);\n if (cached) {\n setMaps(cached);\n return;\n }\n\n let cancelled = false;\n generateGlassMaps(mapOpts).then((m) => {\n if (!cancelled) setMaps(m);\n });\n\n return () => { cancelled = true; };\n }, [width, height, radius, edgeSize, intensity, specularWidth, quality]);\n\n useEffect(() => {\n return () => {\n if (maps) revokeGlassMaps(maps);\n };\n }, [maps]);\n\n useEffect(() => {\n const button = buttonRef.current;\n const displacer = displacerRef.current;\n const blurEl = blurRef.current;\n if (!button || !displacer || !blurEl || disableAnimation) return;\n\n if (window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) return;\n\n const fx = {\n displacement: displacement,\n blur: blur,\n };\n\n const sync = () => {\n displacer.setAttribute(\"scale\", fx.displacement.toString());\n blurEl.setAttribute(\"stdDeviation\", fx.blur.toString());\n };\n\n sync();\n\n const onEnter = () => {\n gsap.killTweensOf([fx, button]);\n gsap.to(fx, {\n displacement: hoverDisplacement,\n blur: hoverBlur,\n duration: hoverDuration,\n ease: \"back.out(1.4)\",\n onUpdate: sync,\n });\n gsap.to(button, {\n scale: hoverScale,\n duration: hoverDuration,\n ease: \"back.out(1.4)\",\n });\n };\n\n const onLeave = () => {\n gsap.killTweensOf([fx, button]);\n gsap.to(fx, {\n displacement: displacement,\n blur: blur,\n duration: hoverDuration,\n ease: \"power2.out\",\n onUpdate: sync,\n });\n gsap.to(button, {\n scale: 1,\n duration: hoverDuration,\n ease: \"power2.out\",\n });\n };\n\n const onClick = () => {\n gsap.killTweensOf(button);\n const cur = gsap.getProperty(button, \"scale\") as number;\n gsap.timeline()\n .to(button, { scale: cur * 0.92, duration: 0.08, ease: \"power2.in\" })\n .to(button, { scale: hoverScale, duration: 0.25, ease: \"back.out(2)\" });\n };\n\n button.addEventListener(\"pointerenter\", onEnter);\n button.addEventListener(\"pointerleave\", onLeave);\n button.addEventListener(\"click\", onClick);\n\n return () => {\n button.removeEventListener(\"pointerenter\", onEnter);\n button.removeEventListener(\"pointerleave\", onLeave);\n button.removeEventListener(\"click\", onClick);\n gsap.killTweensOf([button, fx]);\n };\n }, [buttonRef, maps, displacement, blur, hoverScale, hoverDisplacement, hoverBlur, hoverDuration, disableAnimation]);\n\n if (!maps) return null;\n\n return (\n <>\n <button\n ref={buttonRef}\n className={cn(\"relative overflow-hidden shadow-lg cursor-pointer\", className)}\n style={{ width, height, borderRadius: radius, border: \"none\", background: glassColor, ...style }}\n {...props}\n >\n <div\n className=\"absolute inset-0\"\n style={{\n backdropFilter: `url(#${filterId}) brightness(${brightness * 100}%)`,\n WebkitBackdropFilter: `url(#${filterId}) brightness(${brightness * 100}%)`,\n borderRadius: \"inherit\",\n willChange: \"backdrop-filter\",\n transform: \"translateZ(0)\",\n }}\n />\n <div\n className=\"absolute inset-0 inline-flex items-center justify-center font-bold text-white\"\n style={{ background: \"hsl(0 100% 100% / 15%)\", borderRadius: \"inherit\" }}\n >\n {children}\n </div>\n </button>\n\n <svg\n colorInterpolationFilters=\"sRGB\"\n style={{ position: \"absolute\", width: 0, height: 0, overflow: \"hidden\" }}\n aria-hidden=\"true\"\n >\n <defs>\n <filter id={filterId}>\n <feGaussianBlur\n ref={blurRef}\n in=\"SourceGraphic\"\n stdDeviation={blur}\n result=\"blurred_source\"\n />\n <feImage\n href={maps.displacement}\n x=\"0\"\n y=\"0\"\n width={width}\n height={height}\n result=\"displacement_map\"\n />\n <feDisplacementMap\n ref={displacerRef}\n in=\"blurred_source\"\n in2=\"displacement_map\"\n scale={displacement}\n xChannelSelector=\"R\"\n yChannelSelector=\"G\"\n result=\"displaced\"\n />\n <feColorMatrix\n in=\"displaced\"\n type=\"saturate\"\n result=\"displaced_saturated\"\n values={saturation.toString()}\n />\n <feImage\n href={maps.specular}\n x=\"0\"\n y=\"0\"\n width={width}\n height={height}\n result=\"specular_layer\"\n />\n <feGaussianBlur\n in=\"specular_layer\"\n stdDeviation=\"1\"\n result=\"blurred_specular_layer\"\n />\n <feComposite\n in=\"displaced_saturated\"\n in2=\"blurred_specular_layer\"\n operator=\"in\"\n result=\"final_specular_layer\"\n />\n <feBlend\n in=\"final_specular_layer\"\n in2=\"displaced\"\n mode=\"normal\"\n />\n </filter>\n </defs>\n </svg>\n </>\n );\n }\n);\n","export function cn(...classes: (string | undefined | null | false)[]) {\n return classes.filter(Boolean).join(\" \");\n}\n","export interface MapOptions {\n width: number;\n height: number;\n radius?: number;\n edgeSize?: number;\n intensity?: number;\n specularWidth?: number;\n /** Supersampling multiplier for the displacement map (default: 2). Higher = smoother gradients. */\n quality?: number;\n}\n\nconst VERT = `attribute vec4 position; void main(){ gl_Position = position; }`;\n\nconst FRAG = `\nprecision highp float;\nuniform vec2 uRes;\nuniform float uRadius;\nuniform float uBorderSoftness;\nuniform float uSpecularWidth;\nuniform int uMode; // 0 = displacement, 1 = specular\n\nfloat sdRoundedBox(vec2 p, vec2 b, float r){\n r = min(r, min(b.x, b.y));\n vec2 q = abs(p) - b + r;\n return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;\n}\n\nvec3 calcNormal(vec2 p, vec2 b, float r){\n float e = max(0.5, min(b.x, b.y) * 0.01);\n vec2 h = vec2(e, 0.0);\n return normalize(vec3(\n sdRoundedBox(p+h.xy, b, r) - sdRoundedBox(p-h.xy, b, r),\n sdRoundedBox(p+h.yx, b, r) - sdRoundedBox(p-h.yx, b, r),\n -e * 2.0\n ));\n}\n\nvoid main(){\n vec2 p = gl_FragCoord.xy - uRes * 0.5;\n vec2 halfSize = uRes * 0.5 - 1.0;\n float d = sdRoundedBox(p, halfSize, uRadius);\n\n if(d > 0.0){ gl_FragColor = vec4(0.0); return; }\n\n if(uMode == 0){\n vec3 n = calcNormal(p, halfSize, uRadius);\n vec3 nc = n * 0.5 + 0.5;\n float border = smoothstep(-uBorderSoftness, 0.0, d);\n vec3 flat_ = vec3(0.5, 0.5, 1.0);\n gl_FragColor = vec4(mix(flat_, nc, border), 1.0);\n } else {\n float rim = smoothstep(-uSpecularWidth - 2.0, -uSpecularWidth, d)\n * (1.0 - smoothstep(-2.0, 0.0, d));\n float glow = smoothstep(-uBorderSoftness, 0.0, d) * 0.1;\n float s = clamp(rim + glow, 0.0, 1.0);\n gl_FragColor = vec4(vec3(s), s);\n }\n}\n`;\n\nfunction compile(gl: WebGLRenderingContext, type: number, src: string) {\n const s = gl.createShader(type)!;\n gl.shaderSource(s, src);\n gl.compileShader(s);\n return s;\n}\n\nlet _cachedProgram: { gl: WebGLRenderingContext; program: WebGLProgram; canvas: HTMLCanvasElement } | null = null;\n\nfunction getGL() {\n if (_cachedProgram) return _cachedProgram;\n\n const canvas = document.createElement(\"canvas\");\n const gl = canvas.getContext(\"webgl\", { preserveDrawingBuffer: true, premultipliedAlpha: false })!;\n\n const vs = compile(gl, gl.VERTEX_SHADER, VERT);\n const fs = compile(gl, gl.FRAGMENT_SHADER, FRAG);\n const program = gl.createProgram()!;\n gl.attachShader(program, vs);\n gl.attachShader(program, fs);\n gl.linkProgram(program);\n gl.useProgram(program);\n\n const buf = gl.createBuffer();\n gl.bindBuffer(gl.ARRAY_BUFFER, buf);\n gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);\n const pos = gl.getAttribLocation(program, \"position\");\n gl.enableVertexAttribArray(pos);\n gl.vertexAttribPointer(pos, 2, gl.FLOAT, false, 0, 0);\n\n _cachedProgram = { gl, program, canvas };\n return _cachedProgram;\n}\n\n// ── Cache ──────────────────────────────────────────────────────\n\nconst _mapCache = new Map<string, { displacement: string; specular: string }>();\n\nfunction cacheKey(\n w: number, h: number, r: number, bs: number, sw: number,\n): string {\n return `${w}|${h}|${r}|${bs}|${sw}`;\n}\n\n// ── Render ─────────────────────────────────────────────────────\n\nfunction renderToBlob(\n width: number,\n height: number,\n radius: number,\n borderSoftness: number,\n specularWidth: number,\n mode: number,\n): Promise<string> {\n const { gl, program, canvas } = getGL();\n canvas.width = width;\n canvas.height = height;\n gl.viewport(0, 0, width, height);\n gl.clearColor(0, 0, 0, 0);\n gl.clear(gl.COLOR_BUFFER_BIT);\n gl.useProgram(program);\n\n gl.uniform2f(gl.getUniformLocation(program, \"uRes\"), width, height);\n gl.uniform1f(gl.getUniformLocation(program, \"uRadius\"), radius);\n gl.uniform1f(gl.getUniformLocation(program, \"uBorderSoftness\"), borderSoftness);\n gl.uniform1f(gl.getUniformLocation(program, \"uSpecularWidth\"), specularWidth);\n gl.uniform1i(gl.getUniformLocation(program, \"uMode\"), mode);\n\n gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n\n return new Promise((resolve) => {\n canvas.toBlob((blob) => {\n resolve(URL.createObjectURL(blob!));\n }, \"image/png\");\n });\n}\n\n// ── Public API ─────────────────────────────────────────────────\n\nexport async function generateGlassMaps(opts: MapOptions): Promise<{\n displacement: string;\n specular: string;\n}> {\n const {\n width,\n height,\n radius = 60,\n edgeSize = 30,\n intensity = 0.7,\n specularWidth = 0.02,\n quality = 2,\n } = opts;\n\n const scale = Math.max(1, Math.round(quality));\n const rw = width * scale;\n const rh = height * scale;\n\n const r = Math.min(radius * scale, rw / 2, rh / 2);\n const borderSoftness = edgeSize * intensity * scale;\n const specPx = specularWidth * Math.min(rw, rh);\n\n const key = cacheKey(rw, rh, r, borderSoftness, specPx);\n const cached = _mapCache.get(key);\n if (cached) return cached;\n\n const [displacement, specular] = await Promise.all([\n renderToBlob(rw, rh, r, borderSoftness, specPx, 0),\n renderToBlob(rw, rh, r, borderSoftness, specPx, 1),\n ]);\n\n const result = { displacement, specular };\n _mapCache.set(key, result);\n return result;\n}\n\nexport function getCachedGlassMaps(opts: MapOptions): { displacement: string; specular: string } | null {\n const {\n width,\n height,\n radius = 60,\n edgeSize = 30,\n intensity = 0.7,\n specularWidth = 0.02,\n quality = 2,\n } = opts;\n\n const scale = Math.max(1, Math.round(quality));\n const rw = width * scale;\n const rh = height * scale;\n\n const r = Math.min(radius * scale, rw / 2, rh / 2);\n const borderSoftness = edgeSize * intensity * scale;\n const specPx = specularWidth * Math.min(rw, rh);\n\n return _mapCache.get(cacheKey(rw, rh, r, borderSoftness, specPx)) ?? null;\n}\n\nexport function revokeGlassMaps(maps: { displacement: string; specular: string }) {\n URL.revokeObjectURL(maps.displacement);\n URL.revokeObjectURL(maps.specular);\n}\n"],"mappings":"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","names":["LiquidGlassButton"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marcosdemik/liquidglass",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.5",
|
|
4
4
|
"description": "Liquid Glass UI effect for React - glassmorphism refraction with SVG filters, WebGL and GSAP animations",
|
|
5
5
|
"author": "Marcos Demik <marcosdemik>",
|
|
6
6
|
"license": "MIT",
|