@basiclines/rampa-sdk 1.4.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +161 -7
- package/dist/builder.d.ts +38 -0
- package/dist/color-result.d.ts +6 -0
- package/dist/cube-color-space.d.ts +34 -0
- package/dist/formatters/css.d.ts +2 -0
- package/dist/formatters/json.d.ts +2 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +245 -22
- package/dist/linear-color-space.d.ts +36 -0
- package/dist/read-only.d.ts +8 -0
- package/dist/types.d.ts +86 -0
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -221,17 +221,41 @@ rampa.convert('#fe0000', 'oklch'); // 'oklch(62.8% 0.257 29)'
|
|
|
221
221
|
rampa.convert('#fe0000', 'hex'); // '#fe0000'
|
|
222
222
|
```
|
|
223
223
|
|
|
224
|
+
### `rampa.mix(color1, color2, t)`
|
|
225
|
+
|
|
226
|
+
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.
|
|
227
|
+
|
|
228
|
+
```js
|
|
229
|
+
rampa.mix('#ff0000', '#0000ff', 0); // '#ff0302' (start color)
|
|
230
|
+
rampa.mix('#ff0000', '#0000ff', 0.5); // midpoint — vivid purple, not muddy gray
|
|
231
|
+
rampa.mix('#ff0000', '#0000ff', 1); // '#0031e5' (end color)
|
|
232
|
+
rampa.mix('#000000', '#ffffff', 0.5); // perceptual mid-gray
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Generate a gradient with multiple steps:
|
|
236
|
+
|
|
237
|
+
```js
|
|
238
|
+
const steps = 10;
|
|
239
|
+
const gradient = Array.from({ length: steps }, (_, i) =>
|
|
240
|
+
rampa.mix('#ff0000', '#0000ff', i / (steps - 1))
|
|
241
|
+
);
|
|
242
|
+
```
|
|
243
|
+
|
|
224
244
|
## Types
|
|
225
245
|
|
|
226
246
|
```typescript
|
|
227
247
|
import type {
|
|
228
|
-
ColorFormat,
|
|
229
|
-
ScaleType,
|
|
230
|
-
BlendMode,
|
|
231
|
-
HarmonyType,
|
|
232
|
-
RampResult,
|
|
233
|
-
RampaResult,
|
|
234
|
-
ColorInfo,
|
|
248
|
+
ColorFormat, // 'hex' | 'hsl' | 'rgb' | 'oklch'
|
|
249
|
+
ScaleType, // 'linear' | 'fibonacci' | 'golden-ratio' | ...
|
|
250
|
+
BlendMode, // 'normal' | 'multiply' | 'screen' | ...
|
|
251
|
+
HarmonyType, // 'complementary' | 'triadic' | 'analogous' | ...
|
|
252
|
+
RampResult, // { name, baseColor, colors }
|
|
253
|
+
RampaResult, // { ramps: RampResult[] }
|
|
254
|
+
ColorInfo, // { hex, rgb, hsl, oklch } — returned by readOnly()
|
|
255
|
+
InterpolationMode, // 'oklch' | 'lab' | 'rgb'
|
|
256
|
+
ColorResult, // { hex, format(), toString() }
|
|
257
|
+
LinearColorSpaceFn, // callable function returned by LinearColorSpace.size()
|
|
258
|
+
CubeColorSpaceFn, // callable function returned by CubeColorSpace.size()
|
|
235
259
|
} from '@basiclines/rampa-sdk';
|
|
236
260
|
```
|
|
237
261
|
|
|
@@ -319,6 +343,136 @@ rampa --color "#3b82f6" --size=5 --lightness 10:90 --add=complementary --output
|
|
|
319
343
|
rampa('#3b82f6').size(5).lightness(10, 90).add('complementary').toJSON();
|
|
320
344
|
```
|
|
321
345
|
|
|
346
|
+
## Color Spaces
|
|
347
|
+
|
|
348
|
+
Color spaces let you create structured palettes from a set of anchor colors and query them semantically. Two primitives are available:
|
|
349
|
+
|
|
350
|
+
### `LinearColorSpace`
|
|
351
|
+
|
|
352
|
+
Interpolates between two colors to produce an evenly-spaced ramp.
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
import { LinearColorSpace } from '@basiclines/rampa-sdk';
|
|
356
|
+
|
|
357
|
+
const neutral = new LinearColorSpace('#ffffff', '#000000').size(24);
|
|
358
|
+
neutral(1) // → ColorResult (lightest)
|
|
359
|
+
neutral(12) // → ColorResult (mid gray)
|
|
360
|
+
neutral(24) // → ColorResult (darkest)
|
|
361
|
+
neutral(12).hex // → '#666666'
|
|
362
|
+
neutral(12).format('hsl') // → 'hsl(0, 0%, 40%)'
|
|
363
|
+
neutral.palette // → string[24]
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
#### Lookup table mode
|
|
367
|
+
|
|
368
|
+
Set `.interpolation(false)` for a plain lookup table — no interpolation, just indexed access to the exact colors you provide:
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
const base = new LinearColorSpace(
|
|
372
|
+
'#45475a', '#f38ba8', '#a6e3a1', '#f9e2af',
|
|
373
|
+
'#89b4fa', '#f5c2e7', '#94e2d5', '#a6adc8'
|
|
374
|
+
).interpolation(false).size(8);
|
|
375
|
+
|
|
376
|
+
base(1) // → '#45475a' (exact, no interpolation)
|
|
377
|
+
base(3) // → '#a6e3a1'
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### `CubeColorSpace`
|
|
381
|
+
|
|
382
|
+
Creates a 3D color cube from 8 corner colors via trilinear interpolation. The constructor keys become alias names for semantic lookups.
|
|
383
|
+
|
|
384
|
+
```typescript
|
|
385
|
+
import { CubeColorSpace } from '@basiclines/rampa-sdk';
|
|
386
|
+
|
|
387
|
+
const tint = new CubeColorSpace({
|
|
388
|
+
k: '#1e1e2e', // origin (0,0,0)
|
|
389
|
+
r: '#f38ba8', // x axis
|
|
390
|
+
g: '#a6e3a1', // y axis
|
|
391
|
+
b: '#89b4fa', // z axis
|
|
392
|
+
y: '#f9e2af', // x+y
|
|
393
|
+
m: '#cba6f7', // x+z
|
|
394
|
+
c: '#94e2d5', // y+z
|
|
395
|
+
w: '#cdd6f4', // x+y+z
|
|
396
|
+
}).size(6);
|
|
397
|
+
|
|
398
|
+
tint({ r: 4 }) // → strong red
|
|
399
|
+
tint({ r: 4, b: 2 }) // → red-blue blend
|
|
400
|
+
tint({ w: 3 }) // → mid-white (all axes at 3)
|
|
401
|
+
tint({ r: 5, w: 2 }) // → pastel red (white raises base)
|
|
402
|
+
tint.palette // → string[216] (6³)
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
#### Custom vocabulary
|
|
406
|
+
|
|
407
|
+
The alias names are whatever you put in the constructor — not limited to ANSI names:
|
|
408
|
+
|
|
409
|
+
```typescript
|
|
410
|
+
const space = new CubeColorSpace({
|
|
411
|
+
dark: '#1a1a2e',
|
|
412
|
+
warm: '#e74c3c',
|
|
413
|
+
nature: '#2ecc71',
|
|
414
|
+
ocean: '#3498db',
|
|
415
|
+
sunset: '#f39c12',
|
|
416
|
+
berry: '#9b59b6',
|
|
417
|
+
mint: '#1abc9c',
|
|
418
|
+
light: '#ecf0f1',
|
|
419
|
+
}).size(6);
|
|
420
|
+
|
|
421
|
+
space({ warm: 4, ocean: 2 }) // → warm-ocean blend
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
#### How aliases map to the cube
|
|
425
|
+
|
|
426
|
+
The 8 keys map to cube corners by insertion order:
|
|
427
|
+
|
|
428
|
+
| Position | Key # | Cube coords | Axis mask |
|
|
429
|
+
|----------|-------|-------------|-----------|
|
|
430
|
+
| 1st | origin | (0,0,0) | — |
|
|
431
|
+
| 2nd | x | (1,0,0) | x |
|
|
432
|
+
| 3rd | y | (0,1,0) | y |
|
|
433
|
+
| 4th | z | (0,0,1) | z |
|
|
434
|
+
| 5th | xy | (1,1,0) | x+y |
|
|
435
|
+
| 6th | xz | (1,0,1) | x+z |
|
|
436
|
+
| 7th | yz | (0,1,1) | y+z |
|
|
437
|
+
| 8th | xyz | (1,1,1) | x+y+z |
|
|
438
|
+
|
|
439
|
+
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.
|
|
440
|
+
|
|
441
|
+
### Interpolation modes
|
|
442
|
+
|
|
443
|
+
Both `LinearColorSpace` and `CubeColorSpace` support configurable interpolation:
|
|
444
|
+
|
|
445
|
+
| Mode | Description |
|
|
446
|
+
|------|-------------|
|
|
447
|
+
| `'oklch'` (default) | Perceptually uniform — hues travel the color wheel, even lightness steps |
|
|
448
|
+
| `'lab'` | CIE L\*a\*b\* — perceptual but no hue rotation |
|
|
449
|
+
| `'rgb'` | Linear RGB — fast but perceptually uneven |
|
|
450
|
+
| `false` | No interpolation — plain lookup table (LinearColorSpace only) |
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
// LinearColorSpace
|
|
454
|
+
new LinearColorSpace('#ff0000', '#0000ff').interpolation('oklch').size(10)
|
|
455
|
+
new LinearColorSpace('#ff0000', '#0000ff').interpolation('lab').size(10)
|
|
456
|
+
new LinearColorSpace('#ff0000', '#0000ff').interpolation('rgb').size(10)
|
|
457
|
+
new LinearColorSpace('#f00', '#0f0', '#00f').interpolation(false).size(3)
|
|
458
|
+
|
|
459
|
+
// CubeColorSpace
|
|
460
|
+
new CubeColorSpace({ ... }, { interpolation: 'lab' }).size(6)
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
### Color result chaining
|
|
464
|
+
|
|
465
|
+
All color space lookups return a `ColorResult` that acts as a hex string but supports format conversion:
|
|
466
|
+
|
|
467
|
+
```typescript
|
|
468
|
+
const color = tint({ r: 4, b: 2 });
|
|
469
|
+
color.hex // → '#ab34cd'
|
|
470
|
+
color.format('hsl') // → 'hsl(280, 60%, 50%)'
|
|
471
|
+
color.format('rgb') // → 'rgb(171, 52, 205)'
|
|
472
|
+
color.format('oklch') // → 'oklch(52.3% 0.198 310)'
|
|
473
|
+
color.toString() // → '#ab34cd'
|
|
474
|
+
```
|
|
475
|
+
|
|
322
476
|
## Development
|
|
323
477
|
|
|
324
478
|
```bash
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ColorFormat, ScaleType, BlendMode, HarmonyType, RampaResult } from './types';
|
|
2
|
+
export declare class RampaBuilder {
|
|
3
|
+
private _baseColor;
|
|
4
|
+
private _size;
|
|
5
|
+
private _format;
|
|
6
|
+
private _lightnessStart;
|
|
7
|
+
private _lightnessEnd;
|
|
8
|
+
private _saturationStart;
|
|
9
|
+
private _saturationEnd;
|
|
10
|
+
private _hueStart;
|
|
11
|
+
private _hueEnd;
|
|
12
|
+
private _lightnessScale;
|
|
13
|
+
private _saturationScale;
|
|
14
|
+
private _hueScale;
|
|
15
|
+
private _tintColor?;
|
|
16
|
+
private _tintOpacity;
|
|
17
|
+
private _tintBlend;
|
|
18
|
+
private _harmonies;
|
|
19
|
+
constructor(baseColor: string);
|
|
20
|
+
size(steps: number): this;
|
|
21
|
+
format(fmt: ColorFormat): this;
|
|
22
|
+
lightness(start: number, end: number): this;
|
|
23
|
+
saturation(start: number, end: number): this;
|
|
24
|
+
hue(start: number, end: number): this;
|
|
25
|
+
lightnessScale(scale: ScaleType): this;
|
|
26
|
+
saturationScale(scale: ScaleType): this;
|
|
27
|
+
hueScale(scale: ScaleType): this;
|
|
28
|
+
tint(color: string, opacity: number, blend?: BlendMode): this;
|
|
29
|
+
add(type: HarmonyType): this;
|
|
30
|
+
add(type: 'shift', degrees: number): this;
|
|
31
|
+
private buildConfig;
|
|
32
|
+
private formatColor;
|
|
33
|
+
private getHarmonyColors;
|
|
34
|
+
private buildRamps;
|
|
35
|
+
generate(): RampaResult;
|
|
36
|
+
toCSS(): string;
|
|
37
|
+
toJSON(): string;
|
|
38
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { InterpolationMode, CubeColorSpaceResult } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Create a 3D color cube from 8 named corner colors.
|
|
4
|
+
*
|
|
5
|
+
* The constructor keys become shortcut function names and tint() aliases.
|
|
6
|
+
* Key order determines cube position (see CORNER_MASKS above).
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* const space = new CubeColorSpace({
|
|
11
|
+
* k: '#1e1e2e', r: '#f38ba8', g: '#a6e3a1', b: '#89b4fa',
|
|
12
|
+
* y: '#f9e2af', m: '#cba6f7', c: '#94e2d5', w: '#cdd6f4',
|
|
13
|
+
* }).size(6);
|
|
14
|
+
*
|
|
15
|
+
* space.r(4) // → ColorResult (single-axis shortcut)
|
|
16
|
+
* space.tint({ r: 3, b: 2 }) // → ColorResult (multi-axis)
|
|
17
|
+
* space.cube(3, 0, 2) // → ColorResult (raw coordinates)
|
|
18
|
+
* space.palette // → string[216]
|
|
19
|
+
*
|
|
20
|
+
* // Destructure for convenience:
|
|
21
|
+
* const { r, w, tint, cube } = space;
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare class CubeColorSpace {
|
|
25
|
+
private _corners;
|
|
26
|
+
private _interpolation;
|
|
27
|
+
constructor(corners: Record<string, string>, options?: {
|
|
28
|
+
interpolation?: InterpolationMode;
|
|
29
|
+
});
|
|
30
|
+
/**
|
|
31
|
+
* Set the steps per axis and return the color space accessor object.
|
|
32
|
+
*/
|
|
33
|
+
size(stepsPerAxis: number): CubeColorSpaceResult;
|
|
34
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { RampaBuilder } from './builder';
|
|
2
|
+
import { ReadOnlyBuilder } from './read-only';
|
|
3
|
+
import { LinearColorSpace } from './linear-color-space';
|
|
4
|
+
import { CubeColorSpace } from './cube-color-space';
|
|
5
|
+
import type { ColorFormat, ScaleType, BlendMode, HarmonyType, RampResult, RampaResult, ColorInfo, InterpolationMode, ColorResult, RgbComponents, LinearColorSpaceFn, CubeColorSpaceResult, ColorSpaceOptions } from './types';
|
|
6
|
+
/**
|
|
7
|
+
* Create a new color ramp builder from a base color.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { rampa } from '@basiclines/rampa-sdk';
|
|
12
|
+
*
|
|
13
|
+
* const result = rampa('#3b82f6').size(10).generate();
|
|
14
|
+
* const css = rampa('#3b82f6').add('complementary').toCSS();
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare function rampa(baseColor: string): RampaBuilder;
|
|
18
|
+
export declare namespace rampa {
|
|
19
|
+
var convert: (color: string, format: ColorFormat) => string;
|
|
20
|
+
var readOnly: (color: string) => ReadOnlyBuilder;
|
|
21
|
+
var mix: (color1: string, color2: string, t: number) => string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Create a ColorResult from any hex color string.
|
|
25
|
+
* Provides .hex, .rgb, .luminance, .format() for color inspection.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* import { color } from '@basiclines/rampa-sdk';
|
|
30
|
+
* const c = color('#ff0000');
|
|
31
|
+
* c.rgb // { r: 255, g: 0, b: 0 }
|
|
32
|
+
* c.luminance // 0.627 (OKLCH perceptual lightness)
|
|
33
|
+
* c.format('hsl') // 'hsl(0, 100%, 50%)'
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare function color(hex: string): ColorResult;
|
|
37
|
+
export { RampaBuilder, ReadOnlyBuilder, LinearColorSpace, CubeColorSpace };
|
|
38
|
+
export type { ColorFormat, ScaleType, BlendMode, HarmonyType, RampResult, RampaResult, ColorInfo, InterpolationMode, ColorResult, RgbComponents, LinearColorSpaceFn, CubeColorSpaceResult, ColorSpaceOptions, };
|
package/dist/index.js
CHANGED
|
@@ -7473,10 +7473,6 @@ var generateColorRamp = (config) => {
|
|
|
7473
7473
|
const colors = [];
|
|
7474
7474
|
const middleIndex = Math.floor(config.totalSteps / 2);
|
|
7475
7475
|
for (let i = 0;i < config.totalSteps; i++) {
|
|
7476
|
-
if (config.swatches && config.swatches[i]?.locked) {
|
|
7477
|
-
colors.push(config.swatches[i].color);
|
|
7478
|
-
continue;
|
|
7479
|
-
}
|
|
7480
7476
|
if (config.colorFormat === "oklch") {
|
|
7481
7477
|
const color = generateSingleColorOklch(config, i, convertToOklch(config.baseColor), middleIndex);
|
|
7482
7478
|
colors.push(color);
|
|
@@ -7875,34 +7871,261 @@ class ReadOnlyBuilder {
|
|
|
7875
7871
|
}
|
|
7876
7872
|
}
|
|
7877
7873
|
|
|
7878
|
-
// src/
|
|
7879
|
-
function
|
|
7880
|
-
|
|
7874
|
+
// ../src/engine/ColorSpaceEngine.ts
|
|
7875
|
+
function mixWithMode(color1, color2, t, mode = "oklch") {
|
|
7876
|
+
if (mode === "oklch") {
|
|
7877
|
+
return mixOklch(color1, color2, t);
|
|
7878
|
+
}
|
|
7879
|
+
return chroma_js_default2.mix(color1, color2, t, mode).hex();
|
|
7881
7880
|
}
|
|
7882
|
-
|
|
7883
|
-
const
|
|
7884
|
-
|
|
7885
|
-
|
|
7886
|
-
|
|
7887
|
-
|
|
7881
|
+
function mixOklch(color1, color2, t) {
|
|
7882
|
+
const oklch1 = convertToOklch(color1);
|
|
7883
|
+
const oklch22 = convertToOklch(color2);
|
|
7884
|
+
let h1 = oklch1.h;
|
|
7885
|
+
let h2 = oklch22.h;
|
|
7886
|
+
let hueDiff = h2 - h1;
|
|
7887
|
+
if (hueDiff > 180)
|
|
7888
|
+
hueDiff -= 360;
|
|
7889
|
+
if (hueDiff < -180)
|
|
7890
|
+
hueDiff += 360;
|
|
7891
|
+
let h = h1 + t * hueDiff;
|
|
7892
|
+
if (h < 0)
|
|
7893
|
+
h += 360;
|
|
7894
|
+
if (h >= 360)
|
|
7895
|
+
h -= 360;
|
|
7896
|
+
if (oklch1.c < 0.002 && oklch22.c < 0.002) {
|
|
7897
|
+
h = 0;
|
|
7898
|
+
} else if (oklch1.c < 0.002) {
|
|
7899
|
+
h = oklch22.h;
|
|
7900
|
+
} else if (oklch22.c < 0.002) {
|
|
7901
|
+
h = oklch1.h;
|
|
7902
|
+
}
|
|
7903
|
+
const mixed = {
|
|
7904
|
+
l: oklch1.l + t * (oklch22.l - oklch1.l),
|
|
7905
|
+
c: oklch1.c + t * (oklch22.c - oklch1.c),
|
|
7906
|
+
h
|
|
7907
|
+
};
|
|
7908
|
+
const constrained = constrainOklchValues(mixed);
|
|
7909
|
+
return convertFromOklch(constrained);
|
|
7910
|
+
}
|
|
7911
|
+
function generateLinearSpace(from, to, steps, mode = "oklch") {
|
|
7912
|
+
const colors = [];
|
|
7913
|
+
for (let i = 0;i < steps; i++) {
|
|
7914
|
+
const t = steps === 1 ? 0.5 : i / (steps - 1);
|
|
7915
|
+
colors.push(mixWithMode(from, to, t, mode));
|
|
7916
|
+
}
|
|
7917
|
+
return colors;
|
|
7918
|
+
}
|
|
7919
|
+
function generateCubeSpace(corners, stepsPerAxis, mode = "oklch") {
|
|
7920
|
+
const [origin, x, y, z, xy, xz, yz, xyz] = corners;
|
|
7921
|
+
const mix = (a, b, t) => mixWithMode(a, b, t, mode);
|
|
7922
|
+
const max11 = stepsPerAxis - 1;
|
|
7923
|
+
const colors = [];
|
|
7924
|
+
for (let xi = 0;xi < stepsPerAxis; xi++) {
|
|
7925
|
+
const tx = max11 === 0 ? 0 : xi / max11;
|
|
7926
|
+
const c_x0y0 = mix(origin, x, tx);
|
|
7927
|
+
const c_x0y1 = mix(y, xy, tx);
|
|
7928
|
+
const c_x1y0 = mix(z, xz, tx);
|
|
7929
|
+
const c_x1y1 = mix(yz, xyz, tx);
|
|
7930
|
+
for (let yi = 0;yi < stepsPerAxis; yi++) {
|
|
7931
|
+
const ty = max11 === 0 ? 0 : yi / max11;
|
|
7932
|
+
const c_z0 = mix(c_x0y0, c_x0y1, ty);
|
|
7933
|
+
const c_z1 = mix(c_x1y0, c_x1y1, ty);
|
|
7934
|
+
for (let zi = 0;zi < stepsPerAxis; zi++) {
|
|
7935
|
+
const tz = max11 === 0 ? 0 : zi / max11;
|
|
7936
|
+
colors.push(mix(c_z0, c_z1, tz));
|
|
7937
|
+
}
|
|
7888
7938
|
}
|
|
7889
|
-
|
|
7890
|
-
|
|
7891
|
-
|
|
7939
|
+
}
|
|
7940
|
+
return colors;
|
|
7941
|
+
}
|
|
7942
|
+
|
|
7943
|
+
// src/color-result.ts
|
|
7944
|
+
function createColorResult(hex6) {
|
|
7945
|
+
const c2 = chroma_js_default(hex6);
|
|
7946
|
+
const [r2, g, b] = c2.rgb();
|
|
7947
|
+
const [l] = c2.oklch();
|
|
7948
|
+
const result = {
|
|
7949
|
+
hex: hex6,
|
|
7950
|
+
rgb: { r: r2, g, b },
|
|
7951
|
+
luminance: l,
|
|
7952
|
+
format(fmt) {
|
|
7953
|
+
switch (fmt) {
|
|
7954
|
+
case "hsl": {
|
|
7955
|
+
const [h, s, l2] = c2.hsl();
|
|
7956
|
+
return `hsl(${Math.round(h || 0)}, ${Math.round(s * 100)}%, ${Math.round(l2 * 100)}%)`;
|
|
7957
|
+
}
|
|
7958
|
+
case "rgb": {
|
|
7959
|
+
const [r3, g2, b2] = c2.rgb();
|
|
7960
|
+
return `rgb(${r3}, ${g2}, ${b2})`;
|
|
7961
|
+
}
|
|
7962
|
+
case "oklch": {
|
|
7963
|
+
const [l2, ch, h] = c2.oklch();
|
|
7964
|
+
return `oklch(${(l2 * 100).toFixed(1)}% ${ch.toFixed(3)} ${Math.round(h || 0)})`;
|
|
7965
|
+
}
|
|
7966
|
+
default:
|
|
7967
|
+
return hex6;
|
|
7968
|
+
}
|
|
7969
|
+
},
|
|
7970
|
+
toString() {
|
|
7971
|
+
return hex6;
|
|
7892
7972
|
}
|
|
7893
|
-
|
|
7894
|
-
|
|
7895
|
-
|
|
7973
|
+
};
|
|
7974
|
+
return result;
|
|
7975
|
+
}
|
|
7976
|
+
|
|
7977
|
+
// src/linear-color-space.ts
|
|
7978
|
+
class LinearColorSpace {
|
|
7979
|
+
_colors;
|
|
7980
|
+
_interpolation = "oklch";
|
|
7981
|
+
constructor(...colors) {
|
|
7982
|
+
if (colors.length < 2) {
|
|
7983
|
+
throw new Error("LinearColorSpace requires at least 2 colors");
|
|
7896
7984
|
}
|
|
7897
|
-
|
|
7898
|
-
return c2.hex();
|
|
7985
|
+
this._colors = colors;
|
|
7899
7986
|
}
|
|
7987
|
+
interpolation(mode) {
|
|
7988
|
+
this._interpolation = mode;
|
|
7989
|
+
return this;
|
|
7990
|
+
}
|
|
7991
|
+
size(steps) {
|
|
7992
|
+
let palette;
|
|
7993
|
+
if (this._interpolation === false) {
|
|
7994
|
+
palette = [...this._colors];
|
|
7995
|
+
} else {
|
|
7996
|
+
palette = generateLinearSpace(this._colors[0], this._colors[this._colors.length - 1], steps, this._interpolation);
|
|
7997
|
+
}
|
|
7998
|
+
return buildFn(palette);
|
|
7999
|
+
}
|
|
8000
|
+
}
|
|
8001
|
+
function buildFn(palette) {
|
|
8002
|
+
const fn4 = (index) => {
|
|
8003
|
+
const i = Math.max(1, Math.min(palette.length, index)) - 1;
|
|
8004
|
+
return createColorResult(palette[i]);
|
|
8005
|
+
};
|
|
8006
|
+
fn4.palette = palette;
|
|
8007
|
+
fn4.size = palette.length;
|
|
8008
|
+
return fn4;
|
|
8009
|
+
}
|
|
8010
|
+
|
|
8011
|
+
// src/cube-color-space.ts
|
|
8012
|
+
var CORNER_MASKS = [
|
|
8013
|
+
{ x: 0, y: 0, z: 0 },
|
|
8014
|
+
{ x: 1, y: 0, z: 0 },
|
|
8015
|
+
{ x: 0, y: 1, z: 0 },
|
|
8016
|
+
{ x: 0, y: 0, z: 1 },
|
|
8017
|
+
{ x: 1, y: 1, z: 0 },
|
|
8018
|
+
{ x: 1, y: 0, z: 1 },
|
|
8019
|
+
{ x: 0, y: 1, z: 1 },
|
|
8020
|
+
{ x: 1, y: 1, z: 1 }
|
|
8021
|
+
];
|
|
8022
|
+
|
|
8023
|
+
class CubeColorSpace {
|
|
8024
|
+
_corners;
|
|
8025
|
+
_interpolation;
|
|
8026
|
+
constructor(corners, options) {
|
|
8027
|
+
const keys = Object.keys(corners);
|
|
8028
|
+
if (keys.length !== 8) {
|
|
8029
|
+
throw new Error(`CubeColorSpace requires exactly 8 corner colors, got ${keys.length}`);
|
|
8030
|
+
}
|
|
8031
|
+
this._corners = corners;
|
|
8032
|
+
this._interpolation = options?.interpolation ?? "oklch";
|
|
8033
|
+
}
|
|
8034
|
+
size(stepsPerAxis) {
|
|
8035
|
+
const keys = Object.keys(this._corners);
|
|
8036
|
+
const cornerColors = keys.map((k3) => this._corners[k3]);
|
|
8037
|
+
const aliases = {};
|
|
8038
|
+
for (let i = 0;i < 8; i++) {
|
|
8039
|
+
aliases[keys[i]] = CORNER_MASKS[i];
|
|
8040
|
+
}
|
|
8041
|
+
const palette = generateCubeSpace(cornerColors, stepsPerAxis, this._interpolation);
|
|
8042
|
+
const max11 = stepsPerAxis - 1;
|
|
8043
|
+
const lookup = (cx, cy, cz) => {
|
|
8044
|
+
cx = Math.max(0, Math.min(max11, Math.round(cx)));
|
|
8045
|
+
cy = Math.max(0, Math.min(max11, Math.round(cy)));
|
|
8046
|
+
cz = Math.max(0, Math.min(max11, Math.round(cz)));
|
|
8047
|
+
const index = cx * stepsPerAxis * stepsPerAxis + cy * stepsPerAxis + cz;
|
|
8048
|
+
return createColorResult(palette[index]);
|
|
8049
|
+
};
|
|
8050
|
+
const tint = (query) => {
|
|
8051
|
+
let cx = 0, cy = 0, cz = 0;
|
|
8052
|
+
for (const [key, intensity] of Object.entries(query)) {
|
|
8053
|
+
const mask = aliases[key];
|
|
8054
|
+
if (!mask)
|
|
8055
|
+
continue;
|
|
8056
|
+
cx = Math.max(cx, mask.x * intensity);
|
|
8057
|
+
cy = Math.max(cy, mask.y * intensity);
|
|
8058
|
+
cz = Math.max(cz, mask.z * intensity);
|
|
8059
|
+
}
|
|
8060
|
+
return lookup(cx, cy, cz);
|
|
8061
|
+
};
|
|
8062
|
+
const cube = (x, y, z) => lookup(x, y, z);
|
|
8063
|
+
const result = {
|
|
8064
|
+
tint,
|
|
8065
|
+
cube,
|
|
8066
|
+
palette,
|
|
8067
|
+
size: stepsPerAxis
|
|
8068
|
+
};
|
|
8069
|
+
for (const key of keys) {
|
|
8070
|
+
result[key] = (index) => tint({ [key]: index });
|
|
8071
|
+
}
|
|
8072
|
+
return result;
|
|
8073
|
+
}
|
|
8074
|
+
}
|
|
8075
|
+
|
|
8076
|
+
// ../src/usecases/MixColors.ts
|
|
8077
|
+
function mixColors(color1, color2, t) {
|
|
8078
|
+
const oklch1 = convertToOklch(color1);
|
|
8079
|
+
const oklch22 = convertToOklch(color2);
|
|
8080
|
+
let h1 = oklch1.h;
|
|
8081
|
+
let h2 = oklch22.h;
|
|
8082
|
+
let hueDiff = h2 - h1;
|
|
8083
|
+
if (hueDiff > 180)
|
|
8084
|
+
hueDiff -= 360;
|
|
8085
|
+
if (hueDiff < -180)
|
|
8086
|
+
hueDiff += 360;
|
|
8087
|
+
let h = h1 + t * hueDiff;
|
|
8088
|
+
if (h < 0)
|
|
8089
|
+
h += 360;
|
|
8090
|
+
if (h >= 360)
|
|
8091
|
+
h -= 360;
|
|
8092
|
+
if (oklch1.c < 0.002 && oklch22.c < 0.002) {
|
|
8093
|
+
h = 0;
|
|
8094
|
+
} else if (oklch1.c < 0.002) {
|
|
8095
|
+
h = oklch22.h;
|
|
8096
|
+
} else if (oklch22.c < 0.002) {
|
|
8097
|
+
h = oklch1.h;
|
|
8098
|
+
}
|
|
8099
|
+
const mixed = {
|
|
8100
|
+
l: oklch1.l + t * (oklch22.l - oklch1.l),
|
|
8101
|
+
c: oklch1.c + t * (oklch22.c - oklch1.c),
|
|
8102
|
+
h
|
|
8103
|
+
};
|
|
8104
|
+
const constrained = constrainOklchValues(mixed);
|
|
8105
|
+
return convertFromOklch(constrained);
|
|
8106
|
+
}
|
|
8107
|
+
|
|
8108
|
+
// src/index.ts
|
|
8109
|
+
function rampa(baseColor) {
|
|
8110
|
+
return new RampaBuilder(baseColor);
|
|
8111
|
+
}
|
|
8112
|
+
rampa.convert = function convert(color, format) {
|
|
8113
|
+
return createColorResult(color).format(format);
|
|
7900
8114
|
};
|
|
7901
8115
|
rampa.readOnly = function readOnly(color) {
|
|
7902
8116
|
return new ReadOnlyBuilder(color);
|
|
7903
8117
|
};
|
|
8118
|
+
rampa.mix = function mix(color1, color2, t) {
|
|
8119
|
+
return mixColors(color1, color2, t);
|
|
8120
|
+
};
|
|
8121
|
+
function color(hex6) {
|
|
8122
|
+
return createColorResult(hex6);
|
|
8123
|
+
}
|
|
7904
8124
|
export {
|
|
7905
8125
|
rampa,
|
|
8126
|
+
color,
|
|
7906
8127
|
ReadOnlyBuilder,
|
|
7907
|
-
RampaBuilder
|
|
8128
|
+
RampaBuilder,
|
|
8129
|
+
LinearColorSpace,
|
|
8130
|
+
CubeColorSpace
|
|
7908
8131
|
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { InterpolationMode, LinearColorSpaceFn } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Create a linear color space.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* // Interpolated (default: oklch)
|
|
8
|
+
* const neutral = new LinearColorSpace('#ffffff', '#000000').size(24);
|
|
9
|
+
* neutral(12) // → ColorResult (mid gray)
|
|
10
|
+
* neutral(12).format('hsl') // → 'hsl(...)'
|
|
11
|
+
*
|
|
12
|
+
* // Different interpolation
|
|
13
|
+
* const ramp = new LinearColorSpace('#ff0000', '#0000ff').interpolation('lab').size(10);
|
|
14
|
+
*
|
|
15
|
+
* // Lookup table — no interpolation, just a plain color array
|
|
16
|
+
* const base = new LinearColorSpace('#000', '#f00', '#0f0', '#ff0', '#00f', '#f0f', '#0ff', '#fff')
|
|
17
|
+
* .interpolation(false)
|
|
18
|
+
* .size(8);
|
|
19
|
+
* base(1) // → first color
|
|
20
|
+
* base(3) // → third color
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare class LinearColorSpace {
|
|
24
|
+
private _colors;
|
|
25
|
+
private _interpolation;
|
|
26
|
+
constructor(...colors: string[]);
|
|
27
|
+
/**
|
|
28
|
+
* Set the interpolation mode.
|
|
29
|
+
* Pass false for a plain lookup table (no interpolation).
|
|
30
|
+
*/
|
|
31
|
+
interpolation(mode: InterpolationMode | false): this;
|
|
32
|
+
/**
|
|
33
|
+
* Set the number of color steps and return the color accessor function.
|
|
34
|
+
*/
|
|
35
|
+
size(steps: number): LinearColorSpaceFn;
|
|
36
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export type ColorFormat = 'hex' | 'hsl' | 'rgb' | 'oklch';
|
|
2
|
+
export type ScaleType = 'linear' | 'geometric' | 'fibonacci' | 'golden-ratio' | 'logarithmic' | 'powers-of-2' | 'musical-ratio' | 'cielab-uniform' | 'ease-in' | 'ease-out' | 'ease-in-out';
|
|
3
|
+
export type BlendMode = 'normal' | 'darken' | 'multiply' | 'plus-darker' | 'color-burn' | 'lighten' | 'screen' | 'plus-lighter' | 'color-dodge' | 'overlay' | 'soft-light' | 'hard-light' | 'difference' | 'exclusion' | 'hue' | 'saturation' | 'color' | 'luminosity';
|
|
4
|
+
export type HarmonyType = 'complementary' | 'triadic' | 'analogous' | 'split-complementary' | 'square' | 'compound';
|
|
5
|
+
export interface RampResult {
|
|
6
|
+
name: string;
|
|
7
|
+
baseColor: string;
|
|
8
|
+
colors: string[];
|
|
9
|
+
}
|
|
10
|
+
export interface RampaResult {
|
|
11
|
+
ramps: RampResult[];
|
|
12
|
+
}
|
|
13
|
+
export interface ColorInfo {
|
|
14
|
+
hex: string;
|
|
15
|
+
rgb: {
|
|
16
|
+
r: number;
|
|
17
|
+
g: number;
|
|
18
|
+
b: number;
|
|
19
|
+
};
|
|
20
|
+
hsl: {
|
|
21
|
+
h: number;
|
|
22
|
+
s: number;
|
|
23
|
+
l: number;
|
|
24
|
+
};
|
|
25
|
+
oklch: {
|
|
26
|
+
l: number;
|
|
27
|
+
c: number;
|
|
28
|
+
h: number;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export type InterpolationMode = 'oklch' | 'lab' | 'rgb';
|
|
32
|
+
export interface ColorSpaceOptions {
|
|
33
|
+
interpolation?: InterpolationMode;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* RGB components (0-255).
|
|
37
|
+
*/
|
|
38
|
+
export interface RgbComponents {
|
|
39
|
+
r: number;
|
|
40
|
+
g: number;
|
|
41
|
+
b: number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A color result that acts as a hex string but supports format chaining.
|
|
45
|
+
*/
|
|
46
|
+
export interface ColorResult {
|
|
47
|
+
/** Hex color string */
|
|
48
|
+
hex: string;
|
|
49
|
+
/** RGB components (0-255) */
|
|
50
|
+
rgb: RgbComponents;
|
|
51
|
+
/** Perceptual luminance (0-1) using OKLCH lightness */
|
|
52
|
+
luminance: number;
|
|
53
|
+
/** Format the color as hsl, rgb, oklch, or hex */
|
|
54
|
+
format(fmt: ColorFormat): string;
|
|
55
|
+
/** String coercion returns hex */
|
|
56
|
+
toString(): string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The function signature returned by LinearColorSpace.
|
|
60
|
+
* Call it with a 1-based index to get a color.
|
|
61
|
+
*/
|
|
62
|
+
export interface LinearColorSpaceFn {
|
|
63
|
+
(index: number): ColorResult;
|
|
64
|
+
palette: string[];
|
|
65
|
+
size: number;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The result object returned by CubeColorSpace.size().
|
|
69
|
+
* Provides multiple ways to access colors:
|
|
70
|
+
* - Per-corner shortcut functions (e.g. r(3), w(5))
|
|
71
|
+
* - tint() for multi-axis lookups
|
|
72
|
+
* - cube() for raw 3D coordinate access
|
|
73
|
+
* - palette for the full color array
|
|
74
|
+
*/
|
|
75
|
+
export interface CubeColorSpaceResult {
|
|
76
|
+
/** Multi-axis lookup: tint({ r: 3, b: 2 }) */
|
|
77
|
+
tint(query: Record<string, number>): ColorResult;
|
|
78
|
+
/** Raw 3D coordinate lookup: cube(x, y, z) */
|
|
79
|
+
cube(x: number, y: number, z: number): ColorResult;
|
|
80
|
+
/** Full palette array */
|
|
81
|
+
palette: string[];
|
|
82
|
+
/** Steps per axis */
|
|
83
|
+
size: number;
|
|
84
|
+
/** Per-corner shortcut functions, keyed by constructor key names */
|
|
85
|
+
[key: string]: ((index: number) => ColorResult) | string[] | number | ((query: Record<string, number>) => ColorResult) | ((x: number, y: number, z: number) => ColorResult);
|
|
86
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@basiclines/rampa-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"description": "Programmatic JavaScript SDK for generating color palettes with rampa",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"dist"
|
|
10
10
|
],
|
|
11
11
|
"scripts": {
|
|
12
|
-
"build": "bun build ./src/index.ts --outfile=./dist/index.js --target=node",
|
|
12
|
+
"build": "bun build ./src/index.ts --outfile=./dist/index.js --target=node && tsc -p tsconfig.declarations.json && cp dist/sdk/src/*.d.ts dist/ && cp -r dist/sdk/src/formatters dist/formatters 2>/dev/null; rm -rf dist/sdk dist/src",
|
|
13
13
|
"test": "bun test"
|
|
14
14
|
},
|
|
15
15
|
"repository": {
|
|
@@ -26,6 +26,10 @@
|
|
|
26
26
|
],
|
|
27
27
|
"author": "ismael.fyi",
|
|
28
28
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public",
|
|
31
|
+
"provenance": true
|
|
32
|
+
},
|
|
29
33
|
"dependencies": {
|
|
30
34
|
"chroma-js": "^3.1.2",
|
|
31
35
|
"culori": "^4.0.1"
|