npm package: @phaen/cyma
Description: Lightweight SVG animation library for decorative backgrounds and section dividers
Author: Pablo Kebees
Latest version: 0.1.1

# Cyma

Smooth, configurable SVG animations for decorative backgrounds and section dividers.

**~3KB gzipped. Zero dependencies. TypeScript-first.**

## Features

- **Anchor to any edge** — fill from the top, bottom, left, or right of the container
- **Fully configurable** — speed, colors, wave components, segment resolution, and more
- **Plays well with CSS** — uses `currentColor` by default so it inherits your theme
- **Framework-agnostic** — vanilla DOM; works with React, Vue, Svelte, or plain HTML

## Install

```bash
npm install cyma
```

Or use a CDN — no build step required:

```html
<script type="module">
  import { wave } from "https://esm.sh/cyma";
  wave(document.getElementById("hero"));
</script>
```

## Quick start

```html
<div id="hero"></div>
```

```ts
import { wave } from "cyma";

const animation = wave(document.getElementById("hero")!);

// animation.stop(), animation.start(), animation.destroy()
// See "Animation controls" below.
```

Requires a browser with SVG support. For SSR frameworks (Next.js, Nuxt), run Cyma only on the client side.

## Shared options

All animations accept these options:

| Option    | Type                | Default          | Description                                                                      |
| --------- | ------------------- | ---------------- | -------------------------------------------------------------------------------- |
| `speed`   | `number`            | `1`              | Global speed multiplier                                                          |
| `fill`    | `string`            | `'currentColor'` | SVG fill color (`'currentColor'` inherits from the parent's CSS `color`)         |
| `anchor`  | `'top' \| 'bottom' \| 'left' \| 'right'` | `'top'`          | Edge the filled region clings to                                                 |
| `viewBox` | `{ width, height }` | `1440 × 320`     | SVG coordinate space                                                             |
| `height`  | `string`            | —                | CSS height of the SVG element. If omitted, scales with the viewBox aspect ratio.* |

\* A fixed `height` that doesn't match the viewBox aspect ratio will stretch the animation non-uniformly. This is most noticeable with `bubbles`, where round shapes become elliptical.

## Animations

### `wave(container, options?)`

A traveling wave built from superimposed sine components. The filled region extends from the anchor edge to the wave contour.

```ts
wave(document.getElementById("hero")!, {
  speed: 1.5,
  fill: "#3b82f6",
  anchor: "bottom",
});
```

| Option     | Type              | Default   | Description                                   |
| ---------- | ----------------- | --------- | --------------------------------------------- |
| `segments` | `number`          | `20`      | Curve resolution (higher = smoother, heavier) |
| `waves`    | `WaveComponent[]` | see below | Sinusoidal components to superimpose          |
| `taper`    | `number`          | `0`       | Fraction of the width (0–0.5) to smoothly curve down to zero at each edge. `0` = hard borders. |

The default `waves` preset produces a gentle, layered ocean-like motion from four components with varying amplitudes, frequencies, and directions.

#### `WaveComponent`

```ts
interface WaveComponent {
  amplitude: number; // Peak amplitude in viewBox units
  frequency: number; // Full cycles across the viewport
  phase?: number; // Initial phase offset in radians (default: 0)
  speed: number; // Angular velocity in rad/s (negative = travel left)
}
```

### `bubbles(container, options?)`

Bumps emerge from the anchor edge, morph into semicircles, then detach and float away as fading circles.

The default viewBox is `1440 × 160` (overriding the shared default).

```ts
bubbles(document.getElementById("footer")!, {
  fill: "#8b5cf6",
  anchor: "bottom",
  count: 6,
});
```

| Option                 | Type           | Default    | Description                                            |
| ---------------------- | -------------- | ---------- | ------------------------------------------------------ |
| `segments`             | `number`       | `12`       | Curve resolution per bump (higher = smoother, heavier) |
| `count`                | `number`       | `8`        | Number of bubbles                                      |
| `size`                 | `number \| { min, max }` | `35` | Bubble radius in viewBox units. A number sets the max (min defaults to 40% of max). |
| `fadeDuration`         | `number`       | `6`        | Maximum fade duration in seconds                       |
| `fadeDistribution`     | `Distribution` | `'flat'`   | How fade durations are spread across bubbles            |
| `distanceDistribution` | `Distribution` | `'linear'` | How travel distances are spread across bubbles          |
| `taper`                | `number`       | `0`        | Fraction of the width (0–0.5) to taper at each edge. Bump tails curve to zero in the taper zone; bubbles are offset so their semicircles stay outside it. |

`Distribution` is `'flat' | 'linear' | 'normal'`:

- **`flat`** — all bubbles get the same value
- **`linear`** — uniform spread (equal chance of any value)
- **`normal`** — bell curve (clusters toward the middle, tapers at extremes)

## Animation controls

Every animation function returns an `AnimationInstance`:

```ts
interface AnimationInstance {
  readonly svg: SVGSVGElement;
  stop(): void;
  start(resume?: boolean): void;
  destroy(): void;
}
```

- **`svg`** — Direct reference to the injected SVG element.
- **`stop()`** — Pauses the animation. The SVG remains in the DOM at its last frame.
- **`start(resume?)`** — Resumes the animation. Pass `true` to continue from the paused position. The default (`false`) catches up to the global timeline — the animation snaps to the same frame a never-paused instance would be showing.
- **`destroy()`** — Stops the animation and removes the SVG from the DOM.

## How it works

Shapes are rendered as cubic Bézier curves via Hermite interpolation, so contours stay smooth at any segment count. All timing derives from `Date.now()`, meaning separate instances stay in sync regardless of frame rate or tab throttling.

## TypeScript

Cyma is written in TypeScript and ships its own type declarations. All option interfaces and the `AnimationInstance` type are exported:

```ts
import type { WaveOptions, BubblesOptions, AnimationInstance } from "cyma";
```

## License

MIT