JSPM

facette

0.1.1
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 17
  • Score
    100M100P100Q62799F
  • License MIT

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

Package Exports

  • facette

Readme

Facette

Facette — Perceptual color palette generation

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.

Example palettes generated by Facette

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: 0.06,  // 0 = auto (default), range [0.005, 0.10]
  gamma: 1.5,       // chroma preservation strength, >= 1 (default: 1)
});
  • vividness — controls how aggressively the algorithm avoids low-chroma colors. Higher values push the palette toward more saturated colors. At 0 (default), it adapts automatically based on how vivid your seeds are.
  • gamma — controls chroma preservation on intermediate colors between vivid seeds. At 1 (default), the algorithm uses standard gray avoidance. Values above 1 (e.g. 1.5-2) produce stronger outward bowing of the hull surface in OKLab, keeping midpoint colors more vivid.

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 Gray avoidance strength. 0 = auto. Range [0.005, 0.10].
options.gamma number Chroma preservation strength. Default 1. Must be >= 1.

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

Algorithm pipeline: Seeds → Lift → Hull → Optimize → Inverse Lift → Palette

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

  1. Radial lift — a convex transform contracts the low-chroma region, making gray positions scarce, while anchoring vivid seeds as fixed points
  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 until they reach maximum 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