Power-User Systems

Mirage

Live, fully interactive HTML seen through moving air. An animated turbulence field displaces the real DOM subtree per pixel — no snapshot, no canvas replica — so the text stays selectable, the button still takes focus and the link is still a link while the whole panel shimmers.

Install

# Mirage needs no npm packages

Then copy 2 files into your project:

  • components/registry/mirage/mirage.tsx
  • hooks/use-animation-loop.ts

Props

PropTypeDefaultDescription
mode"haze" | "liquify" | "shatter" | "tear""haze"How the air moves. The four differ by the anisotropy of the noise rather than by strength: equal frequencies on both axes give soup, and a strongly biased axis is what makes a tear read as a tear instead of as more haze.
intensitynumber0.4How far the content is pushed. This is genuine per-pixel displacement rather than a transform — a transform can only move the whole box, which is why skew-based heat haze never convinces. Past about one the text stops being readable, which is a legitimate look and a terrible default.
speednumber0.35Rate the field drifts. The noise is rasterised once as a seamless tile and scrolled, never re-seeded — stepping a turbulence seed swaps in an entirely unrelated field each time, which reads as a component tearing itself apart rather than as air moving. The wrap at one tile is invisible because the tile is seamless.
scalenumber2.2Size of the turbulence cells, measured against a scrolling tile rather than against the element — so the same setting is the same air whether the panel is a card or a page. This sizes the tile itself, because a tile can hold no feature larger than its own period and the noise lattice only wraps on a whole number of cells; free the cell count instead and the field stops tiling, which shows up as the whole panel jumping to a different distortion once a wrap. Small cells shred the glyphs and large ones move the whole block as a unit; the interesting band is where a cell is roughly the height of a line of text.
chromanumber0.25Strength of the colour cast the shimmer carries, gathered where the pointer is. Hot air refracts, so a little colour separation is the cue that sells it as air rather than as a wobbling image.
trigger"always" | "hover""always"What turns the effect on. Whichever you pick it eases rather than switches — a displacement that snaps on reads as the element being swapped out, one that rises reads as air beginning to move.
edgeHoldnumber0.35How much of the element's own silhouette is protected. Displacing evenly moves the border too, so a rounded card dissolves into an amoeba and the one edge a reader uses to locate it is the first thing destroyed. This tapers the push toward neutral near the boundary — at zero the outline shreds, and past about half the shimmer is confined to the middle of the panel.
tintColorstring (hex)"#a855f7"The colour the shimmer carries.

What It Demonstrates

Hover effect
Visual change when the cursor moves over an element.
Blur
A blur filter used to soften an element or mask tiny imperfections.
Mask
Hiding or revealing parts of an element using a shape or gradient — like clip-path, but with soft, fadeable edges.
Idle animation
Subtle motion that plays while an element is just sitting there, waiting to be interacted with.
Compositing
Letting the GPU move or fade an element on its own layer without redoing layout or paint.
Hardware acceleration
Animating transform and opacity lets the GPU keep motion smooth.

Related Components

Appears In