{"_id":"@el-gladiador/liquid-glass-react","_rev":"2-0c6551b1fb40a5c4db6b7ca44cbf9060","name":"@el-gladiador/liquid-glass-react","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.0":{"name":"@el-gladiador/liquid-glass-react","version":"0.1.0","keywords":["react","webgl","webgl2","liquid-glass","glassmorphism","refraction","gaussian-blur","ios-26","apple","ui","shader"],"author":{"name":"Zaki"},"license":"MIT","_id":"@el-gladiador/liquid-glass-react@0.1.0","maintainers":[{"name":"el-gladiador","email":"zaki.jsx@gmail.com"}],"homepage":"https://github.com/el-gladiador/liquid-glass-react#readme","bugs":{"url":"https://github.com/el-gladiador/liquid-glass-react/issues"},"dist":{"shasum":"a8bb98a5e5db3bef7d363636b8d6e42cdc400139","tarball":"https://registry.npmjs.org/@el-gladiador/liquid-glass-react/-/liquid-glass-react-0.1.0.tgz","fileCount":15,"integrity":"sha512-T4EIBFiumGINNDbWPJkIVOIpVGEKITFib/bLCJVUUuoWbLds3+q/9r1r3lmEcV0MftNXds1BvK1tVd5bPPi8kg==","signatures":[{"sig":"MEUCIQD/2Nnm3nOfwr1UEqDeJF1G2UCGhtP7giifySoNkRB0swIgLzVMRXaz5bmxoBChdxLgP9dTvSV70jMxXUERsQsNUqY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":455590},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./core":{"types":"./dist/core/index.d.ts","import":"./dist/core/index.js","require":"./dist/core/index.cjs"}},"gitHead":"ff37c470075c7b138a024b8fd21ebb4c98a3dd7c","scripts":{"dev":"tsup --watch","demo":"vite demo --host","build":"tsup","typecheck":"tsc --noEmit","prepublishOnly":"npm run typecheck && npm run build"},"_npmUser":{"name":"el-gladiador","email":"zaki.jsx@gmail.com"},"repository":{"url":"git+https://github.com/el-gladiador/liquid-glass-react.git","type":"git"},"_npmVersion":"11.13.0","description":"High-performance liquid glass / iOS-26 style refraction effects for React, powered by WebGL2 with multi-layer refraction and a real Gaussian blur kernel.","directories":{},"sideEffects":false,"_nodeVersion":"25.9.0","dependencies":{"html-to-image":"^1.11.13"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","vite":"^5.0.0","react":"^18.2.0","react-dom":"^18.2.0","typescript":"^5.3.0","@types/react":"^18.2.0","@types/react-dom":"^18.2.0"},"peerDependencies":{"react":">=17","react-dom":">=17"},"_npmOperationalInternal":{"tmp":"tmp/liquid-glass-react_0.1.0_1778085319894_0.17001234511565655","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@el-gladiador/liquid-glass-react","version":"0.1.1","description":"High-performance liquid glass / iOS-26 style refraction effects for React, powered by WebGL2 with multi-layer refraction and a real Gaussian blur kernel.","type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./core":{"types":"./dist/core/index.d.ts","import":"./dist/core/index.js","require":"./dist/core/index.cjs"}},"sideEffects":false,"scripts":{"build":"tsup","dev":"tsup --watch","typecheck":"tsc --noEmit","demo":"vite demo --host","prepublishOnly":"npm run typecheck && npm run build"},"keywords":["react","webgl","webgl2","liquid-glass","glassmorphism","refraction","gaussian-blur","ios-26","apple","ui","shader"],"repository":{"type":"git","url":"git+https://github.com/el-gladiador/liquid-glass-react.git"},"homepage":"https://github.com/el-gladiador/liquid-glass-react#readme","bugs":{"url":"https://github.com/el-gladiador/liquid-glass-react/issues"},"author":{"name":"Zaki"},"license":"MIT","publishConfig":{"access":"public"},"peerDependencies":{"react":">=17","react-dom":">=17"},"dependencies":{"html-to-image":"^1.11.13"},"devDependencies":{"@types/react":"^18.2.0","@types/react-dom":"^18.2.0","react":"^18.2.0","react-dom":"^18.2.0","tsup":"^8.0.0","typescript":"^5.3.0","vite":"^5.0.0"},"gitHead":"21e5c3a7ba1316c5ab679fbeeabe29e447c0b736","_id":"@el-gladiador/liquid-glass-react@0.1.1","_nodeVersion":"25.9.0","_npmVersion":"11.13.0","dist":{"integrity":"sha512-rK4WdVis1sMMObv9cFJvjC+TeHQvytK+QIMni4jT857h5kwe05dYaE8jLGdXBPg0o4rMkuUaoCpZzFcoiKx7qQ==","shasum":"a8b4406938401ac4887ff78f33316e8d7675fc5b","tarball":"https://registry.npmjs.org/@el-gladiador/liquid-glass-react/-/liquid-glass-react-0.1.1.tgz","fileCount":15,"unpackedSize":459796,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIClcOK5Prjpp3QFeiUV2WkVVLnQRop5WLvD9ZNL5xIFMAiEA1hXVM33/Y28cvjA8Y2J+YwTJwtQqTX0Za7p7TYD4lbo="}]},"_npmUser":{"name":"el-gladiador","email":"zaki.jsx@gmail.com"},"directories":{},"maintainers":[{"name":"el-gladiador","email":"zaki.jsx@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/liquid-glass-react_0.1.1_1778086146387_0.7792450277562832"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-06T16:35:19.833Z","modified":"2026-05-06T16:49:06.753Z","0.1.0":"2026-05-06T16:35:20.050Z","0.1.1":"2026-05-06T16:49:06.637Z"},"bugs":{"url":"https://github.com/el-gladiador/liquid-glass-react/issues"},"author":{"name":"Zaki"},"license":"MIT","homepage":"https://github.com/el-gladiador/liquid-glass-react#readme","keywords":["react","webgl","webgl2","liquid-glass","glassmorphism","refraction","gaussian-blur","ios-26","apple","ui","shader"],"repository":{"type":"git","url":"git+https://github.com/el-gladiador/liquid-glass-react.git"},"description":"High-performance liquid glass / iOS-26 style refraction effects for React, powered by WebGL2 with multi-layer refraction and a real Gaussian blur kernel.","maintainers":[{"name":"el-gladiador","email":"zaki.jsx@gmail.com"}],"readme":"# liquid-glass-react\n\nA React component for iOS-26-style liquid glass / refraction effects. Single shared WebGL2 context, **three-layer refraction** (edge / rim / centre), **real 13-tap Gaussian blur** sampled in-shader, and **ambient colour pickup** from the surrounding background. No per-element canvas, no per-frame allocation, no `requestAnimationFrame` spinning when nothing's changed.\n\n## Install\n\n```bash\nnpm install @el-gladiador/liquid-glass-react\n```\n\n`html-to-image` is bundled as a dependency. `react` and `react-dom` are peer deps (>=18).\n\n## Quick start\n\n```tsx\nimport { LiquidGlassProvider, LiquidGlass } from '@el-gladiador/liquid-glass-react';\n\nexport default function App() {\n  return (\n    <LiquidGlassProvider style={{ position: 'relative', minHeight: '100vh' }}>\n      {/* Anything here becomes the \"background\" that gets refracted */}\n      <img src=\"/hero.jpg\" alt=\"\" style={{ width: '100%' }} />\n\n      <LiquidGlass\n        style={{\n          position: 'absolute',\n          top: 40,\n          left: 40,\n          width: 280,\n          height: 120,\n          padding: 24,\n        }}\n        borderRadius={24}\n        refraction={0.06}\n        blur={4}\n        chromaticAberration={0.4}\n      >\n        <h2>Hello</h2>\n      </LiquidGlass>\n    </LiquidGlassProvider>\n  );\n}\n```\n\nThat's the whole API for typical use. The provider owns one shared WebGL context; every `<LiquidGlass>` inside it is just a uniform-state record that draws to the same canvas.\n\n## Props\n\n### `<LiquidGlassProvider>`\n\n| Prop | Type | Default | Notes |\n|---|---|---|---|\n| `maxDpr` | `number` | `2` | Caps the canvas pixel ratio. Higher = sharper, more GPU & memory. Most retina screens look fine at 2. |\n| `autoInvalidate` | `boolean` | `true` | If true, watches the DOM with a MutationObserver and recaptures on changes (debounced). |\n| `invalidateDebounceMs` | `number` | `120` | Debounce window for mutation-driven recapture. Mutation bursts (typical of React reconciliation) wait this long after the last change before triggering a capture. |\n| `captureCooldownMs` | `number` | `50` | Minimum interval between successive captures from any source. Acts as a floor that coalesces scroll, mutation, and dynamic-mode captures. Lower = more responsive scrolling, higher = less main-thread cost. |\n| `captureScale` | `number` (0.25–1) | `1` | Texture-resolution multiplier for the captured background. Drop to `0.7` for ~50% capture cost reduction; the result is invisible after blur. |\n| `pauseWhenOffscreen` | `boolean` | `true` | Pause the rAF render loop entirely when the provider scrolls out of the viewport. Recommended for long pages. |\n| `as` | `ElementType` | `'div'` | Tag to render as. |\n\nImperative ref: `{ invalidate(): void }` — call it to manually trigger a recapture.\n\n### `<LiquidGlass>`\n\nAll visual props are optional; sensible defaults are baked in. Refraction is modelled as three independent layers — `edge` (rim bend), `rim` (thin lip just inside the edge), and `base` (broad centre warp) — each with its own intensity and decay rate.\n\n| Prop | Type | Default | Notes |\n|---|---|---|---|\n| `borderRadius` | `number` (px) | `16` | Rounded corners. A circle is `borderRadius = halfMin`, a pill is `borderRadius = halfHeight` with `width > height`. |\n| `refraction` | `number` | `0.06` | Edge layer intensity. The dominant refraction term — bend at the rim. Fraction of element size. |\n| `edgeDistance` | `number` | `0.04` | Decay rate of the edge layer. Higher = bend is concentrated tighter against the rim. |\n| `rimIntensity` | `number` | `0.025` | Rim layer intensity — a thin extra refraction just inside the edge that reads as a glassy lip. |\n| `rimDistance` | `number` | `0.12` | Decay rate of the rim layer. |\n| `baseIntensity` | `number` | `0.012` | Centre layer intensity — a soft broad bend that reads as glass thickness. |\n| `baseDistance` | `number` | `0.008` | Decay rate of the centre layer. |\n| `cornerBoost` | `number` | `0.02` | Extra refraction near rectangle corners. No visible effect on circles or pills. Set to `0` to disable. |\n| `rippleEffect` | `number` | `0` | Tangential rim shimmer — a subtle wavy-glass effect. Try `0.005`–`0.02`. |\n| `blur` | `number` (px) | `4` | Background Gaussian blur radius in pixels. Sampled in-shader as a 13-tap circular kernel; mipmap LOD is auto-selected for large values. |\n| `chromaticAberration` | `number` | `0.4` | RGB split along the refraction direction. Try `0.3`–`0.6` for a subtle prismatic edge. |\n| `tint` | `string` (CSS color) | `'#ffffff'` | Solid tint colour mixed in lightly over the refracted background. |\n| `tintOpacity` | `number` (0–1) | `0.06` | Strength of the tint composition. Drives the vertical-gradient tint, the ambient pickup, and the solid-tint mix. |\n| `ambientTint` | `boolean` | `true` | Sample wide horizontal bands of the captured background above and below the element and use them as a vertical gradient tint. This is what gives Apple's glass its \"I picked up the colour of the room\" feel. |\n| `specular` | `number` | `0.6` | Strength of the directional rim highlight + the always-on inner rim sheen. Set to `0` to disable both. |\n| `lightDirection` | `[number, number]` | `[0.5, -0.7]` | 2D direction of the implied light, in canvas space (positive Y points down). |\n| `dynamic` | `boolean` | `false` | If true, recaptures the background every frame *for this element*. Use only when content behind the glass actually animates. Throttled by the provider's `captureCooldownMs` (default 50ms = ~20 captures/sec). |\n\n## Performance\n\nThree things drive cost, in order:\n\n**1. DOM rasterization (the big one).** `html-to-image` is the only realistic way to get the page into a texture, and it takes 30–200ms on a typical viewport. Everything in this library is built around making it run as rarely as possible.\n\n- The capture is **explicit by default**: it only fires when a MutationObserver detects a real DOM change, when the root scrolls, when the root resizes, or when you call `invalidate()`.\n- A **unified capture cooldown** (default 50ms) coalesces every source — scroll, mutation, dynamic mode — into one dial. Bursts collapse into a single capture.\n- A **mutation-only debounce** (default 120ms) gives React reconciliation time to settle before capturing. Scrolls don't pay this delay; they only pay the cooldown.\n- A **fast path** skips `html-to-image` entirely when the root contains a single `<img>`/`<video>`/`<canvas>` covering ≥95% of the area — common for hero images and video backgrounds.\n- **Off-screen pause:** when `pauseWhenOffscreen` is on (default), an `IntersectionObserver` halts the rAF loop entirely while the provider is scrolled out of view. Long pages pay nothing for off-screen glass sections.\n- **`captureScale` lets you trade sharpness for speed** on the rasterization pass. `captureScale={0.7}` cuts the texture pixel count in half; the result is invisible after blur.\n- `dynamic={true}` is opt-in per element, throttled by `captureCooldownMs`. Still a last resort — if only one small region behind the glass is animating, repaint that region into a `<canvas>` yourself and use the fast path.\n\n**2. Shader cost.** ~1ms at typical sizes. Three-layer refraction + 13-tap Gaussian (×3 with chromatic aberration) + ambient sampler + specular fits in ~50 texture samples per pixel.\n\n- Blur is a 13-tap circular Gaussian sampled in-shader, with the mipmap LOD chosen by the requested sigma — large blurs keep their sample count fixed and fall back to the prefiltered mip chain for the wide support.\n- Refraction normals come from a single SDF gradient (3 SDF samples). The same SDF mathematically reduces to the analytical capsule / circle SDF when `borderRadius` matches the geometry, so it gives correct shape-aware normals for all three shapes without branching.\n- Ambient tint samples 24 taps at LOD 5 — independent of element size or blur strength.\n- Texture re-uploads use `texSubImage2D` when capture dimensions are unchanged, avoiding GPU memory reallocation.\n- Mipmap regeneration is **skipped entirely** if no registered element actually needs a mip chain (no blur, no ambient tint).\n\n**3. Per-element overhead.** One uniform update + one `drawArrays` call per glass element per frame. Resize bursts on N elements are coalesced into a single root-rect read per frame. Negligible up to a few dozen elements.\n\n### Things you can do to keep it fast\n\n- **Wrap a constrained area, not the whole page.** The provider sizes its canvas to its own bounding rect. Wrapping a 4000px-tall scrolling page costs you a 4000px-tall texture, and `html-to-image` has to rasterize all of it. Wrap a hero section, a card grid, a modal — not the body.\n- **Drop `captureScale` to `0.7` or `0.5`** for any glass that uses real blur. The texture gets softer, but blur was already softening it. Visual difference is near-zero, capture cost drops by 2× or 4×.\n- **Avoid putting `<LiquidGlass>` in tight loops.** Every element costs an extra draw call and a `ResizeObserver`. A header full of glass icons is fine; a virtualized list of 200 glass rows is not.\n- **Use the fast-path layout when you can.** If your background is just an image, give it >95% of the root's area and the rasterization step is skipped entirely.\n- **Set `autoInvalidate={false}` and call `invalidate()` yourself** if you know your background only changes at specific moments. The MutationObserver is cheap but not free, and it can fire on benign style updates from CSS-in-JS.\n- **Don't pass fresh `lightDirection={[x, y]}` array literals every render** unless you actually want it animated. The component compares it element-wise so a stable value won't cause spurious re-syncs, but the value-equality check still runs.\n\n## Constraints to know about\n\n- **Glass elements are forced to `z-index: 1`.** The shared canvas sits at `z-index: 1` too, painted first via DOM order. Page content at default `z-index: auto` sits below; glass children float naturally on top via `isolation: isolate`. Other elements with explicit `z-index > 1` will sit above the canvas — set them higher if you need them above the glass too.\n- **The provider's `position` is forced to `relative`** if it's `static`. Otherwise the canvas can't position itself.\n- **Glass element backgrounds are forced transparent.** The whole point is for the WebGL canvas underneath to show through.\n- **No SSR rendering.** The provider sets up GL on mount in a `useEffect`, so the canvas only exists client-side. The component is marked `\"use client\"` for app-router compatibility.\n- **One WebGL context per provider.** Browsers cap WebGL contexts per page (~16 in Chrome, fewer in Safari). Don't nest providers; one per \"scene\" is the intended model.\n- **Cross-origin images won't capture** unless served with `crossorigin=\"anonymous\"` and proper CORS headers. This is an `html-to-image` constraint.\n\n## Low-level API\n\nIf you don't want React, the renderer is exported separately:\n\n```ts\nimport { GlassRenderer } from '@el-gladiador/liquid-glass-react/core';\n\nconst renderer = new GlassRenderer({ root: document.querySelector('#scene')! });\nconst handle = renderer.add(document.querySelector('#card')!, {\n  borderRadius: 24,\n  refraction: 0.05,\n});\n\nhandle.setOptions({ blur: 3 });\nhandle.invalidate();\nconst stop = renderer.registerDynamic(document.querySelector('#card')!);\nstop();\nhandle.destroy();\nrenderer.destroy();\n```\n\n## License\n\nMIT\n","readmeFilename":"README.md"}