@sarmal/core 0.14.0 → 0.15.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.
@@ -369,22 +369,6 @@ function enginePassthroughs(engine) {
369
369
  setSpeedOver: engine.setSpeedOver
370
370
  };
371
371
  }
372
- var GRADIENT = {
373
- bard: ["#a855f7", "#3b82f6", "#14b8a6", "#ec4899"],
374
- sunset: ["#f97316", "#dc2626", "#9333ea", "#f472b6"],
375
- ocean: ["#1e3a8a", "#06b6d4", "#22d3ee", "#e0f2fe"],
376
- ice: ["#1e3a8a", "#67e8f9"],
377
- fire: ["#7f1d1d", "#fbbf24"],
378
- forest: ["#14532d", "#86efac"]
379
- };
380
- var PRESETS = {
381
- bard: GRADIENT.bard,
382
- sunset: GRADIENT.sunset,
383
- ocean: GRADIENT.ocean,
384
- ice: GRADIENT.ice,
385
- fire: GRADIENT.fire,
386
- forest: GRADIENT.forest
387
- };
388
372
  function hexToRgb(hex) {
389
373
  const n = parseInt(hex.slice(1), 16);
390
374
  return { r: n >> 16, g: n >> 8 & 255, b: n & 255 };
@@ -409,18 +393,121 @@ function getPaletteColor(palette, position, timeOffset = 0) {
409
393
  const c2 = hexToRgb(palette[(idx + 1) % palette.length]);
410
394
  return lerpRgb(c1, c2, t);
411
395
  }
412
- function resolvePalette(palette, trailStyle) {
413
- if (Array.isArray(palette)) {
414
- return palette;
396
+ var HEX_COLOR_RE = /^#[0-9a-fA-F]{6}$/;
397
+ var TRAIL_STYLES = ["default", "gradient-static", "gradient-animated"];
398
+ var RENDER_OPTION_KEYS = /* @__PURE__ */ new Set([
399
+ "trailColor",
400
+ "headColor",
401
+ "skeletonColor",
402
+ "trailStyle"
403
+ ]);
404
+ function validateRenderOptions(partial) {
405
+ for (const key of Object.keys(partial)) {
406
+ if (!RENDER_OPTION_KEYS.has(key)) {
407
+ throw new TypeError(`[sarmal] setRenderOptions: unknown key "${key}"`);
408
+ }
409
+ }
410
+ if (partial.trailColor !== void 0) {
411
+ assertTrailColor(partial.trailColor);
415
412
  }
416
- if (palette && palette in PRESETS) {
417
- return PRESETS[palette];
413
+ if (partial.headColor !== void 0) {
414
+ assertHeadColor(partial.headColor);
415
+ }
416
+ if (partial.skeletonColor !== void 0) {
417
+ assertSkeletonColor(partial.skeletonColor);
418
+ }
419
+ if (partial.trailStyle !== void 0) {
420
+ assertTrailStyle(partial.trailStyle);
421
+ }
422
+ }
423
+ function assertTrailColor(value) {
424
+ if (typeof value === "string") {
425
+ if (!HEX_COLOR_RE.test(value)) {
426
+ throw new TypeError(
427
+ `[sarmal] setRenderOptions: trailColor must be a 6-digit hex string, got "${value}"`
428
+ );
429
+ }
430
+ return;
431
+ }
432
+ if (Array.isArray(value)) {
433
+ if (value.length < 2) {
434
+ throw new RangeError(
435
+ `[sarmal] setRenderOptions: trailColor array must have at least 2 entries, got ${value.length}`
436
+ );
437
+ }
438
+ for (let i = 0; i < value.length; i++) {
439
+ const entry = value[i];
440
+ if (typeof entry !== "string" || !HEX_COLOR_RE.test(entry)) {
441
+ throw new TypeError(
442
+ `[sarmal] setRenderOptions: trailColor[${i}] must be a 6-digit hex string, got ${JSON.stringify(entry)}`
443
+ );
444
+ }
445
+ }
446
+ return;
418
447
  }
419
- return trailStyle === "gradient-animated" ? GRADIENT.bard : GRADIENT.ice;
448
+ throw new TypeError(
449
+ `[sarmal] setRenderOptions: trailColor must be a 6-digit hex string or an array of hex strings, got ${JSON.stringify(value)}`
450
+ );
451
+ }
452
+ function assertHeadColor(value) {
453
+ if (value === null) {
454
+ return;
455
+ }
456
+ if (typeof value !== "string" || !HEX_COLOR_RE.test(value)) {
457
+ throw new TypeError(
458
+ `[sarmal] setRenderOptions: headColor must be a 6-digit hex string or null, got ${JSON.stringify(value)}`
459
+ );
460
+ }
461
+ }
462
+ function assertSkeletonColor(value) {
463
+ if (value === "transparent") {
464
+ return;
465
+ }
466
+ if (typeof value !== "string" || !HEX_COLOR_RE.test(value)) {
467
+ throw new TypeError(
468
+ `[sarmal] setRenderOptions: skeletonColor must be a 6-digit hex string or "transparent", got ${JSON.stringify(value)}`
469
+ );
470
+ }
471
+ }
472
+ function assertTrailStyle(value) {
473
+ if (!TRAIL_STYLES.includes(value)) {
474
+ throw new RangeError(
475
+ `[sarmal] setRenderOptions: trailStyle must be one of "default", "gradient-static", "gradient-animated", got ${JSON.stringify(value)}`
476
+ );
477
+ }
478
+ }
479
+ function resolveTrailMainColor(trailColor) {
480
+ return typeof trailColor === "string" ? trailColor : trailColor[0];
420
481
  }
482
+ function resolveTrailPalette(trailColor) {
483
+ return typeof trailColor === "string" ? [trailColor] : trailColor;
484
+ }
485
+ function resolveHeadColor(trailColor, trailStyle) {
486
+ if (trailStyle === "default") {
487
+ return resolveTrailMainColor(trailColor);
488
+ }
489
+ const palette = resolveTrailPalette(trailColor);
490
+ const last = palette[palette.length - 1];
491
+ const { r, g, b } = hexToRgb(last);
492
+ return `rgb(${r},${g},${b})`;
493
+ }
494
+ function warnIfTrailColorMismatch(trailColor, trailStyle) {
495
+ if (trailStyle === "default" && Array.isArray(trailColor)) {
496
+ console.warn(
497
+ '[sarmal] trailColor is an array but trailStyle is "default"; only the first color will be used. Pass a gradient trailStyle to use the whole palette.'
498
+ );
499
+ return;
500
+ }
501
+ if (trailStyle !== "default" && typeof trailColor === "string") {
502
+ console.warn(
503
+ `[sarmal] trailColor is a single color but trailStyle is "${trailStyle}"; the trail will render as a solid color. Pass an array of hex colors to use a real gradient.`
504
+ );
505
+ }
506
+ }
507
+ var getHeadDotRadius = (w, h) => Math.max(1, 3 * Math.sqrt(Math.min(w, h) / 160));
421
508
 
422
509
  // src/renderer.ts
423
- var DEFAULT_SKELETON_COLOR = "#ffffff";
510
+ var WHITE_HEX = "#ffffff";
424
511
  function hexToRgbComponents(hex) {
425
512
  const n = parseInt(hex.slice(1), 16);
426
513
  return `${n >> 16},${n >> 8 & 255},${n & 255}`;
@@ -438,27 +525,18 @@ function createRenderer(options) {
438
525
  }
439
526
  const ctx = canvas.getContext("2d");
440
527
  const engine = options.engine;
441
- const trailStyle = options.trailStyle ?? "default";
442
- const trailColor = options.trailColor ?? "#ffffff";
443
- const palette = resolvePalette(options.palette, trailStyle);
444
- function defaultHeadColor() {
445
- if (trailStyle !== "default") {
446
- const { r, g, b } = getPaletteColor(palette, 1);
447
- return `rgb(${r},${g},${b})`;
448
- }
449
- return trailColor;
450
- }
451
- const opts = {
452
- skeletonColor: options.skeletonColor ?? DEFAULT_SKELETON_COLOR,
453
- trailColor,
454
- headColor: options.headColor ?? defaultHeadColor()
455
- };
456
- const trailRgb = hexToRgbComponents(opts.trailColor);
528
+ let trailStyle = options.trailStyle ?? "default";
529
+ let trailColor = options.trailColor ?? WHITE_HEX;
530
+ let skeletonColor = options.skeletonColor ?? WHITE_HEX;
531
+ let userHeadColor = options.headColor ?? null;
532
+ let headColor = userHeadColor ?? resolveHeadColor(trailColor, trailStyle);
533
+ let trailSolidRgb = hexToRgbComponents(resolveTrailMainColor(trailColor));
534
+ let trailPalette = resolveTrailPalette(trailColor);
535
+ warnIfTrailColorMismatch(trailColor, trailStyle);
457
536
  const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
458
537
  function setupCanvas() {
459
- const rect = canvas.getBoundingClientRect();
460
- const lw = rect.width || 200;
461
- const lh = rect.height || 200;
538
+ const lw = canvas.offsetWidth || 200;
539
+ const lh = canvas.offsetHeight || 200;
462
540
  applyDprSizing(canvas, lw, lh, dpr);
463
541
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
464
542
  }
@@ -488,11 +566,13 @@ function createRenderer(options) {
488
566
  }
489
567
  }
490
568
  function buildSkeletonCanvas() {
491
- if (skeleton.length < 2) return;
569
+ if (skeleton.length < 2) {
570
+ return;
571
+ }
492
572
  skeletonCanvas = new OffscreenCanvas(canvas.width, canvas.height);
493
573
  const skeletonCtx = skeletonCanvas.getContext("2d");
494
574
  skeletonCtx.setTransform(dpr, 0, 0, dpr, 0, 0);
495
- skeletonCtx.strokeStyle = `rgba(${hexToRgbComponents(opts.skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;
575
+ skeletonCtx.strokeStyle = `rgba(${hexToRgbComponents(skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;
496
576
  skeletonCtx.lineWidth = 1.5;
497
577
  skeletonCtx.beginPath();
498
578
  const first = skeleton[0];
@@ -504,8 +584,10 @@ function createRenderer(options) {
504
584
  skeletonCtx.stroke();
505
585
  }
506
586
  function drawSkeletonPath(pts, opacity) {
507
- if (pts.length < 2) return;
508
- ctx.strokeStyle = `rgba(${hexToRgbComponents(opts.skeletonColor)},${opacity})`;
587
+ if (pts.length < 2) {
588
+ return;
589
+ }
590
+ ctx.strokeStyle = `rgba(${hexToRgbComponents(skeletonColor)},${opacity})`;
509
591
  ctx.lineWidth = 1.5;
510
592
  ctx.beginPath();
511
593
  ctx.moveTo(pts[0].x * scale + offsetX, pts[0].y * scale + offsetY);
@@ -515,7 +597,7 @@ function createRenderer(options) {
515
597
  ctx.stroke();
516
598
  }
517
599
  function drawSkeleton() {
518
- if (opts.skeletonColor === "transparent") {
600
+ if (skeletonColor === "transparent") {
519
601
  return;
520
602
  }
521
603
  if (engine.morphAlpha !== null) {
@@ -526,7 +608,7 @@ function createRenderer(options) {
526
608
  if (skeleton.length < 2) {
527
609
  return;
528
610
  }
529
- ctx.strokeStyle = `rgba(${hexToRgbComponents(opts.skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;
611
+ ctx.strokeStyle = `rgba(${hexToRgbComponents(skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;
530
612
  ctx.lineWidth = 1.5;
531
613
  ctx.beginPath();
532
614
  const first = skeleton[0];
@@ -555,10 +637,10 @@ function createRenderer(options) {
555
637
  toY
556
638
  );
557
639
  if (trailStyle === "default") {
558
- ctx.fillStyle = `rgba(${trailRgb},${opacity})`;
640
+ ctx.fillStyle = `rgba(${trailSolidRgb},${opacity})`;
559
641
  } else {
560
642
  const timeOffset = trailStyle === "gradient-animated" ? gradientAnimTime * 5e-4 : 0;
561
- const color = getPaletteColor(palette, progress, timeOffset);
643
+ const color = getPaletteColor(trailPalette, progress, timeOffset);
562
644
  ctx.fillStyle = `rgba(${color.r},${color.g},${color.b},${opacity})`;
563
645
  }
564
646
  ctx.beginPath();
@@ -576,8 +658,8 @@ function createRenderer(options) {
576
658
  }
577
659
  const x = head.x * scale + offsetX;
578
660
  const y = head.y * scale + offsetY;
579
- const r = options.headRadius ?? Math.max(2, 3 * Math.sqrt(Math.min(logicalWidth, logicalHeight) / 160));
580
- ctx.fillStyle = opts.headColor;
661
+ const r = options.headRadius ?? getHeadDotRadius(logicalWidth, logicalHeight);
662
+ ctx.fillStyle = headColor;
581
663
  ctx.beginPath();
582
664
  ctx.arc(x, y, r, 0, Math.PI * 2);
583
665
  ctx.fill();
@@ -677,6 +759,34 @@ function createRenderer(options) {
677
759
  return new Promise((resolve) => {
678
760
  morphResolve = resolve;
679
761
  });
762
+ },
763
+ setRenderOptions(partial) {
764
+ validateRenderOptions(partial);
765
+ if (partial.trailColor !== void 0) {
766
+ trailColor = partial.trailColor;
767
+ trailSolidRgb = hexToRgbComponents(resolveTrailMainColor(trailColor));
768
+ trailPalette = resolveTrailPalette(trailColor);
769
+ }
770
+ if (partial.skeletonColor !== void 0) {
771
+ skeletonColor = partial.skeletonColor;
772
+ if (skeletonColor !== "transparent" && !engine.isLiveSkeleton) {
773
+ buildSkeletonCanvas();
774
+ }
775
+ }
776
+ if (partial.trailStyle !== void 0) {
777
+ trailStyle = partial.trailStyle;
778
+ }
779
+ if (partial.headColor !== void 0) {
780
+ userHeadColor = partial.headColor;
781
+ }
782
+ if (userHeadColor === null) {
783
+ headColor = resolveHeadColor(trailColor, trailStyle);
784
+ } else {
785
+ headColor = userHeadColor;
786
+ }
787
+ if (partial.trailColor !== void 0 || partial.trailStyle !== void 0) {
788
+ warnIfTrailColorMismatch(trailColor, trailStyle);
789
+ }
680
790
  }
681
791
  };
682
792
  if (shouldAutoStart) {
@@ -880,7 +990,7 @@ function createSarmal(canvas, curveDef, options) {
880
990
  }
881
991
 
882
992
  // src/auto-init.ts
883
- function parsePalette(value) {
993
+ function parseTrailColor(value) {
884
994
  try {
885
995
  const parsed = JSON.parse(value);
886
996
  if (Array.isArray(parsed)) {
@@ -901,17 +1011,21 @@ function init() {
901
1011
  if (!curveDef) {
902
1012
  return console.error(`[sarmal] "${curveName}" is not a valid curve name`);
903
1013
  }
904
- createSarmal(canvas, curveDef, {
905
- ...canvas.dataset.trailColor && { trailColor: canvas.dataset.trailColor },
1014
+ const instance = createSarmal(canvas, curveDef, {
1015
+ ...canvas.dataset.trailColor && {
1016
+ trailColor: parseTrailColor(canvas.dataset.trailColor)
1017
+ },
906
1018
  ...canvas.dataset.skeletonColor && { skeletonColor: canvas.dataset.skeletonColor },
907
1019
  ...canvas.dataset.headColor && { headColor: canvas.dataset.headColor },
908
1020
  ...canvas.dataset.headRadius && { headRadius: parseFloat(canvas.dataset.headRadius) },
909
1021
  ...canvas.dataset.trailLength && { trailLength: parseInt(canvas.dataset.trailLength, 10) },
910
1022
  ...canvas.dataset.trailStyle && {
911
1023
  trailStyle: canvas.dataset.trailStyle
912
- },
913
- ...canvas.dataset.palette && { palette: parsePalette(canvas.dataset.palette) }
1024
+ }
914
1025
  });
1026
+ if (canvas.dataset.speed) {
1027
+ instance.setSpeed(parseFloat(canvas.dataset.speed));
1028
+ }
915
1029
  });
916
1030
  }
917
1031
  if (document.readyState === "loading") {
@@ -921,5 +1035,7 @@ if (document.readyState === "loading") {
921
1035
  } else {
922
1036
  requestAnimationFrame(init);
923
1037
  }
1038
+
1039
+ exports.init = init;
924
1040
  //# sourceMappingURL=auto-init.cjs.map
925
1041
  //# sourceMappingURL=auto-init.cjs.map