@5even7/dlc-ui 0.2.10 → 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/vue3.cjs CHANGED
@@ -189,9 +189,9 @@ function _unsupportedIterableToArray(r, a) {
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 @@ function createEmitter() {
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 @@ function dprCapFor(quality) {
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 @@ function normalizeOptions(defaults, preset, user) {
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 @@ var PALETTES$1 = {
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',
@@ -437,6 +443,11 @@ var DEFAULTS = {
437
443
  height: 160,
438
444
  quality: 'auto',
439
445
  renderer: 'auto',
446
+ renderScale: 1,
447
+ powerPreference: 'high-performance',
448
+ fps: 60,
449
+ paused: false,
450
+ static: false,
440
451
  respectReducedMotion: true,
441
452
  mouseColor: true,
442
453
  textRatio: 39
@@ -446,13 +457,22 @@ var DEFAULTS = {
446
457
  height: 104,
447
458
  min: 0,
448
459
  max: 100,
460
+ step: 'any',
449
461
  draggable: true,
462
+ keyboard: true,
450
463
  disabled: false,
451
464
  readonly: false,
465
+ direction: 'ltr',
466
+ precision: 0,
452
467
  valueSuffix: '%',
453
468
  edgeStyle: 'flow',
454
469
  quality: 'auto',
455
470
  renderer: 'auto',
471
+ renderScale: 1,
472
+ powerPreference: 'high-performance',
473
+ fps: 60,
474
+ paused: false,
475
+ static: false,
456
476
  textRatio: 54,
457
477
  showValue: true,
458
478
  respectReducedMotion: true
@@ -737,7 +757,7 @@ var CosmicRenderer = /*#__PURE__*/function () {
737
757
  alpha: false,
738
758
  antialias: false,
739
759
  depth: false,
740
- powerPreference: 'high-performance',
760
+ powerPreference: this.options.powerPreference || 'high-performance',
741
761
  preserveDrawingBuffer: false
742
762
  });
743
763
  if (!this.gl) throw new Error('WebGL2 is not available');
@@ -748,16 +768,19 @@ var CosmicRenderer = /*#__PURE__*/function () {
748
768
  this.motion = 0;
749
769
  this.motionTarget = 0;
750
770
  this.timeOffset = preset.seed * 0.73;
771
+ this.colors = preset.colors.map(hexToRgb01$1);
751
772
  this.visible = true;
752
773
  this.disposed = false;
753
774
  _assertClassBrand(_CosmicRenderer_brand, this, _setupGeometry).call(this);
754
775
  _assertClassBrand(_CosmicRenderer_brand, this, _bindEvents).call(this);
776
+ _assertClassBrand(_CosmicRenderer_brand, this, _bindContextEvents).call(this);
755
777
  this.resize();
756
778
  }
757
779
  return _createClass(CosmicRenderer, [{
758
780
  key: "setPreset",
759
781
  value: function setPreset(preset) {
760
782
  this.preset = _objectSpread2({}, preset);
783
+ this.colors = preset.colors.map(hexToRgb01$1);
761
784
  this.timeOffset = preset.seed * 0.73;
762
785
  }
763
786
  }, {
@@ -802,15 +825,14 @@ var CosmicRenderer = /*#__PURE__*/function () {
802
825
  if (this.canvas.width !== width || this.canvas.height !== height) {
803
826
  this.canvas.width = width;
804
827
  this.canvas.height = height;
805
- this.gl.viewport(0, 0, width, height);
806
828
  }
829
+ this.gl.viewport(0, 0, width, height);
807
830
  }
808
831
  }, {
809
832
  key: "draw",
810
833
  value: function draw(elapsedSeconds) {
811
834
  var paused = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
812
835
  if (this.disposed || !this.visible) return;
813
- this.resize();
814
836
  var gl = this.gl;
815
837
  this.pointer[0] += (this.pointerTarget[0] - this.pointer[0]) * 0.08;
816
838
  this.pointer[1] += (this.pointerTarget[1] - this.pointer[1]) * 0.08;
@@ -819,16 +841,15 @@ var CosmicRenderer = /*#__PURE__*/function () {
819
841
  gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
820
842
  gl.enableVertexAttribArray(this.locations.position);
821
843
  gl.vertexAttribPointer(this.locations.position, 2, gl.FLOAT, false, 0, 0);
822
- var colors = this.preset.colors.map(hexToRgb01$1);
823
844
  gl.uniform2f(this.locations.resolution, this.canvas.width, this.canvas.height);
824
845
  gl.uniform1f(this.locations.time, this.timeOffset + (paused ? 0 : elapsedSeconds * this.preset.speed));
825
846
  gl.uniform1f(this.locations.seed, this.preset.seed);
826
847
  gl.uniform1f(this.locations.motion, this.motion);
827
848
  gl.uniform2f(this.locations.pointer, this.pointer[0], this.pointer[1]);
828
- gl.uniform3fv(this.locations.colorA, colors[0]);
829
- gl.uniform3fv(this.locations.colorB, colors[1]);
830
- gl.uniform3fv(this.locations.colorC, colors[2]);
831
- gl.uniform3fv(this.locations.colorD, colors[3]);
849
+ gl.uniform3fv(this.locations.colorA, this.colors[0]);
850
+ gl.uniform3fv(this.locations.colorB, this.colors[1]);
851
+ gl.uniform3fv(this.locations.colorC, this.colors[2]);
852
+ gl.uniform3fv(this.locations.colorD, this.colors[3]);
832
853
  gl.drawArrays(gl.TRIANGLES, 0, 3);
833
854
  }
834
855
  }, {
@@ -839,6 +860,8 @@ var CosmicRenderer = /*#__PURE__*/function () {
839
860
  target.removeEventListener('pointermove', this.onPointerMove);
840
861
  target.removeEventListener('pointerdown', this.onPointerMove);
841
862
  target.removeEventListener('pointerleave', this.onPointerLeave);
863
+ this.canvas.removeEventListener('webglcontextlost', this.onContextLost, false);
864
+ this.canvas.removeEventListener('webglcontextrestored', this.onContextRestored, false);
842
865
  this.gl.deleteBuffer(this.buffer);
843
866
  this.gl.deleteProgram(this.program);
844
867
  var lose = this.gl.getExtension('WEBGL_lose_context');
@@ -895,6 +918,26 @@ function _bindEvents() {
895
918
  passive: true
896
919
  });
897
920
  }
921
+ function _bindContextEvents() {
922
+ var _this3 = this;
923
+ this.onContextLost = function (event) {
924
+ event.preventDefault();
925
+ if (_this3.disposed) return;
926
+ _this3.visible = false;
927
+ if (typeof _this3.options.onContextLost === 'function') _this3.options.onContextLost(event);
928
+ };
929
+ this.onContextRestored = function () {
930
+ if (_this3.disposed) return;
931
+ _this3.program = createProgram$1(_this3.gl);
932
+ _this3.locations = _assertClassBrand(_CosmicRenderer_brand, _this3, _getLocations).call(_this3);
933
+ _assertClassBrand(_CosmicRenderer_brand, _this3, _setupGeometry).call(_this3);
934
+ _this3.visible = true;
935
+ _this3.resize();
936
+ if (typeof _this3.options.onContextRestored === 'function') _this3.options.onContextRestored();
937
+ };
938
+ this.canvas.addEventListener('webglcontextlost', this.onContextLost, false);
939
+ this.canvas.addEventListener('webglcontextrestored', this.onContextRestored, false);
940
+ }
898
941
 
899
942
  function rgb(color) {
900
943
  var alpha = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
@@ -909,18 +952,20 @@ function rgb(color) {
909
952
  }
910
953
  var FallbackRenderer = /*#__PURE__*/function () {
911
954
  function FallbackRenderer(canvas, preset) {
955
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
912
956
  _classCallCheck(this, FallbackRenderer);
913
957
  this.canvas = canvas;
914
958
  this.preset = preset;
915
959
  this.context = canvas.getContext('2d');
916
960
  this.visible = true;
917
961
  this.timePhase = 0;
962
+ this.dprCap = options.dprCap || 1.5;
918
963
  }
919
964
  return _createClass(FallbackRenderer, [{
920
965
  key: "resize",
921
966
  value: function resize() {
922
967
  var rect = this.canvas.getBoundingClientRect();
923
- var dpr = Math.min(window.devicePixelRatio || 1, 1.5);
968
+ var dpr = Math.min(window.devicePixelRatio || 1, this.dprCap);
924
969
  var width = Math.max(2, Math.round(rect.width * dpr));
925
970
  var height = Math.max(2, Math.round(rect.height * dpr));
926
971
  if (this.canvas.width !== width || this.canvas.height !== height) {
@@ -961,7 +1006,6 @@ var FallbackRenderer = /*#__PURE__*/function () {
961
1006
  key: "draw",
962
1007
  value: function draw(time) {
963
1008
  if (!this.visible) return;
964
- this.resize();
965
1009
  this.drawNebula(time);
966
1010
  }
967
1011
  }, {
@@ -969,6 +1013,12 @@ var FallbackRenderer = /*#__PURE__*/function () {
969
1013
  value: function setPreset(preset) {
970
1014
  this.preset = preset;
971
1015
  }
1016
+ }, {
1017
+ key: "setDprCap",
1018
+ value: function setDprCap(cap) {
1019
+ this.dprCap = cap;
1020
+ this.resize();
1021
+ }
972
1022
  }, {
973
1023
  key: "randomize",
974
1024
  value: function randomize() {
@@ -986,10 +1036,10 @@ var FallbackRenderer = /*#__PURE__*/function () {
986
1036
  }]);
987
1037
  }();
988
1038
 
989
- /**
990
- * Document-level shared rAF scheduler. Every component instance subscribes
991
- * its own frame callback; the whole page runs ONE animation loop (like the
992
- * original demo), which avoids jank from many competing rAF loops.
1039
+ /**
1040
+ * Document-level shared rAF scheduler. Every component instance subscribes
1041
+ * its own frame callback; the whole page runs ONE animation loop (like the
1042
+ * original demo), which avoids jank from many competing rAF loops.
993
1043
  */
994
1044
  var subscribers = [];
995
1045
  var running = false;
@@ -997,11 +1047,7 @@ var rafId = 0;
997
1047
  var last = 0;
998
1048
  function tick(now) {
999
1049
  if (!running) return;
1000
- var delta = last ? Math.min((now - last) / 1000, 0.05) : 0;
1001
- last = now;
1002
- // Schedule the next frame BEFORE running callbacks so one throwing
1003
- // subscriber can never kill the whole animation loop.
1004
- rafId = requestAnimationFrame(tick);
1050
+ var activeItems = [];
1005
1051
  {
1006
1052
  var _iterator = _createForOfIteratorHelper(subscribers.slice()),
1007
1053
  _step;
@@ -1009,10 +1055,8 @@ function tick(now) {
1009
1055
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
1010
1056
  var item = _step.value;
1011
1057
  try {
1012
- if (!item.isPaused()) item.onFrame(delta, now);
1013
- } catch (error) {
1014
- console.warn('[dlc-ui] frame error:', error);
1015
- }
1058
+ if (!item.isPaused()) activeItems.push(item);
1059
+ } catch (_unused) {}
1016
1060
  }
1017
1061
  } catch (err) {
1018
1062
  _iterator.e(err);
@@ -1020,6 +1064,25 @@ function tick(now) {
1020
1064
  _iterator.f();
1021
1065
  }
1022
1066
  }
1067
+ if (activeItems.length === 0) {
1068
+ running = false;
1069
+ rafId = 0;
1070
+ last = 0;
1071
+ return;
1072
+ }
1073
+ var delta = last ? Math.min((now - last) / 1000, 0.05) : 0;
1074
+ last = now;
1075
+ // Schedule the next frame BEFORE running callbacks so one throwing
1076
+ // subscriber can never kill the whole animation loop.
1077
+ rafId = requestAnimationFrame(tick);
1078
+ for (var _i = 0, _activeItems = activeItems; _i < _activeItems.length; _i++) {
1079
+ var _item = _activeItems[_i];
1080
+ try {
1081
+ _item.onFrame(delta, now);
1082
+ } catch (error) {
1083
+ console.warn('[dlc-ui] frame error:', error);
1084
+ }
1085
+ }
1023
1086
  if (subscribers.length === 0) {
1024
1087
  cancelAnimationFrame(rafId);
1025
1088
  running = false;
@@ -1032,6 +1095,9 @@ function start() {
1032
1095
  last = 0;
1033
1096
  rafId = requestAnimationFrame(tick);
1034
1097
  }
1098
+ function wakeScheduler() {
1099
+ start();
1100
+ }
1035
1101
  function subscribeScheduler(onFrame, isPaused) {
1036
1102
  var item = {
1037
1103
  onFrame: onFrame,
@@ -1050,11 +1116,12 @@ function subscribeScheduler(onFrame, isPaused) {
1050
1116
  };
1051
1117
  }
1052
1118
 
1053
- /**
1054
- * Gates drawing on "element intersects viewport AND the page tab is visible".
1055
- * Falls back to always-visible when IntersectionObserver is unavailable.
1119
+ /**
1120
+ * Gates drawing on "element intersects viewport AND the page tab is visible".
1121
+ * Falls back to always-visible when IntersectionObserver is unavailable.
1056
1122
  */
1057
1123
  function createVisibilityGuard(element) {
1124
+ var onChange = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
1058
1125
  var intersecting = true;
1059
1126
  var pageVisible = typeof document === 'undefined' || !document.hidden;
1060
1127
  var disposed = false;
@@ -1064,6 +1131,7 @@ function createVisibilityGuard(element) {
1064
1131
  intersecting = entries.some(function (entry) {
1065
1132
  return entry.isIntersecting;
1066
1133
  });
1134
+ if (typeof onChange === 'function') onChange();
1067
1135
  }, {
1068
1136
  rootMargin: '180px'
1069
1137
  });
@@ -1071,6 +1139,7 @@ function createVisibilityGuard(element) {
1071
1139
  }
1072
1140
  var onVisibilityChange = function onVisibilityChange() {
1073
1141
  pageVisible = typeof document !== 'undefined' && !document.hidden;
1142
+ if (typeof onChange === 'function') onChange();
1074
1143
  };
1075
1144
  if (typeof document !== 'undefined') {
1076
1145
  document.addEventListener('visibilitychange', onVisibilityChange);
@@ -1106,18 +1175,69 @@ function nextTick(fn) {
1106
1175
  }
1107
1176
  }
1108
1177
 
1109
- function prefersReducedMotion$2() {
1110
- return typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches;
1178
+ function normalizeFps(value) {
1179
+ var fallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 60;
1180
+ var fps = Number(value);
1181
+ if (!Number.isFinite(fps)) return fallback;
1182
+ return Math.min(60, Math.max(1, fps));
1183
+ }
1184
+ function createFrameGate() {
1185
+ var initialFps = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 60;
1186
+ var fps = normalizeFps(initialFps);
1187
+ var elapsed = 0;
1188
+ return {
1189
+ shouldDraw: function shouldDraw(delta) {
1190
+ elapsed += delta;
1191
+ var interval = 1 / fps;
1192
+ if (elapsed + 0.0001 < interval) return false;
1193
+ elapsed %= interval;
1194
+ return true;
1195
+ },
1196
+ setFps: function setFps(value) {
1197
+ fps = normalizeFps(value, fps);
1198
+ elapsed = 0;
1199
+ return fps;
1200
+ },
1201
+ getFps: function getFps() {
1202
+ return fps;
1203
+ }
1204
+ };
1205
+ }
1206
+ function createReducedMotionPreference(enabled, onChange) {
1207
+ var query = enabled && typeof matchMedia !== 'undefined' ? matchMedia('(prefers-reduced-motion: reduce)') : null;
1208
+ var notify = function notify() {
1209
+ if (typeof onChange === 'function') onChange(Boolean(query && query.matches));
1210
+ };
1211
+ if (query) {
1212
+ if (typeof query.addEventListener === 'function') query.addEventListener('change', notify);else if (typeof query.addListener === 'function') query.addListener(notify);
1213
+ }
1214
+ return {
1215
+ matches: function matches() {
1216
+ return Boolean(query && query.matches);
1217
+ },
1218
+ dispose: function dispose() {
1219
+ if (!query) return;
1220
+ if (typeof query.removeEventListener === 'function') query.removeEventListener('change', notify);else if (typeof query.removeListener === 'function') query.removeListener(notify);
1221
+ }
1222
+ };
1223
+ }
1224
+
1225
+ function toFiniteNumber(value) {
1226
+ if (value == null || typeof value === 'boolean') return null;
1227
+ if (typeof value === 'string' && value.trim() === '') return null;
1228
+ var number = Number(value);
1229
+ return Number.isFinite(number) ? number : null;
1111
1230
  }
1112
1231
 
1113
- /**
1232
+ /**
1114
1233
  * Mount a cosmic (nebula) capsule into `container`.
1115
1234
  *
1116
1235
  * Options: preset (literary name), width, height (number=px or string with
1117
1236
  * px/%/vw/vh/em/rem), colors, seed, speed, textRatio (0-100, text region
1118
1237
  * width in percent), text (HTML string or DOM nodes for the text slot),
1119
1238
  * colorContent (HTML string or DOM nodes for the color slot), quality,
1120
- * renderer, respectReducedMotion, mouseColor, cssVars.
1239
+ * renderer, quality, renderScale, powerPreference, fps, paused/static,
1240
+ * respectReducedMotion, mouseColor, cssVars.
1121
1241
  */
1122
1242
  function createCapsule(container) {
1123
1243
  var _options$preset;
@@ -1127,10 +1247,19 @@ function createCapsule(container) {
1127
1247
  }
1128
1248
  var preset = _objectSpread2({}, getPreset('capsule', (_options$preset = options.preset) !== null && _options$preset !== void 0 ? _options$preset : '初光'));
1129
1249
  var merged = normalizeOptions(DEFAULTS.capsule, preset, options);
1130
- var normalizedColors = merged.colors.map(normalizeColor);
1131
- if (normalizedColors.every(Boolean)) preset.colors = normalizedColors;
1132
- preset.seed = merged.seed;
1133
- preset.speed = merged.speed;
1250
+ var normalizedColors = Array.isArray(merged.colors) ? merged.colors.map(normalizeColor) : [];
1251
+ if (normalizedColors.length === 4 && normalizedColors.every(Boolean)) preset.colors = normalizedColors;
1252
+ var initialSeed = toFiniteNumber(merged.seed);
1253
+ var initialSpeed = toFiniteNumber(merged.speed);
1254
+ if (initialSeed !== null) preset.seed = initialSeed;
1255
+ if (initialSpeed !== null) preset.speed = initialSpeed;
1256
+ merged.colors = _toConsumableArray(preset.colors);
1257
+ merged.seed = preset.seed;
1258
+ merged.speed = preset.speed;
1259
+ var optionColors = Array.isArray(options.colors) ? options.colors.map(normalizeColor) : [];
1260
+ var colorOverride = optionColors.length === 4 && optionColors.every(Boolean) ? _toConsumableArray(optionColors) : null;
1261
+ var seedOverride = options.seed !== undefined ? toFiniteNumber(options.seed) : null;
1262
+ var speedOverride = options.speed !== undefined ? toFiniteNumber(options.speed) : null;
1134
1263
  var customLabel = typeof options.label === 'string' && options.label ? options.label : null;
1135
1264
  var root = document.createElement('div');
1136
1265
  root.className = 'hj-capsule-root hj-capsule-cosmic';
@@ -1182,25 +1311,49 @@ function createCapsule(container) {
1182
1311
  root.appendChild(canvas);
1183
1312
  container.appendChild(root);
1184
1313
  var emitter = createEmitter();
1185
- var paused = merged.respectReducedMotion && prefersReducedMotion$2();
1314
+ var manuallyPaused = merged.paused === true;
1315
+ var reducedPaused = false;
1316
+ var staticMode = merged.static === true;
1317
+ var contextLost = false;
1186
1318
  var disposed = false;
1319
+ var renderOnce = function renderOnce() {};
1187
1320
  var dirty = {
1188
1321
  preset: false,
1189
1322
  seed: false,
1190
1323
  speed: false,
1191
1324
  colors: false,
1192
1325
  textRatio: false,
1193
- cssVars: false
1326
+ cssVars: false,
1327
+ mouseColor: false,
1328
+ quality: false,
1329
+ renderScale: false,
1330
+ paused: false,
1331
+ static: false,
1332
+ fps: false
1194
1333
  };
1195
1334
  var renderer;
1196
1335
  var useWebgl = merged.renderer !== 'canvas2d';
1197
1336
  if (useWebgl) {
1198
1337
  try {
1199
1338
  renderer = new CosmicRenderer(canvas, merged, {
1200
- dprCap: dprCapFor(merged.quality)
1339
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
1340
+ mouseColor: merged.mouseColor !== false,
1341
+ powerPreference: merged.powerPreference,
1342
+ onContextLost: function onContextLost() {
1343
+ contextLost = true;
1344
+ emitter.emit('contextlost', {});
1345
+ },
1346
+ onContextRestored: function onContextRestored() {
1347
+ contextLost = false;
1348
+ emitter.emit('contextrestored', {});
1349
+ renderOnce();
1350
+ wakeScheduler();
1351
+ }
1201
1352
  });
1202
1353
  } catch (error) {
1203
- renderer = new FallbackRenderer(canvas, merged);
1354
+ renderer = new FallbackRenderer(canvas, merged, {
1355
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale)
1356
+ });
1204
1357
  nextTick(function () {
1205
1358
  if (disposed) return;
1206
1359
  emitter.emit('error', {
@@ -1209,8 +1362,15 @@ function createCapsule(container) {
1209
1362
  });
1210
1363
  }
1211
1364
  } else {
1212
- renderer = new FallbackRenderer(canvas, merged);
1365
+ renderer = new FallbackRenderer(canvas, merged, {
1366
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale)
1367
+ });
1213
1368
  }
1369
+ var animationTime = 0;
1370
+ var frameGate = createFrameGate(merged.fps);
1371
+ renderOnce = function renderOnce() {
1372
+ return renderer.draw(animationTime);
1373
+ };
1214
1374
  var applySize = function applySize() {
1215
1375
  root.style.width = parseSize(merged.width);
1216
1376
  root.style.height = parseSize(merged.height);
@@ -1228,11 +1388,21 @@ function createCapsule(container) {
1228
1388
  renderer.resize();
1229
1389
  };
1230
1390
  applySize();
1231
- var resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(function () {
1232
- return renderer.resize();
1233
- }) : null;
1234
- if (resizeObserver) resizeObserver.observe(root);else window.addEventListener('resize', renderer.resize);
1235
- var visibility = createVisibilityGuard(root);
1391
+ var resize = function resize() {
1392
+ renderer.resize();
1393
+ if (manuallyPaused || reducedPaused || staticMode) renderOnce();
1394
+ };
1395
+ var resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(resize) : null;
1396
+ if (resizeObserver) resizeObserver.observe(root);else window.addEventListener('resize', resize);
1397
+ var visibility = createVisibilityGuard(root, wakeScheduler);
1398
+ var motionPreference = createReducedMotionPreference(merged.respectReducedMotion, function (matches) {
1399
+ reducedPaused = matches;
1400
+ if (matches) renderOnce();else wakeScheduler();
1401
+ });
1402
+ reducedPaused = motionPreference.matches();
1403
+ var isMotionPaused = function isMotionPaused() {
1404
+ return manuallyPaused || reducedPaused || staticMode || contextLost;
1405
+ };
1236
1406
  root.addEventListener('pointerenter', function () {
1237
1407
  return emitter.emit('pointerenter', {
1238
1408
  preset: _objectSpread2({}, preset)
@@ -1267,12 +1437,12 @@ function createCapsule(container) {
1267
1437
  preset: _objectSpread2({}, preset)
1268
1438
  });
1269
1439
  });
1270
- var animationTime = 0;
1440
+ renderOnce();
1271
1441
  var unsubscribe = subscribeScheduler(function (delta) {
1272
1442
  animationTime += delta;
1273
- if (visibility.isVisible()) renderer.draw(animationTime);
1443
+ if (frameGate.shouldDraw(delta)) renderer.draw(animationTime);
1274
1444
  }, function () {
1275
- return paused;
1445
+ return isMotionPaused() || !visibility.isVisible();
1276
1446
  });
1277
1447
  nextTick(function () {
1278
1448
  if (disposed) return;
@@ -1295,11 +1465,15 @@ function createCapsule(container) {
1295
1465
  var next = getPreset('capsule', ref);
1296
1466
  dirty.preset = true;
1297
1467
  Object.assign(preset, next);
1468
+ if (colorOverride) preset.colors = _toConsumableArray(colorOverride);
1469
+ if (seedOverride !== null) preset.seed = seedOverride;
1470
+ if (speedOverride !== null) preset.speed = speedOverride;
1298
1471
  var nextColors = preset.colors.map(normalizeColor);
1299
1472
  if (nextColors.every(Boolean)) preset.colors = nextColors;
1300
1473
  renderer.setPreset(_objectSpread2({}, preset));
1301
1474
  syncTheme();
1302
1475
  renderer.resize();
1476
+ if (isMotionPaused()) renderOnce();
1303
1477
  emitter.emit('presetchange', {
1304
1478
  preset: _objectSpread2({}, next)
1305
1479
  });
@@ -1311,26 +1485,32 @@ function createCapsule(container) {
1311
1485
  return !color;
1312
1486
  })) return this;
1313
1487
  dirty.colors = true;
1488
+ colorOverride = _toConsumableArray(next);
1314
1489
  preset.colors = next;
1315
1490
  renderer.setPreset(_objectSpread2(_objectSpread2({}, preset), {}, {
1316
1491
  colors: next
1317
1492
  }));
1493
+ if (isMotionPaused()) renderOnce();
1318
1494
  return this;
1319
1495
  },
1320
1496
  setSeed: function setSeed(seed) {
1321
- var value = Number(seed);
1322
- if (!Number.isFinite(value)) return this;
1497
+ var value = toFiniteNumber(seed);
1498
+ if (value === null) return this;
1323
1499
  dirty.seed = true;
1500
+ seedOverride = value;
1324
1501
  preset.seed = value;
1325
1502
  renderer.setPreset(_objectSpread2({}, preset));
1503
+ if (isMotionPaused()) renderOnce();
1326
1504
  return this;
1327
1505
  },
1328
1506
  setSpeed: function setSpeed(speed) {
1329
- var value = Number(speed);
1330
- if (!Number.isFinite(value)) return this;
1507
+ var value = toFiniteNumber(speed);
1508
+ if (value === null) return this;
1331
1509
  dirty.speed = true;
1510
+ speedOverride = value;
1332
1511
  preset.speed = value;
1333
1512
  renderer.setPreset(_objectSpread2({}, preset));
1513
+ if (isMotionPaused()) renderOnce();
1334
1514
  return this;
1335
1515
  },
1336
1516
  setText: function setText(content) {
@@ -1366,6 +1546,7 @@ function createCapsule(container) {
1366
1546
  return this;
1367
1547
  },
1368
1548
  setMouseColor: function setMouseColor(value) {
1549
+ dirty.mouseColor = true;
1369
1550
  merged.mouseColor = value !== false;
1370
1551
  if (typeof renderer.setMouseColor === 'function') renderer.setMouseColor(merged.mouseColor);
1371
1552
  return this;
@@ -1380,6 +1561,7 @@ function createCapsule(container) {
1380
1561
  merged.height = height;
1381
1562
  }
1382
1563
  applySize();
1564
+ if (isMotionPaused()) renderOnce();
1383
1565
  return this;
1384
1566
  },
1385
1567
  randomize: function randomize() {
@@ -1387,23 +1569,59 @@ function createCapsule(container) {
1387
1569
  return this;
1388
1570
  },
1389
1571
  pause: function pause() {
1390
- paused = true;
1572
+ dirty.paused = true;
1573
+ manuallyPaused = true;
1574
+ renderOnce();
1391
1575
  return this;
1392
1576
  },
1393
1577
  resume: function resume() {
1394
- paused = false;
1578
+ dirty.paused = true;
1579
+ manuallyPaused = false;
1580
+ wakeScheduler();
1581
+ return this;
1582
+ },
1583
+ setPaused: function setPaused(value) {
1584
+ return value ? this.pause() : this.resume();
1585
+ },
1586
+ setStatic: function setStatic(value) {
1587
+ dirty.static = true;
1588
+ staticMode = value === true;
1589
+ merged.static = staticMode;
1590
+ if (staticMode) renderOnce();else wakeScheduler();
1591
+ return this;
1592
+ },
1593
+ setFps: function setFps(fps) {
1594
+ dirty.fps = true;
1595
+ merged.fps = frameGate.setFps(fps);
1596
+ wakeScheduler();
1395
1597
  return this;
1396
1598
  },
1397
1599
  setQuality: function setQuality(quality) {
1600
+ dirty.quality = true;
1398
1601
  merged.quality = quality;
1399
- if (typeof renderer.setDprCap === 'function') renderer.setDprCap(dprCapFor(quality));
1602
+ if (typeof renderer.setDprCap === 'function') {
1603
+ renderer.setDprCap(effectiveDprCap(quality, merged.renderScale));
1604
+ }
1605
+ if (isMotionPaused()) renderOnce();
1606
+ return this;
1607
+ },
1608
+ setRenderScale: function setRenderScale(renderScale) {
1609
+ var value = Number(renderScale);
1610
+ if (!Number.isFinite(value)) return this;
1611
+ dirty.renderScale = true;
1612
+ merged.renderScale = Math.min(1, Math.max(0.25, value));
1613
+ if (typeof renderer.setDprCap === 'function') {
1614
+ renderer.setDprCap(effectiveDprCap(merged.quality, merged.renderScale));
1615
+ }
1616
+ if (isMotionPaused()) renderOnce();
1400
1617
  return this;
1401
1618
  },
1402
1619
  dispose: function dispose() {
1403
1620
  disposed = true;
1404
1621
  unsubscribe();
1405
1622
  visibility.dispose();
1406
- if (resizeObserver) resizeObserver.disconnect();else window.removeEventListener('resize', renderer.resize);
1623
+ motionPreference.dispose();
1624
+ if (resizeObserver) resizeObserver.disconnect();else window.removeEventListener('resize', resize);
1407
1625
  renderer.dispose();
1408
1626
  root.remove();
1409
1627
  },
@@ -1413,6 +1631,24 @@ function createCapsule(container) {
1413
1631
  get cssVars() {
1414
1632
  return merged.cssVars;
1415
1633
  },
1634
+ get mouseColor() {
1635
+ return merged.mouseColor;
1636
+ },
1637
+ get quality() {
1638
+ return merged.quality;
1639
+ },
1640
+ get renderScale() {
1641
+ return merged.renderScale;
1642
+ },
1643
+ get paused() {
1644
+ return manuallyPaused;
1645
+ },
1646
+ get static() {
1647
+ return staticMode;
1648
+ },
1649
+ get fps() {
1650
+ return frameGate.getFps();
1651
+ },
1416
1652
  dirty: dirty
1417
1653
  };
1418
1654
  }
@@ -1522,7 +1758,7 @@ var MOTION_SCALE_FACTORS = {
1522
1758
  'tide': 1.18
1523
1759
  };
1524
1760
  var VERTEX_SHADER = "#version 300 es\nin vec2 a_position;\nout vec2 v_uv;\nvoid main() {\n v_uv = a_position * 0.5 + 0.5;\n gl_Position = vec4(a_position, 0.0, 1.0);\n}";
1525
- var FRAGMENT_SHADER = "#version 300 es\nprecision highp float;\n\nin vec2 v_uv;\nout vec4 outColor;\n\nuniform vec2 u_resolution;\nuniform float u_time;\nuniform float u_progress;\nuniform float u_seed;\nuniform float u_profile;\nuniform sampler2D u_motion;\nuniform sampler2D u_effect;\nuniform float u_hasEffect;\nuniform float u_effectFrames;\nuniform float u_motionDuration;\nuniform float u_motionScale;\nuniform vec3 u_dark;\nuniform vec3 u_accentA;\nuniform vec3 u_accentB;\nuniform vec3 u_glow;\n\nfloat hash21(vec2 p) {\n p = fract(p * vec2(123.34, 456.21));\n p += dot(p, p + 45.32 + u_seed * 11.7);\n return fract(p.x * p.y);\n}\n\nfloat noise(vec2 p) {\n vec2 i = floor(p);\n vec2 f = fract(p);\n f = f * f * (3.0 - 2.0 * f);\n float a = hash21(i);\n float b = hash21(i + vec2(1.0, 0.0));\n float c = hash21(i + vec2(0.0, 1.0));\n float d = hash21(i + vec2(1.0, 1.0));\n return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);\n}\n\nfloat fbm(vec2 p) {\n float value = 0.0;\n float amplitude = 0.55;\n mat2 rotation = mat2(0.82, 0.57, -0.57, 0.82);\n for (int i = 0; i < 6; i++) {\n value += noise(p) * amplitude;\n p = rotation * p * 2.02 + 13.7;\n amplitude *= 0.48;\n }\n return value;\n}\n\nfloat gaussian(float value, float center, float width) {\n float delta = (value - center) / max(width, 0.0001);\n return exp(-delta * delta);\n}\n\nfloat profileMix(float model, float agent, float visual) {\n if (u_profile < 0.5) return model;\n if (u_profile < 1.5) return agent;\n return visual;\n}\n\nfloat motionSample(float y, float t) {\n float phase = fract(t / max(u_motionDuration, 0.001));\n float captured = texture(u_motion, vec2(phase, 1.0 - clamp(y, 0.0, 1.0))).r;\n return (captured * 2.0 - 1.0) * u_motionScale;\n}\n\nfloat edgeDisplacement(float y, float t) {\n return motionSample(y, t);\n}\n\nfloat flowDisplacement(float y, float t) {\n return (\n motionSample(y - 0.024, t) * 0.08 +\n motionSample(y - 0.012, t) * 0.18 +\n motionSample(y, t) * 0.48 +\n motionSample(y + 0.012, t) * 0.18 +\n motionSample(y + 0.024, t) * 0.08\n );\n}\n\nfloat ellipseRing(vec2 p, float radius, float width) {\n return gaussian(length(p), radius, width);\n}\n\nvoid main() {\n vec2 uv = v_uv;\n float t = u_time;\n float edge = u_progress + edgeDisplacement(uv.y, t);\n float flowEdge = u_progress + flowDisplacement(uv.y, t);\n float d = uv.x - edge;\n float fd = uv.x - flowEdge;\n\n vec3 rightBase = vec3(0.125, 0.129, 0.145);\n vec3 color = rightBase;\n\n float leftMask = 1.0 - smoothstep(-0.001, 0.002, d);\n color = mix(color, u_dark, leftMask * profileMix(0.96, 0.92, 0.96));\n\n vec2 flowP = vec2((fd + 0.10) * 6.2, uv.y * 1.95);\n float flowA = fbm(flowP + vec2(-t * 0.22, t * 0.27) + u_seed * 1.7);\n float flowB = fbm(flowP * 1.52 + vec2(t * 0.28, -t * 0.36) + 8.2 + u_seed);\n float flowC = fbm(flowP * 2.25 + vec2(-t * 0.41, t * 0.46) + 19.0);\n\n float farCenter = profileMix(-0.060, -0.079, -0.045) + (flowA - 0.5) * profileMix(0.018, 0.022, 0.014);\n float midCenter = profileMix(-0.039, -0.052, -0.030) + (flowB - 0.5) * profileMix(0.013, 0.016, 0.010);\n float hotCenter = profileMix(-0.026, -0.029, -0.023) + (flowC - 0.5) * 0.010;\n\n float farBand = gaussian(fd, farCenter, profileMix(0.035, 0.049, 0.030));\n float midBand = gaussian(fd, midCenter, profileMix(0.026, 0.034, 0.026));\n float hotBand = gaussian(fd, hotCenter, profileMix(0.023, 0.027, 0.026));\n float darkTrough = gaussian(fd, profileMix(-0.050, -0.058, -0.044) + (flowB - 0.5) * 0.010, profileMix(0.020, 0.025, 0.021));\n\n float ringY = 0.47 + sin(t * 0.58 + u_seed * 2.4) * 0.12;\n vec2 ringP = vec2((fd + 0.086) / 0.078, (uv.y - ringY) / 0.25);\n ringP += vec2((flowB - 0.5) * 0.08, (flowA - 0.5) * 0.06);\n float ringTexture = fbm(ringP * 2.15 + vec2(t * 0.18, -t * 0.14) + u_seed * 1.9);\n float ring = ellipseRing(ringP, 0.66, 0.32) * (0.30 + 0.64 * ringTexture);\n float ringCore = gaussian(length(ringP), 0.25, 0.25);\n float ringPulse = smoothstep(0.58, 0.90, 0.5 + 0.5 * sin(t * 0.82 + u_seed * 4.1));\n float modelRing = ring * ringPulse * (1.0 - step(0.5, u_profile));\n float visualRing = ring * 0.16 * step(1.5, u_profile) * ringPulse;\n\n float cloudGate = leftMask * smoothstep(-0.30, -0.008, fd);\n float textureA = smoothstep(0.24, 0.92, flowA * 0.72 + flowB * 0.42);\n float textureB = smoothstep(0.28, 0.94, flowB * 0.68 + flowC * 0.38);\n\n vec3 hotColor = u_accentB;\n color += u_accentA * farBand * cloudGate * (0.07 + textureA * profileMix(0.42, 0.24, 0.40));\n color += u_accentB * midBand * cloudGate * (0.15 + textureB * profileMix(0.70, 0.46, 0.68));\n color += hotColor * hotBand * cloudGate * profileMix(0.88, 0.62, 0.84);\n float modelMask = 1.0 - step(0.5, u_profile);\n color += u_accentA * (modelRing + visualRing) * cloudGate * profileMix(0.54, 0.0, 0.34);\n color *= 1.0 - darkTrough * profileMix(0.44, 0.24, 0.24) * cloudGate;\n color *= 1.0 - ringCore * modelMask * ringPulse * 0.44 * cloudGate;\n\n float broadHalo = exp(-abs(d) * 96.0);\n float innerHalo = exp(-abs(d) * 176.0);\n float colorCore = exp(-abs(d) * 360.0);\n float sharpCore = exp(-abs(d) * 760.0);\n float leftGate = 1.0 - smoothstep(-0.003, 0.005, d);\n\n color += u_accentA * broadHalo * leftGate * profileMix(0.09, 0.04, 0.06);\n color += hotColor * innerHalo * leftGate * profileMix(0.76, 0.72, 0.78);\n color += u_glow * colorCore * profileMix(0.72, 0.34, 0.24);\n\n float whiteStrength = profileMix(0.10, 0.0, 0.0);\n color += vec3(1.0, 0.99, 0.91) * sharpCore * whiteStrength;\n\n float rightCut = smoothstep(0.001, 0.006, d);\n color = mix(color, rightBase, rightCut);\n\n float effectX = (d * 1257.0 + 260.0) / 320.0;\n float atlasPhase = fract(t / 12.0) * u_effectFrames;\n float atlasFrameA = floor(atlasPhase);\n float atlasFrameB = mod(atlasFrameA + 1.0, u_effectFrames);\n float atlasMix = smoothstep(0.0, 1.0, fract(atlasPhase));\n float atlasXA = (atlasFrameA + clamp(effectX, 0.0, 1.0)) / u_effectFrames;\n float atlasXB = (atlasFrameB + clamp(effectX, 0.0, 1.0)) / u_effectFrames;\n vec3 referenceA = texture(u_effect, vec2(atlasXA, uv.y)).rgb;\n vec3 referenceB = texture(u_effect, vec2(atlasXB, uv.y)).rgb;\n vec3 referenceColor = mix(referenceA, referenceB, atlasMix);\n float stripMask = smoothstep(0.0, 0.018, effectX) * (1.0 - smoothstep(0.982, 1.0, effectX));\n float referenceLeft = 1.0 - smoothstep(-0.026, -0.012, d);\n color = mix(color, referenceColor, stripMask * referenceLeft * u_hasEffect);\n\n outColor = vec4(clamp(color, 0.0, 1.0), 1.0);\n}";
1761
+ var FRAGMENT_SHADER = "#version 300 es\nprecision highp float;\n\nin vec2 v_uv;\nout vec4 outColor;\n\nuniform vec2 u_resolution;\nuniform float u_time;\nuniform float u_progress;\nuniform float u_seed;\nuniform float u_profile;\nuniform sampler2D u_motion;\nuniform sampler2D u_effect;\nuniform float u_hasEffect;\nuniform float u_effectFrames;\nuniform float u_motionDuration;\nuniform float u_motionScale;\nuniform vec3 u_dark;\nuniform vec3 u_accentA;\nuniform vec3 u_accentB;\nuniform vec3 u_glow;\n\nfloat hash21(vec2 p) {\n p = fract(p * vec2(123.34, 456.21));\n p += dot(p, p + 45.32 + u_seed * 11.7);\n return fract(p.x * p.y);\n}\n\nfloat noise(vec2 p) {\n vec2 i = floor(p);\n vec2 f = fract(p);\n f = f * f * (3.0 - 2.0 * f);\n float a = hash21(i);\n float b = hash21(i + vec2(1.0, 0.0));\n float c = hash21(i + vec2(0.0, 1.0));\n float d = hash21(i + vec2(1.0, 1.0));\n return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);\n}\n\nfloat fbm(vec2 p) {\n float value = 0.0;\n float amplitude = 0.55;\n mat2 rotation = mat2(0.82, 0.57, -0.57, 0.82);\n for (int i = 0; i < 6; i++) {\n value += noise(p) * amplitude;\n p = rotation * p * 2.02 + 13.7;\n amplitude *= 0.48;\n }\n return value;\n}\n\nfloat gaussian(float value, float center, float width) {\n float delta = (value - center) / max(width, 0.0001);\n return exp(-delta * delta);\n}\n\nfloat profileMix(float model, float agent, float visual) {\n if (u_profile < 0.5) return model;\n if (u_profile < 1.5) return agent;\n return visual;\n}\n\nfloat motionSample(float y, float t) {\n float phase = fract(t / max(u_motionDuration, 0.001));\n float captured = texture(u_motion, vec2(phase, 1.0 - clamp(y, 0.0, 1.0))).r;\n return (captured * 2.0 - 1.0) * u_motionScale;\n}\n\nfloat edgeDisplacement(float y, float t) {\n return motionSample(y, t);\n}\n\nfloat flowDisplacement(float y, float t) {\n return (\n motionSample(y - 0.024, t) * 0.08 +\n motionSample(y - 0.012, t) * 0.18 +\n motionSample(y, t) * 0.48 +\n motionSample(y + 0.012, t) * 0.18 +\n motionSample(y + 0.024, t) * 0.08\n );\n}\n\nfloat ellipseRing(vec2 p, float radius, float width) {\n return gaussian(length(p), radius, width);\n}\n\nvoid main() {\n vec2 uv = v_uv;\n float t = u_time;\n float edge = u_progress + edgeDisplacement(uv.y, t);\n float flowEdge = u_progress + flowDisplacement(uv.y, t);\n float d = uv.x - edge;\n float fd = uv.x - flowEdge;\n\n vec3 rightBase = vec3(0.125, 0.129, 0.145);\n vec3 color = rightBase;\n\n float leftMask = 1.0 - smoothstep(-0.001, 0.002, d);\n color = mix(color, u_dark, leftMask * profileMix(0.96, 0.92, 0.96));\n\n vec2 flowP = vec2((fd + 0.10) * 6.2, uv.y * 1.95);\n float flowA = fbm(flowP + vec2(-t * 0.22, t * 0.27) + u_seed * 1.7);\n float flowB = fbm(flowP * 1.52 + vec2(t * 0.28, -t * 0.36) + 8.2 + u_seed);\n float flowC = fbm(flowP * 2.25 + vec2(-t * 0.41, t * 0.46) + 19.0);\n\n float farCenter = profileMix(-0.060, -0.079, -0.045) + (flowA - 0.5) * profileMix(0.018, 0.022, 0.014);\n float midCenter = profileMix(-0.039, -0.052, -0.030) + (flowB - 0.5) * profileMix(0.013, 0.016, 0.010);\n float hotCenter = profileMix(-0.026, -0.029, -0.023) + (flowC - 0.5) * 0.010;\n\n float farBand = gaussian(fd, farCenter, profileMix(0.035, 0.049, 0.030));\n float midBand = gaussian(fd, midCenter, profileMix(0.026, 0.034, 0.026));\n float hotBand = gaussian(fd, hotCenter, profileMix(0.023, 0.027, 0.026));\n float darkTrough = gaussian(fd, profileMix(-0.050, -0.058, -0.044) + (flowB - 0.5) * 0.010, profileMix(0.020, 0.025, 0.021));\n\n float ringY = 0.47 + sin(t * 0.58 + u_seed * 2.4) * 0.12;\n vec2 ringP = vec2((fd + 0.086) / 0.078, (uv.y - ringY) / 0.25);\n ringP += vec2((flowB - 0.5) * 0.08, (flowA - 0.5) * 0.06);\n float ringTexture = fbm(ringP * 2.15 + vec2(t * 0.18, -t * 0.14) + u_seed * 1.9);\n float ring = ellipseRing(ringP, 0.66, 0.32) * (0.30 + 0.64 * ringTexture);\n float ringCore = gaussian(length(ringP), 0.25, 0.25);\n float ringPulse = smoothstep(0.58, 0.90, 0.5 + 0.5 * sin(t * 0.82 + u_seed * 4.1));\n float modelRing = ring * ringPulse * (1.0 - step(0.5, u_profile));\n float visualRing = ring * 0.16 * step(1.5, u_profile) * ringPulse;\n\n float cloudGate = leftMask * smoothstep(-0.30, -0.008, fd);\n float textureA = smoothstep(0.24, 0.92, flowA * 0.72 + flowB * 0.42);\n float textureB = smoothstep(0.28, 0.94, flowB * 0.68 + flowC * 0.38);\n\n vec3 hotColor = u_accentB;\n color += u_accentA * farBand * cloudGate * (0.07 + textureA * profileMix(0.42, 0.24, 0.40));\n color += u_accentB * midBand * cloudGate * (0.15 + textureB * profileMix(0.70, 0.46, 0.68));\n color += hotColor * hotBand * cloudGate * profileMix(0.88, 0.62, 0.84);\n float modelMask = 1.0 - step(0.5, u_profile);\n color += u_accentA * (modelRing + visualRing) * cloudGate * profileMix(0.54, 0.0, 0.34);\n color *= 1.0 - darkTrough * profileMix(0.44, 0.24, 0.24) * cloudGate;\n color *= 1.0 - ringCore * modelMask * ringPulse * 0.44 * cloudGate;\n\n float broadHalo = exp(-abs(d) * 96.0);\n float innerHalo = exp(-abs(d) * 176.0);\n float colorCore = exp(-abs(d) * 360.0);\n float sharpCore = exp(-abs(d) * 760.0);\n float leftGate = 1.0 - smoothstep(-0.003, 0.005, d);\n\n color += u_accentA * broadHalo * leftGate * profileMix(0.09, 0.04, 0.06);\n color += hotColor * innerHalo * leftGate * profileMix(0.76, 0.72, 0.78);\n color += u_glow * colorCore * profileMix(0.72, 0.34, 0.24);\n\n float whiteStrength = profileMix(0.10, 0.0, 0.0);\n color += vec3(1.0, 0.99, 0.91) * sharpCore * whiteStrength;\n\n float rightCut = smoothstep(0.001, 0.006, d);\n color = mix(color, rightBase, rightCut);\n\n float effectX = (d * 1257.0 + 260.0) / 320.0;\n float atlasPhase = fract(t / 12.0) * u_effectFrames;\n float atlasFrameA = floor(atlasPhase);\n float atlasFrameB = mod(atlasFrameA + 1.0, u_effectFrames);\n float atlasMix = smoothstep(0.0, 1.0, fract(atlasPhase));\n float atlasXA = (atlasFrameA + clamp(effectX, 0.0, 1.0)) / u_effectFrames;\n float atlasXB = (atlasFrameB + clamp(effectX, 0.0, 1.0)) / u_effectFrames;\n vec3 referenceA = texture(u_effect, vec2(atlasXA, uv.y)).rgb;\n vec3 referenceB = texture(u_effect, vec2(atlasXB, uv.y)).rgb;\n vec3 referenceColor = mix(referenceA, referenceB, atlasMix);\n float stripMask = smoothstep(0.0, 0.018, effectX) * (1.0 - smoothstep(0.982, 1.0, effectX));\n float referenceLeft = 1.0 - smoothstep(-0.026, -0.012, d);\n // \u53C2\u8003\u56FE\u96C6\u53EA\u63D0\u4F9B\u4EAE\u5EA6\u7ED3\u6784\uFF0C\u989C\u8272\u59CB\u7EC8\u7531\u7528\u6237 colors\uFF08u_dark / u_accentA /\n // u_accentB / u_glow\uFF09\u51B3\u5B9A\uFF1A\u8FD9\u6837 setColors / colors \u5C5E\u6027\u5728 WebGL \u8DEF\u5F84\u4E0B\n // \u771F\u5B9E\u751F\u6548\uFF0C\u6539\u8272\u6709\u53EF\u89C1\u53CD\u9988\uFF0C\u800C\u4E0D\u662F\u88AB\u56FE\u96C6\u6574\u4F53\u8986\u76D6\u3002\n float referenceLuma = dot(referenceColor, vec3(0.299, 0.587, 0.114));\n vec3 tintedColor = color * (0.42 + 0.86 * referenceLuma);\n color = mix(color, tintedColor, stripMask * referenceLeft * u_hasEffect);\n\n outColor = vec4(clamp(color, 0.0, 1.0), 1.0);\n}";
1526
1762
  function compileShader(gl, type, source) {
1527
1763
  var shader = gl.createShader(type);
1528
1764
  gl.shaderSource(shader, source);
@@ -1553,64 +1789,95 @@ function createProgram(gl) {
1553
1789
  var ProgressFlowRenderer = /*#__PURE__*/function () {
1554
1790
  function ProgressFlowRenderer(canvas, preset) {
1555
1791
  var _PROFILE_INDEX$preset;
1792
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
1556
1793
  _classCallCheck(this, ProgressFlowRenderer);
1557
1794
  var gl = canvas.getContext('webgl2', {
1558
1795
  alpha: false,
1559
1796
  antialias: true,
1560
1797
  premultipliedAlpha: false,
1561
- powerPreference: 'high-performance'
1798
+ powerPreference: options.powerPreference || 'high-performance'
1562
1799
  });
1563
1800
  if (!gl) throw new Error('WebGL2 unavailable');
1564
1801
  this.canvas = canvas;
1802
+ this.options = options;
1565
1803
  this.gl = gl;
1566
- this.program = createProgram(gl);
1567
1804
  this.profile = preset.edgeStyle === 'tide' ? 3 : (_PROFILE_INDEX$preset = PROFILE_INDEX[preset.id]) !== null && _PROFILE_INDEX$preset !== void 0 ? _PROFILE_INDEX$preset : 2;
1568
1805
  this.seed = stringSeed$1("".concat(preset.id, "-shader")) * 13.7 + 1.0;
1569
1806
  this.colors = preset.colors.map(hexToRgb01);
1570
1807
  this.motionData = getProgressMotionData(preset.id, preset.edgeStyle);
1571
1808
  var motionFactor = preset.edgeStyle === 'tide' ? MOTION_SCALE_FACTORS.tide : MOTION_SCALE_FACTORS[preset.id] || 1.04;
1572
1809
  this.motionScale = PROGRESS_MOTION_MAX_PX * motionFactor / 1257;
1573
- this.position = gl.getAttribLocation(this.program, 'a_position');
1574
- this.uniforms = {
1575
- resolution: gl.getUniformLocation(this.program, 'u_resolution'),
1576
- time: gl.getUniformLocation(this.program, 'u_time'),
1577
- progress: gl.getUniformLocation(this.program, 'u_progress'),
1578
- seed: gl.getUniformLocation(this.program, 'u_seed'),
1579
- profile: gl.getUniformLocation(this.program, 'u_profile'),
1580
- motion: gl.getUniformLocation(this.program, 'u_motion'),
1581
- effect: gl.getUniformLocation(this.program, 'u_effect'),
1582
- hasEffect: gl.getUniformLocation(this.program, 'u_hasEffect'),
1583
- effectFrames: gl.getUniformLocation(this.program, 'u_effectFrames'),
1584
- motionDuration: gl.getUniformLocation(this.program, 'u_motionDuration'),
1585
- motionScale: gl.getUniformLocation(this.program, 'u_motionScale'),
1586
- dark: gl.getUniformLocation(this.program, 'u_dark'),
1587
- accentA: gl.getUniformLocation(this.program, 'u_accentA'),
1588
- accentB: gl.getUniformLocation(this.program, 'u_accentB'),
1589
- glow: gl.getUniformLocation(this.program, 'u_glow')
1590
- };
1591
- this.buffer = gl.createBuffer();
1592
- gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
1593
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]), gl.STATIC_DRAW);
1594
- this.motionTexture = gl.createTexture();
1595
- gl.activeTexture(gl.TEXTURE0);
1596
- gl.bindTexture(gl.TEXTURE_2D, this.motionTexture);
1597
- gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
1598
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8, PROGRESS_MOTION_WIDTH, PROGRESS_MOTION_HEIGHT, 0, gl.RED, gl.UNSIGNED_BYTE, this.motionData);
1599
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
1600
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
1601
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
1602
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
1603
- this.effectTexture = gl.createTexture();
1604
- gl.activeTexture(gl.TEXTURE1);
1605
- gl.bindTexture(gl.TEXTURE_2D, this.effectTexture);
1606
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, 1, 1, 0, gl.RGB, gl.UNSIGNED_BYTE, new Uint8Array([32, 33, 38]));
1607
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
1608
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
1609
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
1610
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
1611
- this.effectUploaded = false;
1810
+ this.disposed = false;
1811
+ this.contextLost = false;
1812
+ this.setupResources();
1813
+ this.bindContextEvents();
1612
1814
  }
1613
1815
  return _createClass(ProgressFlowRenderer, [{
1816
+ key: "setupResources",
1817
+ value: function setupResources() {
1818
+ var gl = this.gl;
1819
+ this.program = createProgram(gl);
1820
+ this.position = gl.getAttribLocation(this.program, 'a_position');
1821
+ this.uniforms = {
1822
+ resolution: gl.getUniformLocation(this.program, 'u_resolution'),
1823
+ time: gl.getUniformLocation(this.program, 'u_time'),
1824
+ progress: gl.getUniformLocation(this.program, 'u_progress'),
1825
+ seed: gl.getUniformLocation(this.program, 'u_seed'),
1826
+ profile: gl.getUniformLocation(this.program, 'u_profile'),
1827
+ motion: gl.getUniformLocation(this.program, 'u_motion'),
1828
+ effect: gl.getUniformLocation(this.program, 'u_effect'),
1829
+ hasEffect: gl.getUniformLocation(this.program, 'u_hasEffect'),
1830
+ effectFrames: gl.getUniformLocation(this.program, 'u_effectFrames'),
1831
+ motionDuration: gl.getUniformLocation(this.program, 'u_motionDuration'),
1832
+ motionScale: gl.getUniformLocation(this.program, 'u_motionScale'),
1833
+ dark: gl.getUniformLocation(this.program, 'u_dark'),
1834
+ accentA: gl.getUniformLocation(this.program, 'u_accentA'),
1835
+ accentB: gl.getUniformLocation(this.program, 'u_accentB'),
1836
+ glow: gl.getUniformLocation(this.program, 'u_glow')
1837
+ };
1838
+ this.buffer = gl.createBuffer();
1839
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
1840
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]), gl.STATIC_DRAW);
1841
+ this.motionTexture = gl.createTexture();
1842
+ gl.activeTexture(gl.TEXTURE0);
1843
+ gl.bindTexture(gl.TEXTURE_2D, this.motionTexture);
1844
+ gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
1845
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8, PROGRESS_MOTION_WIDTH, PROGRESS_MOTION_HEIGHT, 0, gl.RED, gl.UNSIGNED_BYTE, this.motionData);
1846
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
1847
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
1848
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
1849
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
1850
+ this.effectTexture = gl.createTexture();
1851
+ gl.activeTexture(gl.TEXTURE1);
1852
+ gl.bindTexture(gl.TEXTURE_2D, this.effectTexture);
1853
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, 1, 1, 0, gl.RGB, gl.UNSIGNED_BYTE, new Uint8Array([32, 33, 38]));
1854
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
1855
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
1856
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
1857
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
1858
+ this.effectUploaded = false;
1859
+ }
1860
+ }, {
1861
+ key: "bindContextEvents",
1862
+ value: function bindContextEvents() {
1863
+ var _this = this;
1864
+ this.onContextLost = function (event) {
1865
+ event.preventDefault();
1866
+ if (_this.disposed) return;
1867
+ _this.contextLost = true;
1868
+ if (typeof _this.options.onContextLost === 'function') _this.options.onContextLost(event);
1869
+ };
1870
+ this.onContextRestored = function () {
1871
+ if (_this.disposed) return;
1872
+ _this.setupResources();
1873
+ _this.contextLost = false;
1874
+ _this.gl.viewport(0, 0, _this.canvas.width, _this.canvas.height);
1875
+ if (typeof _this.options.onContextRestored === 'function') _this.options.onContextRestored();
1876
+ };
1877
+ this.canvas.addEventListener('webglcontextlost', this.onContextLost, false);
1878
+ this.canvas.addEventListener('webglcontextrestored', this.onContextRestored, false);
1879
+ }
1880
+ }, {
1614
1881
  key: "resize",
1615
1882
  value: function resize(width, height, dpr) {
1616
1883
  var pixelWidth = Math.max(1, Math.round(width * dpr));
@@ -1625,6 +1892,7 @@ var ProgressFlowRenderer = /*#__PURE__*/function () {
1625
1892
  key: "draw",
1626
1893
  value: function draw(time, progress) {
1627
1894
  var effectImage = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
1895
+ if (this.disposed || this.contextLost) return;
1628
1896
  var gl = this.gl;
1629
1897
  gl.useProgram(this.program);
1630
1898
  gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
@@ -1641,7 +1909,8 @@ var ProgressFlowRenderer = /*#__PURE__*/function () {
1641
1909
  gl.uniform1f(this.uniforms.motionDuration, PROGRESS_MOTION_DURATION);
1642
1910
  gl.uniform1f(this.uniforms.motionScale, this.motionScale);
1643
1911
  var hasEffect = this.effectUploaded ? 1 : 0;
1644
- if (!this.effectUploaded && effectImage && effectImage.complete && effectImage.naturalWidth > 0) {
1912
+ var effectWidth = effectImage && (effectImage.naturalWidth || effectImage.width || 0);
1913
+ if (!this.effectUploaded && effectImage && effectWidth > 0) {
1645
1914
  gl.activeTexture(gl.TEXTURE1);
1646
1915
  gl.bindTexture(gl.TEXTURE_2D, this.effectTexture);
1647
1916
  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
@@ -1673,7 +1942,10 @@ var ProgressFlowRenderer = /*#__PURE__*/function () {
1673
1942
  }, {
1674
1943
  key: "dispose",
1675
1944
  value: function dispose() {
1945
+ this.disposed = true;
1676
1946
  var gl = this.gl;
1947
+ this.canvas.removeEventListener('webglcontextlost', this.onContextLost, false);
1948
+ this.canvas.removeEventListener('webglcontextrestored', this.onContextRestored, false);
1677
1949
  gl.deleteBuffer(this.buffer);
1678
1950
  gl.deleteTexture(this.motionTexture);
1679
1951
  gl.deleteTexture(this.effectTexture);
@@ -1684,8 +1956,9 @@ var ProgressFlowRenderer = /*#__PURE__*/function () {
1684
1956
  }]);
1685
1957
  }();
1686
1958
  function createProgressFlowRenderer(canvas, preset) {
1959
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
1687
1960
  try {
1688
- return new ProgressFlowRenderer(canvas, preset);
1961
+ return new ProgressFlowRenderer(canvas, preset, options);
1689
1962
  } catch (error) {
1690
1963
  console.warn('[画境观屿] 进度流体 WebGL2 不可用,使用 Canvas 2D 降级。', error);
1691
1964
  return null;
@@ -1745,50 +2018,49 @@ function createAtlas(id) {
1745
2018
  context.fillRect(20, 0, 44, FRAME_HEIGHT);
1746
2019
  context.restore();
1747
2020
  }
1748
- var image = new Image();
1749
- image.decoding = 'async';
1750
- var state = {
1751
- image: image,
1752
- ready: false
2021
+
2022
+ // A canvas is a valid TexImageSource. Keeping it directly avoids a PNG
2023
+ // encode -> base64 allocation -> Image decode round trip at startup.
2024
+ return {
2025
+ image: canvas,
2026
+ ready: true
1753
2027
  };
1754
- image.addEventListener('load', function () {
1755
- state.ready = true;
1756
- }, {
1757
- once: true
1758
- });
1759
- image.addEventListener('error', function () {
1760
- state.ready = false;
1761
- }, {
1762
- once: true
1763
- });
1764
- image.src = canvas.toDataURL('image/png');
1765
- return state;
1766
2028
  }
1767
2029
  function getProgressReferenceAtlas(id) {
1768
2030
  if (!CACHE[id]) CACHE[id] = createAtlas(id);
1769
2031
  return CACHE[id];
1770
2032
  }
1771
2033
 
1772
- /**
1773
- * Attach a WebGL2 fluid overlay to a progress capsule root.
1774
- *
1775
- * @param {object} params
1776
- * @param {HTMLElement} params.root progress capsule root element
1777
- * @param {HTMLCanvasElement} params.canvas 2D fallback canvas (kept beneath)
1778
- * @param {object} params.preset progress preset
1779
- * @param {() => number} params.getProgress reads the current progress value
1780
- * @returns {{ update(flowTime: number): void, dispose(): void } | null}
2034
+ /**
2035
+ * Attach a WebGL2 fluid overlay to a progress capsule root.
2036
+ *
2037
+ * @param {object} params
2038
+ * @param {HTMLElement} params.root progress capsule root element
2039
+ * @param {HTMLCanvasElement} params.canvas 2D fallback canvas (kept beneath)
2040
+ * @param {object} params.preset progress preset
2041
+ * @param {() => number} params.getProgress reads the current progress value
2042
+ * @returns {{ update(flowTime: number): void, setDprCap(cap: number): void, dispose(): void } | null}
1781
2043
  */
1782
2044
  function attachProgressFlowOverlay(_ref) {
1783
2045
  var root = _ref.root,
1784
2046
  canvas = _ref.canvas,
1785
2047
  preset = _ref.preset,
1786
- getProgress = _ref.getProgress;
2048
+ getProgress = _ref.getProgress,
2049
+ _ref$dprCap = _ref.dprCap,
2050
+ dprCap = _ref$dprCap === void 0 ? 2 : _ref$dprCap,
2051
+ _ref$powerPreference = _ref.powerPreference,
2052
+ powerPreference = _ref$powerPreference === void 0 ? 'high-performance' : _ref$powerPreference,
2053
+ onContextLost = _ref.onContextLost,
2054
+ onContextRestored = _ref.onContextRestored;
1787
2055
  var overlay = document.createElement('canvas');
1788
2056
  overlay.className = 'hj-progress-canvas hj-progress-overlay';
1789
2057
  overlay.setAttribute('aria-hidden', 'true');
1790
2058
  canvas.insertAdjacentElement('afterend', overlay);
1791
- var renderer = createProgressFlowRenderer(overlay, preset);
2059
+ var renderer = createProgressFlowRenderer(overlay, preset, {
2060
+ powerPreference: powerPreference,
2061
+ onContextLost: onContextLost,
2062
+ onContextRestored: onContextRestored
2063
+ });
1792
2064
  if (!renderer) {
1793
2065
  overlay.remove();
1794
2066
  return null;
@@ -1799,7 +2071,7 @@ function attachProgressFlowOverlay(_ref) {
1799
2071
  var bounds = root.getBoundingClientRect();
1800
2072
  var width = Math.max(1, bounds.width);
1801
2073
  var height = Math.max(1, bounds.height);
1802
- var dpr = Math.min(window.devicePixelRatio || 1, 2);
2074
+ var dpr = Math.min(window.devicePixelRatio || 1, dprCap);
1803
2075
  overlay.style.width = "".concat(width, "px");
1804
2076
  overlay.style.height = "".concat(height, "px");
1805
2077
  renderer.resize(width, height, dpr);
@@ -1823,6 +2095,10 @@ function attachProgressFlowOverlay(_ref) {
1823
2095
  setColors: function setColors(colors) {
1824
2096
  renderer.setColors(colors);
1825
2097
  },
2098
+ setDprCap: function setDprCap(cap) {
2099
+ dprCap = cap;
2100
+ resize();
2101
+ },
1826
2102
  dispose: function dispose() {
1827
2103
  if (resizeObserver) resizeObserver.disconnect();
1828
2104
  if (onWindowResize) window.removeEventListener('resize', onWindowResize);
@@ -1833,6 +2109,47 @@ function attachProgressFlowOverlay(_ref) {
1833
2109
  };
1834
2110
  }
1835
2111
 
2112
+ function normalizeRange(min, max) {
2113
+ var fallbackMin = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
2114
+ var fallbackMax = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 100;
2115
+ var nextMin = toFiniteNumber(min);
2116
+ var nextMax = toFiniteNumber(max);
2117
+ if (nextMin === null) nextMin = fallbackMin;
2118
+ if (nextMax === null) nextMax = fallbackMax;
2119
+ if (nextMin > nextMax) {
2120
+ var swap = nextMin;
2121
+ nextMin = nextMax;
2122
+ nextMax = swap;
2123
+ }
2124
+ return [nextMin, nextMax];
2125
+ }
2126
+ function clampProgressValue(value, min, max) {
2127
+ var number = toFiniteNumber(value);
2128
+ if (number === null) return null;
2129
+ return Math.min(Math.max(number, min), max);
2130
+ }
2131
+ function normalizeProgressStep(step) {
2132
+ var fallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'any';
2133
+ if (step === 'any' || step == null) return 'any';
2134
+ var increment = toFiniteNumber(step);
2135
+ return increment !== null && increment > 0 ? increment : fallback;
2136
+ }
2137
+ function snapProgressValue(value, min, max) {
2138
+ var step = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'any';
2139
+ var clamped = clampProgressValue(value, min, max);
2140
+ if (clamped === null) return null;
2141
+ if (step === 'any' || step == null) return clamped;
2142
+ var increment = normalizeProgressStep(step);
2143
+ if (increment === 'any') return clamped;
2144
+ var snapped = min + Math.round((clamped - min) / increment) * increment;
2145
+ return Math.min(Math.max(Number(snapped.toFixed(12)), min), max);
2146
+ }
2147
+ function progressRatio(value, min, max) {
2148
+ var range = max - min;
2149
+ if (!Number.isFinite(range) || range <= 0) return 0;
2150
+ return Math.min(Math.max((value - min) / range, 0), 1);
2151
+ }
2152
+
1836
2153
  var clamp = function clamp(value, min, max) {
1837
2154
  return Math.min(Math.max(value, min), max);
1838
2155
  };
@@ -1859,9 +2176,6 @@ function interpolate(template, values) {
1859
2176
  return values[key] !== undefined ? values[key] : '';
1860
2177
  });
1861
2178
  }
1862
- function prefersReducedMotion$1() {
1863
- return typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches;
1864
- }
1865
2179
  var FLOW_PROFILES = {
1866
2180
  'model-training': {
1867
2181
  cycles: [0.98, 3.05, 5.45],
@@ -1922,8 +2236,6 @@ var FLOW_PROFILES = {
1922
2236
  var ProgressCapsuleController = /*#__PURE__*/function () {
1923
2237
  function ProgressCapsuleController(_ref) {
1924
2238
  var _options$valueSuffix,
1925
- _options$min,
1926
- _options$max,
1927
2239
  _options$value,
1928
2240
  _this = this;
1929
2241
  var root = _ref.root,
@@ -1945,11 +2257,18 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1945
2257
  this.dirty = dirty;
1946
2258
  this.valueSuffix = (_options$valueSuffix = options.valueSuffix) !== null && _options$valueSuffix !== void 0 ? _options$valueSuffix : copy.valueSuffix;
1947
2259
  this.profile = preset.edgeStyle === 'tide' ? FLOW_PROFILES.tide : FLOW_PROFILES[preset.id] || FLOW_PROFILES['visual-training'];
1948
- this.min = (_options$min = options.min) !== null && _options$min !== void 0 ? _options$min : 0;
1949
- this.max = (_options$max = options.max) !== null && _options$max !== void 0 ? _options$max : 100;
1950
- this.dprCap = dprCapFor(options.quality);
2260
+ var _normalizeRange = normalizeRange(options.min, options.max);
2261
+ var _normalizeRange2 = _slicedToArray(_normalizeRange, 2);
2262
+ this.min = _normalizeRange2[0];
2263
+ this.max = _normalizeRange2[1];
2264
+ this.step = normalizeProgressStep(options.step);
2265
+ this.precision = Number.isInteger(Number(options.precision)) ? clamp(Number(options.precision), 0, 20) : 0;
2266
+ this.formatValue = typeof options.formatValue === 'function' ? options.formatValue : null;
2267
+ this.direction = options.direction === 'rtl' ? 'rtl' : 'ltr';
2268
+ this.dprCap = effectiveDprCap(options.quality, options.renderScale);
1951
2269
  this.ctx = canvas.getContext('2d');
1952
- this.value = clamp((_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : preset.initialProgress, this.min, this.max);
2270
+ this.value = snapProgressValue((_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : preset.initialProgress, this.min, this.max, this.step);
2271
+ if (this.value === null) this.value = clamp(preset.initialProgress, this.min, this.max);
1953
2272
  this.dragging = false;
1954
2273
  this.webglActive = false;
1955
2274
  this.flowTime = stringSeed(preset.id) * 31;
@@ -1959,10 +2278,12 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1959
2278
  this.width = 0;
1960
2279
  this.height = 0;
1961
2280
  this.handlers = {};
1962
- this.resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(function () {
1963
- return _this.resizeCanvas();
1964
- }) : null;
1965
- if (this.resizeObserver) this.resizeObserver.observe(this.root);else window.addEventListener('resize', this.resizeCanvas);
2281
+ this.onResize = function () {
2282
+ _this.resizeCanvas();
2283
+ if (typeof _this.options.onResize === 'function') _this.options.onResize();
2284
+ };
2285
+ this.resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(this.onResize) : null;
2286
+ if (this.resizeObserver) this.resizeObserver.observe(this.root);else window.addEventListener('resize', this.onResize);
1966
2287
  this.suppressEvents = true;
1967
2288
  this.setProgress(this.value, 'init');
1968
2289
  this.suppressEvents = false;
@@ -1975,16 +2296,37 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1975
2296
  this.randomState = Math.imul(this.randomState, 1664525) + 1013904223 >>> 0;
1976
2297
  return this.randomState / 4294967296;
1977
2298
  }
2299
+ }, {
2300
+ key: "displayText",
2301
+ value: function displayText() {
2302
+ if (this.formatValue) {
2303
+ return String(this.formatValue(this.value, {
2304
+ min: this.min,
2305
+ max: this.max,
2306
+ suffix: this.valueSuffix
2307
+ }));
2308
+ }
2309
+ var number = this.precision > 0 ? this.value.toFixed(this.precision) : String(Math.round(this.value));
2310
+ return "".concat(number).concat(this.valueSuffix);
2311
+ }
2312
+ }, {
2313
+ key: "syncValueDom",
2314
+ value: function syncValueDom() {
2315
+ var text = this.displayText();
2316
+ this.root.style.setProperty('--progress', this.value.toFixed(2));
2317
+ this.root.style.setProperty('--progress-ratio', progressRatio(this.value, this.min, this.max).toFixed(4));
2318
+ this.root.setAttribute('aria-valuenow', String(this.value));
2319
+ this.root.setAttribute('aria-valuetext', text);
2320
+ if (this.valueElement) this.valueElement.textContent = text;
2321
+ }
1978
2322
  }, {
1979
2323
  key: "setProgress",
1980
2324
  value: function setProgress(nextValue) {
1981
2325
  var source = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'auto';
1982
- this.value = clamp(nextValue, this.min, this.max);
1983
- var rounded = Math.round(this.value);
1984
- this.root.style.setProperty('--progress', this.value.toFixed(2));
1985
- this.root.setAttribute('aria-valuenow', String(rounded));
1986
- this.root.setAttribute('aria-valuetext', "".concat(rounded).concat(this.valueSuffix));
1987
- if (this.valueElement) this.valueElement.textContent = "".concat(rounded).concat(this.valueSuffix);
2326
+ var next = snapProgressValue(nextValue, this.min, this.max, this.step);
2327
+ if (next === null) return false;
2328
+ this.value = next;
2329
+ this.syncValueDom();
1988
2330
  if (!this.suppressEvents) {
1989
2331
  if (this.dirty) this.dirty.value = true;
1990
2332
  this.emitter.emit('change', {
@@ -1992,19 +2334,58 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
1992
2334
  source: source
1993
2335
  });
1994
2336
  }
2337
+ return true;
1995
2338
  }
1996
2339
  }, {
1997
2340
  key: "setValueSuffix",
1998
2341
  value: function setValueSuffix(suffix) {
2342
+ if (this.dirty) this.dirty.valueSuffix = true;
1999
2343
  this.valueSuffix = String(suffix == null ? '' : suffix);
2000
- var rounded = Math.round(this.value);
2001
- if (this.valueElement) this.valueElement.textContent = "".concat(rounded).concat(this.valueSuffix);
2002
- this.root.setAttribute('aria-valuetext', "".concat(rounded).concat(this.valueSuffix));
2344
+ this.syncValueDom();
2345
+ return this;
2346
+ }
2347
+ }, {
2348
+ key: "setStep",
2349
+ value: function setStep(step) {
2350
+ var nextStep = normalizeProgressStep(step, this.step);
2351
+ if (nextStep === this.step) return this;
2352
+ if (this.dirty) this.dirty.step = true;
2353
+ this.step = nextStep;
2354
+ var next = snapProgressValue(this.value, this.min, this.max, this.step);
2355
+ if (next !== null && next !== this.value) this.setProgress(next, 'prop');
2356
+ return this;
2357
+ }
2358
+ }, {
2359
+ key: "setPrecision",
2360
+ value: function setPrecision(precision) {
2361
+ var value = Number(precision);
2362
+ if (!Number.isInteger(value) || value < 0 || value > 20) return this;
2363
+ if (this.dirty) this.dirty.precision = true;
2364
+ this.precision = value;
2365
+ this.syncValueDom();
2366
+ return this;
2367
+ }
2368
+ }, {
2369
+ key: "setFormatValue",
2370
+ value: function setFormatValue(formatter) {
2371
+ if (formatter != null && typeof formatter !== 'function') return this;
2372
+ if (this.dirty) this.dirty.formatValue = true;
2373
+ this.formatValue = formatter || null;
2374
+ this.syncValueDom();
2375
+ return this;
2376
+ }
2377
+ }, {
2378
+ key: "setDirection",
2379
+ value: function setDirection(direction) {
2380
+ if (this.dirty) this.dirty.direction = true;
2381
+ this.direction = direction === 'rtl' ? 'rtl' : 'ltr';
2382
+ this.root.dataset.direction = this.direction;
2003
2383
  return this;
2004
2384
  }
2005
2385
  }, {
2006
2386
  key: "setShowValue",
2007
2387
  value: function setShowValue(show) {
2388
+ if (this.dirty) this.dirty.showValue = true;
2008
2389
  if (this.valueElement) this.valueElement.hidden = show === false;
2009
2390
  return this;
2010
2391
  }
@@ -2169,15 +2550,14 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
2169
2550
  }, {
2170
2551
  key: "setRange",
2171
2552
  value: function setRange(min, max) {
2172
- if (min > max) {
2173
- var swap = min;
2174
- min = max;
2175
- max = swap;
2176
- }
2177
- this.min = min;
2178
- this.max = max;
2179
- var clamped = clamp(this.value, this.min, this.max);
2180
- if (clamped !== this.value) this.setProgress(clamped, 'prop');
2553
+ var nextRange = normalizeRange(min, max, this.min, this.max);
2554
+ this.min = nextRange[0];
2555
+ this.max = nextRange[1];
2556
+ this.root.setAttribute('aria-valuemin', String(this.min));
2557
+ this.root.setAttribute('aria-valuemax', String(this.max));
2558
+ var next = snapProgressValue(this.value, this.min, this.max, this.step);
2559
+ if (next !== this.value) this.setProgress(next, 'prop');else this.syncValueDom();
2560
+ return this;
2181
2561
  }
2182
2562
  }, {
2183
2563
  key: "drawReferenceFlow",
@@ -2302,14 +2682,45 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
2302
2682
  value: function updateFromPointer(event) {
2303
2683
  var bounds = this.root.getBoundingClientRect();
2304
2684
  var ratio = bounds.width > 0 ? (event.clientX - bounds.left) / bounds.width : 0;
2685
+ if (this.direction === 'rtl') ratio = 1 - ratio;
2305
2686
  this.setProgress(this.min + ratio * (this.max - this.min), 'drag');
2306
2687
  }
2688
+ }, {
2689
+ key: "onKeyDown",
2690
+ value: function onKeyDown(event) {
2691
+ var range = this.max - this.min;
2692
+ var configuredStep = Number(this.step);
2693
+ var step = Number.isFinite(configuredStep) && configuredStep > 0 ? configuredStep : Math.max(range / 100, 1e-7);
2694
+ var direction = this.direction === 'rtl' ? -1 : 1;
2695
+ var aliases = {
2696
+ Left: 'ArrowLeft',
2697
+ Right: 'ArrowRight',
2698
+ Up: 'ArrowUp',
2699
+ Down: 'ArrowDown'
2700
+ };
2701
+ var keyCodes = {
2702
+ 35: 'End',
2703
+ 36: 'Home',
2704
+ 33: 'PageUp',
2705
+ 34: 'PageDown',
2706
+ 37: 'ArrowLeft',
2707
+ 38: 'ArrowUp',
2708
+ 39: 'ArrowRight',
2709
+ 40: 'ArrowDown'
2710
+ };
2711
+ var key = aliases[event.key] || event.key || keyCodes[event.keyCode];
2712
+ var next = null;
2713
+ 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;
2714
+ if (next === null) return;
2715
+ event.preventDefault();
2716
+ this.setProgress(next, 'keyboard');
2717
+ }
2307
2718
  }, {
2308
2719
  key: "beginDrag",
2309
2720
  value: function beginDrag(event) {
2310
2721
  if (event.button !== undefined && event.button !== 0) return;
2311
2722
  event.preventDefault();
2312
- // preventDefault 会吞掉点击聚焦,主动聚焦让键盘方向键立即可用。
2723
+ // preventDefault 会吞掉点击聚焦,主动聚焦让根节点获得无障碍焦点。
2313
2724
  try {
2314
2725
  this.root.focus({
2315
2726
  preventScroll: true
@@ -2391,6 +2802,12 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
2391
2802
  this.root.addEventListener('pointerup', this.handlers.pointerup);
2392
2803
  this.root.addEventListener('pointercancel', this.handlers.pointercancel);
2393
2804
  }
2805
+ if (this.options.keyboard !== false) {
2806
+ this.handlers.keydown = function (event) {
2807
+ return _this3.onKeyDown(event);
2808
+ };
2809
+ this.root.addEventListener('keydown', this.handlers.keydown);
2810
+ }
2394
2811
  }
2395
2812
  }, {
2396
2813
  key: "update",
@@ -2409,20 +2826,19 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
2409
2826
  key: "randomize",
2410
2827
  value: function randomize() {
2411
2828
  this.flowTime = this.random() * 40;
2829
+ return this.flowTime;
2412
2830
  }
2413
2831
  }, {
2414
2832
  key: "setColors",
2415
2833
  value: function setColors(colors) {
2416
2834
  if (!Array.isArray(colors) || colors.length !== 4) return;
2417
- this.preset = _objectSpread2(_objectSpread2({}, this.preset), {}, {
2418
- colors: colors
2419
- });
2835
+ this.preset.colors = _toConsumableArray(colors);
2420
2836
  }
2421
2837
  }, {
2422
2838
  key: "dispose",
2423
2839
  value: function dispose() {
2424
2840
  if (this._dragRaf) cancelAnimationFrame(this._dragRaf);
2425
- if (this.resizeObserver) this.resizeObserver.disconnect();else window.removeEventListener('resize', this.resizeCanvas);
2841
+ if (this.resizeObserver) this.resizeObserver.disconnect();else window.removeEventListener('resize', this.onResize);
2426
2842
  for (var _i = 0, _Object$keys = Object.keys(this.handlers); _i < _Object$keys.length; _i++) {
2427
2843
  var name = _Object$keys[_i];
2428
2844
  var handler = this.handlers[name];
@@ -2433,15 +2849,16 @@ var ProgressCapsuleController = /*#__PURE__*/function () {
2433
2849
  }]);
2434
2850
  }();
2435
2851
 
2436
- /**
2852
+ /**
2437
2853
  * Mount a fluid progress capsule into `container`.
2438
2854
  *
2439
- * Options: preset (id/code/name or object), width, height, value, min, max,
2440
- * draggable, colors, edgeStyle, textRatio (0-100, text region
2855
+ * Options: preset (literary name), width, height, value/modelValue, min, max,
2856
+ * step, draggable, keyboard, direction, precision/formatValue, colors,
2857
+ * edgeStyle, textRatio (0-100, text region
2441
2858
  * width in percent), text (HTML string or DOM nodes for the text slot),
2442
2859
  * colorContent (HTML string or DOM nodes for the color slot),
2443
- * showValue (show/hide the right-side percentage), quality,
2444
- * respectReducedMotion, cssVars.
2860
+ * showValue (show/hide the right-side percentage), quality, renderScale,
2861
+ * powerPreference, fps, paused/static, respectReducedMotion, cssVars.
2445
2862
  */
2446
2863
  function createProgressCapsule(container) {
2447
2864
  var _options$preset, _merged$min, _merged$max;
@@ -2451,14 +2868,20 @@ function createProgressCapsule(container) {
2451
2868
  }
2452
2869
  var preset = _objectSpread2({}, getPreset('progress', (_options$preset = options.preset) !== null && _options$preset !== void 0 ? _options$preset : '星火'));
2453
2870
  var merged = normalizeOptions(DEFAULTS.progress, preset, options);
2454
- if (merged.min > merged.max) {
2455
- var swap = merged.min;
2456
- merged.min = merged.max;
2457
- merged.max = swap;
2458
- }
2459
- var normalizedColors = merged.colors.map(normalizeColor);
2460
- if (normalizedColors.every(Boolean)) preset.colors = normalizedColors;
2461
- preset.edgeStyle = merged.edgeStyle;
2871
+ var _normalizeRange3 = normalizeRange(merged.min, merged.max);
2872
+ var _normalizeRange4 = _slicedToArray(_normalizeRange3, 2);
2873
+ merged.min = _normalizeRange4[0];
2874
+ merged.max = _normalizeRange4[1];
2875
+ if (merged.modelValue !== undefined) merged.value = merged.modelValue;
2876
+ var normalizedColors = Array.isArray(merged.colors) ? merged.colors.map(normalizeColor) : [];
2877
+ if (normalizedColors.length === 4 && normalizedColors.every(Boolean)) preset.colors = normalizedColors;
2878
+ var initialEdgeStyle = merged.edgeStyle === 'tide' || merged.edgeStyle === 'flow' ? merged.edgeStyle : preset.edgeStyle;
2879
+ preset.edgeStyle = initialEdgeStyle;
2880
+ merged.edgeStyle = initialEdgeStyle;
2881
+ merged.colors = _toConsumableArray(preset.colors);
2882
+ var optionColors = Array.isArray(options.colors) ? options.colors.map(normalizeColor) : [];
2883
+ var colorOverride = optionColors.length === 4 && optionColors.every(Boolean) ? _toConsumableArray(optionColors) : null;
2884
+ var edgeStyleOverride = options.edgeStyle === 'flow' || options.edgeStyle === 'tide' ? merged.edgeStyle : null;
2462
2885
  var copy = COPY;
2463
2886
  var customLabel = typeof options.label === 'string' && options.label ? options.label : null;
2464
2887
  // Vue 的裸布尔属性(<ProgressCapsule disabled />)会传成空字符串,
@@ -2467,18 +2890,31 @@ function createProgressCapsule(container) {
2467
2890
  var readonly = merged.readonly === true || merged.readonly === '' || merged.readonly === 'true';
2468
2891
  var locked = disabled || readonly;
2469
2892
  var effectiveDraggable = locked ? false : merged.draggable !== false;
2893
+ var effectiveKeyboard = locked ? false : merged.keyboard !== false;
2470
2894
  var dirty = {
2471
2895
  preset: false,
2472
2896
  colors: false,
2473
2897
  textRatio: false,
2474
2898
  cssVars: false,
2475
2899
  edgeStyle: false,
2476
- value: false
2900
+ value: false,
2901
+ step: false,
2902
+ precision: false,
2903
+ formatValue: false,
2904
+ direction: false,
2905
+ showValue: false,
2906
+ valueSuffix: false,
2907
+ quality: false,
2908
+ renderScale: false,
2909
+ paused: false,
2910
+ static: false,
2911
+ fps: false
2477
2912
  };
2478
2913
  var root = document.createElement('div');
2479
2914
  root.className = 'hj-capsule-root hj-progress-root';
2480
2915
  root.setAttribute('role', 'slider');
2481
2916
  root.setAttribute('data-draggable', String(effectiveDraggable));
2917
+ root.dataset.direction = merged.direction === 'rtl' ? 'rtl' : 'ltr';
2482
2918
  if (disabled) {
2483
2919
  root.setAttribute('aria-disabled', 'true');
2484
2920
  root.classList.add('is-disabled');
@@ -2487,7 +2923,8 @@ function createProgressCapsule(container) {
2487
2923
  root.setAttribute('aria-readonly', 'true');
2488
2924
  root.classList.add('is-readonly');
2489
2925
  }
2490
- if (locked) root.setAttribute('tabindex', '-1');
2926
+ root.setAttribute('tabindex', locked ? '-1' : effectiveKeyboard ? '0' : '-1');
2927
+ root.setAttribute('aria-orientation', 'horizontal');
2491
2928
  root.setAttribute('aria-valuemin', String((_merged$min = merged.min) !== null && _merged$min !== void 0 ? _merged$min : 0));
2492
2929
  root.setAttribute('aria-valuemax', String((_merged$max = merged.max) !== null && _merged$max !== void 0 ? _merged$max : 100));
2493
2930
  root.setAttribute('aria-valuenow', String(preset.initialProgress));
@@ -2542,8 +2979,22 @@ function createProgressCapsule(container) {
2542
2979
  root.appendChild(valueElement);
2543
2980
  container.appendChild(root);
2544
2981
  var emitter = createEmitter();
2545
- var paused = merged.respectReducedMotion && prefersReducedMotion$1();
2982
+ var manuallyPaused = merged.paused === true;
2983
+ var reducedPaused = false;
2984
+ var staticMode = merged.static === true;
2985
+ var contextLost = false;
2546
2986
  var disposed = false;
2987
+ var renderOnce = function renderOnce() {};
2988
+ var onContextLost = function onContextLost() {
2989
+ contextLost = true;
2990
+ emitter.emit('contextlost', {});
2991
+ };
2992
+ var onContextRestored = function onContextRestored() {
2993
+ contextLost = false;
2994
+ emitter.emit('contextrestored', {});
2995
+ renderOnce();
2996
+ wakeScheduler();
2997
+ };
2547
2998
  var applySize = function applySize() {
2548
2999
  root.style.width = parseSize(merged.width);
2549
3000
  root.style.height = parseSize(merged.height);
@@ -2564,7 +3015,11 @@ function createProgressCapsule(container) {
2564
3015
  preset: preset,
2565
3016
  emitter: emitter,
2566
3017
  options: _objectSpread2(_objectSpread2({}, merged), {}, {
2567
- draggable: effectiveDraggable
3018
+ draggable: effectiveDraggable,
3019
+ keyboard: effectiveKeyboard,
3020
+ onResize: function onResize() {
3021
+ if (manuallyPaused || reducedPaused || staticMode) renderOnce();
3022
+ }
2568
3023
  }),
2569
3024
  copy: copy,
2570
3025
  dirty: dirty
@@ -2576,8 +3031,12 @@ function createProgressCapsule(container) {
2576
3031
  canvas: canvas,
2577
3032
  preset: preset,
2578
3033
  getProgress: function getProgress() {
2579
- return controller.value;
2580
- }
3034
+ return progressRatio(controller.value, controller.min, controller.max) * 100;
3035
+ },
3036
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
3037
+ powerPreference: merged.powerPreference,
3038
+ onContextLost: onContextLost,
3039
+ onContextRestored: onContextRestored
2581
3040
  });
2582
3041
  }
2583
3042
  if (overlay) controller.webglActive = true;else if (merged.renderer !== 'canvas2d') {
@@ -2588,17 +3047,34 @@ function createProgressCapsule(container) {
2588
3047
  });
2589
3048
  });
2590
3049
  }
2591
- var visibility = createVisibilityGuard(root);
3050
+ var visibility = createVisibilityGuard(root, wakeScheduler);
3051
+ var motionPreference = createReducedMotionPreference(merged.respectReducedMotion, function (matches) {
3052
+ reducedPaused = matches;
3053
+ if (matches) renderOnce();else wakeScheduler();
3054
+ });
3055
+ reducedPaused = motionPreference.matches();
3056
+ var isMotionPaused = function isMotionPaused() {
3057
+ return manuallyPaused || reducedPaused || staticMode || contextLost;
3058
+ };
2592
3059
  var flowTime = 0;
3060
+ var frameGate = createFrameGate(merged.fps);
3061
+ renderOnce = function renderOnce() {
3062
+ controller.draw();
3063
+ if (overlay) overlay.update(flowTime);
3064
+ };
3065
+ renderOnce();
3066
+ var offPausedChange = emitter.on('change', function () {
3067
+ if (isMotionPaused()) renderOnce();
3068
+ });
2593
3069
  var unsubscribe = subscribeScheduler(function (delta, now) {
2594
3070
  flowTime += delta;
2595
3071
  controller.update(delta, false);
2596
- if (visibility.isVisible()) {
3072
+ if (frameGate.shouldDraw(delta)) {
2597
3073
  controller.draw();
2598
3074
  if (overlay) overlay.update(flowTime);
2599
3075
  }
2600
3076
  }, function () {
2601
- return paused;
3077
+ return isMotionPaused() || !visibility.isVisible();
2602
3078
  });
2603
3079
  nextTick(function () {
2604
3080
  if (disposed) return;
@@ -2625,32 +3101,41 @@ function createProgressCapsule(container) {
2625
3101
  },
2626
3102
  setRange: function setRange(min, max) {
2627
3103
  controller.setRange(min, max);
3104
+ if (isMotionPaused()) renderOnce();
2628
3105
  return this;
2629
3106
  },
2630
3107
  setPreset: function setPreset(ref) {
2631
3108
  var next = getPreset('progress', ref);
2632
3109
  dirty.preset = true;
2633
3110
  Object.assign(preset, next);
3111
+ if (colorOverride) preset.colors = _toConsumableArray(colorOverride);
3112
+ if (edgeStyleOverride) preset.edgeStyle = edgeStyleOverride;
2634
3113
  var nextColors = preset.colors.map(normalizeColor);
2635
3114
  if (nextColors.every(Boolean)) preset.colors = nextColors;
2636
3115
  controller.preset = preset;
2637
- controller.profile = next.edgeStyle === 'tide' ? FLOW_PROFILES.tide : FLOW_PROFILES[next.id] || FLOW_PROFILES['visual-training'];
3116
+ controller.profile = preset.edgeStyle === 'tide' ? FLOW_PROFILES.tide : FLOW_PROFILES[preset.id] || FLOW_PROFILES['visual-training'];
2638
3117
  controller.flowTime = stringSeed(next.id) * 31;
2639
3118
  controller.seed = stringSeed("".concat(next.id, "-reference")) * Math.PI * 2;
2640
3119
  syncDom();
2641
3120
  if (overlay) {
2642
3121
  overlay.dispose();
3122
+ contextLost = false;
2643
3123
  overlay = attachProgressFlowOverlay({
2644
3124
  root: root,
2645
3125
  canvas: canvas,
2646
3126
  preset: preset,
2647
3127
  getProgress: function getProgress() {
2648
- return controller.value;
2649
- }
3128
+ return progressRatio(controller.value, controller.min, controller.max) * 100;
3129
+ },
3130
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
3131
+ powerPreference: merged.powerPreference,
3132
+ onContextLost: onContextLost,
3133
+ onContextRestored: onContextRestored
2650
3134
  });
2651
3135
  }
2652
3136
  controller.webglActive = Boolean(overlay);
2653
3137
  controller.resizeCanvas();
3138
+ if (isMotionPaused()) renderOnce();
2654
3139
  emitter.emit('presetchange', {
2655
3140
  preset: _objectSpread2({}, preset)
2656
3141
  });
@@ -2660,21 +3145,28 @@ function createProgressCapsule(container) {
2660
3145
  var value = String(edgeStyle || '').toLowerCase();
2661
3146
  if (value !== 'flow' && value !== 'tide') return this;
2662
3147
  dirty.edgeStyle = true;
3148
+ edgeStyleOverride = value;
2663
3149
  preset.edgeStyle = value;
2664
3150
  controller.profile = value === 'tide' ? FLOW_PROFILES.tide : FLOW_PROFILES[preset.id] || FLOW_PROFILES['visual-training'];
2665
3151
  if (overlay) {
2666
3152
  overlay.dispose();
3153
+ contextLost = false;
2667
3154
  overlay = attachProgressFlowOverlay({
2668
3155
  root: root,
2669
3156
  canvas: canvas,
2670
3157
  preset: preset,
2671
3158
  getProgress: function getProgress() {
2672
- return controller.value;
2673
- }
3159
+ return progressRatio(controller.value, controller.min, controller.max) * 100;
3160
+ },
3161
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
3162
+ powerPreference: merged.powerPreference,
3163
+ onContextLost: onContextLost,
3164
+ onContextRestored: onContextRestored
2674
3165
  });
2675
3166
  }
2676
3167
  controller.webglActive = Boolean(overlay);
2677
3168
  controller.resizeCanvas();
3169
+ if (isMotionPaused()) renderOnce();
2678
3170
  return this;
2679
3171
  },
2680
3172
  setText: function setText(content) {
@@ -2712,6 +3204,22 @@ function createProgressCapsule(container) {
2712
3204
  controller.setValueSuffix(suffix);
2713
3205
  return this;
2714
3206
  },
3207
+ setStep: function setStep(step) {
3208
+ controller.setStep(step);
3209
+ return this;
3210
+ },
3211
+ setPrecision: function setPrecision(precision) {
3212
+ controller.setPrecision(precision);
3213
+ return this;
3214
+ },
3215
+ setFormatValue: function setFormatValue(formatter) {
3216
+ controller.setFormatValue(formatter);
3217
+ return this;
3218
+ },
3219
+ setDirection: function setDirection(direction) {
3220
+ controller.setDirection(direction);
3221
+ return this;
3222
+ },
2715
3223
  setShowValue: function setShowValue(show) {
2716
3224
  controller.setShowValue(show);
2717
3225
  return this;
@@ -2722,9 +3230,12 @@ function createProgressCapsule(container) {
2722
3230
  return !color;
2723
3231
  })) return this;
2724
3232
  dirty.colors = true;
3233
+ colorOverride = _toConsumableArray(next);
3234
+ preset.colors = _toConsumableArray(next);
2725
3235
  controller.setColors(next);
2726
3236
  if (overlay) overlay.setColors(next);
2727
3237
  controller.resizeCanvas();
3238
+ if (isMotionPaused()) renderOnce();
2728
3239
  return this;
2729
3240
  },
2730
3241
  setSize: function setSize(width, height) {
@@ -2738,30 +3249,68 @@ function createProgressCapsule(container) {
2738
3249
  }
2739
3250
  applySize();
2740
3251
  controller.resizeCanvas();
3252
+ if (isMotionPaused()) renderOnce();
2741
3253
  return this;
2742
3254
  },
2743
3255
  randomize: function randomize() {
2744
- controller.randomize();
3256
+ flowTime = controller.randomize();
3257
+ if (isMotionPaused()) renderOnce();
2745
3258
  return this;
2746
3259
  },
2747
3260
  pause: function pause() {
2748
- paused = true;
3261
+ dirty.paused = true;
3262
+ manuallyPaused = true;
3263
+ renderOnce();
2749
3264
  return this;
2750
3265
  },
2751
3266
  resume: function resume() {
2752
- paused = false;
3267
+ dirty.paused = true;
3268
+ manuallyPaused = false;
3269
+ wakeScheduler();
3270
+ return this;
3271
+ },
3272
+ setPaused: function setPaused(value) {
3273
+ return value ? this.pause() : this.resume();
3274
+ },
3275
+ setStatic: function setStatic(value) {
3276
+ dirty.static = true;
3277
+ staticMode = value === true;
3278
+ merged.static = staticMode;
3279
+ if (staticMode) renderOnce();else wakeScheduler();
3280
+ return this;
3281
+ },
3282
+ setFps: function setFps(fps) {
3283
+ dirty.fps = true;
3284
+ merged.fps = frameGate.setFps(fps);
3285
+ wakeScheduler();
2753
3286
  return this;
2754
3287
  },
2755
3288
  setQuality: function setQuality(quality) {
3289
+ dirty.quality = true;
2756
3290
  merged.quality = quality;
2757
- controller.dprCap = dprCapFor(quality);
3291
+ controller.dprCap = effectiveDprCap(quality, merged.renderScale);
2758
3292
  controller.resizeCanvas();
3293
+ if (overlay) overlay.setDprCap(controller.dprCap);
3294
+ if (isMotionPaused()) renderOnce();
3295
+ return this;
3296
+ },
3297
+ setRenderScale: function setRenderScale(renderScale) {
3298
+ var value = Number(renderScale);
3299
+ if (!Number.isFinite(value)) return this;
3300
+ dirty.renderScale = true;
3301
+ merged.renderScale = Math.min(1, Math.max(0.25, value));
3302
+ controller.dprCap = effectiveDprCap(merged.quality, merged.renderScale);
3303
+ controller.resizeCanvas();
3304
+ if (overlay) overlay.setDprCap(controller.dprCap);
3305
+ if (isMotionPaused()) renderOnce();
2759
3306
  return this;
2760
3307
  },
2761
3308
  dispose: function dispose() {
2762
3309
  disposed = true;
2763
3310
  unsubscribe();
3311
+ offPausedChange();
2764
3312
  visibility.dispose();
3313
+ motionPreference.dispose();
2765
3314
  if (overlay) overlay.dispose();
2766
3315
  controller.dispose();
2767
3316
  root.remove();
@@ -2772,26 +3321,89 @@ function createProgressCapsule(container) {
2772
3321
  get cssVars() {
2773
3322
  return merged.cssVars;
2774
3323
  },
3324
+ get min() {
3325
+ return controller.min;
3326
+ },
3327
+ get max() {
3328
+ return controller.max;
3329
+ },
3330
+ get step() {
3331
+ return controller.step;
3332
+ },
3333
+ get precision() {
3334
+ return controller.precision;
3335
+ },
3336
+ get formatValue() {
3337
+ return controller.formatValue;
3338
+ },
3339
+ get direction() {
3340
+ return controller.direction;
3341
+ },
3342
+ get showValue() {
3343
+ return !valueElement.hidden;
3344
+ },
3345
+ get valueSuffix() {
3346
+ return controller.valueSuffix;
3347
+ },
3348
+ get quality() {
3349
+ return merged.quality;
3350
+ },
3351
+ get renderScale() {
3352
+ return merged.renderScale;
3353
+ },
3354
+ get paused() {
3355
+ return manuallyPaused;
3356
+ },
3357
+ get static() {
3358
+ return staticMode;
3359
+ },
3360
+ get fps() {
3361
+ return frameGate.getFps();
3362
+ },
2775
3363
  dirty: dirty
2776
3364
  };
2777
3365
  }
2778
3366
 
2779
- function prefersReducedMotion() {
2780
- return typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches;
3367
+ var HOST_STYLES = new WeakMap();
3368
+ function acquireHostStyles(host) {
3369
+ var active = HOST_STYLES.get(host);
3370
+ if (active) {
3371
+ active.count += 1;
3372
+ return;
3373
+ }
3374
+ var state = {
3375
+ count: 1,
3376
+ position: host.style.position,
3377
+ isolation: host.style.isolation
3378
+ };
3379
+ var computed = getComputedStyle(host);
3380
+ if (computed.position === 'static' || computed.position === '') host.style.position = 'relative';
3381
+ host.style.isolation = 'isolate';
3382
+ HOST_STYLES.set(host, state);
3383
+ }
3384
+ function releaseHostStyles(host) {
3385
+ var state = HOST_STYLES.get(host);
3386
+ if (!state) return;
3387
+ state.count -= 1;
3388
+ if (state.count > 0) return;
3389
+ host.style.position = state.position;
3390
+ host.style.isolation = state.isolation;
3391
+ HOST_STYLES.delete(host);
2781
3392
  }
2782
3393
 
2783
- /**
2784
- * Mount the cosmic (nebula) material as a background layer inside `host`.
2785
- *
2786
- * The layer is absolutely positioned and defaults to `z-index: -1`, so it
2787
- * paints behind the host's in-flow content: text/buttons/images inside the
2788
- * host sit on top with zero extra CSS. Host gets `position: relative;
2789
- * isolation: isolate` automatically so the layer can never escape its box
2790
- * (or break stacking outside it).
2791
- *
2792
- * Options: preset (id/code/name or object), colors, seed, speed, quality,
2793
- * renderer, mouseColor, respectReducedMotion, opacity (0-1), fallbackColor
2794
- * (true=preset base color, a hex string, or false to disable).
3394
+ /**
3395
+ * Mount the cosmic (nebula) material as a background layer inside `host`.
3396
+ *
3397
+ * The layer is absolutely positioned and defaults to `z-index: -1`, so it
3398
+ * paints behind the host's in-flow content: text/buttons/images inside the
3399
+ * host sit on top with zero extra CSS. Host gets `position: relative;
3400
+ * isolation: isolate` automatically so the layer can never escape its box
3401
+ * (or break stacking outside it).
3402
+ *
3403
+ * Options: preset (literary name), colors, seed, speed, quality,
3404
+ * renderer, renderScale, powerPreference, fps, paused/static, mouseColor,
3405
+ * respectReducedMotion, opacity (0-1), fallbackColor (true=preset base
3406
+ * color, a hex string, or false to disable).
2795
3407
  */
2796
3408
  function createColorBackground(host) {
2797
3409
  var _options$preset;
@@ -2805,18 +3417,29 @@ function createColorBackground(host) {
2805
3417
  renderer: DEFAULTS.capsule.renderer,
2806
3418
  respectReducedMotion: DEFAULTS.capsule.respectReducedMotion,
2807
3419
  mouseColor: true,
3420
+ renderScale: 1,
3421
+ powerPreference: 'high-performance',
3422
+ fps: DEFAULTS.capsule.fps,
3423
+ paused: false,
3424
+ static: false,
2808
3425
  opacity: 1,
2809
3426
  fallbackColor: true
2810
3427
  }, preset, options);
2811
- var normalizedColors = merged.colors.map(normalizeColor);
2812
- if (normalizedColors.every(Boolean)) preset.colors = normalizedColors;
2813
- preset.seed = merged.seed;
2814
- preset.speed = merged.speed;
2815
- var computed = getComputedStyle(host);
2816
- if (computed.position === 'static' || computed.position === '') {
2817
- host.style.position = 'relative';
2818
- }
2819
- host.style.isolation = 'isolate';
3428
+ merged.opacity = Number.isFinite(Number(merged.opacity)) ? Math.min(1, Math.max(0, Number(merged.opacity))) : 1;
3429
+ var normalizedColors = Array.isArray(merged.colors) ? merged.colors.map(normalizeColor) : [];
3430
+ if (normalizedColors.length === 4 && normalizedColors.every(Boolean)) preset.colors = normalizedColors;
3431
+ var initialSeed = toFiniteNumber(merged.seed);
3432
+ var initialSpeed = toFiniteNumber(merged.speed);
3433
+ if (initialSeed !== null) preset.seed = initialSeed;
3434
+ if (initialSpeed !== null) preset.speed = initialSpeed;
3435
+ merged.colors = _toConsumableArray(preset.colors);
3436
+ merged.seed = preset.seed;
3437
+ merged.speed = preset.speed;
3438
+ var optionColors = Array.isArray(options.colors) ? options.colors.map(normalizeColor) : [];
3439
+ var colorOverride = optionColors.length === 4 && optionColors.every(Boolean) ? _toConsumableArray(optionColors) : null;
3440
+ var seedOverride = options.seed !== undefined ? toFiniteNumber(options.seed) : null;
3441
+ var speedOverride = options.speed !== undefined ? toFiniteNumber(options.speed) : null;
3442
+ acquireHostStyles(host);
2820
3443
  var layer = document.createElement('div');
2821
3444
  layer.className = 'dlc-color-layer';
2822
3445
  var syncLayerVars = function syncLayerVars() {
@@ -2831,8 +3454,12 @@ function createColorBackground(host) {
2831
3454
  layer.appendChild(canvas);
2832
3455
  host.appendChild(layer);
2833
3456
  var emitter = createEmitter();
2834
- var paused = merged.respectReducedMotion && prefersReducedMotion();
3457
+ var manuallyPaused = merged.paused === true;
3458
+ var reducedPaused = false;
3459
+ var staticMode = merged.static === true;
3460
+ var contextLost = false;
2835
3461
  var disposed = false;
3462
+ var renderOnce = function renderOnce() {};
2836
3463
  var dirty = {
2837
3464
  preset: false,
2838
3465
  seed: false,
@@ -2840,19 +3467,37 @@ function createColorBackground(host) {
2840
3467
  colors: false,
2841
3468
  opacity: false,
2842
3469
  fallbackColor: false,
2843
- mouseColor: false
3470
+ mouseColor: false,
3471
+ quality: false,
3472
+ renderScale: false,
3473
+ paused: false,
3474
+ static: false,
3475
+ fps: false
2844
3476
  };
2845
3477
  var renderer;
2846
3478
  var useWebgl = merged.renderer !== 'canvas2d';
2847
3479
  if (useWebgl) {
2848
3480
  try {
2849
3481
  renderer = new CosmicRenderer(canvas, merged, {
2850
- dprCap: dprCapFor(merged.quality),
3482
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
2851
3483
  mouseColor: merged.mouseColor !== false,
2852
- eventTarget: host
3484
+ eventTarget: host,
3485
+ powerPreference: merged.powerPreference,
3486
+ onContextLost: function onContextLost() {
3487
+ contextLost = true;
3488
+ emitter.emit('contextlost', {});
3489
+ },
3490
+ onContextRestored: function onContextRestored() {
3491
+ contextLost = false;
3492
+ emitter.emit('contextrestored', {});
3493
+ renderOnce();
3494
+ wakeScheduler();
3495
+ }
2853
3496
  });
2854
3497
  } catch (error) {
2855
- renderer = new FallbackRenderer(canvas, merged);
3498
+ renderer = new FallbackRenderer(canvas, merged, {
3499
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale)
3500
+ });
2856
3501
  nextTick(function () {
2857
3502
  if (disposed) return;
2858
3503
  emitter.emit('error', {
@@ -2861,19 +3506,37 @@ function createColorBackground(host) {
2861
3506
  });
2862
3507
  }
2863
3508
  } else {
2864
- renderer = new FallbackRenderer(canvas, merged);
3509
+ renderer = new FallbackRenderer(canvas, merged, {
3510
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale)
3511
+ });
2865
3512
  }
2866
- var resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(function () {
2867
- return renderer.resize();
2868
- }) : null;
2869
- if (resizeObserver) resizeObserver.observe(host);else window.addEventListener('resize', renderer.resize);
2870
- var visibility = createVisibilityGuard(layer);
3513
+ renderer.resize();
2871
3514
  var animationTime = 0;
3515
+ var frameGate = createFrameGate(merged.fps);
3516
+ renderOnce = function renderOnce() {
3517
+ return renderer.draw(animationTime);
3518
+ };
3519
+ var resize = function resize() {
3520
+ renderer.resize();
3521
+ if (manuallyPaused || reducedPaused || staticMode) renderOnce();
3522
+ };
3523
+ var resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(resize) : null;
3524
+ if (resizeObserver) resizeObserver.observe(host);else window.addEventListener('resize', resize);
3525
+ var visibility = createVisibilityGuard(layer, wakeScheduler);
3526
+ var motionPreference = createReducedMotionPreference(merged.respectReducedMotion, function (matches) {
3527
+ reducedPaused = matches;
3528
+ if (matches) renderOnce();else wakeScheduler();
3529
+ });
3530
+ reducedPaused = motionPreference.matches();
3531
+ var isMotionPaused = function isMotionPaused() {
3532
+ return manuallyPaused || reducedPaused || staticMode || contextLost;
3533
+ };
3534
+ renderOnce();
2872
3535
  var unsubscribe = subscribeScheduler(function (delta) {
2873
3536
  animationTime += delta;
2874
- if (visibility.isVisible()) renderer.draw(animationTime);
3537
+ if (frameGate.shouldDraw(delta)) renderer.draw(animationTime);
2875
3538
  }, function () {
2876
- return paused;
3539
+ return isMotionPaused() || !visibility.isVisible();
2877
3540
  });
2878
3541
  nextTick(function () {
2879
3542
  if (disposed) return;
@@ -2892,11 +3555,15 @@ function createColorBackground(host) {
2892
3555
  var next = getPreset('capsule', ref);
2893
3556
  dirty.preset = true;
2894
3557
  Object.assign(preset, next);
3558
+ if (colorOverride) preset.colors = _toConsumableArray(colorOverride);
3559
+ if (seedOverride !== null) preset.seed = seedOverride;
3560
+ if (speedOverride !== null) preset.speed = speedOverride;
2895
3561
  var nextColors = preset.colors.map(normalizeColor);
2896
3562
  if (nextColors.every(Boolean)) preset.colors = nextColors;
2897
3563
  renderer.setPreset(_objectSpread2({}, preset));
2898
3564
  renderer.resize();
2899
3565
  syncLayerVars();
3566
+ if (isMotionPaused()) renderOnce();
2900
3567
  emitter.emit('presetchange', {
2901
3568
  preset: _objectSpread2({}, preset)
2902
3569
  });
@@ -2908,27 +3575,33 @@ function createColorBackground(host) {
2908
3575
  return !color;
2909
3576
  })) return this;
2910
3577
  dirty.colors = true;
3578
+ colorOverride = _toConsumableArray(next);
2911
3579
  preset.colors = next;
2912
3580
  renderer.setPreset(_objectSpread2(_objectSpread2({}, preset), {}, {
2913
3581
  colors: next
2914
3582
  }));
2915
3583
  syncLayerVars();
3584
+ if (isMotionPaused()) renderOnce();
2916
3585
  return this;
2917
3586
  },
2918
3587
  setSeed: function setSeed(seed) {
2919
- var value = Number(seed);
2920
- if (!Number.isFinite(value)) return this;
3588
+ var value = toFiniteNumber(seed);
3589
+ if (value === null) return this;
2921
3590
  dirty.seed = true;
3591
+ seedOverride = value;
2922
3592
  preset.seed = value;
2923
3593
  renderer.setPreset(_objectSpread2({}, preset));
3594
+ if (isMotionPaused()) renderOnce();
2924
3595
  return this;
2925
3596
  },
2926
3597
  setSpeed: function setSpeed(speed) {
2927
- var value = Number(speed);
2928
- if (!Number.isFinite(value)) return this;
3598
+ var value = toFiniteNumber(speed);
3599
+ if (value === null) return this;
2929
3600
  dirty.speed = true;
3601
+ speedOverride = value;
2930
3602
  preset.speed = value;
2931
3603
  renderer.setPreset(_objectSpread2({}, preset));
3604
+ if (isMotionPaused()) renderOnce();
2932
3605
  return this;
2933
3606
  },
2934
3607
  setFallbackColor: function setFallbackColor(value) {
@@ -2944,8 +3617,23 @@ function createColorBackground(host) {
2944
3617
  return this;
2945
3618
  },
2946
3619
  setQuality: function setQuality(quality) {
3620
+ dirty.quality = true;
2947
3621
  merged.quality = quality;
2948
- if (typeof renderer.setDprCap === 'function') renderer.setDprCap(dprCapFor(quality));
3622
+ if (typeof renderer.setDprCap === 'function') {
3623
+ renderer.setDprCap(effectiveDprCap(quality, merged.renderScale));
3624
+ }
3625
+ if (isMotionPaused()) renderOnce();
3626
+ return this;
3627
+ },
3628
+ setRenderScale: function setRenderScale(renderScale) {
3629
+ var value = Number(renderScale);
3630
+ if (!Number.isFinite(value)) return this;
3631
+ dirty.renderScale = true;
3632
+ merged.renderScale = Math.min(1, Math.max(0.25, value));
3633
+ if (typeof renderer.setDprCap === 'function') {
3634
+ renderer.setDprCap(effectiveDprCap(merged.quality, merged.renderScale));
3635
+ }
3636
+ if (isMotionPaused()) renderOnce();
2949
3637
  return this;
2950
3638
  },
2951
3639
  setOpacity: function setOpacity(value) {
@@ -2961,20 +3649,42 @@ function createColorBackground(host) {
2961
3649
  return this;
2962
3650
  },
2963
3651
  pause: function pause() {
2964
- paused = true;
3652
+ dirty.paused = true;
3653
+ manuallyPaused = true;
3654
+ renderOnce();
2965
3655
  return this;
2966
3656
  },
2967
3657
  resume: function resume() {
2968
- paused = false;
3658
+ dirty.paused = true;
3659
+ manuallyPaused = false;
3660
+ wakeScheduler();
3661
+ return this;
3662
+ },
3663
+ setPaused: function setPaused(value) {
3664
+ return value ? this.pause() : this.resume();
3665
+ },
3666
+ setStatic: function setStatic(value) {
3667
+ dirty.static = true;
3668
+ staticMode = value === true;
3669
+ merged.static = staticMode;
3670
+ if (staticMode) renderOnce();else wakeScheduler();
3671
+ return this;
3672
+ },
3673
+ setFps: function setFps(fps) {
3674
+ dirty.fps = true;
3675
+ merged.fps = frameGate.setFps(fps);
3676
+ wakeScheduler();
2969
3677
  return this;
2970
3678
  },
2971
3679
  dispose: function dispose() {
2972
3680
  disposed = true;
2973
3681
  unsubscribe();
2974
3682
  visibility.dispose();
2975
- if (resizeObserver) resizeObserver.disconnect();else window.removeEventListener('resize', renderer.resize);
3683
+ motionPreference.dispose();
3684
+ if (resizeObserver) resizeObserver.disconnect();else window.removeEventListener('resize', resize);
2976
3685
  renderer.dispose();
2977
3686
  layer.remove();
3687
+ releaseHostStyles(host);
2978
3688
  },
2979
3689
  get opacity() {
2980
3690
  return merged.opacity;
@@ -2985,31 +3695,51 @@ function createColorBackground(host) {
2985
3695
  get mouseColor() {
2986
3696
  return merged.mouseColor;
2987
3697
  },
3698
+ get quality() {
3699
+ return merged.quality;
3700
+ },
3701
+ get renderScale() {
3702
+ return merged.renderScale;
3703
+ },
3704
+ get paused() {
3705
+ return manuallyPaused;
3706
+ },
3707
+ get static() {
3708
+ return staticMode;
3709
+ },
3710
+ get fps() {
3711
+ return frameGate.getFps();
3712
+ },
2988
3713
  dirty: dirty
2989
3714
  };
2990
3715
  }
2991
3716
 
2992
- /**
2993
- * Shared Options-API wrapper factory. The only framework difference is the
2994
- * render function: Vue2 passes `h` as an argument, Vue3 needs the imported
2995
- * `h` (see vue2.js / vue3.js). Lifecycle options cover both.
3717
+ /**
3718
+ * Shared Options-API wrapper factory. The only framework difference is the
3719
+ * render function: Vue2 passes `h` as an argument, Vue3 needs the imported
3720
+ * `h` (see vue2.js / vue3.js). Lifecycle options cover both.
2996
3721
  */
2997
3722
 
2998
- var CAPSULE_EVENTS = ['ready', 'error', 'click', 'pointerdown', 'pointerup', 'dblclick', 'pointerenter', 'pointerleave', 'presetchange'];
2999
- var PROGRESS_EVENTS = ['ready', 'error', 'change', 'dragstart', 'dragend', 'presetchange'];
3000
- var COLOR_EVENTS = ['ready', 'error', 'presetchange'];
3723
+ var CAPSULE_EVENTS = ['ready', 'error', 'click', 'pointerdown', 'pointerup', 'dblclick', 'pointerenter', 'pointerleave', 'presetchange', 'contextlost', 'contextrestored'];
3724
+ var PROGRESS_EVENTS = ['ready', 'error', 'change', 'input', 'update:modelValue', 'dragstart', 'dragend', 'presetchange', 'contextlost', 'contextrestored'];
3725
+ var COLOR_EVENTS = ['ready', 'error', 'presetchange', 'contextlost', 'contextrestored'];
3001
3726
  function hookEvents(controller, events, emit) {
3002
3727
  var _iterator = _createForOfIteratorHelper(events),
3003
3728
  _step;
3004
3729
  try {
3005
3730
  var _loop = function _loop() {
3006
3731
  var event = _step.value;
3732
+ if (event === 'input' || event === 'update:modelValue') return 1; // continue
3007
3733
  controller.on(event, function (payload) {
3008
- return emit(event, payload);
3734
+ emit(event, payload);
3735
+ if (event === 'change' && payload && (payload.source === 'drag' || payload.source === 'keyboard')) {
3736
+ if (events.indexOf('input') !== -1) emit('input', payload.value);
3737
+ if (events.indexOf('update:modelValue') !== -1) emit('update:modelValue', payload.value);
3738
+ }
3009
3739
  });
3010
3740
  };
3011
3741
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
3012
- _loop();
3742
+ if (_loop()) continue;
3013
3743
  }
3014
3744
  } catch (err) {
3015
3745
  _iterator.e(err);
@@ -3100,6 +3830,17 @@ function makeWrapper(_ref) {
3100
3830
  if (dirty.opacity) state.opacity = controller.opacity;
3101
3831
  if (dirty.fallbackColor) state.fallbackColor = controller.fallbackColor;
3102
3832
  if (dirty.mouseColor) state.mouseColor = controller.mouseColor;
3833
+ if (dirty.quality) state.quality = controller.quality;
3834
+ if (dirty.renderScale) state.renderScale = controller.renderScale;
3835
+ if (dirty.paused) state.paused = controller.paused;
3836
+ if (dirty.static) state.static = controller.static;
3837
+ if (dirty.fps) state.fps = controller.fps;
3838
+ if (dirty.step) state.step = controller.step;
3839
+ if (dirty.precision) state.precision = controller.precision;
3840
+ if (dirty.formatValue) state.formatValue = controller.formatValue;
3841
+ if (dirty.direction) state.direction = controller.direction;
3842
+ if (dirty.showValue) state.showValue = controller.showValue;
3843
+ if (dirty.valueSuffix) state.valueSuffix = controller.valueSuffix;
3103
3844
  return state;
3104
3845
  }
3105
3846
  };
@@ -3145,7 +3886,7 @@ function makeWrapper(_ref) {
3145
3886
  function makeCapsuleWrapper(createCapsule, render) {
3146
3887
  return makeWrapper({
3147
3888
  name: 'dlc-capsule',
3148
- props: ['preset', 'width', 'height', 'colors', 'seed', 'speed', 'textRatio', 'label', 'mouseColor', 'renderer', 'quality', 'respectReducedMotion', 'cssVars'],
3889
+ props: ['preset', 'width', 'height', 'colors', 'seed', 'speed', 'textRatio', 'label', 'mouseColor', 'renderer', 'quality', 'respectReducedMotion', 'cssVars', 'paused', 'static', 'fps', 'renderScale', 'powerPreference'],
3149
3890
  events: CAPSULE_EVENTS,
3150
3891
  create: createCapsule,
3151
3892
  render: render,
@@ -3174,8 +3915,26 @@ function makeCapsuleWrapper(createCapsule, render) {
3174
3915
  mouseColor: function mouseColor(value) {
3175
3916
  if (this.controller) this.controller.setMouseColor(value);
3176
3917
  },
3918
+ paused: function paused(value) {
3919
+ if (this.controller) this.controller.setPaused(value);
3920
+ },
3921
+ static: function _static(value) {
3922
+ if (this.controller) this.controller.setStatic(value);
3923
+ },
3924
+ fps: function fps(value) {
3925
+ if (value !== undefined && this.controller) this.controller.setFps(value);
3926
+ },
3927
+ renderScale: function renderScale(value) {
3928
+ if (value !== undefined && this.controller) this.controller.setRenderScale(value);
3929
+ },
3177
3930
  renderer: function renderer() {
3178
3931
  this.scheduleRecreate();
3932
+ },
3933
+ powerPreference: function powerPreference() {
3934
+ this.scheduleRecreate();
3935
+ },
3936
+ respectReducedMotion: function respectReducedMotion() {
3937
+ this.scheduleRecreate();
3179
3938
  }
3180
3939
  },
3181
3940
  methods: {
@@ -3218,6 +3977,18 @@ function makeCapsuleWrapper(createCapsule, render) {
3218
3977
  setQuality: function setQuality(quality) {
3219
3978
  return this.controller && this.controller.setQuality(quality);
3220
3979
  },
3980
+ setRenderScale: function setRenderScale(value) {
3981
+ return this.controller && this.controller.setRenderScale(value);
3982
+ },
3983
+ setPaused: function setPaused(value) {
3984
+ return this.controller && this.controller.setPaused(value);
3985
+ },
3986
+ setStatic: function setStatic(value) {
3987
+ return this.controller && this.controller.setStatic(value);
3988
+ },
3989
+ setFps: function setFps(value) {
3990
+ return this.controller && this.controller.setFps(value);
3991
+ },
3221
3992
  getController: function getController() {
3222
3993
  return this.controller;
3223
3994
  }
@@ -3227,13 +3998,16 @@ function makeCapsuleWrapper(createCapsule, render) {
3227
3998
  function makeProgressWrapper(createProgressCapsule, render) {
3228
3999
  return makeWrapper({
3229
4000
  name: 'dlc-progress',
3230
- props: ['preset', 'width', 'height', 'value', 'min', 'max', 'draggable', 'disabled', 'readonly', 'edgeStyle', 'colors', 'textRatio', 'label', 'showValue', 'valueSuffix', 'quality', 'renderer', 'respectReducedMotion', 'cssVars'],
4001
+ 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'],
3231
4002
  events: PROGRESS_EVENTS,
3232
4003
  create: createProgressCapsule,
3233
4004
  render: render,
3234
4005
  watch: {
3235
4006
  value: function value(_value) {
3236
- if (_value !== undefined && this.controller) this.controller.setValue(_value);
4007
+ if (_value !== undefined && this.modelValue === undefined && this.controller) this.controller.setValue(_value);
4008
+ },
4009
+ modelValue: function modelValue(value) {
4010
+ if (value !== undefined && this.controller) this.controller.setValue(value);
3237
4011
  },
3238
4012
  min: function min(value) {
3239
4013
  if (value !== undefined && this.controller) this.controller.setRange(value, this.controller.max);
@@ -3241,6 +4015,9 @@ function makeProgressWrapper(createProgressCapsule, render) {
3241
4015
  max: function max(value) {
3242
4016
  if (value !== undefined && this.controller) this.controller.setRange(this.controller.min, value);
3243
4017
  },
4018
+ step: function step(value) {
4019
+ if (value !== undefined && this.controller) this.controller.setStep(value);
4020
+ },
3244
4021
  edgeStyle: function edgeStyle(value) {
3245
4022
  if (value && this.controller) this.controller.setEdgeStyle(value);
3246
4023
  },
@@ -3259,6 +4036,27 @@ function makeProgressWrapper(createProgressCapsule, render) {
3259
4036
  showValue: function showValue(value) {
3260
4037
  if (this.controller) this.controller.setShowValue(value);
3261
4038
  },
4039
+ direction: function direction(value) {
4040
+ if (value && this.controller) this.controller.setDirection(value);
4041
+ },
4042
+ precision: function precision(value) {
4043
+ if (value !== undefined && this.controller) this.controller.setPrecision(value);
4044
+ },
4045
+ formatValue: function formatValue(value) {
4046
+ if (this.controller) this.controller.setFormatValue(value);
4047
+ },
4048
+ paused: function paused(value) {
4049
+ if (this.controller) this.controller.setPaused(value);
4050
+ },
4051
+ static: function _static(value) {
4052
+ if (this.controller) this.controller.setStatic(value);
4053
+ },
4054
+ fps: function fps(value) {
4055
+ if (value !== undefined && this.controller) this.controller.setFps(value);
4056
+ },
4057
+ renderScale: function renderScale(value) {
4058
+ if (value !== undefined && this.controller) this.controller.setRenderScale(value);
4059
+ },
3262
4060
  disabled: function disabled() {
3263
4061
  this.scheduleRecreate();
3264
4062
  },
@@ -3270,6 +4068,15 @@ function makeProgressWrapper(createProgressCapsule, render) {
3270
4068
  },
3271
4069
  renderer: function renderer() {
3272
4070
  this.scheduleRecreate();
4071
+ },
4072
+ keyboard: function keyboard() {
4073
+ this.scheduleRecreate();
4074
+ },
4075
+ powerPreference: function powerPreference() {
4076
+ this.scheduleRecreate();
4077
+ },
4078
+ respectReducedMotion: function respectReducedMotion() {
4079
+ this.scheduleRecreate();
3273
4080
  }
3274
4081
  },
3275
4082
  methods: {
@@ -3282,6 +4089,9 @@ function makeProgressWrapper(createProgressCapsule, render) {
3282
4089
  setRange: function setRange(min, max) {
3283
4090
  return this.controller && this.controller.setRange(min, max);
3284
4091
  },
4092
+ setStep: function setStep(step) {
4093
+ return this.controller && this.controller.setStep(step);
4094
+ },
3285
4095
  randomize: function randomize() {
3286
4096
  return this.controller && this.controller.randomize();
3287
4097
  },
@@ -3321,9 +4131,30 @@ function makeProgressWrapper(createProgressCapsule, render) {
3321
4131
  setShowValue: function setShowValue(show) {
3322
4132
  return this.controller && this.controller.setShowValue(show);
3323
4133
  },
4134
+ setDirection: function setDirection(direction) {
4135
+ return this.controller && this.controller.setDirection(direction);
4136
+ },
4137
+ setPrecision: function setPrecision(precision) {
4138
+ return this.controller && this.controller.setPrecision(precision);
4139
+ },
4140
+ setFormatValue: function setFormatValue(formatter) {
4141
+ return this.controller && this.controller.setFormatValue(formatter);
4142
+ },
3324
4143
  setQuality: function setQuality(quality) {
3325
4144
  return this.controller && this.controller.setQuality(quality);
3326
4145
  },
4146
+ setRenderScale: function setRenderScale(value) {
4147
+ return this.controller && this.controller.setRenderScale(value);
4148
+ },
4149
+ setPaused: function setPaused(value) {
4150
+ return this.controller && this.controller.setPaused(value);
4151
+ },
4152
+ setStatic: function setStatic(value) {
4153
+ return this.controller && this.controller.setStatic(value);
4154
+ },
4155
+ setFps: function setFps(value) {
4156
+ return this.controller && this.controller.setFps(value);
4157
+ },
3327
4158
  getController: function getController() {
3328
4159
  return this.controller;
3329
4160
  }
@@ -3333,7 +4164,7 @@ function makeProgressWrapper(createProgressCapsule, render) {
3333
4164
  function makeColorWrapper(createColorBackground, render) {
3334
4165
  return makeWrapper({
3335
4166
  name: 'dlc-color',
3336
- props: ['preset', 'colors', 'seed', 'speed', 'quality', 'renderer', 'mouseColor', 'respectReducedMotion', 'opacity', 'fallbackColor'],
4167
+ props: ['preset', 'colors', 'seed', 'speed', 'quality', 'renderer', 'mouseColor', 'respectReducedMotion', 'opacity', 'fallbackColor', 'paused', 'static', 'fps', 'renderScale', 'powerPreference'],
3337
4168
  events: COLOR_EVENTS,
3338
4169
  create: createColorBackground,
3339
4170
  render: render,
@@ -3355,11 +4186,26 @@ function makeColorWrapper(createColorBackground, render) {
3355
4186
  mouseColor: function mouseColor(value) {
3356
4187
  if (this.controller) this.controller.setMouseColor(value);
3357
4188
  },
4189
+ paused: function paused(value) {
4190
+ if (this.controller) this.controller.setPaused(value);
4191
+ },
4192
+ static: function _static(value) {
4193
+ if (this.controller) this.controller.setStatic(value);
4194
+ },
4195
+ fps: function fps(value) {
4196
+ if (value !== undefined && this.controller) this.controller.setFps(value);
4197
+ },
4198
+ renderScale: function renderScale(value) {
4199
+ if (value !== undefined && this.controller) this.controller.setRenderScale(value);
4200
+ },
3358
4201
  renderer: function renderer() {
3359
4202
  this.scheduleRecreate();
3360
4203
  },
3361
4204
  respectReducedMotion: function respectReducedMotion() {
3362
4205
  this.scheduleRecreate();
4206
+ },
4207
+ powerPreference: function powerPreference() {
4208
+ this.scheduleRecreate();
3363
4209
  }
3364
4210
  },
3365
4211
  methods: {
@@ -3378,6 +4224,9 @@ function makeColorWrapper(createColorBackground, render) {
3378
4224
  setQuality: function setQuality(quality) {
3379
4225
  return this.controller && this.controller.setQuality(quality);
3380
4226
  },
4227
+ setRenderScale: function setRenderScale(value) {
4228
+ return this.controller && this.controller.setRenderScale(value);
4229
+ },
3381
4230
  setOpacity: function setOpacity(value) {
3382
4231
  return this.controller && this.controller.setOpacity(value);
3383
4232
  },
@@ -3396,6 +4245,15 @@ function makeColorWrapper(createColorBackground, render) {
3396
4245
  resume: function resume() {
3397
4246
  return this.controller && this.controller.resume();
3398
4247
  },
4248
+ setPaused: function setPaused(value) {
4249
+ return this.controller && this.controller.setPaused(value);
4250
+ },
4251
+ setStatic: function setStatic(value) {
4252
+ return this.controller && this.controller.setStatic(value);
4253
+ },
4254
+ setFps: function setFps(value) {
4255
+ return this.controller && this.controller.setFps(value);
4256
+ },
3399
4257
  getController: function getController() {
3400
4258
  return this.controller;
3401
4259
  }