@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/vue2.cjs CHANGED
@@ -187,9 +187,9 @@ function _unsupportedIterableToArray(r, a) {
187
187
  }
188
188
  }
189
189
 
190
- /**
191
- * Tiny event emitter. Accepts any event name so the API stays open for
192
- * future events (hover, click, custom) without breaking changes.
190
+ /**
191
+ * Tiny event emitter. Accepts any event name so the API stays open for
192
+ * future events (hover, click, custom) without breaking changes.
193
193
  */
194
194
  function createEmitter() {
195
195
  // Plain object storage (no Map/Set) so the legacy build has no API
@@ -239,9 +239,9 @@ function createEmitter() {
239
239
 
240
240
  var UNIT_PATTERN = /^[\d.]+(?:px|%|vw|vh|vmin|vmax|em|rem)$/;
241
241
 
242
- /**
243
- * Normalize a size option to a CSS length string.
244
- * Accepts numbers (treated as px) or strings with px/%, vw/vh/vmin/vmax, em/rem.
242
+ /**
243
+ * Normalize a size option to a CSS length string.
244
+ * Accepts numbers (treated as px) or strings with px/%, vw/vh/vmin/vmax, em/rem.
245
245
  */
246
246
  function parseSize(value) {
247
247
  if (typeof value === 'number') {
@@ -280,11 +280,17 @@ function dprCapFor(quality) {
280
280
  var tier = QUALITY_TIERS[quality] || QUALITY_TIERS.medium;
281
281
  return tier.dpr;
282
282
  }
283
+ function effectiveDprCap(quality) {
284
+ var renderScale = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
285
+ var scale = Number(renderScale);
286
+ var normalizedScale = Number.isFinite(scale) ? Math.min(1, Math.max(0.25, scale)) : 1;
287
+ return Math.max(0.5, dprCapFor(quality) * normalizedScale);
288
+ }
283
289
 
284
- /**
285
- * Merge defaults < preset < user. Unknown user keys are preserved so the
286
- * component API can grow (new props, cssVars, callbacks) without a breaking
287
- * change.
290
+ /**
291
+ * Merge defaults < preset < user. Unknown user keys are preserved so the
292
+ * component API can grow (new props, cssVars, callbacks) without a breaking
293
+ * change.
288
294
  */
289
295
  function normalizeOptions(defaults, preset, user) {
290
296
  // undefined means "not provided" (e.g. Vue $props with unset props):
@@ -302,9 +308,9 @@ function normalizeOptions(defaults, preset, user) {
302
308
  return _objectSpread2(_objectSpread2(_objectSpread2({}, defaults), presetOptions), userOptions);
303
309
  }
304
310
 
305
- /**
306
- * 公共色板:NC-01~NC-06 的四色组(底色 / 主色 / 辅色 / 高光色)。
307
- * Capsule 预置与 dlc-color 等组件共用,新增色板只改这里。
311
+ /**
312
+ * 公共色板:NC-01~NC-06 的四色组(底色 / 主色 / 辅色 / 高光色)。
313
+ * Capsule 预置与 dlc-color 等组件共用,新增色板只改这里。
308
314
  */
309
315
  var PALETTES$1 = {
310
316
  original: ['#FFF3EA', '#F5B27A', '#F67BC6', '#A978E8'],
@@ -315,9 +321,9 @@ var PALETTES$1 = {
315
321
  plus: ['#FFF0E6', '#F6C26B', '#F98A64', '#E86D74']
316
322
  };
317
323
 
318
- /**
319
- * Capsule 预置:仅星云材质 NC-01~NC-06(NC-07~09 aurora 已移除)。
320
- * 颜色引用公共色板,seed/speed 决定形态与流速。
324
+ /**
325
+ * Capsule 预置:仅星云材质 NC-01~NC-06(NC-07~09 aurora 已移除)。
326
+ * 颜色引用公共色板,seed/speed 决定形态与流速。
321
327
  */
322
328
  var CAPSULE_PRESETS = [{
323
329
  id: 'original',
@@ -435,6 +441,11 @@ var DEFAULTS = {
435
441
  height: 160,
436
442
  quality: 'auto',
437
443
  renderer: 'auto',
444
+ renderScale: 1,
445
+ powerPreference: 'high-performance',
446
+ fps: 60,
447
+ paused: false,
448
+ static: false,
438
449
  respectReducedMotion: true,
439
450
  mouseColor: true,
440
451
  textRatio: 39
@@ -444,13 +455,22 @@ var DEFAULTS = {
444
455
  height: 104,
445
456
  min: 0,
446
457
  max: 100,
458
+ step: 'any',
447
459
  draggable: true,
460
+ keyboard: true,
448
461
  disabled: false,
449
462
  readonly: false,
463
+ direction: 'ltr',
464
+ precision: 0,
450
465
  valueSuffix: '%',
451
466
  edgeStyle: 'flow',
452
467
  quality: 'auto',
453
468
  renderer: 'auto',
469
+ renderScale: 1,
470
+ powerPreference: 'high-performance',
471
+ fps: 60,
472
+ paused: false,
473
+ static: false,
454
474
  textRatio: 54,
455
475
  showValue: true,
456
476
  respectReducedMotion: true
@@ -735,7 +755,7 @@ var CosmicRenderer = /*#__PURE__*/function () {
735
755
  alpha: false,
736
756
  antialias: false,
737
757
  depth: false,
738
- powerPreference: 'high-performance',
758
+ powerPreference: this.options.powerPreference || 'high-performance',
739
759
  preserveDrawingBuffer: false
740
760
  });
741
761
  if (!this.gl) throw new Error('WebGL2 is not available');
@@ -746,16 +766,19 @@ var CosmicRenderer = /*#__PURE__*/function () {
746
766
  this.motion = 0;
747
767
  this.motionTarget = 0;
748
768
  this.timeOffset = preset.seed * 0.73;
769
+ this.colors = preset.colors.map(hexToRgb01$1);
749
770
  this.visible = true;
750
771
  this.disposed = false;
751
772
  _assertClassBrand(_CosmicRenderer_brand, this, _setupGeometry).call(this);
752
773
  _assertClassBrand(_CosmicRenderer_brand, this, _bindEvents).call(this);
774
+ _assertClassBrand(_CosmicRenderer_brand, this, _bindContextEvents).call(this);
753
775
  this.resize();
754
776
  }
755
777
  return _createClass(CosmicRenderer, [{
756
778
  key: "setPreset",
757
779
  value: function setPreset(preset) {
758
780
  this.preset = _objectSpread2({}, preset);
781
+ this.colors = preset.colors.map(hexToRgb01$1);
759
782
  this.timeOffset = preset.seed * 0.73;
760
783
  }
761
784
  }, {
@@ -800,15 +823,14 @@ var CosmicRenderer = /*#__PURE__*/function () {
800
823
  if (this.canvas.width !== width || this.canvas.height !== height) {
801
824
  this.canvas.width = width;
802
825
  this.canvas.height = height;
803
- this.gl.viewport(0, 0, width, height);
804
826
  }
827
+ this.gl.viewport(0, 0, width, height);
805
828
  }
806
829
  }, {
807
830
  key: "draw",
808
831
  value: function draw(elapsedSeconds) {
809
832
  var paused = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
810
833
  if (this.disposed || !this.visible) return;
811
- this.resize();
812
834
  var gl = this.gl;
813
835
  this.pointer[0] += (this.pointerTarget[0] - this.pointer[0]) * 0.08;
814
836
  this.pointer[1] += (this.pointerTarget[1] - this.pointer[1]) * 0.08;
@@ -817,16 +839,15 @@ var CosmicRenderer = /*#__PURE__*/function () {
817
839
  gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
818
840
  gl.enableVertexAttribArray(this.locations.position);
819
841
  gl.vertexAttribPointer(this.locations.position, 2, gl.FLOAT, false, 0, 0);
820
- var colors = this.preset.colors.map(hexToRgb01$1);
821
842
  gl.uniform2f(this.locations.resolution, this.canvas.width, this.canvas.height);
822
843
  gl.uniform1f(this.locations.time, this.timeOffset + (paused ? 0 : elapsedSeconds * this.preset.speed));
823
844
  gl.uniform1f(this.locations.seed, this.preset.seed);
824
845
  gl.uniform1f(this.locations.motion, this.motion);
825
846
  gl.uniform2f(this.locations.pointer, this.pointer[0], this.pointer[1]);
826
- gl.uniform3fv(this.locations.colorA, colors[0]);
827
- gl.uniform3fv(this.locations.colorB, colors[1]);
828
- gl.uniform3fv(this.locations.colorC, colors[2]);
829
- gl.uniform3fv(this.locations.colorD, colors[3]);
847
+ gl.uniform3fv(this.locations.colorA, this.colors[0]);
848
+ gl.uniform3fv(this.locations.colorB, this.colors[1]);
849
+ gl.uniform3fv(this.locations.colorC, this.colors[2]);
850
+ gl.uniform3fv(this.locations.colorD, this.colors[3]);
830
851
  gl.drawArrays(gl.TRIANGLES, 0, 3);
831
852
  }
832
853
  }, {
@@ -837,6 +858,8 @@ var CosmicRenderer = /*#__PURE__*/function () {
837
858
  target.removeEventListener('pointermove', this.onPointerMove);
838
859
  target.removeEventListener('pointerdown', this.onPointerMove);
839
860
  target.removeEventListener('pointerleave', this.onPointerLeave);
861
+ this.canvas.removeEventListener('webglcontextlost', this.onContextLost, false);
862
+ this.canvas.removeEventListener('webglcontextrestored', this.onContextRestored, false);
840
863
  this.gl.deleteBuffer(this.buffer);
841
864
  this.gl.deleteProgram(this.program);
842
865
  var lose = this.gl.getExtension('WEBGL_lose_context');
@@ -893,6 +916,26 @@ function _bindEvents() {
893
916
  passive: true
894
917
  });
895
918
  }
919
+ function _bindContextEvents() {
920
+ var _this3 = this;
921
+ this.onContextLost = function (event) {
922
+ event.preventDefault();
923
+ if (_this3.disposed) return;
924
+ _this3.visible = false;
925
+ if (typeof _this3.options.onContextLost === 'function') _this3.options.onContextLost(event);
926
+ };
927
+ this.onContextRestored = function () {
928
+ if (_this3.disposed) return;
929
+ _this3.program = createProgram$1(_this3.gl);
930
+ _this3.locations = _assertClassBrand(_CosmicRenderer_brand, _this3, _getLocations).call(_this3);
931
+ _assertClassBrand(_CosmicRenderer_brand, _this3, _setupGeometry).call(_this3);
932
+ _this3.visible = true;
933
+ _this3.resize();
934
+ if (typeof _this3.options.onContextRestored === 'function') _this3.options.onContextRestored();
935
+ };
936
+ this.canvas.addEventListener('webglcontextlost', this.onContextLost, false);
937
+ this.canvas.addEventListener('webglcontextrestored', this.onContextRestored, false);
938
+ }
896
939
 
897
940
  function rgb(color) {
898
941
  var alpha = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
@@ -907,18 +950,20 @@ function rgb(color) {
907
950
  }
908
951
  var FallbackRenderer = /*#__PURE__*/function () {
909
952
  function FallbackRenderer(canvas, preset) {
953
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
910
954
  _classCallCheck(this, FallbackRenderer);
911
955
  this.canvas = canvas;
912
956
  this.preset = preset;
913
957
  this.context = canvas.getContext('2d');
914
958
  this.visible = true;
915
959
  this.timePhase = 0;
960
+ this.dprCap = options.dprCap || 1.5;
916
961
  }
917
962
  return _createClass(FallbackRenderer, [{
918
963
  key: "resize",
919
964
  value: function resize() {
920
965
  var rect = this.canvas.getBoundingClientRect();
921
- var dpr = Math.min(window.devicePixelRatio || 1, 1.5);
966
+ var dpr = Math.min(window.devicePixelRatio || 1, this.dprCap);
922
967
  var width = Math.max(2, Math.round(rect.width * dpr));
923
968
  var height = Math.max(2, Math.round(rect.height * dpr));
924
969
  if (this.canvas.width !== width || this.canvas.height !== height) {
@@ -959,7 +1004,6 @@ var FallbackRenderer = /*#__PURE__*/function () {
959
1004
  key: "draw",
960
1005
  value: function draw(time) {
961
1006
  if (!this.visible) return;
962
- this.resize();
963
1007
  this.drawNebula(time);
964
1008
  }
965
1009
  }, {
@@ -967,6 +1011,12 @@ var FallbackRenderer = /*#__PURE__*/function () {
967
1011
  value: function setPreset(preset) {
968
1012
  this.preset = preset;
969
1013
  }
1014
+ }, {
1015
+ key: "setDprCap",
1016
+ value: function setDprCap(cap) {
1017
+ this.dprCap = cap;
1018
+ this.resize();
1019
+ }
970
1020
  }, {
971
1021
  key: "randomize",
972
1022
  value: function randomize() {
@@ -984,10 +1034,10 @@ var FallbackRenderer = /*#__PURE__*/function () {
984
1034
  }]);
985
1035
  }();
986
1036
 
987
- /**
988
- * Document-level shared rAF scheduler. Every component instance subscribes
989
- * its own frame callback; the whole page runs ONE animation loop (like the
990
- * original demo), which avoids jank from many competing rAF loops.
1037
+ /**
1038
+ * Document-level shared rAF scheduler. Every component instance subscribes
1039
+ * its own frame callback; the whole page runs ONE animation loop (like the
1040
+ * original demo), which avoids jank from many competing rAF loops.
991
1041
  */
992
1042
  var subscribers = [];
993
1043
  var running = false;
@@ -995,11 +1045,7 @@ var rafId = 0;
995
1045
  var last = 0;
996
1046
  function tick(now) {
997
1047
  if (!running) return;
998
- var delta = last ? Math.min((now - last) / 1000, 0.05) : 0;
999
- last = now;
1000
- // Schedule the next frame BEFORE running callbacks so one throwing
1001
- // subscriber can never kill the whole animation loop.
1002
- rafId = requestAnimationFrame(tick);
1048
+ var activeItems = [];
1003
1049
  {
1004
1050
  var _iterator = _createForOfIteratorHelper(subscribers.slice()),
1005
1051
  _step;
@@ -1007,10 +1053,8 @@ function tick(now) {
1007
1053
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
1008
1054
  var item = _step.value;
1009
1055
  try {
1010
- if (!item.isPaused()) item.onFrame(delta, now);
1011
- } catch (error) {
1012
- console.warn('[dlc-ui] frame error:', error);
1013
- }
1056
+ if (!item.isPaused()) activeItems.push(item);
1057
+ } catch (_unused) {}
1014
1058
  }
1015
1059
  } catch (err) {
1016
1060
  _iterator.e(err);
@@ -1018,6 +1062,25 @@ function tick(now) {
1018
1062
  _iterator.f();
1019
1063
  }
1020
1064
  }
1065
+ if (activeItems.length === 0) {
1066
+ running = false;
1067
+ rafId = 0;
1068
+ last = 0;
1069
+ return;
1070
+ }
1071
+ var delta = last ? Math.min((now - last) / 1000, 0.05) : 0;
1072
+ last = now;
1073
+ // Schedule the next frame BEFORE running callbacks so one throwing
1074
+ // subscriber can never kill the whole animation loop.
1075
+ rafId = requestAnimationFrame(tick);
1076
+ for (var _i = 0, _activeItems = activeItems; _i < _activeItems.length; _i++) {
1077
+ var _item = _activeItems[_i];
1078
+ try {
1079
+ _item.onFrame(delta, now);
1080
+ } catch (error) {
1081
+ console.warn('[dlc-ui] frame error:', error);
1082
+ }
1083
+ }
1021
1084
  if (subscribers.length === 0) {
1022
1085
  cancelAnimationFrame(rafId);
1023
1086
  running = false;
@@ -1030,6 +1093,9 @@ function start() {
1030
1093
  last = 0;
1031
1094
  rafId = requestAnimationFrame(tick);
1032
1095
  }
1096
+ function wakeScheduler() {
1097
+ start();
1098
+ }
1033
1099
  function subscribeScheduler(onFrame, isPaused) {
1034
1100
  var item = {
1035
1101
  onFrame: onFrame,
@@ -1048,11 +1114,12 @@ function subscribeScheduler(onFrame, isPaused) {
1048
1114
  };
1049
1115
  }
1050
1116
 
1051
- /**
1052
- * Gates drawing on "element intersects viewport AND the page tab is visible".
1053
- * Falls back to always-visible when IntersectionObserver is unavailable.
1117
+ /**
1118
+ * Gates drawing on "element intersects viewport AND the page tab is visible".
1119
+ * Falls back to always-visible when IntersectionObserver is unavailable.
1054
1120
  */
1055
1121
  function createVisibilityGuard(element) {
1122
+ var onChange = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
1056
1123
  var intersecting = true;
1057
1124
  var pageVisible = typeof document === 'undefined' || !document.hidden;
1058
1125
  var disposed = false;
@@ -1062,6 +1129,7 @@ function createVisibilityGuard(element) {
1062
1129
  intersecting = entries.some(function (entry) {
1063
1130
  return entry.isIntersecting;
1064
1131
  });
1132
+ if (typeof onChange === 'function') onChange();
1065
1133
  }, {
1066
1134
  rootMargin: '180px'
1067
1135
  });
@@ -1069,6 +1137,7 @@ function createVisibilityGuard(element) {
1069
1137
  }
1070
1138
  var onVisibilityChange = function onVisibilityChange() {
1071
1139
  pageVisible = typeof document !== 'undefined' && !document.hidden;
1140
+ if (typeof onChange === 'function') onChange();
1072
1141
  };
1073
1142
  if (typeof document !== 'undefined') {
1074
1143
  document.addEventListener('visibilitychange', onVisibilityChange);
@@ -1104,18 +1173,69 @@ function nextTick(fn) {
1104
1173
  }
1105
1174
  }
1106
1175
 
1107
- function prefersReducedMotion$2() {
1108
- return typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches;
1176
+ function normalizeFps(value) {
1177
+ var fallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 60;
1178
+ var fps = Number(value);
1179
+ if (!Number.isFinite(fps)) return fallback;
1180
+ return Math.min(60, Math.max(1, fps));
1181
+ }
1182
+ function createFrameGate() {
1183
+ var initialFps = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 60;
1184
+ var fps = normalizeFps(initialFps);
1185
+ var elapsed = 0;
1186
+ return {
1187
+ shouldDraw: function shouldDraw(delta) {
1188
+ elapsed += delta;
1189
+ var interval = 1 / fps;
1190
+ if (elapsed + 0.0001 < interval) return false;
1191
+ elapsed %= interval;
1192
+ return true;
1193
+ },
1194
+ setFps: function setFps(value) {
1195
+ fps = normalizeFps(value, fps);
1196
+ elapsed = 0;
1197
+ return fps;
1198
+ },
1199
+ getFps: function getFps() {
1200
+ return fps;
1201
+ }
1202
+ };
1203
+ }
1204
+ function createReducedMotionPreference(enabled, onChange) {
1205
+ var query = enabled && typeof matchMedia !== 'undefined' ? matchMedia('(prefers-reduced-motion: reduce)') : null;
1206
+ var notify = function notify() {
1207
+ if (typeof onChange === 'function') onChange(Boolean(query && query.matches));
1208
+ };
1209
+ if (query) {
1210
+ if (typeof query.addEventListener === 'function') query.addEventListener('change', notify);else if (typeof query.addListener === 'function') query.addListener(notify);
1211
+ }
1212
+ return {
1213
+ matches: function matches() {
1214
+ return Boolean(query && query.matches);
1215
+ },
1216
+ dispose: function dispose() {
1217
+ if (!query) return;
1218
+ if (typeof query.removeEventListener === 'function') query.removeEventListener('change', notify);else if (typeof query.removeListener === 'function') query.removeListener(notify);
1219
+ }
1220
+ };
1221
+ }
1222
+
1223
+ function toFiniteNumber(value) {
1224
+ if (value == null || typeof value === 'boolean') return null;
1225
+ if (typeof value === 'string' && value.trim() === '') return null;
1226
+ var number = Number(value);
1227
+ return Number.isFinite(number) ? number : null;
1109
1228
  }
1110
1229
 
1111
- /**
1230
+ /**
1112
1231
  * Mount a cosmic (nebula) capsule into `container`.
1113
1232
  *
1114
1233
  * Options: preset (literary name), width, height (number=px or string with
1115
1234
  * px/%/vw/vh/em/rem), colors, seed, speed, textRatio (0-100, text region
1116
1235
  * width in percent), text (HTML string or DOM nodes for the text slot),
1117
1236
  * colorContent (HTML string or DOM nodes for the color slot), quality,
1118
- * renderer, respectReducedMotion, mouseColor, cssVars.
1237
+ * renderer, quality, renderScale, powerPreference, fps, paused/static,
1238
+ * respectReducedMotion, mouseColor, cssVars.
1119
1239
  */
1120
1240
  function createCapsule(container) {
1121
1241
  var _options$preset;
@@ -1125,10 +1245,19 @@ function createCapsule(container) {
1125
1245
  }
1126
1246
  var preset = _objectSpread2({}, getPreset('capsule', (_options$preset = options.preset) !== null && _options$preset !== void 0 ? _options$preset : '初光'));
1127
1247
  var merged = normalizeOptions(DEFAULTS.capsule, preset, options);
1128
- var normalizedColors = merged.colors.map(normalizeColor);
1129
- if (normalizedColors.every(Boolean)) preset.colors = normalizedColors;
1130
- preset.seed = merged.seed;
1131
- preset.speed = merged.speed;
1248
+ var normalizedColors = Array.isArray(merged.colors) ? merged.colors.map(normalizeColor) : [];
1249
+ if (normalizedColors.length === 4 && normalizedColors.every(Boolean)) preset.colors = normalizedColors;
1250
+ var initialSeed = toFiniteNumber(merged.seed);
1251
+ var initialSpeed = toFiniteNumber(merged.speed);
1252
+ if (initialSeed !== null) preset.seed = initialSeed;
1253
+ if (initialSpeed !== null) preset.speed = initialSpeed;
1254
+ merged.colors = _toConsumableArray(preset.colors);
1255
+ merged.seed = preset.seed;
1256
+ merged.speed = preset.speed;
1257
+ var optionColors = Array.isArray(options.colors) ? options.colors.map(normalizeColor) : [];
1258
+ var colorOverride = optionColors.length === 4 && optionColors.every(Boolean) ? _toConsumableArray(optionColors) : null;
1259
+ var seedOverride = options.seed !== undefined ? toFiniteNumber(options.seed) : null;
1260
+ var speedOverride = options.speed !== undefined ? toFiniteNumber(options.speed) : null;
1132
1261
  var customLabel = typeof options.label === 'string' && options.label ? options.label : null;
1133
1262
  var root = document.createElement('div');
1134
1263
  root.className = 'hj-capsule-root hj-capsule-cosmic';
@@ -1180,25 +1309,49 @@ function createCapsule(container) {
1180
1309
  root.appendChild(canvas);
1181
1310
  container.appendChild(root);
1182
1311
  var emitter = createEmitter();
1183
- var paused = merged.respectReducedMotion && prefersReducedMotion$2();
1312
+ var manuallyPaused = merged.paused === true;
1313
+ var reducedPaused = false;
1314
+ var staticMode = merged.static === true;
1315
+ var contextLost = false;
1184
1316
  var disposed = false;
1317
+ var renderOnce = function renderOnce() {};
1185
1318
  var dirty = {
1186
1319
  preset: false,
1187
1320
  seed: false,
1188
1321
  speed: false,
1189
1322
  colors: false,
1190
1323
  textRatio: false,
1191
- cssVars: false
1324
+ cssVars: false,
1325
+ mouseColor: false,
1326
+ quality: false,
1327
+ renderScale: false,
1328
+ paused: false,
1329
+ static: false,
1330
+ fps: false
1192
1331
  };
1193
1332
  var renderer;
1194
1333
  var useWebgl = merged.renderer !== 'canvas2d';
1195
1334
  if (useWebgl) {
1196
1335
  try {
1197
1336
  renderer = new CosmicRenderer(canvas, merged, {
1198
- dprCap: dprCapFor(merged.quality)
1337
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
1338
+ mouseColor: merged.mouseColor !== false,
1339
+ powerPreference: merged.powerPreference,
1340
+ onContextLost: function onContextLost() {
1341
+ contextLost = true;
1342
+ emitter.emit('contextlost', {});
1343
+ },
1344
+ onContextRestored: function onContextRestored() {
1345
+ contextLost = false;
1346
+ emitter.emit('contextrestored', {});
1347
+ renderOnce();
1348
+ wakeScheduler();
1349
+ }
1199
1350
  });
1200
1351
  } catch (error) {
1201
- renderer = new FallbackRenderer(canvas, merged);
1352
+ renderer = new FallbackRenderer(canvas, merged, {
1353
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale)
1354
+ });
1202
1355
  nextTick(function () {
1203
1356
  if (disposed) return;
1204
1357
  emitter.emit('error', {
@@ -1207,8 +1360,15 @@ function createCapsule(container) {
1207
1360
  });
1208
1361
  }
1209
1362
  } else {
1210
- renderer = new FallbackRenderer(canvas, merged);
1363
+ renderer = new FallbackRenderer(canvas, merged, {
1364
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale)
1365
+ });
1211
1366
  }
1367
+ var animationTime = 0;
1368
+ var frameGate = createFrameGate(merged.fps);
1369
+ renderOnce = function renderOnce() {
1370
+ return renderer.draw(animationTime);
1371
+ };
1212
1372
  var applySize = function applySize() {
1213
1373
  root.style.width = parseSize(merged.width);
1214
1374
  root.style.height = parseSize(merged.height);
@@ -1226,11 +1386,21 @@ function createCapsule(container) {
1226
1386
  renderer.resize();
1227
1387
  };
1228
1388
  applySize();
1229
- var resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(function () {
1230
- return renderer.resize();
1231
- }) : null;
1232
- if (resizeObserver) resizeObserver.observe(root);else window.addEventListener('resize', renderer.resize);
1233
- var visibility = createVisibilityGuard(root);
1389
+ var resize = function resize() {
1390
+ renderer.resize();
1391
+ if (manuallyPaused || reducedPaused || staticMode) renderOnce();
1392
+ };
1393
+ var resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(resize) : null;
1394
+ if (resizeObserver) resizeObserver.observe(root);else window.addEventListener('resize', resize);
1395
+ var visibility = createVisibilityGuard(root, wakeScheduler);
1396
+ var motionPreference = createReducedMotionPreference(merged.respectReducedMotion, function (matches) {
1397
+ reducedPaused = matches;
1398
+ if (matches) renderOnce();else wakeScheduler();
1399
+ });
1400
+ reducedPaused = motionPreference.matches();
1401
+ var isMotionPaused = function isMotionPaused() {
1402
+ return manuallyPaused || reducedPaused || staticMode || contextLost;
1403
+ };
1234
1404
  root.addEventListener('pointerenter', function () {
1235
1405
  return emitter.emit('pointerenter', {
1236
1406
  preset: _objectSpread2({}, preset)
@@ -1265,12 +1435,12 @@ function createCapsule(container) {
1265
1435
  preset: _objectSpread2({}, preset)
1266
1436
  });
1267
1437
  });
1268
- var animationTime = 0;
1438
+ renderOnce();
1269
1439
  var unsubscribe = subscribeScheduler(function (delta) {
1270
1440
  animationTime += delta;
1271
- if (visibility.isVisible()) renderer.draw(animationTime);
1441
+ if (frameGate.shouldDraw(delta)) renderer.draw(animationTime);
1272
1442
  }, function () {
1273
- return paused;
1443
+ return isMotionPaused() || !visibility.isVisible();
1274
1444
  });
1275
1445
  nextTick(function () {
1276
1446
  if (disposed) return;
@@ -1293,11 +1463,15 @@ function createCapsule(container) {
1293
1463
  var next = getPreset('capsule', ref);
1294
1464
  dirty.preset = true;
1295
1465
  Object.assign(preset, next);
1466
+ if (colorOverride) preset.colors = _toConsumableArray(colorOverride);
1467
+ if (seedOverride !== null) preset.seed = seedOverride;
1468
+ if (speedOverride !== null) preset.speed = speedOverride;
1296
1469
  var nextColors = preset.colors.map(normalizeColor);
1297
1470
  if (nextColors.every(Boolean)) preset.colors = nextColors;
1298
1471
  renderer.setPreset(_objectSpread2({}, preset));
1299
1472
  syncTheme();
1300
1473
  renderer.resize();
1474
+ if (isMotionPaused()) renderOnce();
1301
1475
  emitter.emit('presetchange', {
1302
1476
  preset: _objectSpread2({}, next)
1303
1477
  });
@@ -1309,26 +1483,32 @@ function createCapsule(container) {
1309
1483
  return !color;
1310
1484
  })) return this;
1311
1485
  dirty.colors = true;
1486
+ colorOverride = _toConsumableArray(next);
1312
1487
  preset.colors = next;
1313
1488
  renderer.setPreset(_objectSpread2(_objectSpread2({}, preset), {}, {
1314
1489
  colors: next
1315
1490
  }));
1491
+ if (isMotionPaused()) renderOnce();
1316
1492
  return this;
1317
1493
  },
1318
1494
  setSeed: function setSeed(seed) {
1319
- var value = Number(seed);
1320
- if (!Number.isFinite(value)) return this;
1495
+ var value = toFiniteNumber(seed);
1496
+ if (value === null) return this;
1321
1497
  dirty.seed = true;
1498
+ seedOverride = value;
1322
1499
  preset.seed = value;
1323
1500
  renderer.setPreset(_objectSpread2({}, preset));
1501
+ if (isMotionPaused()) renderOnce();
1324
1502
  return this;
1325
1503
  },
1326
1504
  setSpeed: function setSpeed(speed) {
1327
- var value = Number(speed);
1328
- if (!Number.isFinite(value)) return this;
1505
+ var value = toFiniteNumber(speed);
1506
+ if (value === null) return this;
1329
1507
  dirty.speed = true;
1508
+ speedOverride = value;
1330
1509
  preset.speed = value;
1331
1510
  renderer.setPreset(_objectSpread2({}, preset));
1511
+ if (isMotionPaused()) renderOnce();
1332
1512
  return this;
1333
1513
  },
1334
1514
  setText: function setText(content) {
@@ -1364,6 +1544,7 @@ function createCapsule(container) {
1364
1544
  return this;
1365
1545
  },
1366
1546
  setMouseColor: function setMouseColor(value) {
1547
+ dirty.mouseColor = true;
1367
1548
  merged.mouseColor = value !== false;
1368
1549
  if (typeof renderer.setMouseColor === 'function') renderer.setMouseColor(merged.mouseColor);
1369
1550
  return this;
@@ -1378,6 +1559,7 @@ function createCapsule(container) {
1378
1559
  merged.height = height;
1379
1560
  }
1380
1561
  applySize();
1562
+ if (isMotionPaused()) renderOnce();
1381
1563
  return this;
1382
1564
  },
1383
1565
  randomize: function randomize() {
@@ -1385,23 +1567,59 @@ function createCapsule(container) {
1385
1567
  return this;
1386
1568
  },
1387
1569
  pause: function pause() {
1388
- paused = true;
1570
+ dirty.paused = true;
1571
+ manuallyPaused = true;
1572
+ renderOnce();
1389
1573
  return this;
1390
1574
  },
1391
1575
  resume: function resume() {
1392
- paused = false;
1576
+ dirty.paused = true;
1577
+ manuallyPaused = false;
1578
+ wakeScheduler();
1579
+ return this;
1580
+ },
1581
+ setPaused: function setPaused(value) {
1582
+ return value ? this.pause() : this.resume();
1583
+ },
1584
+ setStatic: function setStatic(value) {
1585
+ dirty.static = true;
1586
+ staticMode = value === true;
1587
+ merged.static = staticMode;
1588
+ if (staticMode) renderOnce();else wakeScheduler();
1589
+ return this;
1590
+ },
1591
+ setFps: function setFps(fps) {
1592
+ dirty.fps = true;
1593
+ merged.fps = frameGate.setFps(fps);
1594
+ wakeScheduler();
1393
1595
  return this;
1394
1596
  },
1395
1597
  setQuality: function setQuality(quality) {
1598
+ dirty.quality = true;
1396
1599
  merged.quality = quality;
1397
- if (typeof renderer.setDprCap === 'function') renderer.setDprCap(dprCapFor(quality));
1600
+ if (typeof renderer.setDprCap === 'function') {
1601
+ renderer.setDprCap(effectiveDprCap(quality, merged.renderScale));
1602
+ }
1603
+ if (isMotionPaused()) renderOnce();
1604
+ return this;
1605
+ },
1606
+ setRenderScale: function setRenderScale(renderScale) {
1607
+ var value = Number(renderScale);
1608
+ if (!Number.isFinite(value)) return this;
1609
+ dirty.renderScale = true;
1610
+ merged.renderScale = Math.min(1, Math.max(0.25, value));
1611
+ if (typeof renderer.setDprCap === 'function') {
1612
+ renderer.setDprCap(effectiveDprCap(merged.quality, merged.renderScale));
1613
+ }
1614
+ if (isMotionPaused()) renderOnce();
1398
1615
  return this;
1399
1616
  },
1400
1617
  dispose: function dispose() {
1401
1618
  disposed = true;
1402
1619
  unsubscribe();
1403
1620
  visibility.dispose();
1404
- if (resizeObserver) resizeObserver.disconnect();else window.removeEventListener('resize', renderer.resize);
1621
+ motionPreference.dispose();
1622
+ if (resizeObserver) resizeObserver.disconnect();else window.removeEventListener('resize', resize);
1405
1623
  renderer.dispose();
1406
1624
  root.remove();
1407
1625
  },
@@ -1411,6 +1629,24 @@ function createCapsule(container) {
1411
1629
  get cssVars() {
1412
1630
  return merged.cssVars;
1413
1631
  },
1632
+ get mouseColor() {
1633
+ return merged.mouseColor;
1634
+ },
1635
+ get quality() {
1636
+ return merged.quality;
1637
+ },
1638
+ get renderScale() {
1639
+ return merged.renderScale;
1640
+ },
1641
+ get paused() {
1642
+ return manuallyPaused;
1643
+ },
1644
+ get static() {
1645
+ return staticMode;
1646
+ },
1647
+ get fps() {
1648
+ return frameGate.getFps();
1649
+ },
1414
1650
  dirty: dirty
1415
1651
  };
1416
1652
  }
@@ -1551,64 +1787,95 @@ function createProgram(gl) {
1551
1787
  var ProgressFlowRenderer = /*#__PURE__*/function () {
1552
1788
  function ProgressFlowRenderer(canvas, preset) {
1553
1789
  var _PROFILE_INDEX$preset;
1790
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
1554
1791
  _classCallCheck(this, ProgressFlowRenderer);
1555
1792
  var gl = canvas.getContext('webgl2', {
1556
1793
  alpha: false,
1557
1794
  antialias: true,
1558
1795
  premultipliedAlpha: false,
1559
- powerPreference: 'high-performance'
1796
+ powerPreference: options.powerPreference || 'high-performance'
1560
1797
  });
1561
1798
  if (!gl) throw new Error('WebGL2 unavailable');
1562
1799
  this.canvas = canvas;
1800
+ this.options = options;
1563
1801
  this.gl = gl;
1564
- this.program = createProgram(gl);
1565
1802
  this.profile = preset.edgeStyle === 'tide' ? 3 : (_PROFILE_INDEX$preset = PROFILE_INDEX[preset.id]) !== null && _PROFILE_INDEX$preset !== void 0 ? _PROFILE_INDEX$preset : 2;
1566
1803
  this.seed = stringSeed$1("".concat(preset.id, "-shader")) * 13.7 + 1.0;
1567
1804
  this.colors = preset.colors.map(hexToRgb01);
1568
1805
  this.motionData = getProgressMotionData(preset.id, preset.edgeStyle);
1569
1806
  var motionFactor = preset.edgeStyle === 'tide' ? MOTION_SCALE_FACTORS.tide : MOTION_SCALE_FACTORS[preset.id] || 1.04;
1570
1807
  this.motionScale = PROGRESS_MOTION_MAX_PX * motionFactor / 1257;
1571
- this.position = gl.getAttribLocation(this.program, 'a_position');
1572
- this.uniforms = {
1573
- resolution: gl.getUniformLocation(this.program, 'u_resolution'),
1574
- time: gl.getUniformLocation(this.program, 'u_time'),
1575
- progress: gl.getUniformLocation(this.program, 'u_progress'),
1576
- seed: gl.getUniformLocation(this.program, 'u_seed'),
1577
- profile: gl.getUniformLocation(this.program, 'u_profile'),
1578
- motion: gl.getUniformLocation(this.program, 'u_motion'),
1579
- effect: gl.getUniformLocation(this.program, 'u_effect'),
1580
- hasEffect: gl.getUniformLocation(this.program, 'u_hasEffect'),
1581
- effectFrames: gl.getUniformLocation(this.program, 'u_effectFrames'),
1582
- motionDuration: gl.getUniformLocation(this.program, 'u_motionDuration'),
1583
- motionScale: gl.getUniformLocation(this.program, 'u_motionScale'),
1584
- dark: gl.getUniformLocation(this.program, 'u_dark'),
1585
- accentA: gl.getUniformLocation(this.program, 'u_accentA'),
1586
- accentB: gl.getUniformLocation(this.program, 'u_accentB'),
1587
- glow: gl.getUniformLocation(this.program, 'u_glow')
1588
- };
1589
- this.buffer = gl.createBuffer();
1590
- gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
1591
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]), gl.STATIC_DRAW);
1592
- this.motionTexture = gl.createTexture();
1593
- gl.activeTexture(gl.TEXTURE0);
1594
- gl.bindTexture(gl.TEXTURE_2D, this.motionTexture);
1595
- gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
1596
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8, PROGRESS_MOTION_WIDTH, PROGRESS_MOTION_HEIGHT, 0, gl.RED, gl.UNSIGNED_BYTE, this.motionData);
1597
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
1598
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
1599
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
1600
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
1601
- this.effectTexture = gl.createTexture();
1602
- gl.activeTexture(gl.TEXTURE1);
1603
- gl.bindTexture(gl.TEXTURE_2D, this.effectTexture);
1604
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, 1, 1, 0, gl.RGB, gl.UNSIGNED_BYTE, new Uint8Array([32, 33, 38]));
1605
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
1606
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
1607
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
1608
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
1609
- this.effectUploaded = false;
1808
+ this.disposed = false;
1809
+ this.contextLost = false;
1810
+ this.setupResources();
1811
+ this.bindContextEvents();
1610
1812
  }
1611
1813
  return _createClass(ProgressFlowRenderer, [{
1814
+ key: "setupResources",
1815
+ value: function setupResources() {
1816
+ var gl = this.gl;
1817
+ this.program = createProgram(gl);
1818
+ this.position = gl.getAttribLocation(this.program, 'a_position');
1819
+ this.uniforms = {
1820
+ resolution: gl.getUniformLocation(this.program, 'u_resolution'),
1821
+ time: gl.getUniformLocation(this.program, 'u_time'),
1822
+ progress: gl.getUniformLocation(this.program, 'u_progress'),
1823
+ seed: gl.getUniformLocation(this.program, 'u_seed'),
1824
+ profile: gl.getUniformLocation(this.program, 'u_profile'),
1825
+ motion: gl.getUniformLocation(this.program, 'u_motion'),
1826
+ effect: gl.getUniformLocation(this.program, 'u_effect'),
1827
+ hasEffect: gl.getUniformLocation(this.program, 'u_hasEffect'),
1828
+ effectFrames: gl.getUniformLocation(this.program, 'u_effectFrames'),
1829
+ motionDuration: gl.getUniformLocation(this.program, 'u_motionDuration'),
1830
+ motionScale: gl.getUniformLocation(this.program, 'u_motionScale'),
1831
+ dark: gl.getUniformLocation(this.program, 'u_dark'),
1832
+ accentA: gl.getUniformLocation(this.program, 'u_accentA'),
1833
+ accentB: gl.getUniformLocation(this.program, 'u_accentB'),
1834
+ glow: gl.getUniformLocation(this.program, 'u_glow')
1835
+ };
1836
+ this.buffer = gl.createBuffer();
1837
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
1838
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]), gl.STATIC_DRAW);
1839
+ this.motionTexture = gl.createTexture();
1840
+ gl.activeTexture(gl.TEXTURE0);
1841
+ gl.bindTexture(gl.TEXTURE_2D, this.motionTexture);
1842
+ gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
1843
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8, PROGRESS_MOTION_WIDTH, PROGRESS_MOTION_HEIGHT, 0, gl.RED, gl.UNSIGNED_BYTE, this.motionData);
1844
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
1845
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
1846
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
1847
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
1848
+ this.effectTexture = gl.createTexture();
1849
+ gl.activeTexture(gl.TEXTURE1);
1850
+ gl.bindTexture(gl.TEXTURE_2D, this.effectTexture);
1851
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, 1, 1, 0, gl.RGB, gl.UNSIGNED_BYTE, new Uint8Array([32, 33, 38]));
1852
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
1853
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
1854
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
1855
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
1856
+ this.effectUploaded = false;
1857
+ }
1858
+ }, {
1859
+ key: "bindContextEvents",
1860
+ value: function bindContextEvents() {
1861
+ var _this = this;
1862
+ this.onContextLost = function (event) {
1863
+ event.preventDefault();
1864
+ if (_this.disposed) return;
1865
+ _this.contextLost = true;
1866
+ if (typeof _this.options.onContextLost === 'function') _this.options.onContextLost(event);
1867
+ };
1868
+ this.onContextRestored = function () {
1869
+ if (_this.disposed) return;
1870
+ _this.setupResources();
1871
+ _this.contextLost = false;
1872
+ _this.gl.viewport(0, 0, _this.canvas.width, _this.canvas.height);
1873
+ if (typeof _this.options.onContextRestored === 'function') _this.options.onContextRestored();
1874
+ };
1875
+ this.canvas.addEventListener('webglcontextlost', this.onContextLost, false);
1876
+ this.canvas.addEventListener('webglcontextrestored', this.onContextRestored, false);
1877
+ }
1878
+ }, {
1612
1879
  key: "resize",
1613
1880
  value: function resize(width, height, dpr) {
1614
1881
  var pixelWidth = Math.max(1, Math.round(width * dpr));
@@ -1623,6 +1890,7 @@ var ProgressFlowRenderer = /*#__PURE__*/function () {
1623
1890
  key: "draw",
1624
1891
  value: function draw(time, progress) {
1625
1892
  var effectImage = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
1893
+ if (this.disposed || this.contextLost) return;
1626
1894
  var gl = this.gl;
1627
1895
  gl.useProgram(this.program);
1628
1896
  gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
@@ -1639,7 +1907,8 @@ var ProgressFlowRenderer = /*#__PURE__*/function () {
1639
1907
  gl.uniform1f(this.uniforms.motionDuration, PROGRESS_MOTION_DURATION);
1640
1908
  gl.uniform1f(this.uniforms.motionScale, this.motionScale);
1641
1909
  var hasEffect = this.effectUploaded ? 1 : 0;
1642
- if (!this.effectUploaded && effectImage && effectImage.complete && effectImage.naturalWidth > 0) {
1910
+ var effectWidth = effectImage && (effectImage.naturalWidth || effectImage.width || 0);
1911
+ if (!this.effectUploaded && effectImage && effectWidth > 0) {
1643
1912
  gl.activeTexture(gl.TEXTURE1);
1644
1913
  gl.bindTexture(gl.TEXTURE_2D, this.effectTexture);
1645
1914
  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
@@ -1671,7 +1940,10 @@ var ProgressFlowRenderer = /*#__PURE__*/function () {
1671
1940
  }, {
1672
1941
  key: "dispose",
1673
1942
  value: function dispose() {
1943
+ this.disposed = true;
1674
1944
  var gl = this.gl;
1945
+ this.canvas.removeEventListener('webglcontextlost', this.onContextLost, false);
1946
+ this.canvas.removeEventListener('webglcontextrestored', this.onContextRestored, false);
1675
1947
  gl.deleteBuffer(this.buffer);
1676
1948
  gl.deleteTexture(this.motionTexture);
1677
1949
  gl.deleteTexture(this.effectTexture);
@@ -1682,8 +1954,9 @@ var ProgressFlowRenderer = /*#__PURE__*/function () {
1682
1954
  }]);
1683
1955
  }();
1684
1956
  function createProgressFlowRenderer(canvas, preset) {
1957
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
1685
1958
  try {
1686
- return new ProgressFlowRenderer(canvas, preset);
1959
+ return new ProgressFlowRenderer(canvas, preset, options);
1687
1960
  } catch (error) {
1688
1961
  console.warn('[画境观屿] 进度流体 WebGL2 不可用,使用 Canvas 2D 降级。', error);
1689
1962
  return null;
@@ -1743,50 +2016,49 @@ function createAtlas(id) {
1743
2016
  context.fillRect(20, 0, 44, FRAME_HEIGHT);
1744
2017
  context.restore();
1745
2018
  }
1746
- var image = new Image();
1747
- image.decoding = 'async';
1748
- var state = {
1749
- image: image,
1750
- ready: false
2019
+
2020
+ // A canvas is a valid TexImageSource. Keeping it directly avoids a PNG
2021
+ // encode -> base64 allocation -> Image decode round trip at startup.
2022
+ return {
2023
+ image: canvas,
2024
+ ready: true
1751
2025
  };
1752
- image.addEventListener('load', function () {
1753
- state.ready = true;
1754
- }, {
1755
- once: true
1756
- });
1757
- image.addEventListener('error', function () {
1758
- state.ready = false;
1759
- }, {
1760
- once: true
1761
- });
1762
- image.src = canvas.toDataURL('image/png');
1763
- return state;
1764
2026
  }
1765
2027
  function getProgressReferenceAtlas(id) {
1766
2028
  if (!CACHE[id]) CACHE[id] = createAtlas(id);
1767
2029
  return CACHE[id];
1768
2030
  }
1769
2031
 
1770
- /**
1771
- * Attach a WebGL2 fluid overlay to a progress capsule root.
1772
- *
1773
- * @param {object} params
1774
- * @param {HTMLElement} params.root progress capsule root element
1775
- * @param {HTMLCanvasElement} params.canvas 2D fallback canvas (kept beneath)
1776
- * @param {object} params.preset progress preset
1777
- * @param {() => number} params.getProgress reads the current progress value
1778
- * @returns {{ update(flowTime: number): void, dispose(): void } | null}
2032
+ /**
2033
+ * Attach a WebGL2 fluid overlay to a progress capsule root.
2034
+ *
2035
+ * @param {object} params
2036
+ * @param {HTMLElement} params.root progress capsule root element
2037
+ * @param {HTMLCanvasElement} params.canvas 2D fallback canvas (kept beneath)
2038
+ * @param {object} params.preset progress preset
2039
+ * @param {() => number} params.getProgress reads the current progress value
2040
+ * @returns {{ update(flowTime: number): void, setDprCap(cap: number): void, dispose(): void } | null}
1779
2041
  */
1780
2042
  function attachProgressFlowOverlay(_ref) {
1781
2043
  var root = _ref.root,
1782
2044
  canvas = _ref.canvas,
1783
2045
  preset = _ref.preset,
1784
- getProgress = _ref.getProgress;
2046
+ getProgress = _ref.getProgress,
2047
+ _ref$dprCap = _ref.dprCap,
2048
+ dprCap = _ref$dprCap === void 0 ? 2 : _ref$dprCap,
2049
+ _ref$powerPreference = _ref.powerPreference,
2050
+ powerPreference = _ref$powerPreference === void 0 ? 'high-performance' : _ref$powerPreference,
2051
+ onContextLost = _ref.onContextLost,
2052
+ onContextRestored = _ref.onContextRestored;
1785
2053
  var overlay = document.createElement('canvas');
1786
2054
  overlay.className = 'hj-progress-canvas hj-progress-overlay';
1787
2055
  overlay.setAttribute('aria-hidden', 'true');
1788
2056
  canvas.insertAdjacentElement('afterend', overlay);
1789
- var renderer = createProgressFlowRenderer(overlay, preset);
2057
+ var renderer = createProgressFlowRenderer(overlay, preset, {
2058
+ powerPreference: powerPreference,
2059
+ onContextLost: onContextLost,
2060
+ onContextRestored: onContextRestored
2061
+ });
1790
2062
  if (!renderer) {
1791
2063
  overlay.remove();
1792
2064
  return null;
@@ -1797,7 +2069,7 @@ function attachProgressFlowOverlay(_ref) {
1797
2069
  var bounds = root.getBoundingClientRect();
1798
2070
  var width = Math.max(1, bounds.width);
1799
2071
  var height = Math.max(1, bounds.height);
1800
- var dpr = Math.min(window.devicePixelRatio || 1, 2);
2072
+ var dpr = Math.min(window.devicePixelRatio || 1, dprCap);
1801
2073
  overlay.style.width = "".concat(width, "px");
1802
2074
  overlay.style.height = "".concat(height, "px");
1803
2075
  renderer.resize(width, height, dpr);
@@ -1821,6 +2093,10 @@ function attachProgressFlowOverlay(_ref) {
1821
2093
  setColors: function setColors(colors) {
1822
2094
  renderer.setColors(colors);
1823
2095
  },
2096
+ setDprCap: function setDprCap(cap) {
2097
+ dprCap = cap;
2098
+ resize();
2099
+ },
1824
2100
  dispose: function dispose() {
1825
2101
  if (resizeObserver) resizeObserver.disconnect();
1826
2102
  if (onWindowResize) window.removeEventListener('resize', onWindowResize);
@@ -1831,6 +2107,47 @@ function attachProgressFlowOverlay(_ref) {
1831
2107
  };
1832
2108
  }
1833
2109
 
2110
+ function normalizeRange(min, max) {
2111
+ var fallbackMin = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
2112
+ var fallbackMax = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 100;
2113
+ var nextMin = toFiniteNumber(min);
2114
+ var nextMax = toFiniteNumber(max);
2115
+ if (nextMin === null) nextMin = fallbackMin;
2116
+ if (nextMax === null) nextMax = fallbackMax;
2117
+ if (nextMin > nextMax) {
2118
+ var swap = nextMin;
2119
+ nextMin = nextMax;
2120
+ nextMax = swap;
2121
+ }
2122
+ return [nextMin, nextMax];
2123
+ }
2124
+ function clampProgressValue(value, min, max) {
2125
+ var number = toFiniteNumber(value);
2126
+ if (number === null) return null;
2127
+ return Math.min(Math.max(number, min), max);
2128
+ }
2129
+ function normalizeProgressStep(step) {
2130
+ var fallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'any';
2131
+ if (step === 'any' || step == null) return 'any';
2132
+ var increment = toFiniteNumber(step);
2133
+ return increment !== null && increment > 0 ? increment : fallback;
2134
+ }
2135
+ function snapProgressValue(value, min, max) {
2136
+ var step = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'any';
2137
+ var clamped = clampProgressValue(value, min, max);
2138
+ if (clamped === null) return null;
2139
+ if (step === 'any' || step == null) return clamped;
2140
+ var increment = normalizeProgressStep(step);
2141
+ if (increment === 'any') return clamped;
2142
+ var snapped = min + Math.round((clamped - min) / increment) * increment;
2143
+ return Math.min(Math.max(Number(snapped.toFixed(12)), min), max);
2144
+ }
2145
+ function progressRatio(value, min, max) {
2146
+ var range = max - min;
2147
+ if (!Number.isFinite(range) || range <= 0) return 0;
2148
+ return Math.min(Math.max((value - min) / range, 0), 1);
2149
+ }
2150
+
1834
2151
  var clamp = function clamp(value, min, max) {
1835
2152
  return Math.min(Math.max(value, min), max);
1836
2153
  };
@@ -1857,9 +2174,6 @@ function interpolate(template, values) {
1857
2174
  return values[key] !== undefined ? values[key] : '';
1858
2175
  });
1859
2176
  }
1860
- function prefersReducedMotion$1() {
1861
- return typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches;
1862
- }
1863
2177
  var FLOW_PROFILES = {
1864
2178
  'model-training': {
1865
2179
  cycles: [0.98, 3.05, 5.45],
@@ -1920,8 +2234,6 @@ var FLOW_PROFILES = {
1920
2234
  var ProgressCapsuleController = /*#__PURE__*/function () {
1921
2235
  function ProgressCapsuleController(_ref) {
1922
2236
  var _options$valueSuffix,
1923
- _options$min,
1924
- _options$max,
1925
2237
  _options$value,
1926
2238
  _this = this;
1927
2239
  var root = _ref.root,
@@ -1943,11 +2255,18 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1943
2255
  this.dirty = dirty;
1944
2256
  this.valueSuffix = (_options$valueSuffix = options.valueSuffix) !== null && _options$valueSuffix !== void 0 ? _options$valueSuffix : copy.valueSuffix;
1945
2257
  this.profile = preset.edgeStyle === 'tide' ? FLOW_PROFILES.tide : FLOW_PROFILES[preset.id] || FLOW_PROFILES['visual-training'];
1946
- this.min = (_options$min = options.min) !== null && _options$min !== void 0 ? _options$min : 0;
1947
- this.max = (_options$max = options.max) !== null && _options$max !== void 0 ? _options$max : 100;
1948
- this.dprCap = dprCapFor(options.quality);
2258
+ var _normalizeRange = normalizeRange(options.min, options.max);
2259
+ var _normalizeRange2 = _slicedToArray(_normalizeRange, 2);
2260
+ this.min = _normalizeRange2[0];
2261
+ this.max = _normalizeRange2[1];
2262
+ this.step = normalizeProgressStep(options.step);
2263
+ this.precision = Number.isInteger(Number(options.precision)) ? clamp(Number(options.precision), 0, 20) : 0;
2264
+ this.formatValue = typeof options.formatValue === 'function' ? options.formatValue : null;
2265
+ this.direction = options.direction === 'rtl' ? 'rtl' : 'ltr';
2266
+ this.dprCap = effectiveDprCap(options.quality, options.renderScale);
1949
2267
  this.ctx = canvas.getContext('2d');
1950
- this.value = clamp((_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : preset.initialProgress, this.min, this.max);
2268
+ this.value = snapProgressValue((_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : preset.initialProgress, this.min, this.max, this.step);
2269
+ if (this.value === null) this.value = clamp(preset.initialProgress, this.min, this.max);
1951
2270
  this.dragging = false;
1952
2271
  this.webglActive = false;
1953
2272
  this.flowTime = stringSeed(preset.id) * 31;
@@ -1957,10 +2276,12 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1957
2276
  this.width = 0;
1958
2277
  this.height = 0;
1959
2278
  this.handlers = {};
1960
- this.resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(function () {
1961
- return _this.resizeCanvas();
1962
- }) : null;
1963
- if (this.resizeObserver) this.resizeObserver.observe(this.root);else window.addEventListener('resize', this.resizeCanvas);
2279
+ this.onResize = function () {
2280
+ _this.resizeCanvas();
2281
+ if (typeof _this.options.onResize === 'function') _this.options.onResize();
2282
+ };
2283
+ this.resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(this.onResize) : null;
2284
+ if (this.resizeObserver) this.resizeObserver.observe(this.root);else window.addEventListener('resize', this.onResize);
1964
2285
  this.suppressEvents = true;
1965
2286
  this.setProgress(this.value, 'init');
1966
2287
  this.suppressEvents = false;
@@ -1973,16 +2294,37 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1973
2294
  this.randomState = Math.imul(this.randomState, 1664525) + 1013904223 >>> 0;
1974
2295
  return this.randomState / 4294967296;
1975
2296
  }
2297
+ }, {
2298
+ key: "displayText",
2299
+ value: function displayText() {
2300
+ if (this.formatValue) {
2301
+ return String(this.formatValue(this.value, {
2302
+ min: this.min,
2303
+ max: this.max,
2304
+ suffix: this.valueSuffix
2305
+ }));
2306
+ }
2307
+ var number = this.precision > 0 ? this.value.toFixed(this.precision) : String(Math.round(this.value));
2308
+ return "".concat(number).concat(this.valueSuffix);
2309
+ }
2310
+ }, {
2311
+ key: "syncValueDom",
2312
+ value: function syncValueDom() {
2313
+ var text = this.displayText();
2314
+ this.root.style.setProperty('--progress', this.value.toFixed(2));
2315
+ this.root.style.setProperty('--progress-ratio', progressRatio(this.value, this.min, this.max).toFixed(4));
2316
+ this.root.setAttribute('aria-valuenow', String(this.value));
2317
+ this.root.setAttribute('aria-valuetext', text);
2318
+ if (this.valueElement) this.valueElement.textContent = text;
2319
+ }
1976
2320
  }, {
1977
2321
  key: "setProgress",
1978
2322
  value: function setProgress(nextValue) {
1979
2323
  var source = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'auto';
1980
- this.value = clamp(nextValue, this.min, this.max);
1981
- var rounded = Math.round(this.value);
1982
- this.root.style.setProperty('--progress', this.value.toFixed(2));
1983
- this.root.setAttribute('aria-valuenow', String(rounded));
1984
- this.root.setAttribute('aria-valuetext', "".concat(rounded).concat(this.valueSuffix));
1985
- if (this.valueElement) this.valueElement.textContent = "".concat(rounded).concat(this.valueSuffix);
2324
+ var next = snapProgressValue(nextValue, this.min, this.max, this.step);
2325
+ if (next === null) return false;
2326
+ this.value = next;
2327
+ this.syncValueDom();
1986
2328
  if (!this.suppressEvents) {
1987
2329
  if (this.dirty) this.dirty.value = true;
1988
2330
  this.emitter.emit('change', {
@@ -1990,19 +2332,58 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1990
2332
  source: source
1991
2333
  });
1992
2334
  }
2335
+ return true;
1993
2336
  }
1994
2337
  }, {
1995
2338
  key: "setValueSuffix",
1996
2339
  value: function setValueSuffix(suffix) {
2340
+ if (this.dirty) this.dirty.valueSuffix = true;
1997
2341
  this.valueSuffix = String(suffix == null ? '' : suffix);
1998
- var rounded = Math.round(this.value);
1999
- if (this.valueElement) this.valueElement.textContent = "".concat(rounded).concat(this.valueSuffix);
2000
- this.root.setAttribute('aria-valuetext', "".concat(rounded).concat(this.valueSuffix));
2342
+ this.syncValueDom();
2343
+ return this;
2344
+ }
2345
+ }, {
2346
+ key: "setStep",
2347
+ value: function setStep(step) {
2348
+ var nextStep = normalizeProgressStep(step, this.step);
2349
+ if (nextStep === this.step) return this;
2350
+ if (this.dirty) this.dirty.step = true;
2351
+ this.step = nextStep;
2352
+ var next = snapProgressValue(this.value, this.min, this.max, this.step);
2353
+ if (next !== null && next !== this.value) this.setProgress(next, 'prop');
2354
+ return this;
2355
+ }
2356
+ }, {
2357
+ key: "setPrecision",
2358
+ value: function setPrecision(precision) {
2359
+ var value = Number(precision);
2360
+ if (!Number.isInteger(value) || value < 0 || value > 20) return this;
2361
+ if (this.dirty) this.dirty.precision = true;
2362
+ this.precision = value;
2363
+ this.syncValueDom();
2364
+ return this;
2365
+ }
2366
+ }, {
2367
+ key: "setFormatValue",
2368
+ value: function setFormatValue(formatter) {
2369
+ if (formatter != null && typeof formatter !== 'function') return this;
2370
+ if (this.dirty) this.dirty.formatValue = true;
2371
+ this.formatValue = formatter || null;
2372
+ this.syncValueDom();
2373
+ return this;
2374
+ }
2375
+ }, {
2376
+ key: "setDirection",
2377
+ value: function setDirection(direction) {
2378
+ if (this.dirty) this.dirty.direction = true;
2379
+ this.direction = direction === 'rtl' ? 'rtl' : 'ltr';
2380
+ this.root.dataset.direction = this.direction;
2001
2381
  return this;
2002
2382
  }
2003
2383
  }, {
2004
2384
  key: "setShowValue",
2005
2385
  value: function setShowValue(show) {
2386
+ if (this.dirty) this.dirty.showValue = true;
2006
2387
  if (this.valueElement) this.valueElement.hidden = show === false;
2007
2388
  return this;
2008
2389
  }
@@ -2167,15 +2548,14 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
2167
2548
  }, {
2168
2549
  key: "setRange",
2169
2550
  value: function setRange(min, max) {
2170
- if (min > max) {
2171
- var swap = min;
2172
- min = max;
2173
- max = swap;
2174
- }
2175
- this.min = min;
2176
- this.max = max;
2177
- var clamped = clamp(this.value, this.min, this.max);
2178
- if (clamped !== this.value) this.setProgress(clamped, 'prop');
2551
+ var nextRange = normalizeRange(min, max, this.min, this.max);
2552
+ this.min = nextRange[0];
2553
+ this.max = nextRange[1];
2554
+ this.root.setAttribute('aria-valuemin', String(this.min));
2555
+ this.root.setAttribute('aria-valuemax', String(this.max));
2556
+ var next = snapProgressValue(this.value, this.min, this.max, this.step);
2557
+ if (next !== this.value) this.setProgress(next, 'prop');else this.syncValueDom();
2558
+ return this;
2179
2559
  }
2180
2560
  }, {
2181
2561
  key: "drawReferenceFlow",
@@ -2300,8 +2680,39 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
2300
2680
  value: function updateFromPointer(event) {
2301
2681
  var bounds = this.root.getBoundingClientRect();
2302
2682
  var ratio = bounds.width > 0 ? (event.clientX - bounds.left) / bounds.width : 0;
2683
+ if (this.direction === 'rtl') ratio = 1 - ratio;
2303
2684
  this.setProgress(this.min + ratio * (this.max - this.min), 'drag');
2304
2685
  }
2686
+ }, {
2687
+ key: "onKeyDown",
2688
+ value: function onKeyDown(event) {
2689
+ var range = this.max - this.min;
2690
+ var configuredStep = Number(this.step);
2691
+ var step = Number.isFinite(configuredStep) && configuredStep > 0 ? configuredStep : Math.max(range / 100, 1e-7);
2692
+ var direction = this.direction === 'rtl' ? -1 : 1;
2693
+ var aliases = {
2694
+ Left: 'ArrowLeft',
2695
+ Right: 'ArrowRight',
2696
+ Up: 'ArrowUp',
2697
+ Down: 'ArrowDown'
2698
+ };
2699
+ var keyCodes = {
2700
+ 35: 'End',
2701
+ 36: 'Home',
2702
+ 33: 'PageUp',
2703
+ 34: 'PageDown',
2704
+ 37: 'ArrowLeft',
2705
+ 38: 'ArrowUp',
2706
+ 39: 'ArrowRight',
2707
+ 40: 'ArrowDown'
2708
+ };
2709
+ var key = aliases[event.key] || event.key || keyCodes[event.keyCode];
2710
+ var next = null;
2711
+ 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;
2712
+ if (next === null) return;
2713
+ event.preventDefault();
2714
+ this.setProgress(next, 'keyboard');
2715
+ }
2305
2716
  }, {
2306
2717
  key: "beginDrag",
2307
2718
  value: function beginDrag(event) {
@@ -2389,6 +2800,12 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
2389
2800
  this.root.addEventListener('pointerup', this.handlers.pointerup);
2390
2801
  this.root.addEventListener('pointercancel', this.handlers.pointercancel);
2391
2802
  }
2803
+ if (this.options.keyboard !== false) {
2804
+ this.handlers.keydown = function (event) {
2805
+ return _this3.onKeyDown(event);
2806
+ };
2807
+ this.root.addEventListener('keydown', this.handlers.keydown);
2808
+ }
2392
2809
  }
2393
2810
  }, {
2394
2811
  key: "update",
@@ -2407,20 +2824,19 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
2407
2824
  key: "randomize",
2408
2825
  value: function randomize() {
2409
2826
  this.flowTime = this.random() * 40;
2827
+ return this.flowTime;
2410
2828
  }
2411
2829
  }, {
2412
2830
  key: "setColors",
2413
2831
  value: function setColors(colors) {
2414
2832
  if (!Array.isArray(colors) || colors.length !== 4) return;
2415
- this.preset = _objectSpread2(_objectSpread2({}, this.preset), {}, {
2416
- colors: colors
2417
- });
2833
+ this.preset.colors = _toConsumableArray(colors);
2418
2834
  }
2419
2835
  }, {
2420
2836
  key: "dispose",
2421
2837
  value: function dispose() {
2422
2838
  if (this._dragRaf) cancelAnimationFrame(this._dragRaf);
2423
- if (this.resizeObserver) this.resizeObserver.disconnect();else window.removeEventListener('resize', this.resizeCanvas);
2839
+ if (this.resizeObserver) this.resizeObserver.disconnect();else window.removeEventListener('resize', this.onResize);
2424
2840
  for (var _i = 0, _Object$keys = Object.keys(this.handlers); _i < _Object$keys.length; _i++) {
2425
2841
  var name = _Object$keys[_i];
2426
2842
  var handler = this.handlers[name];
@@ -2431,15 +2847,16 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
2431
2847
  }]);
2432
2848
  }();
2433
2849
 
2434
- /**
2850
+ /**
2435
2851
  * Mount a fluid progress capsule into `container`.
2436
2852
  *
2437
- * Options: preset (literary name), width, height, value, min, max,
2438
- * draggable, colors, edgeStyle, textRatio (0-100, text region
2853
+ * Options: preset (literary name), width, height, value/modelValue, min, max,
2854
+ * step, draggable, keyboard, direction, precision/formatValue, colors,
2855
+ * edgeStyle, textRatio (0-100, text region
2439
2856
  * width in percent), text (HTML string or DOM nodes for the text slot),
2440
2857
  * colorContent (HTML string or DOM nodes for the color slot),
2441
- * showValue (show/hide the right-side percentage), quality,
2442
- * respectReducedMotion, cssVars.
2858
+ * showValue (show/hide the right-side percentage), quality, renderScale,
2859
+ * powerPreference, fps, paused/static, respectReducedMotion, cssVars.
2443
2860
  */
2444
2861
  function createProgressCapsule(container) {
2445
2862
  var _options$preset, _merged$min, _merged$max;
@@ -2449,14 +2866,20 @@ function createProgressCapsule(container) {
2449
2866
  }
2450
2867
  var preset = _objectSpread2({}, getPreset('progress', (_options$preset = options.preset) !== null && _options$preset !== void 0 ? _options$preset : '星火'));
2451
2868
  var merged = normalizeOptions(DEFAULTS.progress, preset, options);
2452
- if (merged.min > merged.max) {
2453
- var swap = merged.min;
2454
- merged.min = merged.max;
2455
- merged.max = swap;
2456
- }
2457
- var normalizedColors = merged.colors.map(normalizeColor);
2458
- if (normalizedColors.every(Boolean)) preset.colors = normalizedColors;
2459
- preset.edgeStyle = merged.edgeStyle;
2869
+ var _normalizeRange3 = normalizeRange(merged.min, merged.max);
2870
+ var _normalizeRange4 = _slicedToArray(_normalizeRange3, 2);
2871
+ merged.min = _normalizeRange4[0];
2872
+ merged.max = _normalizeRange4[1];
2873
+ if (merged.modelValue !== undefined) merged.value = merged.modelValue;
2874
+ var normalizedColors = Array.isArray(merged.colors) ? merged.colors.map(normalizeColor) : [];
2875
+ if (normalizedColors.length === 4 && normalizedColors.every(Boolean)) preset.colors = normalizedColors;
2876
+ var initialEdgeStyle = merged.edgeStyle === 'tide' || merged.edgeStyle === 'flow' ? merged.edgeStyle : preset.edgeStyle;
2877
+ preset.edgeStyle = initialEdgeStyle;
2878
+ merged.edgeStyle = initialEdgeStyle;
2879
+ merged.colors = _toConsumableArray(preset.colors);
2880
+ var optionColors = Array.isArray(options.colors) ? options.colors.map(normalizeColor) : [];
2881
+ var colorOverride = optionColors.length === 4 && optionColors.every(Boolean) ? _toConsumableArray(optionColors) : null;
2882
+ var edgeStyleOverride = options.edgeStyle === 'flow' || options.edgeStyle === 'tide' ? merged.edgeStyle : null;
2460
2883
  var copy = COPY;
2461
2884
  var customLabel = typeof options.label === 'string' && options.label ? options.label : null;
2462
2885
  // Vue 的裸布尔属性(<ProgressCapsule disabled />)会传成空字符串,
@@ -2465,18 +2888,31 @@ function createProgressCapsule(container) {
2465
2888
  var readonly = merged.readonly === true || merged.readonly === '' || merged.readonly === 'true';
2466
2889
  var locked = disabled || readonly;
2467
2890
  var effectiveDraggable = locked ? false : merged.draggable !== false;
2891
+ var effectiveKeyboard = locked ? false : merged.keyboard !== false;
2468
2892
  var dirty = {
2469
2893
  preset: false,
2470
2894
  colors: false,
2471
2895
  textRatio: false,
2472
2896
  cssVars: false,
2473
2897
  edgeStyle: false,
2474
- value: false
2898
+ value: false,
2899
+ step: false,
2900
+ precision: false,
2901
+ formatValue: false,
2902
+ direction: false,
2903
+ showValue: false,
2904
+ valueSuffix: false,
2905
+ quality: false,
2906
+ renderScale: false,
2907
+ paused: false,
2908
+ static: false,
2909
+ fps: false
2475
2910
  };
2476
2911
  var root = document.createElement('div');
2477
2912
  root.className = 'hj-capsule-root hj-progress-root';
2478
2913
  root.setAttribute('role', 'slider');
2479
2914
  root.setAttribute('data-draggable', String(effectiveDraggable));
2915
+ root.dataset.direction = merged.direction === 'rtl' ? 'rtl' : 'ltr';
2480
2916
  if (disabled) {
2481
2917
  root.setAttribute('aria-disabled', 'true');
2482
2918
  root.classList.add('is-disabled');
@@ -2485,7 +2921,8 @@ function createProgressCapsule(container) {
2485
2921
  root.setAttribute('aria-readonly', 'true');
2486
2922
  root.classList.add('is-readonly');
2487
2923
  }
2488
- if (locked) root.setAttribute('tabindex', '-1');
2924
+ root.setAttribute('tabindex', locked ? '-1' : effectiveKeyboard ? '0' : '-1');
2925
+ root.setAttribute('aria-orientation', 'horizontal');
2489
2926
  root.setAttribute('aria-valuemin', String((_merged$min = merged.min) !== null && _merged$min !== void 0 ? _merged$min : 0));
2490
2927
  root.setAttribute('aria-valuemax', String((_merged$max = merged.max) !== null && _merged$max !== void 0 ? _merged$max : 100));
2491
2928
  root.setAttribute('aria-valuenow', String(preset.initialProgress));
@@ -2540,8 +2977,22 @@ function createProgressCapsule(container) {
2540
2977
  root.appendChild(valueElement);
2541
2978
  container.appendChild(root);
2542
2979
  var emitter = createEmitter();
2543
- var paused = merged.respectReducedMotion && prefersReducedMotion$1();
2980
+ var manuallyPaused = merged.paused === true;
2981
+ var reducedPaused = false;
2982
+ var staticMode = merged.static === true;
2983
+ var contextLost = false;
2544
2984
  var disposed = false;
2985
+ var renderOnce = function renderOnce() {};
2986
+ var onContextLost = function onContextLost() {
2987
+ contextLost = true;
2988
+ emitter.emit('contextlost', {});
2989
+ };
2990
+ var onContextRestored = function onContextRestored() {
2991
+ contextLost = false;
2992
+ emitter.emit('contextrestored', {});
2993
+ renderOnce();
2994
+ wakeScheduler();
2995
+ };
2545
2996
  var applySize = function applySize() {
2546
2997
  root.style.width = parseSize(merged.width);
2547
2998
  root.style.height = parseSize(merged.height);
@@ -2562,7 +3013,11 @@ function createProgressCapsule(container) {
2562
3013
  preset: preset,
2563
3014
  emitter: emitter,
2564
3015
  options: _objectSpread2(_objectSpread2({}, merged), {}, {
2565
- draggable: effectiveDraggable
3016
+ draggable: effectiveDraggable,
3017
+ keyboard: effectiveKeyboard,
3018
+ onResize: function onResize() {
3019
+ if (manuallyPaused || reducedPaused || staticMode) renderOnce();
3020
+ }
2566
3021
  }),
2567
3022
  copy: copy,
2568
3023
  dirty: dirty
@@ -2574,8 +3029,12 @@ function createProgressCapsule(container) {
2574
3029
  canvas: canvas,
2575
3030
  preset: preset,
2576
3031
  getProgress: function getProgress() {
2577
- return controller.value;
2578
- }
3032
+ return progressRatio(controller.value, controller.min, controller.max) * 100;
3033
+ },
3034
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
3035
+ powerPreference: merged.powerPreference,
3036
+ onContextLost: onContextLost,
3037
+ onContextRestored: onContextRestored
2579
3038
  });
2580
3039
  }
2581
3040
  if (overlay) controller.webglActive = true;else if (merged.renderer !== 'canvas2d') {
@@ -2586,17 +3045,34 @@ function createProgressCapsule(container) {
2586
3045
  });
2587
3046
  });
2588
3047
  }
2589
- var visibility = createVisibilityGuard(root);
3048
+ var visibility = createVisibilityGuard(root, wakeScheduler);
3049
+ var motionPreference = createReducedMotionPreference(merged.respectReducedMotion, function (matches) {
3050
+ reducedPaused = matches;
3051
+ if (matches) renderOnce();else wakeScheduler();
3052
+ });
3053
+ reducedPaused = motionPreference.matches();
3054
+ var isMotionPaused = function isMotionPaused() {
3055
+ return manuallyPaused || reducedPaused || staticMode || contextLost;
3056
+ };
2590
3057
  var flowTime = 0;
3058
+ var frameGate = createFrameGate(merged.fps);
3059
+ renderOnce = function renderOnce() {
3060
+ controller.draw();
3061
+ if (overlay) overlay.update(flowTime);
3062
+ };
3063
+ renderOnce();
3064
+ var offPausedChange = emitter.on('change', function () {
3065
+ if (isMotionPaused()) renderOnce();
3066
+ });
2591
3067
  var unsubscribe = subscribeScheduler(function (delta, now) {
2592
3068
  flowTime += delta;
2593
3069
  controller.update(delta, false);
2594
- if (visibility.isVisible()) {
3070
+ if (frameGate.shouldDraw(delta)) {
2595
3071
  controller.draw();
2596
3072
  if (overlay) overlay.update(flowTime);
2597
3073
  }
2598
3074
  }, function () {
2599
- return paused;
3075
+ return isMotionPaused() || !visibility.isVisible();
2600
3076
  });
2601
3077
  nextTick(function () {
2602
3078
  if (disposed) return;
@@ -2623,32 +3099,41 @@ function createProgressCapsule(container) {
2623
3099
  },
2624
3100
  setRange: function setRange(min, max) {
2625
3101
  controller.setRange(min, max);
3102
+ if (isMotionPaused()) renderOnce();
2626
3103
  return this;
2627
3104
  },
2628
3105
  setPreset: function setPreset(ref) {
2629
3106
  var next = getPreset('progress', ref);
2630
3107
  dirty.preset = true;
2631
3108
  Object.assign(preset, next);
3109
+ if (colorOverride) preset.colors = _toConsumableArray(colorOverride);
3110
+ if (edgeStyleOverride) preset.edgeStyle = edgeStyleOverride;
2632
3111
  var nextColors = preset.colors.map(normalizeColor);
2633
3112
  if (nextColors.every(Boolean)) preset.colors = nextColors;
2634
3113
  controller.preset = preset;
2635
- controller.profile = next.edgeStyle === 'tide' ? FLOW_PROFILES.tide : FLOW_PROFILES[next.id] || FLOW_PROFILES['visual-training'];
3114
+ controller.profile = preset.edgeStyle === 'tide' ? FLOW_PROFILES.tide : FLOW_PROFILES[preset.id] || FLOW_PROFILES['visual-training'];
2636
3115
  controller.flowTime = stringSeed(next.id) * 31;
2637
3116
  controller.seed = stringSeed("".concat(next.id, "-reference")) * Math.PI * 2;
2638
3117
  syncDom();
2639
3118
  if (overlay) {
2640
3119
  overlay.dispose();
3120
+ contextLost = false;
2641
3121
  overlay = attachProgressFlowOverlay({
2642
3122
  root: root,
2643
3123
  canvas: canvas,
2644
3124
  preset: preset,
2645
3125
  getProgress: function getProgress() {
2646
- return controller.value;
2647
- }
3126
+ return progressRatio(controller.value, controller.min, controller.max) * 100;
3127
+ },
3128
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
3129
+ powerPreference: merged.powerPreference,
3130
+ onContextLost: onContextLost,
3131
+ onContextRestored: onContextRestored
2648
3132
  });
2649
3133
  }
2650
3134
  controller.webglActive = Boolean(overlay);
2651
3135
  controller.resizeCanvas();
3136
+ if (isMotionPaused()) renderOnce();
2652
3137
  emitter.emit('presetchange', {
2653
3138
  preset: _objectSpread2({}, preset)
2654
3139
  });
@@ -2658,21 +3143,28 @@ function createProgressCapsule(container) {
2658
3143
  var value = String(edgeStyle || '').toLowerCase();
2659
3144
  if (value !== 'flow' && value !== 'tide') return this;
2660
3145
  dirty.edgeStyle = true;
3146
+ edgeStyleOverride = value;
2661
3147
  preset.edgeStyle = value;
2662
3148
  controller.profile = value === 'tide' ? FLOW_PROFILES.tide : FLOW_PROFILES[preset.id] || FLOW_PROFILES['visual-training'];
2663
3149
  if (overlay) {
2664
3150
  overlay.dispose();
3151
+ contextLost = false;
2665
3152
  overlay = attachProgressFlowOverlay({
2666
3153
  root: root,
2667
3154
  canvas: canvas,
2668
3155
  preset: preset,
2669
3156
  getProgress: function getProgress() {
2670
- return controller.value;
2671
- }
3157
+ return progressRatio(controller.value, controller.min, controller.max) * 100;
3158
+ },
3159
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
3160
+ powerPreference: merged.powerPreference,
3161
+ onContextLost: onContextLost,
3162
+ onContextRestored: onContextRestored
2672
3163
  });
2673
3164
  }
2674
3165
  controller.webglActive = Boolean(overlay);
2675
3166
  controller.resizeCanvas();
3167
+ if (isMotionPaused()) renderOnce();
2676
3168
  return this;
2677
3169
  },
2678
3170
  setText: function setText(content) {
@@ -2710,6 +3202,22 @@ function createProgressCapsule(container) {
2710
3202
  controller.setValueSuffix(suffix);
2711
3203
  return this;
2712
3204
  },
3205
+ setStep: function setStep(step) {
3206
+ controller.setStep(step);
3207
+ return this;
3208
+ },
3209
+ setPrecision: function setPrecision(precision) {
3210
+ controller.setPrecision(precision);
3211
+ return this;
3212
+ },
3213
+ setFormatValue: function setFormatValue(formatter) {
3214
+ controller.setFormatValue(formatter);
3215
+ return this;
3216
+ },
3217
+ setDirection: function setDirection(direction) {
3218
+ controller.setDirection(direction);
3219
+ return this;
3220
+ },
2713
3221
  setShowValue: function setShowValue(show) {
2714
3222
  controller.setShowValue(show);
2715
3223
  return this;
@@ -2720,9 +3228,12 @@ function createProgressCapsule(container) {
2720
3228
  return !color;
2721
3229
  })) return this;
2722
3230
  dirty.colors = true;
3231
+ colorOverride = _toConsumableArray(next);
3232
+ preset.colors = _toConsumableArray(next);
2723
3233
  controller.setColors(next);
2724
3234
  if (overlay) overlay.setColors(next);
2725
3235
  controller.resizeCanvas();
3236
+ if (isMotionPaused()) renderOnce();
2726
3237
  return this;
2727
3238
  },
2728
3239
  setSize: function setSize(width, height) {
@@ -2736,30 +3247,68 @@ function createProgressCapsule(container) {
2736
3247
  }
2737
3248
  applySize();
2738
3249
  controller.resizeCanvas();
3250
+ if (isMotionPaused()) renderOnce();
2739
3251
  return this;
2740
3252
  },
2741
3253
  randomize: function randomize() {
2742
- controller.randomize();
3254
+ flowTime = controller.randomize();
3255
+ if (isMotionPaused()) renderOnce();
2743
3256
  return this;
2744
3257
  },
2745
3258
  pause: function pause() {
2746
- paused = true;
3259
+ dirty.paused = true;
3260
+ manuallyPaused = true;
3261
+ renderOnce();
2747
3262
  return this;
2748
3263
  },
2749
3264
  resume: function resume() {
2750
- paused = false;
3265
+ dirty.paused = true;
3266
+ manuallyPaused = false;
3267
+ wakeScheduler();
3268
+ return this;
3269
+ },
3270
+ setPaused: function setPaused(value) {
3271
+ return value ? this.pause() : this.resume();
3272
+ },
3273
+ setStatic: function setStatic(value) {
3274
+ dirty.static = true;
3275
+ staticMode = value === true;
3276
+ merged.static = staticMode;
3277
+ if (staticMode) renderOnce();else wakeScheduler();
3278
+ return this;
3279
+ },
3280
+ setFps: function setFps(fps) {
3281
+ dirty.fps = true;
3282
+ merged.fps = frameGate.setFps(fps);
3283
+ wakeScheduler();
2751
3284
  return this;
2752
3285
  },
2753
3286
  setQuality: function setQuality(quality) {
3287
+ dirty.quality = true;
2754
3288
  merged.quality = quality;
2755
- controller.dprCap = dprCapFor(quality);
3289
+ controller.dprCap = effectiveDprCap(quality, merged.renderScale);
2756
3290
  controller.resizeCanvas();
3291
+ if (overlay) overlay.setDprCap(controller.dprCap);
3292
+ if (isMotionPaused()) renderOnce();
3293
+ return this;
3294
+ },
3295
+ setRenderScale: function setRenderScale(renderScale) {
3296
+ var value = Number(renderScale);
3297
+ if (!Number.isFinite(value)) return this;
3298
+ dirty.renderScale = true;
3299
+ merged.renderScale = Math.min(1, Math.max(0.25, value));
3300
+ controller.dprCap = effectiveDprCap(merged.quality, merged.renderScale);
3301
+ controller.resizeCanvas();
3302
+ if (overlay) overlay.setDprCap(controller.dprCap);
3303
+ if (isMotionPaused()) renderOnce();
2757
3304
  return this;
2758
3305
  },
2759
3306
  dispose: function dispose() {
2760
3307
  disposed = true;
2761
3308
  unsubscribe();
3309
+ offPausedChange();
2762
3310
  visibility.dispose();
3311
+ motionPreference.dispose();
2763
3312
  if (overlay) overlay.dispose();
2764
3313
  controller.dispose();
2765
3314
  root.remove();
@@ -2770,26 +3319,89 @@ function createProgressCapsule(container) {
2770
3319
  get cssVars() {
2771
3320
  return merged.cssVars;
2772
3321
  },
3322
+ get min() {
3323
+ return controller.min;
3324
+ },
3325
+ get max() {
3326
+ return controller.max;
3327
+ },
3328
+ get step() {
3329
+ return controller.step;
3330
+ },
3331
+ get precision() {
3332
+ return controller.precision;
3333
+ },
3334
+ get formatValue() {
3335
+ return controller.formatValue;
3336
+ },
3337
+ get direction() {
3338
+ return controller.direction;
3339
+ },
3340
+ get showValue() {
3341
+ return !valueElement.hidden;
3342
+ },
3343
+ get valueSuffix() {
3344
+ return controller.valueSuffix;
3345
+ },
3346
+ get quality() {
3347
+ return merged.quality;
3348
+ },
3349
+ get renderScale() {
3350
+ return merged.renderScale;
3351
+ },
3352
+ get paused() {
3353
+ return manuallyPaused;
3354
+ },
3355
+ get static() {
3356
+ return staticMode;
3357
+ },
3358
+ get fps() {
3359
+ return frameGate.getFps();
3360
+ },
2773
3361
  dirty: dirty
2774
3362
  };
2775
3363
  }
2776
3364
 
2777
- function prefersReducedMotion() {
2778
- return typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches;
3365
+ var HOST_STYLES = new WeakMap();
3366
+ function acquireHostStyles(host) {
3367
+ var active = HOST_STYLES.get(host);
3368
+ if (active) {
3369
+ active.count += 1;
3370
+ return;
3371
+ }
3372
+ var state = {
3373
+ count: 1,
3374
+ position: host.style.position,
3375
+ isolation: host.style.isolation
3376
+ };
3377
+ var computed = getComputedStyle(host);
3378
+ if (computed.position === 'static' || computed.position === '') host.style.position = 'relative';
3379
+ host.style.isolation = 'isolate';
3380
+ HOST_STYLES.set(host, state);
3381
+ }
3382
+ function releaseHostStyles(host) {
3383
+ var state = HOST_STYLES.get(host);
3384
+ if (!state) return;
3385
+ state.count -= 1;
3386
+ if (state.count > 0) return;
3387
+ host.style.position = state.position;
3388
+ host.style.isolation = state.isolation;
3389
+ HOST_STYLES.delete(host);
2779
3390
  }
2780
3391
 
2781
- /**
2782
- * Mount the cosmic (nebula) material as a background layer inside `host`.
2783
- *
2784
- * The layer is absolutely positioned and defaults to `z-index: -1`, so it
2785
- * paints behind the host's in-flow content: text/buttons/images inside the
2786
- * host sit on top with zero extra CSS. Host gets `position: relative;
2787
- * isolation: isolate` automatically so the layer can never escape its box
2788
- * (or break stacking outside it).
2789
- *
3392
+ /**
3393
+ * Mount the cosmic (nebula) material as a background layer inside `host`.
3394
+ *
3395
+ * The layer is absolutely positioned and defaults to `z-index: -1`, so it
3396
+ * paints behind the host's in-flow content: text/buttons/images inside the
3397
+ * host sit on top with zero extra CSS. Host gets `position: relative;
3398
+ * isolation: isolate` automatically so the layer can never escape its box
3399
+ * (or break stacking outside it).
3400
+ *
2790
3401
  * Options: preset (literary name), colors, seed, speed, quality,
2791
- * renderer, mouseColor, respectReducedMotion, opacity (0-1), fallbackColor
2792
- * (true=preset base color, a hex string, or false to disable).
3402
+ * renderer, renderScale, powerPreference, fps, paused/static, mouseColor,
3403
+ * respectReducedMotion, opacity (0-1), fallbackColor (true=preset base
3404
+ * color, a hex string, or false to disable).
2793
3405
  */
2794
3406
  function createColorBackground(host) {
2795
3407
  var _options$preset;
@@ -2803,18 +3415,29 @@ function createColorBackground(host) {
2803
3415
  renderer: DEFAULTS.capsule.renderer,
2804
3416
  respectReducedMotion: DEFAULTS.capsule.respectReducedMotion,
2805
3417
  mouseColor: true,
3418
+ renderScale: 1,
3419
+ powerPreference: 'high-performance',
3420
+ fps: DEFAULTS.capsule.fps,
3421
+ paused: false,
3422
+ static: false,
2806
3423
  opacity: 1,
2807
3424
  fallbackColor: true
2808
3425
  }, preset, options);
2809
- var normalizedColors = merged.colors.map(normalizeColor);
2810
- if (normalizedColors.every(Boolean)) preset.colors = normalizedColors;
2811
- preset.seed = merged.seed;
2812
- preset.speed = merged.speed;
2813
- var computed = getComputedStyle(host);
2814
- if (computed.position === 'static' || computed.position === '') {
2815
- host.style.position = 'relative';
2816
- }
2817
- host.style.isolation = 'isolate';
3426
+ merged.opacity = Number.isFinite(Number(merged.opacity)) ? Math.min(1, Math.max(0, Number(merged.opacity))) : 1;
3427
+ var normalizedColors = Array.isArray(merged.colors) ? merged.colors.map(normalizeColor) : [];
3428
+ if (normalizedColors.length === 4 && normalizedColors.every(Boolean)) preset.colors = normalizedColors;
3429
+ var initialSeed = toFiniteNumber(merged.seed);
3430
+ var initialSpeed = toFiniteNumber(merged.speed);
3431
+ if (initialSeed !== null) preset.seed = initialSeed;
3432
+ if (initialSpeed !== null) preset.speed = initialSpeed;
3433
+ merged.colors = _toConsumableArray(preset.colors);
3434
+ merged.seed = preset.seed;
3435
+ merged.speed = preset.speed;
3436
+ var optionColors = Array.isArray(options.colors) ? options.colors.map(normalizeColor) : [];
3437
+ var colorOverride = optionColors.length === 4 && optionColors.every(Boolean) ? _toConsumableArray(optionColors) : null;
3438
+ var seedOverride = options.seed !== undefined ? toFiniteNumber(options.seed) : null;
3439
+ var speedOverride = options.speed !== undefined ? toFiniteNumber(options.speed) : null;
3440
+ acquireHostStyles(host);
2818
3441
  var layer = document.createElement('div');
2819
3442
  layer.className = 'dlc-color-layer';
2820
3443
  var syncLayerVars = function syncLayerVars() {
@@ -2829,8 +3452,12 @@ function createColorBackground(host) {
2829
3452
  layer.appendChild(canvas);
2830
3453
  host.appendChild(layer);
2831
3454
  var emitter = createEmitter();
2832
- var paused = merged.respectReducedMotion && prefersReducedMotion();
3455
+ var manuallyPaused = merged.paused === true;
3456
+ var reducedPaused = false;
3457
+ var staticMode = merged.static === true;
3458
+ var contextLost = false;
2833
3459
  var disposed = false;
3460
+ var renderOnce = function renderOnce() {};
2834
3461
  var dirty = {
2835
3462
  preset: false,
2836
3463
  seed: false,
@@ -2838,19 +3465,37 @@ function createColorBackground(host) {
2838
3465
  colors: false,
2839
3466
  opacity: false,
2840
3467
  fallbackColor: false,
2841
- mouseColor: false
3468
+ mouseColor: false,
3469
+ quality: false,
3470
+ renderScale: false,
3471
+ paused: false,
3472
+ static: false,
3473
+ fps: false
2842
3474
  };
2843
3475
  var renderer;
2844
3476
  var useWebgl = merged.renderer !== 'canvas2d';
2845
3477
  if (useWebgl) {
2846
3478
  try {
2847
3479
  renderer = new CosmicRenderer(canvas, merged, {
2848
- dprCap: dprCapFor(merged.quality),
3480
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
2849
3481
  mouseColor: merged.mouseColor !== false,
2850
- eventTarget: host
3482
+ eventTarget: host,
3483
+ powerPreference: merged.powerPreference,
3484
+ onContextLost: function onContextLost() {
3485
+ contextLost = true;
3486
+ emitter.emit('contextlost', {});
3487
+ },
3488
+ onContextRestored: function onContextRestored() {
3489
+ contextLost = false;
3490
+ emitter.emit('contextrestored', {});
3491
+ renderOnce();
3492
+ wakeScheduler();
3493
+ }
2851
3494
  });
2852
3495
  } catch (error) {
2853
- renderer = new FallbackRenderer(canvas, merged);
3496
+ renderer = new FallbackRenderer(canvas, merged, {
3497
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale)
3498
+ });
2854
3499
  nextTick(function () {
2855
3500
  if (disposed) return;
2856
3501
  emitter.emit('error', {
@@ -2859,19 +3504,37 @@ function createColorBackground(host) {
2859
3504
  });
2860
3505
  }
2861
3506
  } else {
2862
- renderer = new FallbackRenderer(canvas, merged);
3507
+ renderer = new FallbackRenderer(canvas, merged, {
3508
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale)
3509
+ });
2863
3510
  }
2864
- var resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(function () {
2865
- return renderer.resize();
2866
- }) : null;
2867
- if (resizeObserver) resizeObserver.observe(host);else window.addEventListener('resize', renderer.resize);
2868
- var visibility = createVisibilityGuard(layer);
3511
+ renderer.resize();
2869
3512
  var animationTime = 0;
3513
+ var frameGate = createFrameGate(merged.fps);
3514
+ renderOnce = function renderOnce() {
3515
+ return renderer.draw(animationTime);
3516
+ };
3517
+ var resize = function resize() {
3518
+ renderer.resize();
3519
+ if (manuallyPaused || reducedPaused || staticMode) renderOnce();
3520
+ };
3521
+ var resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(resize) : null;
3522
+ if (resizeObserver) resizeObserver.observe(host);else window.addEventListener('resize', resize);
3523
+ var visibility = createVisibilityGuard(layer, wakeScheduler);
3524
+ var motionPreference = createReducedMotionPreference(merged.respectReducedMotion, function (matches) {
3525
+ reducedPaused = matches;
3526
+ if (matches) renderOnce();else wakeScheduler();
3527
+ });
3528
+ reducedPaused = motionPreference.matches();
3529
+ var isMotionPaused = function isMotionPaused() {
3530
+ return manuallyPaused || reducedPaused || staticMode || contextLost;
3531
+ };
3532
+ renderOnce();
2870
3533
  var unsubscribe = subscribeScheduler(function (delta) {
2871
3534
  animationTime += delta;
2872
- if (visibility.isVisible()) renderer.draw(animationTime);
3535
+ if (frameGate.shouldDraw(delta)) renderer.draw(animationTime);
2873
3536
  }, function () {
2874
- return paused;
3537
+ return isMotionPaused() || !visibility.isVisible();
2875
3538
  });
2876
3539
  nextTick(function () {
2877
3540
  if (disposed) return;
@@ -2890,11 +3553,15 @@ function createColorBackground(host) {
2890
3553
  var next = getPreset('capsule', ref);
2891
3554
  dirty.preset = true;
2892
3555
  Object.assign(preset, next);
3556
+ if (colorOverride) preset.colors = _toConsumableArray(colorOverride);
3557
+ if (seedOverride !== null) preset.seed = seedOverride;
3558
+ if (speedOverride !== null) preset.speed = speedOverride;
2893
3559
  var nextColors = preset.colors.map(normalizeColor);
2894
3560
  if (nextColors.every(Boolean)) preset.colors = nextColors;
2895
3561
  renderer.setPreset(_objectSpread2({}, preset));
2896
3562
  renderer.resize();
2897
3563
  syncLayerVars();
3564
+ if (isMotionPaused()) renderOnce();
2898
3565
  emitter.emit('presetchange', {
2899
3566
  preset: _objectSpread2({}, preset)
2900
3567
  });
@@ -2906,27 +3573,33 @@ function createColorBackground(host) {
2906
3573
  return !color;
2907
3574
  })) return this;
2908
3575
  dirty.colors = true;
3576
+ colorOverride = _toConsumableArray(next);
2909
3577
  preset.colors = next;
2910
3578
  renderer.setPreset(_objectSpread2(_objectSpread2({}, preset), {}, {
2911
3579
  colors: next
2912
3580
  }));
2913
3581
  syncLayerVars();
3582
+ if (isMotionPaused()) renderOnce();
2914
3583
  return this;
2915
3584
  },
2916
3585
  setSeed: function setSeed(seed) {
2917
- var value = Number(seed);
2918
- if (!Number.isFinite(value)) return this;
3586
+ var value = toFiniteNumber(seed);
3587
+ if (value === null) return this;
2919
3588
  dirty.seed = true;
3589
+ seedOverride = value;
2920
3590
  preset.seed = value;
2921
3591
  renderer.setPreset(_objectSpread2({}, preset));
3592
+ if (isMotionPaused()) renderOnce();
2922
3593
  return this;
2923
3594
  },
2924
3595
  setSpeed: function setSpeed(speed) {
2925
- var value = Number(speed);
2926
- if (!Number.isFinite(value)) return this;
3596
+ var value = toFiniteNumber(speed);
3597
+ if (value === null) return this;
2927
3598
  dirty.speed = true;
3599
+ speedOverride = value;
2928
3600
  preset.speed = value;
2929
3601
  renderer.setPreset(_objectSpread2({}, preset));
3602
+ if (isMotionPaused()) renderOnce();
2930
3603
  return this;
2931
3604
  },
2932
3605
  setFallbackColor: function setFallbackColor(value) {
@@ -2942,8 +3615,23 @@ function createColorBackground(host) {
2942
3615
  return this;
2943
3616
  },
2944
3617
  setQuality: function setQuality(quality) {
3618
+ dirty.quality = true;
2945
3619
  merged.quality = quality;
2946
- if (typeof renderer.setDprCap === 'function') renderer.setDprCap(dprCapFor(quality));
3620
+ if (typeof renderer.setDprCap === 'function') {
3621
+ renderer.setDprCap(effectiveDprCap(quality, merged.renderScale));
3622
+ }
3623
+ if (isMotionPaused()) renderOnce();
3624
+ return this;
3625
+ },
3626
+ setRenderScale: function setRenderScale(renderScale) {
3627
+ var value = Number(renderScale);
3628
+ if (!Number.isFinite(value)) return this;
3629
+ dirty.renderScale = true;
3630
+ merged.renderScale = Math.min(1, Math.max(0.25, value));
3631
+ if (typeof renderer.setDprCap === 'function') {
3632
+ renderer.setDprCap(effectiveDprCap(merged.quality, merged.renderScale));
3633
+ }
3634
+ if (isMotionPaused()) renderOnce();
2947
3635
  return this;
2948
3636
  },
2949
3637
  setOpacity: function setOpacity(value) {
@@ -2959,20 +3647,42 @@ function createColorBackground(host) {
2959
3647
  return this;
2960
3648
  },
2961
3649
  pause: function pause() {
2962
- paused = true;
3650
+ dirty.paused = true;
3651
+ manuallyPaused = true;
3652
+ renderOnce();
2963
3653
  return this;
2964
3654
  },
2965
3655
  resume: function resume() {
2966
- paused = false;
3656
+ dirty.paused = true;
3657
+ manuallyPaused = false;
3658
+ wakeScheduler();
3659
+ return this;
3660
+ },
3661
+ setPaused: function setPaused(value) {
3662
+ return value ? this.pause() : this.resume();
3663
+ },
3664
+ setStatic: function setStatic(value) {
3665
+ dirty.static = true;
3666
+ staticMode = value === true;
3667
+ merged.static = staticMode;
3668
+ if (staticMode) renderOnce();else wakeScheduler();
3669
+ return this;
3670
+ },
3671
+ setFps: function setFps(fps) {
3672
+ dirty.fps = true;
3673
+ merged.fps = frameGate.setFps(fps);
3674
+ wakeScheduler();
2967
3675
  return this;
2968
3676
  },
2969
3677
  dispose: function dispose() {
2970
3678
  disposed = true;
2971
3679
  unsubscribe();
2972
3680
  visibility.dispose();
2973
- if (resizeObserver) resizeObserver.disconnect();else window.removeEventListener('resize', renderer.resize);
3681
+ motionPreference.dispose();
3682
+ if (resizeObserver) resizeObserver.disconnect();else window.removeEventListener('resize', resize);
2974
3683
  renderer.dispose();
2975
3684
  layer.remove();
3685
+ releaseHostStyles(host);
2976
3686
  },
2977
3687
  get opacity() {
2978
3688
  return merged.opacity;
@@ -2983,31 +3693,51 @@ function createColorBackground(host) {
2983
3693
  get mouseColor() {
2984
3694
  return merged.mouseColor;
2985
3695
  },
3696
+ get quality() {
3697
+ return merged.quality;
3698
+ },
3699
+ get renderScale() {
3700
+ return merged.renderScale;
3701
+ },
3702
+ get paused() {
3703
+ return manuallyPaused;
3704
+ },
3705
+ get static() {
3706
+ return staticMode;
3707
+ },
3708
+ get fps() {
3709
+ return frameGate.getFps();
3710
+ },
2986
3711
  dirty: dirty
2987
3712
  };
2988
3713
  }
2989
3714
 
2990
- /**
2991
- * Shared Options-API wrapper factory. The only framework difference is the
2992
- * render function: Vue2 passes `h` as an argument, Vue3 needs the imported
2993
- * `h` (see vue2.js / vue3.js). Lifecycle options cover both.
3715
+ /**
3716
+ * Shared Options-API wrapper factory. The only framework difference is the
3717
+ * render function: Vue2 passes `h` as an argument, Vue3 needs the imported
3718
+ * `h` (see vue2.js / vue3.js). Lifecycle options cover both.
2994
3719
  */
2995
3720
 
2996
- var CAPSULE_EVENTS = ['ready', 'error', 'click', 'pointerdown', 'pointerup', 'dblclick', 'pointerenter', 'pointerleave', 'presetchange'];
2997
- var PROGRESS_EVENTS = ['ready', 'error', 'change', 'dragstart', 'dragend', 'presetchange'];
2998
- var COLOR_EVENTS = ['ready', 'error', 'presetchange'];
3721
+ var CAPSULE_EVENTS = ['ready', 'error', 'click', 'pointerdown', 'pointerup', 'dblclick', 'pointerenter', 'pointerleave', 'presetchange', 'contextlost', 'contextrestored'];
3722
+ var PROGRESS_EVENTS = ['ready', 'error', 'change', 'input', 'update:modelValue', 'dragstart', 'dragend', 'presetchange', 'contextlost', 'contextrestored'];
3723
+ var COLOR_EVENTS = ['ready', 'error', 'presetchange', 'contextlost', 'contextrestored'];
2999
3724
  function hookEvents(controller, events, emit) {
3000
3725
  var _iterator = _createForOfIteratorHelper(events),
3001
3726
  _step;
3002
3727
  try {
3003
3728
  var _loop = function _loop() {
3004
3729
  var event = _step.value;
3730
+ if (event === 'input' || event === 'update:modelValue') return 1; // continue
3005
3731
  controller.on(event, function (payload) {
3006
- return emit(event, payload);
3732
+ emit(event, payload);
3733
+ if (event === 'change' && payload && (payload.source === 'drag' || payload.source === 'keyboard')) {
3734
+ if (events.indexOf('input') !== -1) emit('input', payload.value);
3735
+ if (events.indexOf('update:modelValue') !== -1) emit('update:modelValue', payload.value);
3736
+ }
3007
3737
  });
3008
3738
  };
3009
3739
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
3010
- _loop();
3740
+ if (_loop()) continue;
3011
3741
  }
3012
3742
  } catch (err) {
3013
3743
  _iterator.e(err);
@@ -3098,6 +3828,17 @@ function makeWrapper(_ref) {
3098
3828
  if (dirty.opacity) state.opacity = controller.opacity;
3099
3829
  if (dirty.fallbackColor) state.fallbackColor = controller.fallbackColor;
3100
3830
  if (dirty.mouseColor) state.mouseColor = controller.mouseColor;
3831
+ if (dirty.quality) state.quality = controller.quality;
3832
+ if (dirty.renderScale) state.renderScale = controller.renderScale;
3833
+ if (dirty.paused) state.paused = controller.paused;
3834
+ if (dirty.static) state.static = controller.static;
3835
+ if (dirty.fps) state.fps = controller.fps;
3836
+ if (dirty.step) state.step = controller.step;
3837
+ if (dirty.precision) state.precision = controller.precision;
3838
+ if (dirty.formatValue) state.formatValue = controller.formatValue;
3839
+ if (dirty.direction) state.direction = controller.direction;
3840
+ if (dirty.showValue) state.showValue = controller.showValue;
3841
+ if (dirty.valueSuffix) state.valueSuffix = controller.valueSuffix;
3101
3842
  return state;
3102
3843
  }
3103
3844
  };
@@ -3143,7 +3884,7 @@ function makeWrapper(_ref) {
3143
3884
  function makeCapsuleWrapper(createCapsule, render) {
3144
3885
  return makeWrapper({
3145
3886
  name: 'dlc-capsule',
3146
- props: ['preset', 'width', 'height', 'colors', 'seed', 'speed', 'textRatio', 'label', 'mouseColor', 'renderer', 'quality', 'respectReducedMotion', 'cssVars'],
3887
+ props: ['preset', 'width', 'height', 'colors', 'seed', 'speed', 'textRatio', 'label', 'mouseColor', 'renderer', 'quality', 'respectReducedMotion', 'cssVars', 'paused', 'static', 'fps', 'renderScale', 'powerPreference'],
3147
3888
  events: CAPSULE_EVENTS,
3148
3889
  create: createCapsule,
3149
3890
  render: render,
@@ -3172,8 +3913,26 @@ function makeCapsuleWrapper(createCapsule, render) {
3172
3913
  mouseColor: function mouseColor(value) {
3173
3914
  if (this.controller) this.controller.setMouseColor(value);
3174
3915
  },
3916
+ paused: function paused(value) {
3917
+ if (this.controller) this.controller.setPaused(value);
3918
+ },
3919
+ static: function _static(value) {
3920
+ if (this.controller) this.controller.setStatic(value);
3921
+ },
3922
+ fps: function fps(value) {
3923
+ if (value !== undefined && this.controller) this.controller.setFps(value);
3924
+ },
3925
+ renderScale: function renderScale(value) {
3926
+ if (value !== undefined && this.controller) this.controller.setRenderScale(value);
3927
+ },
3175
3928
  renderer: function renderer() {
3176
3929
  this.scheduleRecreate();
3930
+ },
3931
+ powerPreference: function powerPreference() {
3932
+ this.scheduleRecreate();
3933
+ },
3934
+ respectReducedMotion: function respectReducedMotion() {
3935
+ this.scheduleRecreate();
3177
3936
  }
3178
3937
  },
3179
3938
  methods: {
@@ -3216,6 +3975,18 @@ function makeCapsuleWrapper(createCapsule, render) {
3216
3975
  setQuality: function setQuality(quality) {
3217
3976
  return this.controller && this.controller.setQuality(quality);
3218
3977
  },
3978
+ setRenderScale: function setRenderScale(value) {
3979
+ return this.controller && this.controller.setRenderScale(value);
3980
+ },
3981
+ setPaused: function setPaused(value) {
3982
+ return this.controller && this.controller.setPaused(value);
3983
+ },
3984
+ setStatic: function setStatic(value) {
3985
+ return this.controller && this.controller.setStatic(value);
3986
+ },
3987
+ setFps: function setFps(value) {
3988
+ return this.controller && this.controller.setFps(value);
3989
+ },
3219
3990
  getController: function getController() {
3220
3991
  return this.controller;
3221
3992
  }
@@ -3225,13 +3996,16 @@ function makeCapsuleWrapper(createCapsule, render) {
3225
3996
  function makeProgressWrapper(createProgressCapsule, render) {
3226
3997
  return makeWrapper({
3227
3998
  name: 'dlc-progress',
3228
- props: ['preset', 'width', 'height', 'value', 'min', 'max', 'draggable', 'disabled', 'readonly', 'edgeStyle', 'colors', 'textRatio', 'label', 'showValue', 'valueSuffix', 'quality', 'renderer', 'respectReducedMotion', 'cssVars'],
3999
+ props: ['preset', 'width', 'height', 'value', 'modelValue', 'min', 'max', 'step', 'draggable', 'disabled', 'readonly', 'edgeStyle', 'colors', 'textRatio', 'label', 'showValue', 'valueSuffix', 'keyboard', 'direction', 'precision', 'formatValue', 'quality', 'renderer', 'respectReducedMotion', 'cssVars', 'paused', 'static', 'fps', 'renderScale', 'powerPreference'],
3229
4000
  events: PROGRESS_EVENTS,
3230
4001
  create: createProgressCapsule,
3231
4002
  render: render,
3232
4003
  watch: {
3233
4004
  value: function value(_value) {
3234
- if (_value !== undefined && this.controller) this.controller.setValue(_value);
4005
+ if (_value !== undefined && this.modelValue === undefined && this.controller) this.controller.setValue(_value);
4006
+ },
4007
+ modelValue: function modelValue(value) {
4008
+ if (value !== undefined && this.controller) this.controller.setValue(value);
3235
4009
  },
3236
4010
  min: function min(value) {
3237
4011
  if (value !== undefined && this.controller) this.controller.setRange(value, this.controller.max);
@@ -3239,6 +4013,9 @@ function makeProgressWrapper(createProgressCapsule, render) {
3239
4013
  max: function max(value) {
3240
4014
  if (value !== undefined && this.controller) this.controller.setRange(this.controller.min, value);
3241
4015
  },
4016
+ step: function step(value) {
4017
+ if (value !== undefined && this.controller) this.controller.setStep(value);
4018
+ },
3242
4019
  edgeStyle: function edgeStyle(value) {
3243
4020
  if (value && this.controller) this.controller.setEdgeStyle(value);
3244
4021
  },
@@ -3257,6 +4034,27 @@ function makeProgressWrapper(createProgressCapsule, render) {
3257
4034
  showValue: function showValue(value) {
3258
4035
  if (this.controller) this.controller.setShowValue(value);
3259
4036
  },
4037
+ direction: function direction(value) {
4038
+ if (value && this.controller) this.controller.setDirection(value);
4039
+ },
4040
+ precision: function precision(value) {
4041
+ if (value !== undefined && this.controller) this.controller.setPrecision(value);
4042
+ },
4043
+ formatValue: function formatValue(value) {
4044
+ if (this.controller) this.controller.setFormatValue(value);
4045
+ },
4046
+ paused: function paused(value) {
4047
+ if (this.controller) this.controller.setPaused(value);
4048
+ },
4049
+ static: function _static(value) {
4050
+ if (this.controller) this.controller.setStatic(value);
4051
+ },
4052
+ fps: function fps(value) {
4053
+ if (value !== undefined && this.controller) this.controller.setFps(value);
4054
+ },
4055
+ renderScale: function renderScale(value) {
4056
+ if (value !== undefined && this.controller) this.controller.setRenderScale(value);
4057
+ },
3260
4058
  disabled: function disabled() {
3261
4059
  this.scheduleRecreate();
3262
4060
  },
@@ -3268,6 +4066,15 @@ function makeProgressWrapper(createProgressCapsule, render) {
3268
4066
  },
3269
4067
  renderer: function renderer() {
3270
4068
  this.scheduleRecreate();
4069
+ },
4070
+ keyboard: function keyboard() {
4071
+ this.scheduleRecreate();
4072
+ },
4073
+ powerPreference: function powerPreference() {
4074
+ this.scheduleRecreate();
4075
+ },
4076
+ respectReducedMotion: function respectReducedMotion() {
4077
+ this.scheduleRecreate();
3271
4078
  }
3272
4079
  },
3273
4080
  methods: {
@@ -3280,6 +4087,9 @@ function makeProgressWrapper(createProgressCapsule, render) {
3280
4087
  setRange: function setRange(min, max) {
3281
4088
  return this.controller && this.controller.setRange(min, max);
3282
4089
  },
4090
+ setStep: function setStep(step) {
4091
+ return this.controller && this.controller.setStep(step);
4092
+ },
3283
4093
  randomize: function randomize() {
3284
4094
  return this.controller && this.controller.randomize();
3285
4095
  },
@@ -3319,9 +4129,30 @@ function makeProgressWrapper(createProgressCapsule, render) {
3319
4129
  setShowValue: function setShowValue(show) {
3320
4130
  return this.controller && this.controller.setShowValue(show);
3321
4131
  },
4132
+ setDirection: function setDirection(direction) {
4133
+ return this.controller && this.controller.setDirection(direction);
4134
+ },
4135
+ setPrecision: function setPrecision(precision) {
4136
+ return this.controller && this.controller.setPrecision(precision);
4137
+ },
4138
+ setFormatValue: function setFormatValue(formatter) {
4139
+ return this.controller && this.controller.setFormatValue(formatter);
4140
+ },
3322
4141
  setQuality: function setQuality(quality) {
3323
4142
  return this.controller && this.controller.setQuality(quality);
3324
4143
  },
4144
+ setRenderScale: function setRenderScale(value) {
4145
+ return this.controller && this.controller.setRenderScale(value);
4146
+ },
4147
+ setPaused: function setPaused(value) {
4148
+ return this.controller && this.controller.setPaused(value);
4149
+ },
4150
+ setStatic: function setStatic(value) {
4151
+ return this.controller && this.controller.setStatic(value);
4152
+ },
4153
+ setFps: function setFps(value) {
4154
+ return this.controller && this.controller.setFps(value);
4155
+ },
3325
4156
  getController: function getController() {
3326
4157
  return this.controller;
3327
4158
  }
@@ -3331,7 +4162,7 @@ function makeProgressWrapper(createProgressCapsule, render) {
3331
4162
  function makeColorWrapper(createColorBackground, render) {
3332
4163
  return makeWrapper({
3333
4164
  name: 'dlc-color',
3334
- props: ['preset', 'colors', 'seed', 'speed', 'quality', 'renderer', 'mouseColor', 'respectReducedMotion', 'opacity', 'fallbackColor'],
4165
+ props: ['preset', 'colors', 'seed', 'speed', 'quality', 'renderer', 'mouseColor', 'respectReducedMotion', 'opacity', 'fallbackColor', 'paused', 'static', 'fps', 'renderScale', 'powerPreference'],
3335
4166
  events: COLOR_EVENTS,
3336
4167
  create: createColorBackground,
3337
4168
  render: render,
@@ -3353,11 +4184,26 @@ function makeColorWrapper(createColorBackground, render) {
3353
4184
  mouseColor: function mouseColor(value) {
3354
4185
  if (this.controller) this.controller.setMouseColor(value);
3355
4186
  },
4187
+ paused: function paused(value) {
4188
+ if (this.controller) this.controller.setPaused(value);
4189
+ },
4190
+ static: function _static(value) {
4191
+ if (this.controller) this.controller.setStatic(value);
4192
+ },
4193
+ fps: function fps(value) {
4194
+ if (value !== undefined && this.controller) this.controller.setFps(value);
4195
+ },
4196
+ renderScale: function renderScale(value) {
4197
+ if (value !== undefined && this.controller) this.controller.setRenderScale(value);
4198
+ },
3356
4199
  renderer: function renderer() {
3357
4200
  this.scheduleRecreate();
3358
4201
  },
3359
4202
  respectReducedMotion: function respectReducedMotion() {
3360
4203
  this.scheduleRecreate();
4204
+ },
4205
+ powerPreference: function powerPreference() {
4206
+ this.scheduleRecreate();
3361
4207
  }
3362
4208
  },
3363
4209
  methods: {
@@ -3376,6 +4222,9 @@ function makeColorWrapper(createColorBackground, render) {
3376
4222
  setQuality: function setQuality(quality) {
3377
4223
  return this.controller && this.controller.setQuality(quality);
3378
4224
  },
4225
+ setRenderScale: function setRenderScale(value) {
4226
+ return this.controller && this.controller.setRenderScale(value);
4227
+ },
3379
4228
  setOpacity: function setOpacity(value) {
3380
4229
  return this.controller && this.controller.setOpacity(value);
3381
4230
  },
@@ -3394,6 +4243,15 @@ function makeColorWrapper(createColorBackground, render) {
3394
4243
  resume: function resume() {
3395
4244
  return this.controller && this.controller.resume();
3396
4245
  },
4246
+ setPaused: function setPaused(value) {
4247
+ return this.controller && this.controller.setPaused(value);
4248
+ },
4249
+ setStatic: function setStatic(value) {
4250
+ return this.controller && this.controller.setStatic(value);
4251
+ },
4252
+ setFps: function setFps(value) {
4253
+ return this.controller && this.controller.setFps(value);
4254
+ },
3397
4255
  getController: function getController() {
3398
4256
  return this.controller;
3399
4257
  }