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

facette

v0.2.0

Published

Perceptual color palette generation via particle repulsion on convex hulls in OKLab space

Readme

Facette

Perceptual color palette generation. Give it a few seed colors and a target size, and it produces a palette where every color is visually distinct and belongs to the same chromatic family.

Zero dependencies. Works in Node, browsers, and edge runtimes.

Installation

npm install facette

Usage

import { generatePalette } from 'facette';

const result = generatePalette(
  ['#e63946', '#457b9d', '#1d3557'],  // seed colors
  8                                     // palette size
);

console.log(result.colors);
// ['#e63946', '#457b9d', '#1d3557', '#7b2d3e', '#2e6a85', ...]

Options

const result = generatePalette(seeds, 8, {
  vividness: 2,    // 0–4, default 2. Controls adaptive chroma preservation.
  spread: 1.5,     // 1–5, default 1.5. Lightness range expansion.
});
  • vividness — controls how strongly the algorithm preserves chroma on intermediate colors between seeds at wide hue separations. The algorithm computes γ adaptively from seed hue configuration: γ = 1 + vividness × Δh_max / π. At 0, no adaptive chroma preservation (γ = 1 always). At 2 (default), moderate adaptation. Higher values produce more aggressive preservation.
  • spread — controls how much the palette's lightness range extends beyond the seeds. At 1 (no stretching), colors stay within the seed lightness range. At 1.5 (default), a 50% expansion. At 2, the lightness range is doubled.

Stepper API

For inspecting the optimization process frame by frame:

import { createPaletteStepper } from 'facette';

const stepper = createPaletteStepper(['#e63946', '#457b9d', '#1d3557'], 8);

// Step through the optimization frame by frame
for (const frame of stepper.frames()) {
  console.log(`Iteration ${frame.iteration}: energy=${frame.energy.toFixed(4)}, minDeltaE=${frame.minDeltaE.toFixed(4)}`);
}

// Or get everything at once
const trace = stepper.run();
console.log(trace.finalColors);       // hex strings
console.log(trace.frames.length);     // number of iterations
console.log(trace.geometry.kind);     // 'line' or 'hull'

API Reference

generatePalette(seeds, size, options?)

| Parameter | Type | Description | |-----------|------|-------------| | seeds | string[] | Hex colors (e.g. ['#ff0000', '#0000ff']). Minimum 2, must be distinct. | | size | number | Total palette size including seeds. Must be >= seed count. | | options.vividness | number | Adaptive gamma coefficient. Default 2. Range [0, 4]. | | options.spread | number | Lightness range expansion. Default 1.5. Range [1, 5]. |

Returns PaletteResult:

{
  colors: string[];       // hex sRGB colors
  seeds: string[];        // input seeds echoed back
  metadata: {
    minDeltaE: number;    // minimum pairwise perceptual distance
    iterations: number;   // optimization steps taken
    clippedCount: number; // colors that needed gamut clipping
  };
}

createPaletteStepper(seeds, size, options?)

Same parameters as generatePalette. Returns a PaletteStepper:

{
  geometry: Geometry;                      // hull or line segment topology
  seeds: Particle[];                       // classified seed particles
  frames(): Generator<OptimizationFrame>;  // iterate frame by frame
  run(): OptimizationTrace;                // run to completion
}

How it works

Facette treats palette generation as a physics simulation in a radially lifted OKLab color space:

  1. L-stretch + Radial lift — seed lightness values are expanded around their median (controlled by spread), then a convex radial chroma lift contracts the low-chroma region and anchors vivid seeds. γ adapts automatically to seed hue spread.
  2. Convex hull — the hull of lifted seeds defines the palette's chromatic family
  3. Particle repulsion — free particles on the hull surface repel each other via Riesz energy, with the distance metric transitioning from lifted-space to gamut-clipped OKLab distances for gamut-aware separation
  4. Inverse lift — final positions are mapped back to OKLab and clipped to sRGB

The algorithm handles everything automatically: 2 seeds produce a gradient, 3+ seeds define a surface, and the convex hull geometry adapts to any configuration.

License

MIT