vireglass 3.1.1 → 3.2.1

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/dom.d.cts CHANGED
@@ -73,6 +73,11 @@ type DisplacementState = {
73
73
  * caller that only ever had two does not have to change. */
74
74
  morphs?: readonly MorphShape[];
75
75
  };
76
+ /**
77
+ * The `feDisplacementMap` scale for these optics. The map's pixels are a fraction of it and do not
78
+ * depend on it, so a change of strength alone, such as appear, is this attribute and not a bake.
79
+ */
80
+ declare function displacementScale(optics: VireGlassOptics, geometry: VireGlassGeometry): number;
76
81
  /**
77
82
  * The raw pixel buffer, with no canvas involved — the part actually worth unit-testing.
78
83
  * `buildDisplacementMap` below is a thin DOM wrapper around this.
@@ -437,4 +442,4 @@ type GlassHandle = {
437
442
  };
438
443
  declare function attachGlass(el: HTMLElement, opts?: AttachGlassOptions): GlassHandle;
439
444
 
440
- export { type AttachGlassOptions, type BodyBackdrop, type BodyLayer, type DisplacementMap, type DisplacementState, type EdgePaint, type GlassBody, type GlassGroupHandle, type GlassGroupOptions, type GlassHandle, type GlassMorph, type GroupEntry, type ProbeOptions, RIM_WIDTH_PX, type RenderStats, type RgbColor, type RimAmbient, type ScrollEdgeHandle, type ScrollEdgeOptions, type SpectralMap, ambientLuma, attachGlass, attachScrollEdge, bodyLayer, boxShadowCss, buildDisplacementMap, buildSpectralMap, cached, clearMapCache, compositeLayers, contrastRimCss, createGlassGroup, hasSpectralEdge, lightConicAngle, mapCacheSize, paintFor, parseColor, probeBackdrop, renderDisplacementPixels, renderSpectralPixels, renderStats, resetRenderStats, resetSupportCache, resolveBody, rimGradientCss, rimLineAlpha, rimLobe, rimStrength, shadowAlphaFrom, statsFromSamples, supportsBackdropFilter, supportsSvgBackdropFilter, systemAccessibility, watchAccessibility, withPresence };
445
+ export { type AttachGlassOptions, type BodyBackdrop, type BodyLayer, type DisplacementMap, type DisplacementState, type EdgePaint, type GlassBody, type GlassGroupHandle, type GlassGroupOptions, type GlassHandle, type GlassMorph, type GroupEntry, type ProbeOptions, RIM_WIDTH_PX, type RenderStats, type RgbColor, type RimAmbient, type ScrollEdgeHandle, type ScrollEdgeOptions, type SpectralMap, ambientLuma, attachGlass, attachScrollEdge, bodyLayer, boxShadowCss, buildDisplacementMap, buildSpectralMap, cached, clearMapCache, compositeLayers, contrastRimCss, createGlassGroup, displacementScale, hasSpectralEdge, lightConicAngle, mapCacheSize, paintFor, parseColor, probeBackdrop, renderDisplacementPixels, renderSpectralPixels, renderStats, resetRenderStats, resetSupportCache, resolveBody, rimGradientCss, rimLineAlpha, rimLobe, rimStrength, shadowAlphaFrom, statsFromSamples, supportsBackdropFilter, supportsSvgBackdropFilter, systemAccessibility, watchAccessibility, withPresence };
package/dist/dom.d.ts CHANGED
@@ -73,6 +73,11 @@ type DisplacementState = {
73
73
  * caller that only ever had two does not have to change. */
74
74
  morphs?: readonly MorphShape[];
75
75
  };
76
+ /**
77
+ * The `feDisplacementMap` scale for these optics. The map's pixels are a fraction of it and do not
78
+ * depend on it, so a change of strength alone, such as appear, is this attribute and not a bake.
79
+ */
80
+ declare function displacementScale(optics: VireGlassOptics, geometry: VireGlassGeometry): number;
76
81
  /**
77
82
  * The raw pixel buffer, with no canvas involved — the part actually worth unit-testing.
78
83
  * `buildDisplacementMap` below is a thin DOM wrapper around this.
@@ -437,4 +442,4 @@ type GlassHandle = {
437
442
  };
438
443
  declare function attachGlass(el: HTMLElement, opts?: AttachGlassOptions): GlassHandle;
439
444
 
440
- export { type AttachGlassOptions, type BodyBackdrop, type BodyLayer, type DisplacementMap, type DisplacementState, type EdgePaint, type GlassBody, type GlassGroupHandle, type GlassGroupOptions, type GlassHandle, type GlassMorph, type GroupEntry, type ProbeOptions, RIM_WIDTH_PX, type RenderStats, type RgbColor, type RimAmbient, type ScrollEdgeHandle, type ScrollEdgeOptions, type SpectralMap, ambientLuma, attachGlass, attachScrollEdge, bodyLayer, boxShadowCss, buildDisplacementMap, buildSpectralMap, cached, clearMapCache, compositeLayers, contrastRimCss, createGlassGroup, hasSpectralEdge, lightConicAngle, mapCacheSize, paintFor, parseColor, probeBackdrop, renderDisplacementPixels, renderSpectralPixels, renderStats, resetRenderStats, resetSupportCache, resolveBody, rimGradientCss, rimLineAlpha, rimLobe, rimStrength, shadowAlphaFrom, statsFromSamples, supportsBackdropFilter, supportsSvgBackdropFilter, systemAccessibility, watchAccessibility, withPresence };
445
+ export { type AttachGlassOptions, type BodyBackdrop, type BodyLayer, type DisplacementMap, type DisplacementState, type EdgePaint, type GlassBody, type GlassGroupHandle, type GlassGroupOptions, type GlassHandle, type GlassMorph, type GroupEntry, type ProbeOptions, RIM_WIDTH_PX, type RenderStats, type RgbColor, type RimAmbient, type ScrollEdgeHandle, type ScrollEdgeOptions, type SpectralMap, ambientLuma, attachGlass, attachScrollEdge, bodyLayer, boxShadowCss, buildDisplacementMap, buildSpectralMap, cached, clearMapCache, compositeLayers, contrastRimCss, createGlassGroup, displacementScale, hasSpectralEdge, lightConicAngle, mapCacheSize, paintFor, parseColor, probeBackdrop, renderDisplacementPixels, renderSpectralPixels, renderStats, resetRenderStats, resetSupportCache, resolveBody, rimGradientCss, rimLineAlpha, rimLobe, rimStrength, shadowAlphaFrom, statsFromSamples, supportsBackdropFilter, supportsSvgBackdropFilter, systemAccessibility, watchAccessibility, withPresence };
package/dist/dom.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { shadowTint, NO_ACCESSIBILITY, createGroupState, createDeform, elasticAllowed, contrastRimLuma, concentricRadius, scrollEdgeStrength, scrollEdgeStyle, applyAppear, accentTone, accentAmount, applyGlassScale, GLASS_SCALE_DEFAULT, applyAccessibility, raiseIntoGlass } from './chunk-VLDD655U.js';
2
2
  import { ambientFrom, shouldInkBeLight, INK_LIGHT, INK_DARK } from './chunk-JNRZNMJJ.js';
3
3
  import { touchWarp, sceneDistance, sceneGradient, sdfRoundedRect, VIREGLASS_MATERIAL, REST_LIGHT, resolveOptics, refractionStrength } from './chunk-YAN2OK4H.js';
4
- import { shadowOpacity, bevelDp, thicknessDp, rimDp, shadowOpacityFrom, shadowReachDp, halfMinDp, MAX_STRETCH, roundedRectGeometry } from './chunk-Z4FKYY47.js';
5
- import { LENS, BODY, RIM, TOUCH, SHADOW, SHADOW_GAP, MEDIUM, SCROLL_EDGE, BEVEL, SPECTRAL, DISPERSION } from './chunk-CPSIY2DZ.js';
4
+ import { shadowOpacity, thicknessDp, bevelDp, rimDp, shadowOpacityFrom, shadowReachDp, halfMinDp, MAX_STRETCH, roundedRectGeometry } from './chunk-Z4FKYY47.js';
5
+ import { LENS, BODY, RIM, TOUCH, SHADOW, SHADOW_GAP, MEDIUM, SCROLL_EDGE, BEVEL, DISPERSION, SPECTRAL } from './chunk-CPSIY2DZ.js';
6
6
 
7
7
  // src/dom/displacement.ts
8
8
  var SCALE_GAIN = 0.75;
@@ -21,13 +21,15 @@ function bevelProfile(t) {
21
21
  const raw = Math.min(t / Math.sqrt(Math.max(1 - t * t * BEVEL.sphere, BEVEL.floor)), BEVEL.slopeMax);
22
22
  return raw / BEVEL.slopeMax;
23
23
  }
24
+ function displacementScale(optics, geometry) {
25
+ return Math.min(optics.refraction * thicknessDp(geometry, optics) * SCALE_GAIN, MAX_SCALE);
26
+ }
24
27
  function renderDisplacementPixels(optics, geometry, dpr, state = {}) {
25
28
  const bevel = bevelDp(geometry, optics);
26
29
  const density = mapDensity(geometry, bevel, dpr);
27
30
  const width = Math.max(1, Math.round(geometry.width * density));
28
31
  const height = Math.max(1, Math.round(geometry.height * density));
29
- const rawScale = optics.refraction * thicknessDp(geometry, optics) * SCALE_GAIN;
30
- const scale = Math.min(rawScale, MAX_SCALE);
32
+ const scale = displacementScale(optics, geometry);
31
33
  const smoothing = state.smoothing ?? 0;
32
34
  const shapes = [state.morph, state.morph2, ...state.morphs ?? []];
33
35
  const data = new Uint8ClampedArray(width * height * 4);
@@ -151,6 +153,18 @@ function mapDensity2(bevel, dpr) {
151
153
  if (bevel <= 0) return dpr;
152
154
  return Math.min(dpr, BEVEL_SAMPLES2 / bevel);
153
155
  }
156
+ var DEPTH_STEPS = 2048;
157
+ function hueAlongDepth(bevel, rim, thick, optics) {
158
+ const table = new Float64Array((DEPTH_STEPS + 1) * 3);
159
+ for (let k = 0; k <= DEPTH_STEPS; k += 1) {
160
+ const s = k / DEPTH_STEPS;
161
+ const [r, g, b] = spectralHue(s * s * bevel, bevel, rim, thick, optics);
162
+ table[k * 3] = r;
163
+ table[k * 3 + 1] = g;
164
+ table[k * 3 + 2] = b;
165
+ }
166
+ return table;
167
+ }
154
168
  function renderSpectralPixels(optics, geometry, dpr) {
155
169
  const bevel = bevelDp(geometry, optics);
156
170
  const density = mapDensity2(bevel, dpr);
@@ -162,24 +176,28 @@ function renderSpectralPixels(optics, geometry, dpr) {
162
176
  const halfW = geometry.width / 2;
163
177
  const halfH = geometry.height / 2;
164
178
  const neutral = Math.round(1 / HEADROOM * 255);
179
+ const table = hueAlongDepth(bevel, rim, thick, optics);
180
+ const toByte = 255 / HEADROOM;
181
+ new Uint32Array(data.buffer).fill(new Uint32Array(new Uint8ClampedArray([neutral, neutral, neutral, 255]).buffer)[0]);
182
+ const corner = Math.max(geometry.cornerRadius, bevel);
165
183
  for (let py = 0; py < height; py += 1) {
166
184
  const y = (py + 0.5) / density - halfH;
185
+ const ay = Math.abs(y);
186
+ const plain = Math.max(ay <= halfH - bevel ? halfW - corner : -1, ay <= halfH - corner ? halfW - bevel : -1);
167
187
  for (let px = 0; px < width; px += 1) {
168
188
  const x = (px + 0.5) / density - halfW;
189
+ if (Math.abs(x) <= plain) continue;
169
190
  const i = (py * width + px) * 4;
170
191
  const sd = sdfRoundedRect(x, y, geometry.width, geometry.height, geometry.cornerRadius);
171
192
  const e = Math.max(-sd, 0);
172
- if (e >= bevel) {
173
- data[i] = neutral;
174
- data[i + 1] = neutral;
175
- data[i + 2] = neutral;
176
- data[i + 3] = 255;
177
- continue;
178
- }
179
- const [r, g, b] = spectralHue(e, bevel, rim, thick, optics);
180
- data[i] = Math.round(r / HEADROOM * 255);
181
- data[i + 1] = Math.round(g / HEADROOM * 255);
182
- data[i + 2] = Math.round(b / HEADROOM * 255);
193
+ if (e >= bevel) continue;
194
+ const u = Math.sqrt(e / bevel) * DEPTH_STEPS;
195
+ const k = Math.min(Math.floor(u), DEPTH_STEPS - 1);
196
+ const f = u - k;
197
+ const at = k * 3;
198
+ data[i] = Math.round((table[at] + (table[at + 3] - table[at]) * f) * toByte);
199
+ data[i + 1] = Math.round((table[at + 1] + (table[at + 4] - table[at + 1]) * f) * toByte);
200
+ data[i + 2] = Math.round((table[at + 2] + (table[at + 5] - table[at + 2]) * f) * toByte);
183
201
  data[i + 3] = 255;
184
202
  }
185
203
  }
@@ -799,6 +817,9 @@ function getFilterHost(host) {
799
817
  (host instanceof ShadowRoot ? host : host.body).appendChild(svg);
800
818
  return svg;
801
819
  }
820
+ function writeAttr(node, name, value) {
821
+ if (node.getAttribute(name) !== value) node.setAttribute(name, value);
822
+ }
802
823
  function buildFilterElement(id, mapUrl, optics, scale, spectral) {
803
824
  const filter = document.createElementNS(SVG_NS, "filter");
804
825
  filter.setAttribute("id", id);
@@ -884,6 +905,7 @@ function withSpectral(filter, spectral) {
884
905
  product.setAttribute("in2", "hue");
885
906
  product.setAttribute("operator", "arithmetic");
886
907
  product.setAttribute("k1", String(spectral.headroom));
908
+ product.setAttribute("data-vireglass-headroom", String(spectral.headroom));
887
909
  product.setAttribute("k2", "0");
888
910
  product.setAttribute("k3", "0");
889
911
  product.setAttribute("k4", "0");
@@ -941,6 +963,7 @@ function attachGlass(el, opts = {}) {
941
963
  let filterEl = null;
942
964
  let destroyed = false;
943
965
  let mapKey = "";
966
+ let shownGeometry = null;
944
967
  let interim = false;
945
968
  let settling = false;
946
969
  let lastSample = null;
@@ -1003,31 +1026,46 @@ function attachGlass(el, opts = {}) {
1003
1026
  settling = true;
1004
1027
  apply();
1005
1028
  }
1006
- function retarget(f, map, hue) {
1029
+ function retarget(f, map, hue, geometry) {
1007
1030
  const images = f.querySelectorAll("feImage");
1008
1031
  images[0]?.setAttribute("href", map.url);
1009
1032
  if (hue) images[1]?.setAttribute("href", hue.url);
1033
+ shownGeometry = geometry;
1034
+ present(f);
1035
+ }
1036
+ function present(f) {
1037
+ if (!shownGeometry) return;
1038
+ const o = optics();
1039
+ const scale = displacementScale(o, shownGeometry);
1010
1040
  for (const pass of f.querySelectorAll("feDisplacementMap")) {
1011
1041
  const ratio = Number(pass.getAttribute("data-vireglass-ratio") ?? 1);
1012
- pass.setAttribute("scale", String(map.scale * (Number.isFinite(ratio) ? ratio : 1)));
1042
+ writeAttr(pass, "scale", String(scale * (Number.isFinite(ratio) ? ratio : 1)));
1043
+ }
1044
+ const frost = f.querySelector("feGaussianBlur");
1045
+ if (frost) writeAttr(frost, "stdDeviation", String(Math.max(o.blur, 0)));
1046
+ const hue = f.querySelector('feComposite[in2="hue"]');
1047
+ if (hue) {
1048
+ const headroom = Number(hue.getAttribute("data-vireglass-headroom") ?? 1);
1049
+ writeAttr(hue, "k1", String(headroom * appear));
1050
+ writeAttr(hue, "k2", String(1 - appear));
1013
1051
  }
1014
1052
  }
1015
1053
  function requestInterimMap() {
1016
1054
  if (!svgSupported || !filterEl || destroyed) return;
1017
1055
  const geometry = opts.geometry ?? measureGeometry(el);
1018
1056
  if (isEmpty(geometry)) return;
1019
- const opticsNow = optics();
1057
+ const full = settledOptics();
1020
1058
  const key = interimKey(
1021
1059
  geometry.width,
1022
1060
  geometry.height,
1023
1061
  geometry.cornerRadius,
1024
1062
  JSON.stringify([morph?.smoothing, morph?.shape, morph?.shape2, morph?.shapes]),
1025
- `a${appear.toFixed(3)}:${opticsNow.refraction}:${opticsNow.refractionScale}:${opticsNow.bevelDp}`
1063
+ `${full.refraction}:${full.refractionScale}:${full.bevelDp}`
1026
1064
  );
1027
1065
  let rec = interimMaps.get(key);
1028
1066
  if (!rec) {
1029
1067
  bump("reducedBakes");
1030
- rec = { map: buildDisplacementMap(opticsNow, geometry, Math.min(1, devicePixelRatio()), morphState()), ready: false };
1068
+ rec = { map: buildDisplacementMap(full, geometry, Math.min(1, devicePixelRatio()), morphState()), ready: false, geometry };
1031
1069
  interimMaps.set(key, rec);
1032
1070
  }
1033
1071
  const mine = seq += 1;
@@ -1036,7 +1074,7 @@ function attachGlass(el, opts = {}) {
1036
1074
  const show = () => {
1037
1075
  if (destroyed || filterEl !== forFilter || mine <= shownSeq) return;
1038
1076
  shownSeq = mine;
1039
- retarget(forFilter, record.map, null);
1077
+ retarget(forFilter, record.map, null, record.geometry);
1040
1078
  };
1041
1079
  if (record.ready) {
1042
1080
  show();
@@ -1058,17 +1096,18 @@ function attachGlass(el, opts = {}) {
1058
1096
  wasLight = light;
1059
1097
  const body = opts.body === false ? null : bodyCss(settledOptics(), sample, light ? 1 : 0, accent, appear);
1060
1098
  if (svgSupported) {
1061
- const key = `a${appear.toFixed(3)}|m${morphKey()}|${geometry.width}x${geometry.height}r${geometry.cornerRadius}@${devicePixelRatio()}:${opticsNow.refraction}:${opticsNow.refractionScale}:${opticsNow.bevelDp}:${opticsNow.blur}`;
1099
+ const full = settledOptics();
1100
+ const key = `m${morphKey()}|${geometry.width}x${geometry.height}r${geometry.cornerRadius}@${devicePixelRatio()}:${full.refraction}:${full.refractionScale}:${full.bevelDp}:${full.blur}:${full.thicknessDp}:${full.film}:${full.iridescence}:${full.diffraction}`;
1062
1101
  if (interim && filterEl) ; else if (isEmpty(geometry)) ; else if (key !== mapKey || !filterEl) {
1063
1102
  const map = cached(`d|${key}`, () => {
1064
1103
  bump("fullBakes");
1065
- return buildDisplacementMap(opticsNow, geometry, devicePixelRatio(), morphState());
1104
+ return buildDisplacementMap(full, geometry, devicePixelRatio(), morphState());
1066
1105
  });
1067
- const hue = hasSpectralEdge(opticsNow) ? cached(`h|${key}`, () => {
1106
+ const hue = hasSpectralEdge(full) ? cached(`h|${key}`, () => {
1068
1107
  bump("fullBakes");
1069
- return buildSpectralMap(opticsNow, geometry, devicePixelRatio());
1108
+ return buildSpectralMap(full, geometry, devicePixelRatio());
1070
1109
  }) : null;
1071
- const signature = filterSignature(opticsNow.blur, opticsNow.ior, opticsNow.iorSpread, !!hue);
1110
+ const signature = filterSignature(full.blur, full.ior, full.iorSpread, !!hue);
1072
1111
  if (settling && filterEl?.getAttribute("data-vireglass-signature") === signature) {
1073
1112
  settling = false;
1074
1113
  const mine = seq += 1;
@@ -1077,21 +1116,23 @@ function attachGlass(el, opts = {}) {
1077
1116
  void Promise.all([decoded(map.url), hue ? decoded(hue.url) : void 0]).then(() => {
1078
1117
  if (destroyed || filterEl !== forFilter || mapKey !== forKey || mine <= shownSeq) return;
1079
1118
  shownSeq = mine;
1080
- retarget(forFilter, map, hue);
1119
+ retarget(forFilter, map, hue, geometry);
1081
1120
  });
1082
1121
  } else {
1083
1122
  settling = false;
1084
1123
  invalidate();
1085
1124
  const defs = getFilterHost(host).querySelector("defs");
1086
- const next = buildFilterElement(id, map.url, opticsNow, map.scale, hue);
1125
+ const next = buildFilterElement(id, map.url, full, map.scale, hue);
1087
1126
  next.setAttribute("data-vireglass-signature", signature);
1088
1127
  if (filterEl?.parentNode) defs.replaceChild(next, filterEl);
1089
1128
  else defs.appendChild(next);
1090
1129
  bump("filterSwaps");
1091
1130
  filterEl = next;
1131
+ shownGeometry = geometry;
1092
1132
  }
1093
1133
  mapKey = key;
1094
1134
  }
1135
+ if (filterEl) present(filterEl);
1095
1136
  el.style.setProperty("backdrop-filter", `url(#${id})`);
1096
1137
  el.style.setProperty("-webkit-backdrop-filter", `url(#${id})`);
1097
1138
  } else {
@@ -1292,7 +1333,6 @@ function attachGlass(el, opts = {}) {
1292
1333
  if (next === appear) return;
1293
1334
  appear = next;
1294
1335
  apply();
1295
- if (interim) requestInterimMap();
1296
1336
  },
1297
1337
  setAccent(next) {
1298
1338
  accent = next ?? void 0;
@@ -1342,6 +1382,6 @@ function attachGlass(el, opts = {}) {
1342
1382
  return handle;
1343
1383
  }
1344
1384
 
1345
- export { RIM_WIDTH_PX, ambientLuma, attachGlass, attachScrollEdge, bodyLayer, boxShadowCss, buildDisplacementMap, buildSpectralMap, cached, clearMapCache, compositeLayers, contrastRimCss, createGlassGroup, hasSpectralEdge, lightConicAngle, mapCacheSize, paintFor, parseColor, probeBackdrop, renderDisplacementPixels, renderSpectralPixels, renderStats, resetRenderStats, resetSupportCache, resolveBody, rimGradientCss, rimLineAlpha, rimLobe, rimStrength, shadowAlphaFrom, statsFromSamples, supportsBackdropFilter, supportsSvgBackdropFilter, systemAccessibility, watchAccessibility, withPresence };
1385
+ export { RIM_WIDTH_PX, ambientLuma, attachGlass, attachScrollEdge, bodyLayer, boxShadowCss, buildDisplacementMap, buildSpectralMap, cached, clearMapCache, compositeLayers, contrastRimCss, createGlassGroup, displacementScale, hasSpectralEdge, lightConicAngle, mapCacheSize, paintFor, parseColor, probeBackdrop, renderDisplacementPixels, renderSpectralPixels, renderStats, resetRenderStats, resetSupportCache, resolveBody, rimGradientCss, rimLineAlpha, rimLobe, rimStrength, shadowAlphaFrom, statsFromSamples, supportsBackdropFilter, supportsSvgBackdropFilter, systemAccessibility, watchAccessibility, withPresence };
1346
1386
  //# sourceMappingURL=dom.js.map
1347
1387
  //# sourceMappingURL=dom.js.map