@5even7/dlc-ui 0.2.11 → 0.2.12

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',
@@ -434,6 +440,11 @@ var DEFAULTS = {
434
440
  height: 160,
435
441
  quality: 'auto',
436
442
  renderer: 'auto',
443
+ renderScale: 1,
444
+ powerPreference: 'high-performance',
445
+ fps: 60,
446
+ paused: false,
447
+ static: false,
437
448
  respectReducedMotion: true,
438
449
  mouseColor: true,
439
450
  textRatio: 39
@@ -443,13 +454,22 @@ var DEFAULTS = {
443
454
  height: 104,
444
455
  min: 0,
445
456
  max: 100,
457
+ step: 'any',
446
458
  draggable: true,
459
+ keyboard: true,
447
460
  disabled: false,
448
461
  readonly: false,
462
+ direction: 'ltr',
463
+ precision: 0,
449
464
  valueSuffix: '%',
450
465
  edgeStyle: 'flow',
451
466
  quality: 'auto',
452
467
  renderer: 'auto',
468
+ renderScale: 1,
469
+ powerPreference: 'high-performance',
470
+ fps: 60,
471
+ paused: false,
472
+ static: false,
453
473
  textRatio: 54,
454
474
  showValue: true,
455
475
  respectReducedMotion: true
@@ -739,7 +759,7 @@ var CosmicRenderer = /*#__PURE__*/function () {
739
759
  alpha: false,
740
760
  antialias: false,
741
761
  depth: false,
742
- powerPreference: 'high-performance',
762
+ powerPreference: this.options.powerPreference || 'high-performance',
743
763
  preserveDrawingBuffer: false
744
764
  });
745
765
  if (!this.gl) throw new Error('WebGL2 is not available');
@@ -750,16 +770,19 @@ var CosmicRenderer = /*#__PURE__*/function () {
750
770
  this.motion = 0;
751
771
  this.motionTarget = 0;
752
772
  this.timeOffset = preset.seed * 0.73;
773
+ this.colors = preset.colors.map(hexToRgb01$1);
753
774
  this.visible = true;
754
775
  this.disposed = false;
755
776
  _assertClassBrand(_CosmicRenderer_brand, this, _setupGeometry).call(this);
756
777
  _assertClassBrand(_CosmicRenderer_brand, this, _bindEvents).call(this);
778
+ _assertClassBrand(_CosmicRenderer_brand, this, _bindContextEvents).call(this);
757
779
  this.resize();
758
780
  }
759
781
  return _createClass(CosmicRenderer, [{
760
782
  key: "setPreset",
761
783
  value: function setPreset(preset) {
762
784
  this.preset = _objectSpread2({}, preset);
785
+ this.colors = preset.colors.map(hexToRgb01$1);
763
786
  this.timeOffset = preset.seed * 0.73;
764
787
  }
765
788
  }, {
@@ -804,15 +827,14 @@ var CosmicRenderer = /*#__PURE__*/function () {
804
827
  if (this.canvas.width !== width || this.canvas.height !== height) {
805
828
  this.canvas.width = width;
806
829
  this.canvas.height = height;
807
- this.gl.viewport(0, 0, width, height);
808
830
  }
831
+ this.gl.viewport(0, 0, width, height);
809
832
  }
810
833
  }, {
811
834
  key: "draw",
812
835
  value: function draw(elapsedSeconds) {
813
836
  var paused = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
814
837
  if (this.disposed || !this.visible) return;
815
- this.resize();
816
838
  var gl = this.gl;
817
839
  this.pointer[0] += (this.pointerTarget[0] - this.pointer[0]) * 0.08;
818
840
  this.pointer[1] += (this.pointerTarget[1] - this.pointer[1]) * 0.08;
@@ -821,16 +843,15 @@ var CosmicRenderer = /*#__PURE__*/function () {
821
843
  gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
822
844
  gl.enableVertexAttribArray(this.locations.position);
823
845
  gl.vertexAttribPointer(this.locations.position, 2, gl.FLOAT, false, 0, 0);
824
- var colors = this.preset.colors.map(hexToRgb01$1);
825
846
  gl.uniform2f(this.locations.resolution, this.canvas.width, this.canvas.height);
826
847
  gl.uniform1f(this.locations.time, this.timeOffset + (paused ? 0 : elapsedSeconds * this.preset.speed));
827
848
  gl.uniform1f(this.locations.seed, this.preset.seed);
828
849
  gl.uniform1f(this.locations.motion, this.motion);
829
850
  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]);
851
+ gl.uniform3fv(this.locations.colorA, this.colors[0]);
852
+ gl.uniform3fv(this.locations.colorB, this.colors[1]);
853
+ gl.uniform3fv(this.locations.colorC, this.colors[2]);
854
+ gl.uniform3fv(this.locations.colorD, this.colors[3]);
834
855
  gl.drawArrays(gl.TRIANGLES, 0, 3);
835
856
  }
836
857
  }, {
@@ -841,6 +862,8 @@ var CosmicRenderer = /*#__PURE__*/function () {
841
862
  target.removeEventListener('pointermove', this.onPointerMove);
842
863
  target.removeEventListener('pointerdown', this.onPointerMove);
843
864
  target.removeEventListener('pointerleave', this.onPointerLeave);
865
+ this.canvas.removeEventListener('webglcontextlost', this.onContextLost, false);
866
+ this.canvas.removeEventListener('webglcontextrestored', this.onContextRestored, false);
844
867
  this.gl.deleteBuffer(this.buffer);
845
868
  this.gl.deleteProgram(this.program);
846
869
  var lose = this.gl.getExtension('WEBGL_lose_context');
@@ -897,6 +920,26 @@ function _bindEvents() {
897
920
  passive: true
898
921
  });
899
922
  }
923
+ function _bindContextEvents() {
924
+ var _this3 = this;
925
+ this.onContextLost = function (event) {
926
+ event.preventDefault();
927
+ if (_this3.disposed) return;
928
+ _this3.visible = false;
929
+ if (typeof _this3.options.onContextLost === 'function') _this3.options.onContextLost(event);
930
+ };
931
+ this.onContextRestored = function () {
932
+ if (_this3.disposed) return;
933
+ _this3.program = createProgram$1(_this3.gl);
934
+ _this3.locations = _assertClassBrand(_CosmicRenderer_brand, _this3, _getLocations).call(_this3);
935
+ _assertClassBrand(_CosmicRenderer_brand, _this3, _setupGeometry).call(_this3);
936
+ _this3.visible = true;
937
+ _this3.resize();
938
+ if (typeof _this3.options.onContextRestored === 'function') _this3.options.onContextRestored();
939
+ };
940
+ this.canvas.addEventListener('webglcontextlost', this.onContextLost, false);
941
+ this.canvas.addEventListener('webglcontextrestored', this.onContextRestored, false);
942
+ }
900
943
 
901
944
  function rgb(color) {
902
945
  var alpha = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
@@ -911,18 +954,20 @@ function rgb(color) {
911
954
  }
912
955
  var FallbackRenderer = /*#__PURE__*/function () {
913
956
  function FallbackRenderer(canvas, preset) {
957
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
914
958
  _classCallCheck(this, FallbackRenderer);
915
959
  this.canvas = canvas;
916
960
  this.preset = preset;
917
961
  this.context = canvas.getContext('2d');
918
962
  this.visible = true;
919
963
  this.timePhase = 0;
964
+ this.dprCap = options.dprCap || 1.5;
920
965
  }
921
966
  return _createClass(FallbackRenderer, [{
922
967
  key: "resize",
923
968
  value: function resize() {
924
969
  var rect = this.canvas.getBoundingClientRect();
925
- var dpr = Math.min(window.devicePixelRatio || 1, 1.5);
970
+ var dpr = Math.min(window.devicePixelRatio || 1, this.dprCap);
926
971
  var width = Math.max(2, Math.round(rect.width * dpr));
927
972
  var height = Math.max(2, Math.round(rect.height * dpr));
928
973
  if (this.canvas.width !== width || this.canvas.height !== height) {
@@ -963,7 +1008,6 @@ var FallbackRenderer = /*#__PURE__*/function () {
963
1008
  key: "draw",
964
1009
  value: function draw(time) {
965
1010
  if (!this.visible) return;
966
- this.resize();
967
1011
  this.drawNebula(time);
968
1012
  }
969
1013
  }, {
@@ -971,6 +1015,12 @@ var FallbackRenderer = /*#__PURE__*/function () {
971
1015
  value: function setPreset(preset) {
972
1016
  this.preset = preset;
973
1017
  }
1018
+ }, {
1019
+ key: "setDprCap",
1020
+ value: function setDprCap(cap) {
1021
+ this.dprCap = cap;
1022
+ this.resize();
1023
+ }
974
1024
  }, {
975
1025
  key: "randomize",
976
1026
  value: function randomize() {
@@ -988,10 +1038,10 @@ var FallbackRenderer = /*#__PURE__*/function () {
988
1038
  }]);
989
1039
  }();
990
1040
 
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.
1041
+ /**
1042
+ * Document-level shared rAF scheduler. Every component instance subscribes
1043
+ * its own frame callback; the whole page runs ONE animation loop (like the
1044
+ * original demo), which avoids jank from many competing rAF loops.
995
1045
  */
996
1046
  var subscribers = [];
997
1047
  var running = false;
@@ -1000,11 +1050,7 @@ var rafId = 0;
1000
1050
  var last = 0;
1001
1051
  function tick(now) {
1002
1052
  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);
1053
+ var activeItems = [];
1008
1054
  if (!globallyPaused) {
1009
1055
  var _iterator = _createForOfIteratorHelper(subscribers.slice()),
1010
1056
  _step;
@@ -1012,10 +1058,8 @@ function tick(now) {
1012
1058
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
1013
1059
  var item = _step.value;
1014
1060
  try {
1015
- if (!item.isPaused()) item.onFrame(delta, now);
1016
- } catch (error) {
1017
- console.warn('[dlc-ui] frame error:', error);
1018
- }
1061
+ if (!item.isPaused()) activeItems.push(item);
1062
+ } catch (_unused) {}
1019
1063
  }
1020
1064
  } catch (err) {
1021
1065
  _iterator.e(err);
@@ -1023,6 +1067,25 @@ function tick(now) {
1023
1067
  _iterator.f();
1024
1068
  }
1025
1069
  }
1070
+ if (activeItems.length === 0) {
1071
+ running = false;
1072
+ rafId = 0;
1073
+ last = 0;
1074
+ return;
1075
+ }
1076
+ var delta = last ? Math.min((now - last) / 1000, 0.05) : 0;
1077
+ last = now;
1078
+ // Schedule the next frame BEFORE running callbacks so one throwing
1079
+ // subscriber can never kill the whole animation loop.
1080
+ rafId = requestAnimationFrame(tick);
1081
+ for (var _i = 0, _activeItems = activeItems; _i < _activeItems.length; _i++) {
1082
+ var _item = _activeItems[_i];
1083
+ try {
1084
+ _item.onFrame(delta, now);
1085
+ } catch (error) {
1086
+ console.warn('[dlc-ui] frame error:', error);
1087
+ }
1088
+ }
1026
1089
  if (subscribers.length === 0) {
1027
1090
  cancelAnimationFrame(rafId);
1028
1091
  running = false;
@@ -1040,6 +1103,10 @@ function pauseAll() {
1040
1103
  }
1041
1104
  function resumeAll() {
1042
1105
  globallyPaused = false;
1106
+ start();
1107
+ }
1108
+ function wakeScheduler() {
1109
+ start();
1043
1110
  }
1044
1111
  function subscribeScheduler(onFrame, isPaused) {
1045
1112
  var item = {
@@ -1059,11 +1126,12 @@ function subscribeScheduler(onFrame, isPaused) {
1059
1126
  };
1060
1127
  }
1061
1128
 
1062
- /**
1063
- * Gates drawing on "element intersects viewport AND the page tab is visible".
1064
- * Falls back to always-visible when IntersectionObserver is unavailable.
1129
+ /**
1130
+ * Gates drawing on "element intersects viewport AND the page tab is visible".
1131
+ * Falls back to always-visible when IntersectionObserver is unavailable.
1065
1132
  */
1066
1133
  function createVisibilityGuard(element) {
1134
+ var onChange = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
1067
1135
  var intersecting = true;
1068
1136
  var pageVisible = typeof document === 'undefined' || !document.hidden;
1069
1137
  var disposed = false;
@@ -1073,6 +1141,7 @@ function createVisibilityGuard(element) {
1073
1141
  intersecting = entries.some(function (entry) {
1074
1142
  return entry.isIntersecting;
1075
1143
  });
1144
+ if (typeof onChange === 'function') onChange();
1076
1145
  }, {
1077
1146
  rootMargin: '180px'
1078
1147
  });
@@ -1080,6 +1149,7 @@ function createVisibilityGuard(element) {
1080
1149
  }
1081
1150
  var onVisibilityChange = function onVisibilityChange() {
1082
1151
  pageVisible = typeof document !== 'undefined' && !document.hidden;
1152
+ if (typeof onChange === 'function') onChange();
1083
1153
  };
1084
1154
  if (typeof document !== 'undefined') {
1085
1155
  document.addEventListener('visibilitychange', onVisibilityChange);
@@ -1115,18 +1185,69 @@ function nextTick(fn) {
1115
1185
  }
1116
1186
  }
1117
1187
 
1118
- function prefersReducedMotion$2() {
1119
- return typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches;
1188
+ function normalizeFps(value) {
1189
+ var fallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 60;
1190
+ var fps = Number(value);
1191
+ if (!Number.isFinite(fps)) return fallback;
1192
+ return Math.min(60, Math.max(1, fps));
1193
+ }
1194
+ function createFrameGate() {
1195
+ var initialFps = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 60;
1196
+ var fps = normalizeFps(initialFps);
1197
+ var elapsed = 0;
1198
+ return {
1199
+ shouldDraw: function shouldDraw(delta) {
1200
+ elapsed += delta;
1201
+ var interval = 1 / fps;
1202
+ if (elapsed + 0.0001 < interval) return false;
1203
+ elapsed %= interval;
1204
+ return true;
1205
+ },
1206
+ setFps: function setFps(value) {
1207
+ fps = normalizeFps(value, fps);
1208
+ elapsed = 0;
1209
+ return fps;
1210
+ },
1211
+ getFps: function getFps() {
1212
+ return fps;
1213
+ }
1214
+ };
1215
+ }
1216
+ function createReducedMotionPreference(enabled, onChange) {
1217
+ var query = enabled && typeof matchMedia !== 'undefined' ? matchMedia('(prefers-reduced-motion: reduce)') : null;
1218
+ var notify = function notify() {
1219
+ if (typeof onChange === 'function') onChange(Boolean(query && query.matches));
1220
+ };
1221
+ if (query) {
1222
+ if (typeof query.addEventListener === 'function') query.addEventListener('change', notify);else if (typeof query.addListener === 'function') query.addListener(notify);
1223
+ }
1224
+ return {
1225
+ matches: function matches() {
1226
+ return Boolean(query && query.matches);
1227
+ },
1228
+ dispose: function dispose() {
1229
+ if (!query) return;
1230
+ if (typeof query.removeEventListener === 'function') query.removeEventListener('change', notify);else if (typeof query.removeListener === 'function') query.removeListener(notify);
1231
+ }
1232
+ };
1120
1233
  }
1121
1234
 
1122
- /**
1235
+ function toFiniteNumber(value) {
1236
+ if (value == null || typeof value === 'boolean') return null;
1237
+ if (typeof value === 'string' && value.trim() === '') return null;
1238
+ var number = Number(value);
1239
+ return Number.isFinite(number) ? number : null;
1240
+ }
1241
+
1242
+ /**
1123
1243
  * Mount a cosmic (nebula) capsule into `container`.
1124
1244
  *
1125
1245
  * Options: preset (literary name), width, height (number=px or string with
1126
1246
  * px/%/vw/vh/em/rem), colors, seed, speed, textRatio (0-100, text region
1127
1247
  * width in percent), text (HTML string or DOM nodes for the text slot),
1128
1248
  * colorContent (HTML string or DOM nodes for the color slot), quality,
1129
- * renderer, respectReducedMotion, mouseColor, cssVars.
1249
+ * renderer, quality, renderScale, powerPreference, fps, paused/static,
1250
+ * respectReducedMotion, mouseColor, cssVars.
1130
1251
  */
1131
1252
  function createCapsule(container) {
1132
1253
  var _options$preset;
@@ -1136,10 +1257,19 @@ function createCapsule(container) {
1136
1257
  }
1137
1258
  var preset = _objectSpread2({}, getPreset('capsule', (_options$preset = options.preset) !== null && _options$preset !== void 0 ? _options$preset : '初光'));
1138
1259
  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;
1260
+ var normalizedColors = Array.isArray(merged.colors) ? merged.colors.map(normalizeColor) : [];
1261
+ if (normalizedColors.length === 4 && normalizedColors.every(Boolean)) preset.colors = normalizedColors;
1262
+ var initialSeed = toFiniteNumber(merged.seed);
1263
+ var initialSpeed = toFiniteNumber(merged.speed);
1264
+ if (initialSeed !== null) preset.seed = initialSeed;
1265
+ if (initialSpeed !== null) preset.speed = initialSpeed;
1266
+ merged.colors = _toConsumableArray(preset.colors);
1267
+ merged.seed = preset.seed;
1268
+ merged.speed = preset.speed;
1269
+ var optionColors = Array.isArray(options.colors) ? options.colors.map(normalizeColor) : [];
1270
+ var colorOverride = optionColors.length === 4 && optionColors.every(Boolean) ? _toConsumableArray(optionColors) : null;
1271
+ var seedOverride = options.seed !== undefined ? toFiniteNumber(options.seed) : null;
1272
+ var speedOverride = options.speed !== undefined ? toFiniteNumber(options.speed) : null;
1143
1273
  var customLabel = typeof options.label === 'string' && options.label ? options.label : null;
1144
1274
  var root = document.createElement('div');
1145
1275
  root.className = 'hj-capsule-root hj-capsule-cosmic';
@@ -1191,25 +1321,49 @@ function createCapsule(container) {
1191
1321
  root.appendChild(canvas);
1192
1322
  container.appendChild(root);
1193
1323
  var emitter = createEmitter();
1194
- var paused = merged.respectReducedMotion && prefersReducedMotion$2();
1324
+ var manuallyPaused = merged.paused === true;
1325
+ var reducedPaused = false;
1326
+ var staticMode = merged.static === true;
1327
+ var contextLost = false;
1195
1328
  var disposed = false;
1329
+ var renderOnce = function renderOnce() {};
1196
1330
  var dirty = {
1197
1331
  preset: false,
1198
1332
  seed: false,
1199
1333
  speed: false,
1200
1334
  colors: false,
1201
1335
  textRatio: false,
1202
- cssVars: false
1336
+ cssVars: false,
1337
+ mouseColor: false,
1338
+ quality: false,
1339
+ renderScale: false,
1340
+ paused: false,
1341
+ static: false,
1342
+ fps: false
1203
1343
  };
1204
1344
  var renderer;
1205
1345
  var useWebgl = merged.renderer !== 'canvas2d';
1206
1346
  if (useWebgl) {
1207
1347
  try {
1208
1348
  renderer = new CosmicRenderer(canvas, merged, {
1209
- dprCap: dprCapFor(merged.quality)
1349
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
1350
+ mouseColor: merged.mouseColor !== false,
1351
+ powerPreference: merged.powerPreference,
1352
+ onContextLost: function onContextLost() {
1353
+ contextLost = true;
1354
+ emitter.emit('contextlost', {});
1355
+ },
1356
+ onContextRestored: function onContextRestored() {
1357
+ contextLost = false;
1358
+ emitter.emit('contextrestored', {});
1359
+ renderOnce();
1360
+ wakeScheduler();
1361
+ }
1210
1362
  });
1211
1363
  } catch (error) {
1212
- renderer = new FallbackRenderer(canvas, merged);
1364
+ renderer = new FallbackRenderer(canvas, merged, {
1365
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale)
1366
+ });
1213
1367
  nextTick(function () {
1214
1368
  if (disposed) return;
1215
1369
  emitter.emit('error', {
@@ -1218,8 +1372,15 @@ function createCapsule(container) {
1218
1372
  });
1219
1373
  }
1220
1374
  } else {
1221
- renderer = new FallbackRenderer(canvas, merged);
1375
+ renderer = new FallbackRenderer(canvas, merged, {
1376
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale)
1377
+ });
1222
1378
  }
1379
+ var animationTime = 0;
1380
+ var frameGate = createFrameGate(merged.fps);
1381
+ renderOnce = function renderOnce() {
1382
+ return renderer.draw(animationTime);
1383
+ };
1223
1384
  var applySize = function applySize() {
1224
1385
  root.style.width = parseSize(merged.width);
1225
1386
  root.style.height = parseSize(merged.height);
@@ -1237,11 +1398,21 @@ function createCapsule(container) {
1237
1398
  renderer.resize();
1238
1399
  };
1239
1400
  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);
1401
+ var resize = function resize() {
1402
+ renderer.resize();
1403
+ if (manuallyPaused || reducedPaused || staticMode) renderOnce();
1404
+ };
1405
+ var resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(resize) : null;
1406
+ if (resizeObserver) resizeObserver.observe(root);else window.addEventListener('resize', resize);
1407
+ var visibility = createVisibilityGuard(root, wakeScheduler);
1408
+ var motionPreference = createReducedMotionPreference(merged.respectReducedMotion, function (matches) {
1409
+ reducedPaused = matches;
1410
+ if (matches) renderOnce();else wakeScheduler();
1411
+ });
1412
+ reducedPaused = motionPreference.matches();
1413
+ var isMotionPaused = function isMotionPaused() {
1414
+ return manuallyPaused || reducedPaused || staticMode || contextLost;
1415
+ };
1245
1416
  root.addEventListener('pointerenter', function () {
1246
1417
  return emitter.emit('pointerenter', {
1247
1418
  preset: _objectSpread2({}, preset)
@@ -1276,12 +1447,12 @@ function createCapsule(container) {
1276
1447
  preset: _objectSpread2({}, preset)
1277
1448
  });
1278
1449
  });
1279
- var animationTime = 0;
1450
+ renderOnce();
1280
1451
  var unsubscribe = subscribeScheduler(function (delta) {
1281
1452
  animationTime += delta;
1282
- if (visibility.isVisible()) renderer.draw(animationTime);
1453
+ if (frameGate.shouldDraw(delta)) renderer.draw(animationTime);
1283
1454
  }, function () {
1284
- return paused;
1455
+ return isMotionPaused() || !visibility.isVisible();
1285
1456
  });
1286
1457
  nextTick(function () {
1287
1458
  if (disposed) return;
@@ -1304,11 +1475,15 @@ function createCapsule(container) {
1304
1475
  var next = getPreset('capsule', ref);
1305
1476
  dirty.preset = true;
1306
1477
  Object.assign(preset, next);
1478
+ if (colorOverride) preset.colors = _toConsumableArray(colorOverride);
1479
+ if (seedOverride !== null) preset.seed = seedOverride;
1480
+ if (speedOverride !== null) preset.speed = speedOverride;
1307
1481
  var nextColors = preset.colors.map(normalizeColor);
1308
1482
  if (nextColors.every(Boolean)) preset.colors = nextColors;
1309
1483
  renderer.setPreset(_objectSpread2({}, preset));
1310
1484
  syncTheme();
1311
1485
  renderer.resize();
1486
+ if (isMotionPaused()) renderOnce();
1312
1487
  emitter.emit('presetchange', {
1313
1488
  preset: _objectSpread2({}, next)
1314
1489
  });
@@ -1320,26 +1495,32 @@ function createCapsule(container) {
1320
1495
  return !color;
1321
1496
  })) return this;
1322
1497
  dirty.colors = true;
1498
+ colorOverride = _toConsumableArray(next);
1323
1499
  preset.colors = next;
1324
1500
  renderer.setPreset(_objectSpread2(_objectSpread2({}, preset), {}, {
1325
1501
  colors: next
1326
1502
  }));
1503
+ if (isMotionPaused()) renderOnce();
1327
1504
  return this;
1328
1505
  },
1329
1506
  setSeed: function setSeed(seed) {
1330
- var value = Number(seed);
1331
- if (!Number.isFinite(value)) return this;
1507
+ var value = toFiniteNumber(seed);
1508
+ if (value === null) return this;
1332
1509
  dirty.seed = true;
1510
+ seedOverride = value;
1333
1511
  preset.seed = value;
1334
1512
  renderer.setPreset(_objectSpread2({}, preset));
1513
+ if (isMotionPaused()) renderOnce();
1335
1514
  return this;
1336
1515
  },
1337
1516
  setSpeed: function setSpeed(speed) {
1338
- var value = Number(speed);
1339
- if (!Number.isFinite(value)) return this;
1517
+ var value = toFiniteNumber(speed);
1518
+ if (value === null) return this;
1340
1519
  dirty.speed = true;
1520
+ speedOverride = value;
1341
1521
  preset.speed = value;
1342
1522
  renderer.setPreset(_objectSpread2({}, preset));
1523
+ if (isMotionPaused()) renderOnce();
1343
1524
  return this;
1344
1525
  },
1345
1526
  setText: function setText(content) {
@@ -1375,6 +1556,7 @@ function createCapsule(container) {
1375
1556
  return this;
1376
1557
  },
1377
1558
  setMouseColor: function setMouseColor(value) {
1559
+ dirty.mouseColor = true;
1378
1560
  merged.mouseColor = value !== false;
1379
1561
  if (typeof renderer.setMouseColor === 'function') renderer.setMouseColor(merged.mouseColor);
1380
1562
  return this;
@@ -1389,6 +1571,7 @@ function createCapsule(container) {
1389
1571
  merged.height = height;
1390
1572
  }
1391
1573
  applySize();
1574
+ if (isMotionPaused()) renderOnce();
1392
1575
  return this;
1393
1576
  },
1394
1577
  randomize: function randomize() {
@@ -1396,23 +1579,59 @@ function createCapsule(container) {
1396
1579
  return this;
1397
1580
  },
1398
1581
  pause: function pause() {
1399
- paused = true;
1582
+ dirty.paused = true;
1583
+ manuallyPaused = true;
1584
+ renderOnce();
1400
1585
  return this;
1401
1586
  },
1402
1587
  resume: function resume() {
1403
- paused = false;
1588
+ dirty.paused = true;
1589
+ manuallyPaused = false;
1590
+ wakeScheduler();
1591
+ return this;
1592
+ },
1593
+ setPaused: function setPaused(value) {
1594
+ return value ? this.pause() : this.resume();
1595
+ },
1596
+ setStatic: function setStatic(value) {
1597
+ dirty.static = true;
1598
+ staticMode = value === true;
1599
+ merged.static = staticMode;
1600
+ if (staticMode) renderOnce();else wakeScheduler();
1601
+ return this;
1602
+ },
1603
+ setFps: function setFps(fps) {
1604
+ dirty.fps = true;
1605
+ merged.fps = frameGate.setFps(fps);
1606
+ wakeScheduler();
1404
1607
  return this;
1405
1608
  },
1406
1609
  setQuality: function setQuality(quality) {
1610
+ dirty.quality = true;
1407
1611
  merged.quality = quality;
1408
- if (typeof renderer.setDprCap === 'function') renderer.setDprCap(dprCapFor(quality));
1612
+ if (typeof renderer.setDprCap === 'function') {
1613
+ renderer.setDprCap(effectiveDprCap(quality, merged.renderScale));
1614
+ }
1615
+ if (isMotionPaused()) renderOnce();
1616
+ return this;
1617
+ },
1618
+ setRenderScale: function setRenderScale(renderScale) {
1619
+ var value = Number(renderScale);
1620
+ if (!Number.isFinite(value)) return this;
1621
+ dirty.renderScale = true;
1622
+ merged.renderScale = Math.min(1, Math.max(0.25, value));
1623
+ if (typeof renderer.setDprCap === 'function') {
1624
+ renderer.setDprCap(effectiveDprCap(merged.quality, merged.renderScale));
1625
+ }
1626
+ if (isMotionPaused()) renderOnce();
1409
1627
  return this;
1410
1628
  },
1411
1629
  dispose: function dispose() {
1412
1630
  disposed = true;
1413
1631
  unsubscribe();
1414
1632
  visibility.dispose();
1415
- if (resizeObserver) resizeObserver.disconnect();else window.removeEventListener('resize', renderer.resize);
1633
+ motionPreference.dispose();
1634
+ if (resizeObserver) resizeObserver.disconnect();else window.removeEventListener('resize', resize);
1416
1635
  renderer.dispose();
1417
1636
  root.remove();
1418
1637
  },
@@ -1422,6 +1641,24 @@ function createCapsule(container) {
1422
1641
  get cssVars() {
1423
1642
  return merged.cssVars;
1424
1643
  },
1644
+ get mouseColor() {
1645
+ return merged.mouseColor;
1646
+ },
1647
+ get quality() {
1648
+ return merged.quality;
1649
+ },
1650
+ get renderScale() {
1651
+ return merged.renderScale;
1652
+ },
1653
+ get paused() {
1654
+ return manuallyPaused;
1655
+ },
1656
+ get static() {
1657
+ return staticMode;
1658
+ },
1659
+ get fps() {
1660
+ return frameGate.getFps();
1661
+ },
1425
1662
  dirty: dirty
1426
1663
  };
1427
1664
  }
@@ -1562,64 +1799,95 @@ function createProgram(gl) {
1562
1799
  var ProgressFlowRenderer = /*#__PURE__*/function () {
1563
1800
  function ProgressFlowRenderer(canvas, preset) {
1564
1801
  var _PROFILE_INDEX$preset;
1802
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
1565
1803
  _classCallCheck(this, ProgressFlowRenderer);
1566
1804
  var gl = canvas.getContext('webgl2', {
1567
1805
  alpha: false,
1568
1806
  antialias: true,
1569
1807
  premultipliedAlpha: false,
1570
- powerPreference: 'high-performance'
1808
+ powerPreference: options.powerPreference || 'high-performance'
1571
1809
  });
1572
1810
  if (!gl) throw new Error('WebGL2 unavailable');
1573
1811
  this.canvas = canvas;
1812
+ this.options = options;
1574
1813
  this.gl = gl;
1575
- this.program = createProgram(gl);
1576
1814
  this.profile = preset.edgeStyle === 'tide' ? 3 : (_PROFILE_INDEX$preset = PROFILE_INDEX[preset.id]) !== null && _PROFILE_INDEX$preset !== void 0 ? _PROFILE_INDEX$preset : 2;
1577
1815
  this.seed = stringSeed$1("".concat(preset.id, "-shader")) * 13.7 + 1.0;
1578
1816
  this.colors = preset.colors.map(hexToRgb01);
1579
1817
  this.motionData = getProgressMotionData(preset.id, preset.edgeStyle);
1580
1818
  var motionFactor = preset.edgeStyle === 'tide' ? MOTION_SCALE_FACTORS.tide : MOTION_SCALE_FACTORS[preset.id] || 1.04;
1581
1819
  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;
1820
+ this.disposed = false;
1821
+ this.contextLost = false;
1822
+ this.setupResources();
1823
+ this.bindContextEvents();
1621
1824
  }
1622
1825
  return _createClass(ProgressFlowRenderer, [{
1826
+ key: "setupResources",
1827
+ value: function setupResources() {
1828
+ var gl = this.gl;
1829
+ this.program = createProgram(gl);
1830
+ this.position = gl.getAttribLocation(this.program, 'a_position');
1831
+ this.uniforms = {
1832
+ resolution: gl.getUniformLocation(this.program, 'u_resolution'),
1833
+ time: gl.getUniformLocation(this.program, 'u_time'),
1834
+ progress: gl.getUniformLocation(this.program, 'u_progress'),
1835
+ seed: gl.getUniformLocation(this.program, 'u_seed'),
1836
+ profile: gl.getUniformLocation(this.program, 'u_profile'),
1837
+ motion: gl.getUniformLocation(this.program, 'u_motion'),
1838
+ effect: gl.getUniformLocation(this.program, 'u_effect'),
1839
+ hasEffect: gl.getUniformLocation(this.program, 'u_hasEffect'),
1840
+ effectFrames: gl.getUniformLocation(this.program, 'u_effectFrames'),
1841
+ motionDuration: gl.getUniformLocation(this.program, 'u_motionDuration'),
1842
+ motionScale: gl.getUniformLocation(this.program, 'u_motionScale'),
1843
+ dark: gl.getUniformLocation(this.program, 'u_dark'),
1844
+ accentA: gl.getUniformLocation(this.program, 'u_accentA'),
1845
+ accentB: gl.getUniformLocation(this.program, 'u_accentB'),
1846
+ glow: gl.getUniformLocation(this.program, 'u_glow')
1847
+ };
1848
+ this.buffer = gl.createBuffer();
1849
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
1850
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]), gl.STATIC_DRAW);
1851
+ this.motionTexture = gl.createTexture();
1852
+ gl.activeTexture(gl.TEXTURE0);
1853
+ gl.bindTexture(gl.TEXTURE_2D, this.motionTexture);
1854
+ gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
1855
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8, PROGRESS_MOTION_WIDTH, PROGRESS_MOTION_HEIGHT, 0, gl.RED, gl.UNSIGNED_BYTE, this.motionData);
1856
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
1857
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
1858
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
1859
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
1860
+ this.effectTexture = gl.createTexture();
1861
+ gl.activeTexture(gl.TEXTURE1);
1862
+ gl.bindTexture(gl.TEXTURE_2D, this.effectTexture);
1863
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, 1, 1, 0, gl.RGB, gl.UNSIGNED_BYTE, new Uint8Array([32, 33, 38]));
1864
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
1865
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
1866
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
1867
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
1868
+ this.effectUploaded = false;
1869
+ }
1870
+ }, {
1871
+ key: "bindContextEvents",
1872
+ value: function bindContextEvents() {
1873
+ var _this = this;
1874
+ this.onContextLost = function (event) {
1875
+ event.preventDefault();
1876
+ if (_this.disposed) return;
1877
+ _this.contextLost = true;
1878
+ if (typeof _this.options.onContextLost === 'function') _this.options.onContextLost(event);
1879
+ };
1880
+ this.onContextRestored = function () {
1881
+ if (_this.disposed) return;
1882
+ _this.setupResources();
1883
+ _this.contextLost = false;
1884
+ _this.gl.viewport(0, 0, _this.canvas.width, _this.canvas.height);
1885
+ if (typeof _this.options.onContextRestored === 'function') _this.options.onContextRestored();
1886
+ };
1887
+ this.canvas.addEventListener('webglcontextlost', this.onContextLost, false);
1888
+ this.canvas.addEventListener('webglcontextrestored', this.onContextRestored, false);
1889
+ }
1890
+ }, {
1623
1891
  key: "resize",
1624
1892
  value: function resize(width, height, dpr) {
1625
1893
  var pixelWidth = Math.max(1, Math.round(width * dpr));
@@ -1634,6 +1902,7 @@ var ProgressFlowRenderer = /*#__PURE__*/function () {
1634
1902
  key: "draw",
1635
1903
  value: function draw(time, progress) {
1636
1904
  var effectImage = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
1905
+ if (this.disposed || this.contextLost) return;
1637
1906
  var gl = this.gl;
1638
1907
  gl.useProgram(this.program);
1639
1908
  gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
@@ -1650,7 +1919,8 @@ var ProgressFlowRenderer = /*#__PURE__*/function () {
1650
1919
  gl.uniform1f(this.uniforms.motionDuration, PROGRESS_MOTION_DURATION);
1651
1920
  gl.uniform1f(this.uniforms.motionScale, this.motionScale);
1652
1921
  var hasEffect = this.effectUploaded ? 1 : 0;
1653
- if (!this.effectUploaded && effectImage && effectImage.complete && effectImage.naturalWidth > 0) {
1922
+ var effectWidth = effectImage && (effectImage.naturalWidth || effectImage.width || 0);
1923
+ if (!this.effectUploaded && effectImage && effectWidth > 0) {
1654
1924
  gl.activeTexture(gl.TEXTURE1);
1655
1925
  gl.bindTexture(gl.TEXTURE_2D, this.effectTexture);
1656
1926
  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
@@ -1682,7 +1952,10 @@ var ProgressFlowRenderer = /*#__PURE__*/function () {
1682
1952
  }, {
1683
1953
  key: "dispose",
1684
1954
  value: function dispose() {
1955
+ this.disposed = true;
1685
1956
  var gl = this.gl;
1957
+ this.canvas.removeEventListener('webglcontextlost', this.onContextLost, false);
1958
+ this.canvas.removeEventListener('webglcontextrestored', this.onContextRestored, false);
1686
1959
  gl.deleteBuffer(this.buffer);
1687
1960
  gl.deleteTexture(this.motionTexture);
1688
1961
  gl.deleteTexture(this.effectTexture);
@@ -1693,8 +1966,9 @@ var ProgressFlowRenderer = /*#__PURE__*/function () {
1693
1966
  }]);
1694
1967
  }();
1695
1968
  function createProgressFlowRenderer(canvas, preset) {
1969
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
1696
1970
  try {
1697
- return new ProgressFlowRenderer(canvas, preset);
1971
+ return new ProgressFlowRenderer(canvas, preset, options);
1698
1972
  } catch (error) {
1699
1973
  console.warn('[画境观屿] 进度流体 WebGL2 不可用,使用 Canvas 2D 降级。', error);
1700
1974
  return null;
@@ -1754,50 +2028,49 @@ function createAtlas(id) {
1754
2028
  context.fillRect(20, 0, 44, FRAME_HEIGHT);
1755
2029
  context.restore();
1756
2030
  }
1757
- var image = new Image();
1758
- image.decoding = 'async';
1759
- var state = {
1760
- image: image,
1761
- ready: false
2031
+
2032
+ // A canvas is a valid TexImageSource. Keeping it directly avoids a PNG
2033
+ // encode -> base64 allocation -> Image decode round trip at startup.
2034
+ return {
2035
+ image: canvas,
2036
+ ready: true
1762
2037
  };
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
2038
  }
1776
2039
  function getProgressReferenceAtlas(id) {
1777
2040
  if (!CACHE[id]) CACHE[id] = createAtlas(id);
1778
2041
  return CACHE[id];
1779
2042
  }
1780
2043
 
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}
2044
+ /**
2045
+ * Attach a WebGL2 fluid overlay to a progress capsule root.
2046
+ *
2047
+ * @param {object} params
2048
+ * @param {HTMLElement} params.root progress capsule root element
2049
+ * @param {HTMLCanvasElement} params.canvas 2D fallback canvas (kept beneath)
2050
+ * @param {object} params.preset progress preset
2051
+ * @param {() => number} params.getProgress reads the current progress value
2052
+ * @returns {{ update(flowTime: number): void, setDprCap(cap: number): void, dispose(): void } | null}
1790
2053
  */
1791
2054
  function attachProgressFlowOverlay(_ref) {
1792
2055
  var root = _ref.root,
1793
2056
  canvas = _ref.canvas,
1794
2057
  preset = _ref.preset,
1795
- getProgress = _ref.getProgress;
2058
+ getProgress = _ref.getProgress,
2059
+ _ref$dprCap = _ref.dprCap,
2060
+ dprCap = _ref$dprCap === void 0 ? 2 : _ref$dprCap,
2061
+ _ref$powerPreference = _ref.powerPreference,
2062
+ powerPreference = _ref$powerPreference === void 0 ? 'high-performance' : _ref$powerPreference,
2063
+ onContextLost = _ref.onContextLost,
2064
+ onContextRestored = _ref.onContextRestored;
1796
2065
  var overlay = document.createElement('canvas');
1797
2066
  overlay.className = 'hj-progress-canvas hj-progress-overlay';
1798
2067
  overlay.setAttribute('aria-hidden', 'true');
1799
2068
  canvas.insertAdjacentElement('afterend', overlay);
1800
- var renderer = createProgressFlowRenderer(overlay, preset);
2069
+ var renderer = createProgressFlowRenderer(overlay, preset, {
2070
+ powerPreference: powerPreference,
2071
+ onContextLost: onContextLost,
2072
+ onContextRestored: onContextRestored
2073
+ });
1801
2074
  if (!renderer) {
1802
2075
  overlay.remove();
1803
2076
  return null;
@@ -1808,7 +2081,7 @@ function attachProgressFlowOverlay(_ref) {
1808
2081
  var bounds = root.getBoundingClientRect();
1809
2082
  var width = Math.max(1, bounds.width);
1810
2083
  var height = Math.max(1, bounds.height);
1811
- var dpr = Math.min(window.devicePixelRatio || 1, 2);
2084
+ var dpr = Math.min(window.devicePixelRatio || 1, dprCap);
1812
2085
  overlay.style.width = "".concat(width, "px");
1813
2086
  overlay.style.height = "".concat(height, "px");
1814
2087
  renderer.resize(width, height, dpr);
@@ -1832,6 +2105,10 @@ function attachProgressFlowOverlay(_ref) {
1832
2105
  setColors: function setColors(colors) {
1833
2106
  renderer.setColors(colors);
1834
2107
  },
2108
+ setDprCap: function setDprCap(cap) {
2109
+ dprCap = cap;
2110
+ resize();
2111
+ },
1835
2112
  dispose: function dispose() {
1836
2113
  if (resizeObserver) resizeObserver.disconnect();
1837
2114
  if (onWindowResize) window.removeEventListener('resize', onWindowResize);
@@ -1842,6 +2119,47 @@ function attachProgressFlowOverlay(_ref) {
1842
2119
  };
1843
2120
  }
1844
2121
 
2122
+ function normalizeRange(min, max) {
2123
+ var fallbackMin = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
2124
+ var fallbackMax = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 100;
2125
+ var nextMin = toFiniteNumber(min);
2126
+ var nextMax = toFiniteNumber(max);
2127
+ if (nextMin === null) nextMin = fallbackMin;
2128
+ if (nextMax === null) nextMax = fallbackMax;
2129
+ if (nextMin > nextMax) {
2130
+ var swap = nextMin;
2131
+ nextMin = nextMax;
2132
+ nextMax = swap;
2133
+ }
2134
+ return [nextMin, nextMax];
2135
+ }
2136
+ function clampProgressValue(value, min, max) {
2137
+ var number = toFiniteNumber(value);
2138
+ if (number === null) return null;
2139
+ return Math.min(Math.max(number, min), max);
2140
+ }
2141
+ function normalizeProgressStep(step) {
2142
+ var fallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'any';
2143
+ if (step === 'any' || step == null) return 'any';
2144
+ var increment = toFiniteNumber(step);
2145
+ return increment !== null && increment > 0 ? increment : fallback;
2146
+ }
2147
+ function snapProgressValue(value, min, max) {
2148
+ var step = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'any';
2149
+ var clamped = clampProgressValue(value, min, max);
2150
+ if (clamped === null) return null;
2151
+ if (step === 'any' || step == null) return clamped;
2152
+ var increment = normalizeProgressStep(step);
2153
+ if (increment === 'any') return clamped;
2154
+ var snapped = min + Math.round((clamped - min) / increment) * increment;
2155
+ return Math.min(Math.max(Number(snapped.toFixed(12)), min), max);
2156
+ }
2157
+ function progressRatio(value, min, max) {
2158
+ var range = max - min;
2159
+ if (!Number.isFinite(range) || range <= 0) return 0;
2160
+ return Math.min(Math.max((value - min) / range, 0), 1);
2161
+ }
2162
+
1845
2163
  var clamp = function clamp(value, min, max) {
1846
2164
  return Math.min(Math.max(value, min), max);
1847
2165
  };
@@ -1868,9 +2186,6 @@ function interpolate(template, values) {
1868
2186
  return values[key] !== undefined ? values[key] : '';
1869
2187
  });
1870
2188
  }
1871
- function prefersReducedMotion$1() {
1872
- return typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches;
1873
- }
1874
2189
  var FLOW_PROFILES = {
1875
2190
  'model-training': {
1876
2191
  cycles: [0.98, 3.05, 5.45],
@@ -1931,8 +2246,6 @@ var FLOW_PROFILES = {
1931
2246
  var ProgressCapsuleController = /*#__PURE__*/function () {
1932
2247
  function ProgressCapsuleController(_ref) {
1933
2248
  var _options$valueSuffix,
1934
- _options$min,
1935
- _options$max,
1936
2249
  _options$value,
1937
2250
  _this = this;
1938
2251
  var root = _ref.root,
@@ -1954,11 +2267,18 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1954
2267
  this.dirty = dirty;
1955
2268
  this.valueSuffix = (_options$valueSuffix = options.valueSuffix) !== null && _options$valueSuffix !== void 0 ? _options$valueSuffix : copy.valueSuffix;
1956
2269
  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);
2270
+ var _normalizeRange = normalizeRange(options.min, options.max);
2271
+ var _normalizeRange2 = _slicedToArray(_normalizeRange, 2);
2272
+ this.min = _normalizeRange2[0];
2273
+ this.max = _normalizeRange2[1];
2274
+ this.step = normalizeProgressStep(options.step);
2275
+ this.precision = Number.isInteger(Number(options.precision)) ? clamp(Number(options.precision), 0, 20) : 0;
2276
+ this.formatValue = typeof options.formatValue === 'function' ? options.formatValue : null;
2277
+ this.direction = options.direction === 'rtl' ? 'rtl' : 'ltr';
2278
+ this.dprCap = effectiveDprCap(options.quality, options.renderScale);
1960
2279
  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);
2280
+ this.value = snapProgressValue((_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : preset.initialProgress, this.min, this.max, this.step);
2281
+ if (this.value === null) this.value = clamp(preset.initialProgress, this.min, this.max);
1962
2282
  this.dragging = false;
1963
2283
  this.webglActive = false;
1964
2284
  this.flowTime = stringSeed(preset.id) * 31;
@@ -1968,10 +2288,12 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1968
2288
  this.width = 0;
1969
2289
  this.height = 0;
1970
2290
  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);
2291
+ this.onResize = function () {
2292
+ _this.resizeCanvas();
2293
+ if (typeof _this.options.onResize === 'function') _this.options.onResize();
2294
+ };
2295
+ this.resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(this.onResize) : null;
2296
+ if (this.resizeObserver) this.resizeObserver.observe(this.root);else window.addEventListener('resize', this.onResize);
1975
2297
  this.suppressEvents = true;
1976
2298
  this.setProgress(this.value, 'init');
1977
2299
  this.suppressEvents = false;
@@ -1984,16 +2306,37 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1984
2306
  this.randomState = Math.imul(this.randomState, 1664525) + 1013904223 >>> 0;
1985
2307
  return this.randomState / 4294967296;
1986
2308
  }
2309
+ }, {
2310
+ key: "displayText",
2311
+ value: function displayText() {
2312
+ if (this.formatValue) {
2313
+ return String(this.formatValue(this.value, {
2314
+ min: this.min,
2315
+ max: this.max,
2316
+ suffix: this.valueSuffix
2317
+ }));
2318
+ }
2319
+ var number = this.precision > 0 ? this.value.toFixed(this.precision) : String(Math.round(this.value));
2320
+ return "".concat(number).concat(this.valueSuffix);
2321
+ }
2322
+ }, {
2323
+ key: "syncValueDom",
2324
+ value: function syncValueDom() {
2325
+ var text = this.displayText();
2326
+ this.root.style.setProperty('--progress', this.value.toFixed(2));
2327
+ this.root.style.setProperty('--progress-ratio', progressRatio(this.value, this.min, this.max).toFixed(4));
2328
+ this.root.setAttribute('aria-valuenow', String(this.value));
2329
+ this.root.setAttribute('aria-valuetext', text);
2330
+ if (this.valueElement) this.valueElement.textContent = text;
2331
+ }
1987
2332
  }, {
1988
2333
  key: "setProgress",
1989
2334
  value: function setProgress(nextValue) {
1990
2335
  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);
2336
+ var next = snapProgressValue(nextValue, this.min, this.max, this.step);
2337
+ if (next === null) return false;
2338
+ this.value = next;
2339
+ this.syncValueDom();
1997
2340
  if (!this.suppressEvents) {
1998
2341
  if (this.dirty) this.dirty.value = true;
1999
2342
  this.emitter.emit('change', {
@@ -2001,19 +2344,58 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
2001
2344
  source: source
2002
2345
  });
2003
2346
  }
2347
+ return true;
2004
2348
  }
2005
2349
  }, {
2006
2350
  key: "setValueSuffix",
2007
2351
  value: function setValueSuffix(suffix) {
2352
+ if (this.dirty) this.dirty.valueSuffix = true;
2008
2353
  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));
2354
+ this.syncValueDom();
2355
+ return this;
2356
+ }
2357
+ }, {
2358
+ key: "setStep",
2359
+ value: function setStep(step) {
2360
+ var nextStep = normalizeProgressStep(step, this.step);
2361
+ if (nextStep === this.step) return this;
2362
+ if (this.dirty) this.dirty.step = true;
2363
+ this.step = nextStep;
2364
+ var next = snapProgressValue(this.value, this.min, this.max, this.step);
2365
+ if (next !== null && next !== this.value) this.setProgress(next, 'prop');
2366
+ return this;
2367
+ }
2368
+ }, {
2369
+ key: "setPrecision",
2370
+ value: function setPrecision(precision) {
2371
+ var value = Number(precision);
2372
+ if (!Number.isInteger(value) || value < 0 || value > 20) return this;
2373
+ if (this.dirty) this.dirty.precision = true;
2374
+ this.precision = value;
2375
+ this.syncValueDom();
2376
+ return this;
2377
+ }
2378
+ }, {
2379
+ key: "setFormatValue",
2380
+ value: function setFormatValue(formatter) {
2381
+ if (formatter != null && typeof formatter !== 'function') return this;
2382
+ if (this.dirty) this.dirty.formatValue = true;
2383
+ this.formatValue = formatter || null;
2384
+ this.syncValueDom();
2385
+ return this;
2386
+ }
2387
+ }, {
2388
+ key: "setDirection",
2389
+ value: function setDirection(direction) {
2390
+ if (this.dirty) this.dirty.direction = true;
2391
+ this.direction = direction === 'rtl' ? 'rtl' : 'ltr';
2392
+ this.root.dataset.direction = this.direction;
2012
2393
  return this;
2013
2394
  }
2014
2395
  }, {
2015
2396
  key: "setShowValue",
2016
2397
  value: function setShowValue(show) {
2398
+ if (this.dirty) this.dirty.showValue = true;
2017
2399
  if (this.valueElement) this.valueElement.hidden = show === false;
2018
2400
  return this;
2019
2401
  }
@@ -2178,15 +2560,14 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
2178
2560
  }, {
2179
2561
  key: "setRange",
2180
2562
  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');
2563
+ var nextRange = normalizeRange(min, max, this.min, this.max);
2564
+ this.min = nextRange[0];
2565
+ this.max = nextRange[1];
2566
+ this.root.setAttribute('aria-valuemin', String(this.min));
2567
+ this.root.setAttribute('aria-valuemax', String(this.max));
2568
+ var next = snapProgressValue(this.value, this.min, this.max, this.step);
2569
+ if (next !== this.value) this.setProgress(next, 'prop');else this.syncValueDom();
2570
+ return this;
2190
2571
  }
2191
2572
  }, {
2192
2573
  key: "drawReferenceFlow",
@@ -2311,8 +2692,39 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
2311
2692
  value: function updateFromPointer(event) {
2312
2693
  var bounds = this.root.getBoundingClientRect();
2313
2694
  var ratio = bounds.width > 0 ? (event.clientX - bounds.left) / bounds.width : 0;
2695
+ if (this.direction === 'rtl') ratio = 1 - ratio;
2314
2696
  this.setProgress(this.min + ratio * (this.max - this.min), 'drag');
2315
2697
  }
2698
+ }, {
2699
+ key: "onKeyDown",
2700
+ value: function onKeyDown(event) {
2701
+ var range = this.max - this.min;
2702
+ var configuredStep = Number(this.step);
2703
+ var step = Number.isFinite(configuredStep) && configuredStep > 0 ? configuredStep : Math.max(range / 100, 1e-7);
2704
+ var direction = this.direction === 'rtl' ? -1 : 1;
2705
+ var aliases = {
2706
+ Left: 'ArrowLeft',
2707
+ Right: 'ArrowRight',
2708
+ Up: 'ArrowUp',
2709
+ Down: 'ArrowDown'
2710
+ };
2711
+ var keyCodes = {
2712
+ 35: 'End',
2713
+ 36: 'Home',
2714
+ 33: 'PageUp',
2715
+ 34: 'PageDown',
2716
+ 37: 'ArrowLeft',
2717
+ 38: 'ArrowUp',
2718
+ 39: 'ArrowRight',
2719
+ 40: 'ArrowDown'
2720
+ };
2721
+ var key = aliases[event.key] || event.key || keyCodes[event.keyCode];
2722
+ var next = null;
2723
+ 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;
2724
+ if (next === null) return;
2725
+ event.preventDefault();
2726
+ this.setProgress(next, 'keyboard');
2727
+ }
2316
2728
  }, {
2317
2729
  key: "beginDrag",
2318
2730
  value: function beginDrag(event) {
@@ -2400,6 +2812,12 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
2400
2812
  this.root.addEventListener('pointerup', this.handlers.pointerup);
2401
2813
  this.root.addEventListener('pointercancel', this.handlers.pointercancel);
2402
2814
  }
2815
+ if (this.options.keyboard !== false) {
2816
+ this.handlers.keydown = function (event) {
2817
+ return _this3.onKeyDown(event);
2818
+ };
2819
+ this.root.addEventListener('keydown', this.handlers.keydown);
2820
+ }
2403
2821
  }
2404
2822
  }, {
2405
2823
  key: "update",
@@ -2418,20 +2836,19 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
2418
2836
  key: "randomize",
2419
2837
  value: function randomize() {
2420
2838
  this.flowTime = this.random() * 40;
2839
+ return this.flowTime;
2421
2840
  }
2422
2841
  }, {
2423
2842
  key: "setColors",
2424
2843
  value: function setColors(colors) {
2425
2844
  if (!Array.isArray(colors) || colors.length !== 4) return;
2426
- this.preset = _objectSpread2(_objectSpread2({}, this.preset), {}, {
2427
- colors: colors
2428
- });
2845
+ this.preset.colors = _toConsumableArray(colors);
2429
2846
  }
2430
2847
  }, {
2431
2848
  key: "dispose",
2432
2849
  value: function dispose() {
2433
2850
  if (this._dragRaf) cancelAnimationFrame(this._dragRaf);
2434
- if (this.resizeObserver) this.resizeObserver.disconnect();else window.removeEventListener('resize', this.resizeCanvas);
2851
+ if (this.resizeObserver) this.resizeObserver.disconnect();else window.removeEventListener('resize', this.onResize);
2435
2852
  for (var _i = 0, _Object$keys = Object.keys(this.handlers); _i < _Object$keys.length; _i++) {
2436
2853
  var name = _Object$keys[_i];
2437
2854
  var handler = this.handlers[name];
@@ -2442,15 +2859,16 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
2442
2859
  }]);
2443
2860
  }();
2444
2861
 
2445
- /**
2862
+ /**
2446
2863
  * Mount a fluid progress capsule into `container`.
2447
2864
  *
2448
- * Options: preset (literary name), width, height, value, min, max,
2449
- * draggable, colors, edgeStyle, textRatio (0-100, text region
2865
+ * Options: preset (literary name), width, height, value/modelValue, min, max,
2866
+ * step, draggable, keyboard, direction, precision/formatValue, colors,
2867
+ * edgeStyle, textRatio (0-100, text region
2450
2868
  * width in percent), text (HTML string or DOM nodes for the text slot),
2451
2869
  * colorContent (HTML string or DOM nodes for the color slot),
2452
- * showValue (show/hide the right-side percentage), quality,
2453
- * respectReducedMotion, cssVars.
2870
+ * showValue (show/hide the right-side percentage), quality, renderScale,
2871
+ * powerPreference, fps, paused/static, respectReducedMotion, cssVars.
2454
2872
  */
2455
2873
  function createProgressCapsule(container) {
2456
2874
  var _options$preset, _merged$min, _merged$max;
@@ -2460,14 +2878,20 @@ function createProgressCapsule(container) {
2460
2878
  }
2461
2879
  var preset = _objectSpread2({}, getPreset('progress', (_options$preset = options.preset) !== null && _options$preset !== void 0 ? _options$preset : '星火'));
2462
2880
  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;
2881
+ var _normalizeRange3 = normalizeRange(merged.min, merged.max);
2882
+ var _normalizeRange4 = _slicedToArray(_normalizeRange3, 2);
2883
+ merged.min = _normalizeRange4[0];
2884
+ merged.max = _normalizeRange4[1];
2885
+ if (merged.modelValue !== undefined) merged.value = merged.modelValue;
2886
+ var normalizedColors = Array.isArray(merged.colors) ? merged.colors.map(normalizeColor) : [];
2887
+ if (normalizedColors.length === 4 && normalizedColors.every(Boolean)) preset.colors = normalizedColors;
2888
+ var initialEdgeStyle = merged.edgeStyle === 'tide' || merged.edgeStyle === 'flow' ? merged.edgeStyle : preset.edgeStyle;
2889
+ preset.edgeStyle = initialEdgeStyle;
2890
+ merged.edgeStyle = initialEdgeStyle;
2891
+ merged.colors = _toConsumableArray(preset.colors);
2892
+ var optionColors = Array.isArray(options.colors) ? options.colors.map(normalizeColor) : [];
2893
+ var colorOverride = optionColors.length === 4 && optionColors.every(Boolean) ? _toConsumableArray(optionColors) : null;
2894
+ var edgeStyleOverride = options.edgeStyle === 'flow' || options.edgeStyle === 'tide' ? merged.edgeStyle : null;
2471
2895
  var copy = COPY;
2472
2896
  var customLabel = typeof options.label === 'string' && options.label ? options.label : null;
2473
2897
  // Vue 的裸布尔属性(<ProgressCapsule disabled />)会传成空字符串,
@@ -2476,18 +2900,31 @@ function createProgressCapsule(container) {
2476
2900
  var readonly = merged.readonly === true || merged.readonly === '' || merged.readonly === 'true';
2477
2901
  var locked = disabled || readonly;
2478
2902
  var effectiveDraggable = locked ? false : merged.draggable !== false;
2903
+ var effectiveKeyboard = locked ? false : merged.keyboard !== false;
2479
2904
  var dirty = {
2480
2905
  preset: false,
2481
2906
  colors: false,
2482
2907
  textRatio: false,
2483
2908
  cssVars: false,
2484
2909
  edgeStyle: false,
2485
- value: false
2910
+ value: false,
2911
+ step: false,
2912
+ precision: false,
2913
+ formatValue: false,
2914
+ direction: false,
2915
+ showValue: false,
2916
+ valueSuffix: false,
2917
+ quality: false,
2918
+ renderScale: false,
2919
+ paused: false,
2920
+ static: false,
2921
+ fps: false
2486
2922
  };
2487
2923
  var root = document.createElement('div');
2488
2924
  root.className = 'hj-capsule-root hj-progress-root';
2489
2925
  root.setAttribute('role', 'slider');
2490
2926
  root.setAttribute('data-draggable', String(effectiveDraggable));
2927
+ root.dataset.direction = merged.direction === 'rtl' ? 'rtl' : 'ltr';
2491
2928
  if (disabled) {
2492
2929
  root.setAttribute('aria-disabled', 'true');
2493
2930
  root.classList.add('is-disabled');
@@ -2496,7 +2933,8 @@ function createProgressCapsule(container) {
2496
2933
  root.setAttribute('aria-readonly', 'true');
2497
2934
  root.classList.add('is-readonly');
2498
2935
  }
2499
- if (locked) root.setAttribute('tabindex', '-1');
2936
+ root.setAttribute('tabindex', locked ? '-1' : effectiveKeyboard ? '0' : '-1');
2937
+ root.setAttribute('aria-orientation', 'horizontal');
2500
2938
  root.setAttribute('aria-valuemin', String((_merged$min = merged.min) !== null && _merged$min !== void 0 ? _merged$min : 0));
2501
2939
  root.setAttribute('aria-valuemax', String((_merged$max = merged.max) !== null && _merged$max !== void 0 ? _merged$max : 100));
2502
2940
  root.setAttribute('aria-valuenow', String(preset.initialProgress));
@@ -2551,8 +2989,22 @@ function createProgressCapsule(container) {
2551
2989
  root.appendChild(valueElement);
2552
2990
  container.appendChild(root);
2553
2991
  var emitter = createEmitter();
2554
- var paused = merged.respectReducedMotion && prefersReducedMotion$1();
2992
+ var manuallyPaused = merged.paused === true;
2993
+ var reducedPaused = false;
2994
+ var staticMode = merged.static === true;
2995
+ var contextLost = false;
2555
2996
  var disposed = false;
2997
+ var renderOnce = function renderOnce() {};
2998
+ var onContextLost = function onContextLost() {
2999
+ contextLost = true;
3000
+ emitter.emit('contextlost', {});
3001
+ };
3002
+ var onContextRestored = function onContextRestored() {
3003
+ contextLost = false;
3004
+ emitter.emit('contextrestored', {});
3005
+ renderOnce();
3006
+ wakeScheduler();
3007
+ };
2556
3008
  var applySize = function applySize() {
2557
3009
  root.style.width = parseSize(merged.width);
2558
3010
  root.style.height = parseSize(merged.height);
@@ -2573,7 +3025,11 @@ function createProgressCapsule(container) {
2573
3025
  preset: preset,
2574
3026
  emitter: emitter,
2575
3027
  options: _objectSpread2(_objectSpread2({}, merged), {}, {
2576
- draggable: effectiveDraggable
3028
+ draggable: effectiveDraggable,
3029
+ keyboard: effectiveKeyboard,
3030
+ onResize: function onResize() {
3031
+ if (manuallyPaused || reducedPaused || staticMode) renderOnce();
3032
+ }
2577
3033
  }),
2578
3034
  copy: copy,
2579
3035
  dirty: dirty
@@ -2585,8 +3041,12 @@ function createProgressCapsule(container) {
2585
3041
  canvas: canvas,
2586
3042
  preset: preset,
2587
3043
  getProgress: function getProgress() {
2588
- return controller.value;
2589
- }
3044
+ return progressRatio(controller.value, controller.min, controller.max) * 100;
3045
+ },
3046
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
3047
+ powerPreference: merged.powerPreference,
3048
+ onContextLost: onContextLost,
3049
+ onContextRestored: onContextRestored
2590
3050
  });
2591
3051
  }
2592
3052
  if (overlay) controller.webglActive = true;else if (merged.renderer !== 'canvas2d') {
@@ -2597,17 +3057,34 @@ function createProgressCapsule(container) {
2597
3057
  });
2598
3058
  });
2599
3059
  }
2600
- var visibility = createVisibilityGuard(root);
3060
+ var visibility = createVisibilityGuard(root, wakeScheduler);
3061
+ var motionPreference = createReducedMotionPreference(merged.respectReducedMotion, function (matches) {
3062
+ reducedPaused = matches;
3063
+ if (matches) renderOnce();else wakeScheduler();
3064
+ });
3065
+ reducedPaused = motionPreference.matches();
3066
+ var isMotionPaused = function isMotionPaused() {
3067
+ return manuallyPaused || reducedPaused || staticMode || contextLost;
3068
+ };
2601
3069
  var flowTime = 0;
3070
+ var frameGate = createFrameGate(merged.fps);
3071
+ renderOnce = function renderOnce() {
3072
+ controller.draw();
3073
+ if (overlay) overlay.update(flowTime);
3074
+ };
3075
+ renderOnce();
3076
+ var offPausedChange = emitter.on('change', function () {
3077
+ if (isMotionPaused()) renderOnce();
3078
+ });
2602
3079
  var unsubscribe = subscribeScheduler(function (delta, now) {
2603
3080
  flowTime += delta;
2604
3081
  controller.update(delta, false);
2605
- if (visibility.isVisible()) {
3082
+ if (frameGate.shouldDraw(delta)) {
2606
3083
  controller.draw();
2607
3084
  if (overlay) overlay.update(flowTime);
2608
3085
  }
2609
3086
  }, function () {
2610
- return paused;
3087
+ return isMotionPaused() || !visibility.isVisible();
2611
3088
  });
2612
3089
  nextTick(function () {
2613
3090
  if (disposed) return;
@@ -2634,32 +3111,41 @@ function createProgressCapsule(container) {
2634
3111
  },
2635
3112
  setRange: function setRange(min, max) {
2636
3113
  controller.setRange(min, max);
3114
+ if (isMotionPaused()) renderOnce();
2637
3115
  return this;
2638
3116
  },
2639
3117
  setPreset: function setPreset(ref) {
2640
3118
  var next = getPreset('progress', ref);
2641
3119
  dirty.preset = true;
2642
3120
  Object.assign(preset, next);
3121
+ if (colorOverride) preset.colors = _toConsumableArray(colorOverride);
3122
+ if (edgeStyleOverride) preset.edgeStyle = edgeStyleOverride;
2643
3123
  var nextColors = preset.colors.map(normalizeColor);
2644
3124
  if (nextColors.every(Boolean)) preset.colors = nextColors;
2645
3125
  controller.preset = preset;
2646
- controller.profile = next.edgeStyle === 'tide' ? FLOW_PROFILES.tide : FLOW_PROFILES[next.id] || FLOW_PROFILES['visual-training'];
3126
+ controller.profile = preset.edgeStyle === 'tide' ? FLOW_PROFILES.tide : FLOW_PROFILES[preset.id] || FLOW_PROFILES['visual-training'];
2647
3127
  controller.flowTime = stringSeed(next.id) * 31;
2648
3128
  controller.seed = stringSeed("".concat(next.id, "-reference")) * Math.PI * 2;
2649
3129
  syncDom();
2650
3130
  if (overlay) {
2651
3131
  overlay.dispose();
3132
+ contextLost = false;
2652
3133
  overlay = attachProgressFlowOverlay({
2653
3134
  root: root,
2654
3135
  canvas: canvas,
2655
3136
  preset: preset,
2656
3137
  getProgress: function getProgress() {
2657
- return controller.value;
2658
- }
3138
+ return progressRatio(controller.value, controller.min, controller.max) * 100;
3139
+ },
3140
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
3141
+ powerPreference: merged.powerPreference,
3142
+ onContextLost: onContextLost,
3143
+ onContextRestored: onContextRestored
2659
3144
  });
2660
3145
  }
2661
3146
  controller.webglActive = Boolean(overlay);
2662
3147
  controller.resizeCanvas();
3148
+ if (isMotionPaused()) renderOnce();
2663
3149
  emitter.emit('presetchange', {
2664
3150
  preset: _objectSpread2({}, preset)
2665
3151
  });
@@ -2669,21 +3155,28 @@ function createProgressCapsule(container) {
2669
3155
  var value = String(edgeStyle || '').toLowerCase();
2670
3156
  if (value !== 'flow' && value !== 'tide') return this;
2671
3157
  dirty.edgeStyle = true;
3158
+ edgeStyleOverride = value;
2672
3159
  preset.edgeStyle = value;
2673
3160
  controller.profile = value === 'tide' ? FLOW_PROFILES.tide : FLOW_PROFILES[preset.id] || FLOW_PROFILES['visual-training'];
2674
3161
  if (overlay) {
2675
3162
  overlay.dispose();
3163
+ contextLost = false;
2676
3164
  overlay = attachProgressFlowOverlay({
2677
3165
  root: root,
2678
3166
  canvas: canvas,
2679
3167
  preset: preset,
2680
3168
  getProgress: function getProgress() {
2681
- return controller.value;
2682
- }
3169
+ return progressRatio(controller.value, controller.min, controller.max) * 100;
3170
+ },
3171
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
3172
+ powerPreference: merged.powerPreference,
3173
+ onContextLost: onContextLost,
3174
+ onContextRestored: onContextRestored
2683
3175
  });
2684
3176
  }
2685
3177
  controller.webglActive = Boolean(overlay);
2686
3178
  controller.resizeCanvas();
3179
+ if (isMotionPaused()) renderOnce();
2687
3180
  return this;
2688
3181
  },
2689
3182
  setText: function setText(content) {
@@ -2721,6 +3214,22 @@ function createProgressCapsule(container) {
2721
3214
  controller.setValueSuffix(suffix);
2722
3215
  return this;
2723
3216
  },
3217
+ setStep: function setStep(step) {
3218
+ controller.setStep(step);
3219
+ return this;
3220
+ },
3221
+ setPrecision: function setPrecision(precision) {
3222
+ controller.setPrecision(precision);
3223
+ return this;
3224
+ },
3225
+ setFormatValue: function setFormatValue(formatter) {
3226
+ controller.setFormatValue(formatter);
3227
+ return this;
3228
+ },
3229
+ setDirection: function setDirection(direction) {
3230
+ controller.setDirection(direction);
3231
+ return this;
3232
+ },
2724
3233
  setShowValue: function setShowValue(show) {
2725
3234
  controller.setShowValue(show);
2726
3235
  return this;
@@ -2731,9 +3240,12 @@ function createProgressCapsule(container) {
2731
3240
  return !color;
2732
3241
  })) return this;
2733
3242
  dirty.colors = true;
3243
+ colorOverride = _toConsumableArray(next);
3244
+ preset.colors = _toConsumableArray(next);
2734
3245
  controller.setColors(next);
2735
3246
  if (overlay) overlay.setColors(next);
2736
3247
  controller.resizeCanvas();
3248
+ if (isMotionPaused()) renderOnce();
2737
3249
  return this;
2738
3250
  },
2739
3251
  setSize: function setSize(width, height) {
@@ -2747,30 +3259,68 @@ function createProgressCapsule(container) {
2747
3259
  }
2748
3260
  applySize();
2749
3261
  controller.resizeCanvas();
3262
+ if (isMotionPaused()) renderOnce();
2750
3263
  return this;
2751
3264
  },
2752
3265
  randomize: function randomize() {
2753
- controller.randomize();
3266
+ flowTime = controller.randomize();
3267
+ if (isMotionPaused()) renderOnce();
2754
3268
  return this;
2755
3269
  },
2756
3270
  pause: function pause() {
2757
- paused = true;
3271
+ dirty.paused = true;
3272
+ manuallyPaused = true;
3273
+ renderOnce();
2758
3274
  return this;
2759
3275
  },
2760
3276
  resume: function resume() {
2761
- paused = false;
3277
+ dirty.paused = true;
3278
+ manuallyPaused = false;
3279
+ wakeScheduler();
3280
+ return this;
3281
+ },
3282
+ setPaused: function setPaused(value) {
3283
+ return value ? this.pause() : this.resume();
3284
+ },
3285
+ setStatic: function setStatic(value) {
3286
+ dirty.static = true;
3287
+ staticMode = value === true;
3288
+ merged.static = staticMode;
3289
+ if (staticMode) renderOnce();else wakeScheduler();
3290
+ return this;
3291
+ },
3292
+ setFps: function setFps(fps) {
3293
+ dirty.fps = true;
3294
+ merged.fps = frameGate.setFps(fps);
3295
+ wakeScheduler();
2762
3296
  return this;
2763
3297
  },
2764
3298
  setQuality: function setQuality(quality) {
3299
+ dirty.quality = true;
2765
3300
  merged.quality = quality;
2766
- controller.dprCap = dprCapFor(quality);
3301
+ controller.dprCap = effectiveDprCap(quality, merged.renderScale);
3302
+ controller.resizeCanvas();
3303
+ if (overlay) overlay.setDprCap(controller.dprCap);
3304
+ if (isMotionPaused()) renderOnce();
3305
+ return this;
3306
+ },
3307
+ setRenderScale: function setRenderScale(renderScale) {
3308
+ var value = Number(renderScale);
3309
+ if (!Number.isFinite(value)) return this;
3310
+ dirty.renderScale = true;
3311
+ merged.renderScale = Math.min(1, Math.max(0.25, value));
3312
+ controller.dprCap = effectiveDprCap(merged.quality, merged.renderScale);
2767
3313
  controller.resizeCanvas();
3314
+ if (overlay) overlay.setDprCap(controller.dprCap);
3315
+ if (isMotionPaused()) renderOnce();
2768
3316
  return this;
2769
3317
  },
2770
3318
  dispose: function dispose() {
2771
3319
  disposed = true;
2772
3320
  unsubscribe();
3321
+ offPausedChange();
2773
3322
  visibility.dispose();
3323
+ motionPreference.dispose();
2774
3324
  if (overlay) overlay.dispose();
2775
3325
  controller.dispose();
2776
3326
  root.remove();
@@ -2781,26 +3331,89 @@ function createProgressCapsule(container) {
2781
3331
  get cssVars() {
2782
3332
  return merged.cssVars;
2783
3333
  },
3334
+ get min() {
3335
+ return controller.min;
3336
+ },
3337
+ get max() {
3338
+ return controller.max;
3339
+ },
3340
+ get step() {
3341
+ return controller.step;
3342
+ },
3343
+ get precision() {
3344
+ return controller.precision;
3345
+ },
3346
+ get formatValue() {
3347
+ return controller.formatValue;
3348
+ },
3349
+ get direction() {
3350
+ return controller.direction;
3351
+ },
3352
+ get showValue() {
3353
+ return !valueElement.hidden;
3354
+ },
3355
+ get valueSuffix() {
3356
+ return controller.valueSuffix;
3357
+ },
3358
+ get quality() {
3359
+ return merged.quality;
3360
+ },
3361
+ get renderScale() {
3362
+ return merged.renderScale;
3363
+ },
3364
+ get paused() {
3365
+ return manuallyPaused;
3366
+ },
3367
+ get static() {
3368
+ return staticMode;
3369
+ },
3370
+ get fps() {
3371
+ return frameGate.getFps();
3372
+ },
2784
3373
  dirty: dirty
2785
3374
  };
2786
3375
  }
2787
3376
 
2788
- function prefersReducedMotion() {
2789
- return typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches;
3377
+ var HOST_STYLES = new WeakMap();
3378
+ function acquireHostStyles(host) {
3379
+ var active = HOST_STYLES.get(host);
3380
+ if (active) {
3381
+ active.count += 1;
3382
+ return;
3383
+ }
3384
+ var state = {
3385
+ count: 1,
3386
+ position: host.style.position,
3387
+ isolation: host.style.isolation
3388
+ };
3389
+ var computed = getComputedStyle(host);
3390
+ if (computed.position === 'static' || computed.position === '') host.style.position = 'relative';
3391
+ host.style.isolation = 'isolate';
3392
+ HOST_STYLES.set(host, state);
3393
+ }
3394
+ function releaseHostStyles(host) {
3395
+ var state = HOST_STYLES.get(host);
3396
+ if (!state) return;
3397
+ state.count -= 1;
3398
+ if (state.count > 0) return;
3399
+ host.style.position = state.position;
3400
+ host.style.isolation = state.isolation;
3401
+ HOST_STYLES.delete(host);
2790
3402
  }
2791
3403
 
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
- *
3404
+ /**
3405
+ * Mount the cosmic (nebula) material as a background layer inside `host`.
3406
+ *
3407
+ * The layer is absolutely positioned and defaults to `z-index: -1`, so it
3408
+ * paints behind the host's in-flow content: text/buttons/images inside the
3409
+ * host sit on top with zero extra CSS. Host gets `position: relative;
3410
+ * isolation: isolate` automatically so the layer can never escape its box
3411
+ * (or break stacking outside it).
3412
+ *
2801
3413
  * 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).
3414
+ * renderer, renderScale, powerPreference, fps, paused/static, mouseColor,
3415
+ * respectReducedMotion, opacity (0-1), fallbackColor (true=preset base
3416
+ * color, a hex string, or false to disable).
2804
3417
  */
2805
3418
  function createColorBackground(host) {
2806
3419
  var _options$preset;
@@ -2814,18 +3427,29 @@ function createColorBackground(host) {
2814
3427
  renderer: DEFAULTS.capsule.renderer,
2815
3428
  respectReducedMotion: DEFAULTS.capsule.respectReducedMotion,
2816
3429
  mouseColor: true,
3430
+ renderScale: 1,
3431
+ powerPreference: 'high-performance',
3432
+ fps: DEFAULTS.capsule.fps,
3433
+ paused: false,
3434
+ static: false,
2817
3435
  opacity: 1,
2818
3436
  fallbackColor: true
2819
3437
  }, 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';
3438
+ merged.opacity = Number.isFinite(Number(merged.opacity)) ? Math.min(1, Math.max(0, Number(merged.opacity))) : 1;
3439
+ var normalizedColors = Array.isArray(merged.colors) ? merged.colors.map(normalizeColor) : [];
3440
+ if (normalizedColors.length === 4 && normalizedColors.every(Boolean)) preset.colors = normalizedColors;
3441
+ var initialSeed = toFiniteNumber(merged.seed);
3442
+ var initialSpeed = toFiniteNumber(merged.speed);
3443
+ if (initialSeed !== null) preset.seed = initialSeed;
3444
+ if (initialSpeed !== null) preset.speed = initialSpeed;
3445
+ merged.colors = _toConsumableArray(preset.colors);
3446
+ merged.seed = preset.seed;
3447
+ merged.speed = preset.speed;
3448
+ var optionColors = Array.isArray(options.colors) ? options.colors.map(normalizeColor) : [];
3449
+ var colorOverride = optionColors.length === 4 && optionColors.every(Boolean) ? _toConsumableArray(optionColors) : null;
3450
+ var seedOverride = options.seed !== undefined ? toFiniteNumber(options.seed) : null;
3451
+ var speedOverride = options.speed !== undefined ? toFiniteNumber(options.speed) : null;
3452
+ acquireHostStyles(host);
2829
3453
  var layer = document.createElement('div');
2830
3454
  layer.className = 'dlc-color-layer';
2831
3455
  var syncLayerVars = function syncLayerVars() {
@@ -2840,8 +3464,12 @@ function createColorBackground(host) {
2840
3464
  layer.appendChild(canvas);
2841
3465
  host.appendChild(layer);
2842
3466
  var emitter = createEmitter();
2843
- var paused = merged.respectReducedMotion && prefersReducedMotion();
3467
+ var manuallyPaused = merged.paused === true;
3468
+ var reducedPaused = false;
3469
+ var staticMode = merged.static === true;
3470
+ var contextLost = false;
2844
3471
  var disposed = false;
3472
+ var renderOnce = function renderOnce() {};
2845
3473
  var dirty = {
2846
3474
  preset: false,
2847
3475
  seed: false,
@@ -2849,19 +3477,37 @@ function createColorBackground(host) {
2849
3477
  colors: false,
2850
3478
  opacity: false,
2851
3479
  fallbackColor: false,
2852
- mouseColor: false
3480
+ mouseColor: false,
3481
+ quality: false,
3482
+ renderScale: false,
3483
+ paused: false,
3484
+ static: false,
3485
+ fps: false
2853
3486
  };
2854
3487
  var renderer;
2855
3488
  var useWebgl = merged.renderer !== 'canvas2d';
2856
3489
  if (useWebgl) {
2857
3490
  try {
2858
3491
  renderer = new CosmicRenderer(canvas, merged, {
2859
- dprCap: dprCapFor(merged.quality),
3492
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
2860
3493
  mouseColor: merged.mouseColor !== false,
2861
- eventTarget: host
3494
+ eventTarget: host,
3495
+ powerPreference: merged.powerPreference,
3496
+ onContextLost: function onContextLost() {
3497
+ contextLost = true;
3498
+ emitter.emit('contextlost', {});
3499
+ },
3500
+ onContextRestored: function onContextRestored() {
3501
+ contextLost = false;
3502
+ emitter.emit('contextrestored', {});
3503
+ renderOnce();
3504
+ wakeScheduler();
3505
+ }
2862
3506
  });
2863
3507
  } catch (error) {
2864
- renderer = new FallbackRenderer(canvas, merged);
3508
+ renderer = new FallbackRenderer(canvas, merged, {
3509
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale)
3510
+ });
2865
3511
  nextTick(function () {
2866
3512
  if (disposed) return;
2867
3513
  emitter.emit('error', {
@@ -2870,19 +3516,37 @@ function createColorBackground(host) {
2870
3516
  });
2871
3517
  }
2872
3518
  } else {
2873
- renderer = new FallbackRenderer(canvas, merged);
3519
+ renderer = new FallbackRenderer(canvas, merged, {
3520
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale)
3521
+ });
2874
3522
  }
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);
3523
+ renderer.resize();
2880
3524
  var animationTime = 0;
3525
+ var frameGate = createFrameGate(merged.fps);
3526
+ renderOnce = function renderOnce() {
3527
+ return renderer.draw(animationTime);
3528
+ };
3529
+ var resize = function resize() {
3530
+ renderer.resize();
3531
+ if (manuallyPaused || reducedPaused || staticMode) renderOnce();
3532
+ };
3533
+ var resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(resize) : null;
3534
+ if (resizeObserver) resizeObserver.observe(host);else window.addEventListener('resize', resize);
3535
+ var visibility = createVisibilityGuard(layer, wakeScheduler);
3536
+ var motionPreference = createReducedMotionPreference(merged.respectReducedMotion, function (matches) {
3537
+ reducedPaused = matches;
3538
+ if (matches) renderOnce();else wakeScheduler();
3539
+ });
3540
+ reducedPaused = motionPreference.matches();
3541
+ var isMotionPaused = function isMotionPaused() {
3542
+ return manuallyPaused || reducedPaused || staticMode || contextLost;
3543
+ };
3544
+ renderOnce();
2881
3545
  var unsubscribe = subscribeScheduler(function (delta) {
2882
3546
  animationTime += delta;
2883
- if (visibility.isVisible()) renderer.draw(animationTime);
3547
+ if (frameGate.shouldDraw(delta)) renderer.draw(animationTime);
2884
3548
  }, function () {
2885
- return paused;
3549
+ return isMotionPaused() || !visibility.isVisible();
2886
3550
  });
2887
3551
  nextTick(function () {
2888
3552
  if (disposed) return;
@@ -2901,11 +3565,15 @@ function createColorBackground(host) {
2901
3565
  var next = getPreset('capsule', ref);
2902
3566
  dirty.preset = true;
2903
3567
  Object.assign(preset, next);
3568
+ if (colorOverride) preset.colors = _toConsumableArray(colorOverride);
3569
+ if (seedOverride !== null) preset.seed = seedOverride;
3570
+ if (speedOverride !== null) preset.speed = speedOverride;
2904
3571
  var nextColors = preset.colors.map(normalizeColor);
2905
3572
  if (nextColors.every(Boolean)) preset.colors = nextColors;
2906
3573
  renderer.setPreset(_objectSpread2({}, preset));
2907
3574
  renderer.resize();
2908
3575
  syncLayerVars();
3576
+ if (isMotionPaused()) renderOnce();
2909
3577
  emitter.emit('presetchange', {
2910
3578
  preset: _objectSpread2({}, preset)
2911
3579
  });
@@ -2917,27 +3585,33 @@ function createColorBackground(host) {
2917
3585
  return !color;
2918
3586
  })) return this;
2919
3587
  dirty.colors = true;
3588
+ colorOverride = _toConsumableArray(next);
2920
3589
  preset.colors = next;
2921
3590
  renderer.setPreset(_objectSpread2(_objectSpread2({}, preset), {}, {
2922
3591
  colors: next
2923
3592
  }));
2924
3593
  syncLayerVars();
3594
+ if (isMotionPaused()) renderOnce();
2925
3595
  return this;
2926
3596
  },
2927
3597
  setSeed: function setSeed(seed) {
2928
- var value = Number(seed);
2929
- if (!Number.isFinite(value)) return this;
3598
+ var value = toFiniteNumber(seed);
3599
+ if (value === null) return this;
2930
3600
  dirty.seed = true;
3601
+ seedOverride = value;
2931
3602
  preset.seed = value;
2932
3603
  renderer.setPreset(_objectSpread2({}, preset));
3604
+ if (isMotionPaused()) renderOnce();
2933
3605
  return this;
2934
3606
  },
2935
3607
  setSpeed: function setSpeed(speed) {
2936
- var value = Number(speed);
2937
- if (!Number.isFinite(value)) return this;
3608
+ var value = toFiniteNumber(speed);
3609
+ if (value === null) return this;
2938
3610
  dirty.speed = true;
3611
+ speedOverride = value;
2939
3612
  preset.speed = value;
2940
3613
  renderer.setPreset(_objectSpread2({}, preset));
3614
+ if (isMotionPaused()) renderOnce();
2941
3615
  return this;
2942
3616
  },
2943
3617
  setFallbackColor: function setFallbackColor(value) {
@@ -2953,8 +3627,23 @@ function createColorBackground(host) {
2953
3627
  return this;
2954
3628
  },
2955
3629
  setQuality: function setQuality(quality) {
3630
+ dirty.quality = true;
2956
3631
  merged.quality = quality;
2957
- if (typeof renderer.setDprCap === 'function') renderer.setDprCap(dprCapFor(quality));
3632
+ if (typeof renderer.setDprCap === 'function') {
3633
+ renderer.setDprCap(effectiveDprCap(quality, merged.renderScale));
3634
+ }
3635
+ if (isMotionPaused()) renderOnce();
3636
+ return this;
3637
+ },
3638
+ setRenderScale: function setRenderScale(renderScale) {
3639
+ var value = Number(renderScale);
3640
+ if (!Number.isFinite(value)) return this;
3641
+ dirty.renderScale = true;
3642
+ merged.renderScale = Math.min(1, Math.max(0.25, value));
3643
+ if (typeof renderer.setDprCap === 'function') {
3644
+ renderer.setDprCap(effectiveDprCap(merged.quality, merged.renderScale));
3645
+ }
3646
+ if (isMotionPaused()) renderOnce();
2958
3647
  return this;
2959
3648
  },
2960
3649
  setOpacity: function setOpacity(value) {
@@ -2970,20 +3659,42 @@ function createColorBackground(host) {
2970
3659
  return this;
2971
3660
  },
2972
3661
  pause: function pause() {
2973
- paused = true;
3662
+ dirty.paused = true;
3663
+ manuallyPaused = true;
3664
+ renderOnce();
2974
3665
  return this;
2975
3666
  },
2976
3667
  resume: function resume() {
2977
- paused = false;
3668
+ dirty.paused = true;
3669
+ manuallyPaused = false;
3670
+ wakeScheduler();
3671
+ return this;
3672
+ },
3673
+ setPaused: function setPaused(value) {
3674
+ return value ? this.pause() : this.resume();
3675
+ },
3676
+ setStatic: function setStatic(value) {
3677
+ dirty.static = true;
3678
+ staticMode = value === true;
3679
+ merged.static = staticMode;
3680
+ if (staticMode) renderOnce();else wakeScheduler();
3681
+ return this;
3682
+ },
3683
+ setFps: function setFps(fps) {
3684
+ dirty.fps = true;
3685
+ merged.fps = frameGate.setFps(fps);
3686
+ wakeScheduler();
2978
3687
  return this;
2979
3688
  },
2980
3689
  dispose: function dispose() {
2981
3690
  disposed = true;
2982
3691
  unsubscribe();
2983
3692
  visibility.dispose();
2984
- if (resizeObserver) resizeObserver.disconnect();else window.removeEventListener('resize', renderer.resize);
3693
+ motionPreference.dispose();
3694
+ if (resizeObserver) resizeObserver.disconnect();else window.removeEventListener('resize', resize);
2985
3695
  renderer.dispose();
2986
3696
  layer.remove();
3697
+ releaseHostStyles(host);
2987
3698
  },
2988
3699
  get opacity() {
2989
3700
  return merged.opacity;
@@ -2994,6 +3705,21 @@ function createColorBackground(host) {
2994
3705
  get mouseColor() {
2995
3706
  return merged.mouseColor;
2996
3707
  },
3708
+ get quality() {
3709
+ return merged.quality;
3710
+ },
3711
+ get renderScale() {
3712
+ return merged.renderScale;
3713
+ },
3714
+ get paused() {
3715
+ return manuallyPaused;
3716
+ },
3717
+ get static() {
3718
+ return staticMode;
3719
+ },
3720
+ get fps() {
3721
+ return frameGate.getFps();
3722
+ },
2997
3723
  dirty: dirty
2998
3724
  };
2999
3725
  }
@@ -3012,6 +3738,7 @@ exports.createCapsule = createCapsule;
3012
3738
  exports.createColorBackground = createColorBackground;
3013
3739
  exports.createProgressCapsule = createProgressCapsule;
3014
3740
  exports.dprCapFor = dprCapFor;
3741
+ exports.effectiveDprCap = effectiveDprCap;
3015
3742
  exports.getPreset = getPreset;
3016
3743
  exports.hexToRgb01 = hexToRgb01$1;
3017
3744
  exports.hexToRgba = hexToRgba;