@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/color.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
@@ -254,11 +254,17 @@ function dprCapFor(quality) {
254
254
  var tier = QUALITY_TIERS[quality] || QUALITY_TIERS.medium;
255
255
  return tier.dpr;
256
256
  }
257
+ function effectiveDprCap(quality) {
258
+ var renderScale = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
259
+ var scale = Number(renderScale);
260
+ var normalizedScale = Number.isFinite(scale) ? Math.min(1, Math.max(0.25, scale)) : 1;
261
+ return Math.max(0.5, dprCapFor(quality) * normalizedScale);
262
+ }
257
263
 
258
- /**
259
- * Merge defaults < preset < user. Unknown user keys are preserved so the
260
- * component API can grow (new props, cssVars, callbacks) without a breaking
261
- * change.
264
+ /**
265
+ * Merge defaults < preset < user. Unknown user keys are preserved so the
266
+ * component API can grow (new props, cssVars, callbacks) without a breaking
267
+ * change.
262
268
  */
263
269
  function normalizeOptions(defaults, preset, user) {
264
270
  // undefined means "not provided" (e.g. Vue $props with unset props):
@@ -276,9 +282,9 @@ function normalizeOptions(defaults, preset, user) {
276
282
  return _objectSpread2(_objectSpread2(_objectSpread2({}, defaults), presetOptions), userOptions);
277
283
  }
278
284
 
279
- /**
280
- * 公共色板:NC-01~NC-06 的四色组(底色 / 主色 / 辅色 / 高光色)。
281
- * Capsule 预置与 dlc-color 等组件共用,新增色板只改这里。
285
+ /**
286
+ * 公共色板:NC-01~NC-06 的四色组(底色 / 主色 / 辅色 / 高光色)。
287
+ * Capsule 预置与 dlc-color 等组件共用,新增色板只改这里。
282
288
  */
283
289
  var PALETTES = {
284
290
  original: ['#FFF3EA', '#F5B27A', '#F67BC6', '#A978E8'],
@@ -289,9 +295,9 @@ var PALETTES = {
289
295
  plus: ['#FFF0E6', '#F6C26B', '#F98A64', '#E86D74']
290
296
  };
291
297
 
292
- /**
293
- * Capsule 预置:仅星云材质 NC-01~NC-06(NC-07~09 aurora 已移除)。
294
- * 颜色引用公共色板,seed/speed 决定形态与流速。
298
+ /**
299
+ * Capsule 预置:仅星云材质 NC-01~NC-06(NC-07~09 aurora 已移除)。
300
+ * 颜色引用公共色板,seed/speed 决定形态与流速。
295
301
  */
296
302
  var CAPSULE_PRESETS = [{
297
303
  id: 'original',
@@ -407,6 +413,7 @@ var DEFAULTS = {
407
413
  capsule: {
408
414
  quality: 'auto',
409
415
  renderer: 'auto',
416
+ fps: 60,
410
417
  respectReducedMotion: true}};
411
418
 
412
419
  /**
@@ -665,7 +672,7 @@ var CosmicRenderer = /*#__PURE__*/function () {
665
672
  alpha: false,
666
673
  antialias: false,
667
674
  depth: false,
668
- powerPreference: 'high-performance',
675
+ powerPreference: this.options.powerPreference || 'high-performance',
669
676
  preserveDrawingBuffer: false
670
677
  });
671
678
  if (!this.gl) throw new Error('WebGL2 is not available');
@@ -676,16 +683,19 @@ var CosmicRenderer = /*#__PURE__*/function () {
676
683
  this.motion = 0;
677
684
  this.motionTarget = 0;
678
685
  this.timeOffset = preset.seed * 0.73;
686
+ this.colors = preset.colors.map(hexToRgb01);
679
687
  this.visible = true;
680
688
  this.disposed = false;
681
689
  _assertClassBrand(_CosmicRenderer_brand, this, _setupGeometry).call(this);
682
690
  _assertClassBrand(_CosmicRenderer_brand, this, _bindEvents).call(this);
691
+ _assertClassBrand(_CosmicRenderer_brand, this, _bindContextEvents).call(this);
683
692
  this.resize();
684
693
  }
685
694
  return _createClass(CosmicRenderer, [{
686
695
  key: "setPreset",
687
696
  value: function setPreset(preset) {
688
697
  this.preset = _objectSpread2({}, preset);
698
+ this.colors = preset.colors.map(hexToRgb01);
689
699
  this.timeOffset = preset.seed * 0.73;
690
700
  }
691
701
  }, {
@@ -730,15 +740,14 @@ var CosmicRenderer = /*#__PURE__*/function () {
730
740
  if (this.canvas.width !== width || this.canvas.height !== height) {
731
741
  this.canvas.width = width;
732
742
  this.canvas.height = height;
733
- this.gl.viewport(0, 0, width, height);
734
743
  }
744
+ this.gl.viewport(0, 0, width, height);
735
745
  }
736
746
  }, {
737
747
  key: "draw",
738
748
  value: function draw(elapsedSeconds) {
739
749
  var paused = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
740
750
  if (this.disposed || !this.visible) return;
741
- this.resize();
742
751
  var gl = this.gl;
743
752
  this.pointer[0] += (this.pointerTarget[0] - this.pointer[0]) * 0.08;
744
753
  this.pointer[1] += (this.pointerTarget[1] - this.pointer[1]) * 0.08;
@@ -747,16 +756,15 @@ var CosmicRenderer = /*#__PURE__*/function () {
747
756
  gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
748
757
  gl.enableVertexAttribArray(this.locations.position);
749
758
  gl.vertexAttribPointer(this.locations.position, 2, gl.FLOAT, false, 0, 0);
750
- var colors = this.preset.colors.map(hexToRgb01);
751
759
  gl.uniform2f(this.locations.resolution, this.canvas.width, this.canvas.height);
752
760
  gl.uniform1f(this.locations.time, this.timeOffset + (paused ? 0 : elapsedSeconds * this.preset.speed));
753
761
  gl.uniform1f(this.locations.seed, this.preset.seed);
754
762
  gl.uniform1f(this.locations.motion, this.motion);
755
763
  gl.uniform2f(this.locations.pointer, this.pointer[0], this.pointer[1]);
756
- gl.uniform3fv(this.locations.colorA, colors[0]);
757
- gl.uniform3fv(this.locations.colorB, colors[1]);
758
- gl.uniform3fv(this.locations.colorC, colors[2]);
759
- gl.uniform3fv(this.locations.colorD, colors[3]);
764
+ gl.uniform3fv(this.locations.colorA, this.colors[0]);
765
+ gl.uniform3fv(this.locations.colorB, this.colors[1]);
766
+ gl.uniform3fv(this.locations.colorC, this.colors[2]);
767
+ gl.uniform3fv(this.locations.colorD, this.colors[3]);
760
768
  gl.drawArrays(gl.TRIANGLES, 0, 3);
761
769
  }
762
770
  }, {
@@ -767,6 +775,8 @@ var CosmicRenderer = /*#__PURE__*/function () {
767
775
  target.removeEventListener('pointermove', this.onPointerMove);
768
776
  target.removeEventListener('pointerdown', this.onPointerMove);
769
777
  target.removeEventListener('pointerleave', this.onPointerLeave);
778
+ this.canvas.removeEventListener('webglcontextlost', this.onContextLost, false);
779
+ this.canvas.removeEventListener('webglcontextrestored', this.onContextRestored, false);
770
780
  this.gl.deleteBuffer(this.buffer);
771
781
  this.gl.deleteProgram(this.program);
772
782
  var lose = this.gl.getExtension('WEBGL_lose_context');
@@ -823,6 +833,26 @@ function _bindEvents() {
823
833
  passive: true
824
834
  });
825
835
  }
836
+ function _bindContextEvents() {
837
+ var _this3 = this;
838
+ this.onContextLost = function (event) {
839
+ event.preventDefault();
840
+ if (_this3.disposed) return;
841
+ _this3.visible = false;
842
+ if (typeof _this3.options.onContextLost === 'function') _this3.options.onContextLost(event);
843
+ };
844
+ this.onContextRestored = function () {
845
+ if (_this3.disposed) return;
846
+ _this3.program = createProgram(_this3.gl);
847
+ _this3.locations = _assertClassBrand(_CosmicRenderer_brand, _this3, _getLocations).call(_this3);
848
+ _assertClassBrand(_CosmicRenderer_brand, _this3, _setupGeometry).call(_this3);
849
+ _this3.visible = true;
850
+ _this3.resize();
851
+ if (typeof _this3.options.onContextRestored === 'function') _this3.options.onContextRestored();
852
+ };
853
+ this.canvas.addEventListener('webglcontextlost', this.onContextLost, false);
854
+ this.canvas.addEventListener('webglcontextrestored', this.onContextRestored, false);
855
+ }
826
856
 
827
857
  function rgb(color) {
828
858
  var alpha = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
@@ -837,18 +867,20 @@ function rgb(color) {
837
867
  }
838
868
  var FallbackRenderer = /*#__PURE__*/function () {
839
869
  function FallbackRenderer(canvas, preset) {
870
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
840
871
  _classCallCheck(this, FallbackRenderer);
841
872
  this.canvas = canvas;
842
873
  this.preset = preset;
843
874
  this.context = canvas.getContext('2d');
844
875
  this.visible = true;
845
876
  this.timePhase = 0;
877
+ this.dprCap = options.dprCap || 1.5;
846
878
  }
847
879
  return _createClass(FallbackRenderer, [{
848
880
  key: "resize",
849
881
  value: function resize() {
850
882
  var rect = this.canvas.getBoundingClientRect();
851
- var dpr = Math.min(window.devicePixelRatio || 1, 1.5);
883
+ var dpr = Math.min(window.devicePixelRatio || 1, this.dprCap);
852
884
  var width = Math.max(2, Math.round(rect.width * dpr));
853
885
  var height = Math.max(2, Math.round(rect.height * dpr));
854
886
  if (this.canvas.width !== width || this.canvas.height !== height) {
@@ -889,7 +921,6 @@ var FallbackRenderer = /*#__PURE__*/function () {
889
921
  key: "draw",
890
922
  value: function draw(time) {
891
923
  if (!this.visible) return;
892
- this.resize();
893
924
  this.drawNebula(time);
894
925
  }
895
926
  }, {
@@ -897,6 +928,12 @@ var FallbackRenderer = /*#__PURE__*/function () {
897
928
  value: function setPreset(preset) {
898
929
  this.preset = preset;
899
930
  }
931
+ }, {
932
+ key: "setDprCap",
933
+ value: function setDprCap(cap) {
934
+ this.dprCap = cap;
935
+ this.resize();
936
+ }
900
937
  }, {
901
938
  key: "randomize",
902
939
  value: function randomize() {
@@ -914,10 +951,10 @@ var FallbackRenderer = /*#__PURE__*/function () {
914
951
  }]);
915
952
  }();
916
953
 
917
- /**
918
- * Document-level shared rAF scheduler. Every component instance subscribes
919
- * its own frame callback; the whole page runs ONE animation loop (like the
920
- * original demo), which avoids jank from many competing rAF loops.
954
+ /**
955
+ * Document-level shared rAF scheduler. Every component instance subscribes
956
+ * its own frame callback; the whole page runs ONE animation loop (like the
957
+ * original demo), which avoids jank from many competing rAF loops.
921
958
  */
922
959
  var subscribers = [];
923
960
  var running = false;
@@ -925,11 +962,7 @@ var rafId = 0;
925
962
  var last = 0;
926
963
  function tick(now) {
927
964
  if (!running) return;
928
- var delta = last ? Math.min((now - last) / 1000, 0.05) : 0;
929
- last = now;
930
- // Schedule the next frame BEFORE running callbacks so one throwing
931
- // subscriber can never kill the whole animation loop.
932
- rafId = requestAnimationFrame(tick);
965
+ var activeItems = [];
933
966
  {
934
967
  var _iterator = _createForOfIteratorHelper(subscribers.slice()),
935
968
  _step;
@@ -937,10 +970,8 @@ function tick(now) {
937
970
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
938
971
  var item = _step.value;
939
972
  try {
940
- if (!item.isPaused()) item.onFrame(delta, now);
941
- } catch (error) {
942
- console.warn('[dlc-ui] frame error:', error);
943
- }
973
+ if (!item.isPaused()) activeItems.push(item);
974
+ } catch (_unused) {}
944
975
  }
945
976
  } catch (err) {
946
977
  _iterator.e(err);
@@ -948,6 +979,25 @@ function tick(now) {
948
979
  _iterator.f();
949
980
  }
950
981
  }
982
+ if (activeItems.length === 0) {
983
+ running = false;
984
+ rafId = 0;
985
+ last = 0;
986
+ return;
987
+ }
988
+ var delta = last ? Math.min((now - last) / 1000, 0.05) : 0;
989
+ last = now;
990
+ // Schedule the next frame BEFORE running callbacks so one throwing
991
+ // subscriber can never kill the whole animation loop.
992
+ rafId = requestAnimationFrame(tick);
993
+ for (var _i = 0, _activeItems = activeItems; _i < _activeItems.length; _i++) {
994
+ var _item = _activeItems[_i];
995
+ try {
996
+ _item.onFrame(delta, now);
997
+ } catch (error) {
998
+ console.warn('[dlc-ui] frame error:', error);
999
+ }
1000
+ }
951
1001
  if (subscribers.length === 0) {
952
1002
  cancelAnimationFrame(rafId);
953
1003
  running = false;
@@ -960,6 +1010,9 @@ function start() {
960
1010
  last = 0;
961
1011
  rafId = requestAnimationFrame(tick);
962
1012
  }
1013
+ function wakeScheduler() {
1014
+ start();
1015
+ }
963
1016
  function subscribeScheduler(onFrame, isPaused) {
964
1017
  var item = {
965
1018
  onFrame: onFrame,
@@ -978,11 +1031,12 @@ function subscribeScheduler(onFrame, isPaused) {
978
1031
  };
979
1032
  }
980
1033
 
981
- /**
982
- * Gates drawing on "element intersects viewport AND the page tab is visible".
983
- * Falls back to always-visible when IntersectionObserver is unavailable.
1034
+ /**
1035
+ * Gates drawing on "element intersects viewport AND the page tab is visible".
1036
+ * Falls back to always-visible when IntersectionObserver is unavailable.
984
1037
  */
985
1038
  function createVisibilityGuard(element) {
1039
+ var onChange = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
986
1040
  var intersecting = true;
987
1041
  var pageVisible = typeof document === 'undefined' || !document.hidden;
988
1042
  var disposed = false;
@@ -992,6 +1046,7 @@ function createVisibilityGuard(element) {
992
1046
  intersecting = entries.some(function (entry) {
993
1047
  return entry.isIntersecting;
994
1048
  });
1049
+ if (typeof onChange === 'function') onChange();
995
1050
  }, {
996
1051
  rootMargin: '180px'
997
1052
  });
@@ -999,6 +1054,7 @@ function createVisibilityGuard(element) {
999
1054
  }
1000
1055
  var onVisibilityChange = function onVisibilityChange() {
1001
1056
  pageVisible = typeof document !== 'undefined' && !document.hidden;
1057
+ if (typeof onChange === 'function') onChange();
1002
1058
  };
1003
1059
  if (typeof document !== 'undefined') {
1004
1060
  document.addEventListener('visibilitychange', onVisibilityChange);
@@ -1034,22 +1090,100 @@ function nextTick(fn) {
1034
1090
  }
1035
1091
  }
1036
1092
 
1037
- function prefersReducedMotion() {
1038
- return typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches;
1093
+ function normalizeFps(value) {
1094
+ var fallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 60;
1095
+ var fps = Number(value);
1096
+ if (!Number.isFinite(fps)) return fallback;
1097
+ return Math.min(60, Math.max(1, fps));
1098
+ }
1099
+ function createFrameGate() {
1100
+ var initialFps = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 60;
1101
+ var fps = normalizeFps(initialFps);
1102
+ var elapsed = 0;
1103
+ return {
1104
+ shouldDraw: function shouldDraw(delta) {
1105
+ elapsed += delta;
1106
+ var interval = 1 / fps;
1107
+ if (elapsed + 0.0001 < interval) return false;
1108
+ elapsed %= interval;
1109
+ return true;
1110
+ },
1111
+ setFps: function setFps(value) {
1112
+ fps = normalizeFps(value, fps);
1113
+ elapsed = 0;
1114
+ return fps;
1115
+ },
1116
+ getFps: function getFps() {
1117
+ return fps;
1118
+ }
1119
+ };
1120
+ }
1121
+ function createReducedMotionPreference(enabled, onChange) {
1122
+ var query = enabled && typeof matchMedia !== 'undefined' ? matchMedia('(prefers-reduced-motion: reduce)') : null;
1123
+ var notify = function notify() {
1124
+ if (typeof onChange === 'function') onChange(Boolean(query && query.matches));
1125
+ };
1126
+ if (query) {
1127
+ if (typeof query.addEventListener === 'function') query.addEventListener('change', notify);else if (typeof query.addListener === 'function') query.addListener(notify);
1128
+ }
1129
+ return {
1130
+ matches: function matches() {
1131
+ return Boolean(query && query.matches);
1132
+ },
1133
+ dispose: function dispose() {
1134
+ if (!query) return;
1135
+ if (typeof query.removeEventListener === 'function') query.removeEventListener('change', notify);else if (typeof query.removeListener === 'function') query.removeListener(notify);
1136
+ }
1137
+ };
1138
+ }
1139
+
1140
+ function toFiniteNumber(value) {
1141
+ if (value == null || typeof value === 'boolean') return null;
1142
+ if (typeof value === 'string' && value.trim() === '') return null;
1143
+ var number = Number(value);
1144
+ return Number.isFinite(number) ? number : null;
1039
1145
  }
1040
1146
 
1041
- /**
1042
- * Mount the cosmic (nebula) material as a background layer inside `host`.
1043
- *
1044
- * The layer is absolutely positioned and defaults to `z-index: -1`, so it
1045
- * paints behind the host's in-flow content: text/buttons/images inside the
1046
- * host sit on top with zero extra CSS. Host gets `position: relative;
1047
- * isolation: isolate` automatically so the layer can never escape its box
1048
- * (or break stacking outside it).
1049
- *
1050
- * Options: preset (id/code/name or object), colors, seed, speed, quality,
1051
- * renderer, mouseColor, respectReducedMotion, opacity (0-1), fallbackColor
1052
- * (true=preset base color, a hex string, or false to disable).
1147
+ var HOST_STYLES = new WeakMap();
1148
+ function acquireHostStyles(host) {
1149
+ var active = HOST_STYLES.get(host);
1150
+ if (active) {
1151
+ active.count += 1;
1152
+ return;
1153
+ }
1154
+ var state = {
1155
+ count: 1,
1156
+ position: host.style.position,
1157
+ isolation: host.style.isolation
1158
+ };
1159
+ var computed = getComputedStyle(host);
1160
+ if (computed.position === 'static' || computed.position === '') host.style.position = 'relative';
1161
+ host.style.isolation = 'isolate';
1162
+ HOST_STYLES.set(host, state);
1163
+ }
1164
+ function releaseHostStyles(host) {
1165
+ var state = HOST_STYLES.get(host);
1166
+ if (!state) return;
1167
+ state.count -= 1;
1168
+ if (state.count > 0) return;
1169
+ host.style.position = state.position;
1170
+ host.style.isolation = state.isolation;
1171
+ HOST_STYLES.delete(host);
1172
+ }
1173
+
1174
+ /**
1175
+ * Mount the cosmic (nebula) material as a background layer inside `host`.
1176
+ *
1177
+ * The layer is absolutely positioned and defaults to `z-index: -1`, so it
1178
+ * paints behind the host's in-flow content: text/buttons/images inside the
1179
+ * host sit on top with zero extra CSS. Host gets `position: relative;
1180
+ * isolation: isolate` automatically so the layer can never escape its box
1181
+ * (or break stacking outside it).
1182
+ *
1183
+ * Options: preset (literary name), colors, seed, speed, quality,
1184
+ * renderer, renderScale, powerPreference, fps, paused/static, mouseColor,
1185
+ * respectReducedMotion, opacity (0-1), fallbackColor (true=preset base
1186
+ * color, a hex string, or false to disable).
1053
1187
  */
1054
1188
  function createColorBackground(host) {
1055
1189
  var _options$preset;
@@ -1063,18 +1197,29 @@ function createColorBackground(host) {
1063
1197
  renderer: DEFAULTS.capsule.renderer,
1064
1198
  respectReducedMotion: DEFAULTS.capsule.respectReducedMotion,
1065
1199
  mouseColor: true,
1200
+ renderScale: 1,
1201
+ powerPreference: 'high-performance',
1202
+ fps: DEFAULTS.capsule.fps,
1203
+ paused: false,
1204
+ static: false,
1066
1205
  opacity: 1,
1067
1206
  fallbackColor: true
1068
1207
  }, preset, options);
1069
- var normalizedColors = merged.colors.map(normalizeColor);
1070
- if (normalizedColors.every(Boolean)) preset.colors = normalizedColors;
1071
- preset.seed = merged.seed;
1072
- preset.speed = merged.speed;
1073
- var computed = getComputedStyle(host);
1074
- if (computed.position === 'static' || computed.position === '') {
1075
- host.style.position = 'relative';
1076
- }
1077
- host.style.isolation = 'isolate';
1208
+ merged.opacity = Number.isFinite(Number(merged.opacity)) ? Math.min(1, Math.max(0, Number(merged.opacity))) : 1;
1209
+ var normalizedColors = Array.isArray(merged.colors) ? merged.colors.map(normalizeColor) : [];
1210
+ if (normalizedColors.length === 4 && normalizedColors.every(Boolean)) preset.colors = normalizedColors;
1211
+ var initialSeed = toFiniteNumber(merged.seed);
1212
+ var initialSpeed = toFiniteNumber(merged.speed);
1213
+ if (initialSeed !== null) preset.seed = initialSeed;
1214
+ if (initialSpeed !== null) preset.speed = initialSpeed;
1215
+ merged.colors = _toConsumableArray(preset.colors);
1216
+ merged.seed = preset.seed;
1217
+ merged.speed = preset.speed;
1218
+ var optionColors = Array.isArray(options.colors) ? options.colors.map(normalizeColor) : [];
1219
+ var colorOverride = optionColors.length === 4 && optionColors.every(Boolean) ? _toConsumableArray(optionColors) : null;
1220
+ var seedOverride = options.seed !== undefined ? toFiniteNumber(options.seed) : null;
1221
+ var speedOverride = options.speed !== undefined ? toFiniteNumber(options.speed) : null;
1222
+ acquireHostStyles(host);
1078
1223
  var layer = document.createElement('div');
1079
1224
  layer.className = 'dlc-color-layer';
1080
1225
  var syncLayerVars = function syncLayerVars() {
@@ -1089,8 +1234,12 @@ function createColorBackground(host) {
1089
1234
  layer.appendChild(canvas);
1090
1235
  host.appendChild(layer);
1091
1236
  var emitter = createEmitter();
1092
- var paused = merged.respectReducedMotion && prefersReducedMotion();
1237
+ var manuallyPaused = merged.paused === true;
1238
+ var reducedPaused = false;
1239
+ var staticMode = merged.static === true;
1240
+ var contextLost = false;
1093
1241
  var disposed = false;
1242
+ var renderOnce = function renderOnce() {};
1094
1243
  var dirty = {
1095
1244
  preset: false,
1096
1245
  seed: false,
@@ -1098,19 +1247,37 @@ function createColorBackground(host) {
1098
1247
  colors: false,
1099
1248
  opacity: false,
1100
1249
  fallbackColor: false,
1101
- mouseColor: false
1250
+ mouseColor: false,
1251
+ quality: false,
1252
+ renderScale: false,
1253
+ paused: false,
1254
+ static: false,
1255
+ fps: false
1102
1256
  };
1103
1257
  var renderer;
1104
1258
  var useWebgl = merged.renderer !== 'canvas2d';
1105
1259
  if (useWebgl) {
1106
1260
  try {
1107
1261
  renderer = new CosmicRenderer(canvas, merged, {
1108
- dprCap: dprCapFor(merged.quality),
1262
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
1109
1263
  mouseColor: merged.mouseColor !== false,
1110
- eventTarget: host
1264
+ eventTarget: host,
1265
+ powerPreference: merged.powerPreference,
1266
+ onContextLost: function onContextLost() {
1267
+ contextLost = true;
1268
+ emitter.emit('contextlost', {});
1269
+ },
1270
+ onContextRestored: function onContextRestored() {
1271
+ contextLost = false;
1272
+ emitter.emit('contextrestored', {});
1273
+ renderOnce();
1274
+ wakeScheduler();
1275
+ }
1111
1276
  });
1112
1277
  } catch (error) {
1113
- renderer = new FallbackRenderer(canvas, merged);
1278
+ renderer = new FallbackRenderer(canvas, merged, {
1279
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale)
1280
+ });
1114
1281
  nextTick(function () {
1115
1282
  if (disposed) return;
1116
1283
  emitter.emit('error', {
@@ -1119,19 +1286,37 @@ function createColorBackground(host) {
1119
1286
  });
1120
1287
  }
1121
1288
  } else {
1122
- renderer = new FallbackRenderer(canvas, merged);
1289
+ renderer = new FallbackRenderer(canvas, merged, {
1290
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale)
1291
+ });
1123
1292
  }
1124
- var resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(function () {
1125
- return renderer.resize();
1126
- }) : null;
1127
- if (resizeObserver) resizeObserver.observe(host);else window.addEventListener('resize', renderer.resize);
1128
- var visibility = createVisibilityGuard(layer);
1293
+ renderer.resize();
1129
1294
  var animationTime = 0;
1295
+ var frameGate = createFrameGate(merged.fps);
1296
+ renderOnce = function renderOnce() {
1297
+ return renderer.draw(animationTime);
1298
+ };
1299
+ var resize = function resize() {
1300
+ renderer.resize();
1301
+ if (manuallyPaused || reducedPaused || staticMode) renderOnce();
1302
+ };
1303
+ var resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(resize) : null;
1304
+ if (resizeObserver) resizeObserver.observe(host);else window.addEventListener('resize', resize);
1305
+ var visibility = createVisibilityGuard(layer, wakeScheduler);
1306
+ var motionPreference = createReducedMotionPreference(merged.respectReducedMotion, function (matches) {
1307
+ reducedPaused = matches;
1308
+ if (matches) renderOnce();else wakeScheduler();
1309
+ });
1310
+ reducedPaused = motionPreference.matches();
1311
+ var isMotionPaused = function isMotionPaused() {
1312
+ return manuallyPaused || reducedPaused || staticMode || contextLost;
1313
+ };
1314
+ renderOnce();
1130
1315
  var unsubscribe = subscribeScheduler(function (delta) {
1131
1316
  animationTime += delta;
1132
- if (visibility.isVisible()) renderer.draw(animationTime);
1317
+ if (frameGate.shouldDraw(delta)) renderer.draw(animationTime);
1133
1318
  }, function () {
1134
- return paused;
1319
+ return isMotionPaused() || !visibility.isVisible();
1135
1320
  });
1136
1321
  nextTick(function () {
1137
1322
  if (disposed) return;
@@ -1150,11 +1335,15 @@ function createColorBackground(host) {
1150
1335
  var next = getPreset('capsule', ref);
1151
1336
  dirty.preset = true;
1152
1337
  Object.assign(preset, next);
1338
+ if (colorOverride) preset.colors = _toConsumableArray(colorOverride);
1339
+ if (seedOverride !== null) preset.seed = seedOverride;
1340
+ if (speedOverride !== null) preset.speed = speedOverride;
1153
1341
  var nextColors = preset.colors.map(normalizeColor);
1154
1342
  if (nextColors.every(Boolean)) preset.colors = nextColors;
1155
1343
  renderer.setPreset(_objectSpread2({}, preset));
1156
1344
  renderer.resize();
1157
1345
  syncLayerVars();
1346
+ if (isMotionPaused()) renderOnce();
1158
1347
  emitter.emit('presetchange', {
1159
1348
  preset: _objectSpread2({}, preset)
1160
1349
  });
@@ -1166,27 +1355,33 @@ function createColorBackground(host) {
1166
1355
  return !color;
1167
1356
  })) return this;
1168
1357
  dirty.colors = true;
1358
+ colorOverride = _toConsumableArray(next);
1169
1359
  preset.colors = next;
1170
1360
  renderer.setPreset(_objectSpread2(_objectSpread2({}, preset), {}, {
1171
1361
  colors: next
1172
1362
  }));
1173
1363
  syncLayerVars();
1364
+ if (isMotionPaused()) renderOnce();
1174
1365
  return this;
1175
1366
  },
1176
1367
  setSeed: function setSeed(seed) {
1177
- var value = Number(seed);
1178
- if (!Number.isFinite(value)) return this;
1368
+ var value = toFiniteNumber(seed);
1369
+ if (value === null) return this;
1179
1370
  dirty.seed = true;
1371
+ seedOverride = value;
1180
1372
  preset.seed = value;
1181
1373
  renderer.setPreset(_objectSpread2({}, preset));
1374
+ if (isMotionPaused()) renderOnce();
1182
1375
  return this;
1183
1376
  },
1184
1377
  setSpeed: function setSpeed(speed) {
1185
- var value = Number(speed);
1186
- if (!Number.isFinite(value)) return this;
1378
+ var value = toFiniteNumber(speed);
1379
+ if (value === null) return this;
1187
1380
  dirty.speed = true;
1381
+ speedOverride = value;
1188
1382
  preset.speed = value;
1189
1383
  renderer.setPreset(_objectSpread2({}, preset));
1384
+ if (isMotionPaused()) renderOnce();
1190
1385
  return this;
1191
1386
  },
1192
1387
  setFallbackColor: function setFallbackColor(value) {
@@ -1202,8 +1397,23 @@ function createColorBackground(host) {
1202
1397
  return this;
1203
1398
  },
1204
1399
  setQuality: function setQuality(quality) {
1400
+ dirty.quality = true;
1205
1401
  merged.quality = quality;
1206
- if (typeof renderer.setDprCap === 'function') renderer.setDprCap(dprCapFor(quality));
1402
+ if (typeof renderer.setDprCap === 'function') {
1403
+ renderer.setDprCap(effectiveDprCap(quality, merged.renderScale));
1404
+ }
1405
+ if (isMotionPaused()) renderOnce();
1406
+ return this;
1407
+ },
1408
+ setRenderScale: function setRenderScale(renderScale) {
1409
+ var value = Number(renderScale);
1410
+ if (!Number.isFinite(value)) return this;
1411
+ dirty.renderScale = true;
1412
+ merged.renderScale = Math.min(1, Math.max(0.25, value));
1413
+ if (typeof renderer.setDprCap === 'function') {
1414
+ renderer.setDprCap(effectiveDprCap(merged.quality, merged.renderScale));
1415
+ }
1416
+ if (isMotionPaused()) renderOnce();
1207
1417
  return this;
1208
1418
  },
1209
1419
  setOpacity: function setOpacity(value) {
@@ -1219,20 +1429,42 @@ function createColorBackground(host) {
1219
1429
  return this;
1220
1430
  },
1221
1431
  pause: function pause() {
1222
- paused = true;
1432
+ dirty.paused = true;
1433
+ manuallyPaused = true;
1434
+ renderOnce();
1223
1435
  return this;
1224
1436
  },
1225
1437
  resume: function resume() {
1226
- paused = false;
1438
+ dirty.paused = true;
1439
+ manuallyPaused = false;
1440
+ wakeScheduler();
1441
+ return this;
1442
+ },
1443
+ setPaused: function setPaused(value) {
1444
+ return value ? this.pause() : this.resume();
1445
+ },
1446
+ setStatic: function setStatic(value) {
1447
+ dirty.static = true;
1448
+ staticMode = value === true;
1449
+ merged.static = staticMode;
1450
+ if (staticMode) renderOnce();else wakeScheduler();
1451
+ return this;
1452
+ },
1453
+ setFps: function setFps(fps) {
1454
+ dirty.fps = true;
1455
+ merged.fps = frameGate.setFps(fps);
1456
+ wakeScheduler();
1227
1457
  return this;
1228
1458
  },
1229
1459
  dispose: function dispose() {
1230
1460
  disposed = true;
1231
1461
  unsubscribe();
1232
1462
  visibility.dispose();
1233
- if (resizeObserver) resizeObserver.disconnect();else window.removeEventListener('resize', renderer.resize);
1463
+ motionPreference.dispose();
1464
+ if (resizeObserver) resizeObserver.disconnect();else window.removeEventListener('resize', resize);
1234
1465
  renderer.dispose();
1235
1466
  layer.remove();
1467
+ releaseHostStyles(host);
1236
1468
  },
1237
1469
  get opacity() {
1238
1470
  return merged.opacity;
@@ -1243,6 +1475,21 @@ function createColorBackground(host) {
1243
1475
  get mouseColor() {
1244
1476
  return merged.mouseColor;
1245
1477
  },
1478
+ get quality() {
1479
+ return merged.quality;
1480
+ },
1481
+ get renderScale() {
1482
+ return merged.renderScale;
1483
+ },
1484
+ get paused() {
1485
+ return manuallyPaused;
1486
+ },
1487
+ get static() {
1488
+ return staticMode;
1489
+ },
1490
+ get fps() {
1491
+ return frameGate.getFps();
1492
+ },
1246
1493
  dirty: dirty
1247
1494
  };
1248
1495
  }