liqvued 0.1.2 → 0.2.1

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
@@ -15,8 +15,8 @@ Inspired by [Liquid Glass CSS/SVG](https://kube.io/blog/liquid-glass-css-svg/)
15
15
  ## Features
16
16
 
17
17
  - **Real-time SVG filter generation** — displacement maps computed via Canvas API
18
- - **Multiple surface types** — convex, concave, lip
19
- - **Configurable optics** — bezel width, corner radius, displacement thickness, refraction index, glare angle
18
+ - **Multiple surface types** — convex, concave, lip, bowl, bevel, saddle, ripple, noise, asymmetric
19
+ - **Configurable optics** — bezel width, corner radius, displacement thickness, refraction, magnification, focus, glare angle
20
20
  - **Built-in glassmorphism styling** — gradient backgrounds, inset shadows, highlight overlays
21
21
  - **TypeScript** — full type definitions
22
22
  - **Lightweight** — minimal runtime dependencies beyond Vue
@@ -60,10 +60,13 @@ import { Liqvued } from 'liqvued'
60
60
  | bezel | number | 22 | Width of the displacement bezel in pixels |
61
61
  | thickness | number | 42 | Maximum displacement magnitude in pixels |
62
62
  | refraction | number | 1 | Refraction index multiplier |
63
+ | refractionMode | string | `'edge'` | Refraction direction mode: `edge`, `center`, or `split` |
64
+ | magnification | number | 0 | Center magnification amount. Positive values enlarge, negative values shrink |
65
+ | magnificationFocus | number | 0.82 | Focus radius of the magnification area. Lower values concentrate the effect near center |
63
66
  | blur | number | 0.4 | CSS backdrop-filter blur amount |
64
67
  | surface | string | `'convex'` | Glass surface profile shape |
65
68
  | specularOpacity | number | 0.45 | Opacity of the specular highlight |
66
- | glareAngle | number | -60 | Light source angle in degrees for specular highlight |
69
+ | glareAngle | number \| false | -60 | Light source angle in degrees for specular highlight. Set `false` to disable |
67
70
  | glassBackground | string | — | Glass panel background color (auto-derived from `asProps.color` when available) |
68
71
  | fallbackOnly | boolean | `false` | When `true`, disables the SVG displacement effect and uses only CSS blur |
69
72
  | as | string \| Component | `'div'` | HTML tag or Vue component to render as (rendered as a child of the glass root) |
@@ -74,6 +77,18 @@ import { Liqvued } from 'liqvued'
74
77
  - `convex` — squircle convex profile — soft pill-like edges
75
78
  - `concave` — inverted convex — inward-curving dish-like refraction
76
79
  - `lip` — smooth transition from convex at center to concave at edge
80
+ - `bowl` — deeper concave profile with a more even inward pull
81
+ - `bevel` — linear edge ramp for harder, cleaner refraction
82
+ - `saddle` — mirrored S-curve that shifts direction through the middle
83
+ - `ripple` — small wave modulation across the edge band
84
+ - `noise` — irregular organic edge distortion
85
+ - `asymmetric` — biased profile with more weight toward one side of the edge band
86
+
87
+ ### Refraction Modes
88
+
89
+ - `edge` — original behavior; displacement follows the rounded edge normal
90
+ - `center` — displacement points inward from each edge toward the shape centerline
91
+ - `split` — outer and inner bezel halves bend in opposite directions with a neutral midpoint
77
92
 
78
93
  ## Dynamic Element Rendering
79
94
 
@@ -98,7 +113,7 @@ The fallback applies `backdrop-filter: blur(12px)` with a subtle border and shad
98
113
 
99
114
  ## How It Works
100
115
 
101
- The component generates a per-pixel displacement map as an RGBA PNG (red/green channels encode X/Y displacement vectors), injects it into an SVG `<feDisplacementMap>` filter, and applies it via `backdrop-filter` combined with CSS blur. The surface profile functions (`convex`, `concave`, `lip`) define the slope at each point along the bezel, which determines the displacement magnitude. A separate specular highlight map is generated and composited via `feBlend` to simulate surface lighting.
116
+ The component generates a per-pixel displacement map as an RGBA PNG (red/green channels encode X/Y displacement vectors), injects it into an SVG `<feDisplacementMap>` filter, and applies it via `backdrop-filter` combined with CSS blur. The surface profile functions define the edge refraction strength along the bezel, while `refractionMode` controls the displacement direction. `magnification` and `magnificationFocus` control an optional center lens effect. A separate specular highlight map is generated and composited via `feBlend` to simulate surface lighting.
102
117
 
103
118
  ## License
104
119
 
package/dist/liqvued.css CHANGED
@@ -1,2 +1,2 @@
1
- .liquid-glass[data-v-01d40041]{isolation:isolate;position:relative;overflow:hidden}.liquid-glass__svg[data-v-01d40041]{pointer-events:none;width:100%;height:100%;position:absolute;inset:0}@supports not ((-webkit-backdrop-filter:url("#x")) or (backdrop-filter:url("#x"))){.liquid-glass[data-v-01d40041]{-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px)}}
1
+ .liquid-glass[data-v-5607a45c]{isolation:isolate;position:relative;overflow:hidden}.liquid-glass__svg[data-v-5607a45c]{pointer-events:none;width:100%;height:100%;position:absolute;inset:0}@supports not ((-webkit-backdrop-filter:url("#x")) or (backdrop-filter:url("#x"))){.liquid-glass[data-v-5607a45c]{-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px)}}
2
2
  /*$vite$:1*/
package/dist/liqvued.mjs CHANGED
@@ -1,40 +1,78 @@
1
- import { computed as R, createBlock as ie, createCommentVNode as ce, createElementBlock as Q, createElementVNode as v, defineComponent as de, guardReactiveProps as he, inject as fe, nextTick as X, normalizeProps as pe, normalizeStyle as ve, onBeforeUnmount as ge, onMounted as me, openBlock as L, provide as Me, ref as g, renderSlot as be, resolveDynamicComponent as ye, unref as _e, watch as xe, withCtx as ke } from "vue";
2
- var we = {
1
+ import { computed as C, createBlock as he, createCommentVNode as pe, createElementBlock as ee, createElementVNode as _, defineComponent as ve, guardReactiveProps as ge, inject as me, nextTick as te, normalizeProps as Me, normalizeStyle as ye, onBeforeUnmount as be, onMounted as xe, openBlock as N, provide as _e, ref as k, renderSlot as ke, resolveDynamicComponent as we, unref as Se, watch as Be, withCtx as Ie } from "vue";
2
+ function Re(n, t, i, r, c, y, g) {
3
+ if (n === "center") return t;
4
+ const h = {
5
+ x: -t.x,
6
+ y: -t.y
7
+ };
8
+ if (n === "split") {
9
+ const o = g < 0.5 ? -1 : 1;
10
+ return {
11
+ x: h.x * o,
12
+ y: h.y * o
13
+ };
14
+ }
15
+ return h;
16
+ }
17
+ function $e(n, t) {
18
+ return n !== "split" ? 1 : Math.min(1, Math.abs(t - 0.5) * 2);
19
+ }
20
+ function q(n, t, i, r, c) {
21
+ const y = Math.min(c, Math.min(i, r) / 2), g = i / 2 - y, h = r / 2 - y, o = Math.abs(n - i / 2) - g, I = Math.abs(t - r / 2) - h;
22
+ return -(Math.min(Math.max(o, I), 0) + Math.hypot(Math.max(o, 0), Math.max(I, 0)) - y);
23
+ }
24
+ function Ce(n, t, i, r, c) {
25
+ const g = q(n - 1.5, t, i, r, c) - q(n + 1.5, t, i, r, c), h = q(n, t - 1.5, i, r, c) - q(n, t + 1.5, i, r, c), o = Math.hypot(g, h);
26
+ return o < 1e-10 ? {
27
+ x: 0,
28
+ y: -1
29
+ } : {
30
+ x: g / o,
31
+ y: h / o
32
+ };
33
+ }
34
+ var qe = {
3
35
  key: 0,
4
36
  class: "liquid-glass__svg",
5
37
  "aria-hidden": "true"
6
- }, Re = [
38
+ }, Pe = [
7
39
  "href",
8
40
  "width",
9
41
  "height"
10
- ], Be = ["scale"], Ie = [
42
+ ], Fe = ["scale"], Oe = [
11
43
  "href",
12
44
  "width",
13
45
  "height"
14
- ], Se = /* @__PURE__ */ de({
46
+ ], De = /* @__PURE__ */ ve({
15
47
  __name: "Liqvued",
16
48
  props: {
17
49
  as: { default: "div" },
18
50
  asProps: { default: () => ({}) },
19
51
  radius: { default: 32 },
20
- borderRadius: {},
52
+ borderRadius: { default: void 0 },
21
53
  bezel: { default: 22 },
22
54
  thickness: { default: 42 },
23
55
  refraction: { default: 1 },
56
+ refractionMode: { default: "edge" },
57
+ magnification: { default: 0 },
58
+ magnificationFocus: { default: 0.82 },
24
59
  blur: { default: 0.4 },
25
60
  surface: { default: "convex" },
26
61
  specularOpacity: { default: 0.45 },
27
- glareAngle: { default: -60 },
62
+ glareAngle: {
63
+ type: [Number, Boolean],
64
+ default: -60
65
+ },
28
66
  glassBackground: { default: void 0 },
29
67
  fallbackOnly: {
30
68
  type: Boolean,
31
69
  default: !1
32
70
  }
33
71
  },
34
- setup(d) {
35
- const a = d, x = g(null), m = g(1), M = g(1), E = g(""), F = g(""), U = g(1), b = CSS.supports?.("backdrop-filter", "url(#x)") && /Chrome/.test(navigator.userAgent), T = `${`lg-${Math.random().toString(36).slice(2)}`}-filter`, A = fe("_liqvued", !1);
36
- Me("_liqvued", !0);
37
- const y = g(!1), G = {
72
+ setup(n) {
73
+ const t = n, i = k(null), r = k(1), c = k(1), y = k(""), g = k(""), h = k(1), o = typeof CSS < "u" && typeof navigator < "u" && CSS.supports?.("backdrop-filter", "url(#x)") && /Chrome/.test(navigator.userAgent), I = `${`lg-${Math.random().toString(36).slice(2)}`}-filter`, W = me("_liqvued", !1);
74
+ _e("_liqvued", !0);
75
+ const w = k(!1), V = {
38
76
  0: 0,
39
77
  sm: 4,
40
78
  md: 8,
@@ -44,92 +82,110 @@ var we = {
44
82
  circle: 9999,
45
83
  shaped: 16
46
84
  };
47
- function W() {
48
- return x.value;
85
+ function j() {
86
+ return i.value;
49
87
  }
50
- function k(e, t = 0, n = 1) {
51
- return Math.max(t, Math.min(n, e));
88
+ function u(e, a = 0, s = 1) {
89
+ return Math.max(a, Math.min(s, e));
52
90
  }
53
- function Y(e) {
54
- return e = k(e), e * e * e * (e * (e * 6 - 15) + 10);
91
+ function R(e) {
92
+ return e = u(e), e * e * e * (e * (e * 6 - 15) + 10);
55
93
  }
56
- function B(e) {
57
- return Math.pow(1 - Math.pow(1 - k(e), 4), 1 / 4);
94
+ function P(e) {
95
+ return Math.pow(1 - Math.pow(1 - u(e), 4), 1 / 4);
58
96
  }
59
- function N(e) {
60
- if (a.surface === "concave") return 1 - B(e);
61
- if (a.surface === "lip") {
62
- const t = Y(e);
63
- return B(e) * (1 - t) + (1 - B(e)) * t;
64
- }
65
- return B(e);
97
+ function ae(e, a, s) {
98
+ return u(e + Math.sin(u(e) * Math.PI * a) * s);
66
99
  }
67
- function Z(e) {
68
- return (N(e + 1e-3) - N(e - 1e-3)) / (2 * 1e-3);
100
+ function ne(e) {
101
+ const a = u(e);
102
+ return u(a + Math.sin(a * 39.1) * 0.06 + Math.sin(a * 91.7) * 0.035);
69
103
  }
70
- function ee(e) {
71
- const r = Math.atan(e), c = 1 / 1.5 * Math.sin(r), l = Math.asin(k(c, -1, 1));
72
- return Math.tan(r - l);
104
+ function re(e, a, s, v, b, S) {
105
+ const m = s / 2, M = v / 2, $ = Math.max(1, s / 2), x = Math.max(1, v / 2), D = (e - m) / $, z = (a - M) / x, l = Math.hypot(D, z), d = u(S, 0.2, 1.4), p = Math.pow(R(1 - u(l / d)), 1.6), f = b >= 0 ? 1 + b * p : 1 / (1 + Math.abs(b) * p);
106
+ return {
107
+ x: (e - m) / f - (e - m),
108
+ y: (a - M) / f - (a - M)
109
+ };
73
110
  }
74
- function te(e, t, n, r, c) {
75
- const l = Math.min(c, Math.min(n, r) / 2), s = n / 2 - l, u = r / 2 - l, i = Math.abs(e - n / 2) - s, o = Math.abs(t - r / 2) - u;
76
- return -(Math.min(Math.max(i, o), 0) + Math.hypot(Math.max(i, 0), Math.max(o, 0)) - l);
111
+ function H(e) {
112
+ const a = u(e);
113
+ switch (t.surface) {
114
+ case "concave":
115
+ case "bowl":
116
+ return 1 - P(a);
117
+ case "lip":
118
+ return P(a) * (1 - R(a)) + (1 - P(a)) * R(a);
119
+ case "bevel":
120
+ return a;
121
+ case "saddle":
122
+ return 0.5 + Math.sin((a - 0.5) * Math.PI) * 0.5;
123
+ case "ripple":
124
+ return ae(a, 3, 0.12);
125
+ case "noise":
126
+ return ne(a);
127
+ case "asymmetric":
128
+ return u(Math.pow(a, 0.65) * 0.65 + R(a) * 0.35);
129
+ case "convex":
130
+ return P(a);
131
+ }
77
132
  }
78
- function ae(e, t, n, r) {
79
- const c = Math.min(e, n - 1 - e), l = Math.min(t, r - 1 - t), s = 4, u = Math.pow(c, s) + Math.pow(l, s);
80
- let i, o;
81
- u > 1e-10 ? (i = (e > n / 2 ? 1 : -1) * (Math.pow(l, s) / u), o = (t > r / 2 ? 1 : -1) * (Math.pow(c, s) / u)) : (i = e > n / 2 ? 1 : -1, o = t > r / 2 ? 1 : -1);
82
- const _ = Math.hypot(i, o);
83
- return _ < 1e-10 ? {
84
- x: 0,
85
- y: -1
86
- } : {
87
- x: i / _,
88
- y: o / _
89
- };
133
+ function oe(e) {
134
+ return (H(e + 1e-3) - H(e - 1e-3)) / (2 * 1e-3);
135
+ }
136
+ function se(e) {
137
+ const v = Math.atan(e), b = 1 / 1.5 * Math.sin(v), S = Math.asin(u(b, -1, 1));
138
+ return Math.tan(v - S);
90
139
  }
91
- function V(e) {
140
+ function J(e) {
92
141
  return e.toDataURL("image/png");
93
142
  }
94
- function C() {
95
- if (a.fallbackOnly) return;
96
- const e = Math.max(1, Math.round(m.value)), t = Math.max(1, Math.round(M.value)), n = document.createElement("canvas");
97
- n.width = e, n.height = t;
98
- const r = document.createElement("canvas");
99
- r.width = e, r.height = t;
100
- const c = n.getContext("2d"), l = r.getContext("2d"), s = c.createImageData(e, t), u = l.createImageData(e, t), i = [];
101
- let o = 1;
102
- const _ = {
103
- x: Math.cos(a.glareAngle * Math.PI / 180),
104
- y: Math.sin(a.glareAngle * Math.PI / 180)
105
- };
106
- for (let h = 0; h < t; h++) for (let f = 0; f < e; f++) {
107
- const w = te(f, h, e, t, q.value), p = k(w / a.bezel);
108
- let z = 0, D = 0, J = 0;
109
- if (w >= 0 && w < a.bezel) {
110
- const $ = ae(f, h, e, t), K = ee(Z(p)) * a.thickness * (1 - p) * a.refraction;
111
- z = -$.x * K, D = -$.y * K, o = Math.max(o, Math.abs(z), Math.abs(D));
112
- const ue = k($.x * _.x + $.y * _.y, 0, 1);
113
- J = Math.pow(ue, 18) * a.specularOpacity;
143
+ function A() {
144
+ if (t.fallbackOnly) return;
145
+ const e = Math.max(1, Math.round(r.value)), a = Math.max(1, Math.round(c.value)), s = document.createElement("canvas");
146
+ s.width = e, s.height = a;
147
+ const v = document.createElement("canvas");
148
+ v.width = e, v.height = a;
149
+ const b = s.getContext("2d"), S = v.getContext("2d"), m = b.createImageData(e, a), M = S.createImageData(e, a), $ = [];
150
+ let x = 1;
151
+ const D = t.glareAngle !== !1 ? {
152
+ x: Math.cos(t.glareAngle * Math.PI / 180),
153
+ y: Math.sin(t.glareAngle * Math.PI / 180)
154
+ } : {
155
+ x: 0,
156
+ y: 0
157
+ }, z = Math.max(1, Math.min(t.bezel, Math.min(e, a) * 0.28));
158
+ for (let l = 0; l < a; l++) for (let d = 0; d < e; d++) {
159
+ const p = q(d, l, e, a, F.value), f = u(p / z);
160
+ let L = 0, E = 0, X = 0;
161
+ if (p >= 0 && p < z) {
162
+ const B = Ce(d, l, e, a, F.value), Y = se(oe(f)) * t.thickness * R(1 - f) * $e(t.refractionMode, f) * t.refraction, Z = Re(t.refractionMode, B, d, l, e, a, f);
163
+ L = Z.x * Y, E = Z.y * Y, x = Math.max(x, Math.abs(L), Math.abs(E));
164
+ const fe = u(B.x * D.x + B.y * D.y, 0, 1);
165
+ X = Math.pow(fe, 18) * t.specularOpacity;
166
+ }
167
+ if (t.magnification !== 0 && p >= 0) {
168
+ const B = re(d, l, e, a, t.magnification, t.magnificationFocus);
169
+ L += B.x, E += B.y;
114
170
  }
115
- i.push([z, D]);
116
- const S = (h * e + f) * 4, le = Math.round(J * 255);
117
- u.data[S] = 255, u.data[S + 1] = 255, u.data[S + 2] = 255, u.data[S + 3] = le;
171
+ $.push([L, E]);
172
+ const U = (l * e + d) * 4, de = Math.round(X * 255);
173
+ M.data[U] = 255, M.data[U + 1] = 255, M.data[U + 2] = 255, M.data[U + 3] = de;
118
174
  }
119
- i.forEach(([h, f], w) => {
120
- const p = w * 4;
121
- s.data[p] = Math.round(128 + h / o * 127), s.data[p + 1] = Math.round(128 + f / o * 127), s.data[p + 2] = 128, s.data[p + 3] = 255;
122
- }), c.putImageData(s, 0, 0), l.putImageData(u, 0, 0), E.value = V(n), F.value = V(r), U.value = o;
175
+ x = $.reduce((l, [d, p]) => Math.max(l, Math.abs(d), Math.abs(p)), 1), $.forEach(([l, d], p) => {
176
+ const f = p * 4;
177
+ m.data[f] = Math.round(128 + l / x * 127), m.data[f + 1] = Math.round(128 + d / x * 127), m.data[f + 2] = 128, m.data[f + 3] = 255;
178
+ }), b.putImageData(m, 0, 0), S.putImageData(M, 0, 0), y.value = J(s), g.value = J(v), h.value = x;
123
179
  }
124
- function j() {
125
- const e = W();
126
- e && (m.value = Math.max(1, e.offsetWidth), M.value = Math.max(1, e.offsetHeight));
180
+ function K() {
181
+ const e = j();
182
+ e && (r.value = Math.max(1, e.offsetWidth), c.value = Math.max(1, e.offsetHeight));
127
183
  }
128
- const H = b ? "rgba(255, 255, 255, 0.08)" : "rgba(255, 255, 255, 0.22)", q = R(() => {
129
- if (a.radius !== 32) return a.radius;
130
- const e = a.asProps;
131
- return typeof e.rounded == "string" && e.rounded in G ? G[e.rounded] : a.radius;
132
- }), ne = R(() => a.borderRadius || `${q.value}px`), re = /* @__PURE__ */ new Set([
184
+ const Q = o ? "rgba(255, 255, 255, 0.08)" : "rgba(255, 255, 255, 0.22)", F = C(() => {
185
+ if (t.radius !== 32) return t.radius;
186
+ const e = t.asProps;
187
+ return typeof e.rounded == "string" && e.rounded in V ? V[e.rounded] : t.radius;
188
+ }), ie = C(() => t.borderRadius || `${F.value}px`), ce = /* @__PURE__ */ new Set([
133
189
  "primary",
134
190
  "secondary",
135
191
  "accent",
@@ -140,60 +196,63 @@ var we = {
140
196
  "surface",
141
197
  "background"
142
198
  ]);
143
- function oe(e, t) {
144
- if (re.has(e)) return `rgba(var(--v-theme-${e}), ${t})`;
199
+ function ue(e, a) {
200
+ if (ce.has(e)) return `rgba(var(--v-theme-${e}), ${a})`;
145
201
  if (e.startsWith("#")) {
146
- const n = e.replace("#", "");
147
- return `rgba(${parseInt(n.slice(0, 2), 16)}, ${parseInt(n.slice(2, 4), 16)}, ${parseInt(n.slice(4, 6), 16)}, ${t})`;
202
+ const s = e.replace("#", "");
203
+ return `rgba(${parseInt(s.slice(0, 2), 16)}, ${parseInt(s.slice(2, 4), 16)}, ${parseInt(s.slice(4, 6), 16)}, ${a})`;
148
204
  }
149
- return e.startsWith("rgb") ? e.replace("rgb(", "rgba(").replace(")", `, ${t})`) : H;
205
+ return e.startsWith("rgb") ? e.replace("rgb(", "rgba(").replace(")", `, ${a})`) : Q;
150
206
  }
151
- const O = R(() => {
152
- if (a.glassBackground !== void 0) return a.glassBackground;
153
- const e = a.asProps;
154
- return typeof e.color == "string" ? oe(e.color, 0.6) : H;
155
- }), I = R(() => a.fallbackOnly || !b ? "blur(12px)" : `url(#${T}) blur(${a.blur}px)`), se = R(() => ({
156
- borderRadius: `${ne.value}`,
157
- ...O.value !== void 0 ? { backgroundColor: O.value } : {},
158
- ...O.value !== void 0 ? { boxShadow: ["inset 0 0 0 1px rgba(255, 255, 255, 0.28)", "0 18px 48px rgba(0, 0, 0, 0.18)"].join(", ") } : {},
159
- ...y.value && !A ? {
160
- backdropFilter: I.value,
161
- WebkitBackdropFilter: I.value
207
+ const T = C(() => {
208
+ if (t.glassBackground !== void 0) return t.glassBackground;
209
+ const e = t.asProps;
210
+ return typeof e.color == "string" ? ue(e.color, 0.6) : Q;
211
+ }), O = C(() => t.fallbackOnly || !o ? "blur(12px)" : `url(#${I}) blur(${t.blur}px)`), le = C(() => ({
212
+ borderRadius: `${ie.value}`,
213
+ ...T.value !== void 0 ? { backgroundColor: T.value } : {},
214
+ ...T.value !== void 0 ? { boxShadow: ["inset 0 0 0 1px rgba(255, 255, 255, 0.28)", "0 18px 48px rgba(0, 0, 0, 0.18)"].join(", ") } : {},
215
+ ...w.value && !W ? {
216
+ backdropFilter: O.value,
217
+ WebkitBackdropFilter: O.value
162
218
  } : {},
163
- ...y.value && A && !b ? {
164
- backdropFilter: I.value,
165
- WebkitBackdropFilter: I.value
219
+ ...w.value && W && !o ? {
220
+ backdropFilter: O.value,
221
+ WebkitBackdropFilter: O.value
166
222
  } : {}
167
223
  }));
168
- let P = null;
169
- return me(async () => {
170
- if (await X(), b) {
171
- j(), C(), await X(), y.value = !0, P = new ResizeObserver(() => {
172
- j(), C(), y.value = !0;
224
+ let G = null;
225
+ return xe(async () => {
226
+ if (await te(), o) {
227
+ K(), A(), await te(), w.value = !0, G = new ResizeObserver(() => {
228
+ K(), A(), w.value = !0;
173
229
  });
174
- const e = W();
175
- e && P.observe(e);
176
- } else y.value = !0;
177
- }), xe(() => [
178
- q.value,
179
- a.bezel,
180
- a.thickness,
181
- a.refraction,
182
- a.surface,
183
- a.specularOpacity,
184
- a.glareAngle,
185
- a.fallbackOnly
230
+ const e = j();
231
+ e && G.observe(e);
232
+ } else w.value = !0;
233
+ }), Be(() => [
234
+ F.value,
235
+ t.bezel,
236
+ t.thickness,
237
+ t.refraction,
238
+ t.refractionMode,
239
+ t.magnification,
240
+ t.magnificationFocus,
241
+ t.surface,
242
+ t.specularOpacity,
243
+ t.glareAngle,
244
+ t.fallbackOnly
186
245
  ], () => {
187
- b && C(), y.value = !0;
188
- }), ge(() => {
189
- P?.disconnect();
190
- }), (e, t) => (L(), Q("div", {
246
+ o && A(), w.value = !0;
247
+ }), be(() => {
248
+ G?.disconnect();
249
+ }), (e, a) => (N(), ee("div", {
191
250
  ref_key: "root",
192
- ref: x,
251
+ ref: i,
193
252
  class: "liquid-glass",
194
- style: ve(se.value)
195
- }, [_e(b) ? (L(), Q("svg", we, [v("defs", null, [v("filter", {
196
- id: T,
253
+ style: ye(le.value)
254
+ }, [Se(o) ? (N(), ee("svg", qe, [_("defs", null, [_("filter", {
255
+ id: I,
197
256
  "color-interpolation-filters": "sRGB",
198
257
  filterUnits: "userSpaceOnUse",
199
258
  x: "0",
@@ -201,53 +260,53 @@ var we = {
201
260
  width: "10000",
202
261
  height: "10000"
203
262
  }, [
204
- v("feImage", {
205
- href: E.value,
263
+ _("feImage", {
264
+ href: y.value,
206
265
  x: "0",
207
266
  y: "0",
208
- width: m.value,
209
- height: M.value,
267
+ width: r.value,
268
+ height: c.value,
210
269
  result: "displacement_map"
211
- }, null, 8, Re),
212
- t[0] || (t[0] = v("feGaussianBlur", {
270
+ }, null, 8, Pe),
271
+ a[0] || (a[0] = _("feGaussianBlur", {
213
272
  in: "displacement_map",
214
273
  stdDeviation: "2",
215
274
  result: "smooth_disp"
216
275
  }, null, -1)),
217
- v("feDisplacementMap", {
276
+ _("feDisplacementMap", {
218
277
  in: "SourceGraphic",
219
278
  in2: "smooth_disp",
220
- scale: U.value,
279
+ scale: h.value,
221
280
  xChannelSelector: "R",
222
281
  yChannelSelector: "G",
223
282
  result: "refracted"
224
- }, null, 8, Be),
225
- v("feImage", {
226
- href: F.value,
283
+ }, null, 8, Fe),
284
+ _("feImage", {
285
+ href: g.value,
227
286
  x: "0",
228
287
  y: "0",
229
- width: m.value,
230
- height: M.value,
288
+ width: r.value,
289
+ height: c.value,
231
290
  result: "specular"
232
- }, null, 8, Ie),
233
- t[1] || (t[1] = v("feBlend", {
291
+ }, null, 8, Oe),
292
+ a[1] || (a[1] = _("feBlend", {
234
293
  in: "refracted",
235
294
  in2: "specular",
236
295
  mode: "screen"
237
296
  }, null, -1))
238
- ])])])) : ce("", !0), (L(), ie(ye(d.as), pe(he(d.asProps)), {
239
- default: ke(() => [be(e.$slots, "default", {}, void 0, !0)]),
297
+ ])])])) : pe("", !0), (N(), he(we(n.as), Me(ge(n.asProps)), {
298
+ default: Ie(() => [ke(e.$slots, "default", {}, void 0, !0)]),
240
299
  _: 3
241
300
  }, 16))], 4));
242
301
  }
243
- }), $e = (d, a) => {
244
- const x = d.__vccOpts || d;
245
- for (const [m, M] of a) x[m] = M;
246
- return x;
247
- }, Ce = /* @__PURE__ */ $e(Se, [["__scopeId", "data-v-01d40041"]]), Oe = { install(d) {
248
- d.component("Liqvued", Ce);
302
+ }), ze = (n, t) => {
303
+ const i = n.__vccOpts || n;
304
+ for (const [r, c] of t) i[r] = c;
305
+ return i;
306
+ }, Le = /* @__PURE__ */ ze(De, [["__scopeId", "data-v-5607a45c"]]), Ue = { install(n) {
307
+ n.component("Liqvued", Le);
249
308
  } };
250
309
  export {
251
- Ce as Liqvued,
252
- Oe as default
310
+ Le as Liqvued,
311
+ Ue as default
253
312
  };
@@ -1 +1 @@
1
- (function(f,t){typeof exports=="object"&&typeof module<"u"?t(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],t):(f=typeof globalThis<"u"?globalThis:f||self,t(f.Liqvued={},f.Vue))})(this,function(f,t){Object.defineProperties(f,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});var K={key:0,class:"liquid-glass__svg","aria-hidden":"true"},Q=["href","width","height"],X=["scale"],Y=["href","width","height"],Z=(0,t.defineComponent)({__name:"Liqvued",props:{as:{default:"div"},asProps:{default:()=>({})},radius:{default:32},borderRadius:{},bezel:{default:22},thickness:{default:42},refraction:{default:1},blur:{default:.4},surface:{default:"convex"},specularOpacity:{default:.45},glareAngle:{default:-60},glassBackground:{default:void 0},fallbackOnly:{type:Boolean,default:!1}},setup(h){const n=h,k=(0,t.ref)(null),v=(0,t.ref)(1),M=(0,t.ref)(1),V=(0,t.ref)(""),L=(0,t.ref)(""),N=(0,t.ref)(1),b=CSS.supports?.("backdrop-filter","url(#x)")&&/Chrome/.test(navigator.userAgent),z=`${`lg-${Math.random().toString(36).slice(2)}`}-filter`,D=(0,t.inject)("_liqvued",!1);(0,t.provide)("_liqvued",!0);const y=(0,t.ref)(!1),F={0:0,sm:4,md:8,lg:12,xl:16,pill:9999,circle:9999,shaped:16};function U(){return k.value}function x(e,a=0,r=1){return Math.max(a,Math.min(r,e))}function ae(e){return e=x(e),e*e*e*(e*(e*6-15)+10)}function B(e){return Math.pow(1-Math.pow(1-x(e),4),1/4)}function A(e){if(n.surface==="concave")return 1-B(e);if(n.surface==="lip"){const a=ae(e);return B(e)*(1-a)+(1-B(e))*a}return B(e)}function ne(e){return(A(e+.001)-A(e-.001))/(2*.001)}function re(e){const o=Math.atan(e),d=1/1.5*Math.sin(o),i=Math.asin(x(d,-1,1));return Math.tan(o-i)}function oe(e,a,r,o,d){const i=Math.min(d,Math.min(r,o)/2),s=r/2-i,c=o/2-i,u=Math.abs(e-r/2)-s,l=Math.abs(a-o/2)-c;return-(Math.min(Math.max(u,l),0)+Math.hypot(Math.max(u,0),Math.max(l,0))-i)}function le(e,a,r,o){const d=Math.min(e,r-1-e),i=Math.min(a,o-1-a),s=4,c=Math.pow(d,s)+Math.pow(i,s);let u,l;c>1e-10?(u=(e>r/2?1:-1)*(Math.pow(i,s)/c),l=(a>o/2?1:-1)*(Math.pow(d,s)/c)):(u=e>r/2?1:-1,l=a>o/2?1:-1);const _=Math.hypot(u,l);return _<1e-10?{x:0,y:-1}:{x:u/_,y:l/_}}function G(e){return e.toDataURL("image/png")}function E(){if(n.fallbackOnly)return;const e=Math.max(1,Math.round(v.value)),a=Math.max(1,Math.round(M.value)),r=document.createElement("canvas");r.width=e,r.height=a;const o=document.createElement("canvas");o.width=e,o.height=a;const d=r.getContext("2d"),i=o.getContext("2d"),s=d.createImageData(e,a),c=i.createImageData(e,a),u=[];let l=1;const _={x:Math.cos(n.glareAngle*Math.PI/180),y:Math.sin(n.glareAngle*Math.PI/180)};for(let p=0;p<a;p++)for(let m=0;m<e;m++){const w=oe(m,p,e,a,$.value),g=x(w/n.bezel);let O=0,P=0,H=0;if(w>=0&&w<n.bezel){const I=le(m,p,e,a),J=re(ne(g))*n.thickness*(1-g)*n.refraction;O=-I.x*J,P=-I.y*J,l=Math.max(l,Math.abs(O),Math.abs(P));const fe=x(I.x*_.x+I.y*_.y,0,1);H=Math.pow(fe,18)*n.specularOpacity}u.push([O,P]);const R=(p*e+m)*4,de=Math.round(H*255);c.data[R]=255,c.data[R+1]=255,c.data[R+2]=255,c.data[R+3]=de}u.forEach(([p,m],w)=>{const g=w*4;s.data[g]=Math.round(128+p/l*127),s.data[g+1]=Math.round(128+m/l*127),s.data[g+2]=128,s.data[g+3]=255}),d.putImageData(s,0,0),i.putImageData(c,0,0),V.value=G(r),L.value=G(o),N.value=l}function W(){const e=U();e&&(v.value=Math.max(1,e.offsetWidth),M.value=Math.max(1,e.offsetHeight))}const j=b?"rgba(255, 255, 255, 0.08)":"rgba(255, 255, 255, 0.22)",$=(0,t.computed)(()=>{if(n.radius!==32)return n.radius;const e=n.asProps;return typeof e.rounded=="string"&&e.rounded in F?F[e.rounded]:n.radius}),se=(0,t.computed)(()=>n.borderRadius||`${$.value}px`),ie=new Set(["primary","secondary","accent","info","warning","error","success","surface","background"]);function ce(e,a){if(ie.has(e))return`rgba(var(--v-theme-${e}), ${a})`;if(e.startsWith("#")){const r=e.replace("#","");return`rgba(${parseInt(r.slice(0,2),16)}, ${parseInt(r.slice(2,4),16)}, ${parseInt(r.slice(4,6),16)}, ${a})`}return e.startsWith("rgb")?e.replace("rgb(","rgba(").replace(")",`, ${a})`):j}const q=(0,t.computed)(()=>{if(n.glassBackground!==void 0)return n.glassBackground;const e=n.asProps;return typeof e.color=="string"?ce(e.color,.6):j}),S=(0,t.computed)(()=>n.fallbackOnly||!b?"blur(12px)":`url(#${z}) blur(${n.blur}px)`),ue=(0,t.computed)(()=>({borderRadius:`${se.value}`,...q.value!==void 0?{backgroundColor:q.value}:{},...q.value!==void 0?{boxShadow:["inset 0 0 0 1px rgba(255, 255, 255, 0.28)","0 18px 48px rgba(0, 0, 0, 0.18)"].join(", ")}:{},...y.value&&!D?{backdropFilter:S.value,WebkitBackdropFilter:S.value}:{},...y.value&&D&&!b?{backdropFilter:S.value,WebkitBackdropFilter:S.value}:{}}));let C=null;return(0,t.onMounted)(async()=>{if(await(0,t.nextTick)(),b){W(),E(),await(0,t.nextTick)(),y.value=!0,C=new ResizeObserver(()=>{W(),E(),y.value=!0});const e=U();e&&C.observe(e)}else y.value=!0}),(0,t.watch)(()=>[$.value,n.bezel,n.thickness,n.refraction,n.surface,n.specularOpacity,n.glareAngle,n.fallbackOnly],()=>{b&&E(),y.value=!0}),(0,t.onBeforeUnmount)(()=>{C?.disconnect()}),(e,a)=>((0,t.openBlock)(),(0,t.createElementBlock)("div",{ref_key:"root",ref:k,class:"liquid-glass",style:(0,t.normalizeStyle)(ue.value)},[(0,t.unref)(b)?((0,t.openBlock)(),(0,t.createElementBlock)("svg",K,[(0,t.createElementVNode)("defs",null,[(0,t.createElementVNode)("filter",{id:z,"color-interpolation-filters":"sRGB",filterUnits:"userSpaceOnUse",x:"0",y:"0",width:"10000",height:"10000"},[(0,t.createElementVNode)("feImage",{href:V.value,x:"0",y:"0",width:v.value,height:M.value,result:"displacement_map"},null,8,Q),a[0]||(a[0]=(0,t.createElementVNode)("feGaussianBlur",{in:"displacement_map",stdDeviation:"2",result:"smooth_disp"},null,-1)),(0,t.createElementVNode)("feDisplacementMap",{in:"SourceGraphic",in2:"smooth_disp",scale:N.value,xChannelSelector:"R",yChannelSelector:"G",result:"refracted"},null,8,X),(0,t.createElementVNode)("feImage",{href:L.value,x:"0",y:"0",width:v.value,height:M.value,result:"specular"},null,8,Y),a[1]||(a[1]=(0,t.createElementVNode)("feBlend",{in:"refracted",in2:"specular",mode:"screen"},null,-1))])])])):(0,t.createCommentVNode)("",!0),((0,t.openBlock)(),(0,t.createBlock)((0,t.resolveDynamicComponent)(h.as),(0,t.normalizeProps)((0,t.guardReactiveProps)(h.asProps)),{default:(0,t.withCtx)(()=>[(0,t.renderSlot)(e.$slots,"default",{},void 0,!0)]),_:3},16))],4))}}),ee=(h,n)=>{const k=h.__vccOpts||h;for(const[v,M]of n)k[v]=M;return k},T=ee(Z,[["__scopeId","data-v-01d40041"]]),te={install(h){h.component("Liqvued",T)}};f.Liqvued=T,f.default=te});
1
+ (function(b,n){typeof exports=="object"&&typeof module<"u"?n(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],n):(b=typeof globalThis<"u"?globalThis:b||self,n(b.Liqvued={},b.Vue))})(this,function(b,n){Object.defineProperties(b,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});function Z(r,t,c,o,l,x,M){if(r==="center")return t;const p={x:-t.x,y:-t.y};if(r==="split"){const s=M<.5?-1:1;return{x:p.x*s,y:p.y*s}}return p}function ee(r,t){return r!=="split"?1:Math.min(1,Math.abs(t-.5)*2)}function I(r,t,c,o,l){const x=Math.min(l,Math.min(c,o)/2),M=c/2-x,p=o/2-x,s=Math.abs(r-c/2)-M,R=Math.abs(t-o/2)-p;return-(Math.min(Math.max(s,R),0)+Math.hypot(Math.max(s,0),Math.max(R,0))-x)}function te(r,t,c,o,l){const M=I(r-1.5,t,c,o,l)-I(r+1.5,t,c,o,l),p=I(r,t-1.5,c,o,l)-I(r,t+1.5,c,o,l),s=Math.hypot(M,p);return s<1e-10?{x:0,y:-1}:{x:M/s,y:p/s}}var ae={key:0,class:"liquid-glass__svg","aria-hidden":"true"},ne=["href","width","height"],re=["scale"],oe=["href","width","height"],se=(0,n.defineComponent)({__name:"Liqvued",props:{as:{default:"div"},asProps:{default:()=>({})},radius:{default:32},borderRadius:{default:void 0},bezel:{default:22},thickness:{default:42},refraction:{default:1},refractionMode:{default:"edge"},magnification:{default:0},magnificationFocus:{default:.82},blur:{default:.4},surface:{default:"convex"},specularOpacity:{default:.45},glareAngle:{type:[Number,Boolean],default:-60},glassBackground:{default:void 0},fallbackOnly:{type:Boolean,default:!1}},setup(r){const t=r,c=(0,n.ref)(null),o=(0,n.ref)(1),l=(0,n.ref)(1),x=(0,n.ref)(""),M=(0,n.ref)(""),p=(0,n.ref)(1),s=typeof CSS<"u"&&typeof navigator<"u"&&CSS.supports?.("backdrop-filter","url(#x)")&&/Chrome/.test(navigator.userAgent),R=`${`lg-${Math.random().toString(36).slice(2)}`}-filter`,A=(0,n.inject)("_liqvued",!1);(0,n.provide)("_liqvued",!0);const w=(0,n.ref)(!1),G={0:0,sm:4,md:8,lg:12,xl:16,pill:9999,circle:9999,shaped:16};function W(){return c.value}function u(e,a=0,i=1){return Math.max(a,Math.min(i,e))}function E(e){return e=u(e),e*e*e*(e*(e*6-15)+10)}function q(e){return Math.pow(1-Math.pow(1-u(e),4),1/4)}function le(e,a,i){return u(e+Math.sin(u(e)*Math.PI*a)*i)}function ue(e){const a=u(e);return u(a+Math.sin(a*39.1)*.06+Math.sin(a*91.7)*.035)}function de(e,a,i,g,_,B){const v=i/2,y=g/2,$=Math.max(1,i/2),k=Math.max(1,g/2),N=(e-v)/$,O=(a-y)/k,d=Math.hypot(N,O),f=u(B,.2,1.4),m=Math.pow(E(1-u(d/f)),1.6),h=_>=0?1+_*m:1/(1+Math.abs(_)*m);return{x:(e-v)/h-(e-v),y:(a-y)/h-(a-y)}}function j(e){const a=u(e);switch(t.surface){case"concave":case"bowl":return 1-q(a);case"lip":return q(a)*(1-E(a))+(1-q(a))*E(a);case"bevel":return a;case"saddle":return .5+Math.sin((a-.5)*Math.PI)*.5;case"ripple":return le(a,3,.12);case"noise":return ue(a);case"asymmetric":return u(Math.pow(a,.65)*.65+E(a)*.35);case"convex":return q(a)}}function fe(e){return(j(e+.001)-j(e-.001))/(2*.001)}function he(e){const g=Math.atan(e),_=1/1.5*Math.sin(g),B=Math.asin(u(_,-1,1));return Math.tan(g-B)}function H(e){return e.toDataURL("image/png")}function D(){if(t.fallbackOnly)return;const e=Math.max(1,Math.round(o.value)),a=Math.max(1,Math.round(l.value)),i=document.createElement("canvas");i.width=e,i.height=a;const g=document.createElement("canvas");g.width=e,g.height=a;const _=i.getContext("2d"),B=g.getContext("2d"),v=_.createImageData(e,a),y=B.createImageData(e,a),$=[];let k=1;const N=t.glareAngle!==!1?{x:Math.cos(t.glareAngle*Math.PI/180),y:Math.sin(t.glareAngle*Math.PI/180)}:{x:0,y:0},O=Math.max(1,Math.min(t.bezel,Math.min(e,a)*.28));for(let d=0;d<a;d++)for(let f=0;f<e;f++){const m=I(f,d,e,a,C.value),h=u(m/O);let V=0,F=0,Q=0;if(m>=0&&m<O){const S=te(f,d,e,a,C.value),X=he(fe(h))*t.thickness*E(1-h)*ee(t.refractionMode,h)*t.refraction,Y=Z(t.refractionMode,S,f,d,e,a,h);V=Y.x*X,F=Y.y*X,k=Math.max(k,Math.abs(V),Math.abs(F));const ye=u(S.x*N.x+S.y*N.y,0,1);Q=Math.pow(ye,18)*t.specularOpacity}if(t.magnification!==0&&m>=0){const S=de(f,d,e,a,t.magnification,t.magnificationFocus);V+=S.x,F+=S.y}$.push([V,F]);const T=(d*e+f)*4,ve=Math.round(Q*255);y.data[T]=255,y.data[T+1]=255,y.data[T+2]=255,y.data[T+3]=ve}k=$.reduce((d,[f,m])=>Math.max(d,Math.abs(f),Math.abs(m)),1),$.forEach(([d,f],m)=>{const h=m*4;v.data[h]=Math.round(128+d/k*127),v.data[h+1]=Math.round(128+f/k*127),v.data[h+2]=128,v.data[h+3]=255}),_.putImageData(v,0,0),B.putImageData(y,0,0),x.value=H(i),M.value=H(g),p.value=k}function J(){const e=W();e&&(o.value=Math.max(1,e.offsetWidth),l.value=Math.max(1,e.offsetHeight))}const K=s?"rgba(255, 255, 255, 0.08)":"rgba(255, 255, 255, 0.22)",C=(0,n.computed)(()=>{if(t.radius!==32)return t.radius;const e=t.asProps;return typeof e.rounded=="string"&&e.rounded in G?G[e.rounded]:t.radius}),pe=(0,n.computed)(()=>t.borderRadius||`${C.value}px`),me=new Set(["primary","secondary","accent","info","warning","error","success","surface","background"]);function ge(e,a){if(me.has(e))return`rgba(var(--v-theme-${e}), ${a})`;if(e.startsWith("#")){const i=e.replace("#","");return`rgba(${parseInt(i.slice(0,2),16)}, ${parseInt(i.slice(2,4),16)}, ${parseInt(i.slice(4,6),16)}, ${a})`}return e.startsWith("rgb")?e.replace("rgb(","rgba(").replace(")",`, ${a})`):K}const L=(0,n.computed)(()=>{if(t.glassBackground!==void 0)return t.glassBackground;const e=t.asProps;return typeof e.color=="string"?ge(e.color,.6):K}),P=(0,n.computed)(()=>t.fallbackOnly||!s?"blur(12px)":`url(#${R}) blur(${t.blur}px)`),Me=(0,n.computed)(()=>({borderRadius:`${pe.value}`,...L.value!==void 0?{backgroundColor:L.value}:{},...L.value!==void 0?{boxShadow:["inset 0 0 0 1px rgba(255, 255, 255, 0.28)","0 18px 48px rgba(0, 0, 0, 0.18)"].join(", ")}:{},...w.value&&!A?{backdropFilter:P.value,WebkitBackdropFilter:P.value}:{},...w.value&&A&&!s?{backdropFilter:P.value,WebkitBackdropFilter:P.value}:{}}));let z=null;return(0,n.onMounted)(async()=>{if(await(0,n.nextTick)(),s){J(),D(),await(0,n.nextTick)(),w.value=!0,z=new ResizeObserver(()=>{J(),D(),w.value=!0});const e=W();e&&z.observe(e)}else w.value=!0}),(0,n.watch)(()=>[C.value,t.bezel,t.thickness,t.refraction,t.refractionMode,t.magnification,t.magnificationFocus,t.surface,t.specularOpacity,t.glareAngle,t.fallbackOnly],()=>{s&&D(),w.value=!0}),(0,n.onBeforeUnmount)(()=>{z?.disconnect()}),(e,a)=>((0,n.openBlock)(),(0,n.createElementBlock)("div",{ref_key:"root",ref:c,class:"liquid-glass",style:(0,n.normalizeStyle)(Me.value)},[(0,n.unref)(s)?((0,n.openBlock)(),(0,n.createElementBlock)("svg",ae,[(0,n.createElementVNode)("defs",null,[(0,n.createElementVNode)("filter",{id:R,"color-interpolation-filters":"sRGB",filterUnits:"userSpaceOnUse",x:"0",y:"0",width:"10000",height:"10000"},[(0,n.createElementVNode)("feImage",{href:x.value,x:"0",y:"0",width:o.value,height:l.value,result:"displacement_map"},null,8,ne),a[0]||(a[0]=(0,n.createElementVNode)("feGaussianBlur",{in:"displacement_map",stdDeviation:"2",result:"smooth_disp"},null,-1)),(0,n.createElementVNode)("feDisplacementMap",{in:"SourceGraphic",in2:"smooth_disp",scale:p.value,xChannelSelector:"R",yChannelSelector:"G",result:"refracted"},null,8,re),(0,n.createElementVNode)("feImage",{href:M.value,x:"0",y:"0",width:o.value,height:l.value,result:"specular"},null,8,oe),a[1]||(a[1]=(0,n.createElementVNode)("feBlend",{in:"refracted",in2:"specular",mode:"screen"},null,-1))])])])):(0,n.createCommentVNode)("",!0),((0,n.openBlock)(),(0,n.createBlock)((0,n.resolveDynamicComponent)(r.as),(0,n.normalizeProps)((0,n.guardReactiveProps)(r.asProps)),{default:(0,n.withCtx)(()=>[(0,n.renderSlot)(e.$slots,"default",{},void 0,!0)]),_:3},16))],4))}}),ie=(r,t)=>{const c=r.__vccOpts||r;for(const[o,l]of t)c[o]=l;return c},U=ie(se,[["__scopeId","data-v-5607a45c"]]),ce={install(r){r.component("Liqvued",U)}};b.Liqvued=U,b.default=ce});
package/index.d.ts ADDED
@@ -0,0 +1,43 @@
1
+ import type { Component } from 'vue'
2
+
3
+ export type Surface =
4
+ | 'convex'
5
+ | 'concave'
6
+ | 'lip'
7
+ | 'bowl'
8
+ | 'bevel'
9
+ | 'saddle'
10
+ | 'ripple'
11
+ | 'noise'
12
+ | 'asymmetric'
13
+
14
+ export type RefractionMode = 'edge' | 'center' | 'split'
15
+
16
+ export interface LiqvuedProps {
17
+ as?: string | Component
18
+ asProps?: Record<string, unknown>
19
+ radius?: number
20
+ borderRadius?: string
21
+ bezel?: number
22
+ thickness?: number
23
+ refraction?: number
24
+ refractionMode?: RefractionMode
25
+ magnification?: number
26
+ magnificationFocus?: number
27
+ blur?: number
28
+ surface?: Surface
29
+ specularOpacity?: number
30
+ glareAngle?: number | false
31
+ glassBackground?: string
32
+ fallbackOnly?: boolean
33
+ }
34
+
35
+ export declare const Liqvued: new() => {
36
+ $props: LiqvuedProps
37
+ }
38
+
39
+ declare const _default: {
40
+ install(app: unknown): void
41
+ }
42
+
43
+ export default _default
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "liqvued",
3
3
  "types": "./index.d.ts",
4
- "version": "0.1.2",
4
+ "version": "0.2.1",
5
5
  "description": "Liquid-glass for Vue",
6
6
  "exports": {
7
7
  ".": {
@@ -49,32 +49,32 @@
49
49
  "author": "Igor Voytovich",
50
50
  "license": "MIT",
51
51
  "repository": "https://github.com/IVoyt/liqvued",
52
- "packageManager": "pnpm@10.33.4",
52
+ "packageManager": "pnpm@11.17.0",
53
53
  "peerDependencies": {
54
54
  "vue": "^3.5.14"
55
55
  },
56
56
  "devDependencies": {
57
- "@intlify/unplugin-vue-i18n": "^11.1.1",
57
+ "@intlify/unplugin-vue-i18n": "^11.2.4",
58
58
  "@rollup/plugin-alias": "^5.1.1",
59
59
  "@stylistic/eslint-plugin": "^5.10.0",
60
- "@types/node": "^22.19.15",
61
- "@vitejs/plugin-vue": "^6.0.5",
62
- "@vitejs/plugin-vue-jsx": "^5.1.5",
63
- "eslint": "^10.3.0",
60
+ "@types/node": "^26.1.1",
61
+ "@vitejs/plugin-vue": "^6.0.8",
62
+ "@vitejs/plugin-vue-jsx": "^5.1.6",
63
+ "eslint": "^10.8.0",
64
64
  "eslint-import-resolver-typescript": "^4.4.4",
65
65
  "eslint-plugin-case-police": "^2.2.1",
66
66
  "eslint-plugin-import": "^2.32.0",
67
- "eslint-plugin-promise": "^7.2.1",
68
- "eslint-plugin-sonarjs": "^4.0.3",
69
- "eslint-plugin-unicorn": "^64.0.0",
67
+ "eslint-plugin-promise": "^7.3.0",
68
+ "eslint-plugin-sonarjs": "^4.2.0",
69
+ "eslint-plugin-unicorn": "^72.0.0",
70
70
  "eslint-plugin-vue": "^10.9.0",
71
- "sass-embedded": "^1.98.0",
72
- "tsx": "^4.21.0",
71
+ "sass-embedded": "^1.100.0",
72
+ "tsx": "^4.23.1",
73
73
  "typescript": "^5.9.3",
74
- "typescript-eslint": "^8.59.0",
75
- "unplugin-vue-components": "^28.8.0",
76
- "vite": "^8.0.10",
74
+ "typescript-eslint": "^8.65.0",
75
+ "unplugin-vue-components": "^32.1.0",
76
+ "vite": "^8.1.5",
77
77
  "vue-eslint-parser": "^10.4.0",
78
- "vue-tsc": "^3.2.6"
78
+ "vue-tsc": "^3.3.8"
79
79
  }
80
- }
80
+ }
package/src/Liqvued.vue CHANGED
@@ -10,7 +10,9 @@ import {
10
10
  ref,
11
11
  watch,
12
12
  } from 'vue'
13
- import type { Surface } from './types'
13
+ import { refractionDirection, refractionModeStrength } from './refraction'
14
+ import { distanceToRoundedRectEdge, normalFromRoundedRect } from './shape'
15
+ import type { RefractionMode, Surface } from './types'
14
16
 
15
17
  const props = withDefaults(defineProps<{
16
18
  as?: string | Component
@@ -20,10 +22,13 @@ const props = withDefaults(defineProps<{
20
22
  bezel?: number
21
23
  thickness?: number
22
24
  refraction?: number
25
+ refractionMode?: RefractionMode
26
+ magnification?: number
27
+ magnificationFocus?: number
23
28
  blur?: number
24
29
  surface?: Surface
25
30
  specularOpacity?: number
26
- glareAngle?: number
31
+ glareAngle?: number | false
27
32
  glassBackground?: string
28
33
  fallbackOnly?: boolean
29
34
  }>(), {
@@ -31,9 +36,13 @@ const props = withDefaults(defineProps<{
31
36
  asProps: () => ({}),
32
37
  fallbackOnly: false,
33
38
  radius: 32,
39
+ borderRadius: undefined,
34
40
  bezel: 22,
35
41
  thickness: 42,
36
42
  refraction: 1,
43
+ refractionMode: 'edge',
44
+ magnification: 0,
45
+ magnificationFocus: 0.82,
37
46
  blur: 0.4,
38
47
  surface: 'convex',
39
48
  specularOpacity: 0.45,
@@ -49,6 +58,8 @@ const mapUrl = ref('')
49
58
  const specularUrl = ref('')
50
59
  const scale = ref(1)
51
60
  const supportsLiquidGlass =
61
+ typeof CSS !== 'undefined' &&
62
+ typeof navigator !== 'undefined' &&
52
63
  CSS.supports?.('backdrop-filter', 'url(#x)') &&
53
64
  /Chrome/.test(navigator.userAgent)
54
65
 
@@ -91,13 +102,67 @@ function convex(x: number) {
91
102
  return Math.pow(1 - Math.pow(1 - clamp(x), 4), 1 / 4)
92
103
  }
93
104
 
105
+ function wave(x: number, frequency: number, amplitude: number) {
106
+ return clamp(x + Math.sin(clamp(x) * Math.PI * frequency) * amplitude)
107
+ }
108
+
109
+ function deterministicNoise(x: number) {
110
+ const t = clamp(x)
111
+ return clamp(t + Math.sin(t * 39.1) * 0.06 + Math.sin(t * 91.7) * 0.035)
112
+ }
113
+
114
+ function magnificationVector(
115
+ x: number,
116
+ y: number,
117
+ w: number,
118
+ h: number,
119
+ magnification: number,
120
+ magnificationFocus: number,
121
+ ) {
122
+ const cx = w / 2
123
+ const cy = h / 2
124
+ const rx = Math.max(1, w / 2)
125
+ const ry = Math.max(1, h / 2)
126
+ const dx = (x - cx) / rx
127
+ const dy = (y - cy) / ry
128
+ const radius = Math.hypot(dx, dy)
129
+ const focusRadius = clamp(magnificationFocus, 0.2, 1.4)
130
+ const focus = Math.pow(
131
+ smootherStep(1 - clamp(radius / focusRadius)),
132
+ 1.6,
133
+ )
134
+ const zoom = magnification >= 0
135
+ ? 1 + magnification * focus
136
+ : 1 / (1 + Math.abs(magnification) * focus)
137
+
138
+ return {
139
+ x: (x - cx) / zoom - (x - cx),
140
+ y: (y - cy) / zoom - (y - cy),
141
+ }
142
+ }
143
+
94
144
  function surfaceFn(x: number) {
95
- if (props.surface === 'concave') return 1 - convex(x)
96
- if (props.surface === 'lip') {
97
- const t = smootherStep(x)
98
- return convex(x) * (1 - t) + (1 - convex(x)) * t
145
+ const t = clamp(x)
146
+
147
+ switch (props.surface) {
148
+ case 'concave':
149
+ case 'bowl':
150
+ return 1 - convex(t)
151
+ case 'lip':
152
+ return convex(t) * (1 - smootherStep(t)) + (1 - convex(t)) * smootherStep(t)
153
+ case 'bevel':
154
+ return t
155
+ case 'saddle':
156
+ return 0.5 + Math.sin((t - 0.5) * Math.PI) * 0.5
157
+ case 'ripple':
158
+ return wave(t, 3, 0.12)
159
+ case 'noise':
160
+ return deterministicNoise(t)
161
+ case 'asymmetric':
162
+ return clamp(Math.pow(t, 0.65) * 0.65 + smootherStep(t) * 0.35)
163
+ case 'convex':
164
+ return convex(t)
99
165
  }
100
- return convex(x)
101
166
  }
102
167
 
103
168
  function derivative(x: number) {
@@ -118,54 +183,6 @@ function refractSlope(slope: number) {
118
183
  return Math.tan(theta1 - theta2)
119
184
  }
120
185
 
121
- function distanceToRoundedRectEdge(
122
- x: number,
123
- y: number,
124
- w: number,
125
- h: number,
126
- r: number,
127
- ) {
128
- const hr = Math.min(r, Math.min(w, h) / 2)
129
- const hw = w / 2 - hr
130
- const hh = h / 2 - hr
131
- const ax = Math.abs(x - w / 2) - hw
132
- const ay = Math.abs(y - h / 2) - hh
133
-
134
- const outsidePart = Math.hypot(Math.max(ax, 0), Math.max(ay, 0))
135
- const insidePart = Math.min(Math.max(ax, ay), 0)
136
-
137
- return -(insidePart + outsidePart - hr)
138
- }
139
-
140
- function normalFromRoundedRect(
141
- x: number,
142
- y: number,
143
- w: number,
144
- h: number,
145
- ) {
146
- const dx = Math.min(x, w - 1 - x)
147
- const dy = Math.min(y, h - 1 - y)
148
-
149
- const p = 4
150
- const wsum = Math.pow(dx, p) + Math.pow(dy, p)
151
-
152
- let nx: number
153
- let ny: number
154
-
155
- if (wsum > 1e-10) {
156
- nx = (x > w / 2 ? 1 : -1) * (Math.pow(dy, p) / wsum)
157
- ny = (y > h / 2 ? 1 : -1) * (Math.pow(dx, p) / wsum)
158
- } else {
159
- nx = x > w / 2 ? 1 : -1
160
- ny = y > h / 2 ? 1 : -1
161
- }
162
-
163
- const len = Math.hypot(nx, ny)
164
- if (len < 1e-10) return { x: 0, y: -1 }
165
-
166
- return { x: nx / len, y: ny / len }
167
- }
168
-
169
186
  function canvasToUrl(canvas: HTMLCanvasElement) {
170
187
  return canvas.toDataURL('image/png')
171
188
  }
@@ -195,30 +212,42 @@ function buildMaps() {
195
212
  const vectors: Array<[number, number]> = []
196
213
  let max = 1
197
214
 
198
- const light = {
199
- x: Math.cos((props.glareAngle * Math.PI) / 180),
200
- y: Math.sin((props.glareAngle * Math.PI) / 180),
201
- }
215
+ const light = props.glareAngle !== false
216
+ ? {
217
+ x: Math.cos((props.glareAngle * Math.PI) / 180),
218
+ y: Math.sin((props.glareAngle * Math.PI) / 180),
219
+ }
220
+ : { x: 0, y: 0 }
221
+ const edgeBand = Math.max(
222
+ 1,
223
+ Math.min(props.bezel, Math.min(w, h) * 0.28),
224
+ )
202
225
 
203
226
  for (let y = 0; y < h; y++) {
204
227
  for (let x = 0; x < w; x++) {
205
228
  const dist = distanceToRoundedRectEdge(x, y, w, h, resolvedRadius.value)
206
- const t = clamp(dist / props.bezel)
229
+ const t = clamp(dist / edgeBand)
207
230
 
208
231
  let vx = 0
209
232
  let vy = 0
210
233
  let spec = 0
211
234
 
212
- if (dist >= 0 && dist < props.bezel) {
213
- const n = normalFromRoundedRect(x, y, w, h)
235
+ if (dist >= 0 && dist < edgeBand) {
236
+ const n = normalFromRoundedRect(x, y, w, h, resolvedRadius.value)
214
237
  const slope = derivative(t)
215
238
  const bend = refractSlope(slope)
216
239
 
217
240
  const amount =
218
- bend * props.thickness * (1 - t) * props.refraction
241
+ bend *
242
+ props.thickness *
243
+ smootherStep(1 - t) *
244
+ refractionModeStrength(props.refractionMode, t) *
245
+ props.refraction
246
+
247
+ const direction = refractionDirection(props.refractionMode, n, x, y, w, h, t)
219
248
 
220
- vx = -n.x * amount
221
- vy = -n.y * amount
249
+ vx = direction.x * amount
250
+ vy = direction.y * amount
222
251
 
223
252
  max = Math.max(max, Math.abs(vx), Math.abs(vy))
224
253
 
@@ -226,6 +255,19 @@ function buildMaps() {
226
255
  spec = Math.pow(facing, 18) * props.specularOpacity
227
256
  }
228
257
 
258
+ if (props.magnification !== 0 && dist >= 0) {
259
+ const lens = magnificationVector(
260
+ x,
261
+ y,
262
+ w,
263
+ h,
264
+ props.magnification,
265
+ props.magnificationFocus,
266
+ )
267
+ vx += lens.x
268
+ vy += lens.y
269
+ }
270
+
229
271
  vectors.push([vx, vy])
230
272
 
231
273
  const hi = (y * w + x) * 4
@@ -238,6 +280,10 @@ function buildMaps() {
238
280
  }
239
281
  }
240
282
 
283
+ max = vectors.reduce((currentMax, [vx, vy]) => {
284
+ return Math.max(currentMax, Math.abs(vx), Math.abs(vy))
285
+ }, 1)
286
+
241
287
  vectors.forEach(([vx, vy], index) => {
242
288
  const i = index * 4
243
289
 
@@ -375,6 +421,9 @@ watch(
375
421
  props.bezel,
376
422
  props.thickness,
377
423
  props.refraction,
424
+ props.refractionMode,
425
+ props.magnification,
426
+ props.magnificationFocus,
378
427
  props.surface,
379
428
  props.specularOpacity,
380
429
  props.glareAngle,
@@ -0,0 +1,36 @@
1
+ import { deepStrictEqual, ok } from 'node:assert'
2
+ import { refractionDirection, refractionModeStrength } from './refraction'
3
+
4
+ const normal = { x: 1, y: 0 }
5
+
6
+ deepStrictEqual(
7
+ refractionDirection('edge', normal, 20, 50, 100, 100, 0.25),
8
+ { x: -1, y: -0 },
9
+ )
10
+
11
+ deepStrictEqual(
12
+ refractionDirection('center', normal, 20, 50, 100, 100, 0.25),
13
+ { x: 1, y: 0 },
14
+ )
15
+
16
+ deepStrictEqual(
17
+ refractionDirection('center', { x: 0, y: 1 }, 50, 20, 100, 100, 0.25),
18
+ { x: 0, y: 1 },
19
+ )
20
+
21
+ deepStrictEqual(
22
+ refractionDirection('center', { x: 0, y: 1 }, 20, 20, 100, 100, 0.25),
23
+ { x: 0, y: 1 },
24
+ )
25
+
26
+ const outerSplit = refractionDirection('split', normal, 20, 50, 100, 100, 0.25)
27
+ const innerSplit = refractionDirection('split', normal, 20, 50, 100, 100, 0.75)
28
+
29
+ ok(outerSplit.x * innerSplit.x < 0)
30
+ deepStrictEqual(outerSplit, { x: 1, y: 0 })
31
+ deepStrictEqual(innerSplit, { x: -1, y: -0 })
32
+
33
+ deepStrictEqual(refractionModeStrength('center', 0.5), 1)
34
+ deepStrictEqual(refractionModeStrength('split', 0.5), 0)
35
+ ok(refractionModeStrength('split', 0.1) > refractionModeStrength('split', 0.4))
36
+ ok(refractionModeStrength('split', 0.9) > refractionModeStrength('split', 0.6))
@@ -0,0 +1,50 @@
1
+ import type { RefractionMode } from './types'
2
+
3
+ export const refractionModeOptions = [
4
+ 'edge',
5
+ 'center',
6
+ 'split',
7
+ ] as const satisfies readonly RefractionMode[]
8
+
9
+ interface Vector {
10
+ x: number
11
+ y: number
12
+ }
13
+
14
+ export function refractionDirection(
15
+ mode: RefractionMode,
16
+ normal: Vector,
17
+ _x: number,
18
+ _y: number,
19
+ _width: number,
20
+ _height: number,
21
+ t: number,
22
+ ): Vector {
23
+ if (mode === 'center') {
24
+ return normal
25
+ }
26
+
27
+ const edge = {
28
+ x: -normal.x,
29
+ y: -normal.y,
30
+ }
31
+
32
+ if (mode === 'split') {
33
+ const side = t < 0.5 ? -1 : 1
34
+
35
+ return {
36
+ x: edge.x * side,
37
+ y: edge.y * side,
38
+ }
39
+ }
40
+
41
+ return edge
42
+ }
43
+
44
+ export function refractionModeStrength(mode: RefractionMode, t: number): number {
45
+ if (mode !== 'split') {
46
+ return 1
47
+ }
48
+
49
+ return Math.min(1, Math.abs(t - 0.5) * 2)
50
+ }
@@ -0,0 +1,54 @@
1
+ import { strict as assert } from 'node:assert'
2
+ import { normalFromRoundedRect } from './shape'
3
+
4
+ function closeTo(actual: number, expected: number, tolerance = 0.001) {
5
+ assert.ok(
6
+ Math.abs(actual - expected) <= tolerance,
7
+ `Expected ${actual} to be within ${tolerance} of ${expected}`,
8
+ )
9
+ }
10
+
11
+ function assertUnitNormal(point: [number, number]) {
12
+ const normal = normalFromRoundedRect(point[0], point[1], 200, 120, 40)
13
+ const length = Math.hypot(normal.x, normal.y)
14
+
15
+ closeTo(length, 1)
16
+ }
17
+
18
+ function dot(a: [number, number], b: [number, number]) {
19
+ return a[0] * b[0] + a[1] * b[1]
20
+ }
21
+
22
+ function normalAt(point: [number, number]): [number, number] {
23
+ const normal = normalFromRoundedRect(point[0], point[1], 200, 120, 40)
24
+ return [normal.x, normal.y]
25
+ }
26
+
27
+ assertUnitNormal([20, 30])
28
+ assertUnitNormal([180, 30])
29
+ assertUnitNormal([20, 90])
30
+ assertUnitNormal([180, 90])
31
+
32
+ const topLeft = normalAt([20, 30])
33
+ const topRight = normalAt([180, 30])
34
+ const bottomLeft = normalAt([20, 90])
35
+ const bottomRight = normalAt([180, 90])
36
+
37
+ closeTo(topLeft[0], -topRight[0])
38
+ closeTo(topLeft[1], topRight[1])
39
+ closeTo(topLeft[0], bottomLeft[0])
40
+ closeTo(topLeft[1], -bottomLeft[1])
41
+ closeTo(topLeft[0], -bottomRight[0])
42
+ closeTo(topLeft[1], -bottomRight[1])
43
+
44
+ assert.ok(
45
+ dot(normalAt([39, 10]), normalAt([40, 10])) > 0.99,
46
+ 'Expected top edge to top-right corner transition to be smooth',
47
+ )
48
+
49
+ assert.ok(
50
+ dot(normalAt([39, 10]), normalAt([41, 10])) > 0.98,
51
+ 'Expected rounded corner seam to remain visually continuous',
52
+ )
53
+
54
+ console.log('shape geometry tests passed')
package/src/shape.ts ADDED
@@ -0,0 +1,42 @@
1
+ export interface Point {
2
+ x: number
3
+ y: number
4
+ }
5
+
6
+ export function distanceToRoundedRectEdge(
7
+ x: number,
8
+ y: number,
9
+ w: number,
10
+ h: number,
11
+ r: number,
12
+ ) {
13
+ const hr = Math.min(r, Math.min(w, h) / 2)
14
+ const hw = w / 2 - hr
15
+ const hh = h / 2 - hr
16
+ const ax = Math.abs(x - w / 2) - hw
17
+ const ay = Math.abs(y - h / 2) - hh
18
+
19
+ const outsidePart = Math.hypot(Math.max(ax, 0), Math.max(ay, 0))
20
+ const insidePart = Math.min(Math.max(ax, ay), 0)
21
+
22
+ return -(insidePart + outsidePart - hr)
23
+ }
24
+
25
+ export function normalFromRoundedRect(
26
+ x: number,
27
+ y: number,
28
+ w: number,
29
+ h: number,
30
+ r: number,
31
+ ): Point {
32
+ const delta = 1.5
33
+ const nx = distanceToRoundedRectEdge(x - delta, y, w, h, r)
34
+ - distanceToRoundedRectEdge(x + delta, y, w, h, r)
35
+ const ny = distanceToRoundedRectEdge(x, y - delta, w, h, r)
36
+ - distanceToRoundedRectEdge(x, y + delta, w, h, r)
37
+
38
+ const len = Math.hypot(nx, ny)
39
+ if (len < 1e-10) return { x: 0, y: -1 }
40
+
41
+ return { x: nx / len, y: ny / len }
42
+ }
@@ -0,0 +1,13 @@
1
+ import type { Surface } from './types'
2
+
3
+ export const surfaceOptions = [
4
+ 'convex',
5
+ 'concave',
6
+ 'lip',
7
+ 'bowl',
8
+ 'bevel',
9
+ 'saddle',
10
+ 'ripple',
11
+ 'noise',
12
+ 'asymmetric',
13
+ ] as const satisfies readonly Surface[]
package/src/types.ts CHANGED
@@ -1 +1,12 @@
1
- export type Surface = 'convex' | 'concave' | 'lip'
1
+ export type Surface =
2
+ | 'convex'
3
+ | 'concave'
4
+ | 'lip'
5
+ | 'bowl'
6
+ | 'bevel'
7
+ | 'saddle'
8
+ | 'ripple'
9
+ | 'noise'
10
+ | 'asymmetric'
11
+
12
+ export type RefractionMode = 'edge' | 'center' | 'split'