@5even7/dlc-ui 0.2.0 → 0.2.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/index.cjs CHANGED
@@ -148,6 +148,9 @@ function _objectSpread2(e) {
148
148
  }
149
149
  return e;
150
150
  }
151
+ function _readOnlyError(r) {
152
+ throw new TypeError('"' + r + '" is read-only');
153
+ }
151
154
  function _slicedToArray(r, e) {
152
155
  return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
153
156
  }
@@ -432,7 +435,7 @@ var DEFAULTS = {
432
435
  renderer: 'auto',
433
436
  respectReducedMotion: true,
434
437
  mouseColor: true,
435
- showCopy: true
438
+ textRatio: 39
436
439
  },
437
440
  progress: {
438
441
  width: 454,
@@ -441,43 +444,250 @@ var DEFAULTS = {
441
444
  max: 100,
442
445
  draggable: true,
443
446
  keyboard: true,
447
+ disabled: false,
448
+ valueSuffix: '%',
444
449
  edgeStyle: 'flow',
445
450
  quality: 'auto',
446
451
  renderer: 'auto',
447
- showCopy: true,
452
+ textRatio: 54,
453
+ showValue: true,
448
454
  respectReducedMotion: true
449
455
  }
450
456
  };
451
457
 
452
- /**
453
- * All user-facing copy lives here so wording/brand changes never require a
454
- * global search. Templates use {brand} {code} {name} placeholders.
458
+ /**
459
+ * Internal accessibility labels and small UI text. Since the copy API was
460
+ * removed (slots own the text area), these are no longer user-overridable.
461
+ * Templates use {brand} {code} {name} placeholders.
455
462
  */
456
463
  var COPY = {
457
464
  brandName: '画境观屿',
458
- dragLabel: 'DRAG',
459
465
  valueSuffix: '%',
460
466
  progressAria: '{brand} {code} 加载进度',
461
467
  capsuleAria: '打开 {name} 沉浸预览'
462
468
  };
463
469
 
464
- function hexToRgb01$1(hex) {
465
- var normalized = hex.replace('#', '');
466
- var value = Number.parseInt(normalized, 16);
470
+ /**
471
+ * Color helpers. `normalizeColor` converts any supported CSS color
472
+ * (hex / named / rgb() / rgba()) into a 6-digit hex string, so renderers can
473
+ * keep working with #rrggbb only. rgba() alpha is intentionally dropped:
474
+ * the WebGL shader has no per-color alpha channel, and the component is
475
+ * opaque by design — use component-level `opacity` for transparency.
476
+ */
477
+
478
+ var NAMED_COLORS = {
479
+ aliceblue: '#f0f8ff',
480
+ antiquewhite: '#faebd7',
481
+ aqua: '#00ffff',
482
+ aquamarine: '#7fffd4',
483
+ azure: '#f0ffff',
484
+ beige: '#f5f5dc',
485
+ bisque: '#ffe4c4',
486
+ black: '#000000',
487
+ blanchedalmond: '#ffebcd',
488
+ blue: '#0000ff',
489
+ blueviolet: '#8a2be2',
490
+ brown: '#a52a2a',
491
+ burlywood: '#deb887',
492
+ cadetblue: '#5f9ea0',
493
+ chartreuse: '#7fff00',
494
+ chocolate: '#d2691e',
495
+ coral: '#ff7f50',
496
+ cornflowerblue: '#6495ed',
497
+ cornsilk: '#fff8dc',
498
+ crimson: '#dc143c',
499
+ cyan: '#00ffff',
500
+ darkblue: '#00008b',
501
+ darkcyan: '#008b8b',
502
+ darkgoldenrod: '#b8860b',
503
+ darkgray: '#a9a9a9',
504
+ darkgreen: '#006400',
505
+ darkgrey: '#a9a9a9',
506
+ darkkhaki: '#bdb76b',
507
+ darkmagenta: '#8b008b',
508
+ darkolivegreen: '#556b2f',
509
+ darkorange: '#ff8c00',
510
+ darkorchid: '#9932cc',
511
+ darkred: '#8b0000',
512
+ darksalmon: '#e9967a',
513
+ darkseagreen: '#8fbc8f',
514
+ darkslateblue: '#483d8b',
515
+ darkslategray: '#2f4f4f',
516
+ darkslategrey: '#2f4f4f',
517
+ darkturquoise: '#00ced1',
518
+ darkviolet: '#9400d3',
519
+ deeppink: '#ff1493',
520
+ deepskyblue: '#00bfff',
521
+ dimgray: '#696969',
522
+ dimgrey: '#696969',
523
+ dodgerblue: '#1e90ff',
524
+ firebrick: '#b22222',
525
+ floralwhite: '#fffaf0',
526
+ forestgreen: '#228b22',
527
+ fuchsia: '#ff00ff',
528
+ gainsboro: '#dcdcdc',
529
+ ghostwhite: '#f8f8ff',
530
+ gold: '#ffd700',
531
+ goldenrod: '#daa520',
532
+ gray: '#808080',
533
+ green: '#008000',
534
+ greenyellow: '#adff2f',
535
+ grey: '#808080',
536
+ honeydew: '#f0fff0',
537
+ hotpink: '#ff69b4',
538
+ indianred: '#cd5c5c',
539
+ indigo: '#4b0082',
540
+ ivory: '#fffff0',
541
+ khaki: '#f0e68c',
542
+ lavender: '#e6e6fa',
543
+ lavenderblush: '#fff0f5',
544
+ lawngreen: '#7cfc00',
545
+ lemonchiffon: '#fffacd',
546
+ lightblue: '#add8e6',
547
+ lightcoral: '#f08080',
548
+ lightcyan: '#e0ffff',
549
+ lightgoldenrodyellow: '#fafad2',
550
+ lightgray: '#d3d3d3',
551
+ lightgreen: '#90ee90',
552
+ lightgrey: '#d3d3d3',
553
+ lightpink: '#ffb6c1',
554
+ lightsalmon: '#ffa07a',
555
+ lightseagreen: '#20b2aa',
556
+ lightskyblue: '#87cefa',
557
+ lightslategray: '#778899',
558
+ lightslategrey: '#778899',
559
+ lightsteelblue: '#b0c4de',
560
+ lightyellow: '#ffffe0',
561
+ lime: '#00ff00',
562
+ limegreen: '#32cd32',
563
+ linen: '#faf0e6',
564
+ magenta: '#ff00ff',
565
+ maroon: '#800000',
566
+ mediumaquamarine: '#66cdaa',
567
+ mediumblue: '#0000cd',
568
+ mediumorchid: '#ba55d3',
569
+ mediumpurple: '#9370db',
570
+ mediumseagreen: '#3cb371',
571
+ mediumslateblue: '#7b68ee',
572
+ mediumspringgreen: '#00fa9a',
573
+ mediumturquoise: '#48d1cc',
574
+ mediumvioletred: '#c71585',
575
+ midnightblue: '#191970',
576
+ mintcream: '#f5fffa',
577
+ mistyrose: '#ffe4e1',
578
+ moccasin: '#ffe4b5',
579
+ navajowhite: '#ffdead',
580
+ navy: '#000080',
581
+ oldlace: '#fdf5e6',
582
+ olive: '#808000',
583
+ olivedrab: '#6b8e23',
584
+ orange: '#ffa500',
585
+ orangered: '#ff4500',
586
+ orchid: '#da70d6',
587
+ palegoldenrod: '#eee8aa',
588
+ palegreen: '#98fb98',
589
+ paleturquoise: '#afeeee',
590
+ palevioletred: '#db7093',
591
+ papayawhip: '#ffefd5',
592
+ peachpuff: '#ffdab9',
593
+ peru: '#cd853f',
594
+ pink: '#ffc0cb',
595
+ plum: '#dda0dd',
596
+ powderblue: '#b0e0e6',
597
+ purple: '#800080',
598
+ rebeccapurple: '#663399',
599
+ red: '#ff0000',
600
+ rosybrown: '#bc8f8f',
601
+ royalblue: '#4169e1',
602
+ saddlebrown: '#8b4513',
603
+ salmon: '#fa8072',
604
+ sandybrown: '#f4a460',
605
+ seagreen: '#2e8b57',
606
+ seashell: '#fff5ee',
607
+ sienna: '#a0522d',
608
+ silver: '#c0c0c0',
609
+ skyblue: '#87ceeb',
610
+ slateblue: '#6a5acd',
611
+ slategray: '#708090',
612
+ slategrey: '#708090',
613
+ snow: '#fffafa',
614
+ springgreen: '#00ff7f',
615
+ steelblue: '#4682b4',
616
+ tan: '#d2b48c',
617
+ teal: '#008080',
618
+ thistle: '#d8bfd8',
619
+ tomato: '#ff6347',
620
+ turquoise: '#40e0d0',
621
+ violet: '#ee82ee',
622
+ wheat: '#f5deb3',
623
+ white: '#ffffff',
624
+ whitesmoke: '#f5f5f5',
625
+ yellow: '#ffff00',
626
+ yellowgreen: '#9acd32'
627
+ };
628
+ function clampChannel(value) {
629
+ return Math.min(255, Math.max(0, Math.round(value)));
630
+ }
631
+ function normalizeRgbArgs(args) {
632
+ var parts = args.split(/[,\s/]+/).filter(Boolean);
633
+ if (parts.length < 3) return null;
634
+ var to255 = function to255(value) {
635
+ if (typeof value !== 'string' || !value) return null;
636
+ if (value.endsWith('%')) return clampChannel(parseFloat(value) / 100 * 255);
637
+ var number = Number(value);
638
+ return Number.isFinite(number) ? clampChannel(number) : null;
639
+ };
640
+ var red = to255(parts[0]);
641
+ var green = to255(parts[1]);
642
+ var blue = to255(parts[2]);
643
+ if (red === null || green === null || blue === null) return null;
644
+ return "#".concat([red, green, blue].map(function (n) {
645
+ return n.toString(16).padStart(2, '0');
646
+ }).join(''));
647
+ }
648
+
649
+ /**
650
+ * Convert any supported CSS color into `#rrggbb`. Returns null when the
651
+ * value cannot be parsed. Supports: #rgb / #rrggbb (case-insensitive),
652
+ * CSS named colors, rgb() / rgba() with comma or modern space syntax,
653
+ * including percentages. Alpha in rgba() is ignored.
654
+ */
655
+ function normalizeColor(value) {
656
+ if (typeof value !== 'string') return null;
657
+ var input = value.trim();
658
+ if (!input) return null;
659
+ var lower = input.toLowerCase();
660
+ if (NAMED_COLORS[lower]) return NAMED_COLORS[lower];
661
+ if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(input)) {
662
+ var hex = input.slice(1).toLowerCase();
663
+ if (hex.length === 3) return "#".concat(hex.split('').map(function (c) {
664
+ return c + c;
665
+ }).join(''));
666
+ return "#".concat(hex);
667
+ }
668
+ var match = input.match(/^rgba?\((.*)\)$/i);
669
+ if (match) return normalizeRgbArgs(match[1]);
670
+ return null;
671
+ }
672
+ function hexToRgb01$1(color) {
673
+ var hex = normalizeColor(color);
674
+ if (!hex) return [0, 0, 0];
675
+ var value = Number.parseInt(hex.slice(1), 16);
467
676
  return [(value >> 16 & 255) / 255, (value >> 8 & 255) / 255, (value & 255) / 255];
468
677
  }
469
- function hexToRgba(hex) {
678
+ function hexToRgba(color) {
470
679
  var alpha = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
471
- var normalized = hex.replace('#', '');
472
- var value = Number.parseInt(normalized, 16);
680
+ var hex = normalizeColor(color);
681
+ if (!hex) return 'rgba(0, 0, 0, 0)';
682
+ var value = Number.parseInt(hex.slice(1), 16);
473
683
  var red = value >> 16 & 255;
474
684
  var green = value >> 8 & 255;
475
685
  var blue = value & 255;
476
686
  return "rgba(".concat(red, ", ").concat(green, ", ").concat(blue, ", ").concat(alpha, ")");
477
687
  }
478
688
  function validateColors(colors) {
479
- return Array.isArray(colors) && colors.length === 4 && colors.every(function (c) {
480
- return typeof c === 'string' && /^#[0-9a-f]{6}$/i.test(c);
689
+ return Array.isArray(colors) && colors.length === 4 && colors.every(function (color) {
690
+ return normalizeColor(color) !== null;
481
691
  });
482
692
  }
483
693
 
@@ -555,6 +765,26 @@ var CosmicRenderer = /*#__PURE__*/function () {
555
765
  this.options.dprCap = cap;
556
766
  this.resize();
557
767
  }
768
+ }, {
769
+ key: "setMouseColor",
770
+ value: function setMouseColor(enabled) {
771
+ this.options.mouseColor = enabled !== false;
772
+ if (this.options.mouseColor) {
773
+ if (this.onPointerMove) return this;
774
+ _assertClassBrand(_CosmicRenderer_brand, this, _bindEvents).call(this);
775
+ return this;
776
+ }
777
+ if (this.onPointerMove) {
778
+ var target = this.eventTarget;
779
+ target.removeEventListener('pointermove', this.onPointerMove);
780
+ target.removeEventListener('pointerdown', this.onPointerMove);
781
+ target.removeEventListener('pointerleave', this.onPointerLeave);
782
+ this.onPointerMove = null;
783
+ this.onPointerLeave = null;
784
+ }
785
+ this.motionTarget = 0;
786
+ return this;
787
+ }
558
788
  }, {
559
789
  key: "randomize",
560
790
  value: function randomize() {
@@ -703,6 +933,8 @@ var FallbackRenderer = /*#__PURE__*/function () {
703
933
  var _this$canvas = this.canvas,
704
934
  width = _this$canvas.width,
705
935
  height = _this$canvas.height;
936
+ var t = time * (this.preset.speed || 1);
937
+ var phase = (this.preset.seed || 0) * 0.7;
706
938
  var gradient = ctx.createLinearGradient(0, 0, width, height);
707
939
  gradient.addColorStop(0, this.preset.colors[0]);
708
940
  gradient.addColorStop(0.38, this.preset.colors[1]);
@@ -712,8 +944,8 @@ var FallbackRenderer = /*#__PURE__*/function () {
712
944
  ctx.fillRect(0, 0, width, height);
713
945
  ctx.globalCompositeOperation = 'screen';
714
946
  for (var index = 0; index < 6; index += 1) {
715
- var x = (0.5 + 0.45 * Math.sin(time * 0.32 + index * 1.7)) * width;
716
- var y = (0.5 + 0.4 * Math.cos(time * 0.25 + index)) * height;
947
+ var x = (0.5 + 0.45 * Math.sin(t * 0.32 + index * 1.7 + phase)) * width;
948
+ var y = (0.5 + 0.4 * Math.cos(t * 0.25 + index + phase)) * height;
717
949
  var radius = Math.max(width, height) * (0.08 + index % 3 * 0.04);
718
950
  var glow = ctx.createRadialGradient(x, y, 0, x, y, radius);
719
951
  glow.addColorStop(0, rgb(this.preset.colors[(index + 1) % 4], 0.24));
@@ -737,7 +969,12 @@ var FallbackRenderer = /*#__PURE__*/function () {
737
969
  }
738
970
  }, {
739
971
  key: "randomize",
740
- value: function randomize() {}
972
+ value: function randomize() {
973
+ if (this.preset) this.preset.seed = Math.random() * 100;
974
+ }
975
+ }, {
976
+ key: "setMouseColor",
977
+ value: function setMouseColor() {}
741
978
  }, {
742
979
  key: "dispose",
743
980
  value: function dispose() {}
@@ -855,16 +1092,34 @@ function createVisibilityGuard(element) {
855
1092
  };
856
1093
  }
857
1094
 
1095
+ /**
1096
+ * Run a callback asynchronously, right after the current task and before the
1097
+ * next paint. Used for mount-time events (ready / error) so listeners that
1098
+ * are attached after create() still receive them. Falls back for legacy
1099
+ * browsers that lack queueMicrotask / Promise.
1100
+ */
1101
+ function nextTick(fn) {
1102
+ if (typeof queueMicrotask === 'function') {
1103
+ queueMicrotask(fn);
1104
+ } else if (typeof Promise !== 'undefined' && typeof Promise.resolve === 'function') {
1105
+ Promise.resolve().then(fn);
1106
+ } else {
1107
+ setTimeout(fn, 0);
1108
+ }
1109
+ }
1110
+
858
1111
  function prefersReducedMotion$2() {
859
1112
  return typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches;
860
1113
  }
861
1114
 
862
1115
  /**
863
- * Mount a cosmic (nebula) capsule into `container`.
864
- *
865
- * Options: preset (id/code/name or object), width, height (number=px or
866
- * string with px/%/vw/vh/em/rem), colors, seed, speed, quality,
867
- * respectReducedMotion, copy, cssVars.
1116
+ * Mount a cosmic (nebula) capsule into `container`.
1117
+ *
1118
+ * Options: preset (literary name), width, height (number=px or string with
1119
+ * px/%/vw/vh/em/rem), colors, seed, speed, textRatio (0-100, text region
1120
+ * width in percent), text (HTML string or DOM nodes for the text slot),
1121
+ * colorContent (HTML string or DOM nodes for the color slot), quality,
1122
+ * renderer, respectReducedMotion, mouseColor, cssVars.
868
1123
  */
869
1124
  function createCapsule(container) {
870
1125
  var _options$preset;
@@ -874,51 +1129,71 @@ function createCapsule(container) {
874
1129
  }
875
1130
  var preset = _objectSpread2({}, getPreset('capsule', (_options$preset = options.preset) !== null && _options$preset !== void 0 ? _options$preset : '初光'));
876
1131
  var merged = normalizeOptions(DEFAULTS.capsule, preset, options);
877
- var copy = _objectSpread2(_objectSpread2({}, COPY), merged.copy || {});
1132
+ var normalizedColors = merged.colors.map(normalizeColor);
1133
+ if (normalizedColors.every(Boolean)) preset.colors = normalizedColors;
1134
+ preset.seed = merged.seed;
1135
+ preset.speed = merged.speed;
1136
+ var customLabel = typeof options.label === 'string' && options.label ? options.label : null;
878
1137
  var root = document.createElement('div');
879
1138
  root.className = 'hj-capsule-root hj-capsule-cosmic';
880
1139
  root.dataset.group = preset.group;
881
1140
  root.dataset.mode = 'nebula';
882
1141
  root.dataset.theme = preset.theme || 'light';
883
- root.setAttribute('aria-label', copy.capsuleAria.replace('{name}', preset.name));
884
- var copyEnabled = copy.enabled !== false && merged.showCopy !== false;
885
- var copyText = function copyText(key, fallback) {
886
- var value = copy[key];
887
- return value === undefined ? fallback : value;
888
- };
889
- function buildCopyHtml() {
890
- var codeText = copyText('code', preset.code);
891
- var nameText = copyText('name', preset.name);
892
- var subtitleText = copyText('subtitle', preset.subtitle);
893
- var stateText = copyText('state', 'LIVE COSMIC STUDY');
894
- var html = '';
895
- if (codeText) html += "<span class=\"hj-capsule-code\">".concat(codeText, "</span>");
896
- if (nameText) html += "<span class=\"hj-capsule-name\">".concat(nameText, "</span>");
897
- if (subtitleText) html += "<span class=\"hj-capsule-brand\">".concat(subtitleText, "</span>");
898
- if (stateText) html += "<span class=\"hj-capsule-state\">".concat(stateText, "</span>");
899
- return html;
900
- }
901
- var copyLayer = document.createElement('div');
902
- copyLayer.className = 'hj-capsule-copy';
903
- var renderCopy = function renderCopy() {
904
- if (copyEnabled) copyLayer.innerHTML = buildCopyHtml();
1142
+ root.setAttribute('role', 'img');
1143
+ var updateAria = function updateAria() {
1144
+ root.setAttribute('aria-label', customLabel || COPY.capsuleAria.replace('{name}', preset.name));
905
1145
  };
906
- renderCopy();
907
- var isHexColor = function isHexColor(value) {
908
- return typeof value === 'string' && /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(value);
1146
+ updateAria();
1147
+
1148
+ // Slot containers are always present but transparent by default: without
1149
+ // content they are invisible, so "no slot" behaves like the old
1150
+ // showCopy=false. They only provide geometry — no typography, padding or
1151
+ // background is imposed on user content (see docs: 插槽 CSS 约定).
1152
+ var fillContent = function fillContent(layer, content) {
1153
+ layer.innerHTML = '';
1154
+ if (content == null) return;
1155
+ if (typeof content === 'string') {
1156
+ layer.innerHTML = content;
1157
+ return;
1158
+ }
1159
+ var nodes = Array.isArray(content) ? content : [content];
1160
+ var _iterator = _createForOfIteratorHelper(nodes),
1161
+ _step;
1162
+ try {
1163
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
1164
+ var node = _step.value;
1165
+ if (node && typeof node.nodeType === 'number') layer.appendChild(node);
1166
+ }
1167
+ } catch (err) {
1168
+ _iterator.e(err);
1169
+ } finally {
1170
+ _iterator.f();
1171
+ }
909
1172
  };
910
- if (copy.background) {
911
- root.style.setProperty('--hj-copy-bg', isHexColor(copy.background) ? "linear-gradient(90deg, ".concat(copy.background, " 0%, ").concat(copy.background, " 74%, ").concat(hexToRgba(copy.background, 0.97), " 84%, ").concat(hexToRgba(copy.background, 0), " 100%)") : copy.background);
912
- }
913
- if (copy.color) root.style.setProperty('--hj-copy-color', copy.color);
1173
+ var textLayer = document.createElement('div');
1174
+ textLayer.className = 'hj-capsule-text hj-capsule-copy';
1175
+ var visualLayer = document.createElement('div');
1176
+ visualLayer.className = 'hj-capsule-visual';
1177
+ fillContent(textLayer, options.text);
1178
+ fillContent(visualLayer, options.colorContent);
914
1179
  var canvas = document.createElement('canvas');
915
1180
  canvas.className = 'hj-capsule-canvas';
916
1181
  canvas.setAttribute('aria-hidden', 'true');
917
- if (copyEnabled) root.appendChild(copyLayer);
1182
+ root.appendChild(textLayer);
1183
+ root.appendChild(visualLayer);
918
1184
  root.appendChild(canvas);
919
1185
  container.appendChild(root);
920
1186
  var emitter = createEmitter();
921
1187
  var paused = merged.respectReducedMotion && prefersReducedMotion$2();
1188
+ var disposed = false;
1189
+ var dirty = {
1190
+ preset: false,
1191
+ seed: false,
1192
+ speed: false,
1193
+ colors: false,
1194
+ textRatio: false,
1195
+ cssVars: false
1196
+ };
922
1197
  var renderer;
923
1198
  var useWebgl = merged.renderer !== 'canvas2d';
924
1199
  if (useWebgl) {
@@ -928,8 +1203,11 @@ function createCapsule(container) {
928
1203
  });
929
1204
  } catch (error) {
930
1205
  renderer = new FallbackRenderer(canvas, merged);
931
- emitter.emit('error', {
932
- message: String(error && error.message ? error.message : error)
1206
+ nextTick(function () {
1207
+ if (disposed) return;
1208
+ emitter.emit('error', {
1209
+ message: String(error && error.message ? error.message : error)
1210
+ });
933
1211
  });
934
1212
  }
935
1213
  } else {
@@ -943,6 +1221,9 @@ function createCapsule(container) {
943
1221
  var name = _Object$keys[_i];
944
1222
  root.style.setProperty(name, vars[name]);
945
1223
  }
1224
+ if (merged.textRatio !== undefined) {
1225
+ root.style.setProperty('--hj-text-width', "".concat(merged.textRatio, "%"));
1226
+ }
946
1227
  renderer.resize();
947
1228
  };
948
1229
  applySize();
@@ -992,13 +1273,16 @@ function createCapsule(container) {
992
1273
  }, function () {
993
1274
  return paused;
994
1275
  });
995
- emitter.emit('ready', {
996
- preset: _objectSpread2({}, preset)
1276
+ nextTick(function () {
1277
+ if (disposed) return;
1278
+ emitter.emit('ready', {
1279
+ preset: _objectSpread2({}, preset)
1280
+ });
997
1281
  });
998
- var syncCopy = function syncCopy() {
1282
+ var syncTheme = function syncTheme() {
999
1283
  root.dataset.mode = 'nebula';
1000
1284
  root.dataset.theme = preset.theme || 'light';
1001
- renderCopy();
1285
+ updateAria();
1002
1286
  };
1003
1287
  return {
1004
1288
  element: root,
@@ -1008,9 +1292,12 @@ function createCapsule(container) {
1008
1292
  off: emitter.off,
1009
1293
  setPreset: function setPreset(ref) {
1010
1294
  var next = getPreset('capsule', ref);
1295
+ dirty.preset = true;
1011
1296
  Object.assign(preset, next);
1012
- renderer.setPreset(next);
1013
- syncCopy();
1297
+ var nextColors = preset.colors.map(normalizeColor);
1298
+ if (nextColors.every(Boolean)) preset.colors = nextColors;
1299
+ renderer.setPreset(_objectSpread2({}, preset));
1300
+ syncTheme();
1014
1301
  renderer.resize();
1015
1302
  emitter.emit('presetchange', {
1016
1303
  preset: _objectSpread2({}, next)
@@ -1018,16 +1305,21 @@ function createCapsule(container) {
1018
1305
  return this;
1019
1306
  },
1020
1307
  setColors: function setColors(colors) {
1021
- if (!Array.isArray(colors) || colors.length !== 4) return this;
1022
- preset.colors = colors;
1308
+ var next = Array.isArray(colors) ? colors.map(normalizeColor) : [];
1309
+ if (next.length !== 4 || next.some(function (color) {
1310
+ return !color;
1311
+ })) return this;
1312
+ dirty.colors = true;
1313
+ preset.colors = next;
1023
1314
  renderer.setPreset(_objectSpread2(_objectSpread2({}, preset), {}, {
1024
- colors: colors
1315
+ colors: next
1025
1316
  }));
1026
1317
  return this;
1027
1318
  },
1028
1319
  setSeed: function setSeed(seed) {
1029
1320
  var value = Number(seed);
1030
1321
  if (!Number.isFinite(value)) return this;
1322
+ dirty.seed = true;
1031
1323
  preset.seed = value;
1032
1324
  renderer.setPreset(_objectSpread2({}, preset));
1033
1325
  return this;
@@ -1035,10 +1327,42 @@ function createCapsule(container) {
1035
1327
  setSpeed: function setSpeed(speed) {
1036
1328
  var value = Number(speed);
1037
1329
  if (!Number.isFinite(value)) return this;
1330
+ dirty.speed = true;
1038
1331
  preset.speed = value;
1039
1332
  renderer.setPreset(_objectSpread2({}, preset));
1040
1333
  return this;
1041
1334
  },
1335
+ setText: function setText(content) {
1336
+ fillContent(textLayer, content);
1337
+ return this;
1338
+ },
1339
+ setColorContent: function setColorContent(content) {
1340
+ fillContent(visualLayer, content);
1341
+ return this;
1342
+ },
1343
+ setTextRatio: function setTextRatio(ratio) {
1344
+ var value = Number(ratio);
1345
+ if (!Number.isFinite(value) || value < 0 || value > 100) return this;
1346
+ dirty.textRatio = true;
1347
+ merged.textRatio = value;
1348
+ root.style.setProperty('--hj-text-width', "".concat(value, "%"));
1349
+ return this;
1350
+ },
1351
+ setCssVars: function setCssVars(vars) {
1352
+ if (!vars || _typeof(vars) !== 'object') return this;
1353
+ dirty.cssVars = true;
1354
+ merged.cssVars = _objectSpread2(_objectSpread2({}, merged.cssVars || {}), vars);
1355
+ for (var _i2 = 0, _Object$keys2 = Object.keys(vars); _i2 < _Object$keys2.length; _i2++) {
1356
+ var name = _Object$keys2[_i2];
1357
+ root.style.setProperty(name, vars[name]);
1358
+ }
1359
+ return this;
1360
+ },
1361
+ setLabel: function setLabel(label) {
1362
+ customLabel = typeof label === 'string' && label ? label : null;
1363
+ updateAria();
1364
+ return this;
1365
+ },
1042
1366
  setSize: function setSize(width, height) {
1043
1367
  if (width !== undefined) merged.width = width;
1044
1368
  if (height !== undefined) merged.height = height;
@@ -1046,7 +1370,7 @@ function createCapsule(container) {
1046
1370
  return this;
1047
1371
  },
1048
1372
  randomize: function randomize() {
1049
- renderer.randomize();
1373
+ this.setSeed(Math.random() * 100);
1050
1374
  return this;
1051
1375
  },
1052
1376
  pause: function pause() {
@@ -1063,12 +1387,16 @@ function createCapsule(container) {
1063
1387
  return this;
1064
1388
  },
1065
1389
  dispose: function dispose() {
1390
+ disposed = true;
1066
1391
  unsubscribe();
1067
1392
  visibility.dispose();
1068
1393
  if (resizeObserver) resizeObserver.disconnect();else window.removeEventListener('resize', renderer.resize);
1069
1394
  renderer.dispose();
1070
1395
  root.remove();
1071
- }
1396
+ },
1397
+ textRatio: merged.textRatio,
1398
+ cssVars: merged.cssVars,
1399
+ dirty: dirty
1072
1400
  };
1073
1401
  }
1074
1402
 
@@ -1572,7 +1900,8 @@ var FLOW_PROFILES = {
1572
1900
  };
1573
1901
  var ProgressCapsuleController = /*#__PURE__*/function () {
1574
1902
  function ProgressCapsuleController(_ref) {
1575
- var _options$min,
1903
+ var _options$valueSuffix,
1904
+ _options$min,
1576
1905
  _options$max,
1577
1906
  _options$value,
1578
1907
  _this = this;
@@ -1582,7 +1911,8 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1582
1911
  preset = _ref.preset,
1583
1912
  emitter = _ref.emitter,
1584
1913
  options = _ref.options,
1585
- copy = _ref.copy;
1914
+ copy = _ref.copy,
1915
+ dirty = _ref.dirty;
1586
1916
  _classCallCheck(this, ProgressCapsuleController);
1587
1917
  this.root = root;
1588
1918
  this.canvas = canvas;
@@ -1591,6 +1921,8 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1591
1921
  this.emitter = emitter;
1592
1922
  this.options = options;
1593
1923
  this.copy = copy;
1924
+ this.dirty = dirty;
1925
+ this.valueSuffix = (_options$valueSuffix = options.valueSuffix) !== null && _options$valueSuffix !== void 0 ? _options$valueSuffix : copy.valueSuffix;
1594
1926
  this.profile = preset.edgeStyle === 'tide' ? FLOW_PROFILES.tide : FLOW_PROFILES[preset.id] || FLOW_PROFILES['visual-training'];
1595
1927
  this.min = (_options$min = options.min) !== null && _options$min !== void 0 ? _options$min : 0;
1596
1928
  this.max = (_options$max = options.max) !== null && _options$max !== void 0 ? _options$max : 100;
@@ -1630,12 +1962,24 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1630
1962
  var rounded = Math.round(this.value);
1631
1963
  this.root.style.setProperty('--progress', this.value.toFixed(2));
1632
1964
  this.root.setAttribute('aria-valuenow', String(rounded));
1633
- this.root.setAttribute('aria-valuetext', "".concat(rounded).concat(this.copy.valueSuffix));
1634
- this.valueElement.textContent = "".concat(rounded).concat(this.copy.valueSuffix);
1635
- if (!this.suppressEvents) this.emitter.emit('change', {
1636
- value: this.value,
1637
- source: source
1638
- });
1965
+ this.root.setAttribute('aria-valuetext', "".concat(rounded).concat(this.valueSuffix));
1966
+ if (this.valueElement) this.valueElement.textContent = "".concat(rounded).concat(this.valueSuffix);
1967
+ if (!this.suppressEvents) {
1968
+ if (this.dirty) this.dirty.value = true;
1969
+ this.emitter.emit('change', {
1970
+ value: this.value,
1971
+ source: source
1972
+ });
1973
+ }
1974
+ }
1975
+ }, {
1976
+ key: "setValueSuffix",
1977
+ value: function setValueSuffix(suffix) {
1978
+ this.valueSuffix = String(suffix == null ? '' : suffix);
1979
+ var rounded = Math.round(this.value);
1980
+ if (this.valueElement) this.valueElement.textContent = "".concat(rounded).concat(this.valueSuffix);
1981
+ this.root.setAttribute('aria-valuetext', "".concat(rounded).concat(this.valueSuffix));
1982
+ return this;
1639
1983
  }
1640
1984
  }, {
1641
1985
  key: "resizeCanvas",
@@ -1810,7 +2154,8 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1810
2154
  var width = this.width;
1811
2155
  var height = this.height;
1812
2156
  if (!ctx || width <= 0 || height <= 0) return;
1813
- var shoreline = width * (this.value / 100);
2157
+ var range = Math.max(this.max - this.min, 1);
2158
+ var shoreline = width * Math.min(Math.max((this.value - this.min) / range, 0), 1);
1814
2159
  var time = this.flowTime;
1815
2160
  var _this$preset$colors = _slicedToArray(this.preset.colors, 4),
1816
2161
  dark = _this$preset$colors[0],
@@ -1925,7 +2270,7 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1925
2270
  value: function updateFromPointer(event) {
1926
2271
  var bounds = this.root.getBoundingClientRect();
1927
2272
  var ratio = bounds.width > 0 ? (event.clientX - bounds.left) / bounds.width : 0;
1928
- this.setProgress(ratio * 100, 'drag');
2273
+ this.setProgress(this.min + ratio * (this.max - this.min), 'drag');
1929
2274
  }
1930
2275
  }, {
1931
2276
  key: "beginDrag",
@@ -1933,6 +2278,8 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1933
2278
  if (event.button !== undefined && event.button !== 0) return;
1934
2279
  event.preventDefault();
1935
2280
  this.dragging = true;
2281
+ this.pendingPointer = null;
2282
+ this._dragRaf = 0;
1936
2283
  this.root.classList.add('is-dragging');
1937
2284
  try {
1938
2285
  var _this$root$setPointer, _this$root;
@@ -1946,15 +2293,31 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1946
2293
  }, {
1947
2294
  key: "moveDrag",
1948
2295
  value: function moveDrag(event) {
2296
+ var _this2 = this;
1949
2297
  if (!this.dragging) return;
1950
2298
  event.preventDefault();
1951
- this.updateFromPointer(event);
2299
+ // rAF 合并:一帧最多一次 setProgress/change,视觉仍每帧更新。
2300
+ this.pendingPointer = event;
2301
+ if (this._dragRaf) return;
2302
+ this._dragRaf = requestAnimationFrame(function () {
2303
+ _this2._dragRaf = 0;
2304
+ var pending = _this2.pendingPointer;
2305
+ _this2.pendingPointer = null;
2306
+ if (pending) _this2.updateFromPointer(pending);
2307
+ });
1952
2308
  }
1953
2309
  }, {
1954
2310
  key: "endDrag",
1955
2311
  value: function endDrag(event) {
1956
2312
  if (!this.dragging) return;
1957
2313
  this.dragging = false;
2314
+ if (this._dragRaf) {
2315
+ cancelAnimationFrame(this._dragRaf);
2316
+ this._dragRaf = 0;
2317
+ }
2318
+ var pending = this.pendingPointer;
2319
+ this.pendingPointer = null;
2320
+ if (pending) this.updateFromPointer(pending);
1958
2321
  this.root.classList.remove('is-dragging');
1959
2322
  try {
1960
2323
  var _this$root$hasPointer, _this$root2;
@@ -1969,19 +2332,19 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1969
2332
  }, {
1970
2333
  key: "bindEvents",
1971
2334
  value: function bindEvents() {
1972
- var _this2 = this;
2335
+ var _this3 = this;
1973
2336
  if (this.options.draggable !== false) {
1974
2337
  this.handlers.pointerdown = function (event) {
1975
- return _this2.beginDrag(event);
2338
+ return _this3.beginDrag(event);
1976
2339
  };
1977
2340
  this.handlers.pointermove = function (event) {
1978
- return _this2.moveDrag(event);
2341
+ return _this3.moveDrag(event);
1979
2342
  };
1980
2343
  this.handlers.pointerup = function (event) {
1981
- return _this2.endDrag(event);
2344
+ return _this3.endDrag(event);
1982
2345
  };
1983
2346
  this.handlers.pointercancel = function (event) {
1984
- return _this2.endDrag(event);
2347
+ return _this3.endDrag(event);
1985
2348
  };
1986
2349
  this.root.addEventListener('pointerdown', this.handlers.pointerdown);
1987
2350
  this.root.addEventListener('pointermove', this.handlers.pointermove);
@@ -1990,23 +2353,24 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1990
2353
  }
1991
2354
  if (this.options.keyboard !== false) {
1992
2355
  this.handlers.keydown = function (event) {
2356
+ var step = (_this3.max - _this3.min) * 0.02;
1993
2357
  var actions = {
1994
- ArrowLeft: -2,
1995
- ArrowDown: -2,
1996
- ArrowRight: 2,
1997
- ArrowUp: 2,
1998
- PageDown: -10,
1999
- PageUp: 10
2358
+ ArrowLeft: -step,
2359
+ ArrowDown: -step,
2360
+ ArrowRight: step,
2361
+ ArrowUp: step,
2362
+ PageDown: -step * 5,
2363
+ PageUp: step * 5
2000
2364
  };
2001
2365
  if (event.key === 'Home') {
2002
2366
  event.preventDefault();
2003
- _this2.setProgress(_this2.min, 'keyboard');
2367
+ _this3.setProgress(_this3.min, 'keyboard');
2004
2368
  } else if (event.key === 'End') {
2005
2369
  event.preventDefault();
2006
- _this2.setProgress(_this2.max, 'keyboard');
2370
+ _this3.setProgress(_this3.max, 'keyboard');
2007
2371
  } else if (actions[event.key]) {
2008
2372
  event.preventDefault();
2009
- _this2.setProgress(_this2.value + actions[event.key], 'keyboard');
2373
+ _this3.setProgress(_this3.value + actions[event.key], 'keyboard');
2010
2374
  } else {
2011
2375
  return;
2012
2376
  }
@@ -2043,6 +2407,7 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
2043
2407
  }, {
2044
2408
  key: "dispose",
2045
2409
  value: function dispose() {
2410
+ if (this._dragRaf) cancelAnimationFrame(this._dragRaf);
2046
2411
  if (this.resizeObserver) this.resizeObserver.disconnect();else window.removeEventListener('resize', this.resizeCanvas);
2047
2412
  for (var _i = 0, _Object$keys = Object.keys(this.handlers); _i < _Object$keys.length; _i++) {
2048
2413
  var name = _Object$keys[_i];
@@ -2055,11 +2420,14 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
2055
2420
  }();
2056
2421
 
2057
2422
  /**
2058
- * Mount a fluid progress capsule into `container`.
2059
- *
2060
- * Options: preset (id/code/name or object), width, height, value, min, max,
2061
- * draggable, keyboard, colors, edgeStyle, quality,
2062
- * respectReducedMotion, copy, cssVars.
2423
+ * Mount a fluid progress capsule into `container`.
2424
+ *
2425
+ * Options: preset (id/code/name or object), width, height, value, min, max,
2426
+ * draggable, keyboard, colors, edgeStyle, textRatio (0-100, text region
2427
+ * width in percent), text (HTML string or DOM nodes for the text slot),
2428
+ * colorContent (HTML string or DOM nodes for the color slot),
2429
+ * showValue (show/hide the right-side percentage), quality,
2430
+ * respectReducedMotion, cssVars.
2063
2431
  */
2064
2432
  function createProgressCapsule(container) {
2065
2433
  var _options$preset, _merged$min, _merged$max;
@@ -2069,62 +2437,91 @@ function createProgressCapsule(container) {
2069
2437
  }
2070
2438
  var preset = _objectSpread2({}, getPreset('progress', (_options$preset = options.preset) !== null && _options$preset !== void 0 ? _options$preset : '星火'));
2071
2439
  var merged = normalizeOptions(DEFAULTS.progress, preset, options);
2072
- var copy = _objectSpread2(_objectSpread2({}, COPY), merged.copy || {});
2440
+ var normalizedColors = merged.colors.map(normalizeColor);
2441
+ if (normalizedColors.every(Boolean)) preset.colors = normalizedColors;
2442
+ preset.edgeStyle = merged.edgeStyle;
2443
+ var copy = COPY;
2444
+ var customLabel = typeof options.label === 'string' && options.label ? options.label : null;
2445
+ // Vue 的裸布尔属性(<ProgressCapsule disabled />)会传成空字符串,
2446
+ // 这里统一按 true 处理。
2447
+ var disabled = merged.disabled === true || merged.disabled === '' || merged.disabled === 'true';
2448
+ var effectiveDraggable = disabled ? false : merged.draggable !== false;
2449
+ var effectiveKeyboard = disabled ? false : merged.keyboard !== false;
2450
+ var dirty = {
2451
+ preset: false,
2452
+ colors: false,
2453
+ textRatio: false,
2454
+ cssVars: false,
2455
+ edgeStyle: false,
2456
+ value: false
2457
+ };
2073
2458
  var root = document.createElement('div');
2074
2459
  root.className = 'hj-capsule-root hj-progress-root';
2075
2460
  root.setAttribute('role', 'slider');
2076
2461
  root.setAttribute('tabindex', '0');
2077
- root.setAttribute('data-draggable', String(merged.draggable !== false));
2462
+ root.setAttribute('data-draggable', String(effectiveDraggable));
2463
+ if (disabled) {
2464
+ root.setAttribute('aria-disabled', 'true');
2465
+ root.classList.add('is-disabled');
2466
+ }
2078
2467
  root.setAttribute('aria-valuemin', String((_merged$min = merged.min) !== null && _merged$min !== void 0 ? _merged$min : 0));
2079
2468
  root.setAttribute('aria-valuemax', String((_merged$max = merged.max) !== null && _merged$max !== void 0 ? _merged$max : 100));
2080
2469
  root.setAttribute('aria-valuenow', String(preset.initialProgress));
2081
- root.setAttribute('aria-label', interpolate(copy.progressAria, {
2082
- brand: copy.brandName,
2083
- code: preset.code,
2084
- name: preset.name
2085
- }));
2086
- if (merged.keyboard === false) root.setAttribute('tabindex', '-1');
2470
+ var updateAria = function updateAria() {
2471
+ root.setAttribute('aria-label', customLabel || interpolate(copy.progressAria, {
2472
+ brand: copy.brandName,
2473
+ code: preset.code,
2474
+ name: preset.name
2475
+ }));
2476
+ };
2477
+ updateAria();
2478
+ if (!effectiveKeyboard) root.setAttribute('tabindex', '-1');
2087
2479
  var canvas = document.createElement('canvas');
2088
2480
  canvas.className = 'hj-progress-canvas';
2089
2481
  canvas.setAttribute('aria-hidden', 'true');
2090
- var copyEnabled = copy.enabled !== false && merged.showCopy !== false;
2091
- var copyText = function copyText(key, fallback) {
2092
- var value = copy[key];
2093
- return value === undefined ? fallback : value;
2094
- };
2095
- function buildCopyHtml() {
2096
- var html = '';
2097
- var brandText = copy.brandName || '';
2098
- var subtitleText = copyText('subtitle', preset.subtitle);
2099
- if (brandText) html += "<span class=\"hj-progress-name\">".concat(brandText, "</span>");
2100
- if (subtitleText) html += "<span class=\"hj-progress-subtitle\">".concat(subtitleText, "</span>");
2101
- return html;
2102
- }
2103
- var copyLayer = document.createElement('div');
2104
- copyLayer.className = 'hj-progress-copy';
2105
- var renderCopy = function renderCopy() {
2106
- if (copyEnabled) copyLayer.innerHTML = buildCopyHtml();
2482
+
2483
+ // Slot containers are always present but transparent by default; they only
2484
+ // provide geometry, never typography/padding/background (docs: 插槽 CSS 约定).
2485
+ var fillContent = function fillContent(layer, content) {
2486
+ layer.innerHTML = '';
2487
+ if (content == null) return;
2488
+ if (typeof content === 'string') {
2489
+ layer.innerHTML = content;
2490
+ return;
2491
+ }
2492
+ var nodes = Array.isArray(content) ? content : [content];
2493
+ var _iterator2 = _createForOfIteratorHelper(nodes),
2494
+ _step2;
2495
+ try {
2496
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
2497
+ var node = _step2.value;
2498
+ if (node && typeof node.nodeType === 'number') layer.appendChild(node);
2499
+ }
2500
+ } catch (err) {
2501
+ _iterator2.e(err);
2502
+ } finally {
2503
+ _iterator2.f();
2504
+ }
2107
2505
  };
2108
- renderCopy();
2109
- if (copy.background) {
2110
- root.style.setProperty('--hj-progress-copy-bg', copy.background);
2111
- root.style.setProperty('--hj-progress-copy-pad', '6px 12px');
2506
+ var textLayer = document.createElement('div');
2507
+ textLayer.className = 'hj-progress-text hj-progress-copy';
2508
+ var visualLayer = document.createElement('div');
2509
+ visualLayer.className = 'hj-progress-visual';
2510
+ fillContent(textLayer, options.text);
2511
+ fillContent(visualLayer, options.colorContent);
2512
+ var valueElement = merged.showValue === false ? null : document.createElement('div');
2513
+ if (valueElement) {
2514
+ valueElement.className = 'hj-progress-value';
2515
+ valueElement.setAttribute('aria-hidden', 'true');
2112
2516
  }
2113
- if (copy.color) root.style.setProperty('--hj-progress-copy-color', copy.color);
2114
- var valueElement = document.createElement('div');
2115
- valueElement.className = 'hj-progress-value';
2116
- valueElement.setAttribute('aria-hidden', 'true');
2117
- var dragLabel = document.createElement('span');
2118
- dragLabel.className = 'hj-progress-drag-label';
2119
- dragLabel.setAttribute('aria-hidden', 'true');
2120
- dragLabel.textContent = copy.dragLabel;
2121
2517
  root.appendChild(canvas);
2122
- if (copyEnabled) root.appendChild(copyLayer);
2123
- root.appendChild(valueElement);
2124
- root.appendChild(dragLabel);
2518
+ root.appendChild(textLayer);
2519
+ root.appendChild(visualLayer);
2520
+ if (valueElement) root.appendChild(valueElement);
2125
2521
  container.appendChild(root);
2126
2522
  var emitter = createEmitter();
2127
2523
  var paused = merged.respectReducedMotion && prefersReducedMotion$1();
2524
+ var disposed = false;
2128
2525
  var applySize = function applySize() {
2129
2526
  root.style.width = parseSize(merged.width);
2130
2527
  root.style.height = parseSize(merged.height);
@@ -2133,6 +2530,9 @@ function createProgressCapsule(container) {
2133
2530
  var name = _Object$keys2[_i2];
2134
2531
  root.style.setProperty(name, vars[name]);
2135
2532
  }
2533
+ if (merged.textRatio !== undefined) {
2534
+ root.style.setProperty('--hj-text-width', "".concat(merged.textRatio, "%"));
2535
+ }
2136
2536
  };
2137
2537
  applySize();
2138
2538
  var controller = new ProgressCapsuleController({
@@ -2141,8 +2541,12 @@ function createProgressCapsule(container) {
2141
2541
  valueElement: valueElement,
2142
2542
  preset: preset,
2143
2543
  emitter: emitter,
2144
- options: merged,
2145
- copy: copy
2544
+ options: _objectSpread2(_objectSpread2({}, merged), {}, {
2545
+ draggable: effectiveDraggable,
2546
+ keyboard: effectiveKeyboard
2547
+ }),
2548
+ copy: copy,
2549
+ dirty: dirty
2146
2550
  });
2147
2551
  var overlay = null;
2148
2552
  if (merged.renderer !== 'canvas2d') {
@@ -2155,7 +2559,14 @@ function createProgressCapsule(container) {
2155
2559
  }
2156
2560
  });
2157
2561
  }
2158
- if (overlay) controller.webglActive = true;
2562
+ if (overlay) controller.webglActive = true;else if (merged.renderer !== 'canvas2d') {
2563
+ nextTick(function () {
2564
+ if (disposed) return;
2565
+ emitter.emit('error', {
2566
+ message: 'WebGL2 unavailable, using Canvas2D fallback'
2567
+ });
2568
+ });
2569
+ }
2159
2570
  var visibility = createVisibilityGuard(root);
2160
2571
  var flowTime = 0;
2161
2572
  var unsubscribe = subscribeScheduler(function (delta, now) {
@@ -2168,16 +2579,14 @@ function createProgressCapsule(container) {
2168
2579
  }, function () {
2169
2580
  return paused;
2170
2581
  });
2171
- emitter.emit('ready', {
2172
- preset: _objectSpread2({}, preset)
2582
+ nextTick(function () {
2583
+ if (disposed) return;
2584
+ emitter.emit('ready', {
2585
+ preset: _objectSpread2({}, preset)
2586
+ });
2173
2587
  });
2174
2588
  var syncDom = function syncDom() {
2175
- root.setAttribute('aria-label', interpolate(copy.progressAria, {
2176
- brand: copy.brandName,
2177
- code: preset.code,
2178
- name: preset.name
2179
- }));
2180
- renderCopy();
2589
+ updateAria();
2181
2590
  };
2182
2591
  return {
2183
2592
  element: root,
@@ -2199,8 +2608,11 @@ function createProgressCapsule(container) {
2199
2608
  },
2200
2609
  setPreset: function setPreset(ref) {
2201
2610
  var next = getPreset('progress', ref);
2611
+ dirty.preset = true;
2202
2612
  Object.assign(preset, next);
2203
- controller.preset = next;
2613
+ var nextColors = preset.colors.map(normalizeColor);
2614
+ if (nextColors.every(Boolean)) preset.colors = nextColors;
2615
+ controller.preset = preset;
2204
2616
  controller.profile = next.edgeStyle === 'tide' ? FLOW_PROFILES.tide : FLOW_PROFILES[next.id] || FLOW_PROFILES['visual-training'];
2205
2617
  controller.flowTime = stringSeed(next.id) * 31;
2206
2618
  controller.seed = stringSeed("".concat(next.id, "-reference")) * Math.PI * 2;
@@ -2210,7 +2622,7 @@ function createProgressCapsule(container) {
2210
2622
  overlay = attachProgressFlowOverlay({
2211
2623
  root: root,
2212
2624
  canvas: canvas,
2213
- preset: next,
2625
+ preset: preset,
2214
2626
  getProgress: function getProgress() {
2215
2627
  return controller.value;
2216
2628
  }
@@ -2219,13 +2631,14 @@ function createProgressCapsule(container) {
2219
2631
  controller.webglActive = Boolean(overlay);
2220
2632
  controller.resizeCanvas();
2221
2633
  emitter.emit('presetchange', {
2222
- preset: _objectSpread2({}, next)
2634
+ preset: _objectSpread2({}, preset)
2223
2635
  });
2224
2636
  return this;
2225
2637
  },
2226
2638
  setEdgeStyle: function setEdgeStyle(edgeStyle) {
2227
2639
  var value = String(edgeStyle || '').toLowerCase();
2228
2640
  if (value !== 'flow' && value !== 'tide') return this;
2641
+ dirty.edgeStyle = true;
2229
2642
  preset.edgeStyle = value;
2230
2643
  controller.profile = value === 'tide' ? FLOW_PROFILES.tide : FLOW_PROFILES[preset.id] || FLOW_PROFILES['visual-training'];
2231
2644
  if (overlay) {
@@ -2243,10 +2656,49 @@ function createProgressCapsule(container) {
2243
2656
  controller.resizeCanvas();
2244
2657
  return this;
2245
2658
  },
2659
+ setText: function setText(content) {
2660
+ fillContent(textLayer, content);
2661
+ return this;
2662
+ },
2663
+ setColorContent: function setColorContent(content) {
2664
+ fillContent(visualLayer, content);
2665
+ return this;
2666
+ },
2667
+ setTextRatio: function setTextRatio(ratio) {
2668
+ var value = Number(ratio);
2669
+ if (!Number.isFinite(value) || value < 0 || value > 100) return this;
2670
+ dirty.textRatio = true;
2671
+ merged.textRatio = value;
2672
+ root.style.setProperty('--hj-text-width', "".concat(value, "%"));
2673
+ return this;
2674
+ },
2675
+ setCssVars: function setCssVars(vars) {
2676
+ if (!vars || _typeof(vars) !== 'object') return this;
2677
+ dirty.cssVars = true;
2678
+ merged.cssVars = _objectSpread2(_objectSpread2({}, merged.cssVars || {}), vars);
2679
+ for (var _i3 = 0, _Object$keys3 = Object.keys(vars); _i3 < _Object$keys3.length; _i3++) {
2680
+ var name = _Object$keys3[_i3];
2681
+ root.style.setProperty(name, vars[name]);
2682
+ }
2683
+ return this;
2684
+ },
2685
+ setLabel: function setLabel(label) {
2686
+ _readOnlyError("customLabel");
2687
+ updateAria();
2688
+ return this;
2689
+ },
2690
+ setValueSuffix: function setValueSuffix(suffix) {
2691
+ controller.setValueSuffix(suffix);
2692
+ return this;
2693
+ },
2246
2694
  setColors: function setColors(colors) {
2247
- if (!Array.isArray(colors) || colors.length !== 4) return this;
2248
- controller.setColors(colors);
2249
- if (overlay) overlay.setColors(colors);
2695
+ var next = Array.isArray(colors) ? colors.map(normalizeColor) : [];
2696
+ if (next.length !== 4 || next.some(function (color) {
2697
+ return !color;
2698
+ })) return this;
2699
+ dirty.colors = true;
2700
+ controller.setColors(next);
2701
+ if (overlay) overlay.setColors(next);
2250
2702
  controller.resizeCanvas();
2251
2703
  return this;
2252
2704
  },
@@ -2276,12 +2728,16 @@ function createProgressCapsule(container) {
2276
2728
  return this;
2277
2729
  },
2278
2730
  dispose: function dispose() {
2731
+ disposed = true;
2279
2732
  unsubscribe();
2280
2733
  visibility.dispose();
2281
2734
  if (overlay) overlay.dispose();
2282
2735
  controller.dispose();
2283
2736
  root.remove();
2284
- }
2737
+ },
2738
+ textRatio: merged.textRatio,
2739
+ cssVars: merged.cssVars,
2740
+ dirty: dirty
2285
2741
  };
2286
2742
  }
2287
2743
 
@@ -2317,6 +2773,10 @@ function createColorBackground(host) {
2317
2773
  opacity: 1,
2318
2774
  fallbackColor: true
2319
2775
  }, preset, options);
2776
+ var normalizedColors = merged.colors.map(normalizeColor);
2777
+ if (normalizedColors.every(Boolean)) preset.colors = normalizedColors;
2778
+ preset.seed = merged.seed;
2779
+ preset.speed = merged.speed;
2320
2780
  var computed = getComputedStyle(host);
2321
2781
  if (computed.position === 'static' || computed.position === '') {
2322
2782
  host.style.position = 'relative';
@@ -2337,6 +2797,16 @@ function createColorBackground(host) {
2337
2797
  host.appendChild(layer);
2338
2798
  var emitter = createEmitter();
2339
2799
  var paused = merged.respectReducedMotion && prefersReducedMotion();
2800
+ var disposed = false;
2801
+ var dirty = {
2802
+ preset: false,
2803
+ seed: false,
2804
+ speed: false,
2805
+ colors: false,
2806
+ opacity: false,
2807
+ fallbackColor: false,
2808
+ mouseColor: false
2809
+ };
2340
2810
  var renderer;
2341
2811
  var useWebgl = merged.renderer !== 'canvas2d';
2342
2812
  if (useWebgl) {
@@ -2348,8 +2818,11 @@ function createColorBackground(host) {
2348
2818
  });
2349
2819
  } catch (error) {
2350
2820
  renderer = new FallbackRenderer(canvas, merged);
2351
- emitter.emit('error', {
2352
- message: String(error && error.message ? error.message : error)
2821
+ nextTick(function () {
2822
+ if (disposed) return;
2823
+ emitter.emit('error', {
2824
+ message: String(error && error.message ? error.message : error)
2825
+ });
2353
2826
  });
2354
2827
  }
2355
2828
  } else {
@@ -2367,8 +2840,11 @@ function createColorBackground(host) {
2367
2840
  }, function () {
2368
2841
  return paused;
2369
2842
  });
2370
- emitter.emit('ready', {
2371
- preset: _objectSpread2({}, preset)
2843
+ nextTick(function () {
2844
+ if (disposed) return;
2845
+ emitter.emit('ready', {
2846
+ preset: _objectSpread2({}, preset)
2847
+ });
2372
2848
  });
2373
2849
  return {
2374
2850
  element: host,
@@ -2379,24 +2855,59 @@ function createColorBackground(host) {
2379
2855
  off: emitter.off,
2380
2856
  setPreset: function setPreset(ref) {
2381
2857
  var next = getPreset('capsule', ref);
2858
+ dirty.preset = true;
2382
2859
  Object.assign(preset, next);
2383
- renderer.setPreset(next);
2860
+ var nextColors = preset.colors.map(normalizeColor);
2861
+ if (nextColors.every(Boolean)) preset.colors = nextColors;
2862
+ renderer.setPreset(_objectSpread2({}, preset));
2384
2863
  renderer.resize();
2385
2864
  syncLayerVars();
2386
2865
  emitter.emit('presetchange', {
2387
- preset: _objectSpread2({}, next)
2866
+ preset: _objectSpread2({}, preset)
2388
2867
  });
2389
2868
  return this;
2390
2869
  },
2391
2870
  setColors: function setColors(colors) {
2392
- if (!Array.isArray(colors) || colors.length !== 4) return this;
2393
- preset.colors = colors;
2871
+ var next = Array.isArray(colors) ? colors.map(normalizeColor) : [];
2872
+ if (next.length !== 4 || next.some(function (color) {
2873
+ return !color;
2874
+ })) return this;
2875
+ dirty.colors = true;
2876
+ preset.colors = next;
2394
2877
  renderer.setPreset(_objectSpread2(_objectSpread2({}, preset), {}, {
2395
- colors: colors
2878
+ colors: next
2396
2879
  }));
2397
2880
  syncLayerVars();
2398
2881
  return this;
2399
2882
  },
2883
+ setSeed: function setSeed(seed) {
2884
+ var value = Number(seed);
2885
+ if (!Number.isFinite(value)) return this;
2886
+ dirty.seed = true;
2887
+ preset.seed = value;
2888
+ renderer.setPreset(_objectSpread2({}, preset));
2889
+ return this;
2890
+ },
2891
+ setSpeed: function setSpeed(speed) {
2892
+ var value = Number(speed);
2893
+ if (!Number.isFinite(value)) return this;
2894
+ dirty.speed = true;
2895
+ preset.speed = value;
2896
+ renderer.setPreset(_objectSpread2({}, preset));
2897
+ return this;
2898
+ },
2899
+ setFallbackColor: function setFallbackColor(value) {
2900
+ dirty.fallbackColor = true;
2901
+ merged.fallbackColor = value;
2902
+ syncLayerVars();
2903
+ return this;
2904
+ },
2905
+ setMouseColor: function setMouseColor(value) {
2906
+ dirty.mouseColor = true;
2907
+ merged.mouseColor = value !== false;
2908
+ if (typeof renderer.setMouseColor === 'function') renderer.setMouseColor(merged.mouseColor);
2909
+ return this;
2910
+ },
2400
2911
  setQuality: function setQuality(quality) {
2401
2912
  merged.quality = quality;
2402
2913
  if (typeof renderer.setDprCap === 'function') renderer.setDprCap(dprCapFor(quality));
@@ -2405,12 +2916,13 @@ function createColorBackground(host) {
2405
2916
  setOpacity: function setOpacity(value) {
2406
2917
  var opacity = Number(value);
2407
2918
  if (!Number.isFinite(opacity)) return this;
2919
+ dirty.opacity = true;
2408
2920
  merged.opacity = Math.min(1, Math.max(0, opacity));
2409
2921
  layer.style.setProperty('--dlc-color-opacity', String(merged.opacity));
2410
2922
  return this;
2411
2923
  },
2412
2924
  randomize: function randomize() {
2413
- renderer.randomize();
2925
+ this.setSeed(Math.random() * 100);
2414
2926
  return this;
2415
2927
  },
2416
2928
  pause: function pause() {
@@ -2422,12 +2934,17 @@ function createColorBackground(host) {
2422
2934
  return this;
2423
2935
  },
2424
2936
  dispose: function dispose() {
2937
+ disposed = true;
2425
2938
  unsubscribe();
2426
2939
  visibility.dispose();
2427
2940
  if (resizeObserver) resizeObserver.disconnect();else window.removeEventListener('resize', renderer.resize);
2428
2941
  renderer.dispose();
2429
2942
  layer.remove();
2430
- }
2943
+ },
2944
+ opacity: merged.opacity,
2945
+ fallbackColor: merged.fallbackColor,
2946
+ mouseColor: merged.mouseColor,
2947
+ dirty: dirty
2431
2948
  };
2432
2949
  }
2433
2950
 
@@ -2448,6 +2965,7 @@ exports.dprCapFor = dprCapFor;
2448
2965
  exports.getPreset = getPreset;
2449
2966
  exports.hexToRgb01 = hexToRgb01$1;
2450
2967
  exports.hexToRgba = hexToRgba;
2968
+ exports.normalizeColor = normalizeColor;
2451
2969
  exports.parseSize = parseSize;
2452
2970
  exports.pauseAll = pauseAll;
2453
2971
  exports.resumeAll = resumeAll;