@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/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',
@@ -379,6 +385,15 @@ var PROGRESS_PRESETS = [{
379
385
  initialProgress: 30,
380
386
  edgeStyle: 'tide',
381
387
  colors: ['#0A2239', '#2E9BFF', '#7FE3FF', '#EAF9FF']
388
+ }, {
389
+ id: 'botanic-lab',
390
+ code: 'NC-14',
391
+ name: '森息',
392
+ subtitle: 'MOSS CULTURE / ROOTING',
393
+ group: 'progress',
394
+ initialProgress: 46,
395
+ edgeStyle: 'flow',
396
+ colors: ['#102A23', '#287A58', '#9BD65B', '#F2FFC4']
382
397
  }];
383
398
 
384
399
  [].concat(_toConsumableArray(CAPSULE_PRESETS), _toConsumableArray(PROGRESS_PRESETS));
@@ -407,6 +422,7 @@ var DEFAULTS = {
407
422
  capsule: {
408
423
  quality: 'auto',
409
424
  renderer: 'auto',
425
+ fps: 60,
410
426
  respectReducedMotion: true}};
411
427
 
412
428
  /**
@@ -665,7 +681,7 @@ var CosmicRenderer = /*#__PURE__*/function () {
665
681
  alpha: false,
666
682
  antialias: false,
667
683
  depth: false,
668
- powerPreference: 'high-performance',
684
+ powerPreference: this.options.powerPreference || 'high-performance',
669
685
  preserveDrawingBuffer: false
670
686
  });
671
687
  if (!this.gl) throw new Error('WebGL2 is not available');
@@ -676,16 +692,19 @@ var CosmicRenderer = /*#__PURE__*/function () {
676
692
  this.motion = 0;
677
693
  this.motionTarget = 0;
678
694
  this.timeOffset = preset.seed * 0.73;
695
+ this.colors = preset.colors.map(hexToRgb01);
679
696
  this.visible = true;
680
697
  this.disposed = false;
681
698
  _assertClassBrand(_CosmicRenderer_brand, this, _setupGeometry).call(this);
682
699
  _assertClassBrand(_CosmicRenderer_brand, this, _bindEvents).call(this);
700
+ _assertClassBrand(_CosmicRenderer_brand, this, _bindContextEvents).call(this);
683
701
  this.resize();
684
702
  }
685
703
  return _createClass(CosmicRenderer, [{
686
704
  key: "setPreset",
687
705
  value: function setPreset(preset) {
688
706
  this.preset = _objectSpread2({}, preset);
707
+ this.colors = preset.colors.map(hexToRgb01);
689
708
  this.timeOffset = preset.seed * 0.73;
690
709
  }
691
710
  }, {
@@ -730,15 +749,14 @@ var CosmicRenderer = /*#__PURE__*/function () {
730
749
  if (this.canvas.width !== width || this.canvas.height !== height) {
731
750
  this.canvas.width = width;
732
751
  this.canvas.height = height;
733
- this.gl.viewport(0, 0, width, height);
734
752
  }
753
+ this.gl.viewport(0, 0, width, height);
735
754
  }
736
755
  }, {
737
756
  key: "draw",
738
757
  value: function draw(elapsedSeconds) {
739
758
  var paused = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
740
759
  if (this.disposed || !this.visible) return;
741
- this.resize();
742
760
  var gl = this.gl;
743
761
  this.pointer[0] += (this.pointerTarget[0] - this.pointer[0]) * 0.08;
744
762
  this.pointer[1] += (this.pointerTarget[1] - this.pointer[1]) * 0.08;
@@ -747,16 +765,15 @@ var CosmicRenderer = /*#__PURE__*/function () {
747
765
  gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
748
766
  gl.enableVertexAttribArray(this.locations.position);
749
767
  gl.vertexAttribPointer(this.locations.position, 2, gl.FLOAT, false, 0, 0);
750
- var colors = this.preset.colors.map(hexToRgb01);
751
768
  gl.uniform2f(this.locations.resolution, this.canvas.width, this.canvas.height);
752
769
  gl.uniform1f(this.locations.time, this.timeOffset + (paused ? 0 : elapsedSeconds * this.preset.speed));
753
770
  gl.uniform1f(this.locations.seed, this.preset.seed);
754
771
  gl.uniform1f(this.locations.motion, this.motion);
755
772
  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]);
773
+ gl.uniform3fv(this.locations.colorA, this.colors[0]);
774
+ gl.uniform3fv(this.locations.colorB, this.colors[1]);
775
+ gl.uniform3fv(this.locations.colorC, this.colors[2]);
776
+ gl.uniform3fv(this.locations.colorD, this.colors[3]);
760
777
  gl.drawArrays(gl.TRIANGLES, 0, 3);
761
778
  }
762
779
  }, {
@@ -767,6 +784,8 @@ var CosmicRenderer = /*#__PURE__*/function () {
767
784
  target.removeEventListener('pointermove', this.onPointerMove);
768
785
  target.removeEventListener('pointerdown', this.onPointerMove);
769
786
  target.removeEventListener('pointerleave', this.onPointerLeave);
787
+ this.canvas.removeEventListener('webglcontextlost', this.onContextLost, false);
788
+ this.canvas.removeEventListener('webglcontextrestored', this.onContextRestored, false);
770
789
  this.gl.deleteBuffer(this.buffer);
771
790
  this.gl.deleteProgram(this.program);
772
791
  var lose = this.gl.getExtension('WEBGL_lose_context');
@@ -823,6 +842,26 @@ function _bindEvents() {
823
842
  passive: true
824
843
  });
825
844
  }
845
+ function _bindContextEvents() {
846
+ var _this3 = this;
847
+ this.onContextLost = function (event) {
848
+ event.preventDefault();
849
+ if (_this3.disposed) return;
850
+ _this3.visible = false;
851
+ if (typeof _this3.options.onContextLost === 'function') _this3.options.onContextLost(event);
852
+ };
853
+ this.onContextRestored = function () {
854
+ if (_this3.disposed) return;
855
+ _this3.program = createProgram(_this3.gl);
856
+ _this3.locations = _assertClassBrand(_CosmicRenderer_brand, _this3, _getLocations).call(_this3);
857
+ _assertClassBrand(_CosmicRenderer_brand, _this3, _setupGeometry).call(_this3);
858
+ _this3.visible = true;
859
+ _this3.resize();
860
+ if (typeof _this3.options.onContextRestored === 'function') _this3.options.onContextRestored();
861
+ };
862
+ this.canvas.addEventListener('webglcontextlost', this.onContextLost, false);
863
+ this.canvas.addEventListener('webglcontextrestored', this.onContextRestored, false);
864
+ }
826
865
 
827
866
  function rgb(color) {
828
867
  var alpha = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
@@ -837,18 +876,20 @@ function rgb(color) {
837
876
  }
838
877
  var FallbackRenderer = /*#__PURE__*/function () {
839
878
  function FallbackRenderer(canvas, preset) {
879
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
840
880
  _classCallCheck(this, FallbackRenderer);
841
881
  this.canvas = canvas;
842
882
  this.preset = preset;
843
883
  this.context = canvas.getContext('2d');
844
884
  this.visible = true;
845
885
  this.timePhase = 0;
886
+ this.dprCap = options.dprCap || 1.5;
846
887
  }
847
888
  return _createClass(FallbackRenderer, [{
848
889
  key: "resize",
849
890
  value: function resize() {
850
891
  var rect = this.canvas.getBoundingClientRect();
851
- var dpr = Math.min(window.devicePixelRatio || 1, 1.5);
892
+ var dpr = Math.min(window.devicePixelRatio || 1, this.dprCap);
852
893
  var width = Math.max(2, Math.round(rect.width * dpr));
853
894
  var height = Math.max(2, Math.round(rect.height * dpr));
854
895
  if (this.canvas.width !== width || this.canvas.height !== height) {
@@ -889,7 +930,6 @@ var FallbackRenderer = /*#__PURE__*/function () {
889
930
  key: "draw",
890
931
  value: function draw(time) {
891
932
  if (!this.visible) return;
892
- this.resize();
893
933
  this.drawNebula(time);
894
934
  }
895
935
  }, {
@@ -897,6 +937,12 @@ var FallbackRenderer = /*#__PURE__*/function () {
897
937
  value: function setPreset(preset) {
898
938
  this.preset = preset;
899
939
  }
940
+ }, {
941
+ key: "setDprCap",
942
+ value: function setDprCap(cap) {
943
+ this.dprCap = cap;
944
+ this.resize();
945
+ }
900
946
  }, {
901
947
  key: "randomize",
902
948
  value: function randomize() {
@@ -914,10 +960,10 @@ var FallbackRenderer = /*#__PURE__*/function () {
914
960
  }]);
915
961
  }();
916
962
 
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.
963
+ /**
964
+ * Document-level shared rAF scheduler. Every component instance subscribes
965
+ * its own frame callback; the whole page runs ONE animation loop (like the
966
+ * original demo), which avoids jank from many competing rAF loops.
921
967
  */
922
968
  var subscribers = [];
923
969
  var running = false;
@@ -925,11 +971,7 @@ var rafId = 0;
925
971
  var last = 0;
926
972
  function tick(now) {
927
973
  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);
974
+ var activeItems = [];
933
975
  {
934
976
  var _iterator = _createForOfIteratorHelper(subscribers.slice()),
935
977
  _step;
@@ -937,10 +979,8 @@ function tick(now) {
937
979
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
938
980
  var item = _step.value;
939
981
  try {
940
- if (!item.isPaused()) item.onFrame(delta, now);
941
- } catch (error) {
942
- console.warn('[dlc-ui] frame error:', error);
943
- }
982
+ if (!item.isPaused()) activeItems.push(item);
983
+ } catch (_unused) {}
944
984
  }
945
985
  } catch (err) {
946
986
  _iterator.e(err);
@@ -948,6 +988,25 @@ function tick(now) {
948
988
  _iterator.f();
949
989
  }
950
990
  }
991
+ if (activeItems.length === 0) {
992
+ running = false;
993
+ rafId = 0;
994
+ last = 0;
995
+ return;
996
+ }
997
+ var delta = last ? Math.min((now - last) / 1000, 0.05) : 0;
998
+ last = now;
999
+ // Schedule the next frame BEFORE running callbacks so one throwing
1000
+ // subscriber can never kill the whole animation loop.
1001
+ rafId = requestAnimationFrame(tick);
1002
+ for (var _i = 0, _activeItems = activeItems; _i < _activeItems.length; _i++) {
1003
+ var _item = _activeItems[_i];
1004
+ try {
1005
+ _item.onFrame(delta, now);
1006
+ } catch (error) {
1007
+ console.warn('[dlc-ui] frame error:', error);
1008
+ }
1009
+ }
951
1010
  if (subscribers.length === 0) {
952
1011
  cancelAnimationFrame(rafId);
953
1012
  running = false;
@@ -960,6 +1019,9 @@ function start() {
960
1019
  last = 0;
961
1020
  rafId = requestAnimationFrame(tick);
962
1021
  }
1022
+ function wakeScheduler() {
1023
+ start();
1024
+ }
963
1025
  function subscribeScheduler(onFrame, isPaused) {
964
1026
  var item = {
965
1027
  onFrame: onFrame,
@@ -978,11 +1040,12 @@ function subscribeScheduler(onFrame, isPaused) {
978
1040
  };
979
1041
  }
980
1042
 
981
- /**
982
- * Gates drawing on "element intersects viewport AND the page tab is visible".
983
- * Falls back to always-visible when IntersectionObserver is unavailable.
1043
+ /**
1044
+ * Gates drawing on "element intersects viewport AND the page tab is visible".
1045
+ * Falls back to always-visible when IntersectionObserver is unavailable.
984
1046
  */
985
1047
  function createVisibilityGuard(element) {
1048
+ var onChange = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
986
1049
  var intersecting = true;
987
1050
  var pageVisible = typeof document === 'undefined' || !document.hidden;
988
1051
  var disposed = false;
@@ -992,6 +1055,7 @@ function createVisibilityGuard(element) {
992
1055
  intersecting = entries.some(function (entry) {
993
1056
  return entry.isIntersecting;
994
1057
  });
1058
+ if (typeof onChange === 'function') onChange();
995
1059
  }, {
996
1060
  rootMargin: '180px'
997
1061
  });
@@ -999,6 +1063,7 @@ function createVisibilityGuard(element) {
999
1063
  }
1000
1064
  var onVisibilityChange = function onVisibilityChange() {
1001
1065
  pageVisible = typeof document !== 'undefined' && !document.hidden;
1066
+ if (typeof onChange === 'function') onChange();
1002
1067
  };
1003
1068
  if (typeof document !== 'undefined') {
1004
1069
  document.addEventListener('visibilitychange', onVisibilityChange);
@@ -1034,22 +1099,100 @@ function nextTick(fn) {
1034
1099
  }
1035
1100
  }
1036
1101
 
1037
- function prefersReducedMotion() {
1038
- return typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches;
1102
+ function normalizeFps(value) {
1103
+ var fallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 60;
1104
+ var fps = Number(value);
1105
+ if (!Number.isFinite(fps)) return fallback;
1106
+ return Math.min(60, Math.max(1, fps));
1107
+ }
1108
+ function createFrameGate() {
1109
+ var initialFps = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 60;
1110
+ var fps = normalizeFps(initialFps);
1111
+ var elapsed = 0;
1112
+ return {
1113
+ shouldDraw: function shouldDraw(delta) {
1114
+ elapsed += delta;
1115
+ var interval = 1 / fps;
1116
+ if (elapsed + 0.0001 < interval) return false;
1117
+ elapsed %= interval;
1118
+ return true;
1119
+ },
1120
+ setFps: function setFps(value) {
1121
+ fps = normalizeFps(value, fps);
1122
+ elapsed = 0;
1123
+ return fps;
1124
+ },
1125
+ getFps: function getFps() {
1126
+ return fps;
1127
+ }
1128
+ };
1129
+ }
1130
+ function createReducedMotionPreference(enabled, onChange) {
1131
+ var query = enabled && typeof matchMedia !== 'undefined' ? matchMedia('(prefers-reduced-motion: reduce)') : null;
1132
+ var notify = function notify() {
1133
+ if (typeof onChange === 'function') onChange(Boolean(query && query.matches));
1134
+ };
1135
+ if (query) {
1136
+ if (typeof query.addEventListener === 'function') query.addEventListener('change', notify);else if (typeof query.addListener === 'function') query.addListener(notify);
1137
+ }
1138
+ return {
1139
+ matches: function matches() {
1140
+ return Boolean(query && query.matches);
1141
+ },
1142
+ dispose: function dispose() {
1143
+ if (!query) return;
1144
+ if (typeof query.removeEventListener === 'function') query.removeEventListener('change', notify);else if (typeof query.removeListener === 'function') query.removeListener(notify);
1145
+ }
1146
+ };
1147
+ }
1148
+
1149
+ function toFiniteNumber(value) {
1150
+ if (value == null || typeof value === 'boolean') return null;
1151
+ if (typeof value === 'string' && value.trim() === '') return null;
1152
+ var number = Number(value);
1153
+ return Number.isFinite(number) ? number : null;
1154
+ }
1155
+
1156
+ var HOST_STYLES = new WeakMap();
1157
+ function acquireHostStyles(host) {
1158
+ var active = HOST_STYLES.get(host);
1159
+ if (active) {
1160
+ active.count += 1;
1161
+ return;
1162
+ }
1163
+ var state = {
1164
+ count: 1,
1165
+ position: host.style.position,
1166
+ isolation: host.style.isolation
1167
+ };
1168
+ var computed = getComputedStyle(host);
1169
+ if (computed.position === 'static' || computed.position === '') host.style.position = 'relative';
1170
+ host.style.isolation = 'isolate';
1171
+ HOST_STYLES.set(host, state);
1172
+ }
1173
+ function releaseHostStyles(host) {
1174
+ var state = HOST_STYLES.get(host);
1175
+ if (!state) return;
1176
+ state.count -= 1;
1177
+ if (state.count > 0) return;
1178
+ host.style.position = state.position;
1179
+ host.style.isolation = state.isolation;
1180
+ HOST_STYLES.delete(host);
1039
1181
  }
1040
1182
 
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
- *
1183
+ /**
1184
+ * Mount the cosmic (nebula) material as a background layer inside `host`.
1185
+ *
1186
+ * The layer is absolutely positioned and defaults to `z-index: -1`, so it
1187
+ * paints behind the host's in-flow content: text/buttons/images inside the
1188
+ * host sit on top with zero extra CSS. Host gets `position: relative;
1189
+ * isolation: isolate` automatically so the layer can never escape its box
1190
+ * (or break stacking outside it).
1191
+ *
1050
1192
  * Options: preset (literary name), colors, seed, speed, quality,
1051
- * renderer, mouseColor, respectReducedMotion, opacity (0-1), fallbackColor
1052
- * (true=preset base color, a hex string, or false to disable).
1193
+ * renderer, renderScale, powerPreference, fps, paused/static, mouseColor,
1194
+ * respectReducedMotion, opacity (0-1), fallbackColor (true=preset base
1195
+ * color, a hex string, or false to disable).
1053
1196
  */
1054
1197
  function createColorBackground(host) {
1055
1198
  var _options$preset;
@@ -1063,18 +1206,29 @@ function createColorBackground(host) {
1063
1206
  renderer: DEFAULTS.capsule.renderer,
1064
1207
  respectReducedMotion: DEFAULTS.capsule.respectReducedMotion,
1065
1208
  mouseColor: true,
1209
+ renderScale: 1,
1210
+ powerPreference: 'high-performance',
1211
+ fps: DEFAULTS.capsule.fps,
1212
+ paused: false,
1213
+ static: false,
1066
1214
  opacity: 1,
1067
1215
  fallbackColor: true
1068
1216
  }, 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';
1217
+ merged.opacity = Number.isFinite(Number(merged.opacity)) ? Math.min(1, Math.max(0, Number(merged.opacity))) : 1;
1218
+ var normalizedColors = Array.isArray(merged.colors) ? merged.colors.map(normalizeColor) : [];
1219
+ if (normalizedColors.length === 4 && normalizedColors.every(Boolean)) preset.colors = normalizedColors;
1220
+ var initialSeed = toFiniteNumber(merged.seed);
1221
+ var initialSpeed = toFiniteNumber(merged.speed);
1222
+ if (initialSeed !== null) preset.seed = initialSeed;
1223
+ if (initialSpeed !== null) preset.speed = initialSpeed;
1224
+ merged.colors = _toConsumableArray(preset.colors);
1225
+ merged.seed = preset.seed;
1226
+ merged.speed = preset.speed;
1227
+ var optionColors = Array.isArray(options.colors) ? options.colors.map(normalizeColor) : [];
1228
+ var colorOverride = optionColors.length === 4 && optionColors.every(Boolean) ? _toConsumableArray(optionColors) : null;
1229
+ var seedOverride = options.seed !== undefined ? toFiniteNumber(options.seed) : null;
1230
+ var speedOverride = options.speed !== undefined ? toFiniteNumber(options.speed) : null;
1231
+ acquireHostStyles(host);
1078
1232
  var layer = document.createElement('div');
1079
1233
  layer.className = 'dlc-color-layer';
1080
1234
  var syncLayerVars = function syncLayerVars() {
@@ -1089,8 +1243,12 @@ function createColorBackground(host) {
1089
1243
  layer.appendChild(canvas);
1090
1244
  host.appendChild(layer);
1091
1245
  var emitter = createEmitter();
1092
- var paused = merged.respectReducedMotion && prefersReducedMotion();
1246
+ var manuallyPaused = merged.paused === true;
1247
+ var reducedPaused = false;
1248
+ var staticMode = merged.static === true;
1249
+ var contextLost = false;
1093
1250
  var disposed = false;
1251
+ var renderOnce = function renderOnce() {};
1094
1252
  var dirty = {
1095
1253
  preset: false,
1096
1254
  seed: false,
@@ -1098,19 +1256,37 @@ function createColorBackground(host) {
1098
1256
  colors: false,
1099
1257
  opacity: false,
1100
1258
  fallbackColor: false,
1101
- mouseColor: false
1259
+ mouseColor: false,
1260
+ quality: false,
1261
+ renderScale: false,
1262
+ paused: false,
1263
+ static: false,
1264
+ fps: false
1102
1265
  };
1103
1266
  var renderer;
1104
1267
  var useWebgl = merged.renderer !== 'canvas2d';
1105
1268
  if (useWebgl) {
1106
1269
  try {
1107
1270
  renderer = new CosmicRenderer(canvas, merged, {
1108
- dprCap: dprCapFor(merged.quality),
1271
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale),
1109
1272
  mouseColor: merged.mouseColor !== false,
1110
- eventTarget: host
1273
+ eventTarget: host,
1274
+ powerPreference: merged.powerPreference,
1275
+ onContextLost: function onContextLost() {
1276
+ contextLost = true;
1277
+ emitter.emit('contextlost', {});
1278
+ },
1279
+ onContextRestored: function onContextRestored() {
1280
+ contextLost = false;
1281
+ emitter.emit('contextrestored', {});
1282
+ renderOnce();
1283
+ wakeScheduler();
1284
+ }
1111
1285
  });
1112
1286
  } catch (error) {
1113
- renderer = new FallbackRenderer(canvas, merged);
1287
+ renderer = new FallbackRenderer(canvas, merged, {
1288
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale)
1289
+ });
1114
1290
  nextTick(function () {
1115
1291
  if (disposed) return;
1116
1292
  emitter.emit('error', {
@@ -1119,19 +1295,37 @@ function createColorBackground(host) {
1119
1295
  });
1120
1296
  }
1121
1297
  } else {
1122
- renderer = new FallbackRenderer(canvas, merged);
1298
+ renderer = new FallbackRenderer(canvas, merged, {
1299
+ dprCap: effectiveDprCap(merged.quality, merged.renderScale)
1300
+ });
1123
1301
  }
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);
1302
+ renderer.resize();
1129
1303
  var animationTime = 0;
1304
+ var frameGate = createFrameGate(merged.fps);
1305
+ renderOnce = function renderOnce() {
1306
+ return renderer.draw(animationTime);
1307
+ };
1308
+ var resize = function resize() {
1309
+ renderer.resize();
1310
+ if (manuallyPaused || reducedPaused || staticMode) renderOnce();
1311
+ };
1312
+ var resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(resize) : null;
1313
+ if (resizeObserver) resizeObserver.observe(host);else window.addEventListener('resize', resize);
1314
+ var visibility = createVisibilityGuard(layer, wakeScheduler);
1315
+ var motionPreference = createReducedMotionPreference(merged.respectReducedMotion, function (matches) {
1316
+ reducedPaused = matches;
1317
+ if (matches) renderOnce();else wakeScheduler();
1318
+ });
1319
+ reducedPaused = motionPreference.matches();
1320
+ var isMotionPaused = function isMotionPaused() {
1321
+ return manuallyPaused || reducedPaused || staticMode || contextLost;
1322
+ };
1323
+ renderOnce();
1130
1324
  var unsubscribe = subscribeScheduler(function (delta) {
1131
1325
  animationTime += delta;
1132
- if (visibility.isVisible()) renderer.draw(animationTime);
1326
+ if (frameGate.shouldDraw(delta)) renderer.draw(animationTime);
1133
1327
  }, function () {
1134
- return paused;
1328
+ return isMotionPaused() || !visibility.isVisible();
1135
1329
  });
1136
1330
  nextTick(function () {
1137
1331
  if (disposed) return;
@@ -1150,11 +1344,15 @@ function createColorBackground(host) {
1150
1344
  var next = getPreset('capsule', ref);
1151
1345
  dirty.preset = true;
1152
1346
  Object.assign(preset, next);
1347
+ if (colorOverride) preset.colors = _toConsumableArray(colorOverride);
1348
+ if (seedOverride !== null) preset.seed = seedOverride;
1349
+ if (speedOverride !== null) preset.speed = speedOverride;
1153
1350
  var nextColors = preset.colors.map(normalizeColor);
1154
1351
  if (nextColors.every(Boolean)) preset.colors = nextColors;
1155
1352
  renderer.setPreset(_objectSpread2({}, preset));
1156
1353
  renderer.resize();
1157
1354
  syncLayerVars();
1355
+ if (isMotionPaused()) renderOnce();
1158
1356
  emitter.emit('presetchange', {
1159
1357
  preset: _objectSpread2({}, preset)
1160
1358
  });
@@ -1166,27 +1364,33 @@ function createColorBackground(host) {
1166
1364
  return !color;
1167
1365
  })) return this;
1168
1366
  dirty.colors = true;
1367
+ colorOverride = _toConsumableArray(next);
1169
1368
  preset.colors = next;
1170
1369
  renderer.setPreset(_objectSpread2(_objectSpread2({}, preset), {}, {
1171
1370
  colors: next
1172
1371
  }));
1173
1372
  syncLayerVars();
1373
+ if (isMotionPaused()) renderOnce();
1174
1374
  return this;
1175
1375
  },
1176
1376
  setSeed: function setSeed(seed) {
1177
- var value = Number(seed);
1178
- if (!Number.isFinite(value)) return this;
1377
+ var value = toFiniteNumber(seed);
1378
+ if (value === null) return this;
1179
1379
  dirty.seed = true;
1380
+ seedOverride = value;
1180
1381
  preset.seed = value;
1181
1382
  renderer.setPreset(_objectSpread2({}, preset));
1383
+ if (isMotionPaused()) renderOnce();
1182
1384
  return this;
1183
1385
  },
1184
1386
  setSpeed: function setSpeed(speed) {
1185
- var value = Number(speed);
1186
- if (!Number.isFinite(value)) return this;
1387
+ var value = toFiniteNumber(speed);
1388
+ if (value === null) return this;
1187
1389
  dirty.speed = true;
1390
+ speedOverride = value;
1188
1391
  preset.speed = value;
1189
1392
  renderer.setPreset(_objectSpread2({}, preset));
1393
+ if (isMotionPaused()) renderOnce();
1190
1394
  return this;
1191
1395
  },
1192
1396
  setFallbackColor: function setFallbackColor(value) {
@@ -1202,8 +1406,23 @@ function createColorBackground(host) {
1202
1406
  return this;
1203
1407
  },
1204
1408
  setQuality: function setQuality(quality) {
1409
+ dirty.quality = true;
1205
1410
  merged.quality = quality;
1206
- if (typeof renderer.setDprCap === 'function') renderer.setDprCap(dprCapFor(quality));
1411
+ if (typeof renderer.setDprCap === 'function') {
1412
+ renderer.setDprCap(effectiveDprCap(quality, merged.renderScale));
1413
+ }
1414
+ if (isMotionPaused()) renderOnce();
1415
+ return this;
1416
+ },
1417
+ setRenderScale: function setRenderScale(renderScale) {
1418
+ var value = Number(renderScale);
1419
+ if (!Number.isFinite(value)) return this;
1420
+ dirty.renderScale = true;
1421
+ merged.renderScale = Math.min(1, Math.max(0.25, value));
1422
+ if (typeof renderer.setDprCap === 'function') {
1423
+ renderer.setDprCap(effectiveDprCap(merged.quality, merged.renderScale));
1424
+ }
1425
+ if (isMotionPaused()) renderOnce();
1207
1426
  return this;
1208
1427
  },
1209
1428
  setOpacity: function setOpacity(value) {
@@ -1219,20 +1438,42 @@ function createColorBackground(host) {
1219
1438
  return this;
1220
1439
  },
1221
1440
  pause: function pause() {
1222
- paused = true;
1441
+ dirty.paused = true;
1442
+ manuallyPaused = true;
1443
+ renderOnce();
1223
1444
  return this;
1224
1445
  },
1225
1446
  resume: function resume() {
1226
- paused = false;
1447
+ dirty.paused = true;
1448
+ manuallyPaused = false;
1449
+ wakeScheduler();
1450
+ return this;
1451
+ },
1452
+ setPaused: function setPaused(value) {
1453
+ return value ? this.pause() : this.resume();
1454
+ },
1455
+ setStatic: function setStatic(value) {
1456
+ dirty.static = true;
1457
+ staticMode = value === true;
1458
+ merged.static = staticMode;
1459
+ if (staticMode) renderOnce();else wakeScheduler();
1460
+ return this;
1461
+ },
1462
+ setFps: function setFps(fps) {
1463
+ dirty.fps = true;
1464
+ merged.fps = frameGate.setFps(fps);
1465
+ wakeScheduler();
1227
1466
  return this;
1228
1467
  },
1229
1468
  dispose: function dispose() {
1230
1469
  disposed = true;
1231
1470
  unsubscribe();
1232
1471
  visibility.dispose();
1233
- if (resizeObserver) resizeObserver.disconnect();else window.removeEventListener('resize', renderer.resize);
1472
+ motionPreference.dispose();
1473
+ if (resizeObserver) resizeObserver.disconnect();else window.removeEventListener('resize', resize);
1234
1474
  renderer.dispose();
1235
1475
  layer.remove();
1476
+ releaseHostStyles(host);
1236
1477
  },
1237
1478
  get opacity() {
1238
1479
  return merged.opacity;
@@ -1243,6 +1484,21 @@ function createColorBackground(host) {
1243
1484
  get mouseColor() {
1244
1485
  return merged.mouseColor;
1245
1486
  },
1487
+ get quality() {
1488
+ return merged.quality;
1489
+ },
1490
+ get renderScale() {
1491
+ return merged.renderScale;
1492
+ },
1493
+ get paused() {
1494
+ return manuallyPaused;
1495
+ },
1496
+ get static() {
1497
+ return staticMode;
1498
+ },
1499
+ get fps() {
1500
+ return frameGate.getFps();
1501
+ },
1246
1502
  dirty: dirty
1247
1503
  };
1248
1504
  }