npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@meltemi/ocean

v0.1.20

Published

The Meltemi family's sea in motion: a field of points in waves, the Etesian wind and the sailing mark, drawn with WebGL2 or Canvas 2D, with no dependency.

Readme

@meltemi/ocean

The Meltemi family's sea in motion: a field of points rising and falling in waves, a fleet of Meltemi marks sailing on them, and the Etesian wind blowing across the page the way the mark's gust points. It draws with WebGL2 and Canvas 2D, falls back to Canvas 2D, and gives still pictures for pages where nothing moves.

This README is the contract for every product that wears the sea (meltemi.dev, the Diagrams playground). The package is specified by the ocean-package capability of Meltemi Web (changes site-in-motion and fleet-and-wind, decision 0006).

What it never does

  • No network. No fetch, XMLHttpRequest, WebSocket or sendBeacon; a test scans the sources for them.
  • No storage. No cookie, localStorage, sessionStorage or indexedDB.
  • No telemetry. It counts frames for its own adaptive quality and reports nothing anywhere.
  • No dependency. It is plain TypeScript built to ES2022 modules, Apache-2.0. The noise the wind bends with is the public-domain simplex algorithm, written here.
  • No change to the mark. Given the fleet's elements, it only writes their CSS transform.

The states

| State | What it draws | | ---------- | ---------------------------------------------------------------------------------- | | home | The crossing: a fleet of six marks sailing to the right, the lead nearest. | | ide | One heading, many sails: a fleet of nine. | | diagrams | The wind draws: a fleet of five, and nine points that link into a three-node flow. | | still | A calm sea with one boat, drawn once. |

STATES lists them. An unknown state is an error that names the option and the value. fleetSize(state) is how many boats a state sails, the lead included: a page carries that many elements. restProgress(state) is the progress where a state tells its story without motion: the still pictures are drawn there, and a page that does not move draws its one frame there.

Drawing a sea

import { createSea } from "@meltemi/ocean";

const sea = createSea(canvas, {
  state: "home", // one of STATES
  reducedMotion: matchMedia("(prefers-reduced-motion: reduce)").matches,
  progress: 0, // scroll progress through the hero, 0 to 1: the Diagrams flow forms with it
  wind: 0.6, // wind strength, 0 to 1: the fleet sails faster in a fresher wind
  fleet: [leadElement, ...boatElements], // optional: placed on the sea by their transform only
});
sea.start();

createSea(canvas, options) returns a Sea handle:

| Member | Meaning | | ------------------ | ---------------------------------------------------------------------------------- | | renderer | "webgl2", "2d", or "none" when the canvas has neither context. | | state | The state it draws. | | frames | How many frames it has drawn. | | figures | What the last frame drew: boats, the lead's leadX, every boat's xs, links. | | progress, wind | The values it draws with. | | quality | "high", or "low" after slow frames halved the lattice. | | start() | Starts the loop; a reduced or still sea draws one frame and no loop. | | stop() | Stops the loop; the last frame stays. | | setProgress(p) | Takes a scroll progress between 0 and 1; anything else throws, naming it. | | setWind(s) | Takes a wind strength between 0 and 1. | | restyle() | Re-reads the --sea-* colours; the next frame uses them. | | resize() | Re-reads the canvas's size. | | destroy() | Stops and releases the context's programs and buffers. |

The loop never requests an animation frame under reduced motion, while the document is hidden or while the canvas is out of view. It caps the device pixel ratio at 2 (1.5 on a coarse pointer), and halves the lattice when the 95th percentile of the first 90 frames is slower than 20 ms.

The fleet

Every boat is the Meltemi mark. Give the lead as an element that holds the brand's two paths, and every other boat as an element that uses them (<use href="#…">), so the brand's shape exists once in the page. The package writes each element's transform and nothing else: a translation that puts its keel on the wave at its depth, a scale from the depth (the lead at 1, the farthest at about 0.35) and a heel of at most MAX_TILT (6) degrees; gusts make every boat surge and ease on its way. Give the elements transform-origin: 50% 86% so they pivot on the keel. The fleet sails to the right on its own; a boat that leaves the right edge re-enters from the left, behind every other, at a new depth. placeMark(element, placement) is the same placement, exported for pages that position boats themselves. The mark's own shape is exported as MARK_M, MARK_HULL, MARK_STROKE, MARK_BOX and MARK_GRADIENT, equal to the IDE's brand file.

The wind

import { createWind } from "@meltemi/ocean";

const wind = createWind(windCanvas, {
  reducedMotion: matchMedia("(prefers-reduced-motion: reduce)").matches,
  strength: 0.6, // 0 to 1
});
wind.start();

createWind(canvas, options) returns a Wind handle. The wind draws on a canvas that covers whatever surface you give it, with Canvas 2D at one device pixel per CSS pixel: particles carried by a smooth flow, the gust bent by noise of position and slow time, each drawing a short streak that appears, travels a range, fades and starts again elsewhere, the way wind maps draw wind. The canvas is cleared every frame, so nothing piles up however long the page stays open. Put the canvas behind the page (position: fixed; inset: 0; pointer-events: none) and the wind blows across every section. The handle: renderer ("2d" or "none"), frames, strength, particles, start(), stop(), setStrength(s) (0 to 1, else it throws), restyle() (re-reads --sea-wind), resize() and destroy(). Under reduced motion it draws one frame of static streamlines and requests no animation frame; while the document is hidden it requests none.

Colours

The sea reads four CSS custom properties from the canvas's computed style. Define them from your own tokens, for each of your schemes:

| Property | Paints | Brand fallback | | ------------- | --------------------------------------------------- | -------------- | | --sea-point | the sea's points, in troughs | #2563eb | | --sea-crest | the points on the crests | #22d3ee | | --sea-wind | the wind's trails and the Diagrams links | #22d3ee | | --sea-bg | read for completeness; the canvas stays transparent | transparent |

Call restyle() on the sea and the wind when your page changes scheme. A page that follows prefers-color-scheme calls them on the media query's change, and a page with its own switch calls them from the switch.

The sea as a function

heightAt(x, z, t, state) and slopeAt(x, z, t, state) are the sea's height and its slope along x, as pure functions of position, time and state. declaredAmplitude(state) bounds the height. The renderers, the fleet's placement and the still pictures all use them.

Still pictures

stillPicture(state, { scheme, colours }) returns the state at rest as an SVG string of at most 30 KB, for scheme: "light" or "dark", in the point, crest and wind colours given (the brand's when missing, BRAND_STILL), with the state's fleet (the mark defined once, used once per boat) and a few streamlines of the wind. Its colours are attributes: it has no <style>, no script and no reference to another file, so no page policy blocks it. Serve one file per scheme and let the page choose:

<picture>
  <source srcset="/assets/sea-home-dark.svg" media="(prefers-color-scheme: dark)" />
  <img src="/assets/sea-home-light.svg" alt="" />
</picture>

Publishing

A tag ocean-v<x.y.z> naming this package's version publishes it to npm with provenance from Meltemi Web's release workflow, skipping a version npm already has.