@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 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, // 'hex' | 'hsl' | 'rgb' | 'oklch'
229
- ScaleType, // 'linear' | 'fibonacci' | 'golden-ratio' | ...
230
- BlendMode, // 'normal' | 'multiply' | 'screen' | ...
231
- HarmonyType, // 'complementary' | 'triadic' | 'analogous' | ...
232
- RampResult, // { name, baseColor, colors }
233
- RampaResult, // { ramps: RampResult[] }
234
- ColorInfo, // { hex, rgb, hsl, oklch } — returned by readOnly()
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,6 @@
1
+ import type { ColorResult } from './types';
2
+ /**
3
+ * Create a ColorResult from a hex string.
4
+ * Acts as a string (hex) by default, supports .format() for conversions.
5
+ */
6
+ export declare function createColorResult(hex: string): ColorResult;
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ import type { RampResult } from '../types';
2
+ export declare function formatCssOutput(ramps: RampResult[]): string;
@@ -0,0 +1,2 @@
1
+ import type { RampResult } from '../types';
2
+ export declare function formatJsonOutput(ramps: RampResult[]): string;
@@ -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/index.ts
7879
- function rampa(baseColor) {
7880
- return new RampaBuilder(baseColor);
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
- rampa.convert = function convert(color, format) {
7883
- const c2 = chroma_js_default(color);
7884
- switch (format) {
7885
- case "hsl": {
7886
- const [h, s, l] = c2.hsl();
7887
- return `hsl(${Math.round(h || 0)}, ${Math.round(s * 100)}%, ${Math.round(l * 100)}%)`;
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
- case "rgb": {
7890
- const [r2, g, b] = c2.rgb();
7891
- return `rgb(${r2}, ${g}, ${b})`;
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
- case "oklch": {
7894
- const [l, ch, h] = c2.oklch();
7895
- return `oklch(${(l * 100).toFixed(1)}% ${ch.toFixed(3)} ${Math.round(h || 0)})`;
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
- default:
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
+ }
@@ -0,0 +1,8 @@
1
+ import type { ColorFormat, ColorInfo } from './types';
2
+ export declare class ReadOnlyBuilder {
3
+ private _color;
4
+ private _format?;
5
+ constructor(color: string);
6
+ format(fmt: ColorFormat): this;
7
+ generate(): ColorInfo | string;
8
+ }
@@ -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.4.0",
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"