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