fancy-ui-svelte 0.6.0 → 0.7.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.
- package/README.md +76 -71
- package/dist/fancy-ui/frosted-glass/FrostedGlass.svelte +202 -0
- package/dist/fancy-ui/frosted-glass/FrostedGlass.svelte.d.ts +20 -0
- package/dist/fancy-ui/frosted-glass/FrostedGlass.test.d.ts +1 -0
- package/dist/fancy-ui/frosted-glass/FrostedGlass.test.js +48 -0
- package/dist/fancy-ui/frosted-glass/README.md +59 -0
- package/dist/fancy-ui/frosted-glass/index.d.ts +1 -0
- package/dist/fancy-ui/frosted-glass/index.js +1 -0
- package/dist/fancy-ui/index.d.ts +1 -0
- package/dist/fancy-ui/index.js +1 -0
- package/dist/fancy-ui/registry.js +68 -0
- package/dist/fancy-ui/themes.d.ts +38 -0
- package/dist/fancy-ui/themes.js +356 -0
- package/dist/utils/animation.d.ts +109 -0
- package/dist/utils/animation.js +198 -0
- package/dist/utils/color.d.ts +128 -0
- package/dist/utils/color.js +314 -0
- package/dist/utils/geometry.d.ts +125 -0
- package/dist/utils/geometry.js +250 -0
- package/dist/utils/index.d.ts +8 -0
- package/dist/utils/index.js +39 -0
- package/package.json +10 -2
package/README.md
CHANGED
|
@@ -31,88 +31,88 @@ Beautiful animation and UI components for **Svelte 5**.
|
|
|
31
31
|
|
|
32
32
|
### Buttons
|
|
33
33
|
|
|
34
|
-
| Component
|
|
35
|
-
|
|
36
|
-
| RainbowButton
|
|
37
|
-
| RippleButton
|
|
38
|
-
| ShimmerButton
|
|
39
|
-
| GradientButton
|
|
34
|
+
| Component | Description | Demo |
|
|
35
|
+
| ---------------------- | ---------------------------------------- | --------------------------------------------------------------- |
|
|
36
|
+
| RainbowButton | Animated rainbow gradient border effect | [Demo](https://fancy-ui.rama.app/demo/rainbow-button) |
|
|
37
|
+
| RippleButton | Ripple click effect | [Demo](https://fancy-ui.rama.app/demo/ripple-button) |
|
|
38
|
+
| ShimmerButton | Rotating conic-gradient shimmer border | [Demo](https://fancy-ui.rama.app/demo/shimmer-button) |
|
|
39
|
+
| GradientButton | Rotating conic-gradient rainbow border | [Demo](https://fancy-ui.rama.app/demo/gradient-button) |
|
|
40
40
|
| InteractiveHoverButton | Hover effect revealing alternate content | [Demo](https://fancy-ui.rama.app/demo/interactive-hover-button) |
|
|
41
41
|
|
|
42
42
|
### Cards
|
|
43
43
|
|
|
44
|
-
| Component
|
|
45
|
-
|
|
46
|
-
| Card3D
|
|
47
|
-
| CardSpotlight
|
|
44
|
+
| Component | Description | Demo |
|
|
45
|
+
| ------------------- | -------------------------------------------- | ------------------------------------------------------------ |
|
|
46
|
+
| Card3D | 3D tilt card with mouse tracking | [Demo](https://fancy-ui.rama.app/demo/card-3d) |
|
|
47
|
+
| CardSpotlight | Spotlight effect following mouse | [Demo](https://fancy-ui.rama.app/demo/card-spotlight) |
|
|
48
48
|
| DirectionAwareHover | Overlay slides in from mouse entry direction | [Demo](https://fancy-ui.rama.app/demo/direction-aware-hover) |
|
|
49
|
-
| FlipCard
|
|
50
|
-
| GlareCard
|
|
51
|
-
| TextRevealCard
|
|
49
|
+
| FlipCard | Two-sided card with flip animation | [Demo](https://fancy-ui.rama.app/demo/flip-card) |
|
|
50
|
+
| GlareCard | Glare reflection effect on hover | [Demo](https://fancy-ui.rama.app/demo/glare-card) |
|
|
51
|
+
| TextRevealCard | Text reveal on hover | [Demo](https://fancy-ui.rama.app/demo/text-reveal-card) |
|
|
52
52
|
|
|
53
53
|
### Text & Typography
|
|
54
54
|
|
|
55
|
-
| Component
|
|
56
|
-
|
|
57
|
-
| BlurReveal
|
|
58
|
-
| BoxReveal
|
|
59
|
-
| ColourfulText
|
|
60
|
-
| ContainerTextFlip
|
|
61
|
-
| FlipWords
|
|
62
|
-
| HyperText
|
|
63
|
-
| LetterPullup
|
|
64
|
-
| LineShadowText
|
|
65
|
-
| NumberTicker
|
|
66
|
-
| SparklesText
|
|
67
|
-
| TextGenerateEffect | Word-by-word text generation effect
|
|
55
|
+
| Component | Description | Demo |
|
|
56
|
+
| ------------------ | ---------------------------------------------- | ----------------------------------------------------------- |
|
|
57
|
+
| BlurReveal | Scroll-triggered blur-to-clear reveal | [Demo](https://fancy-ui.rama.app/demo/blur-reveal) |
|
|
58
|
+
| BoxReveal | Sliding colored box reveal animation | [Demo](https://fancy-ui.rama.app/demo/box-reveal) |
|
|
59
|
+
| ColourfulText | Per-character color animation | [Demo](https://fancy-ui.rama.app/demo/colourful-text) |
|
|
60
|
+
| ContainerTextFlip | Text flipping inside a container | [Demo](https://fancy-ui.rama.app/demo/container-text-flip) |
|
|
61
|
+
| FlipWords | Cycling word animation with per-letter effects | [Demo](https://fancy-ui.rama.app/demo/flip-words) |
|
|
62
|
+
| HyperText | Character scramble on hover | [Demo](https://fancy-ui.rama.app/demo/hyper-text) |
|
|
63
|
+
| LetterPullup | Staggered letter pull-up animation | [Demo](https://fancy-ui.rama.app/demo/letter-pullup) |
|
|
64
|
+
| LineShadowText | Text with animated line shadow | [Demo](https://fancy-ui.rama.app/demo/line-shadow-text) |
|
|
65
|
+
| NumberTicker | Animated number counter with easing | [Demo](https://fancy-ui.rama.app/demo/number-ticker) |
|
|
66
|
+
| SparklesText | Animated SVG sparkle stars overlay | [Demo](https://fancy-ui.rama.app/demo/sparkles-text) |
|
|
67
|
+
| TextGenerateEffect | Word-by-word text generation effect | [Demo](https://fancy-ui.rama.app/demo/text-generate-effect) |
|
|
68
68
|
|
|
69
69
|
### Backgrounds
|
|
70
70
|
|
|
71
|
-
| Component
|
|
72
|
-
|
|
73
|
-
| FallingStarsBg
|
|
74
|
-
| FlickeringGrid
|
|
75
|
-
| InteractiveGridPattern | SVG grid with hover highlights
|
|
76
|
-
| StarsBackground
|
|
71
|
+
| Component | Description | Demo |
|
|
72
|
+
| ---------------------- | -------------------------------------- | --------------------------------------------------------------- |
|
|
73
|
+
| FallingStarsBg | Canvas 3D starfield with motion trails | [Demo](https://fancy-ui.rama.app/demo/bg-falling-stars) |
|
|
74
|
+
| FlickeringGrid | Canvas grid with flickering opacity | [Demo](https://fancy-ui.rama.app/demo/flickering-grid) |
|
|
75
|
+
| InteractiveGridPattern | SVG grid with hover highlights | [Demo](https://fancy-ui.rama.app/demo/interactive-grid-pattern) |
|
|
76
|
+
| StarsBackground | Starfield with parallax mouse tracking | [Demo](https://fancy-ui.rama.app/demo/bg-stars) |
|
|
77
77
|
|
|
78
78
|
### Effects & Animations
|
|
79
79
|
|
|
80
|
-
| Component
|
|
81
|
-
|
|
82
|
-
| AnimatedBeam
|
|
83
|
-
| BorderBeam
|
|
84
|
-
| Confetti
|
|
85
|
-
| FluidCursor
|
|
86
|
-
| GlowBorder
|
|
87
|
-
| GlowingEffect
|
|
80
|
+
| Component | Description | Demo |
|
|
81
|
+
| ---------------- | ----------------------------------------- | --------------------------------------------------------- |
|
|
82
|
+
| AnimatedBeam | SVG beams connecting elements | [Demo](https://fancy-ui.rama.app/demo/animated-beam) |
|
|
83
|
+
| BorderBeam | Beam effect traveling around borders | [Demo](https://fancy-ui.rama.app/demo/border-beam) |
|
|
84
|
+
| Confetti | Configurable confetti burst | [Demo](https://fancy-ui.rama.app/demo/confetti) |
|
|
85
|
+
| FluidCursor | WebGL fluid simulation following cursor | [Demo](https://fancy-ui.rama.app/demo/fluid-cursor) |
|
|
86
|
+
| GlowBorder | Animated glowing border with gradients | [Demo](https://fancy-ui.rama.app/demo/glow-border) |
|
|
87
|
+
| GlowingEffect | Glowing highlight on hover | [Demo](https://fancy-ui.rama.app/demo/glowing-effect) |
|
|
88
88
|
| ImageTrailCursor | Cursor-following image trail (8 variants) | [Demo](https://fancy-ui.rama.app/demo/image-trail-cursor) |
|
|
89
|
-
| LiquidGlass
|
|
90
|
-
| Meteors
|
|
91
|
-
| NeonBorder
|
|
92
|
-
| Ripple
|
|
93
|
-
| SmoothCursor
|
|
94
|
-
| Sparkles
|
|
95
|
-
| TracingBeam
|
|
89
|
+
| LiquidGlass | Liquid glass morphism effect | [Demo](https://fancy-ui.rama.app/demo/liquid-glass) |
|
|
90
|
+
| Meteors | Animated meteor shower effect | [Demo](https://fancy-ui.rama.app/demo/meteors) |
|
|
91
|
+
| NeonBorder | Dual-color neon glow border | [Demo](https://fancy-ui.rama.app/demo/neon-border) |
|
|
92
|
+
| Ripple | Expanding ripple rings | [Demo](https://fancy-ui.rama.app/demo/ripple) |
|
|
93
|
+
| SmoothCursor | Smooth lagging cursor follower | [Demo](https://fancy-ui.rama.app/demo/smooth-cursor) |
|
|
94
|
+
| Sparkles | Particle sparkle canvas | [Demo](https://fancy-ui.rama.app/demo/sparkles) |
|
|
95
|
+
| TracingBeam | Scroll-driven tracing beam | [Demo](https://fancy-ui.rama.app/demo/tracing-beam) |
|
|
96
96
|
|
|
97
97
|
### Layout
|
|
98
98
|
|
|
99
|
-
| Component
|
|
100
|
-
|
|
101
|
-
| BentoGrid
|
|
102
|
-
| Book
|
|
103
|
-
| ContainerScroll | 3D scroll perspective container
|
|
104
|
-
| Focus
|
|
105
|
-
| Marquee
|
|
99
|
+
| Component | Description | Demo |
|
|
100
|
+
| --------------- | --------------------------------------------- | ------------------------------------------------------- |
|
|
101
|
+
| BentoGrid | Bento-style responsive grid layout | [Demo](https://fancy-ui.rama.app/demo/bento-grid) |
|
|
102
|
+
| Book | 3D book flip animation | [Demo](https://fancy-ui.rama.app/demo/book) |
|
|
103
|
+
| ContainerScroll | 3D scroll perspective container | [Demo](https://fancy-ui.rama.app/demo/container-scroll) |
|
|
104
|
+
| Focus | Focus-expand card layout | [Demo](https://fancy-ui.rama.app/demo/focus) |
|
|
105
|
+
| Marquee | Infinite scrolling for text, images, or cards | [Demo](https://fancy-ui.rama.app/demo/marquee) |
|
|
106
106
|
|
|
107
107
|
### Navigation & Display
|
|
108
108
|
|
|
109
|
-
| Component
|
|
110
|
-
|
|
111
|
-
| AnimatedTooltip | Avatar row with mouse-following tooltips
|
|
112
|
-
| Compare
|
|
113
|
-
| Dock
|
|
114
|
-
| LogoCloud
|
|
115
|
-
| Timeline
|
|
109
|
+
| Component | Description | Demo |
|
|
110
|
+
| --------------- | --------------------------------------------- | ------------------------------------------------------- |
|
|
111
|
+
| AnimatedTooltip | Avatar row with mouse-following tooltips | [Demo](https://fancy-ui.rama.app/demo/animated-tooltip) |
|
|
112
|
+
| Compare | Before/after image comparison slider | [Demo](https://fancy-ui.rama.app/demo/compare) |
|
|
113
|
+
| Dock | macOS-style dock with icon magnification | [Demo](https://fancy-ui.rama.app/demo/dock) |
|
|
114
|
+
| LogoCloud | Marquee, grid, and icon logo layouts | [Demo](https://fancy-ui.rama.app/demo/logo-cloud) |
|
|
115
|
+
| Timeline | Vertical timeline with scroll-driven progress | [Demo](https://fancy-ui.rama.app/demo/timeline) |
|
|
116
116
|
|
|
117
117
|
## Quick Start
|
|
118
118
|
|
|
@@ -125,7 +125,7 @@ npm install fancy-ui-svelte
|
|
|
125
125
|
Then import any component:
|
|
126
126
|
|
|
127
127
|
```ts
|
|
128
|
-
import { Marquee, BorderBeam, Confetti } from
|
|
128
|
+
import { Marquee, BorderBeam, Confetti } from "fancy-ui-svelte";
|
|
129
129
|
```
|
|
130
130
|
|
|
131
131
|
Add the Tailwind integration to your app's CSS (e.g. `src/app.css`):
|
|
@@ -146,9 +146,11 @@ This single import tells Tailwind v4 to scan the library's components so all uti
|
|
|
146
146
|
```
|
|
147
147
|
|
|
148
148
|
The path is relative to your CSS file location.
|
|
149
|
+
|
|
149
150
|
</details>
|
|
150
151
|
|
|
151
152
|
**Or browse and copy a component:**
|
|
153
|
+
|
|
152
154
|
1. Find the component you need in the [live demo](https://fancy-ui.rama.app)
|
|
153
155
|
2. Copy the source from `src/lib/fancy-ui/[component-name]/`
|
|
154
156
|
3. Paste into your project
|
|
@@ -189,8 +191,11 @@ pnpm check # Run Svelte type checker
|
|
|
189
191
|
pnpm test # Run tests
|
|
190
192
|
pnpm test:watch # Run tests in watch mode
|
|
191
193
|
pnpm build # Production build
|
|
194
|
+
pnpm storybook # Component workshop on :6006
|
|
192
195
|
```
|
|
193
196
|
|
|
197
|
+
Component stories live in `src/stories/` — see the [Storybook section in CONTRIBUTING.md](./CONTRIBUTING.md#storybook).
|
|
198
|
+
|
|
194
199
|
## Project Structure
|
|
195
200
|
|
|
196
201
|
```
|
|
@@ -226,15 +231,15 @@ Contributions are welcome! 56 components and counting — PRs for new components
|
|
|
226
231
|
|
|
227
232
|
## Tech Stack
|
|
228
233
|
|
|
229
|
-
| Technology
|
|
230
|
-
|
|
231
|
-
| [Svelte](https://svelte.dev)
|
|
232
|
-
| [SvelteKit](https://svelte.dev/docs/kit)
|
|
233
|
-
| [Tailwind CSS](https://tailwindcss.com)
|
|
234
|
-
| [TypeScript](https://www.typescriptlang.org) | 5
|
|
235
|
-
| [Vitest](https://vitest.dev)
|
|
236
|
-
| [bits-ui](https://bits-ui.com)
|
|
237
|
-
| [GSAP](https://gsap.com)
|
|
234
|
+
| Technology | Version | Purpose |
|
|
235
|
+
| -------------------------------------------- | ------- | ------------------- |
|
|
236
|
+
| [Svelte](https://svelte.dev) | 5 | UI framework |
|
|
237
|
+
| [SvelteKit](https://svelte.dev/docs/kit) | 2 | App framework |
|
|
238
|
+
| [Tailwind CSS](https://tailwindcss.com) | 4 | Styling |
|
|
239
|
+
| [TypeScript](https://www.typescriptlang.org) | 5 | Type safety |
|
|
240
|
+
| [Vitest](https://vitest.dev) | 4 | Testing |
|
|
241
|
+
| [bits-ui](https://bits-ui.com) | 2 | Headless primitives |
|
|
242
|
+
| [GSAP](https://gsap.com) | 3 | Advanced animations |
|
|
238
243
|
|
|
239
244
|
## Credits
|
|
240
245
|
|
|
@@ -0,0 +1,202 @@
|
|
|
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 — fall back to
|
|
159
|
+
plain frosted blur */
|
|
160
|
+
@supports (-webkit-hyphens: none) {
|
|
161
|
+
.frosted-glass-filter {
|
|
162
|
+
filter: none !important;
|
|
163
|
+
backdrop-filter: blur(var(--fg-fallback-blur)) saturate(var(--fg-fallback-saturation));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.frosted-glass-overlay {
|
|
167
|
+
background: hsla(0, 0%, 100%, 0.5);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.frosted-glass-defs {
|
|
172
|
+
position: absolute;
|
|
173
|
+
width: 0;
|
|
174
|
+
height: 0;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.frosted-glass-overlay {
|
|
178
|
+
position: absolute;
|
|
179
|
+
inset: 0;
|
|
180
|
+
z-index: 1;
|
|
181
|
+
background: var(--fg-tint);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.frosted-glass-specular {
|
|
185
|
+
position: absolute;
|
|
186
|
+
inset: 0;
|
|
187
|
+
z-index: 2;
|
|
188
|
+
border-radius: inherit;
|
|
189
|
+
overflow: hidden;
|
|
190
|
+
box-shadow:
|
|
191
|
+
inset 1px 1px 0 var(--fg-highlight),
|
|
192
|
+
inset 0 0 5px var(--fg-highlight);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.frosted-glass-content {
|
|
196
|
+
position: relative;
|
|
197
|
+
z-index: 3;
|
|
198
|
+
display: flex;
|
|
199
|
+
align-items: center;
|
|
200
|
+
width: 100%;
|
|
201
|
+
}
|
|
202
|
+
</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,59 @@
|
|
|
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
|
+
Safari cannot combine `filter: url()` with `backdrop-filter`, so it falls back to a plain `blur() saturate()` frosted look (tunable via `fallbackBlur` / `fallbackSaturation`).
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
```svelte
|
|
21
|
+
<script>
|
|
22
|
+
import { FrostedGlass } from "fancy-ui-svelte";
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<FrostedGlass radius={9999}>
|
|
26
|
+
<nav class="flex w-full items-center justify-between px-7 py-4">
|
|
27
|
+
<span class="font-semibold">Brand</span>
|
|
28
|
+
<a href="/download">Download</a>
|
|
29
|
+
</nav>
|
|
30
|
+
</FrostedGlass>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Props
|
|
34
|
+
|
|
35
|
+
| Prop | Type | Default | Description |
|
|
36
|
+
| -------------------- | --------- | --------------------------- | ------------------------------------------------- |
|
|
37
|
+
| `radius` | `number` | `24` | Border radius in pixels |
|
|
38
|
+
| `baseFrequency` | `number` | `0.008` | Turbulence noise frequency (lower = wider waves) |
|
|
39
|
+
| `numOctaves` | `number` | `2` | Turbulence octaves (detail of the noise) |
|
|
40
|
+
| `seed` | `number` | `92` | Turbulence random seed |
|
|
41
|
+
| `noiseBlur` | `number` | `2` | Gaussian blur softening the noise before displace |
|
|
42
|
+
| `scale` | `number` | `70` | Displacement intensity |
|
|
43
|
+
| `tint` | `string` | `"hsla(0, 0%, 100%, 0.25)"` | Overlay tint color |
|
|
44
|
+
| `highlight` | `string` | `"hsla(0, 0%, 100%, 0.75)"` | Specular rim highlight color |
|
|
45
|
+
| `border` | `boolean` | `true` | Show the conic-gradient glass border |
|
|
46
|
+
| `fallbackBlur` | `number` | `20` | Backdrop blur (px) for the Safari fallback |
|
|
47
|
+
| `fallbackSaturation` | `number` | `180` | Backdrop saturation (%) for the Safari fallback |
|
|
48
|
+
| `class` | `string` | `""` | CSS classes for the content layer |
|
|
49
|
+
| `containerClass` | `string` | `""` | CSS classes for the outer container |
|
|
50
|
+
|
|
51
|
+
## Slots
|
|
52
|
+
|
|
53
|
+
| Slot | Description |
|
|
54
|
+
| ---------- | ------------------------------------ |
|
|
55
|
+
| `children` | Content rendered on top of the glass |
|
|
56
|
+
|
|
57
|
+
## Notes
|
|
58
|
+
|
|
59
|
+
- The displacement only shows over content that scrolls or moves behind the glass; over a flat background the effect reads as a subtle frost.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as FrostedGlass } from "./FrostedGlass.svelte";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as FrostedGlass } from "./FrostedGlass.svelte";
|
package/dist/fancy-ui/index.d.ts
CHANGED
|
@@ -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";
|
package/dist/fancy-ui/index.js
CHANGED
|
@@ -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";
|
|
@@ -1634,6 +1634,74 @@ export const registry = {
|
|
|
1634
1634
|
},
|
|
1635
1635
|
],
|
|
1636
1636
|
},
|
|
1637
|
+
"frosted-glass": {
|
|
1638
|
+
name: "FrostedGlass",
|
|
1639
|
+
slug: "frosted-glass",
|
|
1640
|
+
description: "Frosted glass surface with organic turbulence-noise refraction, an alternative to LiquidGlass",
|
|
1641
|
+
category: "effects",
|
|
1642
|
+
status: "done",
|
|
1643
|
+
tags: ["glass", "effect", "svg", "filter", "turbulence", "displacement", "blur", "navbar"],
|
|
1644
|
+
props: [
|
|
1645
|
+
{ name: "radius", type: "number", default: "24", description: "Border radius in pixels" },
|
|
1646
|
+
{
|
|
1647
|
+
name: "baseFrequency",
|
|
1648
|
+
type: "number",
|
|
1649
|
+
default: "0.008",
|
|
1650
|
+
description: "Turbulence noise frequency (lower = wider waves)",
|
|
1651
|
+
},
|
|
1652
|
+
{
|
|
1653
|
+
name: "numOctaves",
|
|
1654
|
+
type: "number",
|
|
1655
|
+
default: "2",
|
|
1656
|
+
description: "Turbulence octaves controlling noise detail",
|
|
1657
|
+
},
|
|
1658
|
+
{ name: "seed", type: "number", default: "92", description: "Turbulence random seed" },
|
|
1659
|
+
{
|
|
1660
|
+
name: "noiseBlur",
|
|
1661
|
+
type: "number",
|
|
1662
|
+
default: "2",
|
|
1663
|
+
description: "Gaussian blur softening the noise before displacement",
|
|
1664
|
+
},
|
|
1665
|
+
{ name: "scale", type: "number", default: "70", description: "Displacement intensity" },
|
|
1666
|
+
{
|
|
1667
|
+
name: "tint",
|
|
1668
|
+
type: "string",
|
|
1669
|
+
default: '"hsla(0, 0%, 100%, 0.25)"',
|
|
1670
|
+
description: "Overlay tint color",
|
|
1671
|
+
},
|
|
1672
|
+
{
|
|
1673
|
+
name: "highlight",
|
|
1674
|
+
type: "string",
|
|
1675
|
+
default: '"hsla(0, 0%, 100%, 0.75)"',
|
|
1676
|
+
description: "Specular rim highlight color",
|
|
1677
|
+
},
|
|
1678
|
+
{
|
|
1679
|
+
name: "border",
|
|
1680
|
+
type: "boolean",
|
|
1681
|
+
default: "true",
|
|
1682
|
+
description: "Show the conic-gradient glass border",
|
|
1683
|
+
},
|
|
1684
|
+
{
|
|
1685
|
+
name: "fallbackBlur",
|
|
1686
|
+
type: "number",
|
|
1687
|
+
default: "20",
|
|
1688
|
+
description: "Backdrop blur in pixels for the Safari fallback",
|
|
1689
|
+
},
|
|
1690
|
+
{
|
|
1691
|
+
name: "fallbackSaturation",
|
|
1692
|
+
type: "number",
|
|
1693
|
+
default: "180",
|
|
1694
|
+
description: "Backdrop saturation percentage for the Safari fallback",
|
|
1695
|
+
},
|
|
1696
|
+
{
|
|
1697
|
+
name: "containerClass",
|
|
1698
|
+
type: "string",
|
|
1699
|
+
default: '""',
|
|
1700
|
+
description: "CSS classes for the outer container",
|
|
1701
|
+
},
|
|
1702
|
+
],
|
|
1703
|
+
slots: [{ name: "children", description: "Content rendered on top of the glass" }],
|
|
1704
|
+
},
|
|
1637
1705
|
"liquid-glass": {
|
|
1638
1706
|
name: "LiquidGlass",
|
|
1639
1707
|
slug: "liquid-glass",
|