@5even7/dlc-ui 0.2.11 → 0.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.umd.js CHANGED
@@ -189,9 +189,9 @@
189
189
  }
190
190
  }
191
191
 
192
- /**
193
- * Tiny event emitter. Accepts any event name so the API stays open for
194
- * future events (hover, click, custom) without breaking changes.
192
+ /**
193
+ * Tiny event emitter. Accepts any event name so the API stays open for
194
+ * future events (hover, click, custom) without breaking changes.
195
195
  */
196
196
  function createEmitter() {
197
197
  // Plain object storage (no Map/Set) so the legacy build has no API
@@ -241,9 +241,9 @@
241
241
 
242
242
  var UNIT_PATTERN = /^[\d.]+(?:px|%|vw|vh|vmin|vmax|em|rem)$/;
243
243
 
244
- /**
245
- * Normalize a size option to a CSS length string.
246
- * Accepts numbers (treated as px) or strings with px/%, vw/vh/vmin/vmax, em/rem.
244
+ /**
245
+ * Normalize a size option to a CSS length string.
246
+ * Accepts numbers (treated as px) or strings with px/%, vw/vh/vmin/vmax, em/rem.
247
247
  */
248
248
  function parseSize(value) {
249
249
  if (typeof value === 'number') {
@@ -282,11 +282,17 @@
282
282
  var tier = QUALITY_TIERS[quality] || QUALITY_TIERS.medium;
283
283
  return tier.dpr;
284
284
  }
285
+ function effectiveDprCap(quality) {
286
+ var renderScale = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
287
+ var scale = Number(renderScale);
288
+ var normalizedScale = Number.isFinite(scale) ? Math.min(1, Math.max(0.25, scale)) : 1;
289
+ return Math.max(0.5, dprCapFor(quality) * normalizedScale);
290
+ }
285
291
 
286
- /**
287
- * Merge defaults < preset < user. Unknown user keys are preserved so the
288
- * component API can grow (new props, cssVars, callbacks) without a breaking
289
- * change.
292
+ /**
293
+ * Merge defaults < preset < user. Unknown user keys are preserved so the
294
+ * component API can grow (new props, cssVars, callbacks) without a breaking
295
+ * change.
290
296
  */
291
297
  function normalizeOptions(defaults, preset, user) {
292
298
  // undefined means "not provided" (e.g. Vue $props with unset props):
@@ -304,9 +310,9 @@
304
310
  return _objectSpread2(_objectSpread2(_objectSpread2({}, defaults), presetOptions), userOptions);
305
311
  }
306
312
 
307
- /**
308
- * 公共色板:NC-01~NC-06 的四色组(底色 / 主色 / 辅色 / 高光色)。
309
- * Capsule 预置与 dlc-color 等组件共用,新增色板只改这里。
313
+ /**
314
+ * 公共色板:NC-01~NC-06 的四色组(底色 / 主色 / 辅色 / 高光色)。
315
+ * Capsule 预置与 dlc-color 等组件共用,新增色板只改这里。
310
316
  */
311
317
  var PALETTES$1 = {
312
318
  original: ['#FFF3EA', '#F5B27A', '#F67BC6', '#A978E8'],
@@ -317,9 +323,9 @@
317
323
  plus: ['#FFF0E6', '#F6C26B', '#F98A64', '#E86D74']
318
324
  };
319
325
 
320
- /**
321
- * Capsule 预置:仅星云材质 NC-01~NC-06(NC-07~09 aurora 已移除)。
322
- * 颜色引用公共色板,seed/speed 决定形态与流速。
326
+ /**
327
+ * Capsule 预置:仅星云材质 NC-01~NC-06(NC-07~09 aurora 已移除)。
328
+ * 颜色引用公共色板,seed/speed 决定形态与流速。
323
329
  */
324
330
  var CAPSULE_PRESETS = [{
325
331
  id: 'original',
@@ -438,6 +444,11 @@
438
444
  height: 160,
439
445
  quality: 'auto',
440
446
  renderer: 'auto',
447
+ renderScale: 1,
448
+ powerPreference: 'high-performance',
449
+ fps: 60,
450
+ paused: false,
451
+ static: false,
441
452
  respectReducedMotion: true,
442
453
  mouseColor: true,
443
454
  textRatio: 39
@@ -447,13 +458,22 @@
447
458
  height: 104,
448
459
  min: 0,
449
460
  max: 100,
461
+ step: 'any',
450
462
  draggable: true,
463
+ keyboard: true,
451
464
  disabled: false,
452
465
  readonly: false,
466
+ direction: 'ltr',
467
+ precision: 0,
453
468
  valueSuffix: '%',
454
469
  edgeStyle: 'flow',
455
470
  quality: 'auto',
456
471
  renderer: 'auto',
472
+ renderScale: 1,
473
+ powerPreference: 'high-performance',
474
+ fps: 60,
475
+ paused: false,
476
+ static: false,
457
477
  textRatio: 54,
458
478
  showValue: true,
459
479
  respectReducedMotion: true
@@ -743,7 +763,7 @@
743
763
  alpha: false,
744
764
  antialias: false,
745
765
  depth: false,
746
- powerPreference: 'high-performance',
766
+ powerPreference: this.options.powerPreference || 'high-performance',
747
767
  preserveDrawingBuffer: false
748
768
  });
749
769
  if (!this.gl) throw new Error('WebGL2 is not available');
@@ -754,16 +774,19 @@
754
774
  this.motion = 0;
755
775
  this.motionTarget = 0;
756
776
  this.timeOffset = preset.seed * 0.73;
777
+ this.colors = preset.colors.map(hexToRgb01$1);
757
778
  this.visible = true;
758
779
  this.disposed = false;
759
780
  _assertClassBrand(_CosmicRenderer_brand, this, _setupGeometry).call(this);
760
781
  _assertClassBrand(_CosmicRenderer_brand, this, _bindEvents).call(this);
782
+ _assertClassBrand(_CosmicRenderer_brand, this, _bindContextEvents).call(this);
761
783
  this.resize();
762
784
  }
763
785
  return _createClass(CosmicRenderer, [{
764
786
  key: "setPreset",
765
787
  value: function setPreset(preset) {
766
788
  this.preset = _objectSpread2({}, preset);
789
+ this.colors = preset.colors.map(hexToRgb01$1);
767
790
  this.timeOffset = preset.seed * 0.73;
768
791
  }
769
792
  }, {
@@ -808,15 +831,14 @@
808
831
  if (this.canvas.width !== width || this.canvas.height !== height) {
809
832
  this.canvas.width = width;
810
833
  this.canvas.height = height;
811
- this.gl.viewport(0, 0, width, height);
812
834
  }
835
+ this.gl.viewport(0, 0, width, height);
813
836
  }
814
837
  }, {
815
838
  key: "draw",
816
839
  value: function draw(elapsedSeconds) {
817
840
  var paused = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
818
841
  if (this.disposed || !this.visible) return;
819
- this.resize();
820
842
  var gl = this.gl;
821
843
  this.pointer[0] += (this.pointerTarget[0] - this.pointer[0]) * 0.08;
822
844
  this.pointer[1] += (this.pointerTarget[1] - this.pointer[1]) * 0.08;
@@ -825,16 +847,15 @@
825
847
  gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
826
848
  gl.enableVertexAttribArray(this.locations.position);
827
849
  gl.vertexAttribPointer(this.locations.position, 2, gl.FLOAT, false, 0, 0);
828
- var colors = this.preset.colors.map(hexToRgb01$1);
829
850
  gl.uniform2f(this.locations.resolution, this.canvas.width, this.canvas.height);
830
851
  gl.uniform1f(this.locations.time, this.timeOffset + (paused ? 0 : elapsedSeconds * this.preset.speed));
831
852
  gl.uniform1f(this.locations.seed, this.preset.seed);
832
853
  gl.uniform1f(this.locations.motion, this.motion);
833
854
  gl.uniform2f(this.locations.pointer, this.pointer[0], this.pointer[1]);
834
- gl.uniform3fv(this.locations.colorA, colors[0]);
835
- gl.uniform3fv(this.locations.colorB, colors[1]);
836
- gl.uniform3fv(this.locations.colorC, colors[2]);
837
- gl.uniform3fv(this.locations.colorD, colors[3]);
855
+ gl.uniform3fv(this.locations.colorA, this.colors[0]);
856
+ gl.uniform3fv(this.locations.colorB, this.colors[1]);
857
+ gl.uniform3fv(this.locations.colorC, this.colors[2]);
858
+ gl.uniform3fv(this.locations.colorD, this.colors[3]);
838
859
  gl.drawArrays(gl.TRIANGLES, 0, 3);
839
860
  }
840
861
  }, {
@@ -845,6 +866,8 @@
845
866
  target.removeEventListener('pointermove', this.onPointerMove);
846
867
  target.removeEventListener('pointerdown', this.onPointerMove);
847
868
  target.removeEventListener('pointerleave', this.onPointerLeave);
869
+ this.canvas.removeEventListener('webglcontextlost', this.onContextLost, false);
870
+ this.canvas.removeEventListener('webglcontextrestored', this.onContextRestored, false);
848
871
  this.gl.deleteBuffer(this.buffer);
849
872
  this.gl.deleteProgram(this.program);
850
873
  var lose = this.gl.getExtension('WEBGL_lose_context');
@@ -901,6 +924,26 @@
901
924
  passive: true
902
925
  });
903
926
  }
927
+ function _bindContextEvents() {
928
+ var _this3 = this;
929
+ this.onContextLost = function (event) {
930
+ event.preventDefault();
931
+ if (_this3.disposed) return;
932
+ _this3.visible = false;
933
+ if (typeof _this3.options.onContextLost === 'function') _this3.options.onContextLost(event);
934
+ };
935
+ this.onContextRestored = function () {
936
+ if (_this3.disposed) return;
937
+ _this3.program = createProgram$1(_this3.gl);
938
+ _this3.locations = _assertClassBrand(_CosmicRenderer_brand, _this3, _getLocations).call(_this3);
939
+ _assertClassBrand(_CosmicRenderer_brand, _this3, _setupGeometry).call(_this3);
940
+ _this3.visible = true;
941
+ _this3.resize();
942
+ if (typeof _this3.options.onContextRestored === 'function') _this3.options.onContextRestored();
943
+ };
944
+ this.canvas.addEventListener('webglcontextlost', this.onContextLost, false);
945
+ this.canvas.addEventListener('webglcontextrestored', this.onContextRestored, false);
946
+ }
904
947
 
905
948
  function rgb(color) {
906
949
  var alpha = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
@@ -915,18 +958,20 @@
915
958
  }
916
959
  var FallbackRenderer = /*#__PURE__*/function () {
917
960
  function FallbackRenderer(canvas, preset) {
961
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
918
962
  _classCallCheck(this, FallbackRenderer);
919
963
  this.canvas = canvas;
920
964
  this.preset = preset;
921
965
  this.context = canvas.getContext('2d');
922
966
  this.visible = true;
923
967
  this.timePhase = 0;
968
+ this.dprCap = options.dprCap || 1.5;
924
969
  }
925
970
  return _createClass(FallbackRenderer, [{
926
971
  key: "resize",
927
972
  value: function resize() {
928
973
  var rect = this.canvas.getBoundingClientRect();
929
- var dpr = Math.min(window.devicePixelRatio || 1, 1.5);
974
+ var dpr = Math.min(window.devicePixelRatio || 1, this.dprCap);
930
975
  var width = Math.max(2, Math.round(rect.width * dpr));
931
976
  var height = Math.max(2, Math.round(rect.height * dpr));
932
977
  if (this.canvas.width !== width || this.canvas.height !== height) {
@@ -967,7 +1012,6 @@
967
1012
  key: "draw",
968
1013
  value: function draw(time) {
969
1014
  if (!this.visible) return;
970
- this.resize();
971
1015
  this.drawNebula(time);
972
1016
  }
973
1017
  }, {
@@ -975,6 +1019,12 @@
975
1019
  value: function setPreset(preset) {
976
1020
  this.preset = preset;
977
1021
  }
1022
+ }, {
1023
+ key: "setDprCap",
1024
+ value: function setDprCap(cap) {
1025
+ this.dprCap = cap;
1026
+ this.resize();
1027
+ }
978
1028
  }, {
979
1029
  key: "randomize",
980
1030
  value: function randomize() {
@@ -992,10 +1042,10 @@
992
1042
  }]);
993
1043
  }();
994
1044
 
995
- /**
996
- * Document-level shared rAF scheduler. Every component instance subscribes
997
- * its own frame callback; the whole page runs ONE animation loop (like the
998
- * original demo), which avoids jank from many competing rAF loops.
1045
+ /**
1046
+ * Document-level shared rAF scheduler. Every component instance subscribes
1047
+ * its own frame callback; the whole page runs ONE animation loop (like the
1048
+ * original demo), which avoids jank from many competing rAF loops.
999
1049
  */
1000
1050
  var subscribers = [];
1001
1051
  var running = false;
@@ -1004,11 +1054,7 @@
1004
1054
  var last = 0;
1005
1055
  function tick(now) {
1006
1056
  if (!running) return;
1007
- var delta = last ? Math.min((now - last) / 1000, 0.05) : 0;
1008
- last = now;
1009
- // Schedule the next frame BEFORE running callbacks so one throwing
1010
- // subscriber can never kill the whole animation loop.
1011
- rafId = requestAnimationFrame(tick);
1057
+ var activeItems = [];
1012
1058
  if (!globallyPaused) {
1013
1059
  var _iterator = _createForOfIteratorHelper(subscribers.slice()),
1014
1060
  _step;
@@ -1016,10 +1062,8 @@
1016
1062
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
1017
1063
  var item = _step.value;
1018
1064
  try {
1019
- if (!item.isPaused()) item.onFrame(delta, now);
1020
- } catch (error) {
1021
- console.warn('[dlc-ui] frame error:', error);
1022
- }
1065
+ if (!item.isPaused()) activeItems.push(item);
1066
+ } catch (_unused) {}
1023
1067
  }
1024
1068
  } catch (err) {
1025
1069
  _iterator.e(err);
@@ -1027,6 +1071,25 @@
1027
1071
  _iterator.f();
1028
1072
  }
1029
1073
  }
1074
+ if (activeItems.length === 0) {
1075
+ running = false;
1076
+ rafId = 0;
1077
+ last = 0;
1078
+ return;
1079
+ }
1080
+ var delta = last ? Math.min((now - last) / 1000, 0.05) : 0;
1081
+ last = now;
1082
+ // Schedule the next frame BEFORE running callbacks so one throwing
1083
+ // subscriber can never kill the whole animation loop.
1084
+ rafId = requestAnimationFrame(tick);
1085
+ for (var _i = 0, _activeItems = activeItems; _i < _activeItems.length; _i++) {
1086
+ var _item = _activeItems[_i];
1087
+ try {
1088
+ _item.onFrame(delta, now);
1089
+ } catch (error) {
1090
+ console.warn('[dlc-ui] frame error:', error);
1091
+ }
1092
+ }
1030
1093
  if (subscribers.length === 0) {
1031
1094
  cancelAnimationFrame(rafId);
1032
1095
  running = false;
@@ -1044,6 +1107,10 @@
1044
1107
  }
1045
1108
  function resumeAll() {
1046
1109
  globallyPaused = false;
1110
+ start();
1111
+ }
1112
+ function wakeScheduler() {
1113
+ start();
1047
1114
  }
1048
1115
  function subscribeScheduler(onFrame, isPaused) {
1049
1116
  var item = {
@@ -1063,11 +1130,12 @@
1063
1130
  };
1064
1131
  }
1065
1132
 
1066
- /**
1067
- * Gates drawing on "element intersects viewport AND the page tab is visible".
1068
- * Falls back to always-visible when IntersectionObserver is unavailable.
1133
+ /**
1134
+ * Gates drawing on "element intersects viewport AND the page tab is visible".
1135
+ * Falls back to always-visible when IntersectionObserver is unavailable.
1069
1136
  */
1070
1137
  function createVisibilityGuard(element) {
1138
+ var onChange = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
1071
1139
  var intersecting = true;
1072
1140
  var pageVisible = typeof document === 'undefined' || !document.hidden;
1073
1141
  var disposed = false;
@@ -1077,6 +1145,7 @@
1077
1145
  intersecting = entries.some(function (entry) {
1078
1146
  return entry.isIntersecting;
1079
1147
  });
1148
+ if (typeof onChange === 'function') onChange();
1080
1149
  }, {
1081
1150
  rootMargin: '180px'
1082
1151
  });
@@ -1084,6 +1153,7 @@
1084
1153
  }
1085
1154
  var onVisibilityChange = function onVisibilityChange() {
1086
1155
  pageVisible = typeof document !== 'undefined' && !document.hidden;
1156
+ if (typeof onChange === 'function') onChange();
1087
1157
  };
1088
1158
  if (typeof document !== 'undefined') {
1089
1159
  document.addEventListener('visibilitychange', onVisibilityChange);
@@ -1119,18 +1189,69 @@
1119
1189
  }
1120
1190
  }
1121
1191
 
1122
- function prefersReducedMotion$2() {
1123
- return typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches;
1192
+ function normalizeFps(value) {
1193
+ var fallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 60;
1194
+ var fps = Number(value);
1195
+ if (!Number.isFinite(fps)) return fallback;
1196
+ return Math.min(60, Math.max(1, fps));
1197
+ }
1198
+ function createFrameGate() {
1199
+ var initialFps = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 60;
1200
+ var fps = normalizeFps(initialFps);
1201
+ var elapsed = 0;
1202
+ return {
1203
+ shouldDraw: function shouldDraw(delta) {
1204
+ elapsed += delta;
1205
+ var interval = 1 / fps;
1206
+ if (elapsed + 0.0001 < interval) return false;
1207
+ elapsed %= interval;
1208
+ return true;
1209
+ },
1210
+ setFps: function setFps(value) {
1211
+ fps = normalizeFps(value, fps);
1212
+ elapsed = 0;
1213
+ return fps;
1214
+ },
1215
+ getFps: function getFps() {
1216
+ return fps;
1217
+ }
1218
+ };
1219
+ }
1220
+ function createReducedMotionPreference(enabled, onChange) {
1221
+ var query = enabled && typeof matchMedia !== 'undefined' ? matchMedia('(prefers-reduced-motion: reduce)') : null;
1222
+ var notify = function notify() {
1223
+ if (typeof onChange === 'function') onChange(Boolean(query && query.matches));
1224
+ };
1225
+ if (query) {
1226
+ if (typeof query.addEventListener === 'function') query.addEventListener('change', notify);else if (typeof query.addListener === 'function') query.addListener(notify);
1227
+ }
1228
+ return {
1229
+ matches: function matches() {
1230
+ return Boolean(query && query.matches);
1231
+ },
1232
+ dispose: function dispose() {
1233
+ if (!query) return;
1234
+ if (typeof query.removeEventListener === 'function') query.removeEventListener('change', notify);else if (typeof query.removeListener === 'function') query.removeListener(notify);
1235
+ }
1236
+ };
1124
1237
  }
1125
1238
 
1126
- /**
1239
+ function toFiniteNumber(value) {
1240
+ if (value == null || typeof value === 'boolean') return null;
1241
+ if (typeof value === 'string' && value.trim() === '') return null;
1242
+ var number = Number(value);
1243
+ return Number.isFinite(number) ? number : null;
1244
+ }
1245
+
1246
+ /**
1127
1247
  * Mount a cosmic (nebula) capsule into `container`.
1128
1248
  *
1129
1249
  * Options: preset (literary name), width, height (number=px or string with
1130
1250
  * px/%/vw/vh/em/rem), colors, seed, speed, textRatio (0-100, text region
1131
1251
  * width in percent), text (HTML string or DOM nodes for the text slot),
1132
1252
  * colorContent (HTML string or DOM nodes for the color slot), quality,
1133
- * renderer, respectReducedMotion, mouseColor, cssVars.
1253
+ * renderer, quality, renderScale, powerPreference, fps, paused/static,
1254
+ * respectReducedMotion, mouseColor, cssVars.
1134
1255
  */
1135
1256
  function createCapsule(container) {
1136
1257
  var _options$preset;
@@ -1140,10 +1261,19 @@
1140
1261
  }
1141
1262
  var preset = _objectSpread2({}, getPreset('capsule', (_options$preset = options.preset) !== null && _options$preset !== void 0 ? _options$preset : '初光'));
1142
1263
  var merged = normalizeOptions(DEFAULTS.capsule, preset, options);
1143
- var normalizedColors = merged.colors.map(normalizeColor);
1144
- if (normalizedColors.every(Boolean)) preset.colors = normalizedColors;
1145
- preset.seed = merged.seed;
1146
- preset.speed = merged.speed;
1264
+ var normalizedColors = Array.isArray(merged.colors) ? merged.colors.map(normalizeColor) : [];
1265
+ if (normalizedColors.length === 4 && normalizedColors.every(Boolean)) preset.colors = normalizedColors;
1266
+ var initialSeed = toFiniteNumber(merged.seed);
1267
+ var initialSpeed = toFiniteNumber(merged.speed);
1268
+ if (initialSeed !== null) preset.seed = initialSeed;
1269
+ if (initialSpeed !== null) preset.speed = initialSpeed;
1270
+ merged.colors = _toConsumableArray(preset.colors);
1271
+ merged.seed = preset.seed;
1272
+ merged.speed = preset.speed;
1273
+ var optionColors = Array.isArray(options.colors) ? options.colors.map(normalizeColor) : [];
1274
+ var colorOverride = optionColors.length === 4 && optionColors.every(Boolean) ? _toConsumableArray(optionColors) : null;
1275
+ var seedOverride = options.seed !== undefined ? toFiniteNumber(options.seed) : null;
1276
+ var speedOverride = options.speed !== undefined ? toFiniteNumber(options.speed) : null;
1147
1277
  var customLabel = typeof options.label === 'string' && options.label ? options.label : null;
1148
1278
  var root = document.createElement('div');
1149
1279
  root.className = 'hj-capsule-root hj-capsule-cosmic';
@@ -1195,25 +1325,49 @@
1195
1325
  root.appendChild(canvas);
1196
1326
  container.appendChild(root);
1197
1327
  var emitter = createEmitter();
1198
- var paused = merged.respectReducedMotion && prefersReducedMotion$2();
1328
+ var manuallyPaused = merged.paused === true;
1329
+ var reducedPaused = false;
1330
+ var staticMode = merged.static === true;
1331
+ var contextLost = false;
1199
1332
  var disposed = false;
1333
+ var renderOnce = function renderOnce() {};
1200
1334
  var dirty = {
1201
1335
  preset: false,
1202
1336
  seed: false,
1203
1337
  speed: false,
1204
1338
  colors: false,
1205
1339
  textRatio: false,
1206
- cssVars: false
1340
+ cssVars: false,
1341
+ mouseColor: false,
1342
+ quality: false,
1343
+ renderScale: false,
1344
+ paused: false,
1345
+ static: false,
1346
+ fps: false
1207
1347
  };
1208
1348
  var renderer;
1209
1349
  var useWebgl = merged.renderer !== 'canvas2d';
1210
1350
  if (useWebgl) {
1211
1351
  try {
1212
1352
  renderer = new CosmicRenderer(canvas, merged, {
1213
- dprCap: dprCapFor(merged.quality)
1353
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
1354
+ mouseColor: merged.mouseColor !== false,
1355
+ powerPreference: merged.powerPreference,
1356
+ onContextLost: function onContextLost() {
1357
+ contextLost = true;
1358
+ emitter.emit('contextlost', {});
1359
+ },
1360
+ onContextRestored: function onContextRestored() {
1361
+ contextLost = false;
1362
+ emitter.emit('contextrestored', {});
1363
+ renderOnce();
1364
+ wakeScheduler();
1365
+ }
1214
1366
  });
1215
1367
  } catch (error) {
1216
- renderer = new FallbackRenderer(canvas, merged);
1368
+ renderer = new FallbackRenderer(canvas, merged, {
1369
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale)
1370
+ });
1217
1371
  nextTick(function () {
1218
1372
  if (disposed) return;
1219
1373
  emitter.emit('error', {
@@ -1222,8 +1376,15 @@
1222
1376
  });
1223
1377
  }
1224
1378
  } else {
1225
- renderer = new FallbackRenderer(canvas, merged);
1379
+ renderer = new FallbackRenderer(canvas, merged, {
1380
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale)
1381
+ });
1226
1382
  }
1383
+ var animationTime = 0;
1384
+ var frameGate = createFrameGate(merged.fps);
1385
+ renderOnce = function renderOnce() {
1386
+ return renderer.draw(animationTime);
1387
+ };
1227
1388
  var applySize = function applySize() {
1228
1389
  root.style.width = parseSize(merged.width);
1229
1390
  root.style.height = parseSize(merged.height);
@@ -1241,11 +1402,21 @@
1241
1402
  renderer.resize();
1242
1403
  };
1243
1404
  applySize();
1244
- var resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(function () {
1245
- return renderer.resize();
1246
- }) : null;
1247
- if (resizeObserver) resizeObserver.observe(root);else window.addEventListener('resize', renderer.resize);
1248
- var visibility = createVisibilityGuard(root);
1405
+ var resize = function resize() {
1406
+ renderer.resize();
1407
+ if (manuallyPaused || reducedPaused || staticMode) renderOnce();
1408
+ };
1409
+ var resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(resize) : null;
1410
+ if (resizeObserver) resizeObserver.observe(root);else window.addEventListener('resize', resize);
1411
+ var visibility = createVisibilityGuard(root, wakeScheduler);
1412
+ var motionPreference = createReducedMotionPreference(merged.respectReducedMotion, function (matches) {
1413
+ reducedPaused = matches;
1414
+ if (matches) renderOnce();else wakeScheduler();
1415
+ });
1416
+ reducedPaused = motionPreference.matches();
1417
+ var isMotionPaused = function isMotionPaused() {
1418
+ return manuallyPaused || reducedPaused || staticMode || contextLost;
1419
+ };
1249
1420
  root.addEventListener('pointerenter', function () {
1250
1421
  return emitter.emit('pointerenter', {
1251
1422
  preset: _objectSpread2({}, preset)
@@ -1280,12 +1451,12 @@
1280
1451
  preset: _objectSpread2({}, preset)
1281
1452
  });
1282
1453
  });
1283
- var animationTime = 0;
1454
+ renderOnce();
1284
1455
  var unsubscribe = subscribeScheduler(function (delta) {
1285
1456
  animationTime += delta;
1286
- if (visibility.isVisible()) renderer.draw(animationTime);
1457
+ if (frameGate.shouldDraw(delta)) renderer.draw(animationTime);
1287
1458
  }, function () {
1288
- return paused;
1459
+ return isMotionPaused() || !visibility.isVisible();
1289
1460
  });
1290
1461
  nextTick(function () {
1291
1462
  if (disposed) return;
@@ -1308,11 +1479,15 @@
1308
1479
  var next = getPreset('capsule', ref);
1309
1480
  dirty.preset = true;
1310
1481
  Object.assign(preset, next);
1482
+ if (colorOverride) preset.colors = _toConsumableArray(colorOverride);
1483
+ if (seedOverride !== null) preset.seed = seedOverride;
1484
+ if (speedOverride !== null) preset.speed = speedOverride;
1311
1485
  var nextColors = preset.colors.map(normalizeColor);
1312
1486
  if (nextColors.every(Boolean)) preset.colors = nextColors;
1313
1487
  renderer.setPreset(_objectSpread2({}, preset));
1314
1488
  syncTheme();
1315
1489
  renderer.resize();
1490
+ if (isMotionPaused()) renderOnce();
1316
1491
  emitter.emit('presetchange', {
1317
1492
  preset: _objectSpread2({}, next)
1318
1493
  });
@@ -1324,26 +1499,32 @@
1324
1499
  return !color;
1325
1500
  })) return this;
1326
1501
  dirty.colors = true;
1502
+ colorOverride = _toConsumableArray(next);
1327
1503
  preset.colors = next;
1328
1504
  renderer.setPreset(_objectSpread2(_objectSpread2({}, preset), {}, {
1329
1505
  colors: next
1330
1506
  }));
1507
+ if (isMotionPaused()) renderOnce();
1331
1508
  return this;
1332
1509
  },
1333
1510
  setSeed: function setSeed(seed) {
1334
- var value = Number(seed);
1335
- if (!Number.isFinite(value)) return this;
1511
+ var value = toFiniteNumber(seed);
1512
+ if (value === null) return this;
1336
1513
  dirty.seed = true;
1514
+ seedOverride = value;
1337
1515
  preset.seed = value;
1338
1516
  renderer.setPreset(_objectSpread2({}, preset));
1517
+ if (isMotionPaused()) renderOnce();
1339
1518
  return this;
1340
1519
  },
1341
1520
  setSpeed: function setSpeed(speed) {
1342
- var value = Number(speed);
1343
- if (!Number.isFinite(value)) return this;
1521
+ var value = toFiniteNumber(speed);
1522
+ if (value === null) return this;
1344
1523
  dirty.speed = true;
1524
+ speedOverride = value;
1345
1525
  preset.speed = value;
1346
1526
  renderer.setPreset(_objectSpread2({}, preset));
1527
+ if (isMotionPaused()) renderOnce();
1347
1528
  return this;
1348
1529
  },
1349
1530
  setText: function setText(content) {
@@ -1379,6 +1560,7 @@
1379
1560
  return this;
1380
1561
  },
1381
1562
  setMouseColor: function setMouseColor(value) {
1563
+ dirty.mouseColor = true;
1382
1564
  merged.mouseColor = value !== false;
1383
1565
  if (typeof renderer.setMouseColor === 'function') renderer.setMouseColor(merged.mouseColor);
1384
1566
  return this;
@@ -1393,6 +1575,7 @@
1393
1575
  merged.height = height;
1394
1576
  }
1395
1577
  applySize();
1578
+ if (isMotionPaused()) renderOnce();
1396
1579
  return this;
1397
1580
  },
1398
1581
  randomize: function randomize() {
@@ -1400,23 +1583,59 @@
1400
1583
  return this;
1401
1584
  },
1402
1585
  pause: function pause() {
1403
- paused = true;
1586
+ dirty.paused = true;
1587
+ manuallyPaused = true;
1588
+ renderOnce();
1404
1589
  return this;
1405
1590
  },
1406
1591
  resume: function resume() {
1407
- paused = false;
1592
+ dirty.paused = true;
1593
+ manuallyPaused = false;
1594
+ wakeScheduler();
1595
+ return this;
1596
+ },
1597
+ setPaused: function setPaused(value) {
1598
+ return value ? this.pause() : this.resume();
1599
+ },
1600
+ setStatic: function setStatic(value) {
1601
+ dirty.static = true;
1602
+ staticMode = value === true;
1603
+ merged.static = staticMode;
1604
+ if (staticMode) renderOnce();else wakeScheduler();
1605
+ return this;
1606
+ },
1607
+ setFps: function setFps(fps) {
1608
+ dirty.fps = true;
1609
+ merged.fps = frameGate.setFps(fps);
1610
+ wakeScheduler();
1408
1611
  return this;
1409
1612
  },
1410
1613
  setQuality: function setQuality(quality) {
1614
+ dirty.quality = true;
1411
1615
  merged.quality = quality;
1412
- if (typeof renderer.setDprCap === 'function') renderer.setDprCap(dprCapFor(quality));
1616
+ if (typeof renderer.setDprCap === 'function') {
1617
+ renderer.setDprCap(effectiveDprCap(quality, merged.renderScale));
1618
+ }
1619
+ if (isMotionPaused()) renderOnce();
1620
+ return this;
1621
+ },
1622
+ setRenderScale: function setRenderScale(renderScale) {
1623
+ var value = Number(renderScale);
1624
+ if (!Number.isFinite(value)) return this;
1625
+ dirty.renderScale = true;
1626
+ merged.renderScale = Math.min(1, Math.max(0.25, value));
1627
+ if (typeof renderer.setDprCap === 'function') {
1628
+ renderer.setDprCap(effectiveDprCap(merged.quality, merged.renderScale));
1629
+ }
1630
+ if (isMotionPaused()) renderOnce();
1413
1631
  return this;
1414
1632
  },
1415
1633
  dispose: function dispose() {
1416
1634
  disposed = true;
1417
1635
  unsubscribe();
1418
1636
  visibility.dispose();
1419
- if (resizeObserver) resizeObserver.disconnect();else window.removeEventListener('resize', renderer.resize);
1637
+ motionPreference.dispose();
1638
+ if (resizeObserver) resizeObserver.disconnect();else window.removeEventListener('resize', resize);
1420
1639
  renderer.dispose();
1421
1640
  root.remove();
1422
1641
  },
@@ -1426,6 +1645,24 @@
1426
1645
  get cssVars() {
1427
1646
  return merged.cssVars;
1428
1647
  },
1648
+ get mouseColor() {
1649
+ return merged.mouseColor;
1650
+ },
1651
+ get quality() {
1652
+ return merged.quality;
1653
+ },
1654
+ get renderScale() {
1655
+ return merged.renderScale;
1656
+ },
1657
+ get paused() {
1658
+ return manuallyPaused;
1659
+ },
1660
+ get static() {
1661
+ return staticMode;
1662
+ },
1663
+ get fps() {
1664
+ return frameGate.getFps();
1665
+ },
1429
1666
  dirty: dirty
1430
1667
  };
1431
1668
  }
@@ -1566,64 +1803,95 @@
1566
1803
  var ProgressFlowRenderer = /*#__PURE__*/function () {
1567
1804
  function ProgressFlowRenderer(canvas, preset) {
1568
1805
  var _PROFILE_INDEX$preset;
1806
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
1569
1807
  _classCallCheck(this, ProgressFlowRenderer);
1570
1808
  var gl = canvas.getContext('webgl2', {
1571
1809
  alpha: false,
1572
1810
  antialias: true,
1573
1811
  premultipliedAlpha: false,
1574
- powerPreference: 'high-performance'
1812
+ powerPreference: options.powerPreference || 'high-performance'
1575
1813
  });
1576
1814
  if (!gl) throw new Error('WebGL2 unavailable');
1577
1815
  this.canvas = canvas;
1816
+ this.options = options;
1578
1817
  this.gl = gl;
1579
- this.program = createProgram(gl);
1580
1818
  this.profile = preset.edgeStyle === 'tide' ? 3 : (_PROFILE_INDEX$preset = PROFILE_INDEX[preset.id]) !== null && _PROFILE_INDEX$preset !== void 0 ? _PROFILE_INDEX$preset : 2;
1581
1819
  this.seed = stringSeed$1("".concat(preset.id, "-shader")) * 13.7 + 1.0;
1582
1820
  this.colors = preset.colors.map(hexToRgb01);
1583
1821
  this.motionData = getProgressMotionData(preset.id, preset.edgeStyle);
1584
1822
  var motionFactor = preset.edgeStyle === 'tide' ? MOTION_SCALE_FACTORS.tide : MOTION_SCALE_FACTORS[preset.id] || 1.04;
1585
1823
  this.motionScale = PROGRESS_MOTION_MAX_PX * motionFactor / 1257;
1586
- this.position = gl.getAttribLocation(this.program, 'a_position');
1587
- this.uniforms = {
1588
- resolution: gl.getUniformLocation(this.program, 'u_resolution'),
1589
- time: gl.getUniformLocation(this.program, 'u_time'),
1590
- progress: gl.getUniformLocation(this.program, 'u_progress'),
1591
- seed: gl.getUniformLocation(this.program, 'u_seed'),
1592
- profile: gl.getUniformLocation(this.program, 'u_profile'),
1593
- motion: gl.getUniformLocation(this.program, 'u_motion'),
1594
- effect: gl.getUniformLocation(this.program, 'u_effect'),
1595
- hasEffect: gl.getUniformLocation(this.program, 'u_hasEffect'),
1596
- effectFrames: gl.getUniformLocation(this.program, 'u_effectFrames'),
1597
- motionDuration: gl.getUniformLocation(this.program, 'u_motionDuration'),
1598
- motionScale: gl.getUniformLocation(this.program, 'u_motionScale'),
1599
- dark: gl.getUniformLocation(this.program, 'u_dark'),
1600
- accentA: gl.getUniformLocation(this.program, 'u_accentA'),
1601
- accentB: gl.getUniformLocation(this.program, 'u_accentB'),
1602
- glow: gl.getUniformLocation(this.program, 'u_glow')
1603
- };
1604
- this.buffer = gl.createBuffer();
1605
- gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
1606
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]), gl.STATIC_DRAW);
1607
- this.motionTexture = gl.createTexture();
1608
- gl.activeTexture(gl.TEXTURE0);
1609
- gl.bindTexture(gl.TEXTURE_2D, this.motionTexture);
1610
- gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
1611
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8, PROGRESS_MOTION_WIDTH, PROGRESS_MOTION_HEIGHT, 0, gl.RED, gl.UNSIGNED_BYTE, this.motionData);
1612
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
1613
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
1614
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
1615
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
1616
- this.effectTexture = gl.createTexture();
1617
- gl.activeTexture(gl.TEXTURE1);
1618
- gl.bindTexture(gl.TEXTURE_2D, this.effectTexture);
1619
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, 1, 1, 0, gl.RGB, gl.UNSIGNED_BYTE, new Uint8Array([32, 33, 38]));
1620
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
1621
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
1622
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
1623
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
1624
- this.effectUploaded = false;
1824
+ this.disposed = false;
1825
+ this.contextLost = false;
1826
+ this.setupResources();
1827
+ this.bindContextEvents();
1625
1828
  }
1626
1829
  return _createClass(ProgressFlowRenderer, [{
1830
+ key: "setupResources",
1831
+ value: function setupResources() {
1832
+ var gl = this.gl;
1833
+ this.program = createProgram(gl);
1834
+ this.position = gl.getAttribLocation(this.program, 'a_position');
1835
+ this.uniforms = {
1836
+ resolution: gl.getUniformLocation(this.program, 'u_resolution'),
1837
+ time: gl.getUniformLocation(this.program, 'u_time'),
1838
+ progress: gl.getUniformLocation(this.program, 'u_progress'),
1839
+ seed: gl.getUniformLocation(this.program, 'u_seed'),
1840
+ profile: gl.getUniformLocation(this.program, 'u_profile'),
1841
+ motion: gl.getUniformLocation(this.program, 'u_motion'),
1842
+ effect: gl.getUniformLocation(this.program, 'u_effect'),
1843
+ hasEffect: gl.getUniformLocation(this.program, 'u_hasEffect'),
1844
+ effectFrames: gl.getUniformLocation(this.program, 'u_effectFrames'),
1845
+ motionDuration: gl.getUniformLocation(this.program, 'u_motionDuration'),
1846
+ motionScale: gl.getUniformLocation(this.program, 'u_motionScale'),
1847
+ dark: gl.getUniformLocation(this.program, 'u_dark'),
1848
+ accentA: gl.getUniformLocation(this.program, 'u_accentA'),
1849
+ accentB: gl.getUniformLocation(this.program, 'u_accentB'),
1850
+ glow: gl.getUniformLocation(this.program, 'u_glow')
1851
+ };
1852
+ this.buffer = gl.createBuffer();
1853
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
1854
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]), gl.STATIC_DRAW);
1855
+ this.motionTexture = gl.createTexture();
1856
+ gl.activeTexture(gl.TEXTURE0);
1857
+ gl.bindTexture(gl.TEXTURE_2D, this.motionTexture);
1858
+ gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
1859
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8, PROGRESS_MOTION_WIDTH, PROGRESS_MOTION_HEIGHT, 0, gl.RED, gl.UNSIGNED_BYTE, this.motionData);
1860
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
1861
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
1862
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
1863
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
1864
+ this.effectTexture = gl.createTexture();
1865
+ gl.activeTexture(gl.TEXTURE1);
1866
+ gl.bindTexture(gl.TEXTURE_2D, this.effectTexture);
1867
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, 1, 1, 0, gl.RGB, gl.UNSIGNED_BYTE, new Uint8Array([32, 33, 38]));
1868
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
1869
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
1870
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
1871
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
1872
+ this.effectUploaded = false;
1873
+ }
1874
+ }, {
1875
+ key: "bindContextEvents",
1876
+ value: function bindContextEvents() {
1877
+ var _this = this;
1878
+ this.onContextLost = function (event) {
1879
+ event.preventDefault();
1880
+ if (_this.disposed) return;
1881
+ _this.contextLost = true;
1882
+ if (typeof _this.options.onContextLost === 'function') _this.options.onContextLost(event);
1883
+ };
1884
+ this.onContextRestored = function () {
1885
+ if (_this.disposed) return;
1886
+ _this.setupResources();
1887
+ _this.contextLost = false;
1888
+ _this.gl.viewport(0, 0, _this.canvas.width, _this.canvas.height);
1889
+ if (typeof _this.options.onContextRestored === 'function') _this.options.onContextRestored();
1890
+ };
1891
+ this.canvas.addEventListener('webglcontextlost', this.onContextLost, false);
1892
+ this.canvas.addEventListener('webglcontextrestored', this.onContextRestored, false);
1893
+ }
1894
+ }, {
1627
1895
  key: "resize",
1628
1896
  value: function resize(width, height, dpr) {
1629
1897
  var pixelWidth = Math.max(1, Math.round(width * dpr));
@@ -1638,6 +1906,7 @@
1638
1906
  key: "draw",
1639
1907
  value: function draw(time, progress) {
1640
1908
  var effectImage = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
1909
+ if (this.disposed || this.contextLost) return;
1641
1910
  var gl = this.gl;
1642
1911
  gl.useProgram(this.program);
1643
1912
  gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
@@ -1654,7 +1923,8 @@
1654
1923
  gl.uniform1f(this.uniforms.motionDuration, PROGRESS_MOTION_DURATION);
1655
1924
  gl.uniform1f(this.uniforms.motionScale, this.motionScale);
1656
1925
  var hasEffect = this.effectUploaded ? 1 : 0;
1657
- if (!this.effectUploaded && effectImage && effectImage.complete && effectImage.naturalWidth > 0) {
1926
+ var effectWidth = effectImage && (effectImage.naturalWidth || effectImage.width || 0);
1927
+ if (!this.effectUploaded && effectImage && effectWidth > 0) {
1658
1928
  gl.activeTexture(gl.TEXTURE1);
1659
1929
  gl.bindTexture(gl.TEXTURE_2D, this.effectTexture);
1660
1930
  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
@@ -1686,7 +1956,10 @@
1686
1956
  }, {
1687
1957
  key: "dispose",
1688
1958
  value: function dispose() {
1959
+ this.disposed = true;
1689
1960
  var gl = this.gl;
1961
+ this.canvas.removeEventListener('webglcontextlost', this.onContextLost, false);
1962
+ this.canvas.removeEventListener('webglcontextrestored', this.onContextRestored, false);
1690
1963
  gl.deleteBuffer(this.buffer);
1691
1964
  gl.deleteTexture(this.motionTexture);
1692
1965
  gl.deleteTexture(this.effectTexture);
@@ -1697,8 +1970,9 @@
1697
1970
  }]);
1698
1971
  }();
1699
1972
  function createProgressFlowRenderer(canvas, preset) {
1973
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
1700
1974
  try {
1701
- return new ProgressFlowRenderer(canvas, preset);
1975
+ return new ProgressFlowRenderer(canvas, preset, options);
1702
1976
  } catch (error) {
1703
1977
  console.warn('[画境观屿] 进度流体 WebGL2 不可用,使用 Canvas 2D 降级。', error);
1704
1978
  return null;
@@ -1758,50 +2032,49 @@
1758
2032
  context.fillRect(20, 0, 44, FRAME_HEIGHT);
1759
2033
  context.restore();
1760
2034
  }
1761
- var image = new Image();
1762
- image.decoding = 'async';
1763
- var state = {
1764
- image: image,
1765
- ready: false
2035
+
2036
+ // A canvas is a valid TexImageSource. Keeping it directly avoids a PNG
2037
+ // encode -> base64 allocation -> Image decode round trip at startup.
2038
+ return {
2039
+ image: canvas,
2040
+ ready: true
1766
2041
  };
1767
- image.addEventListener('load', function () {
1768
- state.ready = true;
1769
- }, {
1770
- once: true
1771
- });
1772
- image.addEventListener('error', function () {
1773
- state.ready = false;
1774
- }, {
1775
- once: true
1776
- });
1777
- image.src = canvas.toDataURL('image/png');
1778
- return state;
1779
2042
  }
1780
2043
  function getProgressReferenceAtlas(id) {
1781
2044
  if (!CACHE[id]) CACHE[id] = createAtlas(id);
1782
2045
  return CACHE[id];
1783
2046
  }
1784
2047
 
1785
- /**
1786
- * Attach a WebGL2 fluid overlay to a progress capsule root.
1787
- *
1788
- * @param {object} params
1789
- * @param {HTMLElement} params.root progress capsule root element
1790
- * @param {HTMLCanvasElement} params.canvas 2D fallback canvas (kept beneath)
1791
- * @param {object} params.preset progress preset
1792
- * @param {() => number} params.getProgress reads the current progress value
1793
- * @returns {{ update(flowTime: number): void, dispose(): void } | null}
2048
+ /**
2049
+ * Attach a WebGL2 fluid overlay to a progress capsule root.
2050
+ *
2051
+ * @param {object} params
2052
+ * @param {HTMLElement} params.root progress capsule root element
2053
+ * @param {HTMLCanvasElement} params.canvas 2D fallback canvas (kept beneath)
2054
+ * @param {object} params.preset progress preset
2055
+ * @param {() => number} params.getProgress reads the current progress value
2056
+ * @returns {{ update(flowTime: number): void, setDprCap(cap: number): void, dispose(): void } | null}
1794
2057
  */
1795
2058
  function attachProgressFlowOverlay(_ref) {
1796
2059
  var root = _ref.root,
1797
2060
  canvas = _ref.canvas,
1798
2061
  preset = _ref.preset,
1799
- getProgress = _ref.getProgress;
2062
+ getProgress = _ref.getProgress,
2063
+ _ref$dprCap = _ref.dprCap,
2064
+ dprCap = _ref$dprCap === void 0 ? 2 : _ref$dprCap,
2065
+ _ref$powerPreference = _ref.powerPreference,
2066
+ powerPreference = _ref$powerPreference === void 0 ? 'high-performance' : _ref$powerPreference,
2067
+ onContextLost = _ref.onContextLost,
2068
+ onContextRestored = _ref.onContextRestored;
1800
2069
  var overlay = document.createElement('canvas');
1801
2070
  overlay.className = 'hj-progress-canvas hj-progress-overlay';
1802
2071
  overlay.setAttribute('aria-hidden', 'true');
1803
2072
  canvas.insertAdjacentElement('afterend', overlay);
1804
- var renderer = createProgressFlowRenderer(overlay, preset);
2073
+ var renderer = createProgressFlowRenderer(overlay, preset, {
2074
+ powerPreference: powerPreference,
2075
+ onContextLost: onContextLost,
2076
+ onContextRestored: onContextRestored
2077
+ });
1805
2078
  if (!renderer) {
1806
2079
  overlay.remove();
1807
2080
  return null;
@@ -1812,7 +2085,7 @@
1812
2085
  var bounds = root.getBoundingClientRect();
1813
2086
  var width = Math.max(1, bounds.width);
1814
2087
  var height = Math.max(1, bounds.height);
1815
- var dpr = Math.min(window.devicePixelRatio || 1, 2);
2088
+ var dpr = Math.min(window.devicePixelRatio || 1, dprCap);
1816
2089
  overlay.style.width = "".concat(width, "px");
1817
2090
  overlay.style.height = "".concat(height, "px");
1818
2091
  renderer.resize(width, height, dpr);
@@ -1836,6 +2109,10 @@
1836
2109
  setColors: function setColors(colors) {
1837
2110
  renderer.setColors(colors);
1838
2111
  },
2112
+ setDprCap: function setDprCap(cap) {
2113
+ dprCap = cap;
2114
+ resize();
2115
+ },
1839
2116
  dispose: function dispose() {
1840
2117
  if (resizeObserver) resizeObserver.disconnect();
1841
2118
  if (onWindowResize) window.removeEventListener('resize', onWindowResize);
@@ -1846,6 +2123,47 @@
1846
2123
  };
1847
2124
  }
1848
2125
 
2126
+ function normalizeRange(min, max) {
2127
+ var fallbackMin = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
2128
+ var fallbackMax = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 100;
2129
+ var nextMin = toFiniteNumber(min);
2130
+ var nextMax = toFiniteNumber(max);
2131
+ if (nextMin === null) nextMin = fallbackMin;
2132
+ if (nextMax === null) nextMax = fallbackMax;
2133
+ if (nextMin > nextMax) {
2134
+ var swap = nextMin;
2135
+ nextMin = nextMax;
2136
+ nextMax = swap;
2137
+ }
2138
+ return [nextMin, nextMax];
2139
+ }
2140
+ function clampProgressValue(value, min, max) {
2141
+ var number = toFiniteNumber(value);
2142
+ if (number === null) return null;
2143
+ return Math.min(Math.max(number, min), max);
2144
+ }
2145
+ function normalizeProgressStep(step) {
2146
+ var fallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'any';
2147
+ if (step === 'any' || step == null) return 'any';
2148
+ var increment = toFiniteNumber(step);
2149
+ return increment !== null && increment > 0 ? increment : fallback;
2150
+ }
2151
+ function snapProgressValue(value, min, max) {
2152
+ var step = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'any';
2153
+ var clamped = clampProgressValue(value, min, max);
2154
+ if (clamped === null) return null;
2155
+ if (step === 'any' || step == null) return clamped;
2156
+ var increment = normalizeProgressStep(step);
2157
+ if (increment === 'any') return clamped;
2158
+ var snapped = min + Math.round((clamped - min) / increment) * increment;
2159
+ return Math.min(Math.max(Number(snapped.toFixed(12)), min), max);
2160
+ }
2161
+ function progressRatio(value, min, max) {
2162
+ var range = max - min;
2163
+ if (!Number.isFinite(range) || range <= 0) return 0;
2164
+ return Math.min(Math.max((value - min) / range, 0), 1);
2165
+ }
2166
+
1849
2167
  var clamp = function clamp(value, min, max) {
1850
2168
  return Math.min(Math.max(value, min), max);
1851
2169
  };
@@ -1872,9 +2190,6 @@
1872
2190
  return values[key] !== undefined ? values[key] : '';
1873
2191
  });
1874
2192
  }
1875
- function prefersReducedMotion$1() {
1876
- return typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches;
1877
- }
1878
2193
  var FLOW_PROFILES = {
1879
2194
  'model-training': {
1880
2195
  cycles: [0.98, 3.05, 5.45],
@@ -1935,8 +2250,6 @@
1935
2250
  var ProgressCapsuleController = /*#__PURE__*/function () {
1936
2251
  function ProgressCapsuleController(_ref) {
1937
2252
  var _options$valueSuffix,
1938
- _options$min,
1939
- _options$max,
1940
2253
  _options$value,
1941
2254
  _this = this;
1942
2255
  var root = _ref.root,
@@ -1958,11 +2271,18 @@
1958
2271
  this.dirty = dirty;
1959
2272
  this.valueSuffix = (_options$valueSuffix = options.valueSuffix) !== null && _options$valueSuffix !== void 0 ? _options$valueSuffix : copy.valueSuffix;
1960
2273
  this.profile = preset.edgeStyle === 'tide' ? FLOW_PROFILES.tide : FLOW_PROFILES[preset.id] || FLOW_PROFILES['visual-training'];
1961
- this.min = (_options$min = options.min) !== null && _options$min !== void 0 ? _options$min : 0;
1962
- this.max = (_options$max = options.max) !== null && _options$max !== void 0 ? _options$max : 100;
1963
- this.dprCap = dprCapFor(options.quality);
2274
+ var _normalizeRange = normalizeRange(options.min, options.max);
2275
+ var _normalizeRange2 = _slicedToArray(_normalizeRange, 2);
2276
+ this.min = _normalizeRange2[0];
2277
+ this.max = _normalizeRange2[1];
2278
+ this.step = normalizeProgressStep(options.step);
2279
+ this.precision = Number.isInteger(Number(options.precision)) ? clamp(Number(options.precision), 0, 20) : 0;
2280
+ this.formatValue = typeof options.formatValue === 'function' ? options.formatValue : null;
2281
+ this.direction = options.direction === 'rtl' ? 'rtl' : 'ltr';
2282
+ this.dprCap = effectiveDprCap(options.quality, options.renderScale);
1964
2283
  this.ctx = canvas.getContext('2d');
1965
- this.value = clamp((_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : preset.initialProgress, this.min, this.max);
2284
+ this.value = snapProgressValue((_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : preset.initialProgress, this.min, this.max, this.step);
2285
+ if (this.value === null) this.value = clamp(preset.initialProgress, this.min, this.max);
1966
2286
  this.dragging = false;
1967
2287
  this.webglActive = false;
1968
2288
  this.flowTime = stringSeed(preset.id) * 31;
@@ -1972,10 +2292,12 @@
1972
2292
  this.width = 0;
1973
2293
  this.height = 0;
1974
2294
  this.handlers = {};
1975
- this.resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(function () {
1976
- return _this.resizeCanvas();
1977
- }) : null;
1978
- if (this.resizeObserver) this.resizeObserver.observe(this.root);else window.addEventListener('resize', this.resizeCanvas);
2295
+ this.onResize = function () {
2296
+ _this.resizeCanvas();
2297
+ if (typeof _this.options.onResize === 'function') _this.options.onResize();
2298
+ };
2299
+ this.resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(this.onResize) : null;
2300
+ if (this.resizeObserver) this.resizeObserver.observe(this.root);else window.addEventListener('resize', this.onResize);
1979
2301
  this.suppressEvents = true;
1980
2302
  this.setProgress(this.value, 'init');
1981
2303
  this.suppressEvents = false;
@@ -1988,16 +2310,37 @@
1988
2310
  this.randomState = Math.imul(this.randomState, 1664525) + 1013904223 >>> 0;
1989
2311
  return this.randomState / 4294967296;
1990
2312
  }
2313
+ }, {
2314
+ key: "displayText",
2315
+ value: function displayText() {
2316
+ if (this.formatValue) {
2317
+ return String(this.formatValue(this.value, {
2318
+ min: this.min,
2319
+ max: this.max,
2320
+ suffix: this.valueSuffix
2321
+ }));
2322
+ }
2323
+ var number = this.precision > 0 ? this.value.toFixed(this.precision) : String(Math.round(this.value));
2324
+ return "".concat(number).concat(this.valueSuffix);
2325
+ }
2326
+ }, {
2327
+ key: "syncValueDom",
2328
+ value: function syncValueDom() {
2329
+ var text = this.displayText();
2330
+ this.root.style.setProperty('--progress', this.value.toFixed(2));
2331
+ this.root.style.setProperty('--progress-ratio', progressRatio(this.value, this.min, this.max).toFixed(4));
2332
+ this.root.setAttribute('aria-valuenow', String(this.value));
2333
+ this.root.setAttribute('aria-valuetext', text);
2334
+ if (this.valueElement) this.valueElement.textContent = text;
2335
+ }
1991
2336
  }, {
1992
2337
  key: "setProgress",
1993
2338
  value: function setProgress(nextValue) {
1994
2339
  var source = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'auto';
1995
- this.value = clamp(nextValue, this.min, this.max);
1996
- var rounded = Math.round(this.value);
1997
- this.root.style.setProperty('--progress', this.value.toFixed(2));
1998
- this.root.setAttribute('aria-valuenow', String(rounded));
1999
- this.root.setAttribute('aria-valuetext', "".concat(rounded).concat(this.valueSuffix));
2000
- if (this.valueElement) this.valueElement.textContent = "".concat(rounded).concat(this.valueSuffix);
2340
+ var next = snapProgressValue(nextValue, this.min, this.max, this.step);
2341
+ if (next === null) return false;
2342
+ this.value = next;
2343
+ this.syncValueDom();
2001
2344
  if (!this.suppressEvents) {
2002
2345
  if (this.dirty) this.dirty.value = true;
2003
2346
  this.emitter.emit('change', {
@@ -2005,19 +2348,58 @@
2005
2348
  source: source
2006
2349
  });
2007
2350
  }
2351
+ return true;
2008
2352
  }
2009
2353
  }, {
2010
2354
  key: "setValueSuffix",
2011
2355
  value: function setValueSuffix(suffix) {
2356
+ if (this.dirty) this.dirty.valueSuffix = true;
2012
2357
  this.valueSuffix = String(suffix == null ? '' : suffix);
2013
- var rounded = Math.round(this.value);
2014
- if (this.valueElement) this.valueElement.textContent = "".concat(rounded).concat(this.valueSuffix);
2015
- this.root.setAttribute('aria-valuetext', "".concat(rounded).concat(this.valueSuffix));
2358
+ this.syncValueDom();
2359
+ return this;
2360
+ }
2361
+ }, {
2362
+ key: "setStep",
2363
+ value: function setStep(step) {
2364
+ var nextStep = normalizeProgressStep(step, this.step);
2365
+ if (nextStep === this.step) return this;
2366
+ if (this.dirty) this.dirty.step = true;
2367
+ this.step = nextStep;
2368
+ var next = snapProgressValue(this.value, this.min, this.max, this.step);
2369
+ if (next !== null && next !== this.value) this.setProgress(next, 'prop');
2370
+ return this;
2371
+ }
2372
+ }, {
2373
+ key: "setPrecision",
2374
+ value: function setPrecision(precision) {
2375
+ var value = Number(precision);
2376
+ if (!Number.isInteger(value) || value < 0 || value > 20) return this;
2377
+ if (this.dirty) this.dirty.precision = true;
2378
+ this.precision = value;
2379
+ this.syncValueDom();
2380
+ return this;
2381
+ }
2382
+ }, {
2383
+ key: "setFormatValue",
2384
+ value: function setFormatValue(formatter) {
2385
+ if (formatter != null && typeof formatter !== 'function') return this;
2386
+ if (this.dirty) this.dirty.formatValue = true;
2387
+ this.formatValue = formatter || null;
2388
+ this.syncValueDom();
2389
+ return this;
2390
+ }
2391
+ }, {
2392
+ key: "setDirection",
2393
+ value: function setDirection(direction) {
2394
+ if (this.dirty) this.dirty.direction = true;
2395
+ this.direction = direction === 'rtl' ? 'rtl' : 'ltr';
2396
+ this.root.dataset.direction = this.direction;
2016
2397
  return this;
2017
2398
  }
2018
2399
  }, {
2019
2400
  key: "setShowValue",
2020
2401
  value: function setShowValue(show) {
2402
+ if (this.dirty) this.dirty.showValue = true;
2021
2403
  if (this.valueElement) this.valueElement.hidden = show === false;
2022
2404
  return this;
2023
2405
  }
@@ -2182,15 +2564,14 @@
2182
2564
  }, {
2183
2565
  key: "setRange",
2184
2566
  value: function setRange(min, max) {
2185
- if (min > max) {
2186
- var swap = min;
2187
- min = max;
2188
- max = swap;
2189
- }
2190
- this.min = min;
2191
- this.max = max;
2192
- var clamped = clamp(this.value, this.min, this.max);
2193
- if (clamped !== this.value) this.setProgress(clamped, 'prop');
2567
+ var nextRange = normalizeRange(min, max, this.min, this.max);
2568
+ this.min = nextRange[0];
2569
+ this.max = nextRange[1];
2570
+ this.root.setAttribute('aria-valuemin', String(this.min));
2571
+ this.root.setAttribute('aria-valuemax', String(this.max));
2572
+ var next = snapProgressValue(this.value, this.min, this.max, this.step);
2573
+ if (next !== this.value) this.setProgress(next, 'prop');else this.syncValueDom();
2574
+ return this;
2194
2575
  }
2195
2576
  }, {
2196
2577
  key: "drawReferenceFlow",
@@ -2315,8 +2696,39 @@
2315
2696
  value: function updateFromPointer(event) {
2316
2697
  var bounds = this.root.getBoundingClientRect();
2317
2698
  var ratio = bounds.width > 0 ? (event.clientX - bounds.left) / bounds.width : 0;
2699
+ if (this.direction === 'rtl') ratio = 1 - ratio;
2318
2700
  this.setProgress(this.min + ratio * (this.max - this.min), 'drag');
2319
2701
  }
2702
+ }, {
2703
+ key: "onKeyDown",
2704
+ value: function onKeyDown(event) {
2705
+ var range = this.max - this.min;
2706
+ var configuredStep = Number(this.step);
2707
+ var step = Number.isFinite(configuredStep) && configuredStep > 0 ? configuredStep : Math.max(range / 100, 1e-7);
2708
+ var direction = this.direction === 'rtl' ? -1 : 1;
2709
+ var aliases = {
2710
+ Left: 'ArrowLeft',
2711
+ Right: 'ArrowRight',
2712
+ Up: 'ArrowUp',
2713
+ Down: 'ArrowDown'
2714
+ };
2715
+ var keyCodes = {
2716
+ 35: 'End',
2717
+ 36: 'Home',
2718
+ 33: 'PageUp',
2719
+ 34: 'PageDown',
2720
+ 37: 'ArrowLeft',
2721
+ 38: 'ArrowUp',
2722
+ 39: 'ArrowRight',
2723
+ 40: 'ArrowDown'
2724
+ };
2725
+ var key = aliases[event.key] || event.key || keyCodes[event.keyCode];
2726
+ var next = null;
2727
+ 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;
2728
+ if (next === null) return;
2729
+ event.preventDefault();
2730
+ this.setProgress(next, 'keyboard');
2731
+ }
2320
2732
  }, {
2321
2733
  key: "beginDrag",
2322
2734
  value: function beginDrag(event) {
@@ -2404,6 +2816,12 @@
2404
2816
  this.root.addEventListener('pointerup', this.handlers.pointerup);
2405
2817
  this.root.addEventListener('pointercancel', this.handlers.pointercancel);
2406
2818
  }
2819
+ if (this.options.keyboard !== false) {
2820
+ this.handlers.keydown = function (event) {
2821
+ return _this3.onKeyDown(event);
2822
+ };
2823
+ this.root.addEventListener('keydown', this.handlers.keydown);
2824
+ }
2407
2825
  }
2408
2826
  }, {
2409
2827
  key: "update",
@@ -2422,20 +2840,19 @@
2422
2840
  key: "randomize",
2423
2841
  value: function randomize() {
2424
2842
  this.flowTime = this.random() * 40;
2843
+ return this.flowTime;
2425
2844
  }
2426
2845
  }, {
2427
2846
  key: "setColors",
2428
2847
  value: function setColors(colors) {
2429
2848
  if (!Array.isArray(colors) || colors.length !== 4) return;
2430
- this.preset = _objectSpread2(_objectSpread2({}, this.preset), {}, {
2431
- colors: colors
2432
- });
2849
+ this.preset.colors = _toConsumableArray(colors);
2433
2850
  }
2434
2851
  }, {
2435
2852
  key: "dispose",
2436
2853
  value: function dispose() {
2437
2854
  if (this._dragRaf) cancelAnimationFrame(this._dragRaf);
2438
- if (this.resizeObserver) this.resizeObserver.disconnect();else window.removeEventListener('resize', this.resizeCanvas);
2855
+ if (this.resizeObserver) this.resizeObserver.disconnect();else window.removeEventListener('resize', this.onResize);
2439
2856
  for (var _i = 0, _Object$keys = Object.keys(this.handlers); _i < _Object$keys.length; _i++) {
2440
2857
  var name = _Object$keys[_i];
2441
2858
  var handler = this.handlers[name];
@@ -2446,15 +2863,16 @@
2446
2863
  }]);
2447
2864
  }();
2448
2865
 
2449
- /**
2866
+ /**
2450
2867
  * Mount a fluid progress capsule into `container`.
2451
2868
  *
2452
- * Options: preset (literary name), width, height, value, min, max,
2453
- * draggable, colors, edgeStyle, textRatio (0-100, text region
2869
+ * Options: preset (literary name), width, height, value/modelValue, min, max,
2870
+ * step, draggable, keyboard, direction, precision/formatValue, colors,
2871
+ * edgeStyle, textRatio (0-100, text region
2454
2872
  * width in percent), text (HTML string or DOM nodes for the text slot),
2455
2873
  * colorContent (HTML string or DOM nodes for the color slot),
2456
- * showValue (show/hide the right-side percentage), quality,
2457
- * respectReducedMotion, cssVars.
2874
+ * showValue (show/hide the right-side percentage), quality, renderScale,
2875
+ * powerPreference, fps, paused/static, respectReducedMotion, cssVars.
2458
2876
  */
2459
2877
  function createProgressCapsule(container) {
2460
2878
  var _options$preset, _merged$min, _merged$max;
@@ -2464,14 +2882,20 @@
2464
2882
  }
2465
2883
  var preset = _objectSpread2({}, getPreset('progress', (_options$preset = options.preset) !== null && _options$preset !== void 0 ? _options$preset : '星火'));
2466
2884
  var merged = normalizeOptions(DEFAULTS.progress, preset, options);
2467
- if (merged.min > merged.max) {
2468
- var swap = merged.min;
2469
- merged.min = merged.max;
2470
- merged.max = swap;
2471
- }
2472
- var normalizedColors = merged.colors.map(normalizeColor);
2473
- if (normalizedColors.every(Boolean)) preset.colors = normalizedColors;
2474
- preset.edgeStyle = merged.edgeStyle;
2885
+ var _normalizeRange3 = normalizeRange(merged.min, merged.max);
2886
+ var _normalizeRange4 = _slicedToArray(_normalizeRange3, 2);
2887
+ merged.min = _normalizeRange4[0];
2888
+ merged.max = _normalizeRange4[1];
2889
+ if (merged.modelValue !== undefined) merged.value = merged.modelValue;
2890
+ var normalizedColors = Array.isArray(merged.colors) ? merged.colors.map(normalizeColor) : [];
2891
+ if (normalizedColors.length === 4 && normalizedColors.every(Boolean)) preset.colors = normalizedColors;
2892
+ var initialEdgeStyle = merged.edgeStyle === 'tide' || merged.edgeStyle === 'flow' ? merged.edgeStyle : preset.edgeStyle;
2893
+ preset.edgeStyle = initialEdgeStyle;
2894
+ merged.edgeStyle = initialEdgeStyle;
2895
+ merged.colors = _toConsumableArray(preset.colors);
2896
+ var optionColors = Array.isArray(options.colors) ? options.colors.map(normalizeColor) : [];
2897
+ var colorOverride = optionColors.length === 4 && optionColors.every(Boolean) ? _toConsumableArray(optionColors) : null;
2898
+ var edgeStyleOverride = options.edgeStyle === 'flow' || options.edgeStyle === 'tide' ? merged.edgeStyle : null;
2475
2899
  var copy = COPY;
2476
2900
  var customLabel = typeof options.label === 'string' && options.label ? options.label : null;
2477
2901
  // Vue 的裸布尔属性(<ProgressCapsule disabled />)会传成空字符串,
@@ -2480,18 +2904,31 @@
2480
2904
  var readonly = merged.readonly === true || merged.readonly === '' || merged.readonly === 'true';
2481
2905
  var locked = disabled || readonly;
2482
2906
  var effectiveDraggable = locked ? false : merged.draggable !== false;
2907
+ var effectiveKeyboard = locked ? false : merged.keyboard !== false;
2483
2908
  var dirty = {
2484
2909
  preset: false,
2485
2910
  colors: false,
2486
2911
  textRatio: false,
2487
2912
  cssVars: false,
2488
2913
  edgeStyle: false,
2489
- value: false
2914
+ value: false,
2915
+ step: false,
2916
+ precision: false,
2917
+ formatValue: false,
2918
+ direction: false,
2919
+ showValue: false,
2920
+ valueSuffix: false,
2921
+ quality: false,
2922
+ renderScale: false,
2923
+ paused: false,
2924
+ static: false,
2925
+ fps: false
2490
2926
  };
2491
2927
  var root = document.createElement('div');
2492
2928
  root.className = 'hj-capsule-root hj-progress-root';
2493
2929
  root.setAttribute('role', 'slider');
2494
2930
  root.setAttribute('data-draggable', String(effectiveDraggable));
2931
+ root.dataset.direction = merged.direction === 'rtl' ? 'rtl' : 'ltr';
2495
2932
  if (disabled) {
2496
2933
  root.setAttribute('aria-disabled', 'true');
2497
2934
  root.classList.add('is-disabled');
@@ -2500,7 +2937,8 @@
2500
2937
  root.setAttribute('aria-readonly', 'true');
2501
2938
  root.classList.add('is-readonly');
2502
2939
  }
2503
- if (locked) root.setAttribute('tabindex', '-1');
2940
+ root.setAttribute('tabindex', locked ? '-1' : effectiveKeyboard ? '0' : '-1');
2941
+ root.setAttribute('aria-orientation', 'horizontal');
2504
2942
  root.setAttribute('aria-valuemin', String((_merged$min = merged.min) !== null && _merged$min !== void 0 ? _merged$min : 0));
2505
2943
  root.setAttribute('aria-valuemax', String((_merged$max = merged.max) !== null && _merged$max !== void 0 ? _merged$max : 100));
2506
2944
  root.setAttribute('aria-valuenow', String(preset.initialProgress));
@@ -2555,8 +2993,22 @@
2555
2993
  root.appendChild(valueElement);
2556
2994
  container.appendChild(root);
2557
2995
  var emitter = createEmitter();
2558
- var paused = merged.respectReducedMotion && prefersReducedMotion$1();
2996
+ var manuallyPaused = merged.paused === true;
2997
+ var reducedPaused = false;
2998
+ var staticMode = merged.static === true;
2999
+ var contextLost = false;
2559
3000
  var disposed = false;
3001
+ var renderOnce = function renderOnce() {};
3002
+ var onContextLost = function onContextLost() {
3003
+ contextLost = true;
3004
+ emitter.emit('contextlost', {});
3005
+ };
3006
+ var onContextRestored = function onContextRestored() {
3007
+ contextLost = false;
3008
+ emitter.emit('contextrestored', {});
3009
+ renderOnce();
3010
+ wakeScheduler();
3011
+ };
2560
3012
  var applySize = function applySize() {
2561
3013
  root.style.width = parseSize(merged.width);
2562
3014
  root.style.height = parseSize(merged.height);
@@ -2577,7 +3029,11 @@
2577
3029
  preset: preset,
2578
3030
  emitter: emitter,
2579
3031
  options: _objectSpread2(_objectSpread2({}, merged), {}, {
2580
- draggable: effectiveDraggable
3032
+ draggable: effectiveDraggable,
3033
+ keyboard: effectiveKeyboard,
3034
+ onResize: function onResize() {
3035
+ if (manuallyPaused || reducedPaused || staticMode) renderOnce();
3036
+ }
2581
3037
  }),
2582
3038
  copy: copy,
2583
3039
  dirty: dirty
@@ -2589,8 +3045,12 @@
2589
3045
  canvas: canvas,
2590
3046
  preset: preset,
2591
3047
  getProgress: function getProgress() {
2592
- return controller.value;
2593
- }
3048
+ return progressRatio(controller.value, controller.min, controller.max) * 100;
3049
+ },
3050
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
3051
+ powerPreference: merged.powerPreference,
3052
+ onContextLost: onContextLost,
3053
+ onContextRestored: onContextRestored
2594
3054
  });
2595
3055
  }
2596
3056
  if (overlay) controller.webglActive = true;else if (merged.renderer !== 'canvas2d') {
@@ -2601,17 +3061,34 @@
2601
3061
  });
2602
3062
  });
2603
3063
  }
2604
- var visibility = createVisibilityGuard(root);
3064
+ var visibility = createVisibilityGuard(root, wakeScheduler);
3065
+ var motionPreference = createReducedMotionPreference(merged.respectReducedMotion, function (matches) {
3066
+ reducedPaused = matches;
3067
+ if (matches) renderOnce();else wakeScheduler();
3068
+ });
3069
+ reducedPaused = motionPreference.matches();
3070
+ var isMotionPaused = function isMotionPaused() {
3071
+ return manuallyPaused || reducedPaused || staticMode || contextLost;
3072
+ };
2605
3073
  var flowTime = 0;
3074
+ var frameGate = createFrameGate(merged.fps);
3075
+ renderOnce = function renderOnce() {
3076
+ controller.draw();
3077
+ if (overlay) overlay.update(flowTime);
3078
+ };
3079
+ renderOnce();
3080
+ var offPausedChange = emitter.on('change', function () {
3081
+ if (isMotionPaused()) renderOnce();
3082
+ });
2606
3083
  var unsubscribe = subscribeScheduler(function (delta, now) {
2607
3084
  flowTime += delta;
2608
3085
  controller.update(delta, false);
2609
- if (visibility.isVisible()) {
3086
+ if (frameGate.shouldDraw(delta)) {
2610
3087
  controller.draw();
2611
3088
  if (overlay) overlay.update(flowTime);
2612
3089
  }
2613
3090
  }, function () {
2614
- return paused;
3091
+ return isMotionPaused() || !visibility.isVisible();
2615
3092
  });
2616
3093
  nextTick(function () {
2617
3094
  if (disposed) return;
@@ -2638,32 +3115,41 @@
2638
3115
  },
2639
3116
  setRange: function setRange(min, max) {
2640
3117
  controller.setRange(min, max);
3118
+ if (isMotionPaused()) renderOnce();
2641
3119
  return this;
2642
3120
  },
2643
3121
  setPreset: function setPreset(ref) {
2644
3122
  var next = getPreset('progress', ref);
2645
3123
  dirty.preset = true;
2646
3124
  Object.assign(preset, next);
3125
+ if (colorOverride) preset.colors = _toConsumableArray(colorOverride);
3126
+ if (edgeStyleOverride) preset.edgeStyle = edgeStyleOverride;
2647
3127
  var nextColors = preset.colors.map(normalizeColor);
2648
3128
  if (nextColors.every(Boolean)) preset.colors = nextColors;
2649
3129
  controller.preset = preset;
2650
- controller.profile = next.edgeStyle === 'tide' ? FLOW_PROFILES.tide : FLOW_PROFILES[next.id] || FLOW_PROFILES['visual-training'];
3130
+ controller.profile = preset.edgeStyle === 'tide' ? FLOW_PROFILES.tide : FLOW_PROFILES[preset.id] || FLOW_PROFILES['visual-training'];
2651
3131
  controller.flowTime = stringSeed(next.id) * 31;
2652
3132
  controller.seed = stringSeed("".concat(next.id, "-reference")) * Math.PI * 2;
2653
3133
  syncDom();
2654
3134
  if (overlay) {
2655
3135
  overlay.dispose();
3136
+ contextLost = false;
2656
3137
  overlay = attachProgressFlowOverlay({
2657
3138
  root: root,
2658
3139
  canvas: canvas,
2659
3140
  preset: preset,
2660
3141
  getProgress: function getProgress() {
2661
- return controller.value;
2662
- }
3142
+ return progressRatio(controller.value, controller.min, controller.max) * 100;
3143
+ },
3144
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
3145
+ powerPreference: merged.powerPreference,
3146
+ onContextLost: onContextLost,
3147
+ onContextRestored: onContextRestored
2663
3148
  });
2664
3149
  }
2665
3150
  controller.webglActive = Boolean(overlay);
2666
3151
  controller.resizeCanvas();
3152
+ if (isMotionPaused()) renderOnce();
2667
3153
  emitter.emit('presetchange', {
2668
3154
  preset: _objectSpread2({}, preset)
2669
3155
  });
@@ -2673,21 +3159,28 @@
2673
3159
  var value = String(edgeStyle || '').toLowerCase();
2674
3160
  if (value !== 'flow' && value !== 'tide') return this;
2675
3161
  dirty.edgeStyle = true;
3162
+ edgeStyleOverride = value;
2676
3163
  preset.edgeStyle = value;
2677
3164
  controller.profile = value === 'tide' ? FLOW_PROFILES.tide : FLOW_PROFILES[preset.id] || FLOW_PROFILES['visual-training'];
2678
3165
  if (overlay) {
2679
3166
  overlay.dispose();
3167
+ contextLost = false;
2680
3168
  overlay = attachProgressFlowOverlay({
2681
3169
  root: root,
2682
3170
  canvas: canvas,
2683
3171
  preset: preset,
2684
3172
  getProgress: function getProgress() {
2685
- return controller.value;
2686
- }
3173
+ return progressRatio(controller.value, controller.min, controller.max) * 100;
3174
+ },
3175
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
3176
+ powerPreference: merged.powerPreference,
3177
+ onContextLost: onContextLost,
3178
+ onContextRestored: onContextRestored
2687
3179
  });
2688
3180
  }
2689
3181
  controller.webglActive = Boolean(overlay);
2690
3182
  controller.resizeCanvas();
3183
+ if (isMotionPaused()) renderOnce();
2691
3184
  return this;
2692
3185
  },
2693
3186
  setText: function setText(content) {
@@ -2725,6 +3218,22 @@
2725
3218
  controller.setValueSuffix(suffix);
2726
3219
  return this;
2727
3220
  },
3221
+ setStep: function setStep(step) {
3222
+ controller.setStep(step);
3223
+ return this;
3224
+ },
3225
+ setPrecision: function setPrecision(precision) {
3226
+ controller.setPrecision(precision);
3227
+ return this;
3228
+ },
3229
+ setFormatValue: function setFormatValue(formatter) {
3230
+ controller.setFormatValue(formatter);
3231
+ return this;
3232
+ },
3233
+ setDirection: function setDirection(direction) {
3234
+ controller.setDirection(direction);
3235
+ return this;
3236
+ },
2728
3237
  setShowValue: function setShowValue(show) {
2729
3238
  controller.setShowValue(show);
2730
3239
  return this;
@@ -2735,9 +3244,12 @@
2735
3244
  return !color;
2736
3245
  })) return this;
2737
3246
  dirty.colors = true;
3247
+ colorOverride = _toConsumableArray(next);
3248
+ preset.colors = _toConsumableArray(next);
2738
3249
  controller.setColors(next);
2739
3250
  if (overlay) overlay.setColors(next);
2740
3251
  controller.resizeCanvas();
3252
+ if (isMotionPaused()) renderOnce();
2741
3253
  return this;
2742
3254
  },
2743
3255
  setSize: function setSize(width, height) {
@@ -2751,30 +3263,68 @@
2751
3263
  }
2752
3264
  applySize();
2753
3265
  controller.resizeCanvas();
3266
+ if (isMotionPaused()) renderOnce();
2754
3267
  return this;
2755
3268
  },
2756
3269
  randomize: function randomize() {
2757
- controller.randomize();
3270
+ flowTime = controller.randomize();
3271
+ if (isMotionPaused()) renderOnce();
2758
3272
  return this;
2759
3273
  },
2760
3274
  pause: function pause() {
2761
- paused = true;
3275
+ dirty.paused = true;
3276
+ manuallyPaused = true;
3277
+ renderOnce();
2762
3278
  return this;
2763
3279
  },
2764
3280
  resume: function resume() {
2765
- paused = false;
3281
+ dirty.paused = true;
3282
+ manuallyPaused = false;
3283
+ wakeScheduler();
3284
+ return this;
3285
+ },
3286
+ setPaused: function setPaused(value) {
3287
+ return value ? this.pause() : this.resume();
3288
+ },
3289
+ setStatic: function setStatic(value) {
3290
+ dirty.static = true;
3291
+ staticMode = value === true;
3292
+ merged.static = staticMode;
3293
+ if (staticMode) renderOnce();else wakeScheduler();
3294
+ return this;
3295
+ },
3296
+ setFps: function setFps(fps) {
3297
+ dirty.fps = true;
3298
+ merged.fps = frameGate.setFps(fps);
3299
+ wakeScheduler();
2766
3300
  return this;
2767
3301
  },
2768
3302
  setQuality: function setQuality(quality) {
3303
+ dirty.quality = true;
2769
3304
  merged.quality = quality;
2770
- controller.dprCap = dprCapFor(quality);
3305
+ controller.dprCap = effectiveDprCap(quality, merged.renderScale);
3306
+ controller.resizeCanvas();
3307
+ if (overlay) overlay.setDprCap(controller.dprCap);
3308
+ if (isMotionPaused()) renderOnce();
3309
+ return this;
3310
+ },
3311
+ setRenderScale: function setRenderScale(renderScale) {
3312
+ var value = Number(renderScale);
3313
+ if (!Number.isFinite(value)) return this;
3314
+ dirty.renderScale = true;
3315
+ merged.renderScale = Math.min(1, Math.max(0.25, value));
3316
+ controller.dprCap = effectiveDprCap(merged.quality, merged.renderScale);
2771
3317
  controller.resizeCanvas();
3318
+ if (overlay) overlay.setDprCap(controller.dprCap);
3319
+ if (isMotionPaused()) renderOnce();
2772
3320
  return this;
2773
3321
  },
2774
3322
  dispose: function dispose() {
2775
3323
  disposed = true;
2776
3324
  unsubscribe();
3325
+ offPausedChange();
2777
3326
  visibility.dispose();
3327
+ motionPreference.dispose();
2778
3328
  if (overlay) overlay.dispose();
2779
3329
  controller.dispose();
2780
3330
  root.remove();
@@ -2785,26 +3335,89 @@
2785
3335
  get cssVars() {
2786
3336
  return merged.cssVars;
2787
3337
  },
3338
+ get min() {
3339
+ return controller.min;
3340
+ },
3341
+ get max() {
3342
+ return controller.max;
3343
+ },
3344
+ get step() {
3345
+ return controller.step;
3346
+ },
3347
+ get precision() {
3348
+ return controller.precision;
3349
+ },
3350
+ get formatValue() {
3351
+ return controller.formatValue;
3352
+ },
3353
+ get direction() {
3354
+ return controller.direction;
3355
+ },
3356
+ get showValue() {
3357
+ return !valueElement.hidden;
3358
+ },
3359
+ get valueSuffix() {
3360
+ return controller.valueSuffix;
3361
+ },
3362
+ get quality() {
3363
+ return merged.quality;
3364
+ },
3365
+ get renderScale() {
3366
+ return merged.renderScale;
3367
+ },
3368
+ get paused() {
3369
+ return manuallyPaused;
3370
+ },
3371
+ get static() {
3372
+ return staticMode;
3373
+ },
3374
+ get fps() {
3375
+ return frameGate.getFps();
3376
+ },
2788
3377
  dirty: dirty
2789
3378
  };
2790
3379
  }
2791
3380
 
2792
- function prefersReducedMotion() {
2793
- return typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches;
3381
+ var HOST_STYLES = new WeakMap();
3382
+ function acquireHostStyles(host) {
3383
+ var active = HOST_STYLES.get(host);
3384
+ if (active) {
3385
+ active.count += 1;
3386
+ return;
3387
+ }
3388
+ var state = {
3389
+ count: 1,
3390
+ position: host.style.position,
3391
+ isolation: host.style.isolation
3392
+ };
3393
+ var computed = getComputedStyle(host);
3394
+ if (computed.position === 'static' || computed.position === '') host.style.position = 'relative';
3395
+ host.style.isolation = 'isolate';
3396
+ HOST_STYLES.set(host, state);
3397
+ }
3398
+ function releaseHostStyles(host) {
3399
+ var state = HOST_STYLES.get(host);
3400
+ if (!state) return;
3401
+ state.count -= 1;
3402
+ if (state.count > 0) return;
3403
+ host.style.position = state.position;
3404
+ host.style.isolation = state.isolation;
3405
+ HOST_STYLES.delete(host);
2794
3406
  }
2795
3407
 
2796
- /**
2797
- * Mount the cosmic (nebula) material as a background layer inside `host`.
2798
- *
2799
- * The layer is absolutely positioned and defaults to `z-index: -1`, so it
2800
- * paints behind the host's in-flow content: text/buttons/images inside the
2801
- * host sit on top with zero extra CSS. Host gets `position: relative;
2802
- * isolation: isolate` automatically so the layer can never escape its box
2803
- * (or break stacking outside it).
2804
- *
3408
+ /**
3409
+ * Mount the cosmic (nebula) material as a background layer inside `host`.
3410
+ *
3411
+ * The layer is absolutely positioned and defaults to `z-index: -1`, so it
3412
+ * paints behind the host's in-flow content: text/buttons/images inside the
3413
+ * host sit on top with zero extra CSS. Host gets `position: relative;
3414
+ * isolation: isolate` automatically so the layer can never escape its box
3415
+ * (or break stacking outside it).
3416
+ *
2805
3417
  * Options: preset (literary name), colors, seed, speed, quality,
2806
- * renderer, mouseColor, respectReducedMotion, opacity (0-1), fallbackColor
2807
- * (true=preset base color, a hex string, or false to disable).
3418
+ * renderer, renderScale, powerPreference, fps, paused/static, mouseColor,
3419
+ * respectReducedMotion, opacity (0-1), fallbackColor (true=preset base
3420
+ * color, a hex string, or false to disable).
2808
3421
  */
2809
3422
  function createColorBackground(host) {
2810
3423
  var _options$preset;
@@ -2818,18 +3431,29 @@
2818
3431
  renderer: DEFAULTS.capsule.renderer,
2819
3432
  respectReducedMotion: DEFAULTS.capsule.respectReducedMotion,
2820
3433
  mouseColor: true,
3434
+ renderScale: 1,
3435
+ powerPreference: 'high-performance',
3436
+ fps: DEFAULTS.capsule.fps,
3437
+ paused: false,
3438
+ static: false,
2821
3439
  opacity: 1,
2822
3440
  fallbackColor: true
2823
3441
  }, preset, options);
2824
- var normalizedColors = merged.colors.map(normalizeColor);
2825
- if (normalizedColors.every(Boolean)) preset.colors = normalizedColors;
2826
- preset.seed = merged.seed;
2827
- preset.speed = merged.speed;
2828
- var computed = getComputedStyle(host);
2829
- if (computed.position === 'static' || computed.position === '') {
2830
- host.style.position = 'relative';
2831
- }
2832
- host.style.isolation = 'isolate';
3442
+ merged.opacity = Number.isFinite(Number(merged.opacity)) ? Math.min(1, Math.max(0, Number(merged.opacity))) : 1;
3443
+ var normalizedColors = Array.isArray(merged.colors) ? merged.colors.map(normalizeColor) : [];
3444
+ if (normalizedColors.length === 4 && normalizedColors.every(Boolean)) preset.colors = normalizedColors;
3445
+ var initialSeed = toFiniteNumber(merged.seed);
3446
+ var initialSpeed = toFiniteNumber(merged.speed);
3447
+ if (initialSeed !== null) preset.seed = initialSeed;
3448
+ if (initialSpeed !== null) preset.speed = initialSpeed;
3449
+ merged.colors = _toConsumableArray(preset.colors);
3450
+ merged.seed = preset.seed;
3451
+ merged.speed = preset.speed;
3452
+ var optionColors = Array.isArray(options.colors) ? options.colors.map(normalizeColor) : [];
3453
+ var colorOverride = optionColors.length === 4 && optionColors.every(Boolean) ? _toConsumableArray(optionColors) : null;
3454
+ var seedOverride = options.seed !== undefined ? toFiniteNumber(options.seed) : null;
3455
+ var speedOverride = options.speed !== undefined ? toFiniteNumber(options.speed) : null;
3456
+ acquireHostStyles(host);
2833
3457
  var layer = document.createElement('div');
2834
3458
  layer.className = 'dlc-color-layer';
2835
3459
  var syncLayerVars = function syncLayerVars() {
@@ -2844,8 +3468,12 @@
2844
3468
  layer.appendChild(canvas);
2845
3469
  host.appendChild(layer);
2846
3470
  var emitter = createEmitter();
2847
- var paused = merged.respectReducedMotion && prefersReducedMotion();
3471
+ var manuallyPaused = merged.paused === true;
3472
+ var reducedPaused = false;
3473
+ var staticMode = merged.static === true;
3474
+ var contextLost = false;
2848
3475
  var disposed = false;
3476
+ var renderOnce = function renderOnce() {};
2849
3477
  var dirty = {
2850
3478
  preset: false,
2851
3479
  seed: false,
@@ -2853,19 +3481,37 @@
2853
3481
  colors: false,
2854
3482
  opacity: false,
2855
3483
  fallbackColor: false,
2856
- mouseColor: false
3484
+ mouseColor: false,
3485
+ quality: false,
3486
+ renderScale: false,
3487
+ paused: false,
3488
+ static: false,
3489
+ fps: false
2857
3490
  };
2858
3491
  var renderer;
2859
3492
  var useWebgl = merged.renderer !== 'canvas2d';
2860
3493
  if (useWebgl) {
2861
3494
  try {
2862
3495
  renderer = new CosmicRenderer(canvas, merged, {
2863
- dprCap: dprCapFor(merged.quality),
3496
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
2864
3497
  mouseColor: merged.mouseColor !== false,
2865
- eventTarget: host
3498
+ eventTarget: host,
3499
+ powerPreference: merged.powerPreference,
3500
+ onContextLost: function onContextLost() {
3501
+ contextLost = true;
3502
+ emitter.emit('contextlost', {});
3503
+ },
3504
+ onContextRestored: function onContextRestored() {
3505
+ contextLost = false;
3506
+ emitter.emit('contextrestored', {});
3507
+ renderOnce();
3508
+ wakeScheduler();
3509
+ }
2866
3510
  });
2867
3511
  } catch (error) {
2868
- renderer = new FallbackRenderer(canvas, merged);
3512
+ renderer = new FallbackRenderer(canvas, merged, {
3513
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale)
3514
+ });
2869
3515
  nextTick(function () {
2870
3516
  if (disposed) return;
2871
3517
  emitter.emit('error', {
@@ -2874,19 +3520,37 @@
2874
3520
  });
2875
3521
  }
2876
3522
  } else {
2877
- renderer = new FallbackRenderer(canvas, merged);
3523
+ renderer = new FallbackRenderer(canvas, merged, {
3524
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale)
3525
+ });
2878
3526
  }
2879
- var resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(function () {
2880
- return renderer.resize();
2881
- }) : null;
2882
- if (resizeObserver) resizeObserver.observe(host);else window.addEventListener('resize', renderer.resize);
2883
- var visibility = createVisibilityGuard(layer);
3527
+ renderer.resize();
2884
3528
  var animationTime = 0;
3529
+ var frameGate = createFrameGate(merged.fps);
3530
+ renderOnce = function renderOnce() {
3531
+ return renderer.draw(animationTime);
3532
+ };
3533
+ var resize = function resize() {
3534
+ renderer.resize();
3535
+ if (manuallyPaused || reducedPaused || staticMode) renderOnce();
3536
+ };
3537
+ var resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(resize) : null;
3538
+ if (resizeObserver) resizeObserver.observe(host);else window.addEventListener('resize', resize);
3539
+ var visibility = createVisibilityGuard(layer, wakeScheduler);
3540
+ var motionPreference = createReducedMotionPreference(merged.respectReducedMotion, function (matches) {
3541
+ reducedPaused = matches;
3542
+ if (matches) renderOnce();else wakeScheduler();
3543
+ });
3544
+ reducedPaused = motionPreference.matches();
3545
+ var isMotionPaused = function isMotionPaused() {
3546
+ return manuallyPaused || reducedPaused || staticMode || contextLost;
3547
+ };
3548
+ renderOnce();
2885
3549
  var unsubscribe = subscribeScheduler(function (delta) {
2886
3550
  animationTime += delta;
2887
- if (visibility.isVisible()) renderer.draw(animationTime);
3551
+ if (frameGate.shouldDraw(delta)) renderer.draw(animationTime);
2888
3552
  }, function () {
2889
- return paused;
3553
+ return isMotionPaused() || !visibility.isVisible();
2890
3554
  });
2891
3555
  nextTick(function () {
2892
3556
  if (disposed) return;
@@ -2905,11 +3569,15 @@
2905
3569
  var next = getPreset('capsule', ref);
2906
3570
  dirty.preset = true;
2907
3571
  Object.assign(preset, next);
3572
+ if (colorOverride) preset.colors = _toConsumableArray(colorOverride);
3573
+ if (seedOverride !== null) preset.seed = seedOverride;
3574
+ if (speedOverride !== null) preset.speed = speedOverride;
2908
3575
  var nextColors = preset.colors.map(normalizeColor);
2909
3576
  if (nextColors.every(Boolean)) preset.colors = nextColors;
2910
3577
  renderer.setPreset(_objectSpread2({}, preset));
2911
3578
  renderer.resize();
2912
3579
  syncLayerVars();
3580
+ if (isMotionPaused()) renderOnce();
2913
3581
  emitter.emit('presetchange', {
2914
3582
  preset: _objectSpread2({}, preset)
2915
3583
  });
@@ -2921,27 +3589,33 @@
2921
3589
  return !color;
2922
3590
  })) return this;
2923
3591
  dirty.colors = true;
3592
+ colorOverride = _toConsumableArray(next);
2924
3593
  preset.colors = next;
2925
3594
  renderer.setPreset(_objectSpread2(_objectSpread2({}, preset), {}, {
2926
3595
  colors: next
2927
3596
  }));
2928
3597
  syncLayerVars();
3598
+ if (isMotionPaused()) renderOnce();
2929
3599
  return this;
2930
3600
  },
2931
3601
  setSeed: function setSeed(seed) {
2932
- var value = Number(seed);
2933
- if (!Number.isFinite(value)) return this;
3602
+ var value = toFiniteNumber(seed);
3603
+ if (value === null) return this;
2934
3604
  dirty.seed = true;
3605
+ seedOverride = value;
2935
3606
  preset.seed = value;
2936
3607
  renderer.setPreset(_objectSpread2({}, preset));
3608
+ if (isMotionPaused()) renderOnce();
2937
3609
  return this;
2938
3610
  },
2939
3611
  setSpeed: function setSpeed(speed) {
2940
- var value = Number(speed);
2941
- if (!Number.isFinite(value)) return this;
3612
+ var value = toFiniteNumber(speed);
3613
+ if (value === null) return this;
2942
3614
  dirty.speed = true;
3615
+ speedOverride = value;
2943
3616
  preset.speed = value;
2944
3617
  renderer.setPreset(_objectSpread2({}, preset));
3618
+ if (isMotionPaused()) renderOnce();
2945
3619
  return this;
2946
3620
  },
2947
3621
  setFallbackColor: function setFallbackColor(value) {
@@ -2957,8 +3631,23 @@
2957
3631
  return this;
2958
3632
  },
2959
3633
  setQuality: function setQuality(quality) {
3634
+ dirty.quality = true;
2960
3635
  merged.quality = quality;
2961
- if (typeof renderer.setDprCap === 'function') renderer.setDprCap(dprCapFor(quality));
3636
+ if (typeof renderer.setDprCap === 'function') {
3637
+ renderer.setDprCap(effectiveDprCap(quality, merged.renderScale));
3638
+ }
3639
+ if (isMotionPaused()) renderOnce();
3640
+ return this;
3641
+ },
3642
+ setRenderScale: function setRenderScale(renderScale) {
3643
+ var value = Number(renderScale);
3644
+ if (!Number.isFinite(value)) return this;
3645
+ dirty.renderScale = true;
3646
+ merged.renderScale = Math.min(1, Math.max(0.25, value));
3647
+ if (typeof renderer.setDprCap === 'function') {
3648
+ renderer.setDprCap(effectiveDprCap(merged.quality, merged.renderScale));
3649
+ }
3650
+ if (isMotionPaused()) renderOnce();
2962
3651
  return this;
2963
3652
  },
2964
3653
  setOpacity: function setOpacity(value) {
@@ -2974,20 +3663,42 @@
2974
3663
  return this;
2975
3664
  },
2976
3665
  pause: function pause() {
2977
- paused = true;
3666
+ dirty.paused = true;
3667
+ manuallyPaused = true;
3668
+ renderOnce();
2978
3669
  return this;
2979
3670
  },
2980
3671
  resume: function resume() {
2981
- paused = false;
3672
+ dirty.paused = true;
3673
+ manuallyPaused = false;
3674
+ wakeScheduler();
3675
+ return this;
3676
+ },
3677
+ setPaused: function setPaused(value) {
3678
+ return value ? this.pause() : this.resume();
3679
+ },
3680
+ setStatic: function setStatic(value) {
3681
+ dirty.static = true;
3682
+ staticMode = value === true;
3683
+ merged.static = staticMode;
3684
+ if (staticMode) renderOnce();else wakeScheduler();
3685
+ return this;
3686
+ },
3687
+ setFps: function setFps(fps) {
3688
+ dirty.fps = true;
3689
+ merged.fps = frameGate.setFps(fps);
3690
+ wakeScheduler();
2982
3691
  return this;
2983
3692
  },
2984
3693
  dispose: function dispose() {
2985
3694
  disposed = true;
2986
3695
  unsubscribe();
2987
3696
  visibility.dispose();
2988
- if (resizeObserver) resizeObserver.disconnect();else window.removeEventListener('resize', renderer.resize);
3697
+ motionPreference.dispose();
3698
+ if (resizeObserver) resizeObserver.disconnect();else window.removeEventListener('resize', resize);
2989
3699
  renderer.dispose();
2990
3700
  layer.remove();
3701
+ releaseHostStyles(host);
2991
3702
  },
2992
3703
  get opacity() {
2993
3704
  return merged.opacity;
@@ -2998,6 +3709,21 @@
2998
3709
  get mouseColor() {
2999
3710
  return merged.mouseColor;
3000
3711
  },
3712
+ get quality() {
3713
+ return merged.quality;
3714
+ },
3715
+ get renderScale() {
3716
+ return merged.renderScale;
3717
+ },
3718
+ get paused() {
3719
+ return manuallyPaused;
3720
+ },
3721
+ get static() {
3722
+ return staticMode;
3723
+ },
3724
+ get fps() {
3725
+ return frameGate.getFps();
3726
+ },
3001
3727
  dirty: dirty
3002
3728
  };
3003
3729
  }
@@ -3016,6 +3742,7 @@
3016
3742
  exports.createColorBackground = createColorBackground;
3017
3743
  exports.createProgressCapsule = createProgressCapsule;
3018
3744
  exports.dprCapFor = dprCapFor;
3745
+ exports.effectiveDprCap = effectiveDprCap;
3019
3746
  exports.getPreset = getPreset;
3020
3747
  exports.hexToRgb01 = hexToRgb01$1;
3021
3748
  exports.hexToRgba = hexToRgba;