@5even7/dlc-ui 0.2.11 → 0.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/capsule.cjs CHANGED
@@ -182,9 +182,9 @@ function _unsupportedIterableToArray(r, a) {
182
182
  }
183
183
  }
184
184
 
185
- /**
186
- * Tiny event emitter. Accepts any event name so the API stays open for
187
- * future events (hover, click, custom) without breaking changes.
185
+ /**
186
+ * Tiny event emitter. Accepts any event name so the API stays open for
187
+ * future events (hover, click, custom) without breaking changes.
188
188
  */
189
189
  function createEmitter() {
190
190
  // Plain object storage (no Map/Set) so the legacy build has no API
@@ -234,9 +234,9 @@ function createEmitter() {
234
234
 
235
235
  var UNIT_PATTERN = /^[\d.]+(?:px|%|vw|vh|vmin|vmax|em|rem)$/;
236
236
 
237
- /**
238
- * Normalize a size option to a CSS length string.
239
- * Accepts numbers (treated as px) or strings with px/%, vw/vh/vmin/vmax, em/rem.
237
+ /**
238
+ * Normalize a size option to a CSS length string.
239
+ * Accepts numbers (treated as px) or strings with px/%, vw/vh/vmin/vmax, em/rem.
240
240
  */
241
241
  function parseSize(value) {
242
242
  if (typeof value === 'number') {
@@ -275,11 +275,17 @@ function dprCapFor(quality) {
275
275
  var tier = QUALITY_TIERS[quality] || QUALITY_TIERS.medium;
276
276
  return tier.dpr;
277
277
  }
278
+ function effectiveDprCap(quality) {
279
+ var renderScale = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
280
+ var scale = Number(renderScale);
281
+ var normalizedScale = Number.isFinite(scale) ? Math.min(1, Math.max(0.25, scale)) : 1;
282
+ return Math.max(0.5, dprCapFor(quality) * normalizedScale);
283
+ }
278
284
 
279
- /**
280
- * Merge defaults < preset < user. Unknown user keys are preserved so the
281
- * component API can grow (new props, cssVars, callbacks) without a breaking
282
- * change.
285
+ /**
286
+ * Merge defaults < preset < user. Unknown user keys are preserved so the
287
+ * component API can grow (new props, cssVars, callbacks) without a breaking
288
+ * change.
283
289
  */
284
290
  function normalizeOptions(defaults, preset, user) {
285
291
  // undefined means "not provided" (e.g. Vue $props with unset props):
@@ -297,9 +303,9 @@ function normalizeOptions(defaults, preset, user) {
297
303
  return _objectSpread2(_objectSpread2(_objectSpread2({}, defaults), presetOptions), userOptions);
298
304
  }
299
305
 
300
- /**
301
- * 公共色板:NC-01~NC-06 的四色组(底色 / 主色 / 辅色 / 高光色)。
302
- * Capsule 预置与 dlc-color 等组件共用,新增色板只改这里。
306
+ /**
307
+ * 公共色板:NC-01~NC-06 的四色组(底色 / 主色 / 辅色 / 高光色)。
308
+ * Capsule 预置与 dlc-color 等组件共用,新增色板只改这里。
303
309
  */
304
310
  var PALETTES = {
305
311
  original: ['#FFF3EA', '#F5B27A', '#F67BC6', '#A978E8'],
@@ -310,9 +316,9 @@ var PALETTES = {
310
316
  plus: ['#FFF0E6', '#F6C26B', '#F98A64', '#E86D74']
311
317
  };
312
318
 
313
- /**
314
- * Capsule 预置:仅星云材质 NC-01~NC-06(NC-07~09 aurora 已移除)。
315
- * 颜色引用公共色板,seed/speed 决定形态与流速。
319
+ /**
320
+ * Capsule 预置:仅星云材质 NC-01~NC-06(NC-07~09 aurora 已移除)。
321
+ * 颜色引用公共色板,seed/speed 决定形态与流速。
316
322
  */
317
323
  var CAPSULE_PRESETS = [{
318
324
  id: 'original',
@@ -400,6 +406,15 @@ var PROGRESS_PRESETS = [{
400
406
  initialProgress: 30,
401
407
  edgeStyle: 'tide',
402
408
  colors: ['#0A2239', '#2E9BFF', '#7FE3FF', '#EAF9FF']
409
+ }, {
410
+ id: 'botanic-lab',
411
+ code: 'NC-14',
412
+ name: '森息',
413
+ subtitle: 'MOSS CULTURE / ROOTING',
414
+ group: 'progress',
415
+ initialProgress: 46,
416
+ edgeStyle: 'flow',
417
+ colors: ['#102A23', '#287A58', '#9BD65B', '#F2FFC4']
403
418
  }];
404
419
 
405
420
  [].concat(_toConsumableArray(CAPSULE_PRESETS), _toConsumableArray(PROGRESS_PRESETS));
@@ -430,6 +445,11 @@ var DEFAULTS = {
430
445
  height: 160,
431
446
  quality: 'auto',
432
447
  renderer: 'auto',
448
+ renderScale: 1,
449
+ powerPreference: 'high-performance',
450
+ fps: 60,
451
+ paused: false,
452
+ static: false,
433
453
  respectReducedMotion: true,
434
454
  mouseColor: true,
435
455
  textRatio: 39
@@ -700,7 +720,7 @@ var CosmicRenderer = /*#__PURE__*/function () {
700
720
  alpha: false,
701
721
  antialias: false,
702
722
  depth: false,
703
- powerPreference: 'high-performance',
723
+ powerPreference: this.options.powerPreference || 'high-performance',
704
724
  preserveDrawingBuffer: false
705
725
  });
706
726
  if (!this.gl) throw new Error('WebGL2 is not available');
@@ -711,16 +731,19 @@ var CosmicRenderer = /*#__PURE__*/function () {
711
731
  this.motion = 0;
712
732
  this.motionTarget = 0;
713
733
  this.timeOffset = preset.seed * 0.73;
734
+ this.colors = preset.colors.map(hexToRgb01);
714
735
  this.visible = true;
715
736
  this.disposed = false;
716
737
  _assertClassBrand(_CosmicRenderer_brand, this, _setupGeometry).call(this);
717
738
  _assertClassBrand(_CosmicRenderer_brand, this, _bindEvents).call(this);
739
+ _assertClassBrand(_CosmicRenderer_brand, this, _bindContextEvents).call(this);
718
740
  this.resize();
719
741
  }
720
742
  return _createClass(CosmicRenderer, [{
721
743
  key: "setPreset",
722
744
  value: function setPreset(preset) {
723
745
  this.preset = _objectSpread2({}, preset);
746
+ this.colors = preset.colors.map(hexToRgb01);
724
747
  this.timeOffset = preset.seed * 0.73;
725
748
  }
726
749
  }, {
@@ -765,15 +788,14 @@ var CosmicRenderer = /*#__PURE__*/function () {
765
788
  if (this.canvas.width !== width || this.canvas.height !== height) {
766
789
  this.canvas.width = width;
767
790
  this.canvas.height = height;
768
- this.gl.viewport(0, 0, width, height);
769
791
  }
792
+ this.gl.viewport(0, 0, width, height);
770
793
  }
771
794
  }, {
772
795
  key: "draw",
773
796
  value: function draw(elapsedSeconds) {
774
797
  var paused = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
775
798
  if (this.disposed || !this.visible) return;
776
- this.resize();
777
799
  var gl = this.gl;
778
800
  this.pointer[0] += (this.pointerTarget[0] - this.pointer[0]) * 0.08;
779
801
  this.pointer[1] += (this.pointerTarget[1] - this.pointer[1]) * 0.08;
@@ -782,16 +804,15 @@ var CosmicRenderer = /*#__PURE__*/function () {
782
804
  gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
783
805
  gl.enableVertexAttribArray(this.locations.position);
784
806
  gl.vertexAttribPointer(this.locations.position, 2, gl.FLOAT, false, 0, 0);
785
- var colors = this.preset.colors.map(hexToRgb01);
786
807
  gl.uniform2f(this.locations.resolution, this.canvas.width, this.canvas.height);
787
808
  gl.uniform1f(this.locations.time, this.timeOffset + (paused ? 0 : elapsedSeconds * this.preset.speed));
788
809
  gl.uniform1f(this.locations.seed, this.preset.seed);
789
810
  gl.uniform1f(this.locations.motion, this.motion);
790
811
  gl.uniform2f(this.locations.pointer, this.pointer[0], this.pointer[1]);
791
- gl.uniform3fv(this.locations.colorA, colors[0]);
792
- gl.uniform3fv(this.locations.colorB, colors[1]);
793
- gl.uniform3fv(this.locations.colorC, colors[2]);
794
- gl.uniform3fv(this.locations.colorD, colors[3]);
812
+ gl.uniform3fv(this.locations.colorA, this.colors[0]);
813
+ gl.uniform3fv(this.locations.colorB, this.colors[1]);
814
+ gl.uniform3fv(this.locations.colorC, this.colors[2]);
815
+ gl.uniform3fv(this.locations.colorD, this.colors[3]);
795
816
  gl.drawArrays(gl.TRIANGLES, 0, 3);
796
817
  }
797
818
  }, {
@@ -802,6 +823,8 @@ var CosmicRenderer = /*#__PURE__*/function () {
802
823
  target.removeEventListener('pointermove', this.onPointerMove);
803
824
  target.removeEventListener('pointerdown', this.onPointerMove);
804
825
  target.removeEventListener('pointerleave', this.onPointerLeave);
826
+ this.canvas.removeEventListener('webglcontextlost', this.onContextLost, false);
827
+ this.canvas.removeEventListener('webglcontextrestored', this.onContextRestored, false);
805
828
  this.gl.deleteBuffer(this.buffer);
806
829
  this.gl.deleteProgram(this.program);
807
830
  var lose = this.gl.getExtension('WEBGL_lose_context');
@@ -858,6 +881,26 @@ function _bindEvents() {
858
881
  passive: true
859
882
  });
860
883
  }
884
+ function _bindContextEvents() {
885
+ var _this3 = this;
886
+ this.onContextLost = function (event) {
887
+ event.preventDefault();
888
+ if (_this3.disposed) return;
889
+ _this3.visible = false;
890
+ if (typeof _this3.options.onContextLost === 'function') _this3.options.onContextLost(event);
891
+ };
892
+ this.onContextRestored = function () {
893
+ if (_this3.disposed) return;
894
+ _this3.program = createProgram(_this3.gl);
895
+ _this3.locations = _assertClassBrand(_CosmicRenderer_brand, _this3, _getLocations).call(_this3);
896
+ _assertClassBrand(_CosmicRenderer_brand, _this3, _setupGeometry).call(_this3);
897
+ _this3.visible = true;
898
+ _this3.resize();
899
+ if (typeof _this3.options.onContextRestored === 'function') _this3.options.onContextRestored();
900
+ };
901
+ this.canvas.addEventListener('webglcontextlost', this.onContextLost, false);
902
+ this.canvas.addEventListener('webglcontextrestored', this.onContextRestored, false);
903
+ }
861
904
 
862
905
  function rgb(color) {
863
906
  var alpha = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
@@ -872,18 +915,20 @@ function rgb(color) {
872
915
  }
873
916
  var FallbackRenderer = /*#__PURE__*/function () {
874
917
  function FallbackRenderer(canvas, preset) {
918
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
875
919
  _classCallCheck(this, FallbackRenderer);
876
920
  this.canvas = canvas;
877
921
  this.preset = preset;
878
922
  this.context = canvas.getContext('2d');
879
923
  this.visible = true;
880
924
  this.timePhase = 0;
925
+ this.dprCap = options.dprCap || 1.5;
881
926
  }
882
927
  return _createClass(FallbackRenderer, [{
883
928
  key: "resize",
884
929
  value: function resize() {
885
930
  var rect = this.canvas.getBoundingClientRect();
886
- var dpr = Math.min(window.devicePixelRatio || 1, 1.5);
931
+ var dpr = Math.min(window.devicePixelRatio || 1, this.dprCap);
887
932
  var width = Math.max(2, Math.round(rect.width * dpr));
888
933
  var height = Math.max(2, Math.round(rect.height * dpr));
889
934
  if (this.canvas.width !== width || this.canvas.height !== height) {
@@ -924,7 +969,6 @@ var FallbackRenderer = /*#__PURE__*/function () {
924
969
  key: "draw",
925
970
  value: function draw(time) {
926
971
  if (!this.visible) return;
927
- this.resize();
928
972
  this.drawNebula(time);
929
973
  }
930
974
  }, {
@@ -932,6 +976,12 @@ var FallbackRenderer = /*#__PURE__*/function () {
932
976
  value: function setPreset(preset) {
933
977
  this.preset = preset;
934
978
  }
979
+ }, {
980
+ key: "setDprCap",
981
+ value: function setDprCap(cap) {
982
+ this.dprCap = cap;
983
+ this.resize();
984
+ }
935
985
  }, {
936
986
  key: "randomize",
937
987
  value: function randomize() {
@@ -949,10 +999,10 @@ var FallbackRenderer = /*#__PURE__*/function () {
949
999
  }]);
950
1000
  }();
951
1001
 
952
- /**
953
- * Document-level shared rAF scheduler. Every component instance subscribes
954
- * its own frame callback; the whole page runs ONE animation loop (like the
955
- * original demo), which avoids jank from many competing rAF loops.
1002
+ /**
1003
+ * Document-level shared rAF scheduler. Every component instance subscribes
1004
+ * its own frame callback; the whole page runs ONE animation loop (like the
1005
+ * original demo), which avoids jank from many competing rAF loops.
956
1006
  */
957
1007
  var subscribers = [];
958
1008
  var running = false;
@@ -960,11 +1010,7 @@ var rafId = 0;
960
1010
  var last = 0;
961
1011
  function tick(now) {
962
1012
  if (!running) return;
963
- var delta = last ? Math.min((now - last) / 1000, 0.05) : 0;
964
- last = now;
965
- // Schedule the next frame BEFORE running callbacks so one throwing
966
- // subscriber can never kill the whole animation loop.
967
- rafId = requestAnimationFrame(tick);
1013
+ var activeItems = [];
968
1014
  {
969
1015
  var _iterator = _createForOfIteratorHelper(subscribers.slice()),
970
1016
  _step;
@@ -972,10 +1018,8 @@ function tick(now) {
972
1018
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
973
1019
  var item = _step.value;
974
1020
  try {
975
- if (!item.isPaused()) item.onFrame(delta, now);
976
- } catch (error) {
977
- console.warn('[dlc-ui] frame error:', error);
978
- }
1021
+ if (!item.isPaused()) activeItems.push(item);
1022
+ } catch (_unused) {}
979
1023
  }
980
1024
  } catch (err) {
981
1025
  _iterator.e(err);
@@ -983,6 +1027,25 @@ function tick(now) {
983
1027
  _iterator.f();
984
1028
  }
985
1029
  }
1030
+ if (activeItems.length === 0) {
1031
+ running = false;
1032
+ rafId = 0;
1033
+ last = 0;
1034
+ return;
1035
+ }
1036
+ var delta = last ? Math.min((now - last) / 1000, 0.05) : 0;
1037
+ last = now;
1038
+ // Schedule the next frame BEFORE running callbacks so one throwing
1039
+ // subscriber can never kill the whole animation loop.
1040
+ rafId = requestAnimationFrame(tick);
1041
+ for (var _i = 0, _activeItems = activeItems; _i < _activeItems.length; _i++) {
1042
+ var _item = _activeItems[_i];
1043
+ try {
1044
+ _item.onFrame(delta, now);
1045
+ } catch (error) {
1046
+ console.warn('[dlc-ui] frame error:', error);
1047
+ }
1048
+ }
986
1049
  if (subscribers.length === 0) {
987
1050
  cancelAnimationFrame(rafId);
988
1051
  running = false;
@@ -995,6 +1058,9 @@ function start() {
995
1058
  last = 0;
996
1059
  rafId = requestAnimationFrame(tick);
997
1060
  }
1061
+ function wakeScheduler() {
1062
+ start();
1063
+ }
998
1064
  function subscribeScheduler(onFrame, isPaused) {
999
1065
  var item = {
1000
1066
  onFrame: onFrame,
@@ -1013,11 +1079,12 @@ function subscribeScheduler(onFrame, isPaused) {
1013
1079
  };
1014
1080
  }
1015
1081
 
1016
- /**
1017
- * Gates drawing on "element intersects viewport AND the page tab is visible".
1018
- * Falls back to always-visible when IntersectionObserver is unavailable.
1082
+ /**
1083
+ * Gates drawing on "element intersects viewport AND the page tab is visible".
1084
+ * Falls back to always-visible when IntersectionObserver is unavailable.
1019
1085
  */
1020
1086
  function createVisibilityGuard(element) {
1087
+ var onChange = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
1021
1088
  var intersecting = true;
1022
1089
  var pageVisible = typeof document === 'undefined' || !document.hidden;
1023
1090
  var disposed = false;
@@ -1027,6 +1094,7 @@ function createVisibilityGuard(element) {
1027
1094
  intersecting = entries.some(function (entry) {
1028
1095
  return entry.isIntersecting;
1029
1096
  });
1097
+ if (typeof onChange === 'function') onChange();
1030
1098
  }, {
1031
1099
  rootMargin: '180px'
1032
1100
  });
@@ -1034,6 +1102,7 @@ function createVisibilityGuard(element) {
1034
1102
  }
1035
1103
  var onVisibilityChange = function onVisibilityChange() {
1036
1104
  pageVisible = typeof document !== 'undefined' && !document.hidden;
1105
+ if (typeof onChange === 'function') onChange();
1037
1106
  };
1038
1107
  if (typeof document !== 'undefined') {
1039
1108
  document.addEventListener('visibilitychange', onVisibilityChange);
@@ -1069,18 +1138,69 @@ function nextTick(fn) {
1069
1138
  }
1070
1139
  }
1071
1140
 
1072
- function prefersReducedMotion() {
1073
- return typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches;
1141
+ function normalizeFps(value) {
1142
+ var fallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 60;
1143
+ var fps = Number(value);
1144
+ if (!Number.isFinite(fps)) return fallback;
1145
+ return Math.min(60, Math.max(1, fps));
1146
+ }
1147
+ function createFrameGate() {
1148
+ var initialFps = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 60;
1149
+ var fps = normalizeFps(initialFps);
1150
+ var elapsed = 0;
1151
+ return {
1152
+ shouldDraw: function shouldDraw(delta) {
1153
+ elapsed += delta;
1154
+ var interval = 1 / fps;
1155
+ if (elapsed + 0.0001 < interval) return false;
1156
+ elapsed %= interval;
1157
+ return true;
1158
+ },
1159
+ setFps: function setFps(value) {
1160
+ fps = normalizeFps(value, fps);
1161
+ elapsed = 0;
1162
+ return fps;
1163
+ },
1164
+ getFps: function getFps() {
1165
+ return fps;
1166
+ }
1167
+ };
1168
+ }
1169
+ function createReducedMotionPreference(enabled, onChange) {
1170
+ var query = enabled && typeof matchMedia !== 'undefined' ? matchMedia('(prefers-reduced-motion: reduce)') : null;
1171
+ var notify = function notify() {
1172
+ if (typeof onChange === 'function') onChange(Boolean(query && query.matches));
1173
+ };
1174
+ if (query) {
1175
+ if (typeof query.addEventListener === 'function') query.addEventListener('change', notify);else if (typeof query.addListener === 'function') query.addListener(notify);
1176
+ }
1177
+ return {
1178
+ matches: function matches() {
1179
+ return Boolean(query && query.matches);
1180
+ },
1181
+ dispose: function dispose() {
1182
+ if (!query) return;
1183
+ if (typeof query.removeEventListener === 'function') query.removeEventListener('change', notify);else if (typeof query.removeListener === 'function') query.removeListener(notify);
1184
+ }
1185
+ };
1186
+ }
1187
+
1188
+ function toFiniteNumber(value) {
1189
+ if (value == null || typeof value === 'boolean') return null;
1190
+ if (typeof value === 'string' && value.trim() === '') return null;
1191
+ var number = Number(value);
1192
+ return Number.isFinite(number) ? number : null;
1074
1193
  }
1075
1194
 
1076
- /**
1195
+ /**
1077
1196
  * Mount a cosmic (nebula) capsule into `container`.
1078
1197
  *
1079
1198
  * Options: preset (literary name), width, height (number=px or string with
1080
1199
  * px/%/vw/vh/em/rem), colors, seed, speed, textRatio (0-100, text region
1081
1200
  * width in percent), text (HTML string or DOM nodes for the text slot),
1082
1201
  * colorContent (HTML string or DOM nodes for the color slot), quality,
1083
- * renderer, respectReducedMotion, mouseColor, cssVars.
1202
+ * renderer, quality, renderScale, powerPreference, fps, paused/static,
1203
+ * respectReducedMotion, mouseColor, cssVars.
1084
1204
  */
1085
1205
  function createCapsule(container) {
1086
1206
  var _options$preset;
@@ -1090,10 +1210,19 @@ function createCapsule(container) {
1090
1210
  }
1091
1211
  var preset = _objectSpread2({}, getPreset('capsule', (_options$preset = options.preset) !== null && _options$preset !== void 0 ? _options$preset : '初光'));
1092
1212
  var merged = normalizeOptions(DEFAULTS.capsule, preset, options);
1093
- var normalizedColors = merged.colors.map(normalizeColor);
1094
- if (normalizedColors.every(Boolean)) preset.colors = normalizedColors;
1095
- preset.seed = merged.seed;
1096
- preset.speed = merged.speed;
1213
+ var normalizedColors = Array.isArray(merged.colors) ? merged.colors.map(normalizeColor) : [];
1214
+ if (normalizedColors.length === 4 && normalizedColors.every(Boolean)) preset.colors = normalizedColors;
1215
+ var initialSeed = toFiniteNumber(merged.seed);
1216
+ var initialSpeed = toFiniteNumber(merged.speed);
1217
+ if (initialSeed !== null) preset.seed = initialSeed;
1218
+ if (initialSpeed !== null) preset.speed = initialSpeed;
1219
+ merged.colors = _toConsumableArray(preset.colors);
1220
+ merged.seed = preset.seed;
1221
+ merged.speed = preset.speed;
1222
+ var optionColors = Array.isArray(options.colors) ? options.colors.map(normalizeColor) : [];
1223
+ var colorOverride = optionColors.length === 4 && optionColors.every(Boolean) ? _toConsumableArray(optionColors) : null;
1224
+ var seedOverride = options.seed !== undefined ? toFiniteNumber(options.seed) : null;
1225
+ var speedOverride = options.speed !== undefined ? toFiniteNumber(options.speed) : null;
1097
1226
  var customLabel = typeof options.label === 'string' && options.label ? options.label : null;
1098
1227
  var root = document.createElement('div');
1099
1228
  root.className = 'hj-capsule-root hj-capsule-cosmic';
@@ -1145,25 +1274,49 @@ function createCapsule(container) {
1145
1274
  root.appendChild(canvas);
1146
1275
  container.appendChild(root);
1147
1276
  var emitter = createEmitter();
1148
- var paused = merged.respectReducedMotion && prefersReducedMotion();
1277
+ var manuallyPaused = merged.paused === true;
1278
+ var reducedPaused = false;
1279
+ var staticMode = merged.static === true;
1280
+ var contextLost = false;
1149
1281
  var disposed = false;
1282
+ var renderOnce = function renderOnce() {};
1150
1283
  var dirty = {
1151
1284
  preset: false,
1152
1285
  seed: false,
1153
1286
  speed: false,
1154
1287
  colors: false,
1155
1288
  textRatio: false,
1156
- cssVars: false
1289
+ cssVars: false,
1290
+ mouseColor: false,
1291
+ quality: false,
1292
+ renderScale: false,
1293
+ paused: false,
1294
+ static: false,
1295
+ fps: false
1157
1296
  };
1158
1297
  var renderer;
1159
1298
  var useWebgl = merged.renderer !== 'canvas2d';
1160
1299
  if (useWebgl) {
1161
1300
  try {
1162
1301
  renderer = new CosmicRenderer(canvas, merged, {
1163
- dprCap: dprCapFor(merged.quality)
1302
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
1303
+ mouseColor: merged.mouseColor !== false,
1304
+ powerPreference: merged.powerPreference,
1305
+ onContextLost: function onContextLost() {
1306
+ contextLost = true;
1307
+ emitter.emit('contextlost', {});
1308
+ },
1309
+ onContextRestored: function onContextRestored() {
1310
+ contextLost = false;
1311
+ emitter.emit('contextrestored', {});
1312
+ renderOnce();
1313
+ wakeScheduler();
1314
+ }
1164
1315
  });
1165
1316
  } catch (error) {
1166
- renderer = new FallbackRenderer(canvas, merged);
1317
+ renderer = new FallbackRenderer(canvas, merged, {
1318
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale)
1319
+ });
1167
1320
  nextTick(function () {
1168
1321
  if (disposed) return;
1169
1322
  emitter.emit('error', {
@@ -1172,8 +1325,15 @@ function createCapsule(container) {
1172
1325
  });
1173
1326
  }
1174
1327
  } else {
1175
- renderer = new FallbackRenderer(canvas, merged);
1328
+ renderer = new FallbackRenderer(canvas, merged, {
1329
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale)
1330
+ });
1176
1331
  }
1332
+ var animationTime = 0;
1333
+ var frameGate = createFrameGate(merged.fps);
1334
+ renderOnce = function renderOnce() {
1335
+ return renderer.draw(animationTime);
1336
+ };
1177
1337
  var applySize = function applySize() {
1178
1338
  root.style.width = parseSize(merged.width);
1179
1339
  root.style.height = parseSize(merged.height);
@@ -1191,11 +1351,21 @@ function createCapsule(container) {
1191
1351
  renderer.resize();
1192
1352
  };
1193
1353
  applySize();
1194
- var resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(function () {
1195
- return renderer.resize();
1196
- }) : null;
1197
- if (resizeObserver) resizeObserver.observe(root);else window.addEventListener('resize', renderer.resize);
1198
- var visibility = createVisibilityGuard(root);
1354
+ var resize = function resize() {
1355
+ renderer.resize();
1356
+ if (manuallyPaused || reducedPaused || staticMode) renderOnce();
1357
+ };
1358
+ var resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(resize) : null;
1359
+ if (resizeObserver) resizeObserver.observe(root);else window.addEventListener('resize', resize);
1360
+ var visibility = createVisibilityGuard(root, wakeScheduler);
1361
+ var motionPreference = createReducedMotionPreference(merged.respectReducedMotion, function (matches) {
1362
+ reducedPaused = matches;
1363
+ if (matches) renderOnce();else wakeScheduler();
1364
+ });
1365
+ reducedPaused = motionPreference.matches();
1366
+ var isMotionPaused = function isMotionPaused() {
1367
+ return manuallyPaused || reducedPaused || staticMode || contextLost;
1368
+ };
1199
1369
  root.addEventListener('pointerenter', function () {
1200
1370
  return emitter.emit('pointerenter', {
1201
1371
  preset: _objectSpread2({}, preset)
@@ -1230,12 +1400,12 @@ function createCapsule(container) {
1230
1400
  preset: _objectSpread2({}, preset)
1231
1401
  });
1232
1402
  });
1233
- var animationTime = 0;
1403
+ renderOnce();
1234
1404
  var unsubscribe = subscribeScheduler(function (delta) {
1235
1405
  animationTime += delta;
1236
- if (visibility.isVisible()) renderer.draw(animationTime);
1406
+ if (frameGate.shouldDraw(delta)) renderer.draw(animationTime);
1237
1407
  }, function () {
1238
- return paused;
1408
+ return isMotionPaused() || !visibility.isVisible();
1239
1409
  });
1240
1410
  nextTick(function () {
1241
1411
  if (disposed) return;
@@ -1258,11 +1428,15 @@ function createCapsule(container) {
1258
1428
  var next = getPreset('capsule', ref);
1259
1429
  dirty.preset = true;
1260
1430
  Object.assign(preset, next);
1431
+ if (colorOverride) preset.colors = _toConsumableArray(colorOverride);
1432
+ if (seedOverride !== null) preset.seed = seedOverride;
1433
+ if (speedOverride !== null) preset.speed = speedOverride;
1261
1434
  var nextColors = preset.colors.map(normalizeColor);
1262
1435
  if (nextColors.every(Boolean)) preset.colors = nextColors;
1263
1436
  renderer.setPreset(_objectSpread2({}, preset));
1264
1437
  syncTheme();
1265
1438
  renderer.resize();
1439
+ if (isMotionPaused()) renderOnce();
1266
1440
  emitter.emit('presetchange', {
1267
1441
  preset: _objectSpread2({}, next)
1268
1442
  });
@@ -1274,26 +1448,32 @@ function createCapsule(container) {
1274
1448
  return !color;
1275
1449
  })) return this;
1276
1450
  dirty.colors = true;
1451
+ colorOverride = _toConsumableArray(next);
1277
1452
  preset.colors = next;
1278
1453
  renderer.setPreset(_objectSpread2(_objectSpread2({}, preset), {}, {
1279
1454
  colors: next
1280
1455
  }));
1456
+ if (isMotionPaused()) renderOnce();
1281
1457
  return this;
1282
1458
  },
1283
1459
  setSeed: function setSeed(seed) {
1284
- var value = Number(seed);
1285
- if (!Number.isFinite(value)) return this;
1460
+ var value = toFiniteNumber(seed);
1461
+ if (value === null) return this;
1286
1462
  dirty.seed = true;
1463
+ seedOverride = value;
1287
1464
  preset.seed = value;
1288
1465
  renderer.setPreset(_objectSpread2({}, preset));
1466
+ if (isMotionPaused()) renderOnce();
1289
1467
  return this;
1290
1468
  },
1291
1469
  setSpeed: function setSpeed(speed) {
1292
- var value = Number(speed);
1293
- if (!Number.isFinite(value)) return this;
1470
+ var value = toFiniteNumber(speed);
1471
+ if (value === null) return this;
1294
1472
  dirty.speed = true;
1473
+ speedOverride = value;
1295
1474
  preset.speed = value;
1296
1475
  renderer.setPreset(_objectSpread2({}, preset));
1476
+ if (isMotionPaused()) renderOnce();
1297
1477
  return this;
1298
1478
  },
1299
1479
  setText: function setText(content) {
@@ -1329,6 +1509,7 @@ function createCapsule(container) {
1329
1509
  return this;
1330
1510
  },
1331
1511
  setMouseColor: function setMouseColor(value) {
1512
+ dirty.mouseColor = true;
1332
1513
  merged.mouseColor = value !== false;
1333
1514
  if (typeof renderer.setMouseColor === 'function') renderer.setMouseColor(merged.mouseColor);
1334
1515
  return this;
@@ -1343,6 +1524,7 @@ function createCapsule(container) {
1343
1524
  merged.height = height;
1344
1525
  }
1345
1526
  applySize();
1527
+ if (isMotionPaused()) renderOnce();
1346
1528
  return this;
1347
1529
  },
1348
1530
  randomize: function randomize() {
@@ -1350,23 +1532,59 @@ function createCapsule(container) {
1350
1532
  return this;
1351
1533
  },
1352
1534
  pause: function pause() {
1353
- paused = true;
1535
+ dirty.paused = true;
1536
+ manuallyPaused = true;
1537
+ renderOnce();
1354
1538
  return this;
1355
1539
  },
1356
1540
  resume: function resume() {
1357
- paused = false;
1541
+ dirty.paused = true;
1542
+ manuallyPaused = false;
1543
+ wakeScheduler();
1544
+ return this;
1545
+ },
1546
+ setPaused: function setPaused(value) {
1547
+ return value ? this.pause() : this.resume();
1548
+ },
1549
+ setStatic: function setStatic(value) {
1550
+ dirty.static = true;
1551
+ staticMode = value === true;
1552
+ merged.static = staticMode;
1553
+ if (staticMode) renderOnce();else wakeScheduler();
1554
+ return this;
1555
+ },
1556
+ setFps: function setFps(fps) {
1557
+ dirty.fps = true;
1558
+ merged.fps = frameGate.setFps(fps);
1559
+ wakeScheduler();
1358
1560
  return this;
1359
1561
  },
1360
1562
  setQuality: function setQuality(quality) {
1563
+ dirty.quality = true;
1361
1564
  merged.quality = quality;
1362
- if (typeof renderer.setDprCap === 'function') renderer.setDprCap(dprCapFor(quality));
1565
+ if (typeof renderer.setDprCap === 'function') {
1566
+ renderer.setDprCap(effectiveDprCap(quality, merged.renderScale));
1567
+ }
1568
+ if (isMotionPaused()) renderOnce();
1569
+ return this;
1570
+ },
1571
+ setRenderScale: function setRenderScale(renderScale) {
1572
+ var value = Number(renderScale);
1573
+ if (!Number.isFinite(value)) return this;
1574
+ dirty.renderScale = true;
1575
+ merged.renderScale = Math.min(1, Math.max(0.25, value));
1576
+ if (typeof renderer.setDprCap === 'function') {
1577
+ renderer.setDprCap(effectiveDprCap(merged.quality, merged.renderScale));
1578
+ }
1579
+ if (isMotionPaused()) renderOnce();
1363
1580
  return this;
1364
1581
  },
1365
1582
  dispose: function dispose() {
1366
1583
  disposed = true;
1367
1584
  unsubscribe();
1368
1585
  visibility.dispose();
1369
- if (resizeObserver) resizeObserver.disconnect();else window.removeEventListener('resize', renderer.resize);
1586
+ motionPreference.dispose();
1587
+ if (resizeObserver) resizeObserver.disconnect();else window.removeEventListener('resize', resize);
1370
1588
  renderer.dispose();
1371
1589
  root.remove();
1372
1590
  },
@@ -1376,6 +1594,24 @@ function createCapsule(container) {
1376
1594
  get cssVars() {
1377
1595
  return merged.cssVars;
1378
1596
  },
1597
+ get mouseColor() {
1598
+ return merged.mouseColor;
1599
+ },
1600
+ get quality() {
1601
+ return merged.quality;
1602
+ },
1603
+ get renderScale() {
1604
+ return merged.renderScale;
1605
+ },
1606
+ get paused() {
1607
+ return manuallyPaused;
1608
+ },
1609
+ get static() {
1610
+ return staticMode;
1611
+ },
1612
+ get fps() {
1613
+ return frameGate.getFps();
1614
+ },
1379
1615
  dirty: dirty
1380
1616
  };
1381
1617
  }