fancy-ui-svelte 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +84 -71
  2. package/dist/fancy-ui/fluid-cursor/FluidCursor.svelte +1132 -1111
  3. package/dist/fancy-ui/fluid-cursor/FluidCursor.svelte.d.ts +3 -5
  4. package/dist/fancy-ui/fluid-cursor/FluidCursor.test.js +29 -0
  5. package/dist/fancy-ui/fluid-cursor/README.md +57 -6
  6. package/dist/fancy-ui/fluid-cursor/fluid-shared.d.ts +40 -0
  7. package/dist/fancy-ui/fluid-cursor/fluid-shared.js +125 -0
  8. package/dist/fancy-ui/fluid-cursor/fluid-shared.test.d.ts +1 -0
  9. package/dist/fancy-ui/fluid-cursor/fluid-shared.test.js +39 -0
  10. package/dist/fancy-ui/fluid-cursor/webgpu-engine.d.ts +29 -0
  11. package/dist/fancy-ui/fluid-cursor/webgpu-engine.js +847 -0
  12. package/dist/fancy-ui/frosted-glass/FrostedGlass.svelte +204 -0
  13. package/dist/fancy-ui/frosted-glass/FrostedGlass.svelte.d.ts +20 -0
  14. package/dist/fancy-ui/frosted-glass/FrostedGlass.test.d.ts +1 -0
  15. package/dist/fancy-ui/frosted-glass/FrostedGlass.test.js +48 -0
  16. package/dist/fancy-ui/frosted-glass/README.md +61 -0
  17. package/dist/fancy-ui/frosted-glass/index.d.ts +1 -0
  18. package/dist/fancy-ui/frosted-glass/index.js +1 -0
  19. package/dist/fancy-ui/index.d.ts +2 -0
  20. package/dist/fancy-ui/index.js +2 -0
  21. package/dist/fancy-ui/liquid-glass/LiquidGlass.svelte +19 -2
  22. package/dist/fancy-ui/liquid-glass/LiquidGlass.svelte.d.ts +4 -0
  23. package/dist/fancy-ui/liquid-glass/LiquidGlass.test.js +14 -0
  24. package/dist/fancy-ui/liquid-glass/README.md +7 -1
  25. package/dist/fancy-ui/liquid-text/LiquidText.svelte +1151 -0
  26. package/dist/fancy-ui/liquid-text/LiquidText.svelte.d.ts +37 -0
  27. package/dist/fancy-ui/liquid-text/LiquidText.test.d.ts +1 -0
  28. package/dist/fancy-ui/liquid-text/LiquidText.test.js +219 -0
  29. package/dist/fancy-ui/liquid-text/README.md +139 -0
  30. package/dist/fancy-ui/liquid-text/index.d.ts +2 -0
  31. package/dist/fancy-ui/liquid-text/index.js +1 -0
  32. package/dist/fancy-ui/registry.js +192 -0
  33. package/dist/fancy-ui/themes.d.ts +38 -0
  34. package/dist/fancy-ui/themes.js +356 -0
  35. package/dist/utils/animation.d.ts +109 -0
  36. package/dist/utils/animation.js +198 -0
  37. package/dist/utils/color.d.ts +128 -0
  38. package/dist/utils/color.js +314 -0
  39. package/dist/utils/geometry.d.ts +125 -0
  40. package/dist/utils/geometry.js +250 -0
  41. package/dist/utils/index.d.ts +8 -0
  42. package/dist/utils/index.js +39 -0
  43. package/package.json +13 -4
@@ -0,0 +1,204 @@
1
+ <script lang="ts">
2
+ import { cn } from "../../utils";
3
+ import { onMount } from "svelte";
4
+ import type { Snippet } from "svelte";
5
+
6
+ interface Props {
7
+ radius?: number;
8
+ baseFrequency?: number;
9
+ numOctaves?: number;
10
+ seed?: number;
11
+ noiseBlur?: number;
12
+ scale?: number;
13
+ tint?: string;
14
+ highlight?: string;
15
+ border?: boolean;
16
+ fallbackBlur?: number;
17
+ fallbackSaturation?: number;
18
+ class?: string;
19
+ containerClass?: string;
20
+ children?: Snippet;
21
+ }
22
+
23
+ let {
24
+ radius = 24,
25
+ baseFrequency = 0.008,
26
+ numOctaves = 2,
27
+ seed = 92,
28
+ noiseBlur = 2,
29
+ scale = 70,
30
+ tint = "hsla(0, 0%, 100%, 0.25)",
31
+ highlight = "hsla(0, 0%, 100%, 0.75)",
32
+ border = true,
33
+ fallbackBlur = 20,
34
+ fallbackSaturation = 180,
35
+ class: className = "",
36
+ containerClass = "",
37
+ children,
38
+ }: Props = $props();
39
+
40
+ let filterId = $state("");
41
+
42
+ let containerStyle = $derived(
43
+ `border-radius:${radius}px;--fg-tint:${tint};--fg-highlight:${highlight};` +
44
+ `--fg-fallback-blur:${fallbackBlur}px;--fg-fallback-saturation:${fallbackSaturation}%;`
45
+ );
46
+
47
+ onMount(() => {
48
+ filterId = `fg-dist-${Math.random().toString(36).slice(2, 8)}`;
49
+ });
50
+ </script>
51
+
52
+ <div
53
+ class={cn("frosted-glass", border && "frosted-glass-border", containerClass)}
54
+ style={containerStyle}
55
+ >
56
+ {#if filterId}
57
+ <div class="frosted-glass-filter" style="filter:url(#{filterId});"></div>
58
+
59
+ <svg class="frosted-glass-defs" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
60
+ <defs>
61
+ <filter
62
+ id={filterId}
63
+ x="-50%"
64
+ y="-50%"
65
+ width="200%"
66
+ height="200%"
67
+ filterUnits="objectBoundingBox"
68
+ primitiveUnits="userSpaceOnUse"
69
+ color-interpolation-filters="linearRGB"
70
+ >
71
+ <feTurbulence
72
+ type="fractalNoise"
73
+ baseFrequency="{baseFrequency} {baseFrequency}"
74
+ {numOctaves}
75
+ {seed}
76
+ stitchTiles="stitch"
77
+ result="noise"
78
+ />
79
+ <feGaussianBlur in="noise" stdDeviation={noiseBlur} result="blurred" />
80
+ <feDisplacementMap
81
+ in="SourceGraphic"
82
+ in2="blurred"
83
+ {scale}
84
+ xChannelSelector="R"
85
+ yChannelSelector="G"
86
+ />
87
+ </filter>
88
+ </defs>
89
+ </svg>
90
+ {/if}
91
+
92
+ <div class="frosted-glass-overlay"></div>
93
+ <div class="frosted-glass-specular"></div>
94
+
95
+ <div class={cn("frosted-glass-content", className)}>
96
+ {#if children}
97
+ {@render children()}
98
+ {/if}
99
+ </div>
100
+ </div>
101
+
102
+ <style>
103
+ .frosted-glass {
104
+ position: relative;
105
+ display: flex;
106
+ background: transparent;
107
+ overflow: hidden;
108
+ contain: layout paint;
109
+ box-shadow:
110
+ 0 2px 4px rgba(0, 0, 0, 0.1),
111
+ 0 0 8px rgba(0, 0, 0, 0.05);
112
+ }
113
+
114
+ .frosted-glass-border::before {
115
+ content: "";
116
+ position: absolute;
117
+ inset: 0;
118
+ z-index: 4;
119
+ border-radius: inherit;
120
+ padding: 1px;
121
+ background:
122
+ conic-gradient(
123
+ from -75deg at 50% 50%,
124
+ rgba(20, 60, 120, 0.5),
125
+ rgba(150, 200, 255, 0.1) 5% 40%,
126
+ rgba(20, 60, 120, 0.5) 50%,
127
+ rgba(150, 200, 255, 0.1) 60% 95%,
128
+ rgba(20, 60, 120, 0.5)
129
+ ),
130
+ linear-gradient(180deg, rgba(220, 240, 255, 0.5), rgba(240, 250, 255, 0.5));
131
+ box-shadow: inset 0 0 0 0.5px rgba(220, 240, 255, 0.5);
132
+ pointer-events: none;
133
+ mask:
134
+ linear-gradient(#fff 0 0) content-box,
135
+ linear-gradient(#fff 0 0);
136
+ mask-composite: exclude;
137
+ }
138
+
139
+ /* backdrop-filter: blur(0) pulls the backdrop into this layer so the SVG
140
+ displacement filter can distort it */
141
+ .frosted-glass-filter {
142
+ position: absolute;
143
+ inset: 0;
144
+ z-index: 0;
145
+ backdrop-filter: blur(0);
146
+ isolation: isolate;
147
+ transform: translateZ(0);
148
+ will-change: filter;
149
+ animation: frosted-glass-settle 0.22s ease-out;
150
+ }
151
+
152
+ @keyframes frosted-glass-settle {
153
+ 0% {
154
+ opacity: 0;
155
+ }
156
+ }
157
+
158
+ /* Safari can't combine filter:url() with backdrop-filter, so it needs a plain-blur
159
+ fallback. -webkit-named-image is a WebKit-only feature query (Chromium and Firefox
160
+ return false). Ship the -webkit- prefixed backdrop-filter too for Safari and iOS 17 and older. */
161
+ @supports (background: -webkit-named-image(i)) {
162
+ .frosted-glass-filter {
163
+ filter: none !important;
164
+ -webkit-backdrop-filter: blur(var(--fg-fallback-blur)) saturate(var(--fg-fallback-saturation));
165
+ backdrop-filter: blur(var(--fg-fallback-blur)) saturate(var(--fg-fallback-saturation));
166
+ }
167
+
168
+ .frosted-glass-overlay {
169
+ background: hsla(0, 0%, 100%, 0.5);
170
+ }
171
+ }
172
+
173
+ .frosted-glass-defs {
174
+ position: absolute;
175
+ width: 0;
176
+ height: 0;
177
+ }
178
+
179
+ .frosted-glass-overlay {
180
+ position: absolute;
181
+ inset: 0;
182
+ z-index: 1;
183
+ background: var(--fg-tint);
184
+ }
185
+
186
+ .frosted-glass-specular {
187
+ position: absolute;
188
+ inset: 0;
189
+ z-index: 2;
190
+ border-radius: inherit;
191
+ overflow: hidden;
192
+ box-shadow:
193
+ inset 1px 1px 0 var(--fg-highlight),
194
+ inset 0 0 5px var(--fg-highlight);
195
+ }
196
+
197
+ .frosted-glass-content {
198
+ position: relative;
199
+ z-index: 3;
200
+ display: flex;
201
+ align-items: center;
202
+ width: 100%;
203
+ }
204
+ </style>
@@ -0,0 +1,20 @@
1
+ import type { Snippet } from "svelte";
2
+ interface Props {
3
+ radius?: number;
4
+ baseFrequency?: number;
5
+ numOctaves?: number;
6
+ seed?: number;
7
+ noiseBlur?: number;
8
+ scale?: number;
9
+ tint?: string;
10
+ highlight?: string;
11
+ border?: boolean;
12
+ fallbackBlur?: number;
13
+ fallbackSaturation?: number;
14
+ class?: string;
15
+ containerClass?: string;
16
+ children?: Snippet;
17
+ }
18
+ declare const FrostedGlass: import("svelte").Component<Props, {}, "">;
19
+ type FrostedGlass = ReturnType<typeof FrostedGlass>;
20
+ export default FrostedGlass;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,48 @@
1
+ import { render, cleanup } from "@testing-library/svelte";
2
+ import { afterEach, describe, it, expect } from "vitest";
3
+ import FrostedGlass from "./FrostedGlass.svelte";
4
+ describe("FrostedGlass", () => {
5
+ afterEach(cleanup);
6
+ it("renders the component", () => {
7
+ const { container } = render(FrostedGlass);
8
+ const wrapper = container.querySelector(".frosted-glass");
9
+ expect(wrapper).toBeInTheDocument();
10
+ });
11
+ it("renders the layer stack", () => {
12
+ const { container } = render(FrostedGlass);
13
+ expect(container.querySelector(".frosted-glass-overlay")).toBeInTheDocument();
14
+ expect(container.querySelector(".frosted-glass-specular")).toBeInTheDocument();
15
+ expect(container.querySelector(".frosted-glass-content")).toBeInTheDocument();
16
+ });
17
+ it("applies custom container class", () => {
18
+ const { container } = render(FrostedGlass, {
19
+ props: { containerClass: "my-container" },
20
+ });
21
+ const wrapper = container.querySelector(".frosted-glass");
22
+ expect(wrapper?.className).toContain("my-container");
23
+ });
24
+ it("applies custom class to content", () => {
25
+ const { container } = render(FrostedGlass, { props: { class: "my-content" } });
26
+ const content = container.querySelector(".frosted-glass-content");
27
+ expect(content?.className).toContain("my-content");
28
+ });
29
+ it("sets border radius", () => {
30
+ const { container } = render(FrostedGlass, { props: { radius: 9999 } });
31
+ const wrapper = container.querySelector(".frosted-glass");
32
+ expect(wrapper.style.borderRadius).toBe("9999px");
33
+ });
34
+ it("sets tint CSS variable", () => {
35
+ const { container } = render(FrostedGlass, {
36
+ props: { tint: "hsla(0, 0%, 0%, 0.3)" },
37
+ });
38
+ const wrapper = container.querySelector(".frosted-glass");
39
+ expect(wrapper.style.getPropertyValue("--fg-tint")).toBe("hsla(0, 0%, 0%, 0.3)");
40
+ });
41
+ it("renders border layer by default and hides it when border=false", () => {
42
+ const withBorder = render(FrostedGlass);
43
+ expect(withBorder.container.querySelector(".frosted-glass-border")).toBeInTheDocument();
44
+ cleanup();
45
+ const withoutBorder = render(FrostedGlass, { props: { border: false } });
46
+ expect(withoutBorder.container.querySelector(".frosted-glass-border")).not.toBeInTheDocument();
47
+ });
48
+ });
@@ -0,0 +1,61 @@
1
+ # FrostedGlass
2
+
3
+ Frosted glass surface with an organic, wavy refraction of the content behind it. An alternative to [LiquidGlass](../liquid-glass/README.md): where LiquidGlass produces chromatic (rainbow-edge) refraction from gradient displacement maps, FrostedGlass distorts the backdrop with fractal turbulence noise for a softer, rippled-glass look — the effect used by glassy pill navbars.
4
+
5
+ ## How it works
6
+
7
+ The effect stacks four layers inside a rounded container:
8
+
9
+ 1. **Filter layer** — `backdrop-filter: blur(0)` pulls the backdrop into the element's own rendering, then a CSS `filter: url(#...)` pointing at an inline SVG filter distorts it: `feTurbulence` (fractal noise) → `feGaussianBlur` → `feDisplacementMap`.
10
+ 2. **Overlay** — translucent tint (`tint` prop).
11
+ 3. **Specular** — inset highlights simulating a lit glass rim (`highlight` prop).
12
+ 4. **Content** — your slotted content.
13
+
14
+ An optional conic-gradient border (`border` prop) frames the container.
15
+
16
+ ## Usage
17
+
18
+ ```svelte
19
+ <script>
20
+ import { FrostedGlass } from "fancy-ui-svelte";
21
+ </script>
22
+
23
+ <FrostedGlass radius={9999}>
24
+ <nav class="flex w-full items-center justify-between px-7 py-4">
25
+ <span class="font-semibold">Brand</span>
26
+ <a href="/download">Download</a>
27
+ </nav>
28
+ </FrostedGlass>
29
+ ```
30
+
31
+ ## Props
32
+
33
+ | Prop | Type | Default | Description |
34
+ | -------------------- | --------- | --------------------------- | ------------------------------------------------- |
35
+ | `radius` | `number` | `24` | Border radius in pixels |
36
+ | `baseFrequency` | `number` | `0.008` | Turbulence noise frequency (lower = wider waves) |
37
+ | `numOctaves` | `number` | `2` | Turbulence octaves (detail of the noise) |
38
+ | `seed` | `number` | `92` | Turbulence random seed |
39
+ | `noiseBlur` | `number` | `2` | Gaussian blur softening the noise before displace |
40
+ | `scale` | `number` | `70` | Displacement intensity |
41
+ | `tint` | `string` | `"hsla(0, 0%, 100%, 0.25)"` | Overlay tint color |
42
+ | `highlight` | `string` | `"hsla(0, 0%, 100%, 0.75)"` | Specular rim highlight color |
43
+ | `border` | `boolean` | `true` | Show the conic-gradient glass border |
44
+ | `fallbackBlur` | `number` | `20` | Backdrop blur (px) for the Safari fallback |
45
+ | `fallbackSaturation` | `number` | `180` | Backdrop saturation (%) for the Safari fallback |
46
+ | `class` | `string` | `""` | CSS classes for the content layer |
47
+ | `containerClass` | `string` | `""` | CSS classes for the outer container |
48
+
49
+ ## Slots
50
+
51
+ | Slot | Description |
52
+ | ---------- | ------------------------------------ |
53
+ | `children` | Content rendered on top of the glass |
54
+
55
+ ## Notes
56
+
57
+ - The displacement only shows over content that scrolls or moves behind the glass; over a flat background the effect reads as a subtle frost.
58
+
59
+ ## Browser support
60
+
61
+ Safari (WebKit) cannot combine an SVG `filter: url(#…)` with `backdrop-filter`, so the turbulence displacement silently disappears there. To keep the component usable, FrostedGlass automatically detects Safari (via a WebKit-only `@supports` feature query) and falls back to a plain frosted `blur()` + `saturate()` (both `-webkit-` prefixed and unprefixed), tunable via the `fallbackBlur` and `fallbackSaturation` props. Chromium and Firefox get the full turbulence refraction.
@@ -0,0 +1 @@
1
+ export { default as FrostedGlass } from "./FrostedGlass.svelte";
@@ -0,0 +1 @@
1
+ export { default as FrostedGlass } from "./FrostedGlass.svelte";
@@ -42,6 +42,7 @@ export * from "./text-reveal-card/index.js";
42
42
  export * from "./container-scroll/index.js";
43
43
  export * from "./container-text-flip/index.js";
44
44
  export * from "./focus/index.js";
45
+ export * from "./frosted-glass/index.js";
45
46
  export * from "./liquid-glass/index.js";
46
47
  export * from "./smooth-cursor/index.js";
47
48
  export * from "./sparkles/index.js";
@@ -57,4 +58,5 @@ export * from "./terminal-text/index.js";
57
58
  export * from "./noise-reveal/index.js";
58
59
  export * from "./line-reveal/index.js";
59
60
  export * from "./editorial-engine/index.js";
61
+ export * from "./liquid-text/index.js";
60
62
  export { registry, categories, categoryLabels, categoryDescriptions, getAllComponents, getComponent, getComponentsByStatus, getComponentsByCategory, getComponentsGroupedByCategory, getComponentsGroupedByStatus, searchComponents, getStats, hasComponent, getComponentCategory, } from "./registry.js";
@@ -47,6 +47,7 @@ export * from "./text-reveal-card/index.js";
47
47
  export * from "./container-scroll/index.js";
48
48
  export * from "./container-text-flip/index.js";
49
49
  export * from "./focus/index.js";
50
+ export * from "./frosted-glass/index.js";
50
51
  export * from "./liquid-glass/index.js";
51
52
  export * from "./smooth-cursor/index.js";
52
53
  export * from "./sparkles/index.js";
@@ -62,6 +63,7 @@ export * from "./terminal-text/index.js";
62
63
  export * from "./noise-reveal/index.js";
63
64
  export * from "./line-reveal/index.js";
64
65
  export * from "./editorial-engine/index.js";
66
+ export * from "./liquid-text/index.js";
65
67
  // =============================================================================
66
68
  // Registry
67
69
  // =============================================================================
@@ -18,6 +18,10 @@
18
18
  bOffset?: number;
19
19
  scale?: number;
20
20
  frost?: number;
21
+ /** Backdrop blur in pixels for the Safari fallback */
22
+ fallbackBlur?: number;
23
+ /** Backdrop saturation percentage for the Safari fallback */
24
+ fallbackSaturation?: number;
21
25
  class?: string;
22
26
  containerClass?: string;
23
27
  children?: Snippet;
@@ -38,6 +42,8 @@
38
42
  bOffset = 20,
39
43
  scale = -180,
40
44
  frost = 0.05,
45
+ fallbackBlur = 20,
46
+ fallbackSaturation = 180,
41
47
  class: className = "",
42
48
  containerClass = "",
43
49
  children,
@@ -57,8 +63,8 @@
57
63
 
58
64
  let backdropStyle = $derived(
59
65
  filterId
60
- ? `--frost:${frost};border-radius:${radius}px;backdrop-filter:url(#displacementFilter-${filterId});`
61
- : `--frost:${frost};border-radius:${radius}px;`
66
+ ? `--frost:${frost};border-radius:${radius}px;backdrop-filter:url(#displacementFilter-${filterId});--lg-fallback-blur:${fallbackBlur}px;--lg-fallback-saturation:${fallbackSaturation}%;`
67
+ : `--frost:${frost};border-radius:${radius}px;--lg-fallback-blur:${fallbackBlur}px;--lg-fallback-saturation:${fallbackSaturation}%;`
62
68
  );
63
69
 
64
70
  onMount(() => {
@@ -187,6 +193,17 @@
187
193
  0px 16px 56px rgba(17, 17, 26, 0.05) inset;
188
194
  }
189
195
 
196
+ /* Safari cannot resolve SVG url() references inside backdrop-filter, so it needs a
197
+ plain-blur fallback. -webkit-named-image is a WebKit-only feature query (Chromium
198
+ and Firefox return false), so this never overrides Chromium's working SVG filter.
199
+ Ship the -webkit- prefixed backdrop-filter too for Safari and iOS 17 and older. */
200
+ @supports (background: -webkit-named-image(i)) {
201
+ .liquid-glass-effect {
202
+ -webkit-backdrop-filter: blur(var(--lg-fallback-blur)) saturate(var(--lg-fallback-saturation)) !important;
203
+ backdrop-filter: blur(var(--lg-fallback-blur)) saturate(var(--lg-fallback-saturation)) !important;
204
+ }
205
+ }
206
+
190
207
  .liquid-glass-slot {
191
208
  width: 100%;
192
209
  height: 100%;
@@ -14,6 +14,10 @@ interface Props {
14
14
  bOffset?: number;
15
15
  scale?: number;
16
16
  frost?: number;
17
+ /** Backdrop blur in pixels for the Safari fallback */
18
+ fallbackBlur?: number;
19
+ /** Backdrop saturation percentage for the Safari fallback */
20
+ fallbackSaturation?: number;
17
21
  class?: string;
18
22
  containerClass?: string;
19
23
  children?: Snippet;
@@ -45,4 +45,18 @@ describe("LiquidGlass", () => {
45
45
  expect(wrapper.getAttribute("style")).toContain("border-radius");
46
46
  expect(wrapper.getAttribute("style")).toContain("24px");
47
47
  });
48
+ it("exposes Safari fallback CSS variables from props", () => {
49
+ const { container: defaultContainer } = render(LiquidGlass);
50
+ const defaultWrapper = defaultContainer.querySelector(".liquid-glass-effect");
51
+ const defaultStyle = defaultWrapper.getAttribute("style") ?? "";
52
+ expect(defaultStyle).toMatch(/--lg-fallback-blur:\s*20px/);
53
+ expect(defaultStyle).toMatch(/--lg-fallback-saturation:\s*180%/);
54
+ const { container: customContainer } = render(LiquidGlass, {
55
+ props: { fallbackBlur: 8, fallbackSaturation: 120 },
56
+ });
57
+ const customWrapper = customContainer.querySelector(".liquid-glass-effect");
58
+ const customStyle = customWrapper.getAttribute("style") ?? "";
59
+ expect(customStyle).toMatch(/--lg-fallback-blur:\s*8px/);
60
+ expect(customStyle).toMatch(/--lg-fallback-saturation:\s*120%/);
61
+ });
48
62
  });
@@ -20,6 +20,8 @@ A glass-like visual effect using SVG filters for chromatic displacement, creatin
20
20
  | `bOffset` | `number` | `20` | Blue channel offset |
21
21
  | `scale` | `number` | `-180` | Displacement scale |
22
22
  | `frost` | `number` | `0.05` | Frosted overlay opacity |
23
+ | `fallbackBlur` | `number` | `20` | Backdrop blur in pixels for the Safari fallback |
24
+ | `fallbackSaturation` | `number` | `180` | Backdrop saturation percentage for the Safari fallback |
23
25
  | `class` | `string` | `""` | CSS classes for inner container |
24
26
  | `containerClass` | `string` | `""` | CSS classes for outer container |
25
27
 
@@ -31,4 +33,8 @@ A glass-like visual effect using SVG filters for chromatic displacement, creatin
31
33
  </LiquidGlass>
32
34
  ```
33
35
 
34
- **Note**: This component uses `position: fixed` and `backdrop-filter` with SVG filters. Place it over content to see the refraction effect.
36
+ **Note**: This component uses `position: relative` and `backdrop-filter` with SVG filters. Place it over content to see the refraction effect.
37
+
38
+ ## Browser support
39
+
40
+ Safari (WebKit) cannot resolve an SVG `url(#…)` filter reference inside `backdrop-filter`, so the chromatic displacement silently disappears there. To keep the component usable, LiquidGlass automatically detects Safari (via a WebKit-only `@supports` feature query) and falls back to a plain frosted `blur()` + `saturate()` (both `-webkit-` prefixed and unprefixed), tunable via the `fallbackBlur` and `fallbackSaturation` props. Chromium and Firefox get the full chromatic refraction.