# ethereal-glow — full reference > Travelling-light borders and black-hole accretion glows as React components — pure CSS gradients and masks by default, with a dithered-canvas renderer alongside, all driven by one shared 60fps loop for every mounted instance. - Docs: https://ethereal.ale.design/docs - Playground: https://ethereal.ale.design/playground - Source: https://github.com/TheAleSch/ethereal - License: MIT --- # ethereal-glow **[Playground](https://ethereal.ale.design/playground)** · **[Docs](https://ethereal.ale.design/docs)** · **[ethereal.ale.design](https://ethereal.ale.design)** Travelling-light, black-hole and dithered glow effects for React. Two renderers use CSS gradients and masks; the dithered renderer uses a pixelated canvas. All three are driven by **one shared ~60fps loop** for every mounted instance. Three effects: - **``** — a comet head travels along your element behind a spotlight mask: a lit stretch of the border ring, an interior color wash, thin light needles and a white-hot core. - **``** — a black-hole accretion disk: a white-hot head orbits trailing a doppler-tinted plasma stream, wrapped in a graduated gravitational lens, a living photon rim and a lensed halo. - **``** — the travelling-light comet rendered as quantized canvas blocks through a Bayer 4×4 matrix. ## Install ```sh npm i ethereal-glow ``` React ≥18 is a peer dependency. All three components are client components (`'use client'`) — safe to import from React Server Components. ## Use Drop the effect inside any element that has `position: relative` and `isolation: isolate`: ```tsx import { Ethereal, EventHorizon } from 'ethereal-glow' ``` When you can't edit the child (inputs, third-party components), use the wrapper form — replaced elements can't contain the effect span: ```tsx import { EtherealWrap, EventHorizonWrap, EtherealDitherWrap, } from "ethereal-glow"; ; ``` All props are optional overrides of the exported defaults (`ETHEREAL`, `EVENT_HORIZON`, `ETHEREAL_DITHER`). The playground's **Copy link** / code panel emits a ready-to-spread props object: ```tsx ``` Event Horizon ships presets: ```tsx import { EVENT_HORIZON_PRESETS } from "ethereal-glow"; ; ``` ## EtherealDither The comet as **digital dithered blocks** — a canvas at one-pixel-per-cell resolution upscaled with `image-rendering: pixelated`, intensity quantized through a Bayer 4×4 matrix. Retro-terminal cousin of ``, same host contract and shared ticker: ```tsx import { EtherealDither, EtherealDitherWrap } from "ethereal-glow"; ; ``` Key props: `block` (cell px), `levels` (quantization steps), `reach` (glow radius), `band` (border band px), `bleed` (grid overhang px), plus the familiar `colors` / `path` / `heads` / `spin` / `duration` / `hover`. ## States `Ethereal` (and `EtherealWrap`) accept a `state` prop — a named **variation of the config you gave**, not a different look bolted on top. The built-in states are derived from your own config: your colors, path, geometry and pacing go in, and the same effect comes back quicker and more restless (`thinking`). A red comet going `around` stays a red comet going around. Changing state rebuilds the layers with a fade-in (`transitionMs`, default `320`, `0` disables). Built-ins mirror an AI chat composer: ```tsx const [state, setState] = useState<'idle' | 'thinking'>('idle') ``` | State | Character | | ---------- | ------------------------------------------------------------------------------------------------------------------------- | | `idle` | your base config, untouched | | `thinking` | your config, quicker and more restless — shorter duration, more wander and flicker, a tighter pulse, hover reactions off | `EventHorizon` and `EtherealDither` derive the same temperament from their own config shapes (orbit and shimmer; block wander and flicker). The derivation is a fallback, not an override: anything you state explicitly wins. Define or override states with the `states` prop — an entry there replaces the derived value key by key, and leaves the rest derived. A state is keyed by **variant**, not by config key directly: | Variant | Applied | | ---------------- | -------------------------------------------------------------------------------------------------------------------------- | | `light` / `dark` | the theme branch, picked by the resolved theme | | `.base` | that theme's config for the state | | `.whileHover` | merged on top while hovered (mouse only — a touch tap never latches it on) | | `.whilePressed` | merged on top while pressed, by pointer **or** keyboard (Enter/Space), held ~600ms so a quick click still reads as a pulse | Theme owns interaction, so light and dark can carry different hover treatments. Within a theme, later slots win: `whilePressed` overrides `whileHover`. Full precedence, lowest to highest: defaults → your flat props → `themes` branch → **derived state variation** → the named state's config (built-in, then `states`, then its hover/press slots) → the flat `whileHover` / `whilePressed` props. To show a state's variation without mounting the component — a preview, a generated snippet — call the rule directly: `deriveEtherealState(cfg, 'thinking')` (also `deriveEventHorizonState`, `deriveEtherealDitherState`) returns the `Partial` that would be merged over `cfg`. ```tsx ``` The same shape works on `` and `` — all three resolve states through one shared implementation. For a hover or press treatment that is the _same_ in both themes, skip the state entirely and use the top-level `whileHover` / `whilePressed` props: ```tsx ``` Both are flat `Partial` — no theme branch. They are spread after the resolved state, so for any one key use the prop overlay **or** the state's slot, not both. ### Per-theme base config When light and dark want different _values_ — not just a dimmer version of the same glow — branch the base config with `themes`: ```tsx ``` The full merge order, lowest to highest: | Layer | Source | | ----- | ------------------------------------------------------------------ | | 1 | exported defaults (`ETHEREAL`, `EVENT_HORIZON`, `ETHEREAL_DITHER`) | | 2 | your flat props | | 3 | `themes[resolvedTheme]` | | 4 | the named `state`'s theme branch, then its hover/press slots | | 5 | the flat `whileHover` / `whilePressed` props | `themes` sits below states on purpose: a state is the more specific thing, so `state="error"` can still override the theme baseline. If it were the other way round, one `themes.dark.duration` would silently pin duration across every state. `themes` is the only per-theme mechanism — all three effects take it, and it is the whole light-mode story for each of them. "Same config, just dimmer on white" is `themes={{ light: { strength: 0.6 } }}`. Mind the shape asymmetry between the two props: a `themes` branch is flat, but a `states` branch has interaction slots — so `themes.light.duration` works while `themes.light.base.duration` is a silent no-op. ### Theme resolution Zero configuration in the common case. On mount each effect resolves, in order: 1. the `theme` prop, if you passed one — detection is skipped entirely 2. the host's own `data-theme="dark" | "light"` 3. the host's own `.dark` / `.light` class 4. the same two checks on every ancestor 5. `prefers-color-scheme: dark` 6. `light` | Setup | Resolves via | | -------------------------------------------------------------- | --------------------------- | | Tailwind `darkMode: 'class'` (and v4's `@custom-variant dark`) | `.dark` on ``, step 4 | | shadcn/ui + next-themes | `class="dark"`, step 4 | | next-themes `attribute="data-theme"` | step 2 / 4 | | Tailwind `darkMode: 'media'`, or no theme system at all | step 5 | | anything else | `themeDetector` | Changes are picked up live — one document-wide `MutationObserver` shared by every mounted instance watches `data-theme` and `class`, and a `matchMedia` listener covers the OS scheme. A theme toggle updates the glow without a remount. Two escape hatches: ```tsx {/* pin it */} myThemeStore.current} /> {/* replace the chain */} ``` Both opt out of watching: if you own the value, you own re-rendering when it changes. ## Key props ### Ethereal | Prop | Default | What it does | | ----------------------------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------- | | `colors` | 9-color rainbow | Palette cycled across blobs/needles | | `path` | `'bottom'` | `bottom` line · `around` full perimeter · `breathe` stationary pulse · `static` fixed band + waveform needles | | `heads` / `spin` | `1` / `'same'` | Dual comets, co- or counter-rotating | | `place` | `'internal'` | `internal` · `external` · `ext-border` (glow only outside) · `both` | | `spotShape` | `'round'` | `round` = the spotlight is a chain of circles that individually follow the path — never a stretched ellipse | | `spotSamples` | `0` | Circles in the round chain (0 = auto from spotW/spotH ratio) | | `hotspots` / `hotSpread` | `1` / `22` | Extra white-hot cores fanned along the path, each walking it individually | | `hover` | `'none'` | `boost` · `speed` · `reveal` | | `duration`, `spotW/H`, `needles`, `glowBlur`, … | | See `EtherealCfg` — every field is typed and documented in the source | ### EventHorizon | Prop | Default | What it does | | ------------------------------------------ | --------------------- | ------------------------------------------------------------------------------ | | `colors` | warm oranges + violet | Doppler palette down the tail | | `duration` / `dir` | `6` / `1` | Orbit period and direction | | `ring`, `tail`, `nodes`, `node`, `shimmer` | | Accretion ring thickness, tail length, stream density, node size, surge amount | | `blur`, `halo`, `dist` | `14`, `0.9`, `0` | Halo blur, opacity, and distance off the border | | `shape` | `'adaptive'` | `round` = fixed-circle silhouette through corners | | `corner` | `0.3` | Superellipse corner exponent (lower = squarer path) | | `lens` | `4` | Graduated backdrop-filter lensing strength (0 = off) | | `shadow` | `0.35` | Center vignette depth | | `hover` | `'boost'` | `boost` · `speed` · `reveal` · `none` | ## Behavior & performance - **One rAF loop** drives every instance of all three effects, targeting ~60fps — one tick per frame on a 60Hz display; higher-refresh displays are gated down to the target. It stops entirely when the last instance unmounts. - **The frame rate is tunable** via `setTickRate(fps)` — pass `0` to tick at the display's native refresh rate, or `30` to halve the paint cost on hero-size effects. Speed is unaffected: every effect integrates against wall-clock `dt` rather than counting frames. ```ts import { setTickRate, getTickRate } from "ethereal-glow"; setTickRate(30); // large effect, tight budget ``` It is one process-wide setting, not per-instance — every effect shares the loop, so the last caller wins. `getTickRate()` reads it back if a component needs to restore the previous value on unmount. `` redraws its canvas every tick rather than only writing CSS custom properties, so it can be the most expensive renderer on a large host — consider `setTickRate(30)` when using it. - **Off-screen instances pause** (IntersectionObserver, 160px margin) and the loop clamps `dt` after background-tab pauses so clocks never jump. - **Layout reads are cached** by ResizeObserver — the per-frame code only writes CSS custom properties, so there is no layout thrash. - **`prefers-reduced-motion: reduce`** renders a static glow with no animation loop. - **Theming**: honors `html[data-theme]`, `.light`/`.dark` classes, or the OS scheme — see [Theme resolution](#theme-resolution). One shared MutationObserver and one `matchMedia` listener serve every mounted instance, regardless of how deep in the tree they sit. Use `themes.light` to tune any of the three effects for light backgrounds. - **State transitions**: re-render with a different config and the rebuilt layers fade in over ~320ms (e.g. idle → thinking on a chat composer). ## Clipping caveat With `place: "external"` / `"ext-border"` / `"both"` (and Event Horizon's halo always) the glow paints OUTSIDE the host, so any ancestor that clips will cut it off: `overflow` hidden/auto/scroll/clip, `contain: paint`, `clip-path`, transformed ancestors with overflow. Give the nearest scroll container enough padding for the glow reach (~`glowBlur×2 + 30px`), lift the element out of the clipping wrapper, or use `place: "internal"`. Also: `place: "internal"` draws INSIDE the element — on a solid bright fill it barely reads. Use external placement there, or keep ethereal elements dark/outlined. ## License MIT --- # Complete prop reference Every prop below is an optional override of the exported defaults (`ETHEREAL`, `EVENT_HORIZON`, `ETHEREAL_DITHER`). ## Getting started ```sh npm i ethereal-glow ``` Or via the shadcn/ui registry — this drops an `EtherealButton` you own into `components/ui/` and adds the package as a dependency, so fixes still arrive over npm rather than being stranded in your tree: ```sh npx shadcn@latest add https://ethereal.ale.design/r/ethereal.json ``` ```tsx import { Ethereal, EventHorizon } from 'ethereal-glow' // Ethereal — a comet travels the border behind a spotlight mask // EventHorizon — a black-hole accretion disk ``` ### The host contract ```css /* Every effect needs a positioned, isolated host. Tailwind: */ /* Plain CSS: */ .host { position: relative; isolation: isolate; } ``` ### Wrap components ```tsx import { EtherealWrap, EventHorizonWrap, EtherealDitherWrap } from 'ethereal-glow' // Replaced elements (inputs, textareas) can't contain the effect span — // wrap them instead. The wrapper span becomes the positioned, isolated host. // EventHorizonWrap and EtherealDitherWrap provide the same host contract. ``` ## `` props #### Colors, path & timing | Prop | Type | Default | Description | | --- | --- | --- | --- | | `colors` | `string[]` | `9-color rainbow` | Palette cycled across blobs, needles and cores. Any CSS-parseable color. | | `path` | `'bottom' \| 'around' \| 'breathe' \| 'static'` | `'bottom'` | bottom = light sweeps the bottom edge · around = full perimeter · breathe = stationary pulse · static = fixed bottom band with waveform needles. | | `duration` | `number` | `3.1` | Seconds for one full travel cycle (clamped to 0.1–600). | | `repeatDelay` | `number` | `0` | Dead time (s) after each cycle before the next begins — less frequent, not slower. | | `heads` | `1 \| 2` | `1` | One travelling comet head, or two. | | `spin` | `'same' \| 'counter'` | `'same'` | With heads = 2, the two heads co-rotate or counter-rotate. | | `travelEase` | `'linear' \| 'ease-in' \| 'ease-out' \| 'ease-in-out' \| 'back' \| 'bounce'` | `'linear'` | Easing applied to the head's progress along the path. | | `wander` | `number` | `0` | Slow sine warp of progress — the head hesitates and hurries (0 = steady). | | `breatheAmp` | `number` | `0.25` | Amplitude of the breathe-mode pulse; also drives subtle size pulsing. | #### Spot geometry & placement | Prop | Type | Default | Description | | --- | --- | --- | --- | | `place` | `'internal' \| 'external' \| 'ext-border' \| 'both'` | `'internal'` | Where the glow paints: inside the host, outside, only outside the border, or both. External placements can be clipped by ancestors (see caveats). | | `spotShape` | `'adaptive' \| 'round'` | `'round'` | adaptive stretches the spotlight along the travel direction; round is a chain of equal circles that each follow the path — never a stretched ellipse. | | `spotSamples` | `number` | `0` | round only: circles in the chain (0 = auto from spotW/spotH ratio, capped at 9). | | `trail` | `number` | `1` | Comet-tail length multiplier for the spot chain (clamped to 0.2–4). | | `lead` | `number` | `0` | Leading trail — the chain extends AHEAD of the head too (0 = none, 1 = as long as the tail). | | `trailFade` | `number` | `0.45` | How strongly trail circles shrink and dim toward the tail end (0–1). | | `spotW` | `number` | `78` | Spotlight width in px (clamped to 1–4000). | | `spotH` | `number` | `60` | Height of the light in px (clamped to 1–4000). Scales the glow's paint — blobs, needles, edge-relight bands — as well as the reveal window. | | `heightMin` | `number` | `0.8` | Lower bound of the height breathing, ×spotH (travel paths; breathe keeps breatheAmp). | | `heightMax` | `number` | `1.3` | Upper bound of the height breathing, ×spotH — raise it to let the light surge tall. | | `spotBlur` | `number` | `0` | Blur (px) of the masked-in spot content, softening the mask cutoff. | | `spotOffset` | `number` | `0` | Pushes the spotlight anchor outward past the border along the head's normal (px). Ignored in breathe. | | `strokeWidth` | `number` | `1` | Thickness (px) of the lit border ring. | | `blendSoftness` | `number` | `0.5` | Softness of gradient stops across the spot and needles (0–1). | | `reveal` | `number` | `1` | Scales the bloom spotlight mask (internal and external) — smaller = tighter reveal. | | `hotspots` | `number` | `1` | Extra white-hot cores fanned along the path, each walking it individually. | | `hotSpread` | `number` | `22` | Spacing (px) between fanned hotspot cores. | #### Needles | Prop | Type | Default | Description | | --- | --- | --- | --- | | `needles` | `number` | `7` | Count of thin light needles emitted along the edge. | | `needleHeight` | `number` | `1` | Length multiplier for the needles. | | `needleJitter` | `boolean` | `false` | breathe only: needles wobble and reshuffle onto new perimeter spots each cycle. | #### Bloom & opacities | Prop | Type | Default | Description | | --- | --- | --- | --- | | `glowBlur` | `number` | `8` | Gaussian blur (px) of the bloom / needle halo layer. | | `strokeOpacity` | `number` | `1.14` | Opacity multiplier of the lit border ring. | | `innerOpacity` | `number` | `0.7` | Opacity multiplier of the interior color wash (internal / both). | | `bloomOpacity` | `number` | `0.8` | Opacity multiplier of the needle bloom layer. | | `strength` | `number` | `1` | Master opacity multiplier over all layers. | | `saturation` | `number` | `1` | saturate() multiplier applied to every layer. | | `brightness` | `number` | `1` | brightness() multiplier applied to every layer. | | `hueRange` | `number` | `13` | Degrees of hue oscillation over time (0 = fixed hue; breathe does a full-circle rotation). | | `gamut` | `'srgb' \| 'p3'` | `'srgb'` | Color space for emitted gradients — p3 uses color(display-p3 …). | #### Hover | Prop | Type | Default | Description | | --- | --- | --- | --- | | `hover` | `'none' \| 'boost' \| 'speed' \| 'boost-speed' \| 'reveal'` | `'none'` | Pointer response: none · boost (brighter + larger) · speed (faster travel) · boost-speed (both at once) · reveal (fades in from hidden on hover). | | `hoverAmount` | `number` | `1` | Strength of the hover response. | | `hoverEase` | `number` | `8` | Smoothing rate of the hover transition (higher = snappier). | #### Misc & theming | Prop | Type | Default | Description | | --- | --- | --- | --- | | `flicker` | `number` | `0` | Candle-like irregular intensity jitter (0 = steady). | | `pulseMin` | `number` | `0.8` | Lower bound of the size / intensity pulse oscillators. | | `pulseMax` | `number` | `1.4` | Upper bound of the size / intensity pulse oscillators. | ## State, theme & interaction props Accepted by all three components. `Cfg` in the type column is the component's own config type: `EtherealCfg`, `EventHorizonCfg` or `EtherealDitherCfg`. | Prop | Type | Default | Description | | --- | --- | --- | --- | | `state` | `string \| null` | `'idle'` | Named state to apply: a built-in ('idle' \| 'thinking') or any key of `states`. Unknown names warn once and fall back to the base config; `null` suppresses state resolution entirely. | | `states` | `Record>` | `—` | Custom / overriding state configs, merged over the built-ins per key. | | `themes` | `Partial>>` | `—` | Per-theme base config, merged over your flat props and UNDER any named state — so a state can still override it. Note the shape: a themes branch is flat, unlike a states branch, so `themes.light.base.duration` is a silent no-op. It is the only per-theme mechanism on all three effects — a glow that should be dimmer on white is `themes={{ light: { strength: 0.6 } }}`. | | `transitionMs` | `number` | `320` | Cross-fade duration (ms) when the config or state changes; 0 disables the cross-fade. | | `whileHover` | `Partial` | `—` | Merged while the host is hovered (mouse only), with or without a named state. Theme-independent — for a light/dark split use a state's light.whileHover instead. | | `whilePressed` | `Partial` | `—` | Merged while pressed (pointer or keyboard). Layers over whileHover, never directly over base — a real press is hovered and pressed at once. A quick click pulses for ~0.6s. | | `theme` | `'light' \| 'dark'` | `auto` | Pins the theme and skips detection entirely. Omit it and the effect resolves: host data-theme → host .dark/.light class → the same two checks on every ancestor → prefers-color-scheme. Tailwind's class strategy, shadcn/ui and next-themes all work with no configuration. | | `themeDetector` | `(host: HTMLElement \| null) => 'light' \| 'dark'` | `—` | Replaces the whole resolution chain — use it when the theme lives somewhere the DOM does not show it (React context, a cookie, a custom attribute). Like `theme`, it opts out of watching: you own re-rendering when the value changes. | ### Tuning this in the playground The playground edits the same cascade, one cell at a time. The `Base` row is the flat config every state inherits; each named state below it is a `states` entry. Inside a row, two pill strips pick the cell you are editing: theme, then interaction slot. On the `Base` row the theme pills read `base` and `dark ^` rather than light/dark, because the base config is not symmetric: `base` edits the shared flat props, and `dark ^` writes a `themes.dark` override on top — the same shape as Tailwind's base styles plus `dark:` variants. On a named state the pills are plain `light` / `dark`, because a state does have two symmetric branches. A green dot on a pill means that cell holds overrides; an amber dot beside a control means that cell sets it, and clicking it clears the override. The expanded control section persists across theme, slot and state switches. ## `` props #### Colors & timing | Prop | Type | Default | Description | | --- | --- | --- | --- | | `colors` | `string[]` | `['#ffb46b', '#ff8a3d', '#b58cff']` | Doppler palette cycled down the accretion tail. | | `duration` | `number` | `6` | Seconds for one orbit (clamped to 0.5–600). | | `repeatDelay` | `number` | `0` | Dead time (s) after each orbit before the next begins — less frequent, not slower. | | `dir` | `1 \| -1` | `1` | Orbit direction. | #### Disk geometry | Prop | Type | Default | Description | | --- | --- | --- | --- | | `ring` | `number` | `2` | Accretion-ring thickness (px). | | `tail` | `number` | `1.2` | Tail length multiplier. | | `nodes` | `number` | `9` | Tail micro-spot pairs — stream density (chain capped at 32 spots). | | `node` | `number` | `1` | Node size multiplier. | | `shimmer` | `number` | `0.45` | How much the tail surges and pulses. | | `shape` | `'adaptive' \| 'round'` | `'adaptive'` | round collapses oriented ellipses to fixed circles — the silhouette never swells or squashes through corners. | | `corner` | `number` | `0.3` | Superellipse corner exponent for the orbit path (lower = squarer). | #### Lens, halo & shadow | Prop | Type | Default | Description | | --- | --- | --- | --- | | `lens` | `number` | `4` | Graduated gravitational-lens backdrop-blur strength (0 = off). | | `halo` | `number` | `0.9` | Halo opacity. | | `blur` | `number` | `14` | Halo gaussian blur (px). | | `dist` | `number` | `0` | Halo distance off the border (px). | | `shadow` | `number` | `0.35` | Center vignette depth. | #### Hover | Prop | Type | Default | Description | | --- | --- | --- | --- | | `hover` | `'none' \| 'boost' \| 'speed' \| 'reveal'` | `'boost'` | Pointer response — same modes as Ethereal. | | `hoverAmount` | `number` | `1.2` | Strength of the hover response. | | `hoverEase` | `number` | `8` | Smoothing rate of the hover transition. | ## Event Horizon presets Exported as `EVENT_HORIZON_PRESETS`: - `Gargantua` - `Blue giant` - `Ember disk` - `Violet quasar` - `Neutron` ```tsx import { EventHorizon, EVENT_HORIZON_PRESETS } from 'ethereal-glow' ``` ## `` props The same travelling comet rendered as ordered-dithered blocks on a canvas. It repaints every tick instead of writing CSS variables, so consider `setTickRate(30)` when you use it. #### Colors, path & timing | Prop | Type | Default | Description | | --- | --- | --- | --- | | `colors` | `string[]` | `['#4285f4', '#9b72cb', '#d96570', '#9b72cb']` | Palette cycled through the quantized blocks. | | `path` | `'around' \| 'bottom'` | `'around'` | Full perimeter, or a bottom-edge sweep. | | `place` | `'both' \| 'internal' \| 'external'` | `'both'` | Which side of the border the blocks paint on. | | `duration` | `number` | `7` | Seconds for one full travel cycle (clamped to 0.1–600). | | `heads` | `1 \| 2` | `1` | One travelling head, or two. | | `spin` | `'same' \| 'counter'` | `'same'` | With heads = 2, the two heads co-rotate or counter-rotate. | | `travelEase` | `'linear' \| 'ease-in' \| 'ease-out' \| 'ease-in-out' \| 'back' \| 'bounce'` | `'linear'` | Easing applied to the head's progress along the path. | | `repeatDelay` | `number` | `0` | Seconds the head rests at the end of the path before the next cycle. | | `wander` | `number` | `0` | Slow sine warp of progress — the head hesitates and hurries. | #### Dither grid | Prop | Type | Default | Description | | --- | --- | --- | --- | | `block` | `number` | `2` | Size of one block cell in px (clamped to 2–64) — the pixel size of the whole effect. | | `levels` | `number` | `4` | Intensity quantization steps, 2–16. Fewer = chunkier, more posterized. | | `reach` | `number` | `75` | Glow radius around the head (px, 8–2000). | | `band` | `number` | `6` | Thickness of the border band the glow hugs (px, 2–400). | | `bleed` | `number` | `0` | How far the grid extends past the host (px). 0 = auto, wide enough for the edge band's tail to fade out. | | `corner` | `number` | `0.3` | Superellipse corner exponent for the path (lower = squarer). | #### Intensity & color | Prop | Type | Default | Description | | --- | --- | --- | --- | | `strength` | `number` | `2` | Master intensity multiplier (0–4). | | `saturation` | `number` | `1.6` | Saturation multiplier applied to the palette. | | `brightness` | `number` | `1` | Brightness multiplier applied to the palette. | | `hueRange` | `number` | `6` | Degrees of hue oscillation over time (0 = fixed hue). | | `flicker` | `number` | `0` | Candle-like irregular intensity jitter (0 = steady). | | `pulseMin` | `number` | `0.9` | Lower bound of the reach pulse. | | `pulseMax` | `number` | `1.15` | Upper bound of the reach pulse. | | `hotspots` | `number` | `1` | Extra white-hot cores fanned along the path (1–8). | | `hotSpread` | `number` | `22` | Spacing (px) between fanned cores. | #### Hover | Prop | Type | Default | Description | | --- | --- | --- | --- | | `hover` | `'none' \| 'boost'` | `'boost'` | Pointer response — boost brightens and enlarges the glow. | | `hoverAmount` | `number` | `1` | Strength of the hover response. | ## Notes for coding agents - The host **must** be `position: relative` and `isolation: isolate`, and the effect **must** be a child of it. Without the stacking context the glow paints over your content instead of behind it. - Keep your own content on a higher layer (`relative z-10`) so it sits above the glow. - Replaced elements (``, ``, `