@5even7/dlc-ui 0.1.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/capsule.cjs CHANGED
@@ -317,7 +317,7 @@ var PALETTES = {
317
317
  var CAPSULE_PRESETS = [{
318
318
  id: 'original',
319
319
  code: 'NC-01',
320
- name: 'ORIGINAL',
320
+ name: '初光',
321
321
  group: 'warm',
322
322
  seed: 1.7,
323
323
  speed: 0.5,
@@ -325,7 +325,7 @@ var CAPSULE_PRESETS = [{
325
325
  }, {
326
326
  id: 'ocean',
327
327
  code: 'NC-02',
328
- name: 'OCEAN',
328
+ name: '沧溟',
329
329
  group: 'cold',
330
330
  seed: 8.2,
331
331
  speed: 0.48,
@@ -333,7 +333,7 @@ var CAPSULE_PRESETS = [{
333
333
  }, {
334
334
  id: 'klein',
335
335
  code: 'NC-03',
336
- name: 'KLEIN',
336
+ name: '玄夜',
337
337
  group: 'cold',
338
338
  seed: 14.1,
339
339
  speed: 0.49,
@@ -341,7 +341,7 @@ var CAPSULE_PRESETS = [{
341
341
  }, {
342
342
  id: 'ultraviolet',
343
343
  code: 'NC-04',
344
- name: 'ULTRAVIOLET',
344
+ name: '紫霄',
345
345
  group: 'cold',
346
346
  seed: 23.4,
347
347
  speed: 0.47,
@@ -349,7 +349,7 @@ var CAPSULE_PRESETS = [{
349
349
  }, {
350
350
  id: 'chrome',
351
351
  code: 'NC-05',
352
- name: 'CHROME',
352
+ name: '银汉',
353
353
  group: 'cold',
354
354
  seed: 37.8,
355
355
  speed: 0.42,
@@ -357,7 +357,7 @@ var CAPSULE_PRESETS = [{
357
357
  }, {
358
358
  id: 'plus',
359
359
  code: 'NC-06',
360
- name: 'PLUS',
360
+ name: '熔金',
361
361
  group: 'warm',
362
362
  seed: 51.3,
363
363
  speed: 0.5,
@@ -367,7 +367,7 @@ var CAPSULE_PRESETS = [{
367
367
  var PROGRESS_PRESETS = [{
368
368
  id: 'model-training',
369
369
  code: 'NC-10',
370
- name: 'MODEL TRAINING',
370
+ name: '星火',
371
371
  subtitle: 'SHA 4.5 + 100 2026 TKN',
372
372
  group: 'progress',
373
373
  initialProgress: 43,
@@ -376,7 +376,7 @@ var PROGRESS_PRESETS = [{
376
376
  }, {
377
377
  id: 'agent-migration',
378
378
  code: 'NC-11',
379
- name: 'AGENT MIGRATION',
379
+ name: '迁流',
380
380
  subtitle: 'TRANSFERRING PROTOCOL',
381
381
  group: 'progress',
382
382
  initialProgress: 33,
@@ -385,7 +385,7 @@ var PROGRESS_PRESETS = [{
385
385
  }, {
386
386
  id: 'visual-training',
387
387
  code: 'NC-12',
388
- name: 'VISUAL TRAINING',
388
+ name: '幻境',
389
389
  subtitle: 'GENERATING POWER ++',
390
390
  group: 'progress',
391
391
  initialProgress: 58,
@@ -394,7 +394,7 @@ var PROGRESS_PRESETS = [{
394
394
  }, {
395
395
  id: 'tide',
396
396
  code: 'NC-13',
397
- name: 'TIDE',
397
+ name: '汐潮',
398
398
  subtitle: 'MOON PULL / COAST',
399
399
  group: 'progress',
400
400
  initialProgress: 30,
@@ -403,20 +403,18 @@ var PROGRESS_PRESETS = [{
403
403
  }];
404
404
 
405
405
  [].concat(_toConsumableArray(CAPSULE_PRESETS), _toConsumableArray(PROGRESS_PRESETS));
406
- function validatePreset(preset) {
407
- return Boolean(preset && typeof preset.id === 'string' && typeof preset.code === 'string' && typeof preset.name === 'string' && Number.isFinite(preset.seed) && Number.isFinite(preset.speed) && Array.isArray(preset.colors) && preset.colors.length === 4);
408
- }
406
+
407
+ /**
408
+ * Resolve a preset by its literary name (e.g. '沧溟').
409
+ * Matching is case-insensitive and ignores surrounding whitespace,
410
+ * so '沧溟' and ' 沧溟 ' both work.
411
+ */
409
412
  function getPreset(kind, ref) {
410
413
  var list = CAPSULE_PRESETS ;
411
414
  if (!list) throw new Error("Unknown preset kind: ".concat(kind, " (use \"capsule\" or \"progress\")"));
412
- if (ref && _typeof(ref) === 'object') {
413
- var valid = validatePreset(ref) ;
414
- if (!valid) throw new Error("Invalid ".concat(kind, " preset object"));
415
- return ref;
416
- }
417
415
  var key = String(ref).trim().toLowerCase();
418
416
  var found = list.find(function (preset) {
419
- return preset.id.toLowerCase() === key || preset.code.toLowerCase() === key || preset.name.toLowerCase() === key;
417
+ return preset.name.toLowerCase() === key;
420
418
  });
421
419
  if (!found) throw new Error("Unknown ".concat(kind, " preset: ").concat(ref));
422
420
  return found;
@@ -429,36 +427,226 @@ var DEFAULTS = {
429
427
  quality: 'auto',
430
428
  renderer: 'auto',
431
429
  respectReducedMotion: true,
432
- interactive: true,
433
430
  mouseColor: true,
434
- showCopy: true
431
+ textRatio: 39
435
432
  }};
436
433
 
437
- /**
438
- * All user-facing copy lives here so wording/brand changes never require a
439
- * global search. Templates use {brand} {code} {name} placeholders.
434
+ /**
435
+ * Internal accessibility labels and small UI text. Since the copy API was
436
+ * removed (slots own the text area), these are no longer user-overridable.
437
+ * Templates use {brand} {code} {name} placeholders.
440
438
  */
441
439
  var COPY = {
442
- brandName: '画境观屿',
443
- dragLabel: 'DRAG',
444
- valueSuffix: '%',
445
- progressAria: '{brand} {code} 加载进度',
446
440
  capsuleAria: '打开 {name} 沉浸预览'
447
441
  };
448
442
 
449
- function hexToRgb01(hex) {
450
- var normalized = hex.replace('#', '');
451
- var value = Number.parseInt(normalized, 16);
452
- return [(value >> 16 & 255) / 255, (value >> 8 & 255) / 255, (value & 255) / 255];
443
+ /**
444
+ * Color helpers. `normalizeColor` converts any supported CSS color
445
+ * (hex / named / rgb() / rgba()) into a 6-digit hex string, so renderers can
446
+ * keep working with #rrggbb only. rgba() alpha is intentionally dropped:
447
+ * the WebGL shader has no per-color alpha channel, and the component is
448
+ * opaque by design — use component-level `opacity` for transparency.
449
+ */
450
+
451
+ var NAMED_COLORS = {
452
+ aliceblue: '#f0f8ff',
453
+ antiquewhite: '#faebd7',
454
+ aqua: '#00ffff',
455
+ aquamarine: '#7fffd4',
456
+ azure: '#f0ffff',
457
+ beige: '#f5f5dc',
458
+ bisque: '#ffe4c4',
459
+ black: '#000000',
460
+ blanchedalmond: '#ffebcd',
461
+ blue: '#0000ff',
462
+ blueviolet: '#8a2be2',
463
+ brown: '#a52a2a',
464
+ burlywood: '#deb887',
465
+ cadetblue: '#5f9ea0',
466
+ chartreuse: '#7fff00',
467
+ chocolate: '#d2691e',
468
+ coral: '#ff7f50',
469
+ cornflowerblue: '#6495ed',
470
+ cornsilk: '#fff8dc',
471
+ crimson: '#dc143c',
472
+ cyan: '#00ffff',
473
+ darkblue: '#00008b',
474
+ darkcyan: '#008b8b',
475
+ darkgoldenrod: '#b8860b',
476
+ darkgray: '#a9a9a9',
477
+ darkgreen: '#006400',
478
+ darkgrey: '#a9a9a9',
479
+ darkkhaki: '#bdb76b',
480
+ darkmagenta: '#8b008b',
481
+ darkolivegreen: '#556b2f',
482
+ darkorange: '#ff8c00',
483
+ darkorchid: '#9932cc',
484
+ darkred: '#8b0000',
485
+ darksalmon: '#e9967a',
486
+ darkseagreen: '#8fbc8f',
487
+ darkslateblue: '#483d8b',
488
+ darkslategray: '#2f4f4f',
489
+ darkslategrey: '#2f4f4f',
490
+ darkturquoise: '#00ced1',
491
+ darkviolet: '#9400d3',
492
+ deeppink: '#ff1493',
493
+ deepskyblue: '#00bfff',
494
+ dimgray: '#696969',
495
+ dimgrey: '#696969',
496
+ dodgerblue: '#1e90ff',
497
+ firebrick: '#b22222',
498
+ floralwhite: '#fffaf0',
499
+ forestgreen: '#228b22',
500
+ fuchsia: '#ff00ff',
501
+ gainsboro: '#dcdcdc',
502
+ ghostwhite: '#f8f8ff',
503
+ gold: '#ffd700',
504
+ goldenrod: '#daa520',
505
+ gray: '#808080',
506
+ green: '#008000',
507
+ greenyellow: '#adff2f',
508
+ grey: '#808080',
509
+ honeydew: '#f0fff0',
510
+ hotpink: '#ff69b4',
511
+ indianred: '#cd5c5c',
512
+ indigo: '#4b0082',
513
+ ivory: '#fffff0',
514
+ khaki: '#f0e68c',
515
+ lavender: '#e6e6fa',
516
+ lavenderblush: '#fff0f5',
517
+ lawngreen: '#7cfc00',
518
+ lemonchiffon: '#fffacd',
519
+ lightblue: '#add8e6',
520
+ lightcoral: '#f08080',
521
+ lightcyan: '#e0ffff',
522
+ lightgoldenrodyellow: '#fafad2',
523
+ lightgray: '#d3d3d3',
524
+ lightgreen: '#90ee90',
525
+ lightgrey: '#d3d3d3',
526
+ lightpink: '#ffb6c1',
527
+ lightsalmon: '#ffa07a',
528
+ lightseagreen: '#20b2aa',
529
+ lightskyblue: '#87cefa',
530
+ lightslategray: '#778899',
531
+ lightslategrey: '#778899',
532
+ lightsteelblue: '#b0c4de',
533
+ lightyellow: '#ffffe0',
534
+ lime: '#00ff00',
535
+ limegreen: '#32cd32',
536
+ linen: '#faf0e6',
537
+ magenta: '#ff00ff',
538
+ maroon: '#800000',
539
+ mediumaquamarine: '#66cdaa',
540
+ mediumblue: '#0000cd',
541
+ mediumorchid: '#ba55d3',
542
+ mediumpurple: '#9370db',
543
+ mediumseagreen: '#3cb371',
544
+ mediumslateblue: '#7b68ee',
545
+ mediumspringgreen: '#00fa9a',
546
+ mediumturquoise: '#48d1cc',
547
+ mediumvioletred: '#c71585',
548
+ midnightblue: '#191970',
549
+ mintcream: '#f5fffa',
550
+ mistyrose: '#ffe4e1',
551
+ moccasin: '#ffe4b5',
552
+ navajowhite: '#ffdead',
553
+ navy: '#000080',
554
+ oldlace: '#fdf5e6',
555
+ olive: '#808000',
556
+ olivedrab: '#6b8e23',
557
+ orange: '#ffa500',
558
+ orangered: '#ff4500',
559
+ orchid: '#da70d6',
560
+ palegoldenrod: '#eee8aa',
561
+ palegreen: '#98fb98',
562
+ paleturquoise: '#afeeee',
563
+ palevioletred: '#db7093',
564
+ papayawhip: '#ffefd5',
565
+ peachpuff: '#ffdab9',
566
+ peru: '#cd853f',
567
+ pink: '#ffc0cb',
568
+ plum: '#dda0dd',
569
+ powderblue: '#b0e0e6',
570
+ purple: '#800080',
571
+ rebeccapurple: '#663399',
572
+ red: '#ff0000',
573
+ rosybrown: '#bc8f8f',
574
+ royalblue: '#4169e1',
575
+ saddlebrown: '#8b4513',
576
+ salmon: '#fa8072',
577
+ sandybrown: '#f4a460',
578
+ seagreen: '#2e8b57',
579
+ seashell: '#fff5ee',
580
+ sienna: '#a0522d',
581
+ silver: '#c0c0c0',
582
+ skyblue: '#87ceeb',
583
+ slateblue: '#6a5acd',
584
+ slategray: '#708090',
585
+ slategrey: '#708090',
586
+ snow: '#fffafa',
587
+ springgreen: '#00ff7f',
588
+ steelblue: '#4682b4',
589
+ tan: '#d2b48c',
590
+ teal: '#008080',
591
+ thistle: '#d8bfd8',
592
+ tomato: '#ff6347',
593
+ turquoise: '#40e0d0',
594
+ violet: '#ee82ee',
595
+ wheat: '#f5deb3',
596
+ white: '#ffffff',
597
+ whitesmoke: '#f5f5f5',
598
+ yellow: '#ffff00',
599
+ yellowgreen: '#9acd32'
600
+ };
601
+ function clampChannel(value) {
602
+ return Math.min(255, Math.max(0, Math.round(value)));
453
603
  }
454
- function hexToRgba(hex) {
455
- var alpha = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
456
- var normalized = hex.replace('#', '');
457
- var value = Number.parseInt(normalized, 16);
458
- var red = value >> 16 & 255;
459
- var green = value >> 8 & 255;
460
- var blue = value & 255;
461
- return "rgba(".concat(red, ", ").concat(green, ", ").concat(blue, ", ").concat(alpha, ")");
604
+ function normalizeRgbArgs(args) {
605
+ var parts = args.split(/[,\s/]+/).filter(Boolean);
606
+ if (parts.length < 3) return null;
607
+ var to255 = function to255(value) {
608
+ if (typeof value !== 'string' || !value) return null;
609
+ if (value.endsWith('%')) return clampChannel(parseFloat(value) / 100 * 255);
610
+ var number = Number(value);
611
+ return Number.isFinite(number) ? clampChannel(number) : null;
612
+ };
613
+ var red = to255(parts[0]);
614
+ var green = to255(parts[1]);
615
+ var blue = to255(parts[2]);
616
+ if (red === null || green === null || blue === null) return null;
617
+ return "#".concat([red, green, blue].map(function (n) {
618
+ return n.toString(16).padStart(2, '0');
619
+ }).join(''));
620
+ }
621
+
622
+ /**
623
+ * Convert any supported CSS color into `#rrggbb`. Returns null when the
624
+ * value cannot be parsed. Supports: #rgb / #rrggbb (case-insensitive),
625
+ * CSS named colors, rgb() / rgba() with comma or modern space syntax,
626
+ * including percentages. Alpha in rgba() is ignored.
627
+ */
628
+ function normalizeColor(value) {
629
+ if (typeof value !== 'string') return null;
630
+ var input = value.trim();
631
+ if (!input) return null;
632
+ var lower = input.toLowerCase();
633
+ if (NAMED_COLORS[lower]) return NAMED_COLORS[lower];
634
+ if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(input)) {
635
+ var hex = input.slice(1).toLowerCase();
636
+ if (hex.length === 3) return "#".concat(hex.split('').map(function (c) {
637
+ return c + c;
638
+ }).join(''));
639
+ return "#".concat(hex);
640
+ }
641
+ var match = input.match(/^rgba?\((.*)\)$/i);
642
+ if (match) return normalizeRgbArgs(match[1]);
643
+ return null;
644
+ }
645
+ function hexToRgb01(color) {
646
+ var hex = normalizeColor(color);
647
+ if (!hex) return [0, 0, 0];
648
+ var value = Number.parseInt(hex.slice(1), 16);
649
+ return [(value >> 16 & 255) / 255, (value >> 8 & 255) / 255, (value & 255) / 255];
462
650
  }
463
651
 
464
652
  var VERTEX_SHADER = "#version 300 es\nin vec2 a_position;\nout vec2 v_uv;\nvoid main() {\n v_uv = a_position * 0.5 + 0.5;\n gl_Position = vec4(a_position, 0.0, 1.0);\n}";
@@ -535,6 +723,26 @@ var CosmicRenderer = /*#__PURE__*/function () {
535
723
  this.options.dprCap = cap;
536
724
  this.resize();
537
725
  }
726
+ }, {
727
+ key: "setMouseColor",
728
+ value: function setMouseColor(enabled) {
729
+ this.options.mouseColor = enabled !== false;
730
+ if (this.options.mouseColor) {
731
+ if (this.onPointerMove) return this;
732
+ _assertClassBrand(_CosmicRenderer_brand, this, _bindEvents).call(this);
733
+ return this;
734
+ }
735
+ if (this.onPointerMove) {
736
+ var target = this.eventTarget;
737
+ target.removeEventListener('pointermove', this.onPointerMove);
738
+ target.removeEventListener('pointerdown', this.onPointerMove);
739
+ target.removeEventListener('pointerleave', this.onPointerLeave);
740
+ this.onPointerMove = null;
741
+ this.onPointerLeave = null;
742
+ }
743
+ this.motionTarget = 0;
744
+ return this;
745
+ }
538
746
  }, {
539
747
  key: "randomize",
540
748
  value: function randomize() {
@@ -683,6 +891,8 @@ var FallbackRenderer = /*#__PURE__*/function () {
683
891
  var _this$canvas = this.canvas,
684
892
  width = _this$canvas.width,
685
893
  height = _this$canvas.height;
894
+ var t = time * (this.preset.speed || 1);
895
+ var phase = (this.preset.seed || 0) * 0.7;
686
896
  var gradient = ctx.createLinearGradient(0, 0, width, height);
687
897
  gradient.addColorStop(0, this.preset.colors[0]);
688
898
  gradient.addColorStop(0.38, this.preset.colors[1]);
@@ -692,8 +902,8 @@ var FallbackRenderer = /*#__PURE__*/function () {
692
902
  ctx.fillRect(0, 0, width, height);
693
903
  ctx.globalCompositeOperation = 'screen';
694
904
  for (var index = 0; index < 6; index += 1) {
695
- var x = (0.5 + 0.45 * Math.sin(time * 0.32 + index * 1.7)) * width;
696
- var y = (0.5 + 0.4 * Math.cos(time * 0.25 + index)) * height;
905
+ var x = (0.5 + 0.45 * Math.sin(t * 0.32 + index * 1.7 + phase)) * width;
906
+ var y = (0.5 + 0.4 * Math.cos(t * 0.25 + index + phase)) * height;
697
907
  var radius = Math.max(width, height) * (0.08 + index % 3 * 0.04);
698
908
  var glow = ctx.createRadialGradient(x, y, 0, x, y, radius);
699
909
  glow.addColorStop(0, rgb(this.preset.colors[(index + 1) % 4], 0.24));
@@ -717,7 +927,12 @@ var FallbackRenderer = /*#__PURE__*/function () {
717
927
  }
718
928
  }, {
719
929
  key: "randomize",
720
- value: function randomize() {}
930
+ value: function randomize() {
931
+ if (this.preset) this.preset.seed = Math.random() * 100;
932
+ }
933
+ }, {
934
+ key: "setMouseColor",
935
+ value: function setMouseColor() {}
721
936
  }, {
722
937
  key: "dispose",
723
938
  value: function dispose() {}
@@ -828,16 +1043,34 @@ function createVisibilityGuard(element) {
828
1043
  };
829
1044
  }
830
1045
 
1046
+ /**
1047
+ * Run a callback asynchronously, right after the current task and before the
1048
+ * next paint. Used for mount-time events (ready / error) so listeners that
1049
+ * are attached after create() still receive them. Falls back for legacy
1050
+ * browsers that lack queueMicrotask / Promise.
1051
+ */
1052
+ function nextTick(fn) {
1053
+ if (typeof queueMicrotask === 'function') {
1054
+ queueMicrotask(fn);
1055
+ } else if (typeof Promise !== 'undefined' && typeof Promise.resolve === 'function') {
1056
+ Promise.resolve().then(fn);
1057
+ } else {
1058
+ setTimeout(fn, 0);
1059
+ }
1060
+ }
1061
+
831
1062
  function prefersReducedMotion() {
832
1063
  return typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches;
833
1064
  }
834
1065
 
835
1066
  /**
836
- * Mount a cosmic (nebula) capsule into `container`.
837
- *
838
- * Options: preset (id/code/name or object), width, height (number=px or
839
- * string with px/%/vw/vh/em/rem), colors, seed, speed, quality, interactive,
840
- * respectReducedMotion, copy, cssVars.
1067
+ * Mount a cosmic (nebula) capsule into `container`.
1068
+ *
1069
+ * Options: preset (literary name), width, height (number=px or string with
1070
+ * px/%/vw/vh/em/rem), colors, seed, speed, textRatio (0-100, text region
1071
+ * width in percent), text (HTML string or DOM nodes for the text slot),
1072
+ * colorContent (HTML string or DOM nodes for the color slot), quality,
1073
+ * renderer, respectReducedMotion, mouseColor, cssVars.
841
1074
  */
842
1075
  function createCapsule(container) {
843
1076
  var _options$preset;
@@ -845,53 +1078,73 @@ function createCapsule(container) {
845
1078
  if (!container || typeof container.appendChild !== 'function') {
846
1079
  throw new Error('createCapsule: container element is required');
847
1080
  }
848
- var preset = _objectSpread2({}, getPreset('capsule', (_options$preset = options.preset) !== null && _options$preset !== void 0 ? _options$preset : 'NC-01'));
1081
+ var preset = _objectSpread2({}, getPreset('capsule', (_options$preset = options.preset) !== null && _options$preset !== void 0 ? _options$preset : '初光'));
849
1082
  var merged = normalizeOptions(DEFAULTS.capsule, preset, options);
850
- var copy = _objectSpread2(_objectSpread2({}, COPY), merged.copy || {});
1083
+ var normalizedColors = merged.colors.map(normalizeColor);
1084
+ if (normalizedColors.every(Boolean)) preset.colors = normalizedColors;
1085
+ preset.seed = merged.seed;
1086
+ preset.speed = merged.speed;
1087
+ var customLabel = typeof options.label === 'string' && options.label ? options.label : null;
851
1088
  var root = document.createElement('div');
852
1089
  root.className = 'hj-capsule-root hj-capsule-cosmic';
853
1090
  root.dataset.group = preset.group;
854
1091
  root.dataset.mode = 'nebula';
855
1092
  root.dataset.theme = preset.theme || 'light';
856
- root.setAttribute('aria-label', copy.capsuleAria.replace('{name}', preset.name));
857
- var copyEnabled = copy.enabled !== false && merged.showCopy !== false;
858
- var copyText = function copyText(key, fallback) {
859
- var value = copy[key];
860
- return value === undefined ? fallback : value;
1093
+ root.setAttribute('role', 'img');
1094
+ var updateAria = function updateAria() {
1095
+ root.setAttribute('aria-label', customLabel || COPY.capsuleAria.replace('{name}', preset.name));
861
1096
  };
862
- function buildCopyHtml() {
863
- var codeText = copyText('code', preset.code);
864
- var nameText = copyText('name', preset.name);
865
- var subtitleText = copyText('subtitle', preset.subtitle);
866
- var stateText = copyText('state', 'LIVE COSMIC STUDY');
867
- var html = '';
868
- if (codeText) html += "<span class=\"hj-capsule-code\">".concat(codeText, "</span>");
869
- if (nameText) html += "<span class=\"hj-capsule-name\">".concat(nameText, "</span>");
870
- if (subtitleText) html += "<span class=\"hj-capsule-brand\">".concat(subtitleText, "</span>");
871
- if (stateText) html += "<span class=\"hj-capsule-state\">".concat(stateText, "</span>");
872
- return html;
873
- }
874
- var copyLayer = document.createElement('div');
875
- copyLayer.className = 'hj-capsule-copy';
876
- var renderCopy = function renderCopy() {
877
- if (copyEnabled) copyLayer.innerHTML = buildCopyHtml();
878
- };
879
- renderCopy();
880
- var isHexColor = function isHexColor(value) {
881
- return typeof value === 'string' && /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(value);
1097
+ updateAria();
1098
+
1099
+ // Slot containers are always present but transparent by default: without
1100
+ // content they are invisible, so "no slot" behaves like the old
1101
+ // showCopy=false. They only provide geometry — no typography, padding or
1102
+ // background is imposed on user content (see docs: 插槽 CSS 约定).
1103
+ var fillContent = function fillContent(layer, content) {
1104
+ layer.innerHTML = '';
1105
+ if (content == null) return;
1106
+ if (typeof content === 'string') {
1107
+ layer.innerHTML = content;
1108
+ return;
1109
+ }
1110
+ var nodes = Array.isArray(content) ? content : [content];
1111
+ var _iterator = _createForOfIteratorHelper(nodes),
1112
+ _step;
1113
+ try {
1114
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
1115
+ var node = _step.value;
1116
+ if (node && typeof node.nodeType === 'number') layer.appendChild(node);
1117
+ }
1118
+ } catch (err) {
1119
+ _iterator.e(err);
1120
+ } finally {
1121
+ _iterator.f();
1122
+ }
882
1123
  };
883
- if (copy.background) {
884
- 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);
885
- }
886
- if (copy.color) root.style.setProperty('--hj-copy-color', copy.color);
1124
+ var textLayer = document.createElement('div');
1125
+ textLayer.className = 'hj-capsule-text hj-capsule-copy';
1126
+ var visualLayer = document.createElement('div');
1127
+ visualLayer.className = 'hj-capsule-visual';
1128
+ fillContent(textLayer, options.text);
1129
+ fillContent(visualLayer, options.colorContent);
887
1130
  var canvas = document.createElement('canvas');
888
1131
  canvas.className = 'hj-capsule-canvas';
889
1132
  canvas.setAttribute('aria-hidden', 'true');
890
- if (copyEnabled) root.appendChild(copyLayer);
1133
+ root.appendChild(textLayer);
1134
+ root.appendChild(visualLayer);
891
1135
  root.appendChild(canvas);
892
1136
  container.appendChild(root);
893
1137
  var emitter = createEmitter();
894
1138
  var paused = merged.respectReducedMotion && prefersReducedMotion();
1139
+ var disposed = false;
1140
+ var dirty = {
1141
+ preset: false,
1142
+ seed: false,
1143
+ speed: false,
1144
+ colors: false,
1145
+ textRatio: false,
1146
+ cssVars: false
1147
+ };
895
1148
  var renderer;
896
1149
  var useWebgl = merged.renderer !== 'canvas2d';
897
1150
  if (useWebgl) {
@@ -901,8 +1154,11 @@ function createCapsule(container) {
901
1154
  });
902
1155
  } catch (error) {
903
1156
  renderer = new FallbackRenderer(canvas, merged);
904
- emitter.emit('error', {
905
- message: String(error && error.message ? error.message : error)
1157
+ nextTick(function () {
1158
+ if (disposed) return;
1159
+ emitter.emit('error', {
1160
+ message: String(error && error.message ? error.message : error)
1161
+ });
906
1162
  });
907
1163
  }
908
1164
  } else {
@@ -916,6 +1172,9 @@ function createCapsule(container) {
916
1172
  var name = _Object$keys[_i];
917
1173
  root.style.setProperty(name, vars[name]);
918
1174
  }
1175
+ if (merged.textRatio !== undefined) {
1176
+ root.style.setProperty('--hj-text-width', "".concat(merged.textRatio, "%"));
1177
+ }
919
1178
  renderer.resize();
920
1179
  };
921
1180
  applySize();
@@ -924,18 +1183,16 @@ function createCapsule(container) {
924
1183
  }) : null;
925
1184
  if (resizeObserver) resizeObserver.observe(root);else window.addEventListener('resize', renderer.resize);
926
1185
  var visibility = createVisibilityGuard(root);
927
- if (merged.interactive !== false) {
928
- root.addEventListener('pointerenter', function () {
929
- return emitter.emit('pointerenter', {
930
- preset: _objectSpread2({}, preset)
931
- });
1186
+ root.addEventListener('pointerenter', function () {
1187
+ return emitter.emit('pointerenter', {
1188
+ preset: _objectSpread2({}, preset)
932
1189
  });
933
- root.addEventListener('pointerleave', function () {
934
- return emitter.emit('pointerleave', {
935
- preset: _objectSpread2({}, preset)
936
- });
1190
+ });
1191
+ root.addEventListener('pointerleave', function () {
1192
+ return emitter.emit('pointerleave', {
1193
+ preset: _objectSpread2({}, preset)
937
1194
  });
938
- }
1195
+ });
939
1196
  root.addEventListener('click', function (event) {
940
1197
  emitter.emit('click', {
941
1198
  event: event,
@@ -967,13 +1224,16 @@ function createCapsule(container) {
967
1224
  }, function () {
968
1225
  return paused;
969
1226
  });
970
- emitter.emit('ready', {
971
- preset: _objectSpread2({}, preset)
1227
+ nextTick(function () {
1228
+ if (disposed) return;
1229
+ emitter.emit('ready', {
1230
+ preset: _objectSpread2({}, preset)
1231
+ });
972
1232
  });
973
- var syncCopy = function syncCopy() {
1233
+ var syncTheme = function syncTheme() {
974
1234
  root.dataset.mode = 'nebula';
975
1235
  root.dataset.theme = preset.theme || 'light';
976
- renderCopy();
1236
+ updateAria();
977
1237
  };
978
1238
  return {
979
1239
  element: root,
@@ -983,9 +1243,12 @@ function createCapsule(container) {
983
1243
  off: emitter.off,
984
1244
  setPreset: function setPreset(ref) {
985
1245
  var next = getPreset('capsule', ref);
1246
+ dirty.preset = true;
986
1247
  Object.assign(preset, next);
987
- renderer.setPreset(next);
988
- syncCopy();
1248
+ var nextColors = preset.colors.map(normalizeColor);
1249
+ if (nextColors.every(Boolean)) preset.colors = nextColors;
1250
+ renderer.setPreset(_objectSpread2({}, preset));
1251
+ syncTheme();
989
1252
  renderer.resize();
990
1253
  emitter.emit('presetchange', {
991
1254
  preset: _objectSpread2({}, next)
@@ -993,13 +1256,64 @@ function createCapsule(container) {
993
1256
  return this;
994
1257
  },
995
1258
  setColors: function setColors(colors) {
996
- if (!Array.isArray(colors) || colors.length !== 4) return this;
997
- preset.colors = colors;
1259
+ var next = Array.isArray(colors) ? colors.map(normalizeColor) : [];
1260
+ if (next.length !== 4 || next.some(function (color) {
1261
+ return !color;
1262
+ })) return this;
1263
+ dirty.colors = true;
1264
+ preset.colors = next;
998
1265
  renderer.setPreset(_objectSpread2(_objectSpread2({}, preset), {}, {
999
- colors: colors
1266
+ colors: next
1000
1267
  }));
1001
1268
  return this;
1002
1269
  },
1270
+ setSeed: function setSeed(seed) {
1271
+ var value = Number(seed);
1272
+ if (!Number.isFinite(value)) return this;
1273
+ dirty.seed = true;
1274
+ preset.seed = value;
1275
+ renderer.setPreset(_objectSpread2({}, preset));
1276
+ return this;
1277
+ },
1278
+ setSpeed: function setSpeed(speed) {
1279
+ var value = Number(speed);
1280
+ if (!Number.isFinite(value)) return this;
1281
+ dirty.speed = true;
1282
+ preset.speed = value;
1283
+ renderer.setPreset(_objectSpread2({}, preset));
1284
+ return this;
1285
+ },
1286
+ setText: function setText(content) {
1287
+ fillContent(textLayer, content);
1288
+ return this;
1289
+ },
1290
+ setColorContent: function setColorContent(content) {
1291
+ fillContent(visualLayer, content);
1292
+ return this;
1293
+ },
1294
+ setTextRatio: function setTextRatio(ratio) {
1295
+ var value = Number(ratio);
1296
+ if (!Number.isFinite(value) || value < 0 || value > 100) return this;
1297
+ dirty.textRatio = true;
1298
+ merged.textRatio = value;
1299
+ root.style.setProperty('--hj-text-width', "".concat(value, "%"));
1300
+ return this;
1301
+ },
1302
+ setCssVars: function setCssVars(vars) {
1303
+ if (!vars || _typeof(vars) !== 'object') return this;
1304
+ dirty.cssVars = true;
1305
+ merged.cssVars = _objectSpread2(_objectSpread2({}, merged.cssVars || {}), vars);
1306
+ for (var _i2 = 0, _Object$keys2 = Object.keys(vars); _i2 < _Object$keys2.length; _i2++) {
1307
+ var name = _Object$keys2[_i2];
1308
+ root.style.setProperty(name, vars[name]);
1309
+ }
1310
+ return this;
1311
+ },
1312
+ setLabel: function setLabel(label) {
1313
+ customLabel = typeof label === 'string' && label ? label : null;
1314
+ updateAria();
1315
+ return this;
1316
+ },
1003
1317
  setSize: function setSize(width, height) {
1004
1318
  if (width !== undefined) merged.width = width;
1005
1319
  if (height !== undefined) merged.height = height;
@@ -1007,7 +1321,7 @@ function createCapsule(container) {
1007
1321
  return this;
1008
1322
  },
1009
1323
  randomize: function randomize() {
1010
- renderer.randomize();
1324
+ this.setSeed(Math.random() * 100);
1011
1325
  return this;
1012
1326
  },
1013
1327
  pause: function pause() {
@@ -1024,12 +1338,16 @@ function createCapsule(container) {
1024
1338
  return this;
1025
1339
  },
1026
1340
  dispose: function dispose() {
1341
+ disposed = true;
1027
1342
  unsubscribe();
1028
1343
  visibility.dispose();
1029
1344
  if (resizeObserver) resizeObserver.disconnect();else window.removeEventListener('resize', renderer.resize);
1030
1345
  renderer.dispose();
1031
1346
  root.remove();
1032
- }
1347
+ },
1348
+ textRatio: merged.textRatio,
1349
+ cssVars: merged.cssVars,
1350
+ dirty: dirty
1033
1351
  };
1034
1352
  }
1035
1353