glass-pulse-fx 0.1.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/LICENSE +21 -0
- package/README.md +224 -0
- package/dist/chunk-GF5DSI6Z.js +1360 -0
- package/dist/chunk-GF5DSI6Z.js.map +1 -0
- package/dist/core.cjs +1371 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +49 -0
- package/dist/core.d.ts +49 -0
- package/dist/core.js +3 -0
- package/dist/core.js.map +1 -0
- package/dist/index.cjs +1492 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +32 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +106 -0
- package/dist/index.js.map +1 -0
- package/dist/presets.cjs +326 -0
- package/dist/presets.cjs.map +1 -0
- package/dist/presets.d.cts +28 -0
- package/dist/presets.d.ts +28 -0
- package/dist/presets.js +313 -0
- package/dist/presets.js.map +1 -0
- package/dist/types-qQT95c-N.d.cts +157 -0
- package/dist/types-qQT95c-N.d.ts +157 -0
- package/package.json +86 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jerry Kou
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# glass-pulse-fx
|
|
2
|
+
|
|
3
|
+
> Frosted-glass UI surfaces lit by animated, original WebGL shaders.
|
|
4
|
+
|
|
5
|
+
Wrap any element — button, chip, card — and `glass-pulse-fx` paints a frosted-glass
|
|
6
|
+
material **on top of it**: a shared animated shader, a `backdrop-filter` frost, an opaque
|
|
7
|
+
blurred core, an optional lit rim, and two bloom layers that spill glow past the
|
|
8
|
+
silhouette. It never replaces your element — it reads your element's own background and
|
|
9
|
+
frosts *that*, so the glass sits on your surface.
|
|
10
|
+
|
|
11
|
+
One shared WebGL context renders the effect for the whole page; every instance is a handful
|
|
12
|
+
of cheap canvas copies plus CSS layers. The base shader is original (no third-party
|
|
13
|
+
attribution required) and the shader layer is pluggable.
|
|
14
|
+
|
|
15
|
+
## Requirements
|
|
16
|
+
|
|
17
|
+
- **React 18+** for the `<GlassFx>` component — or skip React entirely with the
|
|
18
|
+
framework-free `glass-pulse-fx/core` (`createGlass`).
|
|
19
|
+
- **WebGL** for the animated light. Without it the effect degrades to a flat fill + border;
|
|
20
|
+
without `backdrop-filter`, to a flat translucent tint.
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install glass-pulse-fx
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick start (React)
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
import { GlassFx } from 'glass-pulse-fx';
|
|
32
|
+
import { bloom } from 'glass-pulse-fx/presets';
|
|
33
|
+
|
|
34
|
+
export default function Cta() {
|
|
35
|
+
return (
|
|
36
|
+
<GlassFx preset={bloom} radius={12}>
|
|
37
|
+
<button className="your-button">Upgrade to Pro</button>
|
|
38
|
+
</GlassFx>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`<GlassFx>` wraps your component and overlays the glass. Style `.your-button` however you
|
|
44
|
+
like — its background becomes the frosted surface. `preset` is one ready-made look;
|
|
45
|
+
`radius` matches the glass corners to your component.
|
|
46
|
+
|
|
47
|
+
## Quick start (vanilla)
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { createGlass } from 'glass-pulse-fx/core';
|
|
51
|
+
import { comet } from 'glass-pulse-fx/presets';
|
|
52
|
+
|
|
53
|
+
const glass = createGlass(document.querySelector('#cta')!, {
|
|
54
|
+
preset: comet,
|
|
55
|
+
theme: 'dark',
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// later — tweak live, then clean up
|
|
59
|
+
glass.update({ bgBlur: 10, frost: 0.5 });
|
|
60
|
+
glass.destroy();
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## The overlay model
|
|
64
|
+
|
|
65
|
+
`glass-pulse-fx` paints **on top of** the element you wrap — it never touches its
|
|
66
|
+
background, border, radius, or shadow. The frost and core take their tint from your
|
|
67
|
+
element's own background (flattened to an opaque color over whatever sits behind it), so the
|
|
68
|
+
glass reads as *your* component's surface, frosted and lit — not a fill the library owns.
|
|
69
|
+
|
|
70
|
+
In short: style your component normally; the library reads it. `fill` and `border` (below)
|
|
71
|
+
are **optional overrides** — by default the surface tint comes from your element's
|
|
72
|
+
background and your element's own CSS border is the border.
|
|
73
|
+
|
|
74
|
+
## Presets
|
|
75
|
+
|
|
76
|
+
A `GlassPreset` is one shareable look — shader + params + glass material. It applies
|
|
77
|
+
identically in dark and light mode; anything it does **not** pin (palette, frost, …) still
|
|
78
|
+
adapts to the theme defaults. It carries **no component styling** (`fill` / `border` /
|
|
79
|
+
`radius`) — those belong to the component you wrap. Eleven presets ship — `bloom`, `halo`,
|
|
80
|
+
`rush`, `comet`, `cinder`, `plasma`, `kaleido`, `nimbus`, `emerald`, `glow`, `tide` (plus
|
|
81
|
+
`LIBRARY_PRESETS`, the full array):
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
import { GlassFx } from 'glass-pulse-fx';
|
|
85
|
+
import { bloom } from 'glass-pulse-fx/presets';
|
|
86
|
+
|
|
87
|
+
<GlassFx preset={bloom}>
|
|
88
|
+
<button className="your-button">Ping</button>
|
|
89
|
+
</GlassFx>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Want a *different* look per mode? One ternary:
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
import { rush, tide } from 'glass-pulse-fx/presets';
|
|
96
|
+
|
|
97
|
+
<GlassFx preset={isDark ? tide : rush} theme={isDark ? 'dark' : 'light'}>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Vanilla — `preset` is a `createGlass` option:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import { comet } from 'glass-pulse-fx/presets';
|
|
104
|
+
const glass = createGlass(el, { preset: comet });
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Custom presets
|
|
108
|
+
|
|
109
|
+
A preset is just an object — the [demo playground](#demo--playground) serializes whatever
|
|
110
|
+
you tune into a ready-to-paste literal (it updates live as you drag the controls; copying it
|
|
111
|
+
**freezes that look** into your code — it's a snapshot, not a live link):
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import type { GlassPreset } from 'glass-pulse-fx';
|
|
115
|
+
|
|
116
|
+
const mine: GlassPreset = {
|
|
117
|
+
name: 'Mine',
|
|
118
|
+
version: 1,
|
|
119
|
+
effectParams: { speed: 0.4, colors: ['#ff2d9b', '#19c3ff', '#15e6a4'] },
|
|
120
|
+
settings: { bgBlur: 10, frost: 0.5 },
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
<GlassFx preset={mine}>…</GlassFx>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Props (React) / options (vanilla)
|
|
127
|
+
|
|
128
|
+
| Prop / option | Type | Default | Notes |
|
|
129
|
+
|---|---|---|---|
|
|
130
|
+
| `preset` | `GlassPreset` | — | a shareable look; explicit props below win over it |
|
|
131
|
+
| `effect` | `'panes'` | `panes` | base shader |
|
|
132
|
+
| `effectParams` | `Partial<EffectParams>` | — | merged onto the preset's params + the effect's theme defaults |
|
|
133
|
+
| `theme` | `'dark' \| 'light' \| 'auto'` | `auto` (React) / `dark` (vanilla) | `auto` follows `prefers-color-scheme` |
|
|
134
|
+
| `fill` | CSS color | your element's background | override the frosted surface tint |
|
|
135
|
+
| `border` | `Partial<BorderConfig>` | off | optional lit rim `{ width, opacity, color }` — off by default, your element's own border shows |
|
|
136
|
+
| `radius` | `number \| string` | inferred | sets the glass clip + your element's `border-radius` |
|
|
137
|
+
| `kind` | `'pill' \| 'circle' \| 'rect' \| 'tag' \| 'card' \| 'icon'` | inferred | crop scale + default corner radius |
|
|
138
|
+
| `fps` | `15 \| 30 \| 60` | `30` | animation paint rate |
|
|
139
|
+
| `paused` | `boolean` | `false` | also auto-pauses on reduced-motion / offscreen / hidden tab |
|
|
140
|
+
| `bloomClip` | `boolean` | `false` | clip the bloom to the rounded box instead of letting it spill |
|
|
141
|
+
| `settings` | `GlassSettingsPatch` | — | glass material, merged onto theme defaults |
|
|
142
|
+
| `settingsByTheme` | `Partial<Record<Theme, GlassSettingsPatch>>` | — | per-theme glass overrides (React) |
|
|
143
|
+
|
|
144
|
+
### `GlassSettings` (the glass material — effect-independent)
|
|
145
|
+
|
|
146
|
+
| Field | Range | Default (dark) | Meaning |
|
|
147
|
+
|---|---|---|---|
|
|
148
|
+
| `bgBlur` | 0–20 px | 6 | frost background-blur |
|
|
149
|
+
| `frost` | 0.3–1 | 0.66 | frost tint opacity over the shader |
|
|
150
|
+
| `frostInset` | 0–12 px | 0 | insets the frost veil, exposing a raw full-brightness shader rim at the edge |
|
|
151
|
+
| `shaderInset` | 0–28 px | 0 | insets the animated shader source/bloom from the outer edge |
|
|
152
|
+
| `coreInset` | 0–28 px | 8 | opaque core inset from edge |
|
|
153
|
+
| `coreBlur` | 0–32 px | 8 | layer blur softening core → rim |
|
|
154
|
+
| `coreOpacity` | 0–1 | 1 | core opacity (lower = shader shows through the center) |
|
|
155
|
+
| `coreProportional` | bool | `false` | scale `coreInset` + `coreBlur` with element size (ref 52px) |
|
|
156
|
+
| `saturate` | 1–2× | 1.3 | chroma boost in the frost (counters the veil) |
|
|
157
|
+
| `innerBloom` | `{ size 0–24, level 0–1 }` | `{2, 1}` | tight, full-bright edge bleed |
|
|
158
|
+
| `outerBloom` | `{ size 0–64, level 0–0.9 }` | `{16, 0.45}` | wide, soft ambient glow |
|
|
159
|
+
|
|
160
|
+
## How it works
|
|
161
|
+
|
|
162
|
+
Per instance, bottom → top: **outer bloom** · **inner bloom** · a shared rounded
|
|
163
|
+
**surface mask** containing the insettable **shader** crop, **frost** (`backdrop-filter:
|
|
164
|
+
saturate() blur()` over a tint taken from your element's background), and **core** (an
|
|
165
|
+
inset, layer-blurred opaque fill) · an optional **lit border** · then your content. The
|
|
166
|
+
wrapper is `position:relative; isolation:isolate` and is **not** `overflow:hidden` (the
|
|
167
|
+
blooms must spill, unless you set `bloomClip`). All effect layers are `pointer-events:none`,
|
|
168
|
+
and your element's own background, border, radius, and shadow are left untouched.
|
|
169
|
+
|
|
170
|
+
## Shaders
|
|
171
|
+
|
|
172
|
+
One built-in base shader (the layer is pluggable — bring your own):
|
|
173
|
+
|
|
174
|
+
- **Panes** — discrete colored bands moving along a 1D coordinate: each fades in, holds,
|
|
175
|
+
fades out (via alpha, so the glass shows through), then a transparent interval before the
|
|
176
|
+
next. Knobs: `motion` (**Linear** sweep, **Center** — mirrored, bands emanate from the
|
|
177
|
+
middle, **Radial** — concentric rings ripple outward, **Orbit** — spokes sweep around
|
|
178
|
+
like a radar; `scale` rounds to the spoke count and the colour gradient snaps to whole
|
|
179
|
+
cycles around the ring so it wraps seamlessly),
|
|
180
|
+
`speed` (sign sets direction), a **velocity preset** (`velocity` — how band speed varies
|
|
181
|
+
across the axis: uniform, ease in/out, slow/fast middle), `scale` (band density), `interval`
|
|
182
|
+
(transparent spacing between bands), `rampIn` / `rampOut` (independent leading/trailing
|
|
183
|
+
fade), `angle`, and a **colour field decoupled from the bands** — 1–5 palette stops
|
|
184
|
+
sampled by `colorSpread` (along the motion), `colorSkew` (perpendicular → mesh) and
|
|
185
|
+
`colorDrift` (over time), so colour can vary *within* a band, not just band-to-band.
|
|
186
|
+
|
|
187
|
+
### Writing your own shader
|
|
188
|
+
|
|
189
|
+
Effects are isolated to [`src/engine/effects/`](src/engine/effects/). To add one:
|
|
190
|
+
|
|
191
|
+
1. Write an `EffectDef` (GLSL `frag`, `uniforms`, per-theme `defaults`, an `upload`, and
|
|
192
|
+
demo `controls`) — see [`panes.ts`](src/engine/effects/panes.ts). Reuse the shared
|
|
193
|
+
`paneColor` helper + uniform plumbing in [`common.ts`](src/engine/effects/common.ts).
|
|
194
|
+
2. List it in [`effects/index.ts`](src/engine/effects/index.ts) and add its id to the
|
|
195
|
+
`EffectId` union + any new fields to `EffectParams` in [`types.ts`](src/types.ts).
|
|
196
|
+
|
|
197
|
+
The renderer and compositor never reference uniform names — they only call
|
|
198
|
+
`renderEffect(id, params, time)`.
|
|
199
|
+
|
|
200
|
+
## Performance & accessibility
|
|
201
|
+
|
|
202
|
+
- One WebGL context, one program per effect, one RAF loop (~30 fps), torn down on last
|
|
203
|
+
unmount. Cost is one shader render per distinct (effect, params) group + N cheap copies.
|
|
204
|
+
- Offscreen instances are skipped (`IntersectionObserver`); the loop pauses when the tab is
|
|
205
|
+
hidden and freezes on `prefers-reduced-motion: reduce`.
|
|
206
|
+
- `backdrop-filter` is the heaviest part — set `bgBlur: 0` to drop the frost blur.
|
|
207
|
+
- No WebGL → flat `fill` + border. No `backdrop-filter` → flat translucent tint.
|
|
208
|
+
|
|
209
|
+
## Demo / playground
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
npm run dev # http://localhost:5173 — sidebar playground, save full-look presets
|
|
213
|
+
npm run build:demo # static site -> dist-demo/
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
The playground docks all controls in a left sidebar, shows live mockups, and keeps your
|
|
217
|
+
presets in `localStorage` (library presets from [`src/presets/`](src/presets/) appear
|
|
218
|
+
read-only — Duplicate to riff on one). The **Install** tab generates ready-to-paste React or
|
|
219
|
+
vanilla code from whatever you've tuned. See [SEPARATING-THE-DEMO.md](SEPARATING-THE-DEMO.md)
|
|
220
|
+
for deploying it standalone.
|
|
221
|
+
|
|
222
|
+
## License
|
|
223
|
+
|
|
224
|
+
MIT © 2026 Jerry Kou. All shaders are original work.
|