@filip.mazev/blocks 1.4.13 → 1.4.15

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.
Files changed (32) hide show
  1. package/README.md +5 -5
  2. package/core/styles/_a11y.scss +1 -1
  3. package/core/styles/_index.scss +3 -9
  4. package/core/styles/_palettes.scss +54 -10
  5. package/core/styles/themes/_azure-ember-theme.scss +4 -0
  6. package/core/styles/themes/_brick-rose-cyan-theme.scss +4 -0
  7. package/core/styles/themes/_carbon-cobalt-theme.scss +4 -0
  8. package/effects/effects.css +120 -8
  9. package/fesm2022/filip.mazev-blocks-core.mjs +20 -31
  10. package/fesm2022/filip.mazev-blocks-core.mjs.map +1 -1
  11. package/fesm2022/filip.mazev-blocks-effects-runtime.mjs +167 -46
  12. package/fesm2022/filip.mazev-blocks-effects-runtime.mjs.map +1 -1
  13. package/fesm2022/filip.mazev-blocks-forms.mjs +12 -12
  14. package/fesm2022/filip.mazev-blocks-forms.mjs.map +1 -1
  15. package/fesm2022/filip.mazev-blocks.mjs +1 -1
  16. package/fesm2022/filip.mazev-blocks.mjs.map +1 -1
  17. package/package.json +1 -1
  18. package/schematics/ng-add/index.d.ts +1 -10
  19. package/schematics/ng-add/index.js +4 -13
  20. package/schematics/ng-add/schema.json +11 -17
  21. package/types/filip.mazev-blocks-core.d.ts +4 -13
  22. package/types/filip.mazev-blocks-effects-runtime.d.ts +2 -0
  23. package/types/filip.mazev-blocks.d.ts +1 -1
  24. package/core/styles/themes/_azure-coral-theme.scss +0 -4
  25. package/core/styles/themes/_blue-theme.scss +0 -5
  26. package/core/styles/themes/_graphite-sky-blue-theme.scss +0 -19
  27. package/core/styles/themes/_green-theme.scss +0 -5
  28. package/core/styles/themes/_orange-theme.scss +0 -5
  29. package/core/styles/themes/_purple-theme.scss +0 -5
  30. package/core/styles/themes/_red-theme.scss +0 -5
  31. package/core/styles/themes/_scarlet-cyan-theme.scss +0 -4
  32. package/core/styles/themes/_white-theme.scss +0 -5
@@ -31,6 +31,7 @@ function configureAppearance(root, initial = {}) {
31
31
  if (configured.has(root)) throw new Error("A glass configuration is already mounted on this root.");
32
32
  configured.add(root);
33
33
  const restoreAttributes = rememberAttributes(root, attributes);
34
+ const originalAttributes = new Map(attributes.map((name) => [name, root.getAttribute(name)]));
34
35
  const restoreStyles = rememberStyles(root, properties);
35
36
  let options = { ...initial };
36
37
  let disposed = false;
@@ -38,27 +39,34 @@ function configureAppearance(root, initial = {}) {
38
39
  const viewEvent = root.ownerDocument.defaultView?.CustomEvent;
39
40
  if (viewEvent) root.ownerDocument.dispatchEvent(new viewEvent(configurationEvent, { detail: root }));
40
41
  };
41
- const paint = () => {
42
- restoreAttributes();
43
- restoreStyles();
42
+ const paint = (sync = true) => {
44
43
  const values = [
45
44
  ["data-bx-glass-quality", options.quality],
46
45
  ["data-bx-reduced-motion", options.reducedMotion],
47
46
  ["data-bx-reduced-transparency", options.reducedTransparency]
48
47
  ];
49
- for (const [name, value] of values) if (value !== void 0) root.setAttribute(name, String(value));
50
- if (options.glassiness !== void 0) root.style.setProperty("--bx-glassiness", String(finiteClamp(options.glassiness, 0, 1, .5)));
51
- notify();
48
+ for (const [name, value] of values) {
49
+ const next = value === void 0 ? originalAttributes.get(name) ?? null : String(value);
50
+ if (root.getAttribute(name) === next) continue;
51
+ if (next === null) root.removeAttribute(name);
52
+ else root.setAttribute(name, next);
53
+ }
54
+ if (options.glassiness !== void 0) {
55
+ const next = String(finiteClamp(options.glassiness, 0, 1, .5));
56
+ if (root.style.getPropertyValue("--bx-glassiness") !== next) root.style.setProperty("--bx-glassiness", next);
57
+ } else restoreStyles();
58
+ if (sync) notify();
52
59
  };
53
60
  paint();
54
61
  return {
55
62
  update(next) {
56
63
  if (!disposed) {
64
+ const previous = options;
57
65
  options = {
58
66
  ...options,
59
67
  ...next
60
68
  };
61
- paint();
69
+ paint(previous.quality !== options.quality || previous.reducedMotion !== options.reducedMotion || previous.reducedTransparency !== options.reducedTransparency);
62
70
  }
63
71
  },
64
72
  destroy() {
@@ -246,7 +254,7 @@ function lightingVisibility(doc, view) {
246
254
  };
247
255
  }
248
256
  const sampleInterval = 180;
249
- const liveInterval = 500;
257
+ const liveInterval = 16;
250
258
  const rasterSize = 32;
251
259
  var AmbientSampler = class {
252
260
  doc;
@@ -256,6 +264,7 @@ var AmbientSampler = class {
256
264
  samples = /* @__PURE__ */ new Map();
257
265
  dirty = /* @__PURE__ */ new Set();
258
266
  pending = /* @__PURE__ */ new Set();
267
+ urgent = /* @__PURE__ */ new Set();
259
268
  sources = /* @__PURE__ */ new Set();
260
269
  initialized = false;
261
270
  media = /* @__PURE__ */ new WeakMap();
@@ -296,15 +305,31 @@ var AmbientSampler = class {
296
305
  this.samples.delete(entry);
297
306
  this.dirty.delete(entry);
298
307
  this.pending.delete(entry);
308
+ this.urgent.delete(entry);
299
309
  this.syncLive();
300
310
  }
301
311
  lights(entry) {
302
- return this.samples.get(entry)?.lights ?? [];
312
+ const sample = this.samples.get(entry);
313
+ if (!sample) return [];
314
+ const dx = entry.bounds && sample.bounds ? entry.bounds.left - sample.bounds.left : 0;
315
+ const dy = entry.bounds && sample.bounds ? entry.bounds.top - sample.bounds.top : 0;
316
+ return dx || dy ? sample.lights.map((light) => ({
317
+ ...light,
318
+ x: light.x + dx,
319
+ y: light.y + dy
320
+ })) : sample.lights;
303
321
  }
304
- invalidate(entry) {
305
- for (const receiver of entry ? [entry] : this.samples.keys()) if (receiver.visible && receiver.element.isConnected) this.dirty.add(receiver);
322
+ invalidate(entry, immediate = false) {
323
+ for (const receiver of entry ? Array.isArray(entry) ? entry : [entry] : this.samples.keys()) {
324
+ if (!receiver.visible || !receiver.element.isConnected) continue;
325
+ if (!this.pending.has(receiver)) this.dirty.add(receiver);
326
+ if (immediate) this.urgent.add(receiver);
327
+ }
306
328
  this.resume();
307
329
  }
330
+ isPending(entry) {
331
+ return this.urgent.has(entry);
332
+ }
308
333
  assetChanged(element) {
309
334
  this.media.delete(element);
310
335
  this.invalidate();
@@ -324,6 +349,13 @@ var AmbientSampler = class {
324
349
  this.pending.clear();
325
350
  return;
326
351
  }
352
+ if (this.urgent.size) {
353
+ if (this.timer !== void 0) this.view.clearTimeout(this.timer);
354
+ this.timer = void 0;
355
+ for (const entry of this.dirty) this.pending.add(entry);
356
+ this.dirty.clear();
357
+ this.stopFrame ??= subscribeFrame(() => this.sampleBatch());
358
+ }
327
359
  if (this.dirty.size && this.timer === void 0 && !this.stopFrame) this.timer = this.view.setTimeout(() => {
328
360
  this.timer = void 0;
329
361
  this.nextSample = this.view.performance.now() + sampleInterval;
@@ -342,6 +374,7 @@ var AmbientSampler = class {
342
374
  this.samples.clear();
343
375
  this.dirty.clear();
344
376
  this.pending.clear();
377
+ this.urgent.clear();
345
378
  this.sources.clear();
346
379
  this.media = /* @__PURE__ */ new WeakMap();
347
380
  this.initialized = false;
@@ -365,15 +398,19 @@ var AmbientSampler = class {
365
398
  const rasterSamples = /* @__PURE__ */ new Map();
366
399
  const sources = [...this.sources];
367
400
  const completed = [];
368
- for (const entry of this.pending) {
401
+ for (const entry of /* @__PURE__ */ new Set([...this.urgent, ...this.pending])) {
369
402
  this.pending.delete(entry);
403
+ this.urgent.delete(entry);
370
404
  if (!entry.visible || !entry.element.isConnected || !this.samples.has(entry)) continue;
371
405
  entry.bounds = entry.element.getBoundingClientRect();
406
+ const bounds = entry.bounds;
407
+ if (bounds.bottom <= 0 || bounds.right <= 0 || bounds.top >= this.view.innerHeight || bounds.left >= this.view.innerWidth) continue;
372
408
  this.dependencies = /* @__PURE__ */ new Set();
373
409
  const lights = this.environmentLights(entry, sources, rasterSamples, rects, visibility);
374
410
  const mediaRects = /* @__PURE__ */ new Map();
375
411
  for (const source of this.dependencies) mediaRects.set(source, this.rectFor(source, rects));
376
412
  this.samples.set(entry, {
413
+ bounds,
377
414
  lights,
378
415
  media: this.dependencies,
379
416
  rects: mediaRects
@@ -385,6 +422,7 @@ var AmbientSampler = class {
385
422
  for (const entry of completed) this.changed(entry);
386
423
  if (this.pending.size) return true;
387
424
  this.stopFrame = void 0;
425
+ this.nextSample = this.view.performance.now() + sampleInterval;
388
426
  this.resume();
389
427
  return false;
390
428
  }
@@ -393,6 +431,8 @@ var AmbientSampler = class {
393
431
  for (const [entry, sample] of this.samples) {
394
432
  const motion = inheritedAttribute(entry.element, "data-bx-reduced-motion");
395
433
  if (!entry.visible || !entry.element.isConnected || (motion === null ? this.motion.matches : motion === "true")) continue;
434
+ const bounds = entry.bounds;
435
+ if (bounds && (bounds.bottom <= 0 || bounds.right <= 0 || bounds.top >= this.view.innerHeight || bounds.left >= this.view.innerWidth)) continue;
396
436
  for (const source of sample.media) {
397
437
  if (!source.isConnected || !/^(CANVAS|VIDEO)$/.test(source.tagName) || this.media.get(source)?.data === null) continue;
398
438
  if (source.tagName === "VIDEO" && source.paused) continue;
@@ -426,6 +466,7 @@ var AmbientSampler = class {
426
466
  const sample = this.samples.get(entry);
427
467
  if (!sample || coverageChanged || this.dirty.has(entry) || this.pending.has(entry)) {
428
468
  this.dirty.add(entry);
469
+ this.urgent.add(entry);
429
470
  continue;
430
471
  }
431
472
  const pixels = /* @__PURE__ */ new Map([[source, next?.data ?? null]]);
@@ -442,12 +483,12 @@ var AmbientSampler = class {
442
483
  }
443
484
  }
444
485
  for (const entry of recolored) this.changed(entry);
445
- this.liveDelay = changed ? liveInterval : Math.min(2e3, this.liveDelay * 2);
486
+ this.liveDelay = changed ? liveInterval : Math.min(250, this.liveDelay * 2);
446
487
  this.resume();
447
488
  }, this.liveDelay);
448
489
  }
449
490
  samePixels(first, second, alphaOnly = false) {
450
- for (let index = alphaOnly ? 3 : 0; index < first.data.length; index += alphaOnly ? 4 : 1) if (first.data[index] >> 3 !== second.data[index] >> 3) return false;
491
+ for (let index = alphaOnly ? 3 : 0; index < first.data.length; index += alphaOnly ? 4 : 1) if (alphaOnly ? first.data[index] >> 3 !== second.data[index] >> 3 : Math.abs(first.data[index] - second.data[index]) >= 2) return false;
451
492
  return true;
452
493
  }
453
494
  readRaster(element, refresh = false) {
@@ -736,11 +777,46 @@ var AmbientSampler = class {
736
777
  v = (y - bounds.top - offset(positionY, bounds.height - height * scale)) / (height * scale);
737
778
  if (u < 0 || u > 1 || v < 0 || v > 1) return void 0;
738
779
  }
739
- const px = Math.max(0, Math.min(data.width - 1, Math.floor(u * data.width)));
740
- const offset = (Math.max(0, Math.min(data.height - 1, Math.floor(v * data.height))) * data.width + px) * 4;
741
- const alpha = data.data[offset + 3] / 255;
780
+ const px = Math.max(0, Math.min(data.width - 1, u * data.width - .5));
781
+ const py = Math.max(0, Math.min(data.height - 1, v * data.height - .5));
782
+ const left = Math.floor(px), top = Math.floor(py);
783
+ const right = Math.min(data.width - 1, left + 1), bottom = Math.min(data.height - 1, top + 1);
784
+ const taps = [
785
+ [
786
+ left,
787
+ top,
788
+ (1 - (px - left)) * (1 - (py - top))
789
+ ],
790
+ [
791
+ right,
792
+ top,
793
+ (px - left) * (1 - (py - top))
794
+ ],
795
+ [
796
+ left,
797
+ bottom,
798
+ (1 - (px - left)) * (py - top)
799
+ ],
800
+ [
801
+ right,
802
+ bottom,
803
+ (px - left) * (py - top)
804
+ ]
805
+ ];
806
+ const channels = [
807
+ 0,
808
+ 0,
809
+ 0
810
+ ];
811
+ let alpha = 0;
812
+ for (const [x, y, weight] of taps) {
813
+ const offset = (y * data.width + x) * 4;
814
+ const coverage = data.data[offset + 3] / 255 * weight;
815
+ alpha += coverage;
816
+ for (let channel = 0; channel < 3; channel++) channels[channel] += data.data[offset + channel] * coverage;
817
+ }
742
818
  if (alpha < .08) return void 0;
743
- return `rgb(${data.data[offset]} ${data.data[offset + 1]} ${data.data[offset + 2]} / ${alpha.toFixed(2)})`;
819
+ return `rgb(${channels.map((channel) => Math.round(channel / alpha)).join(" ")} / ${alpha.toFixed(2)})`;
744
820
  }
745
821
  gradientColorAt(image, bounds, x, y) {
746
822
  if (!image.includes("gradient(")) return void 0;
@@ -768,7 +844,10 @@ var AmbientSampler = class {
768
844
  const v = (y - (bounds.top + bounds.height / 2)) / Math.max(1, bounds.height / 2);
769
845
  amount = Math.max(0, Math.min(1, Math.hypot(u, v) / Math.SQRT2));
770
846
  }
771
- return colors[Math.min(colors.length - 1, Math.round(amount * (colors.length - 1)))];
847
+ const position = amount * (colors.length - 1);
848
+ const index = Math.min(colors.length - 1, Math.floor(position));
849
+ if (index === colors.length - 1 || position === index) return colors[index];
850
+ return `color-mix(in srgb, ${colors[index]} ${((1 - (position - index)) * 100).toFixed(2)}%, ${colors[index + 1]})`;
772
851
  }
773
852
  isTransparent(color) {
774
853
  const value = color.trim().toLowerCase();
@@ -941,13 +1020,25 @@ var SceneRuntime = class {
941
1020
  this.refresh();
942
1021
  };
943
1022
  refresh = () => {
944
- this.ambient.invalidate();
945
- for (const entry of this.entries.values()) {
946
- entry.bounds = void 0;
947
- entry.lastLighting = void 0;
948
- if (entry.visible) this.queue.add(entry);
1023
+ this.refreshEntries([...this.entries.values()]);
1024
+ };
1025
+ scroll = (event) => {
1026
+ const root = event.target;
1027
+ if (root?.nodeType !== 1) {
1028
+ this.refresh();
1029
+ return;
949
1030
  }
950
- this.schedule();
1031
+ const area = root.getBoundingClientRect();
1032
+ const nearby = [];
1033
+ this.refreshEntries([...this.entries.values()].filter((entry) => {
1034
+ if (!entry.visible) return false;
1035
+ if (root.contains(entry.element)) return true;
1036
+ const bounds = entry.bounds ??= entry.element.getBoundingClientRect();
1037
+ if (bounds.right > area.left && bounds.left < area.right && bounds.bottom > area.top && bounds.top < area.bottom) return true;
1038
+ if (bounds.right >= area.left - 160 && bounds.left <= area.right + 160 && bounds.bottom >= area.top - 160 && bounds.top <= area.bottom + 160) nearby.push(entry);
1039
+ return false;
1040
+ }));
1041
+ this.ambient.invalidate(nearby);
951
1042
  };
952
1043
  constructor(doc) {
953
1044
  this.doc = doc;
@@ -965,11 +1056,11 @@ var SceneRuntime = class {
965
1056
  for (const record of records) {
966
1057
  const entry = this.entries.get(record.target);
967
1058
  if (!entry) continue;
1059
+ if (record.isIntersecting && !entry.visible) entry.resuming = true;
968
1060
  entry.visible = record.isIntersecting;
969
- this.ambient.invalidate(entry);
1061
+ this.ambient.invalidate(entry, true);
970
1062
  if (entry.visible) {
971
1063
  entry.bounds = void 0;
972
- entry.lastLighting = void 0;
973
1064
  this.queue.add(entry);
974
1065
  }
975
1066
  }
@@ -1058,7 +1149,7 @@ var SceneRuntime = class {
1058
1149
  if (entry) this.updateSelected(entry);
1059
1150
  this.updateEmitter();
1060
1151
  if (entry) {
1061
- this.ambient.invalidate(entry);
1152
+ this.ambient.invalidate(entry, true);
1062
1153
  entry.bounds = void 0;
1063
1154
  entry.lastLighting = void 0;
1064
1155
  this.queue.add(entry);
@@ -1097,6 +1188,14 @@ var SceneRuntime = class {
1097
1188
  }
1098
1189
  };
1099
1190
  }
1191
+ refreshEntries(entries) {
1192
+ this.ambient.invalidate(entries, true);
1193
+ for (const entry of entries) {
1194
+ entry.bounds = void 0;
1195
+ if (entry.visible) this.queue.add(entry);
1196
+ }
1197
+ this.schedule();
1198
+ }
1100
1199
  isLightingDecoration(node) {
1101
1200
  if (node.nodeType !== node.ELEMENT_NODE) return true;
1102
1201
  const element = node;
@@ -1208,7 +1307,7 @@ var SceneRuntime = class {
1208
1307
  this.doc.addEventListener("pointerleave", this.leave, { passive: true });
1209
1308
  this.view.addEventListener("blur", this.blur);
1210
1309
  this.view.addEventListener("resize", this.refresh, { passive: true });
1211
- this.doc.addEventListener("scroll", this.refresh, {
1310
+ this.doc.addEventListener("scroll", this.scroll, {
1212
1311
  passive: true,
1213
1312
  capture: true
1214
1313
  });
@@ -1243,7 +1342,7 @@ var SceneRuntime = class {
1243
1342
  this.doc.removeEventListener("pointerleave", this.leave);
1244
1343
  this.view.removeEventListener("blur", this.blur);
1245
1344
  this.view.removeEventListener("resize", this.refresh);
1246
- this.doc.removeEventListener("scroll", this.refresh, true);
1345
+ this.doc.removeEventListener("scroll", this.scroll, true);
1247
1346
  this.doc.removeEventListener("load", this.assetLoad, true);
1248
1347
  this.doc.removeEventListener("loadeddata", this.assetLoad, true);
1249
1348
  this.doc.removeEventListener("play", this.assetLoad, true);
@@ -1279,7 +1378,13 @@ var SceneRuntime = class {
1279
1378
  }, `hover:${emitter.id}`, true)
1280
1379
  }));
1281
1380
  const sceneData = /* @__PURE__ */ new Map();
1282
- const projections = batch.filter((entry) => entry.visible && entry.element.isConnected).map((entry) => {
1381
+ const projections = batch.filter((entry) => {
1382
+ if (!entry.visible || !entry.element.isConnected || entry.resuming && this.ambient.isPending(entry)) return false;
1383
+ const bounds = entry.bounds ??= entry.element.getBoundingClientRect();
1384
+ const visible = bounds.bottom > 0 && bounds.right > 0 && bounds.top < this.view.innerHeight && bounds.left < this.view.innerWidth;
1385
+ if (!visible) entry.resuming = true;
1386
+ return visible;
1387
+ }).map((entry) => {
1283
1388
  entry.bounds ??= entry.element.getBoundingClientRect();
1284
1389
  const origin = lightingOrigin(entry.element);
1285
1390
  let nearest;
@@ -1298,13 +1403,17 @@ var SceneRuntime = class {
1298
1403
  for (const { emitter, light } of activeLights) if (this.scope(emitter.element) === this.scope(entry.element) && visibility.layer(lightingOrigin(emitter.element)) === visibility.layer(origin) && !emitter.element.contains(entry.element) && (!entry.element.contains(emitter.element) || entry.element.matches("[role=\"menu\"], [role=\"tablist\"]"))) lights.push(light);
1299
1404
  lights.push(...this.ambient.lights(entry));
1300
1405
  const reflections = (data?.reflections ?? []).filter((reflection) => visibility.boundary(reflection.source) === boundary);
1301
- return [entry, this.project(entry, lights, reflections, visibility)];
1406
+ const lighting = this.project(entry, lights, reflections, visibility);
1407
+ lighting.immediate = entry.resuming || !entry.painted;
1408
+ entry.resuming = false;
1409
+ return [entry, lighting];
1302
1410
  });
1303
1411
  for (const entry of batch) this.queue.delete(entry);
1304
1412
  for (const { emitter, color, exposed } of emissions) emitter.hooks.emit?.(exposed, color);
1305
1413
  for (const [entry, lighting] of projections) {
1306
1414
  if (this.sameLighting(entry.lastLighting, lighting)) continue;
1307
1415
  entry.lastLighting = lighting;
1416
+ entry.painted = true;
1308
1417
  const before = entry.element.getAttribute("style");
1309
1418
  entry.hooks.light(lighting);
1310
1419
  const after = entry.element.getAttribute("style");
@@ -1544,14 +1653,12 @@ function mountSurface(element, initial = {}) {
1544
1653
  rim.className = "bx-glass-rim";
1545
1654
  rim.setAttribute("aria-hidden", "true");
1546
1655
  element.append(rim);
1547
- const environment = [doc.createElement("span"), doc.createElement("span")];
1548
- for (const node of environment) {
1549
- node.className = "bx-glass-contribution bx-glass-environment";
1550
- node.style.opacity = "0";
1551
- rim.append(node);
1552
- }
1656
+ const environment = doc.createElement("span");
1657
+ environment.className = "bx-glass-contribution bx-glass-environment";
1658
+ rim.append(environment);
1553
1659
  let environmentImage = "none";
1554
- let environmentIndex = 0;
1660
+ const environmentLayers = /* @__PURE__ */ new Map();
1661
+ const environmentColors = /* @__PURE__ */ new Map();
1555
1662
  const ambient = doc.createElement("span");
1556
1663
  ambient.className = "bx-glass-ambient";
1557
1664
  decoration.append(light);
@@ -1703,16 +1810,30 @@ function mountSurface(element, initial = {}) {
1703
1810
  element.style.setProperty("--bx-glass-lit-edge-bottom", edgeAt(50, 100));
1704
1811
  element.style.setProperty("--bx-glass-lit-edge-left", edgeAt(0, 50));
1705
1812
  }
1706
- const ambientImage = value.contributions.filter((contribution) => contribution.ambient).map((contribution) => `radial-gradient(circle ${contribution.spread}px at ${contribution.x.toFixed(2)}% ${contribution.y.toFixed(2)}%, color-mix(in srgb, ${contribution.color} ${(contribution.edgeStrength * 100).toFixed(2)}%, transparent), transparent 75%)`).join(", ") || "none";
1813
+ const activeEnvironment = /* @__PURE__ */ new Set();
1814
+ for (const contribution of value.contributions) {
1815
+ if (!contribution.ambient) continue;
1816
+ const slot = Number(contribution.key.split(":").pop());
1817
+ if (!Number.isInteger(slot) || slot < 0 || slot >= 16) continue;
1818
+ activeEnvironment.add(slot);
1819
+ const property = `--bx-glass-environment-color-${slot}`;
1820
+ const color = `color-mix(in srgb, ${contribution.color} ${(contribution.edgeStrength * 100).toFixed(2)}%, transparent)`;
1821
+ if (environmentColors.get(slot) !== color) {
1822
+ environment.style.setProperty(property, color);
1823
+ environmentColors.set(slot, color);
1824
+ }
1825
+ environmentLayers.set(slot, `radial-gradient(circle ${contribution.spread}px at ${contribution.x.toFixed(2)}% ${contribution.y.toFixed(2)}%, var(${property}), transparent 75%)`);
1826
+ }
1827
+ for (const slot of environmentColors.keys()) if (!activeEnvironment.has(slot) && environmentColors.get(slot) !== "transparent") {
1828
+ environment.style.setProperty(`--bx-glass-environment-color-${slot}`, "transparent");
1829
+ environmentColors.set(slot, "transparent");
1830
+ }
1831
+ const ambientImage = [...environmentLayers.values()].join(", ") || "none";
1707
1832
  if (ambientImage !== environmentImage) {
1708
1833
  environmentImage = ambientImage;
1709
- const previous = environment[environmentIndex];
1710
- environmentIndex = 1 - environmentIndex;
1711
- const next = environment[environmentIndex];
1712
- next.style.backgroundImage = ambientImage;
1713
- next.style.opacity = "1";
1714
- previous.style.opacity = "0";
1834
+ environment.style.backgroundImage = ambientImage;
1715
1835
  }
1836
+ if (value.immediate || reducedMotion) for (const animation of environment.getAnimations()) animation.finish();
1716
1837
  const contributions = value.contributions.filter((contribution) => !contribution.ambient);
1717
1838
  const active = new Set(contributions.map((contribution) => contribution.key));
1718
1839
  for (const contribution of contributions) setLayer(contribution);