@5even7/dlc-ui 0.2.11 → 0.2.13

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
@@ -185,9 +185,9 @@ function _unsupportedIterableToArray(r, a) {
185
185
  }
186
186
  }
187
187
 
188
- /**
189
- * Tiny event emitter. Accepts any event name so the API stays open for
190
- * future events (hover, click, custom) without breaking changes.
188
+ /**
189
+ * Tiny event emitter. Accepts any event name so the API stays open for
190
+ * future events (hover, click, custom) without breaking changes.
191
191
  */
192
192
  function createEmitter() {
193
193
  // Plain object storage (no Map/Set) so the legacy build has no API
@@ -237,9 +237,9 @@ function createEmitter() {
237
237
 
238
238
  var UNIT_PATTERN = /^[\d.]+(?:px|%|vw|vh|vmin|vmax|em|rem)$/;
239
239
 
240
- /**
241
- * Normalize a size option to a CSS length string.
242
- * Accepts numbers (treated as px) or strings with px/%, vw/vh/vmin/vmax, em/rem.
240
+ /**
241
+ * Normalize a size option to a CSS length string.
242
+ * Accepts numbers (treated as px) or strings with px/%, vw/vh/vmin/vmax, em/rem.
243
243
  */
244
244
  function parseSize(value) {
245
245
  if (typeof value === 'number') {
@@ -278,11 +278,17 @@ function dprCapFor(quality) {
278
278
  var tier = QUALITY_TIERS[quality] || QUALITY_TIERS.medium;
279
279
  return tier.dpr;
280
280
  }
281
+ function effectiveDprCap(quality) {
282
+ var renderScale = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
283
+ var scale = Number(renderScale);
284
+ var normalizedScale = Number.isFinite(scale) ? Math.min(1, Math.max(0.25, scale)) : 1;
285
+ return Math.max(0.5, dprCapFor(quality) * normalizedScale);
286
+ }
281
287
 
282
- /**
283
- * Merge defaults < preset < user. Unknown user keys are preserved so the
284
- * component API can grow (new props, cssVars, callbacks) without a breaking
285
- * change.
288
+ /**
289
+ * Merge defaults < preset < user. Unknown user keys are preserved so the
290
+ * component API can grow (new props, cssVars, callbacks) without a breaking
291
+ * change.
286
292
  */
287
293
  function normalizeOptions(defaults, preset, user) {
288
294
  // undefined means "not provided" (e.g. Vue $props with unset props):
@@ -300,9 +306,9 @@ function normalizeOptions(defaults, preset, user) {
300
306
  return _objectSpread2(_objectSpread2(_objectSpread2({}, defaults), presetOptions), userOptions);
301
307
  }
302
308
 
303
- /**
304
- * 公共色板:NC-01~NC-06 的四色组(底色 / 主色 / 辅色 / 高光色)。
305
- * Capsule 预置与 dlc-color 等组件共用,新增色板只改这里。
309
+ /**
310
+ * 公共色板:NC-01~NC-06 的四色组(底色 / 主色 / 辅色 / 高光色)。
311
+ * Capsule 预置与 dlc-color 等组件共用,新增色板只改这里。
306
312
  */
307
313
  var PALETTES$1 = {
308
314
  original: ['#FFF3EA', '#F5B27A', '#F67BC6', '#A978E8'],
@@ -313,9 +319,9 @@ var PALETTES$1 = {
313
319
  plus: ['#FFF0E6', '#F6C26B', '#F98A64', '#E86D74']
314
320
  };
315
321
 
316
- /**
317
- * Capsule 预置:仅星云材质 NC-01~NC-06(NC-07~09 aurora 已移除)。
318
- * 颜色引用公共色板,seed/speed 决定形态与流速。
322
+ /**
323
+ * Capsule 预置:仅星云材质 NC-01~NC-06(NC-07~09 aurora 已移除)。
324
+ * 颜色引用公共色板,seed/speed 决定形态与流速。
319
325
  */
320
326
  var CAPSULE_PRESETS = [{
321
327
  id: 'original',
@@ -403,6 +409,15 @@ var PROGRESS_PRESETS = [{
403
409
  initialProgress: 30,
404
410
  edgeStyle: 'tide',
405
411
  colors: ['#0A2239', '#2E9BFF', '#7FE3FF', '#EAF9FF']
412
+ }, {
413
+ id: 'botanic-lab',
414
+ code: 'NC-14',
415
+ name: '森息',
416
+ subtitle: 'MOSS CULTURE / ROOTING',
417
+ group: 'progress',
418
+ initialProgress: 46,
419
+ edgeStyle: 'flow',
420
+ colors: ['#102A23', '#287A58', '#9BD65B', '#F2FFC4']
406
421
  }];
407
422
 
408
423
  var PRESETS = CAPSULE_PRESETS;
@@ -434,6 +449,11 @@ var DEFAULTS = {
434
449
  height: 160,
435
450
  quality: 'auto',
436
451
  renderer: 'auto',
452
+ renderScale: 1,
453
+ powerPreference: 'high-performance',
454
+ fps: 60,
455
+ paused: false,
456
+ static: false,
437
457
  respectReducedMotion: true,
438
458
  mouseColor: true,
439
459
  textRatio: 39
@@ -443,13 +463,22 @@ var DEFAULTS = {
443
463
  height: 104,
444
464
  min: 0,
445
465
  max: 100,
466
+ step: 'any',
446
467
  draggable: true,
468
+ keyboard: true,
447
469
  disabled: false,
448
470
  readonly: false,
471
+ direction: 'ltr',
472
+ precision: 0,
449
473
  valueSuffix: '%',
450
474
  edgeStyle: 'flow',
451
475
  quality: 'auto',
452
476
  renderer: 'auto',
477
+ renderScale: 1,
478
+ powerPreference: 'high-performance',
479
+ fps: 60,
480
+ paused: false,
481
+ static: false,
453
482
  textRatio: 54,
454
483
  showValue: true,
455
484
  respectReducedMotion: true
@@ -739,7 +768,7 @@ var CosmicRenderer = /*#__PURE__*/function () {
739
768
  alpha: false,
740
769
  antialias: false,
741
770
  depth: false,
742
- powerPreference: 'high-performance',
771
+ powerPreference: this.options.powerPreference || 'high-performance',
743
772
  preserveDrawingBuffer: false
744
773
  });
745
774
  if (!this.gl) throw new Error('WebGL2 is not available');
@@ -750,16 +779,19 @@ var CosmicRenderer = /*#__PURE__*/function () {
750
779
  this.motion = 0;
751
780
  this.motionTarget = 0;
752
781
  this.timeOffset = preset.seed * 0.73;
782
+ this.colors = preset.colors.map(hexToRgb01$1);
753
783
  this.visible = true;
754
784
  this.disposed = false;
755
785
  _assertClassBrand(_CosmicRenderer_brand, this, _setupGeometry).call(this);
756
786
  _assertClassBrand(_CosmicRenderer_brand, this, _bindEvents).call(this);
787
+ _assertClassBrand(_CosmicRenderer_brand, this, _bindContextEvents).call(this);
757
788
  this.resize();
758
789
  }
759
790
  return _createClass(CosmicRenderer, [{
760
791
  key: "setPreset",
761
792
  value: function setPreset(preset) {
762
793
  this.preset = _objectSpread2({}, preset);
794
+ this.colors = preset.colors.map(hexToRgb01$1);
763
795
  this.timeOffset = preset.seed * 0.73;
764
796
  }
765
797
  }, {
@@ -804,15 +836,14 @@ var CosmicRenderer = /*#__PURE__*/function () {
804
836
  if (this.canvas.width !== width || this.canvas.height !== height) {
805
837
  this.canvas.width = width;
806
838
  this.canvas.height = height;
807
- this.gl.viewport(0, 0, width, height);
808
839
  }
840
+ this.gl.viewport(0, 0, width, height);
809
841
  }
810
842
  }, {
811
843
  key: "draw",
812
844
  value: function draw(elapsedSeconds) {
813
845
  var paused = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
814
846
  if (this.disposed || !this.visible) return;
815
- this.resize();
816
847
  var gl = this.gl;
817
848
  this.pointer[0] += (this.pointerTarget[0] - this.pointer[0]) * 0.08;
818
849
  this.pointer[1] += (this.pointerTarget[1] - this.pointer[1]) * 0.08;
@@ -821,16 +852,15 @@ var CosmicRenderer = /*#__PURE__*/function () {
821
852
  gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
822
853
  gl.enableVertexAttribArray(this.locations.position);
823
854
  gl.vertexAttribPointer(this.locations.position, 2, gl.FLOAT, false, 0, 0);
824
- var colors = this.preset.colors.map(hexToRgb01$1);
825
855
  gl.uniform2f(this.locations.resolution, this.canvas.width, this.canvas.height);
826
856
  gl.uniform1f(this.locations.time, this.timeOffset + (paused ? 0 : elapsedSeconds * this.preset.speed));
827
857
  gl.uniform1f(this.locations.seed, this.preset.seed);
828
858
  gl.uniform1f(this.locations.motion, this.motion);
829
859
  gl.uniform2f(this.locations.pointer, this.pointer[0], this.pointer[1]);
830
- gl.uniform3fv(this.locations.colorA, colors[0]);
831
- gl.uniform3fv(this.locations.colorB, colors[1]);
832
- gl.uniform3fv(this.locations.colorC, colors[2]);
833
- gl.uniform3fv(this.locations.colorD, colors[3]);
860
+ gl.uniform3fv(this.locations.colorA, this.colors[0]);
861
+ gl.uniform3fv(this.locations.colorB, this.colors[1]);
862
+ gl.uniform3fv(this.locations.colorC, this.colors[2]);
863
+ gl.uniform3fv(this.locations.colorD, this.colors[3]);
834
864
  gl.drawArrays(gl.TRIANGLES, 0, 3);
835
865
  }
836
866
  }, {
@@ -841,6 +871,8 @@ var CosmicRenderer = /*#__PURE__*/function () {
841
871
  target.removeEventListener('pointermove', this.onPointerMove);
842
872
  target.removeEventListener('pointerdown', this.onPointerMove);
843
873
  target.removeEventListener('pointerleave', this.onPointerLeave);
874
+ this.canvas.removeEventListener('webglcontextlost', this.onContextLost, false);
875
+ this.canvas.removeEventListener('webglcontextrestored', this.onContextRestored, false);
844
876
  this.gl.deleteBuffer(this.buffer);
845
877
  this.gl.deleteProgram(this.program);
846
878
  var lose = this.gl.getExtension('WEBGL_lose_context');
@@ -897,6 +929,26 @@ function _bindEvents() {
897
929
  passive: true
898
930
  });
899
931
  }
932
+ function _bindContextEvents() {
933
+ var _this3 = this;
934
+ this.onContextLost = function (event) {
935
+ event.preventDefault();
936
+ if (_this3.disposed) return;
937
+ _this3.visible = false;
938
+ if (typeof _this3.options.onContextLost === 'function') _this3.options.onContextLost(event);
939
+ };
940
+ this.onContextRestored = function () {
941
+ if (_this3.disposed) return;
942
+ _this3.program = createProgram$1(_this3.gl);
943
+ _this3.locations = _assertClassBrand(_CosmicRenderer_brand, _this3, _getLocations).call(_this3);
944
+ _assertClassBrand(_CosmicRenderer_brand, _this3, _setupGeometry).call(_this3);
945
+ _this3.visible = true;
946
+ _this3.resize();
947
+ if (typeof _this3.options.onContextRestored === 'function') _this3.options.onContextRestored();
948
+ };
949
+ this.canvas.addEventListener('webglcontextlost', this.onContextLost, false);
950
+ this.canvas.addEventListener('webglcontextrestored', this.onContextRestored, false);
951
+ }
900
952
 
901
953
  function rgb(color) {
902
954
  var alpha = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
@@ -911,18 +963,20 @@ function rgb(color) {
911
963
  }
912
964
  var FallbackRenderer = /*#__PURE__*/function () {
913
965
  function FallbackRenderer(canvas, preset) {
966
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
914
967
  _classCallCheck(this, FallbackRenderer);
915
968
  this.canvas = canvas;
916
969
  this.preset = preset;
917
970
  this.context = canvas.getContext('2d');
918
971
  this.visible = true;
919
972
  this.timePhase = 0;
973
+ this.dprCap = options.dprCap || 1.5;
920
974
  }
921
975
  return _createClass(FallbackRenderer, [{
922
976
  key: "resize",
923
977
  value: function resize() {
924
978
  var rect = this.canvas.getBoundingClientRect();
925
- var dpr = Math.min(window.devicePixelRatio || 1, 1.5);
979
+ var dpr = Math.min(window.devicePixelRatio || 1, this.dprCap);
926
980
  var width = Math.max(2, Math.round(rect.width * dpr));
927
981
  var height = Math.max(2, Math.round(rect.height * dpr));
928
982
  if (this.canvas.width !== width || this.canvas.height !== height) {
@@ -963,7 +1017,6 @@ var FallbackRenderer = /*#__PURE__*/function () {
963
1017
  key: "draw",
964
1018
  value: function draw(time) {
965
1019
  if (!this.visible) return;
966
- this.resize();
967
1020
  this.drawNebula(time);
968
1021
  }
969
1022
  }, {
@@ -971,6 +1024,12 @@ var FallbackRenderer = /*#__PURE__*/function () {
971
1024
  value: function setPreset(preset) {
972
1025
  this.preset = preset;
973
1026
  }
1027
+ }, {
1028
+ key: "setDprCap",
1029
+ value: function setDprCap(cap) {
1030
+ this.dprCap = cap;
1031
+ this.resize();
1032
+ }
974
1033
  }, {
975
1034
  key: "randomize",
976
1035
  value: function randomize() {
@@ -988,10 +1047,10 @@ var FallbackRenderer = /*#__PURE__*/function () {
988
1047
  }]);
989
1048
  }();
990
1049
 
991
- /**
992
- * Document-level shared rAF scheduler. Every component instance subscribes
993
- * its own frame callback; the whole page runs ONE animation loop (like the
994
- * original demo), which avoids jank from many competing rAF loops.
1050
+ /**
1051
+ * Document-level shared rAF scheduler. Every component instance subscribes
1052
+ * its own frame callback; the whole page runs ONE animation loop (like the
1053
+ * original demo), which avoids jank from many competing rAF loops.
995
1054
  */
996
1055
  var subscribers = [];
997
1056
  var running = false;
@@ -1000,11 +1059,7 @@ var rafId = 0;
1000
1059
  var last = 0;
1001
1060
  function tick(now) {
1002
1061
  if (!running) return;
1003
- var delta = last ? Math.min((now - last) / 1000, 0.05) : 0;
1004
- last = now;
1005
- // Schedule the next frame BEFORE running callbacks so one throwing
1006
- // subscriber can never kill the whole animation loop.
1007
- rafId = requestAnimationFrame(tick);
1062
+ var activeItems = [];
1008
1063
  if (!globallyPaused) {
1009
1064
  var _iterator = _createForOfIteratorHelper(subscribers.slice()),
1010
1065
  _step;
@@ -1012,10 +1067,8 @@ function tick(now) {
1012
1067
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
1013
1068
  var item = _step.value;
1014
1069
  try {
1015
- if (!item.isPaused()) item.onFrame(delta, now);
1016
- } catch (error) {
1017
- console.warn('[dlc-ui] frame error:', error);
1018
- }
1070
+ if (!item.isPaused()) activeItems.push(item);
1071
+ } catch (_unused) {}
1019
1072
  }
1020
1073
  } catch (err) {
1021
1074
  _iterator.e(err);
@@ -1023,6 +1076,25 @@ function tick(now) {
1023
1076
  _iterator.f();
1024
1077
  }
1025
1078
  }
1079
+ if (activeItems.length === 0) {
1080
+ running = false;
1081
+ rafId = 0;
1082
+ last = 0;
1083
+ return;
1084
+ }
1085
+ var delta = last ? Math.min((now - last) / 1000, 0.05) : 0;
1086
+ last = now;
1087
+ // Schedule the next frame BEFORE running callbacks so one throwing
1088
+ // subscriber can never kill the whole animation loop.
1089
+ rafId = requestAnimationFrame(tick);
1090
+ for (var _i = 0, _activeItems = activeItems; _i < _activeItems.length; _i++) {
1091
+ var _item = _activeItems[_i];
1092
+ try {
1093
+ _item.onFrame(delta, now);
1094
+ } catch (error) {
1095
+ console.warn('[dlc-ui] frame error:', error);
1096
+ }
1097
+ }
1026
1098
  if (subscribers.length === 0) {
1027
1099
  cancelAnimationFrame(rafId);
1028
1100
  running = false;
@@ -1040,6 +1112,10 @@ function pauseAll() {
1040
1112
  }
1041
1113
  function resumeAll() {
1042
1114
  globallyPaused = false;
1115
+ start();
1116
+ }
1117
+ function wakeScheduler() {
1118
+ start();
1043
1119
  }
1044
1120
  function subscribeScheduler(onFrame, isPaused) {
1045
1121
  var item = {
@@ -1059,11 +1135,12 @@ function subscribeScheduler(onFrame, isPaused) {
1059
1135
  };
1060
1136
  }
1061
1137
 
1062
- /**
1063
- * Gates drawing on "element intersects viewport AND the page tab is visible".
1064
- * Falls back to always-visible when IntersectionObserver is unavailable.
1138
+ /**
1139
+ * Gates drawing on "element intersects viewport AND the page tab is visible".
1140
+ * Falls back to always-visible when IntersectionObserver is unavailable.
1065
1141
  */
1066
1142
  function createVisibilityGuard(element) {
1143
+ var onChange = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
1067
1144
  var intersecting = true;
1068
1145
  var pageVisible = typeof document === 'undefined' || !document.hidden;
1069
1146
  var disposed = false;
@@ -1073,6 +1150,7 @@ function createVisibilityGuard(element) {
1073
1150
  intersecting = entries.some(function (entry) {
1074
1151
  return entry.isIntersecting;
1075
1152
  });
1153
+ if (typeof onChange === 'function') onChange();
1076
1154
  }, {
1077
1155
  rootMargin: '180px'
1078
1156
  });
@@ -1080,6 +1158,7 @@ function createVisibilityGuard(element) {
1080
1158
  }
1081
1159
  var onVisibilityChange = function onVisibilityChange() {
1082
1160
  pageVisible = typeof document !== 'undefined' && !document.hidden;
1161
+ if (typeof onChange === 'function') onChange();
1083
1162
  };
1084
1163
  if (typeof document !== 'undefined') {
1085
1164
  document.addEventListener('visibilitychange', onVisibilityChange);
@@ -1115,18 +1194,69 @@ function nextTick(fn) {
1115
1194
  }
1116
1195
  }
1117
1196
 
1118
- function prefersReducedMotion$2() {
1119
- return typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches;
1197
+ function normalizeFps(value) {
1198
+ var fallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 60;
1199
+ var fps = Number(value);
1200
+ if (!Number.isFinite(fps)) return fallback;
1201
+ return Math.min(60, Math.max(1, fps));
1202
+ }
1203
+ function createFrameGate() {
1204
+ var initialFps = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 60;
1205
+ var fps = normalizeFps(initialFps);
1206
+ var elapsed = 0;
1207
+ return {
1208
+ shouldDraw: function shouldDraw(delta) {
1209
+ elapsed += delta;
1210
+ var interval = 1 / fps;
1211
+ if (elapsed + 0.0001 < interval) return false;
1212
+ elapsed %= interval;
1213
+ return true;
1214
+ },
1215
+ setFps: function setFps(value) {
1216
+ fps = normalizeFps(value, fps);
1217
+ elapsed = 0;
1218
+ return fps;
1219
+ },
1220
+ getFps: function getFps() {
1221
+ return fps;
1222
+ }
1223
+ };
1224
+ }
1225
+ function createReducedMotionPreference(enabled, onChange) {
1226
+ var query = enabled && typeof matchMedia !== 'undefined' ? matchMedia('(prefers-reduced-motion: reduce)') : null;
1227
+ var notify = function notify() {
1228
+ if (typeof onChange === 'function') onChange(Boolean(query && query.matches));
1229
+ };
1230
+ if (query) {
1231
+ if (typeof query.addEventListener === 'function') query.addEventListener('change', notify);else if (typeof query.addListener === 'function') query.addListener(notify);
1232
+ }
1233
+ return {
1234
+ matches: function matches() {
1235
+ return Boolean(query && query.matches);
1236
+ },
1237
+ dispose: function dispose() {
1238
+ if (!query) return;
1239
+ if (typeof query.removeEventListener === 'function') query.removeEventListener('change', notify);else if (typeof query.removeListener === 'function') query.removeListener(notify);
1240
+ }
1241
+ };
1242
+ }
1243
+
1244
+ function toFiniteNumber(value) {
1245
+ if (value == null || typeof value === 'boolean') return null;
1246
+ if (typeof value === 'string' && value.trim() === '') return null;
1247
+ var number = Number(value);
1248
+ return Number.isFinite(number) ? number : null;
1120
1249
  }
1121
1250
 
1122
- /**
1251
+ /**
1123
1252
  * Mount a cosmic (nebula) capsule into `container`.
1124
1253
  *
1125
1254
  * Options: preset (literary name), width, height (number=px or string with
1126
1255
  * px/%/vw/vh/em/rem), colors, seed, speed, textRatio (0-100, text region
1127
1256
  * width in percent), text (HTML string or DOM nodes for the text slot),
1128
1257
  * colorContent (HTML string or DOM nodes for the color slot), quality,
1129
- * renderer, respectReducedMotion, mouseColor, cssVars.
1258
+ * renderer, quality, renderScale, powerPreference, fps, paused/static,
1259
+ * respectReducedMotion, mouseColor, cssVars.
1130
1260
  */
1131
1261
  function createCapsule(container) {
1132
1262
  var _options$preset;
@@ -1136,10 +1266,19 @@ function createCapsule(container) {
1136
1266
  }
1137
1267
  var preset = _objectSpread2({}, getPreset('capsule', (_options$preset = options.preset) !== null && _options$preset !== void 0 ? _options$preset : '初光'));
1138
1268
  var merged = normalizeOptions(DEFAULTS.capsule, preset, options);
1139
- var normalizedColors = merged.colors.map(normalizeColor);
1140
- if (normalizedColors.every(Boolean)) preset.colors = normalizedColors;
1141
- preset.seed = merged.seed;
1142
- preset.speed = merged.speed;
1269
+ var normalizedColors = Array.isArray(merged.colors) ? merged.colors.map(normalizeColor) : [];
1270
+ if (normalizedColors.length === 4 && normalizedColors.every(Boolean)) preset.colors = normalizedColors;
1271
+ var initialSeed = toFiniteNumber(merged.seed);
1272
+ var initialSpeed = toFiniteNumber(merged.speed);
1273
+ if (initialSeed !== null) preset.seed = initialSeed;
1274
+ if (initialSpeed !== null) preset.speed = initialSpeed;
1275
+ merged.colors = _toConsumableArray(preset.colors);
1276
+ merged.seed = preset.seed;
1277
+ merged.speed = preset.speed;
1278
+ var optionColors = Array.isArray(options.colors) ? options.colors.map(normalizeColor) : [];
1279
+ var colorOverride = optionColors.length === 4 && optionColors.every(Boolean) ? _toConsumableArray(optionColors) : null;
1280
+ var seedOverride = options.seed !== undefined ? toFiniteNumber(options.seed) : null;
1281
+ var speedOverride = options.speed !== undefined ? toFiniteNumber(options.speed) : null;
1143
1282
  var customLabel = typeof options.label === 'string' && options.label ? options.label : null;
1144
1283
  var root = document.createElement('div');
1145
1284
  root.className = 'hj-capsule-root hj-capsule-cosmic';
@@ -1191,25 +1330,49 @@ function createCapsule(container) {
1191
1330
  root.appendChild(canvas);
1192
1331
  container.appendChild(root);
1193
1332
  var emitter = createEmitter();
1194
- var paused = merged.respectReducedMotion && prefersReducedMotion$2();
1333
+ var manuallyPaused = merged.paused === true;
1334
+ var reducedPaused = false;
1335
+ var staticMode = merged.static === true;
1336
+ var contextLost = false;
1195
1337
  var disposed = false;
1338
+ var renderOnce = function renderOnce() {};
1196
1339
  var dirty = {
1197
1340
  preset: false,
1198
1341
  seed: false,
1199
1342
  speed: false,
1200
1343
  colors: false,
1201
1344
  textRatio: false,
1202
- cssVars: false
1345
+ cssVars: false,
1346
+ mouseColor: false,
1347
+ quality: false,
1348
+ renderScale: false,
1349
+ paused: false,
1350
+ static: false,
1351
+ fps: false
1203
1352
  };
1204
1353
  var renderer;
1205
1354
  var useWebgl = merged.renderer !== 'canvas2d';
1206
1355
  if (useWebgl) {
1207
1356
  try {
1208
1357
  renderer = new CosmicRenderer(canvas, merged, {
1209
- dprCap: dprCapFor(merged.quality)
1358
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
1359
+ mouseColor: merged.mouseColor !== false,
1360
+ powerPreference: merged.powerPreference,
1361
+ onContextLost: function onContextLost() {
1362
+ contextLost = true;
1363
+ emitter.emit('contextlost', {});
1364
+ },
1365
+ onContextRestored: function onContextRestored() {
1366
+ contextLost = false;
1367
+ emitter.emit('contextrestored', {});
1368
+ renderOnce();
1369
+ wakeScheduler();
1370
+ }
1210
1371
  });
1211
1372
  } catch (error) {
1212
- renderer = new FallbackRenderer(canvas, merged);
1373
+ renderer = new FallbackRenderer(canvas, merged, {
1374
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale)
1375
+ });
1213
1376
  nextTick(function () {
1214
1377
  if (disposed) return;
1215
1378
  emitter.emit('error', {
@@ -1218,8 +1381,15 @@ function createCapsule(container) {
1218
1381
  });
1219
1382
  }
1220
1383
  } else {
1221
- renderer = new FallbackRenderer(canvas, merged);
1384
+ renderer = new FallbackRenderer(canvas, merged, {
1385
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale)
1386
+ });
1222
1387
  }
1388
+ var animationTime = 0;
1389
+ var frameGate = createFrameGate(merged.fps);
1390
+ renderOnce = function renderOnce() {
1391
+ return renderer.draw(animationTime);
1392
+ };
1223
1393
  var applySize = function applySize() {
1224
1394
  root.style.width = parseSize(merged.width);
1225
1395
  root.style.height = parseSize(merged.height);
@@ -1237,11 +1407,21 @@ function createCapsule(container) {
1237
1407
  renderer.resize();
1238
1408
  };
1239
1409
  applySize();
1240
- var resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(function () {
1241
- return renderer.resize();
1242
- }) : null;
1243
- if (resizeObserver) resizeObserver.observe(root);else window.addEventListener('resize', renderer.resize);
1244
- var visibility = createVisibilityGuard(root);
1410
+ var resize = function resize() {
1411
+ renderer.resize();
1412
+ if (manuallyPaused || reducedPaused || staticMode) renderOnce();
1413
+ };
1414
+ var resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(resize) : null;
1415
+ if (resizeObserver) resizeObserver.observe(root);else window.addEventListener('resize', resize);
1416
+ var visibility = createVisibilityGuard(root, wakeScheduler);
1417
+ var motionPreference = createReducedMotionPreference(merged.respectReducedMotion, function (matches) {
1418
+ reducedPaused = matches;
1419
+ if (matches) renderOnce();else wakeScheduler();
1420
+ });
1421
+ reducedPaused = motionPreference.matches();
1422
+ var isMotionPaused = function isMotionPaused() {
1423
+ return manuallyPaused || reducedPaused || staticMode || contextLost;
1424
+ };
1245
1425
  root.addEventListener('pointerenter', function () {
1246
1426
  return emitter.emit('pointerenter', {
1247
1427
  preset: _objectSpread2({}, preset)
@@ -1276,12 +1456,12 @@ function createCapsule(container) {
1276
1456
  preset: _objectSpread2({}, preset)
1277
1457
  });
1278
1458
  });
1279
- var animationTime = 0;
1459
+ renderOnce();
1280
1460
  var unsubscribe = subscribeScheduler(function (delta) {
1281
1461
  animationTime += delta;
1282
- if (visibility.isVisible()) renderer.draw(animationTime);
1462
+ if (frameGate.shouldDraw(delta)) renderer.draw(animationTime);
1283
1463
  }, function () {
1284
- return paused;
1464
+ return isMotionPaused() || !visibility.isVisible();
1285
1465
  });
1286
1466
  nextTick(function () {
1287
1467
  if (disposed) return;
@@ -1304,11 +1484,15 @@ function createCapsule(container) {
1304
1484
  var next = getPreset('capsule', ref);
1305
1485
  dirty.preset = true;
1306
1486
  Object.assign(preset, next);
1487
+ if (colorOverride) preset.colors = _toConsumableArray(colorOverride);
1488
+ if (seedOverride !== null) preset.seed = seedOverride;
1489
+ if (speedOverride !== null) preset.speed = speedOverride;
1307
1490
  var nextColors = preset.colors.map(normalizeColor);
1308
1491
  if (nextColors.every(Boolean)) preset.colors = nextColors;
1309
1492
  renderer.setPreset(_objectSpread2({}, preset));
1310
1493
  syncTheme();
1311
1494
  renderer.resize();
1495
+ if (isMotionPaused()) renderOnce();
1312
1496
  emitter.emit('presetchange', {
1313
1497
  preset: _objectSpread2({}, next)
1314
1498
  });
@@ -1320,26 +1504,32 @@ function createCapsule(container) {
1320
1504
  return !color;
1321
1505
  })) return this;
1322
1506
  dirty.colors = true;
1507
+ colorOverride = _toConsumableArray(next);
1323
1508
  preset.colors = next;
1324
1509
  renderer.setPreset(_objectSpread2(_objectSpread2({}, preset), {}, {
1325
1510
  colors: next
1326
1511
  }));
1512
+ if (isMotionPaused()) renderOnce();
1327
1513
  return this;
1328
1514
  },
1329
1515
  setSeed: function setSeed(seed) {
1330
- var value = Number(seed);
1331
- if (!Number.isFinite(value)) return this;
1516
+ var value = toFiniteNumber(seed);
1517
+ if (value === null) return this;
1332
1518
  dirty.seed = true;
1519
+ seedOverride = value;
1333
1520
  preset.seed = value;
1334
1521
  renderer.setPreset(_objectSpread2({}, preset));
1522
+ if (isMotionPaused()) renderOnce();
1335
1523
  return this;
1336
1524
  },
1337
1525
  setSpeed: function setSpeed(speed) {
1338
- var value = Number(speed);
1339
- if (!Number.isFinite(value)) return this;
1526
+ var value = toFiniteNumber(speed);
1527
+ if (value === null) return this;
1340
1528
  dirty.speed = true;
1529
+ speedOverride = value;
1341
1530
  preset.speed = value;
1342
1531
  renderer.setPreset(_objectSpread2({}, preset));
1532
+ if (isMotionPaused()) renderOnce();
1343
1533
  return this;
1344
1534
  },
1345
1535
  setText: function setText(content) {
@@ -1375,6 +1565,7 @@ function createCapsule(container) {
1375
1565
  return this;
1376
1566
  },
1377
1567
  setMouseColor: function setMouseColor(value) {
1568
+ dirty.mouseColor = true;
1378
1569
  merged.mouseColor = value !== false;
1379
1570
  if (typeof renderer.setMouseColor === 'function') renderer.setMouseColor(merged.mouseColor);
1380
1571
  return this;
@@ -1389,6 +1580,7 @@ function createCapsule(container) {
1389
1580
  merged.height = height;
1390
1581
  }
1391
1582
  applySize();
1583
+ if (isMotionPaused()) renderOnce();
1392
1584
  return this;
1393
1585
  },
1394
1586
  randomize: function randomize() {
@@ -1396,23 +1588,59 @@ function createCapsule(container) {
1396
1588
  return this;
1397
1589
  },
1398
1590
  pause: function pause() {
1399
- paused = true;
1591
+ dirty.paused = true;
1592
+ manuallyPaused = true;
1593
+ renderOnce();
1400
1594
  return this;
1401
1595
  },
1402
1596
  resume: function resume() {
1403
- paused = false;
1597
+ dirty.paused = true;
1598
+ manuallyPaused = false;
1599
+ wakeScheduler();
1600
+ return this;
1601
+ },
1602
+ setPaused: function setPaused(value) {
1603
+ return value ? this.pause() : this.resume();
1604
+ },
1605
+ setStatic: function setStatic(value) {
1606
+ dirty.static = true;
1607
+ staticMode = value === true;
1608
+ merged.static = staticMode;
1609
+ if (staticMode) renderOnce();else wakeScheduler();
1610
+ return this;
1611
+ },
1612
+ setFps: function setFps(fps) {
1613
+ dirty.fps = true;
1614
+ merged.fps = frameGate.setFps(fps);
1615
+ wakeScheduler();
1404
1616
  return this;
1405
1617
  },
1406
1618
  setQuality: function setQuality(quality) {
1619
+ dirty.quality = true;
1407
1620
  merged.quality = quality;
1408
- if (typeof renderer.setDprCap === 'function') renderer.setDprCap(dprCapFor(quality));
1621
+ if (typeof renderer.setDprCap === 'function') {
1622
+ renderer.setDprCap(effectiveDprCap(quality, merged.renderScale));
1623
+ }
1624
+ if (isMotionPaused()) renderOnce();
1625
+ return this;
1626
+ },
1627
+ setRenderScale: function setRenderScale(renderScale) {
1628
+ var value = Number(renderScale);
1629
+ if (!Number.isFinite(value)) return this;
1630
+ dirty.renderScale = true;
1631
+ merged.renderScale = Math.min(1, Math.max(0.25, value));
1632
+ if (typeof renderer.setDprCap === 'function') {
1633
+ renderer.setDprCap(effectiveDprCap(merged.quality, merged.renderScale));
1634
+ }
1635
+ if (isMotionPaused()) renderOnce();
1409
1636
  return this;
1410
1637
  },
1411
1638
  dispose: function dispose() {
1412
1639
  disposed = true;
1413
1640
  unsubscribe();
1414
1641
  visibility.dispose();
1415
- if (resizeObserver) resizeObserver.disconnect();else window.removeEventListener('resize', renderer.resize);
1642
+ motionPreference.dispose();
1643
+ if (resizeObserver) resizeObserver.disconnect();else window.removeEventListener('resize', resize);
1416
1644
  renderer.dispose();
1417
1645
  root.remove();
1418
1646
  },
@@ -1422,6 +1650,24 @@ function createCapsule(container) {
1422
1650
  get cssVars() {
1423
1651
  return merged.cssVars;
1424
1652
  },
1653
+ get mouseColor() {
1654
+ return merged.mouseColor;
1655
+ },
1656
+ get quality() {
1657
+ return merged.quality;
1658
+ },
1659
+ get renderScale() {
1660
+ return merged.renderScale;
1661
+ },
1662
+ get paused() {
1663
+ return manuallyPaused;
1664
+ },
1665
+ get static() {
1666
+ return staticMode;
1667
+ },
1668
+ get fps() {
1669
+ return frameGate.getFps();
1670
+ },
1425
1671
  dirty: dirty
1426
1672
  };
1427
1673
  }
@@ -1452,6 +1698,13 @@ var PROFILE_CONFIG = {
1452
1698
  detail: 0.06,
1453
1699
  lobe: 0.23
1454
1700
  },
1701
+ 'botanic-lab': {
1702
+ seed: 3.46,
1703
+ broad: 0.54,
1704
+ middle: 0.18,
1705
+ detail: 0.05,
1706
+ lobe: 0.28
1707
+ },
1455
1708
  // ponytail: first-pass tide = asymmetric time warp on the same motion pipeline.
1456
1709
  // Refine (foam line / wash streaks) in the shader after visual QA.
1457
1710
  'tide': {
@@ -1562,64 +1815,95 @@ function createProgram(gl) {
1562
1815
  var ProgressFlowRenderer = /*#__PURE__*/function () {
1563
1816
  function ProgressFlowRenderer(canvas, preset) {
1564
1817
  var _PROFILE_INDEX$preset;
1818
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
1565
1819
  _classCallCheck(this, ProgressFlowRenderer);
1566
1820
  var gl = canvas.getContext('webgl2', {
1567
1821
  alpha: false,
1568
1822
  antialias: true,
1569
1823
  premultipliedAlpha: false,
1570
- powerPreference: 'high-performance'
1824
+ powerPreference: options.powerPreference || 'high-performance'
1571
1825
  });
1572
1826
  if (!gl) throw new Error('WebGL2 unavailable');
1573
1827
  this.canvas = canvas;
1828
+ this.options = options;
1574
1829
  this.gl = gl;
1575
- this.program = createProgram(gl);
1576
1830
  this.profile = preset.edgeStyle === 'tide' ? 3 : (_PROFILE_INDEX$preset = PROFILE_INDEX[preset.id]) !== null && _PROFILE_INDEX$preset !== void 0 ? _PROFILE_INDEX$preset : 2;
1577
1831
  this.seed = stringSeed$1("".concat(preset.id, "-shader")) * 13.7 + 1.0;
1578
1832
  this.colors = preset.colors.map(hexToRgb01);
1579
1833
  this.motionData = getProgressMotionData(preset.id, preset.edgeStyle);
1580
1834
  var motionFactor = preset.edgeStyle === 'tide' ? MOTION_SCALE_FACTORS.tide : MOTION_SCALE_FACTORS[preset.id] || 1.04;
1581
1835
  this.motionScale = PROGRESS_MOTION_MAX_PX * motionFactor / 1257;
1582
- this.position = gl.getAttribLocation(this.program, 'a_position');
1583
- this.uniforms = {
1584
- resolution: gl.getUniformLocation(this.program, 'u_resolution'),
1585
- time: gl.getUniformLocation(this.program, 'u_time'),
1586
- progress: gl.getUniformLocation(this.program, 'u_progress'),
1587
- seed: gl.getUniformLocation(this.program, 'u_seed'),
1588
- profile: gl.getUniformLocation(this.program, 'u_profile'),
1589
- motion: gl.getUniformLocation(this.program, 'u_motion'),
1590
- effect: gl.getUniformLocation(this.program, 'u_effect'),
1591
- hasEffect: gl.getUniformLocation(this.program, 'u_hasEffect'),
1592
- effectFrames: gl.getUniformLocation(this.program, 'u_effectFrames'),
1593
- motionDuration: gl.getUniformLocation(this.program, 'u_motionDuration'),
1594
- motionScale: gl.getUniformLocation(this.program, 'u_motionScale'),
1595
- dark: gl.getUniformLocation(this.program, 'u_dark'),
1596
- accentA: gl.getUniformLocation(this.program, 'u_accentA'),
1597
- accentB: gl.getUniformLocation(this.program, 'u_accentB'),
1598
- glow: gl.getUniformLocation(this.program, 'u_glow')
1599
- };
1600
- this.buffer = gl.createBuffer();
1601
- gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
1602
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]), gl.STATIC_DRAW);
1603
- this.motionTexture = gl.createTexture();
1604
- gl.activeTexture(gl.TEXTURE0);
1605
- gl.bindTexture(gl.TEXTURE_2D, this.motionTexture);
1606
- gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
1607
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8, PROGRESS_MOTION_WIDTH, PROGRESS_MOTION_HEIGHT, 0, gl.RED, gl.UNSIGNED_BYTE, this.motionData);
1608
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
1609
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
1610
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
1611
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
1612
- this.effectTexture = gl.createTexture();
1613
- gl.activeTexture(gl.TEXTURE1);
1614
- gl.bindTexture(gl.TEXTURE_2D, this.effectTexture);
1615
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, 1, 1, 0, gl.RGB, gl.UNSIGNED_BYTE, new Uint8Array([32, 33, 38]));
1616
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
1617
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
1618
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
1619
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
1620
- this.effectUploaded = false;
1836
+ this.disposed = false;
1837
+ this.contextLost = false;
1838
+ this.setupResources();
1839
+ this.bindContextEvents();
1621
1840
  }
1622
1841
  return _createClass(ProgressFlowRenderer, [{
1842
+ key: "setupResources",
1843
+ value: function setupResources() {
1844
+ var gl = this.gl;
1845
+ this.program = createProgram(gl);
1846
+ this.position = gl.getAttribLocation(this.program, 'a_position');
1847
+ this.uniforms = {
1848
+ resolution: gl.getUniformLocation(this.program, 'u_resolution'),
1849
+ time: gl.getUniformLocation(this.program, 'u_time'),
1850
+ progress: gl.getUniformLocation(this.program, 'u_progress'),
1851
+ seed: gl.getUniformLocation(this.program, 'u_seed'),
1852
+ profile: gl.getUniformLocation(this.program, 'u_profile'),
1853
+ motion: gl.getUniformLocation(this.program, 'u_motion'),
1854
+ effect: gl.getUniformLocation(this.program, 'u_effect'),
1855
+ hasEffect: gl.getUniformLocation(this.program, 'u_hasEffect'),
1856
+ effectFrames: gl.getUniformLocation(this.program, 'u_effectFrames'),
1857
+ motionDuration: gl.getUniformLocation(this.program, 'u_motionDuration'),
1858
+ motionScale: gl.getUniformLocation(this.program, 'u_motionScale'),
1859
+ dark: gl.getUniformLocation(this.program, 'u_dark'),
1860
+ accentA: gl.getUniformLocation(this.program, 'u_accentA'),
1861
+ accentB: gl.getUniformLocation(this.program, 'u_accentB'),
1862
+ glow: gl.getUniformLocation(this.program, 'u_glow')
1863
+ };
1864
+ this.buffer = gl.createBuffer();
1865
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
1866
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]), gl.STATIC_DRAW);
1867
+ this.motionTexture = gl.createTexture();
1868
+ gl.activeTexture(gl.TEXTURE0);
1869
+ gl.bindTexture(gl.TEXTURE_2D, this.motionTexture);
1870
+ gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
1871
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8, PROGRESS_MOTION_WIDTH, PROGRESS_MOTION_HEIGHT, 0, gl.RED, gl.UNSIGNED_BYTE, this.motionData);
1872
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
1873
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
1874
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
1875
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
1876
+ this.effectTexture = gl.createTexture();
1877
+ gl.activeTexture(gl.TEXTURE1);
1878
+ gl.bindTexture(gl.TEXTURE_2D, this.effectTexture);
1879
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, 1, 1, 0, gl.RGB, gl.UNSIGNED_BYTE, new Uint8Array([32, 33, 38]));
1880
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
1881
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
1882
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
1883
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
1884
+ this.effectUploaded = false;
1885
+ }
1886
+ }, {
1887
+ key: "bindContextEvents",
1888
+ value: function bindContextEvents() {
1889
+ var _this = this;
1890
+ this.onContextLost = function (event) {
1891
+ event.preventDefault();
1892
+ if (_this.disposed) return;
1893
+ _this.contextLost = true;
1894
+ if (typeof _this.options.onContextLost === 'function') _this.options.onContextLost(event);
1895
+ };
1896
+ this.onContextRestored = function () {
1897
+ if (_this.disposed) return;
1898
+ _this.setupResources();
1899
+ _this.contextLost = false;
1900
+ _this.gl.viewport(0, 0, _this.canvas.width, _this.canvas.height);
1901
+ if (typeof _this.options.onContextRestored === 'function') _this.options.onContextRestored();
1902
+ };
1903
+ this.canvas.addEventListener('webglcontextlost', this.onContextLost, false);
1904
+ this.canvas.addEventListener('webglcontextrestored', this.onContextRestored, false);
1905
+ }
1906
+ }, {
1623
1907
  key: "resize",
1624
1908
  value: function resize(width, height, dpr) {
1625
1909
  var pixelWidth = Math.max(1, Math.round(width * dpr));
@@ -1634,6 +1918,7 @@ var ProgressFlowRenderer = /*#__PURE__*/function () {
1634
1918
  key: "draw",
1635
1919
  value: function draw(time, progress) {
1636
1920
  var effectImage = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
1921
+ if (this.disposed || this.contextLost) return;
1637
1922
  var gl = this.gl;
1638
1923
  gl.useProgram(this.program);
1639
1924
  gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
@@ -1650,7 +1935,8 @@ var ProgressFlowRenderer = /*#__PURE__*/function () {
1650
1935
  gl.uniform1f(this.uniforms.motionDuration, PROGRESS_MOTION_DURATION);
1651
1936
  gl.uniform1f(this.uniforms.motionScale, this.motionScale);
1652
1937
  var hasEffect = this.effectUploaded ? 1 : 0;
1653
- if (!this.effectUploaded && effectImage && effectImage.complete && effectImage.naturalWidth > 0) {
1938
+ var effectWidth = effectImage && (effectImage.naturalWidth || effectImage.width || 0);
1939
+ if (!this.effectUploaded && effectImage && effectWidth > 0) {
1654
1940
  gl.activeTexture(gl.TEXTURE1);
1655
1941
  gl.bindTexture(gl.TEXTURE_2D, this.effectTexture);
1656
1942
  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
@@ -1682,7 +1968,10 @@ var ProgressFlowRenderer = /*#__PURE__*/function () {
1682
1968
  }, {
1683
1969
  key: "dispose",
1684
1970
  value: function dispose() {
1971
+ this.disposed = true;
1685
1972
  var gl = this.gl;
1973
+ this.canvas.removeEventListener('webglcontextlost', this.onContextLost, false);
1974
+ this.canvas.removeEventListener('webglcontextrestored', this.onContextRestored, false);
1686
1975
  gl.deleteBuffer(this.buffer);
1687
1976
  gl.deleteTexture(this.motionTexture);
1688
1977
  gl.deleteTexture(this.effectTexture);
@@ -1693,8 +1982,9 @@ var ProgressFlowRenderer = /*#__PURE__*/function () {
1693
1982
  }]);
1694
1983
  }();
1695
1984
  function createProgressFlowRenderer(canvas, preset) {
1985
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
1696
1986
  try {
1697
- return new ProgressFlowRenderer(canvas, preset);
1987
+ return new ProgressFlowRenderer(canvas, preset, options);
1698
1988
  } catch (error) {
1699
1989
  console.warn('[画境观屿] 进度流体 WebGL2 不可用,使用 Canvas 2D 降级。', error);
1700
1990
  return null;
@@ -1710,7 +2000,8 @@ var PALETTES = {
1710
2000
  'model-training': ['#20131f', '#ff3f94', '#ff8a3d', '#fff06a'],
1711
2001
  'agent-migration': ['#111a31', '#245bff', '#00cfff', '#5dffe6'],
1712
2002
  'visual-training': ['#1f172b', '#7042ff', '#42f58d', '#c4ff8a'],
1713
- 'tide': ['#0a2239', '#2e9bff', '#7fe3ff', '#eaf9ff']
2003
+ 'tide': ['#0a2239', '#2e9bff', '#7fe3ff', '#eaf9ff'],
2004
+ 'botanic-lab': ['#102a23', '#287a58', '#9bd65b', '#f2ffc4']
1714
2005
  };
1715
2006
  function drawCloud(context, x, y, radiusX, radiusY, color, alpha) {
1716
2007
  context.save();
@@ -1754,50 +2045,49 @@ function createAtlas(id) {
1754
2045
  context.fillRect(20, 0, 44, FRAME_HEIGHT);
1755
2046
  context.restore();
1756
2047
  }
1757
- var image = new Image();
1758
- image.decoding = 'async';
1759
- var state = {
1760
- image: image,
1761
- ready: false
2048
+
2049
+ // A canvas is a valid TexImageSource. Keeping it directly avoids a PNG
2050
+ // encode -> base64 allocation -> Image decode round trip at startup.
2051
+ return {
2052
+ image: canvas,
2053
+ ready: true
1762
2054
  };
1763
- image.addEventListener('load', function () {
1764
- state.ready = true;
1765
- }, {
1766
- once: true
1767
- });
1768
- image.addEventListener('error', function () {
1769
- state.ready = false;
1770
- }, {
1771
- once: true
1772
- });
1773
- image.src = canvas.toDataURL('image/png');
1774
- return state;
1775
2055
  }
1776
2056
  function getProgressReferenceAtlas(id) {
1777
2057
  if (!CACHE[id]) CACHE[id] = createAtlas(id);
1778
2058
  return CACHE[id];
1779
2059
  }
1780
2060
 
1781
- /**
1782
- * Attach a WebGL2 fluid overlay to a progress capsule root.
1783
- *
1784
- * @param {object} params
1785
- * @param {HTMLElement} params.root progress capsule root element
1786
- * @param {HTMLCanvasElement} params.canvas 2D fallback canvas (kept beneath)
1787
- * @param {object} params.preset progress preset
1788
- * @param {() => number} params.getProgress reads the current progress value
1789
- * @returns {{ update(flowTime: number): void, dispose(): void } | null}
2061
+ /**
2062
+ * Attach a WebGL2 fluid overlay to a progress capsule root.
2063
+ *
2064
+ * @param {object} params
2065
+ * @param {HTMLElement} params.root progress capsule root element
2066
+ * @param {HTMLCanvasElement} params.canvas 2D fallback canvas (kept beneath)
2067
+ * @param {object} params.preset progress preset
2068
+ * @param {() => number} params.getProgress reads the current progress value
2069
+ * @returns {{ update(flowTime: number): void, setDprCap(cap: number): void, dispose(): void } | null}
1790
2070
  */
1791
2071
  function attachProgressFlowOverlay(_ref) {
1792
2072
  var root = _ref.root,
1793
2073
  canvas = _ref.canvas,
1794
2074
  preset = _ref.preset,
1795
- getProgress = _ref.getProgress;
2075
+ getProgress = _ref.getProgress,
2076
+ _ref$dprCap = _ref.dprCap,
2077
+ dprCap = _ref$dprCap === void 0 ? 2 : _ref$dprCap,
2078
+ _ref$powerPreference = _ref.powerPreference,
2079
+ powerPreference = _ref$powerPreference === void 0 ? 'high-performance' : _ref$powerPreference,
2080
+ onContextLost = _ref.onContextLost,
2081
+ onContextRestored = _ref.onContextRestored;
1796
2082
  var overlay = document.createElement('canvas');
1797
2083
  overlay.className = 'hj-progress-canvas hj-progress-overlay';
1798
2084
  overlay.setAttribute('aria-hidden', 'true');
1799
2085
  canvas.insertAdjacentElement('afterend', overlay);
1800
- var renderer = createProgressFlowRenderer(overlay, preset);
2086
+ var renderer = createProgressFlowRenderer(overlay, preset, {
2087
+ powerPreference: powerPreference,
2088
+ onContextLost: onContextLost,
2089
+ onContextRestored: onContextRestored
2090
+ });
1801
2091
  if (!renderer) {
1802
2092
  overlay.remove();
1803
2093
  return null;
@@ -1808,7 +2098,7 @@ function attachProgressFlowOverlay(_ref) {
1808
2098
  var bounds = root.getBoundingClientRect();
1809
2099
  var width = Math.max(1, bounds.width);
1810
2100
  var height = Math.max(1, bounds.height);
1811
- var dpr = Math.min(window.devicePixelRatio || 1, 2);
2101
+ var dpr = Math.min(window.devicePixelRatio || 1, dprCap);
1812
2102
  overlay.style.width = "".concat(width, "px");
1813
2103
  overlay.style.height = "".concat(height, "px");
1814
2104
  renderer.resize(width, height, dpr);
@@ -1832,6 +2122,10 @@ function attachProgressFlowOverlay(_ref) {
1832
2122
  setColors: function setColors(colors) {
1833
2123
  renderer.setColors(colors);
1834
2124
  },
2125
+ setDprCap: function setDprCap(cap) {
2126
+ dprCap = cap;
2127
+ resize();
2128
+ },
1835
2129
  dispose: function dispose() {
1836
2130
  if (resizeObserver) resizeObserver.disconnect();
1837
2131
  if (onWindowResize) window.removeEventListener('resize', onWindowResize);
@@ -1842,6 +2136,47 @@ function attachProgressFlowOverlay(_ref) {
1842
2136
  };
1843
2137
  }
1844
2138
 
2139
+ function normalizeRange(min, max) {
2140
+ var fallbackMin = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
2141
+ var fallbackMax = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 100;
2142
+ var nextMin = toFiniteNumber(min);
2143
+ var nextMax = toFiniteNumber(max);
2144
+ if (nextMin === null) nextMin = fallbackMin;
2145
+ if (nextMax === null) nextMax = fallbackMax;
2146
+ if (nextMin > nextMax) {
2147
+ var swap = nextMin;
2148
+ nextMin = nextMax;
2149
+ nextMax = swap;
2150
+ }
2151
+ return [nextMin, nextMax];
2152
+ }
2153
+ function clampProgressValue(value, min, max) {
2154
+ var number = toFiniteNumber(value);
2155
+ if (number === null) return null;
2156
+ return Math.min(Math.max(number, min), max);
2157
+ }
2158
+ function normalizeProgressStep(step) {
2159
+ var fallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'any';
2160
+ if (step === 'any' || step == null) return 'any';
2161
+ var increment = toFiniteNumber(step);
2162
+ return increment !== null && increment > 0 ? increment : fallback;
2163
+ }
2164
+ function snapProgressValue(value, min, max) {
2165
+ var step = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'any';
2166
+ var clamped = clampProgressValue(value, min, max);
2167
+ if (clamped === null) return null;
2168
+ if (step === 'any' || step == null) return clamped;
2169
+ var increment = normalizeProgressStep(step);
2170
+ if (increment === 'any') return clamped;
2171
+ var snapped = min + Math.round((clamped - min) / increment) * increment;
2172
+ return Math.min(Math.max(Number(snapped.toFixed(12)), min), max);
2173
+ }
2174
+ function progressRatio(value, min, max) {
2175
+ var range = max - min;
2176
+ if (!Number.isFinite(range) || range <= 0) return 0;
2177
+ return Math.min(Math.max((value - min) / range, 0), 1);
2178
+ }
2179
+
1845
2180
  var clamp = function clamp(value, min, max) {
1846
2181
  return Math.min(Math.max(value, min), max);
1847
2182
  };
@@ -1868,9 +2203,6 @@ function interpolate(template, values) {
1868
2203
  return values[key] !== undefined ? values[key] : '';
1869
2204
  });
1870
2205
  }
1871
- function prefersReducedMotion$1() {
1872
- return typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches;
1873
- }
1874
2206
  var FLOW_PROFILES = {
1875
2207
  'model-training': {
1876
2208
  cycles: [0.98, 3.05, 5.45],
@@ -1931,8 +2263,6 @@ var FLOW_PROFILES = {
1931
2263
  var ProgressCapsuleController = /*#__PURE__*/function () {
1932
2264
  function ProgressCapsuleController(_ref) {
1933
2265
  var _options$valueSuffix,
1934
- _options$min,
1935
- _options$max,
1936
2266
  _options$value,
1937
2267
  _this = this;
1938
2268
  var root = _ref.root,
@@ -1954,11 +2284,18 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1954
2284
  this.dirty = dirty;
1955
2285
  this.valueSuffix = (_options$valueSuffix = options.valueSuffix) !== null && _options$valueSuffix !== void 0 ? _options$valueSuffix : copy.valueSuffix;
1956
2286
  this.profile = preset.edgeStyle === 'tide' ? FLOW_PROFILES.tide : FLOW_PROFILES[preset.id] || FLOW_PROFILES['visual-training'];
1957
- this.min = (_options$min = options.min) !== null && _options$min !== void 0 ? _options$min : 0;
1958
- this.max = (_options$max = options.max) !== null && _options$max !== void 0 ? _options$max : 100;
1959
- this.dprCap = dprCapFor(options.quality);
2287
+ var _normalizeRange = normalizeRange(options.min, options.max);
2288
+ var _normalizeRange2 = _slicedToArray(_normalizeRange, 2);
2289
+ this.min = _normalizeRange2[0];
2290
+ this.max = _normalizeRange2[1];
2291
+ this.step = normalizeProgressStep(options.step);
2292
+ this.precision = Number.isInteger(Number(options.precision)) ? clamp(Number(options.precision), 0, 20) : 0;
2293
+ this.formatValue = typeof options.formatValue === 'function' ? options.formatValue : null;
2294
+ this.direction = options.direction === 'rtl' ? 'rtl' : 'ltr';
2295
+ this.dprCap = effectiveDprCap(options.quality, options.renderScale);
1960
2296
  this.ctx = canvas.getContext('2d');
1961
- this.value = clamp((_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : preset.initialProgress, this.min, this.max);
2297
+ this.value = snapProgressValue((_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : preset.initialProgress, this.min, this.max, this.step);
2298
+ if (this.value === null) this.value = clamp(preset.initialProgress, this.min, this.max);
1962
2299
  this.dragging = false;
1963
2300
  this.webglActive = false;
1964
2301
  this.flowTime = stringSeed(preset.id) * 31;
@@ -1968,10 +2305,12 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1968
2305
  this.width = 0;
1969
2306
  this.height = 0;
1970
2307
  this.handlers = {};
1971
- this.resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(function () {
1972
- return _this.resizeCanvas();
1973
- }) : null;
1974
- if (this.resizeObserver) this.resizeObserver.observe(this.root);else window.addEventListener('resize', this.resizeCanvas);
2308
+ this.onResize = function () {
2309
+ _this.resizeCanvas();
2310
+ if (typeof _this.options.onResize === 'function') _this.options.onResize();
2311
+ };
2312
+ this.resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(this.onResize) : null;
2313
+ if (this.resizeObserver) this.resizeObserver.observe(this.root);else window.addEventListener('resize', this.onResize);
1975
2314
  this.suppressEvents = true;
1976
2315
  this.setProgress(this.value, 'init');
1977
2316
  this.suppressEvents = false;
@@ -1984,16 +2323,37 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1984
2323
  this.randomState = Math.imul(this.randomState, 1664525) + 1013904223 >>> 0;
1985
2324
  return this.randomState / 4294967296;
1986
2325
  }
2326
+ }, {
2327
+ key: "displayText",
2328
+ value: function displayText() {
2329
+ if (this.formatValue) {
2330
+ return String(this.formatValue(this.value, {
2331
+ min: this.min,
2332
+ max: this.max,
2333
+ suffix: this.valueSuffix
2334
+ }));
2335
+ }
2336
+ var number = this.precision > 0 ? this.value.toFixed(this.precision) : String(Math.round(this.value));
2337
+ return "".concat(number).concat(this.valueSuffix);
2338
+ }
2339
+ }, {
2340
+ key: "syncValueDom",
2341
+ value: function syncValueDom() {
2342
+ var text = this.displayText();
2343
+ this.root.style.setProperty('--progress', this.value.toFixed(2));
2344
+ this.root.style.setProperty('--progress-ratio', progressRatio(this.value, this.min, this.max).toFixed(4));
2345
+ this.root.setAttribute('aria-valuenow', String(this.value));
2346
+ this.root.setAttribute('aria-valuetext', text);
2347
+ if (this.valueElement) this.valueElement.textContent = text;
2348
+ }
1987
2349
  }, {
1988
2350
  key: "setProgress",
1989
2351
  value: function setProgress(nextValue) {
1990
2352
  var source = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'auto';
1991
- this.value = clamp(nextValue, this.min, this.max);
1992
- var rounded = Math.round(this.value);
1993
- this.root.style.setProperty('--progress', this.value.toFixed(2));
1994
- this.root.setAttribute('aria-valuenow', String(rounded));
1995
- this.root.setAttribute('aria-valuetext', "".concat(rounded).concat(this.valueSuffix));
1996
- if (this.valueElement) this.valueElement.textContent = "".concat(rounded).concat(this.valueSuffix);
2353
+ var next = snapProgressValue(nextValue, this.min, this.max, this.step);
2354
+ if (next === null) return false;
2355
+ this.value = next;
2356
+ this.syncValueDom();
1997
2357
  if (!this.suppressEvents) {
1998
2358
  if (this.dirty) this.dirty.value = true;
1999
2359
  this.emitter.emit('change', {
@@ -2001,19 +2361,58 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
2001
2361
  source: source
2002
2362
  });
2003
2363
  }
2364
+ return true;
2004
2365
  }
2005
2366
  }, {
2006
2367
  key: "setValueSuffix",
2007
2368
  value: function setValueSuffix(suffix) {
2369
+ if (this.dirty) this.dirty.valueSuffix = true;
2008
2370
  this.valueSuffix = String(suffix == null ? '' : suffix);
2009
- var rounded = Math.round(this.value);
2010
- if (this.valueElement) this.valueElement.textContent = "".concat(rounded).concat(this.valueSuffix);
2011
- this.root.setAttribute('aria-valuetext', "".concat(rounded).concat(this.valueSuffix));
2371
+ this.syncValueDom();
2372
+ return this;
2373
+ }
2374
+ }, {
2375
+ key: "setStep",
2376
+ value: function setStep(step) {
2377
+ var nextStep = normalizeProgressStep(step, this.step);
2378
+ if (nextStep === this.step) return this;
2379
+ if (this.dirty) this.dirty.step = true;
2380
+ this.step = nextStep;
2381
+ var next = snapProgressValue(this.value, this.min, this.max, this.step);
2382
+ if (next !== null && next !== this.value) this.setProgress(next, 'prop');
2383
+ return this;
2384
+ }
2385
+ }, {
2386
+ key: "setPrecision",
2387
+ value: function setPrecision(precision) {
2388
+ var value = Number(precision);
2389
+ if (!Number.isInteger(value) || value < 0 || value > 20) return this;
2390
+ if (this.dirty) this.dirty.precision = true;
2391
+ this.precision = value;
2392
+ this.syncValueDom();
2393
+ return this;
2394
+ }
2395
+ }, {
2396
+ key: "setFormatValue",
2397
+ value: function setFormatValue(formatter) {
2398
+ if (formatter != null && typeof formatter !== 'function') return this;
2399
+ if (this.dirty) this.dirty.formatValue = true;
2400
+ this.formatValue = formatter || null;
2401
+ this.syncValueDom();
2402
+ return this;
2403
+ }
2404
+ }, {
2405
+ key: "setDirection",
2406
+ value: function setDirection(direction) {
2407
+ if (this.dirty) this.dirty.direction = true;
2408
+ this.direction = direction === 'rtl' ? 'rtl' : 'ltr';
2409
+ this.root.dataset.direction = this.direction;
2012
2410
  return this;
2013
2411
  }
2014
2412
  }, {
2015
2413
  key: "setShowValue",
2016
2414
  value: function setShowValue(show) {
2415
+ if (this.dirty) this.dirty.showValue = true;
2017
2416
  if (this.valueElement) this.valueElement.hidden = show === false;
2018
2417
  return this;
2019
2418
  }
@@ -2178,15 +2577,14 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
2178
2577
  }, {
2179
2578
  key: "setRange",
2180
2579
  value: function setRange(min, max) {
2181
- if (min > max) {
2182
- var swap = min;
2183
- min = max;
2184
- max = swap;
2185
- }
2186
- this.min = min;
2187
- this.max = max;
2188
- var clamped = clamp(this.value, this.min, this.max);
2189
- if (clamped !== this.value) this.setProgress(clamped, 'prop');
2580
+ var nextRange = normalizeRange(min, max, this.min, this.max);
2581
+ this.min = nextRange[0];
2582
+ this.max = nextRange[1];
2583
+ this.root.setAttribute('aria-valuemin', String(this.min));
2584
+ this.root.setAttribute('aria-valuemax', String(this.max));
2585
+ var next = snapProgressValue(this.value, this.min, this.max, this.step);
2586
+ if (next !== this.value) this.setProgress(next, 'prop');else this.syncValueDom();
2587
+ return this;
2190
2588
  }
2191
2589
  }, {
2192
2590
  key: "drawReferenceFlow",
@@ -2311,8 +2709,39 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
2311
2709
  value: function updateFromPointer(event) {
2312
2710
  var bounds = this.root.getBoundingClientRect();
2313
2711
  var ratio = bounds.width > 0 ? (event.clientX - bounds.left) / bounds.width : 0;
2712
+ if (this.direction === 'rtl') ratio = 1 - ratio;
2314
2713
  this.setProgress(this.min + ratio * (this.max - this.min), 'drag');
2315
2714
  }
2715
+ }, {
2716
+ key: "onKeyDown",
2717
+ value: function onKeyDown(event) {
2718
+ var range = this.max - this.min;
2719
+ var configuredStep = Number(this.step);
2720
+ var step = Number.isFinite(configuredStep) && configuredStep > 0 ? configuredStep : Math.max(range / 100, 1e-7);
2721
+ var direction = this.direction === 'rtl' ? -1 : 1;
2722
+ var aliases = {
2723
+ Left: 'ArrowLeft',
2724
+ Right: 'ArrowRight',
2725
+ Up: 'ArrowUp',
2726
+ Down: 'ArrowDown'
2727
+ };
2728
+ var keyCodes = {
2729
+ 35: 'End',
2730
+ 36: 'Home',
2731
+ 33: 'PageUp',
2732
+ 34: 'PageDown',
2733
+ 37: 'ArrowLeft',
2734
+ 38: 'ArrowUp',
2735
+ 39: 'ArrowRight',
2736
+ 40: 'ArrowDown'
2737
+ };
2738
+ var key = aliases[event.key] || event.key || keyCodes[event.keyCode];
2739
+ var next = null;
2740
+ if (key === 'ArrowRight') next = this.value + step * direction;else if (key === 'ArrowLeft') next = this.value - step * direction;else if (key === 'ArrowUp') next = this.value + step;else if (key === 'ArrowDown') next = this.value - step;else if (key === 'PageUp') next = this.value + step * 10;else if (key === 'PageDown') next = this.value - step * 10;else if (key === 'Home') next = this.min;else if (key === 'End') next = this.max;
2741
+ if (next === null) return;
2742
+ event.preventDefault();
2743
+ this.setProgress(next, 'keyboard');
2744
+ }
2316
2745
  }, {
2317
2746
  key: "beginDrag",
2318
2747
  value: function beginDrag(event) {
@@ -2400,6 +2829,12 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
2400
2829
  this.root.addEventListener('pointerup', this.handlers.pointerup);
2401
2830
  this.root.addEventListener('pointercancel', this.handlers.pointercancel);
2402
2831
  }
2832
+ if (this.options.keyboard !== false) {
2833
+ this.handlers.keydown = function (event) {
2834
+ return _this3.onKeyDown(event);
2835
+ };
2836
+ this.root.addEventListener('keydown', this.handlers.keydown);
2837
+ }
2403
2838
  }
2404
2839
  }, {
2405
2840
  key: "update",
@@ -2418,20 +2853,19 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
2418
2853
  key: "randomize",
2419
2854
  value: function randomize() {
2420
2855
  this.flowTime = this.random() * 40;
2856
+ return this.flowTime;
2421
2857
  }
2422
2858
  }, {
2423
2859
  key: "setColors",
2424
2860
  value: function setColors(colors) {
2425
2861
  if (!Array.isArray(colors) || colors.length !== 4) return;
2426
- this.preset = _objectSpread2(_objectSpread2({}, this.preset), {}, {
2427
- colors: colors
2428
- });
2862
+ this.preset.colors = _toConsumableArray(colors);
2429
2863
  }
2430
2864
  }, {
2431
2865
  key: "dispose",
2432
2866
  value: function dispose() {
2433
2867
  if (this._dragRaf) cancelAnimationFrame(this._dragRaf);
2434
- if (this.resizeObserver) this.resizeObserver.disconnect();else window.removeEventListener('resize', this.resizeCanvas);
2868
+ if (this.resizeObserver) this.resizeObserver.disconnect();else window.removeEventListener('resize', this.onResize);
2435
2869
  for (var _i = 0, _Object$keys = Object.keys(this.handlers); _i < _Object$keys.length; _i++) {
2436
2870
  var name = _Object$keys[_i];
2437
2871
  var handler = this.handlers[name];
@@ -2442,15 +2876,16 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
2442
2876
  }]);
2443
2877
  }();
2444
2878
 
2445
- /**
2879
+ /**
2446
2880
  * Mount a fluid progress capsule into `container`.
2447
2881
  *
2448
- * Options: preset (literary name), width, height, value, min, max,
2449
- * draggable, colors, edgeStyle, textRatio (0-100, text region
2882
+ * Options: preset (literary name), width, height, value/modelValue, min, max,
2883
+ * step, draggable, keyboard, direction, precision/formatValue, colors,
2884
+ * edgeStyle, textRatio (0-100, text region
2450
2885
  * width in percent), text (HTML string or DOM nodes for the text slot),
2451
2886
  * colorContent (HTML string or DOM nodes for the color slot),
2452
- * showValue (show/hide the right-side percentage), quality,
2453
- * respectReducedMotion, cssVars.
2887
+ * showValue (show/hide the right-side percentage), quality, renderScale,
2888
+ * powerPreference, fps, paused/static, respectReducedMotion, cssVars.
2454
2889
  */
2455
2890
  function createProgressCapsule(container) {
2456
2891
  var _options$preset, _merged$min, _merged$max;
@@ -2460,14 +2895,20 @@ function createProgressCapsule(container) {
2460
2895
  }
2461
2896
  var preset = _objectSpread2({}, getPreset('progress', (_options$preset = options.preset) !== null && _options$preset !== void 0 ? _options$preset : '星火'));
2462
2897
  var merged = normalizeOptions(DEFAULTS.progress, preset, options);
2463
- if (merged.min > merged.max) {
2464
- var swap = merged.min;
2465
- merged.min = merged.max;
2466
- merged.max = swap;
2467
- }
2468
- var normalizedColors = merged.colors.map(normalizeColor);
2469
- if (normalizedColors.every(Boolean)) preset.colors = normalizedColors;
2470
- preset.edgeStyle = merged.edgeStyle;
2898
+ var _normalizeRange3 = normalizeRange(merged.min, merged.max);
2899
+ var _normalizeRange4 = _slicedToArray(_normalizeRange3, 2);
2900
+ merged.min = _normalizeRange4[0];
2901
+ merged.max = _normalizeRange4[1];
2902
+ if (merged.modelValue !== undefined) merged.value = merged.modelValue;
2903
+ var normalizedColors = Array.isArray(merged.colors) ? merged.colors.map(normalizeColor) : [];
2904
+ if (normalizedColors.length === 4 && normalizedColors.every(Boolean)) preset.colors = normalizedColors;
2905
+ var initialEdgeStyle = merged.edgeStyle === 'tide' || merged.edgeStyle === 'flow' ? merged.edgeStyle : preset.edgeStyle;
2906
+ preset.edgeStyle = initialEdgeStyle;
2907
+ merged.edgeStyle = initialEdgeStyle;
2908
+ merged.colors = _toConsumableArray(preset.colors);
2909
+ var optionColors = Array.isArray(options.colors) ? options.colors.map(normalizeColor) : [];
2910
+ var colorOverride = optionColors.length === 4 && optionColors.every(Boolean) ? _toConsumableArray(optionColors) : null;
2911
+ var edgeStyleOverride = options.edgeStyle === 'flow' || options.edgeStyle === 'tide' ? merged.edgeStyle : null;
2471
2912
  var copy = COPY;
2472
2913
  var customLabel = typeof options.label === 'string' && options.label ? options.label : null;
2473
2914
  // Vue 的裸布尔属性(<ProgressCapsule disabled />)会传成空字符串,
@@ -2476,18 +2917,31 @@ function createProgressCapsule(container) {
2476
2917
  var readonly = merged.readonly === true || merged.readonly === '' || merged.readonly === 'true';
2477
2918
  var locked = disabled || readonly;
2478
2919
  var effectiveDraggable = locked ? false : merged.draggable !== false;
2920
+ var effectiveKeyboard = locked ? false : merged.keyboard !== false;
2479
2921
  var dirty = {
2480
2922
  preset: false,
2481
2923
  colors: false,
2482
2924
  textRatio: false,
2483
2925
  cssVars: false,
2484
2926
  edgeStyle: false,
2485
- value: false
2927
+ value: false,
2928
+ step: false,
2929
+ precision: false,
2930
+ formatValue: false,
2931
+ direction: false,
2932
+ showValue: false,
2933
+ valueSuffix: false,
2934
+ quality: false,
2935
+ renderScale: false,
2936
+ paused: false,
2937
+ static: false,
2938
+ fps: false
2486
2939
  };
2487
2940
  var root = document.createElement('div');
2488
2941
  root.className = 'hj-capsule-root hj-progress-root';
2489
2942
  root.setAttribute('role', 'slider');
2490
2943
  root.setAttribute('data-draggable', String(effectiveDraggable));
2944
+ root.dataset.direction = merged.direction === 'rtl' ? 'rtl' : 'ltr';
2491
2945
  if (disabled) {
2492
2946
  root.setAttribute('aria-disabled', 'true');
2493
2947
  root.classList.add('is-disabled');
@@ -2496,7 +2950,8 @@ function createProgressCapsule(container) {
2496
2950
  root.setAttribute('aria-readonly', 'true');
2497
2951
  root.classList.add('is-readonly');
2498
2952
  }
2499
- if (locked) root.setAttribute('tabindex', '-1');
2953
+ root.setAttribute('tabindex', locked ? '-1' : effectiveKeyboard ? '0' : '-1');
2954
+ root.setAttribute('aria-orientation', 'horizontal');
2500
2955
  root.setAttribute('aria-valuemin', String((_merged$min = merged.min) !== null && _merged$min !== void 0 ? _merged$min : 0));
2501
2956
  root.setAttribute('aria-valuemax', String((_merged$max = merged.max) !== null && _merged$max !== void 0 ? _merged$max : 100));
2502
2957
  root.setAttribute('aria-valuenow', String(preset.initialProgress));
@@ -2551,8 +3006,22 @@ function createProgressCapsule(container) {
2551
3006
  root.appendChild(valueElement);
2552
3007
  container.appendChild(root);
2553
3008
  var emitter = createEmitter();
2554
- var paused = merged.respectReducedMotion && prefersReducedMotion$1();
3009
+ var manuallyPaused = merged.paused === true;
3010
+ var reducedPaused = false;
3011
+ var staticMode = merged.static === true;
3012
+ var contextLost = false;
2555
3013
  var disposed = false;
3014
+ var renderOnce = function renderOnce() {};
3015
+ var onContextLost = function onContextLost() {
3016
+ contextLost = true;
3017
+ emitter.emit('contextlost', {});
3018
+ };
3019
+ var onContextRestored = function onContextRestored() {
3020
+ contextLost = false;
3021
+ emitter.emit('contextrestored', {});
3022
+ renderOnce();
3023
+ wakeScheduler();
3024
+ };
2556
3025
  var applySize = function applySize() {
2557
3026
  root.style.width = parseSize(merged.width);
2558
3027
  root.style.height = parseSize(merged.height);
@@ -2573,7 +3042,11 @@ function createProgressCapsule(container) {
2573
3042
  preset: preset,
2574
3043
  emitter: emitter,
2575
3044
  options: _objectSpread2(_objectSpread2({}, merged), {}, {
2576
- draggable: effectiveDraggable
3045
+ draggable: effectiveDraggable,
3046
+ keyboard: effectiveKeyboard,
3047
+ onResize: function onResize() {
3048
+ if (manuallyPaused || reducedPaused || staticMode) renderOnce();
3049
+ }
2577
3050
  }),
2578
3051
  copy: copy,
2579
3052
  dirty: dirty
@@ -2585,8 +3058,12 @@ function createProgressCapsule(container) {
2585
3058
  canvas: canvas,
2586
3059
  preset: preset,
2587
3060
  getProgress: function getProgress() {
2588
- return controller.value;
2589
- }
3061
+ return progressRatio(controller.value, controller.min, controller.max) * 100;
3062
+ },
3063
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
3064
+ powerPreference: merged.powerPreference,
3065
+ onContextLost: onContextLost,
3066
+ onContextRestored: onContextRestored
2590
3067
  });
2591
3068
  }
2592
3069
  if (overlay) controller.webglActive = true;else if (merged.renderer !== 'canvas2d') {
@@ -2597,17 +3074,34 @@ function createProgressCapsule(container) {
2597
3074
  });
2598
3075
  });
2599
3076
  }
2600
- var visibility = createVisibilityGuard(root);
3077
+ var visibility = createVisibilityGuard(root, wakeScheduler);
3078
+ var motionPreference = createReducedMotionPreference(merged.respectReducedMotion, function (matches) {
3079
+ reducedPaused = matches;
3080
+ if (matches) renderOnce();else wakeScheduler();
3081
+ });
3082
+ reducedPaused = motionPreference.matches();
3083
+ var isMotionPaused = function isMotionPaused() {
3084
+ return manuallyPaused || reducedPaused || staticMode || contextLost;
3085
+ };
2601
3086
  var flowTime = 0;
3087
+ var frameGate = createFrameGate(merged.fps);
3088
+ renderOnce = function renderOnce() {
3089
+ controller.draw();
3090
+ if (overlay) overlay.update(flowTime);
3091
+ };
3092
+ renderOnce();
3093
+ var offPausedChange = emitter.on('change', function () {
3094
+ if (isMotionPaused()) renderOnce();
3095
+ });
2602
3096
  var unsubscribe = subscribeScheduler(function (delta, now) {
2603
3097
  flowTime += delta;
2604
3098
  controller.update(delta, false);
2605
- if (visibility.isVisible()) {
3099
+ if (frameGate.shouldDraw(delta)) {
2606
3100
  controller.draw();
2607
3101
  if (overlay) overlay.update(flowTime);
2608
3102
  }
2609
3103
  }, function () {
2610
- return paused;
3104
+ return isMotionPaused() || !visibility.isVisible();
2611
3105
  });
2612
3106
  nextTick(function () {
2613
3107
  if (disposed) return;
@@ -2634,32 +3128,41 @@ function createProgressCapsule(container) {
2634
3128
  },
2635
3129
  setRange: function setRange(min, max) {
2636
3130
  controller.setRange(min, max);
3131
+ if (isMotionPaused()) renderOnce();
2637
3132
  return this;
2638
3133
  },
2639
3134
  setPreset: function setPreset(ref) {
2640
3135
  var next = getPreset('progress', ref);
2641
3136
  dirty.preset = true;
2642
3137
  Object.assign(preset, next);
3138
+ if (colorOverride) preset.colors = _toConsumableArray(colorOverride);
3139
+ if (edgeStyleOverride) preset.edgeStyle = edgeStyleOverride;
2643
3140
  var nextColors = preset.colors.map(normalizeColor);
2644
3141
  if (nextColors.every(Boolean)) preset.colors = nextColors;
2645
3142
  controller.preset = preset;
2646
- controller.profile = next.edgeStyle === 'tide' ? FLOW_PROFILES.tide : FLOW_PROFILES[next.id] || FLOW_PROFILES['visual-training'];
3143
+ controller.profile = preset.edgeStyle === 'tide' ? FLOW_PROFILES.tide : FLOW_PROFILES[preset.id] || FLOW_PROFILES['visual-training'];
2647
3144
  controller.flowTime = stringSeed(next.id) * 31;
2648
3145
  controller.seed = stringSeed("".concat(next.id, "-reference")) * Math.PI * 2;
2649
3146
  syncDom();
2650
3147
  if (overlay) {
2651
3148
  overlay.dispose();
3149
+ contextLost = false;
2652
3150
  overlay = attachProgressFlowOverlay({
2653
3151
  root: root,
2654
3152
  canvas: canvas,
2655
3153
  preset: preset,
2656
3154
  getProgress: function getProgress() {
2657
- return controller.value;
2658
- }
3155
+ return progressRatio(controller.value, controller.min, controller.max) * 100;
3156
+ },
3157
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
3158
+ powerPreference: merged.powerPreference,
3159
+ onContextLost: onContextLost,
3160
+ onContextRestored: onContextRestored
2659
3161
  });
2660
3162
  }
2661
3163
  controller.webglActive = Boolean(overlay);
2662
3164
  controller.resizeCanvas();
3165
+ if (isMotionPaused()) renderOnce();
2663
3166
  emitter.emit('presetchange', {
2664
3167
  preset: _objectSpread2({}, preset)
2665
3168
  });
@@ -2669,21 +3172,28 @@ function createProgressCapsule(container) {
2669
3172
  var value = String(edgeStyle || '').toLowerCase();
2670
3173
  if (value !== 'flow' && value !== 'tide') return this;
2671
3174
  dirty.edgeStyle = true;
3175
+ edgeStyleOverride = value;
2672
3176
  preset.edgeStyle = value;
2673
3177
  controller.profile = value === 'tide' ? FLOW_PROFILES.tide : FLOW_PROFILES[preset.id] || FLOW_PROFILES['visual-training'];
2674
3178
  if (overlay) {
2675
3179
  overlay.dispose();
3180
+ contextLost = false;
2676
3181
  overlay = attachProgressFlowOverlay({
2677
3182
  root: root,
2678
3183
  canvas: canvas,
2679
3184
  preset: preset,
2680
3185
  getProgress: function getProgress() {
2681
- return controller.value;
2682
- }
3186
+ return progressRatio(controller.value, controller.min, controller.max) * 100;
3187
+ },
3188
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
3189
+ powerPreference: merged.powerPreference,
3190
+ onContextLost: onContextLost,
3191
+ onContextRestored: onContextRestored
2683
3192
  });
2684
3193
  }
2685
3194
  controller.webglActive = Boolean(overlay);
2686
3195
  controller.resizeCanvas();
3196
+ if (isMotionPaused()) renderOnce();
2687
3197
  return this;
2688
3198
  },
2689
3199
  setText: function setText(content) {
@@ -2721,6 +3231,22 @@ function createProgressCapsule(container) {
2721
3231
  controller.setValueSuffix(suffix);
2722
3232
  return this;
2723
3233
  },
3234
+ setStep: function setStep(step) {
3235
+ controller.setStep(step);
3236
+ return this;
3237
+ },
3238
+ setPrecision: function setPrecision(precision) {
3239
+ controller.setPrecision(precision);
3240
+ return this;
3241
+ },
3242
+ setFormatValue: function setFormatValue(formatter) {
3243
+ controller.setFormatValue(formatter);
3244
+ return this;
3245
+ },
3246
+ setDirection: function setDirection(direction) {
3247
+ controller.setDirection(direction);
3248
+ return this;
3249
+ },
2724
3250
  setShowValue: function setShowValue(show) {
2725
3251
  controller.setShowValue(show);
2726
3252
  return this;
@@ -2731,9 +3257,12 @@ function createProgressCapsule(container) {
2731
3257
  return !color;
2732
3258
  })) return this;
2733
3259
  dirty.colors = true;
3260
+ colorOverride = _toConsumableArray(next);
3261
+ preset.colors = _toConsumableArray(next);
2734
3262
  controller.setColors(next);
2735
3263
  if (overlay) overlay.setColors(next);
2736
3264
  controller.resizeCanvas();
3265
+ if (isMotionPaused()) renderOnce();
2737
3266
  return this;
2738
3267
  },
2739
3268
  setSize: function setSize(width, height) {
@@ -2747,30 +3276,68 @@ function createProgressCapsule(container) {
2747
3276
  }
2748
3277
  applySize();
2749
3278
  controller.resizeCanvas();
3279
+ if (isMotionPaused()) renderOnce();
2750
3280
  return this;
2751
3281
  },
2752
3282
  randomize: function randomize() {
2753
- controller.randomize();
3283
+ flowTime = controller.randomize();
3284
+ if (isMotionPaused()) renderOnce();
2754
3285
  return this;
2755
3286
  },
2756
3287
  pause: function pause() {
2757
- paused = true;
3288
+ dirty.paused = true;
3289
+ manuallyPaused = true;
3290
+ renderOnce();
2758
3291
  return this;
2759
3292
  },
2760
3293
  resume: function resume() {
2761
- paused = false;
3294
+ dirty.paused = true;
3295
+ manuallyPaused = false;
3296
+ wakeScheduler();
3297
+ return this;
3298
+ },
3299
+ setPaused: function setPaused(value) {
3300
+ return value ? this.pause() : this.resume();
3301
+ },
3302
+ setStatic: function setStatic(value) {
3303
+ dirty.static = true;
3304
+ staticMode = value === true;
3305
+ merged.static = staticMode;
3306
+ if (staticMode) renderOnce();else wakeScheduler();
3307
+ return this;
3308
+ },
3309
+ setFps: function setFps(fps) {
3310
+ dirty.fps = true;
3311
+ merged.fps = frameGate.setFps(fps);
3312
+ wakeScheduler();
2762
3313
  return this;
2763
3314
  },
2764
3315
  setQuality: function setQuality(quality) {
3316
+ dirty.quality = true;
2765
3317
  merged.quality = quality;
2766
- controller.dprCap = dprCapFor(quality);
3318
+ controller.dprCap = effectiveDprCap(quality, merged.renderScale);
3319
+ controller.resizeCanvas();
3320
+ if (overlay) overlay.setDprCap(controller.dprCap);
3321
+ if (isMotionPaused()) renderOnce();
3322
+ return this;
3323
+ },
3324
+ setRenderScale: function setRenderScale(renderScale) {
3325
+ var value = Number(renderScale);
3326
+ if (!Number.isFinite(value)) return this;
3327
+ dirty.renderScale = true;
3328
+ merged.renderScale = Math.min(1, Math.max(0.25, value));
3329
+ controller.dprCap = effectiveDprCap(merged.quality, merged.renderScale);
2767
3330
  controller.resizeCanvas();
3331
+ if (overlay) overlay.setDprCap(controller.dprCap);
3332
+ if (isMotionPaused()) renderOnce();
2768
3333
  return this;
2769
3334
  },
2770
3335
  dispose: function dispose() {
2771
3336
  disposed = true;
2772
3337
  unsubscribe();
3338
+ offPausedChange();
2773
3339
  visibility.dispose();
3340
+ motionPreference.dispose();
2774
3341
  if (overlay) overlay.dispose();
2775
3342
  controller.dispose();
2776
3343
  root.remove();
@@ -2781,26 +3348,89 @@ function createProgressCapsule(container) {
2781
3348
  get cssVars() {
2782
3349
  return merged.cssVars;
2783
3350
  },
3351
+ get min() {
3352
+ return controller.min;
3353
+ },
3354
+ get max() {
3355
+ return controller.max;
3356
+ },
3357
+ get step() {
3358
+ return controller.step;
3359
+ },
3360
+ get precision() {
3361
+ return controller.precision;
3362
+ },
3363
+ get formatValue() {
3364
+ return controller.formatValue;
3365
+ },
3366
+ get direction() {
3367
+ return controller.direction;
3368
+ },
3369
+ get showValue() {
3370
+ return !valueElement.hidden;
3371
+ },
3372
+ get valueSuffix() {
3373
+ return controller.valueSuffix;
3374
+ },
3375
+ get quality() {
3376
+ return merged.quality;
3377
+ },
3378
+ get renderScale() {
3379
+ return merged.renderScale;
3380
+ },
3381
+ get paused() {
3382
+ return manuallyPaused;
3383
+ },
3384
+ get static() {
3385
+ return staticMode;
3386
+ },
3387
+ get fps() {
3388
+ return frameGate.getFps();
3389
+ },
2784
3390
  dirty: dirty
2785
3391
  };
2786
3392
  }
2787
3393
 
2788
- function prefersReducedMotion() {
2789
- return typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches;
3394
+ var HOST_STYLES = new WeakMap();
3395
+ function acquireHostStyles(host) {
3396
+ var active = HOST_STYLES.get(host);
3397
+ if (active) {
3398
+ active.count += 1;
3399
+ return;
3400
+ }
3401
+ var state = {
3402
+ count: 1,
3403
+ position: host.style.position,
3404
+ isolation: host.style.isolation
3405
+ };
3406
+ var computed = getComputedStyle(host);
3407
+ if (computed.position === 'static' || computed.position === '') host.style.position = 'relative';
3408
+ host.style.isolation = 'isolate';
3409
+ HOST_STYLES.set(host, state);
3410
+ }
3411
+ function releaseHostStyles(host) {
3412
+ var state = HOST_STYLES.get(host);
3413
+ if (!state) return;
3414
+ state.count -= 1;
3415
+ if (state.count > 0) return;
3416
+ host.style.position = state.position;
3417
+ host.style.isolation = state.isolation;
3418
+ HOST_STYLES.delete(host);
2790
3419
  }
2791
3420
 
2792
- /**
2793
- * Mount the cosmic (nebula) material as a background layer inside `host`.
2794
- *
2795
- * The layer is absolutely positioned and defaults to `z-index: -1`, so it
2796
- * paints behind the host's in-flow content: text/buttons/images inside the
2797
- * host sit on top with zero extra CSS. Host gets `position: relative;
2798
- * isolation: isolate` automatically so the layer can never escape its box
2799
- * (or break stacking outside it).
2800
- *
3421
+ /**
3422
+ * Mount the cosmic (nebula) material as a background layer inside `host`.
3423
+ *
3424
+ * The layer is absolutely positioned and defaults to `z-index: -1`, so it
3425
+ * paints behind the host's in-flow content: text/buttons/images inside the
3426
+ * host sit on top with zero extra CSS. Host gets `position: relative;
3427
+ * isolation: isolate` automatically so the layer can never escape its box
3428
+ * (or break stacking outside it).
3429
+ *
2801
3430
  * Options: preset (literary name), colors, seed, speed, quality,
2802
- * renderer, mouseColor, respectReducedMotion, opacity (0-1), fallbackColor
2803
- * (true=preset base color, a hex string, or false to disable).
3431
+ * renderer, renderScale, powerPreference, fps, paused/static, mouseColor,
3432
+ * respectReducedMotion, opacity (0-1), fallbackColor (true=preset base
3433
+ * color, a hex string, or false to disable).
2804
3434
  */
2805
3435
  function createColorBackground(host) {
2806
3436
  var _options$preset;
@@ -2814,18 +3444,29 @@ function createColorBackground(host) {
2814
3444
  renderer: DEFAULTS.capsule.renderer,
2815
3445
  respectReducedMotion: DEFAULTS.capsule.respectReducedMotion,
2816
3446
  mouseColor: true,
3447
+ renderScale: 1,
3448
+ powerPreference: 'high-performance',
3449
+ fps: DEFAULTS.capsule.fps,
3450
+ paused: false,
3451
+ static: false,
2817
3452
  opacity: 1,
2818
3453
  fallbackColor: true
2819
3454
  }, preset, options);
2820
- var normalizedColors = merged.colors.map(normalizeColor);
2821
- if (normalizedColors.every(Boolean)) preset.colors = normalizedColors;
2822
- preset.seed = merged.seed;
2823
- preset.speed = merged.speed;
2824
- var computed = getComputedStyle(host);
2825
- if (computed.position === 'static' || computed.position === '') {
2826
- host.style.position = 'relative';
2827
- }
2828
- host.style.isolation = 'isolate';
3455
+ merged.opacity = Number.isFinite(Number(merged.opacity)) ? Math.min(1, Math.max(0, Number(merged.opacity))) : 1;
3456
+ var normalizedColors = Array.isArray(merged.colors) ? merged.colors.map(normalizeColor) : [];
3457
+ if (normalizedColors.length === 4 && normalizedColors.every(Boolean)) preset.colors = normalizedColors;
3458
+ var initialSeed = toFiniteNumber(merged.seed);
3459
+ var initialSpeed = toFiniteNumber(merged.speed);
3460
+ if (initialSeed !== null) preset.seed = initialSeed;
3461
+ if (initialSpeed !== null) preset.speed = initialSpeed;
3462
+ merged.colors = _toConsumableArray(preset.colors);
3463
+ merged.seed = preset.seed;
3464
+ merged.speed = preset.speed;
3465
+ var optionColors = Array.isArray(options.colors) ? options.colors.map(normalizeColor) : [];
3466
+ var colorOverride = optionColors.length === 4 && optionColors.every(Boolean) ? _toConsumableArray(optionColors) : null;
3467
+ var seedOverride = options.seed !== undefined ? toFiniteNumber(options.seed) : null;
3468
+ var speedOverride = options.speed !== undefined ? toFiniteNumber(options.speed) : null;
3469
+ acquireHostStyles(host);
2829
3470
  var layer = document.createElement('div');
2830
3471
  layer.className = 'dlc-color-layer';
2831
3472
  var syncLayerVars = function syncLayerVars() {
@@ -2840,8 +3481,12 @@ function createColorBackground(host) {
2840
3481
  layer.appendChild(canvas);
2841
3482
  host.appendChild(layer);
2842
3483
  var emitter = createEmitter();
2843
- var paused = merged.respectReducedMotion && prefersReducedMotion();
3484
+ var manuallyPaused = merged.paused === true;
3485
+ var reducedPaused = false;
3486
+ var staticMode = merged.static === true;
3487
+ var contextLost = false;
2844
3488
  var disposed = false;
3489
+ var renderOnce = function renderOnce() {};
2845
3490
  var dirty = {
2846
3491
  preset: false,
2847
3492
  seed: false,
@@ -2849,19 +3494,37 @@ function createColorBackground(host) {
2849
3494
  colors: false,
2850
3495
  opacity: false,
2851
3496
  fallbackColor: false,
2852
- mouseColor: false
3497
+ mouseColor: false,
3498
+ quality: false,
3499
+ renderScale: false,
3500
+ paused: false,
3501
+ static: false,
3502
+ fps: false
2853
3503
  };
2854
3504
  var renderer;
2855
3505
  var useWebgl = merged.renderer !== 'canvas2d';
2856
3506
  if (useWebgl) {
2857
3507
  try {
2858
3508
  renderer = new CosmicRenderer(canvas, merged, {
2859
- dprCap: dprCapFor(merged.quality),
3509
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
2860
3510
  mouseColor: merged.mouseColor !== false,
2861
- eventTarget: host
3511
+ eventTarget: host,
3512
+ powerPreference: merged.powerPreference,
3513
+ onContextLost: function onContextLost() {
3514
+ contextLost = true;
3515
+ emitter.emit('contextlost', {});
3516
+ },
3517
+ onContextRestored: function onContextRestored() {
3518
+ contextLost = false;
3519
+ emitter.emit('contextrestored', {});
3520
+ renderOnce();
3521
+ wakeScheduler();
3522
+ }
2862
3523
  });
2863
3524
  } catch (error) {
2864
- renderer = new FallbackRenderer(canvas, merged);
3525
+ renderer = new FallbackRenderer(canvas, merged, {
3526
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale)
3527
+ });
2865
3528
  nextTick(function () {
2866
3529
  if (disposed) return;
2867
3530
  emitter.emit('error', {
@@ -2870,19 +3533,37 @@ function createColorBackground(host) {
2870
3533
  });
2871
3534
  }
2872
3535
  } else {
2873
- renderer = new FallbackRenderer(canvas, merged);
3536
+ renderer = new FallbackRenderer(canvas, merged, {
3537
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale)
3538
+ });
2874
3539
  }
2875
- var resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(function () {
2876
- return renderer.resize();
2877
- }) : null;
2878
- if (resizeObserver) resizeObserver.observe(host);else window.addEventListener('resize', renderer.resize);
2879
- var visibility = createVisibilityGuard(layer);
3540
+ renderer.resize();
2880
3541
  var animationTime = 0;
3542
+ var frameGate = createFrameGate(merged.fps);
3543
+ renderOnce = function renderOnce() {
3544
+ return renderer.draw(animationTime);
3545
+ };
3546
+ var resize = function resize() {
3547
+ renderer.resize();
3548
+ if (manuallyPaused || reducedPaused || staticMode) renderOnce();
3549
+ };
3550
+ var resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(resize) : null;
3551
+ if (resizeObserver) resizeObserver.observe(host);else window.addEventListener('resize', resize);
3552
+ var visibility = createVisibilityGuard(layer, wakeScheduler);
3553
+ var motionPreference = createReducedMotionPreference(merged.respectReducedMotion, function (matches) {
3554
+ reducedPaused = matches;
3555
+ if (matches) renderOnce();else wakeScheduler();
3556
+ });
3557
+ reducedPaused = motionPreference.matches();
3558
+ var isMotionPaused = function isMotionPaused() {
3559
+ return manuallyPaused || reducedPaused || staticMode || contextLost;
3560
+ };
3561
+ renderOnce();
2881
3562
  var unsubscribe = subscribeScheduler(function (delta) {
2882
3563
  animationTime += delta;
2883
- if (visibility.isVisible()) renderer.draw(animationTime);
3564
+ if (frameGate.shouldDraw(delta)) renderer.draw(animationTime);
2884
3565
  }, function () {
2885
- return paused;
3566
+ return isMotionPaused() || !visibility.isVisible();
2886
3567
  });
2887
3568
  nextTick(function () {
2888
3569
  if (disposed) return;
@@ -2901,11 +3582,15 @@ function createColorBackground(host) {
2901
3582
  var next = getPreset('capsule', ref);
2902
3583
  dirty.preset = true;
2903
3584
  Object.assign(preset, next);
3585
+ if (colorOverride) preset.colors = _toConsumableArray(colorOverride);
3586
+ if (seedOverride !== null) preset.seed = seedOverride;
3587
+ if (speedOverride !== null) preset.speed = speedOverride;
2904
3588
  var nextColors = preset.colors.map(normalizeColor);
2905
3589
  if (nextColors.every(Boolean)) preset.colors = nextColors;
2906
3590
  renderer.setPreset(_objectSpread2({}, preset));
2907
3591
  renderer.resize();
2908
3592
  syncLayerVars();
3593
+ if (isMotionPaused()) renderOnce();
2909
3594
  emitter.emit('presetchange', {
2910
3595
  preset: _objectSpread2({}, preset)
2911
3596
  });
@@ -2917,27 +3602,33 @@ function createColorBackground(host) {
2917
3602
  return !color;
2918
3603
  })) return this;
2919
3604
  dirty.colors = true;
3605
+ colorOverride = _toConsumableArray(next);
2920
3606
  preset.colors = next;
2921
3607
  renderer.setPreset(_objectSpread2(_objectSpread2({}, preset), {}, {
2922
3608
  colors: next
2923
3609
  }));
2924
3610
  syncLayerVars();
3611
+ if (isMotionPaused()) renderOnce();
2925
3612
  return this;
2926
3613
  },
2927
3614
  setSeed: function setSeed(seed) {
2928
- var value = Number(seed);
2929
- if (!Number.isFinite(value)) return this;
3615
+ var value = toFiniteNumber(seed);
3616
+ if (value === null) return this;
2930
3617
  dirty.seed = true;
3618
+ seedOverride = value;
2931
3619
  preset.seed = value;
2932
3620
  renderer.setPreset(_objectSpread2({}, preset));
3621
+ if (isMotionPaused()) renderOnce();
2933
3622
  return this;
2934
3623
  },
2935
3624
  setSpeed: function setSpeed(speed) {
2936
- var value = Number(speed);
2937
- if (!Number.isFinite(value)) return this;
3625
+ var value = toFiniteNumber(speed);
3626
+ if (value === null) return this;
2938
3627
  dirty.speed = true;
3628
+ speedOverride = value;
2939
3629
  preset.speed = value;
2940
3630
  renderer.setPreset(_objectSpread2({}, preset));
3631
+ if (isMotionPaused()) renderOnce();
2941
3632
  return this;
2942
3633
  },
2943
3634
  setFallbackColor: function setFallbackColor(value) {
@@ -2953,8 +3644,23 @@ function createColorBackground(host) {
2953
3644
  return this;
2954
3645
  },
2955
3646
  setQuality: function setQuality(quality) {
3647
+ dirty.quality = true;
2956
3648
  merged.quality = quality;
2957
- if (typeof renderer.setDprCap === 'function') renderer.setDprCap(dprCapFor(quality));
3649
+ if (typeof renderer.setDprCap === 'function') {
3650
+ renderer.setDprCap(effectiveDprCap(quality, merged.renderScale));
3651
+ }
3652
+ if (isMotionPaused()) renderOnce();
3653
+ return this;
3654
+ },
3655
+ setRenderScale: function setRenderScale(renderScale) {
3656
+ var value = Number(renderScale);
3657
+ if (!Number.isFinite(value)) return this;
3658
+ dirty.renderScale = true;
3659
+ merged.renderScale = Math.min(1, Math.max(0.25, value));
3660
+ if (typeof renderer.setDprCap === 'function') {
3661
+ renderer.setDprCap(effectiveDprCap(merged.quality, merged.renderScale));
3662
+ }
3663
+ if (isMotionPaused()) renderOnce();
2958
3664
  return this;
2959
3665
  },
2960
3666
  setOpacity: function setOpacity(value) {
@@ -2970,20 +3676,42 @@ function createColorBackground(host) {
2970
3676
  return this;
2971
3677
  },
2972
3678
  pause: function pause() {
2973
- paused = true;
3679
+ dirty.paused = true;
3680
+ manuallyPaused = true;
3681
+ renderOnce();
2974
3682
  return this;
2975
3683
  },
2976
3684
  resume: function resume() {
2977
- paused = false;
3685
+ dirty.paused = true;
3686
+ manuallyPaused = false;
3687
+ wakeScheduler();
3688
+ return this;
3689
+ },
3690
+ setPaused: function setPaused(value) {
3691
+ return value ? this.pause() : this.resume();
3692
+ },
3693
+ setStatic: function setStatic(value) {
3694
+ dirty.static = true;
3695
+ staticMode = value === true;
3696
+ merged.static = staticMode;
3697
+ if (staticMode) renderOnce();else wakeScheduler();
3698
+ return this;
3699
+ },
3700
+ setFps: function setFps(fps) {
3701
+ dirty.fps = true;
3702
+ merged.fps = frameGate.setFps(fps);
3703
+ wakeScheduler();
2978
3704
  return this;
2979
3705
  },
2980
3706
  dispose: function dispose() {
2981
3707
  disposed = true;
2982
3708
  unsubscribe();
2983
3709
  visibility.dispose();
2984
- if (resizeObserver) resizeObserver.disconnect();else window.removeEventListener('resize', renderer.resize);
3710
+ motionPreference.dispose();
3711
+ if (resizeObserver) resizeObserver.disconnect();else window.removeEventListener('resize', resize);
2985
3712
  renderer.dispose();
2986
3713
  layer.remove();
3714
+ releaseHostStyles(host);
2987
3715
  },
2988
3716
  get opacity() {
2989
3717
  return merged.opacity;
@@ -2994,6 +3722,21 @@ function createColorBackground(host) {
2994
3722
  get mouseColor() {
2995
3723
  return merged.mouseColor;
2996
3724
  },
3725
+ get quality() {
3726
+ return merged.quality;
3727
+ },
3728
+ get renderScale() {
3729
+ return merged.renderScale;
3730
+ },
3731
+ get paused() {
3732
+ return manuallyPaused;
3733
+ },
3734
+ get static() {
3735
+ return staticMode;
3736
+ },
3737
+ get fps() {
3738
+ return frameGate.getFps();
3739
+ },
2997
3740
  dirty: dirty
2998
3741
  };
2999
3742
  }
@@ -3012,6 +3755,7 @@ exports.createCapsule = createCapsule;
3012
3755
  exports.createColorBackground = createColorBackground;
3013
3756
  exports.createProgressCapsule = createProgressCapsule;
3014
3757
  exports.dprCapFor = dprCapFor;
3758
+ exports.effectiveDprCap = effectiveDprCap;
3015
3759
  exports.getPreset = getPreset;
3016
3760
  exports.hexToRgb01 = hexToRgb01$1;
3017
3761
  exports.hexToRgba = hexToRgba;