@sarmal/core 0.30.0 → 0.33.0

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.
Files changed (53) hide show
  1. package/dist/auto-init.cjs +115 -31
  2. package/dist/auto-init.cjs.map +1 -1
  3. package/dist/auto-init.js +115 -31
  4. package/dist/auto-init.js.map +1 -1
  5. package/dist/cli.js +8 -7
  6. package/dist/cli.js.map +1 -1
  7. package/dist/curves/artemis2.d.cts +1 -1
  8. package/dist/curves/artemis2.d.ts +1 -1
  9. package/dist/curves/astroid.d.cts +1 -1
  10. package/dist/curves/astroid.d.ts +1 -1
  11. package/dist/curves/deltoid.d.cts +1 -1
  12. package/dist/curves/deltoid.d.ts +1 -1
  13. package/dist/curves/epicycloid3.d.cts +1 -1
  14. package/dist/curves/epicycloid3.d.ts +1 -1
  15. package/dist/curves/epitrochoid7.d.cts +1 -1
  16. package/dist/curves/epitrochoid7.d.ts +1 -1
  17. package/dist/curves/index.d.cts +1 -1
  18. package/dist/curves/index.d.ts +1 -1
  19. package/dist/curves/lame.d.cts +1 -1
  20. package/dist/curves/lame.d.ts +1 -1
  21. package/dist/curves/lissajous32.d.cts +1 -1
  22. package/dist/curves/lissajous32.d.ts +1 -1
  23. package/dist/curves/lissajous43.d.cts +1 -1
  24. package/dist/curves/lissajous43.d.ts +1 -1
  25. package/dist/curves/rose3.d.cts +1 -1
  26. package/dist/curves/rose3.d.ts +1 -1
  27. package/dist/curves/rose5.d.cts +1 -1
  28. package/dist/curves/rose5.d.ts +1 -1
  29. package/dist/curves/rose52.d.cts +1 -1
  30. package/dist/curves/rose52.d.ts +1 -1
  31. package/dist/curves/star.d.cts +1 -1
  32. package/dist/curves/star.d.ts +1 -1
  33. package/dist/curves/star4.d.cts +1 -1
  34. package/dist/curves/star4.d.ts +1 -1
  35. package/dist/curves/star7.d.cts +1 -1
  36. package/dist/curves/star7.d.ts +1 -1
  37. package/dist/index.cjs +122 -34
  38. package/dist/index.cjs.map +1 -1
  39. package/dist/index.d.cts +3 -3
  40. package/dist/index.d.ts +3 -3
  41. package/dist/index.js +122 -34
  42. package/dist/index.js.map +1 -1
  43. package/dist/{renderer-shared-OR--cv-t.d.ts → renderer-shared-Bdca4O4G.d.ts} +8 -4
  44. package/dist/{renderer-shared-jqw_Q1WO.d.cts → renderer-shared-Ke9BeK1P.d.cts} +8 -4
  45. package/dist/terminal.cjs +8 -7
  46. package/dist/terminal.cjs.map +1 -1
  47. package/dist/terminal.d.cts +2 -2
  48. package/dist/terminal.d.ts +2 -2
  49. package/dist/terminal.js +8 -7
  50. package/dist/terminal.js.map +1 -1
  51. package/dist/{types-zbxUgcmZ.d.cts → types-BBuUk6nn.d.cts} +9 -0
  52. package/dist/{types-zbxUgcmZ.d.ts → types-BBuUk6nn.d.ts} +9 -0
  53. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { C as CurveDef } from '../types-zbxUgcmZ.cjs';
1
+ import { C as CurveDef } from '../types-BBuUk6nn.cjs';
2
2
 
3
3
  /**
4
4
  * Lamé curve (superellipse) with time-varying exponent
@@ -1,4 +1,4 @@
1
- import { C as CurveDef } from '../types-zbxUgcmZ.js';
1
+ import { C as CurveDef } from '../types-BBuUk6nn.js';
2
2
 
3
3
  /**
4
4
  * Lamé curve (superellipse) with time-varying exponent
@@ -1,4 +1,4 @@
1
- import { C as CurveDef } from '../types-zbxUgcmZ.cjs';
1
+ import { C as CurveDef } from '../types-BBuUk6nn.cjs';
2
2
 
3
3
  /**
4
4
  * Lissajous curve with frequency ratio 3:2
@@ -1,4 +1,4 @@
1
- import { C as CurveDef } from '../types-zbxUgcmZ.js';
1
+ import { C as CurveDef } from '../types-BBuUk6nn.js';
2
2
 
3
3
  /**
4
4
  * Lissajous curve with frequency ratio 3:2
@@ -1,4 +1,4 @@
1
- import { C as CurveDef } from '../types-zbxUgcmZ.cjs';
1
+ import { C as CurveDef } from '../types-BBuUk6nn.cjs';
2
2
 
3
3
  /**
4
4
  * Lissajous curve with frequency ratio 4:3
@@ -1,4 +1,4 @@
1
- import { C as CurveDef } from '../types-zbxUgcmZ.js';
1
+ import { C as CurveDef } from '../types-BBuUk6nn.js';
2
2
 
3
3
  /**
4
4
  * Lissajous curve with frequency ratio 4:3
@@ -1,4 +1,4 @@
1
- import { C as CurveDef } from '../types-zbxUgcmZ.cjs';
1
+ import { C as CurveDef } from '../types-BBuUk6nn.cjs';
2
2
 
3
3
  /**
4
4
  * Rose curve with 3 petals
@@ -1,4 +1,4 @@
1
- import { C as CurveDef } from '../types-zbxUgcmZ.js';
1
+ import { C as CurveDef } from '../types-BBuUk6nn.js';
2
2
 
3
3
  /**
4
4
  * Rose curve with 3 petals
@@ -1,4 +1,4 @@
1
- import { C as CurveDef } from '../types-zbxUgcmZ.cjs';
1
+ import { C as CurveDef } from '../types-BBuUk6nn.cjs';
2
2
 
3
3
  /**
4
4
  * Rose curve with 5 petals
@@ -1,4 +1,4 @@
1
- import { C as CurveDef } from '../types-zbxUgcmZ.js';
1
+ import { C as CurveDef } from '../types-BBuUk6nn.js';
2
2
 
3
3
  /**
4
4
  * Rose curve with 5 petals
@@ -1,4 +1,4 @@
1
- import { C as CurveDef } from '../types-zbxUgcmZ.cjs';
1
+ import { C as CurveDef } from '../types-BBuUk6nn.cjs';
2
2
 
3
3
  /**
4
4
  * Rose curve with n=5/2 that traces 5 petals over two full revolutions
@@ -1,4 +1,4 @@
1
- import { C as CurveDef } from '../types-zbxUgcmZ.js';
1
+ import { C as CurveDef } from '../types-BBuUk6nn.js';
2
2
 
3
3
  /**
4
4
  * Rose curve with n=5/2 that traces 5 petals over two full revolutions
@@ -1,4 +1,4 @@
1
- import { C as CurveDef } from '../types-zbxUgcmZ.cjs';
1
+ import { C as CurveDef } from '../types-BBuUk6nn.cjs';
2
2
 
3
3
  /**
4
4
  * 5-pointed star based on Fourier harmonics.
@@ -1,4 +1,4 @@
1
- import { C as CurveDef } from '../types-zbxUgcmZ.js';
1
+ import { C as CurveDef } from '../types-BBuUk6nn.js';
2
2
 
3
3
  /**
4
4
  * 5-pointed star based on Fourier harmonics.
@@ -1,4 +1,4 @@
1
- import { C as CurveDef } from '../types-zbxUgcmZ.cjs';
1
+ import { C as CurveDef } from '../types-BBuUk6nn.cjs';
2
2
 
3
3
  /**
4
4
  * 4-pointed star based on Fourier harmonics.
@@ -1,4 +1,4 @@
1
- import { C as CurveDef } from '../types-zbxUgcmZ.js';
1
+ import { C as CurveDef } from '../types-BBuUk6nn.js';
2
2
 
3
3
  /**
4
4
  * 4-pointed star based on Fourier harmonics.
@@ -1,4 +1,4 @@
1
- import { C as CurveDef } from '../types-zbxUgcmZ.cjs';
1
+ import { C as CurveDef } from '../types-BBuUk6nn.cjs';
2
2
 
3
3
  /**
4
4
  * 7-pointed star based on Fourier harmonics.
@@ -1,4 +1,4 @@
1
- import { C as CurveDef } from '../types-zbxUgcmZ.js';
1
+ import { C as CurveDef } from '../types-BBuUk6nn.js';
2
2
 
3
3
  /**
4
4
  * 7-pointed star based on Fourier harmonics.
package/dist/index.cjs CHANGED
@@ -375,11 +375,15 @@ function enginePassthroughs(engine) {
375
375
  }
376
376
  var palettes = {
377
377
  bard: ["#a855f7", "#3b82f6", "#14b8a6", "#ec4899"],
378
- sunset: ["#f97316", "#dc2626", "#9333ea", "#f472b6"],
378
+ carnival: ["#ff6b6b", "#4ecdc4", "#ffe66d"],
379
379
  ocean: ["#1e3a8a", "#06b6d4", "#22d3ee", "#e0f2fe"],
380
+ sunset: ["#f97316", "#dc2626", "#9333ea", "#f472b6"],
380
381
  ice: ["#1e3a8a", "#67e8f9"],
381
- fire: ["#7f1d1d", "#fbbf24"],
382
- forest: ["#14532d", "#86efac"]
382
+ rocketpop: ["#08b8cd", "#ffffff", "#ff001f"],
383
+ neon: ["#00e5ff", "#7c3aed", "#e040fb"],
384
+ vaporwave: ["#ff71ce", "#01cdfe", "#b967ff"],
385
+ pastel: ["#c4b5fd", "#fbcfe8", "#bae6fd"],
386
+ sakura: ["#fff1f2", "#fda4af", "#fb7185"]
383
387
  };
384
388
  function hexToRgb(hex) {
385
389
  const n = parseInt(hex.slice(1), 16);
@@ -414,6 +418,49 @@ function parseColorToRgb(s) {
414
418
  }
415
419
  return null;
416
420
  }
421
+ var OKLCH_RE = /^oklch\(\s*([\d.]+)\s+([\d.]+)\s+([\d.]+)(?:\s*\/\s*[\d.]+)?\s*\)$/i;
422
+ function parseOklchToOklab(s) {
423
+ const m = OKLCH_RE.exec(s.trim());
424
+ if (!m) {
425
+ return null;
426
+ }
427
+ const L = parseFloat(m[1]);
428
+ const C = parseFloat(m[2]);
429
+ const H = parseFloat(m[3]);
430
+ if (Number.isNaN(L) || Number.isNaN(C) || Number.isNaN(H)) {
431
+ return null;
432
+ }
433
+ const clampedL = Math.max(0, Math.min(1, L));
434
+ const clampedC = Math.max(0, Math.min(0.4, C));
435
+ const H_rad = H * (Math.PI / 180);
436
+ return {
437
+ L: clampedL,
438
+ a: clampedC * Math.cos(H_rad),
439
+ b: clampedC * Math.sin(H_rad)
440
+ };
441
+ }
442
+ function parseColorToOklab(s) {
443
+ const oklab = parseOklchToOklab(s);
444
+ if (oklab !== null) {
445
+ return oklab;
446
+ }
447
+ const rgb = parseColorToRgb(s);
448
+ if (rgb === null) {
449
+ return null;
450
+ }
451
+ return rgbToOklab(rgb);
452
+ }
453
+ function colorToRgb(color) {
454
+ const rgb = parseColorToRgb(color);
455
+ if (rgb !== null) {
456
+ return rgb;
457
+ }
458
+ const lab = parseOklchToOklab(color);
459
+ if (lab !== null) {
460
+ return oklabToRgb(lab);
461
+ }
462
+ throw new Error(`[sarmal] unrecognized color "${color}"`);
463
+ }
417
464
  function srgbByteToLinear(c) {
418
465
  const n = c / 255;
419
466
  return n <= 0.04045 ? n / 12.92 : Math.pow((n + 0.055) / 1.055, 2.4);
@@ -451,26 +498,25 @@ var lerpOklab = (a, b, t) => {
451
498
  if (t >= 1) {
452
499
  return b;
453
500
  }
454
- const la = rgbToOklab(a), lb = rgbToOklab(b);
455
- return oklabToRgb({
456
- L: la.L + (lb.L - la.L) * t,
457
- a: la.a + (lb.a - la.a) * t,
458
- b: la.b + (lb.b - la.b) * t
459
- });
501
+ return {
502
+ L: a.L + (b.L - a.L) * t,
503
+ a: a.a + (b.a - a.a) * t,
504
+ b: a.b + (b.b - a.b) * t
505
+ };
460
506
  };
461
507
  function getPaletteColor(palette, position, timeOffset = 0) {
462
508
  if (palette.length === 0) {
463
- return { r: 255, g: 255, b: 255 };
509
+ return { L: 1, a: 0, b: 0 };
464
510
  }
465
511
  if (palette.length === 1) {
466
- return hexToRgb(palette[0]);
512
+ return palette[0];
467
513
  }
468
514
  const cyclePos = ((position + timeOffset) % 1 + 1) % 1;
469
515
  const scaled = cyclePos * palette.length;
470
516
  const idx = Math.floor(scaled);
471
517
  const t = scaled - idx;
472
- const c1 = hexToRgb(palette[idx % palette.length]);
473
- const c2 = hexToRgb(palette[(idx + 1) % palette.length]);
518
+ const c1 = palette[idx % palette.length];
519
+ const c2 = palette[(idx + 1) % palette.length];
474
520
  return lerpOklab(c1, c2, t);
475
521
  }
476
522
  var TRAIL_STYLES = ["default", "gradient-static", "gradient-animated"];
@@ -479,7 +525,8 @@ var RENDER_OPTION_KEYS = /* @__PURE__ */ new Set([
479
525
  "headColor",
480
526
  "skeletonColor",
481
527
  "trailStyle",
482
- "headRadius"
528
+ "headRadius",
529
+ "trailWidth"
483
530
  ]);
484
531
  function validateRenderOptions(partial) {
485
532
  for (const key of Object.keys(partial)) {
@@ -502,12 +549,15 @@ function validateRenderOptions(partial) {
502
549
  if (partial.headRadius !== void 0) {
503
550
  assertHeadRadius(partial.headRadius);
504
551
  }
552
+ if (partial.trailWidth !== void 0) {
553
+ assertTrailWidth(partial.trailWidth);
554
+ }
505
555
  }
506
556
  function assertTrailColor(value) {
507
557
  if (typeof value === "string") {
508
- if (parseColorToRgb(value) === null) {
558
+ if (parseColorToOklab(value) === null) {
509
559
  throw new TypeError(
510
- `[sarmal] setRenderOptions: trailColor must be a valid color string (#rrggbb, #rgb, rgb(), rgba()), got "${value}"`
560
+ `[sarmal] setRenderOptions: trailColor must be a valid color string (#rrggbb, #rgb, rgb(), rgba(), oklch()), got "${value}"`
511
561
  );
512
562
  }
513
563
  return;
@@ -520,25 +570,25 @@ function assertTrailColor(value) {
520
570
  }
521
571
  for (let i = 0; i < value.length; i++) {
522
572
  const entry = value[i];
523
- if (typeof entry !== "string" || parseColorToRgb(entry) === null) {
573
+ if (typeof entry !== "string" || parseColorToOklab(entry) === null) {
524
574
  throw new TypeError(
525
- `[sarmal] setRenderOptions: trailColor[${i}] must be a valid color string (#rrggbb, #rgb, rgb(), rgba()), got ${JSON.stringify(entry)}`
575
+ `[sarmal] setRenderOptions: trailColor[${i}] must be a valid color string (#rrggbb, #rgb, rgb(), rgba(), oklch()), got ${JSON.stringify(entry)}`
526
576
  );
527
577
  }
528
578
  }
529
579
  return;
530
580
  }
531
581
  throw new TypeError(
532
- `[sarmal] setRenderOptions: trailColor must be a valid color string (#rrggbb, #rgb, rgb(), rgba()) or an array of color strings, got ${JSON.stringify(value)}`
582
+ `[sarmal] setRenderOptions: trailColor must be a valid color string (#rrggbb, #rgb, rgb(), rgba(), oklch()) or an array of color strings, got ${JSON.stringify(value)}`
533
583
  );
534
584
  }
535
585
  function assertHeadColor(value) {
536
586
  if (value === null) {
537
587
  return;
538
588
  }
539
- if (typeof value !== "string" || parseColorToRgb(value) === null) {
589
+ if (typeof value !== "string" || parseColorToOklab(value) === null) {
540
590
  throw new TypeError(
541
- `[sarmal] setRenderOptions: headColor must be a valid color string (#rrggbb, #rgb, rgb(), rgba()) or null, got ${JSON.stringify(value)}`
591
+ `[sarmal] setRenderOptions: headColor must be a valid color string (#rrggbb, #rgb, rgb(), rgba(), oklch()) or null, got ${JSON.stringify(value)}`
542
592
  );
543
593
  }
544
594
  }
@@ -546,9 +596,9 @@ function assertSkeletonColor(value) {
546
596
  if (value === "transparent") {
547
597
  return;
548
598
  }
549
- if (typeof value !== "string" || parseColorToRgb(value) === null) {
599
+ if (typeof value !== "string" || parseColorToOklab(value) === null) {
550
600
  throw new TypeError(
551
- `[sarmal] setRenderOptions: skeletonColor must be a valid color string (#rrggbb, #rgb, rgb(), rgba()) or "transparent", got ${JSON.stringify(value)}`
601
+ `[sarmal] setRenderOptions: skeletonColor must be a valid color string (#rrggbb, #rgb, rgb(), rgba(), oklch()) or "transparent", got ${JSON.stringify(value)}`
552
602
  );
553
603
  }
554
604
  }
@@ -571,6 +621,18 @@ function assertHeadRadius(value) {
571
621
  );
572
622
  }
573
623
  }
624
+ function assertTrailWidth(value) {
625
+ if (typeof value !== "number") {
626
+ throw new TypeError(
627
+ `[sarmal] setRenderOptions: trailWidth must be a number, got ${JSON.stringify(value)}`
628
+ );
629
+ }
630
+ if (!Number.isFinite(value) || value <= 0) {
631
+ throw new TypeError(
632
+ `[sarmal] setRenderOptions: trailWidth must be a finite positive number, got ${value}`
633
+ );
634
+ }
635
+ }
574
636
  function resolveTrailMainColor(trailColor) {
575
637
  return typeof trailColor === "string" ? trailColor : trailColor[0];
576
638
  }
@@ -583,7 +645,7 @@ function resolveHeadColor(trailColor, trailStyle) {
583
645
  }
584
646
  const palette = resolveTrailPalette(trailColor);
585
647
  const last = palette[palette.length - 1];
586
- const { r, g, b } = parseColorToRgb(last);
648
+ const { r, g, b } = colorToRgb(last);
587
649
  return `rgb(${r},${g},${b})`;
588
650
  }
589
651
  function warnIfTrailColorMismatch(trailColor, trailStyle) {
@@ -604,8 +666,8 @@ function warnIfTrailColorMismatch(trailColor, trailStyle) {
604
666
  var getHeadDotRadius = (w, h) => Math.max(1, 3 * Math.sqrt(Math.min(w, h) / 160));
605
667
  var WHITE_HEX = "#ffffff";
606
668
  function colorToRgbComponents(color) {
607
- const c = parseColorToRgb(color);
608
- return `${c.r},${c.g},${c.b}`;
669
+ const { r, g, b } = colorToRgb(color);
670
+ return `${r},${g},${b}`;
609
671
  }
610
672
  function applyDprSizing(target, logicalWidth, logicalHeight, dpr) {
611
673
  target.style.width = `${logicalWidth}px`;
@@ -627,6 +689,7 @@ function createRenderer(options) {
627
689
  let headColor = userHeadColor ?? resolveHeadColor(trailColor, trailStyle);
628
690
  let trailSolidRgb = colorToRgbComponents(resolveTrailMainColor(trailColor));
629
691
  let trailPalette = resolveTrailPalette(trailColor);
692
+ let trailPaletteOklab = trailPalette.map((c) => parseColorToOklab(c));
630
693
  warnIfTrailColorMismatch(trailColor, trailStyle);
631
694
  const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
632
695
  function setupCanvas() {
@@ -638,7 +701,14 @@ function createRenderer(options) {
638
701
  setupCanvas();
639
702
  let logicalWidth = canvas.width / dpr;
640
703
  let logicalHeight = canvas.height / dpr;
704
+ if (options.headRadius !== void 0) {
705
+ validateRenderOptions({ headRadius: options.headRadius });
706
+ }
707
+ if (options.trailWidth !== void 0) {
708
+ validateRenderOptions({ trailWidth: options.trailWidth });
709
+ }
641
710
  let headRadius = options.headRadius ?? getHeadDotRadius(logicalWidth, logicalHeight);
711
+ let trailWidth = options.trailWidth ?? 1;
642
712
  let skeleton = [];
643
713
  let skeletonCanvas = null;
644
714
  let trail = [];
@@ -732,14 +802,16 @@ function createRenderer(options) {
732
802
  i,
733
803
  trailCount,
734
804
  toX,
735
- toY
805
+ toY,
806
+ TRAIL_MIN_WIDTH * trailWidth,
807
+ TRAIL_MAX_WIDTH * trailWidth
736
808
  );
737
809
  if (trailStyle === "default") {
738
810
  ctx.fillStyle = `rgba(${trailSolidRgb},${opacity})`;
739
811
  } else {
740
812
  const timeOffset = trailStyle === "gradient-animated" ? gradientAnimTime * 5e-4 : 0;
741
- const color = getPaletteColor(trailPalette, progress, timeOffset);
742
- ctx.fillStyle = `rgba(${color.r},${color.g},${color.b},${opacity})`;
813
+ const { r, g, b } = oklabToRgb(getPaletteColor(trailPaletteOklab, progress, timeOffset));
814
+ ctx.fillStyle = `rgba(${r},${g},${b},${opacity})`;
743
815
  }
744
816
  ctx.beginPath();
745
817
  ctx.moveTo(l0x, l0y);
@@ -873,6 +945,7 @@ function createRenderer(options) {
873
945
  trailColor = partial.trailColor;
874
946
  trailSolidRgb = colorToRgbComponents(resolveTrailMainColor(trailColor));
875
947
  trailPalette = resolveTrailPalette(trailColor);
948
+ trailPaletteOklab = trailPalette.map((c) => parseColorToOklab(c));
876
949
  }
877
950
  if (partial.skeletonColor !== void 0) {
878
951
  skeletonColor = partial.skeletonColor;
@@ -889,6 +962,9 @@ function createRenderer(options) {
889
962
  if (partial.headRadius !== void 0) {
890
963
  headRadius = partial.headRadius;
891
964
  }
965
+ if (partial.trailWidth !== void 0) {
966
+ trailWidth = partial.trailWidth;
967
+ }
892
968
  if (userHeadColor === null) {
893
969
  headColor = resolveHeadColor(trailColor, trailStyle);
894
970
  } else {
@@ -956,8 +1032,8 @@ function el(tag) {
956
1032
  return document.createElementNS("http://www.w3.org/2000/svg", tag);
957
1033
  }
958
1034
  function colorToRgbAttr(color) {
959
- const c = parseColorToRgb(color);
960
- return `rgb(${c.r},${c.g},${c.b})`;
1035
+ const { r, g, b } = colorToRgb(color);
1036
+ return `rgb(${r},${g},${b})`;
961
1037
  }
962
1038
  function createSVGRenderer(options) {
963
1039
  const { container, engine } = options;
@@ -975,6 +1051,7 @@ function createSVGRenderer(options) {
975
1051
  let headRadius;
976
1052
  let trailSolid = colorToRgbAttr(resolveTrailMainColor(trailColor));
977
1053
  let trailPalette = resolveTrailPalette(trailColor);
1054
+ let trailPaletteOklab = trailPalette.map((c) => parseColorToOklab(c));
978
1055
  const ariaLabel = options.ariaLabel ?? "Loading";
979
1056
  warnIfTrailColorMismatch(trailColor, trailStyle);
980
1057
  const viewSize = 100;
@@ -986,7 +1063,14 @@ function createSVGRenderer(options) {
986
1063
  const svgTrailMinWidth = TRAIL_MIN_WIDTH * viewSize / containerPx;
987
1064
  const svgTrailMaxWidth = TRAIL_MAX_WIDTH * viewSize / containerPx;
988
1065
  const svgSkeletonStrokeWidth = String(SKELETON_STROKE_WIDTH_PX * viewSize / containerPx);
1066
+ if (options.headRadius !== void 0) {
1067
+ validateRenderOptions({ headRadius: options.headRadius });
1068
+ }
1069
+ if (options.trailWidth !== void 0) {
1070
+ validateRenderOptions({ trailWidth: options.trailWidth });
1071
+ }
989
1072
  headRadius = options.headRadius ?? SVG_DEFAULT_HEAD_RADIUS;
1073
+ let trailWidth = options.trailWidth ?? 1;
990
1074
  container.setAttribute("viewBox", `0 0 ${viewSize} ${viewSize}`);
991
1075
  container.setAttribute("role", "img");
992
1076
  container.setAttribute("aria-label", ariaLabel);
@@ -1081,15 +1165,15 @@ function createSVGRenderer(options) {
1081
1165
  trailCount,
1082
1166
  px,
1083
1167
  py,
1084
- svgTrailMinWidth,
1085
- svgTrailMaxWidth
1168
+ svgTrailMinWidth * trailWidth,
1169
+ svgTrailMaxWidth * trailWidth
1086
1170
  );
1087
1171
  const d = `M${l0x.toFixed(2)} ${l0y.toFixed(2)} L${l1x.toFixed(2)} ${l1y.toFixed(2)} L${r1x.toFixed(2)} ${r1y.toFixed(2)} L${r0x.toFixed(2)} ${r0y.toFixed(2)} Z`;
1088
1172
  trailPaths[i].setAttribute("d", d);
1089
1173
  trailPaths[i].setAttribute("fill-opacity", opacity.toFixed(3));
1090
1174
  if (trailStyle !== "default") {
1091
1175
  const timeOffset = trailStyle === "gradient-animated" ? gradientAnimTime * 5e-4 : 0;
1092
- const { r, g, b } = getPaletteColor(trailPalette, progress, timeOffset);
1176
+ const { r, g, b } = oklabToRgb(getPaletteColor(trailPaletteOklab, progress, timeOffset));
1093
1177
  trailPaths[i].setAttribute("fill", `rgb(${r},${g},${b})`);
1094
1178
  }
1095
1179
  }
@@ -1241,6 +1325,7 @@ function createSVGRenderer(options) {
1241
1325
  trailColor = partial.trailColor;
1242
1326
  trailSolid = colorToRgbAttr(resolveTrailMainColor(trailColor));
1243
1327
  trailPalette = resolveTrailPalette(trailColor);
1328
+ trailPaletteOklab = trailPalette.map((c) => parseColorToOklab(c));
1244
1329
  if (trailStyle === "default") {
1245
1330
  for (const p of trailPaths) {
1246
1331
  p.setAttribute("fill", trailSolid);
@@ -1273,6 +1358,9 @@ function createSVGRenderer(options) {
1273
1358
  headRadius = partial.headRadius;
1274
1359
  headCircle.setAttribute("r", String(headRadius));
1275
1360
  }
1361
+ if (partial.trailWidth !== void 0) {
1362
+ trailWidth = partial.trailWidth;
1363
+ }
1276
1364
  if (userHeadColor === null) {
1277
1365
  headColor = resolveHeadColor(trailColor, trailStyle);
1278
1366
  } else {