Package Exports
- @basiclines/rampa-sdk
- @basiclines/rampa-sdk/dist/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@basiclines/rampa-sdk) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
Rampa SDK
Programmatic JavaScript/TypeScript SDK for generating mathematically accurate, accessible color palettes. Same engine as the Rampa CLI, designed for use in applications, build tools, and design systems.
Installation
npm install @basiclines/rampa-sdkVanilla JS (CDN / Script Tag)
A pre-built browser bundle is available on every GitHub Release. No bundler or build step needed — just drop a <script> tag:
<script src="https://github.com/basiclines/rampa-studio/releases/latest/download/rampa-sdk.min.js"></script>
<script>
// Everything is available under the global `Rampa` object
const palette = Rampa.rampa('#3b82f6').size(5);
console.log('' + palette(1)); // first color
console.log(palette.palette); // all colors
const css = Rampa.rampa('#3b82f6').output('css');
const mixed = Rampa.rampa.mix('#ff0000', '#0000ff', 0.5);
const linear = new Rampa.LinearColorSpace('#ffffff', '#000000').size(10);
console.log('' + linear(5)); // midpoint gray
const c = Rampa.color('#3b82f6');
console.log(c.hex, c.rgb, c.luminance);
</script>Pin a specific version by replacing latest with the tag name:
https://github.com/basiclines/rampa-studio/releases/download/v3.0.0/rampa-sdk.min.jsQuick Start
import { rampa } from '@basiclines/rampa-sdk';
// Generate a 10-color palette from blue
const palette = rampa('#3b82f6');
palette(1) // first color (ColorAccessor)
palette(5).oklch() // format conversion
// Custom size with lightness range
const custom = rampa('#3b82f6').size(5).lightness(10, 90);
custom(3) // third color
// Add complementary harmony
const harmonies = rampa('#3b82f6').add('complementary');
harmonies.ramps // [base, complementary]
// Output as CSS variables
const css = rampa('#3b82f6').output('css');API
rampa(baseColor)
Creates a callable color palette. Accepts any valid CSS color (hex, hsl, rgb, oklch, named colors). All methods are chainable in any order, and the result is always callable with a 1-based index.
const palette = rampa('#3b82f6');
const palette = rampa('rgb(59, 130, 246)');
const palette = rampa('hsl(217, 91%, 60%)');
const palette = rampa('oklch(62.3% 0.214 259)');
palette(1) // first color
palette(5).hsl() // format conversion
palette.palette // string[] of all colorsBuilder Methods
All builder methods return the same callable for chaining in any order.
.size(steps)
Number of colors in the palette (2-100, default: 10).
rampa('#3b82f6').size(5);.format(type)
Color format for output values: hex, hsl, rgb, oklch (default: hex).
rampa('#3b82f6').format('hsl');
// palette(1) → 'hsl(0, 0%, 0%)', palette(2) → 'hsl(212, 69%, 25%)', ....lightness(start, end)
Lightness range 0-100 (default: 0, 100).
rampa('#3b82f6').lightness(10, 90);.saturation(start, end)
Saturation range 0-100 (default: 100, 0).
rampa('#3b82f6').saturation(80, 20);.hue(start, end)
Hue shift in degrees (default: -10, 10).
rampa('#3b82f6').hue(-30, 30);.lightnessDistribution(type), .saturationDistribution(type), .hueDistribution(type)
Distribution curve for each channel (default: linear).
Available distributions: linear, geometric, fibonacci, golden-ratio, logarithmic, powers-of-2, musical-ratio, cielab-uniform, ease-in, ease-out, ease-in-out
rampa('#3b82f6')
.lightnessDistribution('fibonacci')
.saturationDistribution('ease-out')
.hueDistribution('golden-ratio');.tint(color, opacity, blend?)
Apply a tint color over the palette.
color— Any valid CSS coloropacity— Tint strength 0-100blend— Blend mode (default:normal)
Available blend modes: normal, multiply, screen, overlay, darken, lighten, color-dodge, color-burn, hard-light, soft-light, difference, exclusion, hue, saturation, color, luminosity, plus-darker, plus-lighter
rampa('#3b82f6')
.tint('#FF0000', 15, 'overlay');.add(type) / .add('shift', degrees)
Add a harmony ramp. Can be called multiple times.
Available harmony types:
complementary— Opposite on color wheel (+180°)triadic— 3 colors, 120° apartanalogous— Adjacent colors, 30° apartsplit-complementary— 2 colors near oppositesquare— 4 colors, 90° apartcompound— Complementary + splitshift— Custom hue rotation by N degrees
rampa('#3b82f6')
.add('complementary')
.add('triadic')
.add('shift', 45);Accessing Colors
Call the palette with a 1-based index to get a ColorAccessor:
const palette = rampa('#3b82f6').size(5).lightness(10, 90);
palette(1) // ColorAccessor — first color
palette(3).hsl() // format conversion
palette(5).oklch() // any format
`${palette(1)}` // works in template literals
palette.palette // string[] of all colors
palette.ramps // RampResult[] (base + harmonies)Output Methods
.output(format, prefix?)
Export the palette as 'css', 'json', or 'text'. Optional prefix sets variable/ramp names (default: 'base').
// CSS custom properties
rampa('#3b82f6').size(5).output('css', 'primary');
// :root {
// /* primary */
// --primary-0: #000000;
// --primary-1: #143d6b;
// --primary-2: #4572ba;
// --primary-3: #b1b9ce;
// --primary-4: #ffffff;
// }
// JSON
rampa('#3b82f6').size(5).output('json', 'primary');
// {
// "ramps": [
// {
// "name": "primary",
// "baseColor": "#3b82f6",
// "colors": ["#000000", "#143d6b", "#4572ba", "#b1b9ce", "#ffffff"]
// }
// ]
// }
// Plain text (one color per line)
rampa('#3b82f6').size(5).output('text');
// #000000
// #143d6b
// #4572ba
// #b1b9ce
// #ffffff.ramps / .palette
Access the generated ramps and colors directly as properties:
const result = rampa('#3b82f6').size(5).add('complementary');
result.palette // base ramp colors: ['#000000', '#143d6b', ...]
result.ramps // all ramps: [{ name: 'base', ... }, { name: 'complementary', ... }]
result.ramps[1].colors // complementary ramp colorscolor(input)
Inspect, transform, mix, and export a single color. Equivalent to rampa color in the CLI.
All property values use 0-1 normalized ranges to match CSS spec conventions:
| Property | Range |
|---|---|
hsl.h |
0–360 (degrees) |
hsl.s, hsl.l |
0–1 |
oklch.l |
0–1 |
oklch.c |
0–0.4 (native OKLCH scale) |
oklch.h |
0–360 (degrees) |
import { color } from '@basiclines/rampa-sdk';
const c = color('#fe0000');
c.hex // '#fe0000'
c.rgb // { r: 254, g: 0, b: 0 }
c.hsl // { h: 0, s: 1.0, l: 0.5 }
c.oklch // { l: 0.628, c: 0.258, h: 29 }
c.luminance // 0.628
// Format conversion (string output uses CSS conventions: percentages)
c.format('hsl') // 'hsl(0, 100%, 50%)'
c.format('rgb') // 'rgb(254, 0, 0)'
c.format('oklch') // 'oklch(62.8% 0.258 29)'
// Export
c.output('json') // JSON with all formats
c.output('css', 'brand') // CSS custom properties with prefix
c.output('text') // plain hex string
// String coercion
`${c}` // '#fe0000'Color Transforms
All transforms operate in OKLCH space and return a new immutable Color. Chain freely.
// Absolute deltas — clamped to valid range
c.lighten(0.1) // L += 0.1 (OKLCH lightness, 0-1)
c.darken(0.1) // L -= 0.1
c.saturate(0.05) // C += 0.05 (OKLCH chroma)
c.desaturate(0.05) // C -= 0.05
c.rotate(30) // H += 30° (hue rotation)
// All accept negative values: lighten(-0.1) === darken(0.1)
// Set absolute OKLCH values
c.set({ lightness: 0.48 }) // set L
c.set({ chroma: 0.15, hue: 200 }) // batch set
// Chain transforms
color('#66b172').lighten(0.1).desaturate(0.05).hex
// → bright desaturated green, no throwaway convertersMix (color space interpolation)
Like CSS color-mix() — interpolates between two colors in a chosen color space.
c.mix('#0000ff', 0.5) // 50% mix in oklch (default)
c.mix('#0000ff', 0.18, 'lab') // explicit color space
c.mix('#0000ff', 0.18, 'srgb') // sRGB interpolationBlend (compositing modes)
Like Photoshop/CSS blend modes — composites two colors with an opacity and a blend mode.
c.blend('#0000ff', 0.15, 'multiply')
c.blend('#0000ff', 0.15, 'screen')
c.blend('#0000ff', 0.15, 'overlay')Available blend modes: normal, multiply, screen, overlay, darken, lighten, color-dodge, color-burn, hard-light, soft-light, difference, exclusion, hue, saturation, color, luminosity, plus-darker, plus-lighter
rampa.convert(color, format)
Standalone utility to convert a color to a different format.
rampa.convert('#fe0000', 'hsl'); // 'hsl(0, 100%, 50%)'
rampa.convert('#fe0000', 'rgb'); // 'rgb(254, 0, 0)'
rampa.convert('#fe0000', 'oklch'); // 'oklch(62.8% 0.257 29)'
rampa.convert('#fe0000', 'hex'); // '#fe0000'rampa.mix(color1, color2, t)
Mix two colors in OKLCH space at a given ratio. Produces perceptually uniform transitions — hues travel the color wheel naturally, lightness steps look even, and chroma stays vivid instead of dipping through gray.
rampa.mix('#ff0000', '#0000ff', 0); // '#ff0302' (start color)
rampa.mix('#ff0000', '#0000ff', 0.5); // midpoint — vivid purple, not muddy gray
rampa.mix('#ff0000', '#0000ff', 1); // '#0031e5' (end color)
rampa.mix('#000000', '#ffffff', 0.5); // perceptual mid-grayGenerate a gradient with multiple steps:
const steps = 10;
const gradient = Array.from({ length: steps }, (_, i) =>
rampa.mix('#ff0000', '#0000ff', i / (steps - 1))
);lint(foreground, background)
Evaluate contrast between two colors. Returns score, pass/fail levels, and lint warnings. Default mode is 'apca'.
import { lint } from '@basiclines/rampa-sdk';
// APCA (default) — returns Lc value
const result = lint('#ffffff', '#1e1e2e');
result.score // -104.3 (Lc value)
result.pass // true (at least one level passes)
result.levels // [{ name: 'Preferred body text', threshold: 90, pass: true }, ...]
result.warnings // []
// WCAG 2.x — chain .mode('wcag')
const wcag = lint('#777', '#ffffff').mode('wcag');
wcag.score // 4.48 (contrast ratio)
wcag.levels // [{ name: 'AAA Normal text', threshold: 7, pass: false }, ...]
// Export
result.output('json') // JSON with full result
result.output('text') // readable text report
result.output('css') // CSS custom propertiesLint warnings fire automatically:
- Near-identical colors (deltaE < 3)
- Contrast below minimum usable threshold
- Pure
#000000or#ffffffdetected
Types
import type {
ColorFormat, // 'hex' | 'hsl' | 'rgb' | 'oklch'
ScaleType, // 'linear' | 'fibonacci' | 'golden-ratio' | ...
BlendMode, // 'normal' | 'multiply' | 'screen' | ...
HarmonyType, // 'complementary' | 'triadic' | 'analogous' | ...
RampResult, // { name, baseColor, colors }
RampaResult, // { ramps: RampResult[] }
RampaFn, // callable palette returned by rampa()
Color, // { hex, rgb, hsl, oklch, luminance, format(), output(), lighten(), ... }
ColorInfo, // { hex, rgb, hsl, oklch }
OklchSetValues, // { lightness?, chroma?, hue? } for color.set()
InterpolationMode, // 'oklch' | 'lab' | 'rgb' | 'srgb'
LinearColorSpaceFn, // callable function returned by LinearColorSpace.size()
CubeColorSpaceFn, // callable function returned by CubeColorSpace.size()
LintResult, // lint builder with score, pass, levels, warnings, output()
ContrastMode, // 'wcag' | 'apca'
ContrastLevelResult, // { name, threshold, pass }
ContrastResult, // { mode, score, pass, levels, warnings, foreground, background }
} from '@basiclines/rampa-sdk';Examples
Design System Palette
import { rampa } from '@basiclines/rampa-sdk';
const primary = rampa('#3b82f6')
.size(10)
.lightness(5, 95)
.saturationDistribution('ease-out');
const neutral = rampa('#64748b')
.size(10)
.saturation(20, 0);
const danger = rampa('#ef4444')
.size(10)
.lightness(10, 90);
// Access individual colors
primary(5) // mid-tone blue
neutral(1) // darkest gray
danger(8).hsl() // light red in HSLFull Palette with Harmonies
const palette = rampa('#3b82f6')
.size(10)
.lightness(5, 95)
.lightnessDistribution('ease-in-out')
.add('complementary')
.add('analogous');
palette(1) // first base color
palette.ramps[1] // complementary ramp
palette.ramps[2] // analogous rampCSS Variables for a Theme
const theme = rampa('#3b82f6')
.size(10)
.add('complementary')
.tint('#FFD700', 5, 'overlay')
.output('css');
// Inject into page
const style = document.createElement('style');
style.textContent = theme;
document.head.appendChild(style);Build Tool Integration
import { rampa } from '@basiclines/rampa-sdk';
import { writeFileSync } from 'fs';
const colors = rampa('#3b82f6')
.size(10)
.format('hsl');
// Write to a tokens file
writeFileSync('tokens.json', JSON.stringify({ colors: colors.palette }, null, 2));CLI Parity
The SDK produces identical output to the CLI. These are equivalent:
# CLI
rampa --color "#3b82f6" --size=5 --lightness 10:90 --add=complementary --output json// SDK
rampa('#3b82f6').size(5).lightness(10, 90).add('complementary').output('json');Color Spaces
Color spaces let you create structured palettes from a set of anchor colors and query them semantically. Three geometric primitives are available:
- LinearColorSpace — 1D interpolated ramp
- PlaneColorSpace — 2D saturation×lightness plane
- CubeColorSpace — 3D trilinear interpolation cube
LinearColorSpace
Interpolates between two colors to produce an evenly-spaced ramp.
import { LinearColorSpace } from '@basiclines/rampa-sdk';
const neutral = new LinearColorSpace('#ffffff', '#000000').size(24);
neutral(1) // → '#ffffff' (lightest, returns string directly)
neutral(12) // → '#666666' (mid gray)
neutral(12).hsl() // → 'hsl(0, 0%, 40%)'
neutral.palette // → string[24]Ramp introspection
Access individual steps as full Color objects with .at(), or get all steps with .colors():
const ramp = new LinearColorSpace('#000', '#fff').size(12);
ramp.at(3) // → Color (0-based index)
ramp.at(3).oklch.c // → chroma at step 3
ramp.at(3).lighten(0.1).hex // → transform a ramp step
ramp.colors() // → Color[]
ramp.colors().map(c => c.oklch.c) // → chroma curve across ramp.at() is 0-based (like Array.at()), while the callable ramp(n) remains 1-based for backward compatibility.
Lookup table mode
Set .interpolation(false) for a plain lookup table — no interpolation, just indexed access to the exact colors you provide:
const base = new LinearColorSpace(
'#45475a', '#f38ba8', '#a6e3a1', '#f9e2af',
'#89b4fa', '#f5c2e7', '#94e2d5', '#a6adc8'
).interpolation(false).size(8);
base(1) // → '#45475a' (exact, no interpolation)
base(3) // → '#a6e3a1'PlaneColorSpace
Creates a 2D color plane for a single hue, anchored to shared dark/light values. Create one plane per hue.
Y(lightness)
1 │ light ─────────── hue
│ │ │
│ │ interpolated │
│ │ │
0 │ dark ─────────── dark
└──────────────────────── X(saturation)
0 1At Y=0 the entire row converges to the dark anchor.
import { PlaneColorSpace } from '@basiclines/rampa-sdk';
// One plane per hue, reuse dark/light anchors
const red = new PlaneColorSpace('#1e1e2e', '#cdd6f4', '#f38ba8').size(6);
const blue = new PlaneColorSpace('#1e1e2e', '#cdd6f4', '#89b4fa').size(6);
red(3, 5) // → ColorAccessor (saturation=3, lightness=5)
red(0, 3) // → achromatic at lightness 3 (no hue influence)
red(5, 5) // → full hue color
red(0, 0) // → dark anchor
red.palette // → string[36] (6²)
// Ramp introspection
red.at(3, 5) // → Color at (saturation=3, lightness=5)
red.at(3, 5).oklch.c // → chroma at that coordinate
red.colors() // → Color[] (flat, row-major)
// Template literals and concatenation work directly
`background: ${red(3, 5)};` // → 'background: #ab34cd;'
// Format conversion
red(3, 5).hsl() // → 'hsl(280, 60%, 50%)'
red(3, 5).oklch() // → 'oklch(52.3% 0.198 310)'CubeColorSpace
Creates a 3D color cube from 8 corner colors via trilinear interpolation. The constructor keys become alias names for semantic lookups.
import { CubeColorSpace } from '@basiclines/rampa-sdk';
const { r, b, tint } = new CubeColorSpace({
k: '#1e1e2e', // origin (0,0,0)
r: '#f38ba8', // x axis
g: '#a6e3a1', // y axis
b: '#89b4fa', // z axis
y: '#f9e2af', // x+y
m: '#cba6f7', // x+z
c: '#94e2d5', // y+z
w: '#cdd6f4', // x+y+z
}).size(6);
r(4) // → strong red
tint({ r: 4, b: 2 }) // → red-blue blend
tint({ w: 3 }) // → mid-white (all axes at 3)
// Ramp introspection
cube.at(2, 3, 1) // → Color at (x=2, y=3, z=1)
cube.colors() // → Color[]Custom vocabulary
The alias names are whatever you put in the constructor — not limited to ANSI names:
const space = new CubeColorSpace({
dark: '#1a1a2e',
warm: '#e74c3c',
nature: '#2ecc71',
ocean: '#3498db',
sunset: '#f39c12',
berry: '#9b59b6',
mint: '#1abc9c',
light: '#ecf0f1',
}).size(6);
space({ warm: 4, ocean: 2 }) // → warm-ocean blendHow aliases map to the cube
The 8 keys map to cube corners by insertion order:
| Position | Key # | Cube coords | Axis mask |
|---|---|---|---|
| 1st | origin | (0,0,0) | — |
| 2nd | x | (1,0,0) | x |
| 3rd | y | (0,1,0) | y |
| 4th | z | (0,0,1) | z |
| 5th | xy | (1,1,0) | x+y |
| 6th | xz | (1,0,1) | x+z |
| 7th | yz | (0,1,1) | y+z |
| 8th | xyz | (1,1,1) | x+y+z |
When you call tint({ r: 4, b: 2 }), each alias's mask is multiplied by its intensity and merged with Math.max to produce cube coordinates.
Interpolation modes
LinearColorSpace, PlaneColorSpace, and CubeColorSpace all support configurable interpolation:
| Mode | Description |
|---|---|
'oklch' (default) |
Perceptually uniform — hues travel the color wheel, even lightness steps |
'lab' |
CIE L*a*b* — perceptual but no hue rotation |
'rgb' |
Linear RGB — fast but perceptually uneven |
false |
No interpolation — plain lookup table (LinearColorSpace only) |
// LinearColorSpace
new LinearColorSpace('#ff0000', '#0000ff').interpolation('oklch').size(10)
new LinearColorSpace('#ff0000', '#0000ff').interpolation('lab').size(10)
new LinearColorSpace('#f00', '#0f0', '#00f').interpolation(false).size(3)
// PlaneColorSpace
new PlaneColorSpace('#000', '#fff', '#f00').interpolation('oklch').size(6)
new PlaneColorSpace('#000', '#fff', '#f00').interpolation('lab').size(6)
// CubeColorSpace
new CubeColorSpace({ ... }).interpolation('lab').size(6)Distribution curves
All color space classes support .distribution(scale) to apply non-linear step spacing. When not set, steps are evenly spaced (zero overhead on the default path).
Available scales: ease-in, ease-out, ease-in-out, fibonacci, golden-ratio, geometric, logarithmic, powers-of-2, musical-ratio, cielab-uniform
// Ease-in: bunch colors toward the start, spread out toward the end
const ramp = new LinearColorSpace('#ffffff', '#000000')
.distribution('ease-in')
.size(10);
// Fibonacci spacing on a 2D plane
const red = new PlaneColorSpace('#1e1e2e', '#cdd6f4', '#f38ba8')
.distribution('fibonacci')
.size(8);
// Golden ratio on a cube
const space = new CubeColorSpace({ ... })
.distribution('golden-ratio')
.size(6);Color accessor
All color space lookups return a ColorAccessor — a string that works directly in template literals and concatenation, with conversion methods:
const c = red(3, 5);
`${c}` // → '#ab34cd' (string coercion)
'bg: ' + c // → 'bg: #ab34cd' (concatenation)
c.hex() // → '#ab34cd'
c.hsl() // → 'hsl(280, 60%, 50%)'
c.rgb() // → 'rgb(171, 52, 205)'
c.oklch() // → 'oklch(52.3% 0.198 310)'
c.luminance // → 0.52 (perceptual, 0–1)Use .format() on the color space to change the default output format:
const rgbSpace = new PlaneColorSpace('#000', '#fff', '#f00').format('rgb').size(6);
`${rgbSpace(3, 5)}` // → 'rgb(255, 128, 128)'
rgbSpace(3, 5).hex() // → '#ff8080' (still available)Image Palette Extraction
Extract color palettes from images with three tiers of analysis: raw unique colors, dominant color clusters, and ANSI-classified palettes.
Requires fast-png and jpeg-js (installed as dependencies). Supports PNG and JPEG files.
palette(filePath)
import { palette } from '@basiclines/rampa-sdk';
const p = await palette('photo.jpg');Dominant Colors
Top N color groups via k-means clustering in OKLCH space:
p.dominant() // → PaletteEntry[] (top 10)
p.dominant({ count: 5 }) // top 5
p.dominant({ tolerance: 10 }) // wider clustering (default: 4)
// Shortcut with .at() (0-based, like ramp.at())
p.at(0) // most dominant color
p.at(0).color.hex // → '#2d2a18'
p.at(0).frequency // → 0.278 (27.8% of pixels)
p.at(0).color.lighten(0.1).hex // chain transformsRaw Palette
All unique colors with near-duplicates merged:
p.raw() // → PaletteEntry[]
p.raw({ tolerance: 3 }) // deltaE merge threshold (default: 2)
p.raw({ maxColors: 500 }) // cap output (default: 1000)ANSI Palette
Colors classified into terminal color categories:
p.ansi()
// → { red: PaletteEntry[], green: [...], blue: [...], ... }
p.ansi({ count: 3 }) // max 3 per category (default: 5)Categories: black, red, green, yellow, blue, magenta, cyan, white
Group by OKLCH Property
Group colors into buckets by lightness, chroma, or hue:
p.group({ by: 'L' }) // → { darkest, dark, mid, light, lightest }
p.group({ by: 'C' }) // → { gray, muted, saturated, vivid }
p.group({ by: 'H' }) // → { red, orange, yellow, green, cyan, blue, purple, pink }
p.group({ by: 'L', count: 3 }) // max 3 colors per bucket
p.group({ by: 'L', tolerance: 10 }) // wider dedup within bucketsCustom bucket boundaries:
p.group({
by: 'L',
buckets: [
{ name: 'dark', min: 0, max: 0.3 },
{ name: 'mid', min: 0.3, max: 0.7 },
{ name: 'light', min: 0.7, max: 1.0 },
],
})Sorting with .sortBy()
All array and grouped results support .sortBy() for reordering:
// Sort dominant colors by lightness (dark → light)
p.dominant({ count: 5 }).sortBy('L')
// Sort within each group bucket
p.group({ by: 'C' }).sortBy('L') // chroma groups, each sorted dark→light
p.ansi().sortBy('L') // ANSI categories, each sorted dark→light
// Sort fields: 'L' (lightness), 'C' (chroma), 'H' (hue), 'frequency'
p.raw().sortBy('H') // all colors sorted by hue angle
// Chainable — returns a new sorted result
p.dominant().sortBy('L').sortBy('C')Extras
p.average() // → Color (weighted OKLCH average)
p.temperature() // → 'warm' | 'cool' | 'neutral'Output
p.output('json') // full analysis as JSON
p.output('css', 'photo') // CSS custom properties
p.output('text') // readable textPaletteEntry Shape
interface PaletteEntry {
color: Color; // full Color with transforms
frequency: number; // 0-1, percentage of sampled pixels
}Development
cd sdk
bun install
# Run tests
bun test
# Build
bun run build