# Shape Waves

> A field of triangles, circles and squares that brighten and grow along rolling waves, with optional text carved out of the grid.

A WebGL2 grid of small shapes that morph between triangles, circles and squares as noise waves roll through it. The shapes pop in from the centre on mount, text can be cut out of the field, and moving the cursor drops splashes that ripple outward, cycle the shapes and light them up with a soft glow.

<demo-shape-waves>



</demo-shape-waves>

## Installation

```bash
npx shadcn-vue@latest add "https://nxui.geoql.in/r/shape-waves.json"
```

## Usage

```vue
<script setup lang="ts">
  import ShapeWaves from '~/components/ui/shape-waves/ShapeWaves.vue';
</script>

<template>
  <div class="relative h-125 w-full overflow-hidden">
    <ShapeWaves
      text="</>"
      shapes="mixed"
      color="#929292"
      hover-color="#ffffff"
      background-color="#120f17"
      :cell-size="10"
      :glow="0.35"
      class="absolute inset-0"
    />
  </div>
</template>
```

## Props

<table>
<thead>
  <tr>
    <th>
      Prop
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Default
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        text
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Optional text carved out of the field. Cells under the glyphs stay empty and the waves flow around them.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        font-family
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'Geist, "Geist Sans", system-ui, sans-serif'
      </code>
    </td>
    
    <td>
      CSS font family used to draw the text cutout.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        font-weight
      </code>
    </td>
    
    <td>
      <code>
        string | number
      </code>
    </td>
    
    <td>
      <code>
        500
      </code>
    </td>
    
    <td>
      Font weight of the text cutout.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        text-size
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.6
      </code>
    </td>
    
    <td>
      Text height as a fraction of the container height. Long text shrinks to fit the width.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        shapes
      </code>
    </td>
    
    <td>
      <code>
        'mixed' | 'squares' | 'circles' | 'triangles'
      </code>
    </td>
    
    <td>
      <code>
        'mixed'
      </code>
    </td>
    
    <td>
      <code>
        'mixed'
      </code>
      
       draws dark bands as triangles, mid bands as circles and bright bands as squares. A single shape varies its size per band instead.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        cell-size
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        10
      </code>
    </td>
    
    <td>
      Size of one grid cell in CSS pixels.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        dot-size
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.75
      </code>
    </td>
    
    <td>
      Glyph size as a fraction of the cell.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        '#929292'
      </code>
    </td>
    
    <td>
      Glyph color. One flat color for the whole field.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        hover-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        '#ffffff'
      </code>
    </td>
    
    <td>
      Color the glyphs step towards inside a splash.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        background-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        '#000000'
      </code>
    </td>
    
    <td>
      Background color behind the glyphs.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        speed
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      How fast the wave bands morph over time. 0 freezes them.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        scale
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      Size of the wave bands. Larger values give broader, calmer swells.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        contrast
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      How sharply the field is cut into its three bands.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        brightness
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.4
      </code>
    </td>
    
    <td>
      Shifts the balance between dark, mid and bright bands.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        flow
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      Drift speed of the field in cells per second. 0 evolves in place.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        direction
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      Drift direction in degrees, used when <code>
        flow
      </code>
      
       is above 0.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        fade
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.25
      </code>
    </td>
    
    <td>
      Strength of the radial fade into the background towards the edges. 0 disables it.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        interactive
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Lets the cursor splash into the field, sending ripples that cycle the glyphs and light them up.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        splash-radius
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        40
      </code>
    </td>
    
    <td>
      Size of a cursor splash in CSS pixels.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        splash-strength
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.4
      </code>
    </td>
    
    <td>
      How much energy a splash drops in. Higher values throw wider, brighter ripples.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        glow
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.35
      </code>
    </td>
    
    <td>
      Bloom on lit glyphs. 0 disables the glow pass entirely.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        intro
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Plays a spawn animation on mount: the shapes pop in along a wave that travels from the centre to the corners.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        intro-duration
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1.6
      </code>
    </td>
    
    <td>
      Length of the intro wave in seconds.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        intro-key
      </code>
    </td>
    
    <td>
      <code>
        string | number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      Change this value to replay the intro wave without remounting.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        paused
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Freezes the wave motion. Splashes keep working.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        class
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Extra classes for the root element.
    </td>
  </tr>
</tbody>
</table>

## Events

<table>
<thead>
  <tr>
    <th>
      Event
    </th>
    
    <th>
      Payload
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        error
      </code>
    </td>
    
    <td>
      <code>
        Error
      </code>
    </td>
    
    <td>
      Emitted when WebGL2 is unavailable or rendering fails. The background color still shows.
    </td>
  </tr>
</tbody>
</table>

## Accessibility

The effect is decorative: the root is `aria-hidden` and ignores pointer events, so it never blocks content placed on top. Cursor splashes are read from window pointer moves. With `prefers-reduced-motion: reduce` the waves hold still and the intro is skipped, while splashes still respond. The render loop sleeps when the field is offscreen, the tab is hidden, or nothing is moving.

- Ported from [React Bits](https://reactbits.dev/backgrounds/shape-waves) by David Haz.
