vireglass 2.0.0 → 2.2.0

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 (43) hide show
  1. package/README.md +2 -1
  2. package/dist/{chunk-JF3FK254.js → chunk-2XJJO7SC.js} +4 -4
  3. package/dist/{chunk-JF3FK254.js.map → chunk-2XJJO7SC.js.map} +1 -1
  4. package/dist/{chunk-3PGYF2NK.js → chunk-CTTZNZFV.js} +3 -3
  5. package/dist/{chunk-3PGYF2NK.js.map → chunk-CTTZNZFV.js.map} +1 -1
  6. package/dist/{chunk-IQOBZGCF.js → chunk-H5QTBF67.js} +30 -32
  7. package/dist/chunk-H5QTBF67.js.map +1 -0
  8. package/dist/{chunk-TTOKBARR.js → chunk-MMPK6DCK.js} +10 -3
  9. package/dist/chunk-MMPK6DCK.js.map +1 -0
  10. package/dist/{chunk-P2ZOODXD.js → chunk-SLRPHDV7.js} +3 -3
  11. package/dist/{chunk-P2ZOODXD.js.map → chunk-SLRPHDV7.js.map} +1 -1
  12. package/dist/dom.cjs +79 -52
  13. package/dist/dom.cjs.map +1 -1
  14. package/dist/dom.d.cts +27 -4
  15. package/dist/dom.d.ts +27 -4
  16. package/dist/dom.js +48 -26
  17. package/dist/dom.js.map +1 -1
  18. package/dist/{group-model-8u4GKrUu.d.cts → group-model-DwtixGhG.d.cts} +15 -6
  19. package/dist/{group-model-vYSmEVia.d.ts → group-model-z4N27qsC.d.ts} +15 -6
  20. package/dist/index.cjs +28 -31
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.cts +1 -1
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.js +5 -5
  25. package/dist/law.cjs +8 -1
  26. package/dist/law.cjs.map +1 -1
  27. package/dist/law.d.cts +7 -0
  28. package/dist/law.d.ts +7 -0
  29. package/dist/law.js +1 -1
  30. package/dist/reference.js +2 -2
  31. package/dist/web/index.cjs +1 -2
  32. package/dist/web/index.cjs.map +1 -1
  33. package/dist/web/index.js +4 -4
  34. package/docs/law.md +2 -2
  35. package/package.json +11 -9
  36. package/src/dom/body.ts +24 -9
  37. package/src/dom/displacement.ts +7 -2
  38. package/src/dom/index.ts +52 -11
  39. package/src/dom/probe.ts +27 -3
  40. package/src/law.ts +7 -0
  41. package/src/sdf.ts +44 -36
  42. package/dist/chunk-IQOBZGCF.js.map +0 -1
  43. package/dist/chunk-TTOKBARR.js.map +0 -1
package/dist/dom.d.cts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { B as BackdropSample } from './adaptation-BSbbdExK.cjs';
2
2
  import { V as VireGlassGeometry } from './geometry-RhKd7CM5.cjs';
3
3
  import { V as VireGlassOptics, a as VireGlassMaterial } from './material-CnUmdKg1.cjs';
4
- import { T as TouchWarp, M as MorphShape, V as VireGlassAccessibility, S as ScrollEdgeStyle, G as GroupPlane } from './group-model-8u4GKrUu.cjs';
4
+ import { T as TouchWarp, M as MorphShape, V as VireGlassAccessibility, S as ScrollEdgeStyle, G as GroupPlane } from './group-model-DwtixGhG.cjs';
5
5
  import { V as VireGlassAccent } from './adapters-DugC6Qri.cjs';
6
6
 
7
7
  /** Any `backdrop-filter` at all (e.g. `blur()`) — the fallback path's own requirement. */
@@ -67,6 +67,9 @@ type DisplacementState = {
67
67
  smoothing?: number;
68
68
  morph?: MorphShape;
69
69
  morph2?: MorphShape;
70
+ /** Any number of shapes flowing into this one. `morph`/`morph2` are the first two, kept so a
71
+ * caller that only ever had two does not have to change. */
72
+ morphs?: readonly MorphShape[];
70
73
  };
71
74
  /**
72
75
  * The raw pixel buffer, with no canvas involved — the part actually worth unit-testing.
@@ -157,9 +160,21 @@ type GlassBody = {
157
160
  declare function resolveBody(optics: VireGlassOptics, sample: BodyBackdrop, ink: number): GlassBody;
158
161
  /**
159
162
  * PRESENCE (§3): over a uniform backdrop there is nothing to refract and the glass honestly
160
- * disappears — right for a piece of background, wrong for a control. The sign comes from the
161
- * BACKDROP, not from ink polarity: lighter over dark, darker over light. It is a minimum, not an
162
- * addition, so a body that is already dense enough is left alone.
163
+ * disappears — right for a piece of background, wrong for a control. It is a MINIMUM, not an
164
+ * addition, so a body that already stands apart is left alone.
165
+ *
166
+ * The direction is the body's own. The previous version decided "lighter over dark, darker over
167
+ * light" from the backdrop alone, and then, when the body disagreed, clamped `tintLuma` to the
168
+ * backdrop's mean — the one value that separates from nothing. Light ink over a dark backdrop puts
169
+ * the body at 0.07 against a backdrop of 0.12: already separating, downward, and the clamp pulled
170
+ * it back onto the backdrop. Measured on the default material at presence 0.08: exactly 0.0000 at
171
+ * backdrops of 0.12, 0.20 and 0.35.
172
+ *
173
+ * What the demand is DIVIDED by is deliberately the distance to the tint's pole rather than to the
174
+ * body's own tint. Dividing by the latter is arithmetically tighter and asks for far more density
175
+ * wherever the two are close — enough to drive it to the ceiling over a dark backdrop and stop the
176
+ * glass being a window at all, which check:optics says plainly. Presence is a floor on visibility,
177
+ * not a licence to go opaque.
163
178
  */
164
179
  declare function withPresence(body: GlassBody, optics: VireGlassOptics, sample: BodyBackdrop): GlassBody;
165
180
 
@@ -321,6 +336,14 @@ type GlassMorph = {
321
336
  smoothing: number;
322
337
  shape?: MorphShape;
323
338
  shape2?: MorphShape;
339
+ /**
340
+ * Any number of shapes flowing into this one. Several independent elements becoming one whole is
341
+ * the behaviour; how many of them there are is the host's business, and it used to be the
342
+ * material's — the count lived in the field NAMES, so a fourth element had nowhere to go.
343
+ *
344
+ * `shape` and `shape2` still work and come first.
345
+ */
346
+ shapes?: readonly MorphShape[];
324
347
  };
325
348
  type GlassHandle = {
326
349
  update(): void;
package/dist/dom.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { B as BackdropSample } from './adaptation-BSbbdExK.js';
2
2
  import { V as VireGlassGeometry } from './geometry-GvXtt-_S.js';
3
3
  import { V as VireGlassOptics, a as VireGlassMaterial } from './material-CnUmdKg1.js';
4
- import { T as TouchWarp, M as MorphShape, V as VireGlassAccessibility, S as ScrollEdgeStyle, G as GroupPlane } from './group-model-vYSmEVia.js';
4
+ import { T as TouchWarp, M as MorphShape, V as VireGlassAccessibility, S as ScrollEdgeStyle, G as GroupPlane } from './group-model-z4N27qsC.js';
5
5
  import { V as VireGlassAccent } from './adapters-BfeAopGO.js';
6
6
 
7
7
  /** Any `backdrop-filter` at all (e.g. `blur()`) — the fallback path's own requirement. */
@@ -67,6 +67,9 @@ type DisplacementState = {
67
67
  smoothing?: number;
68
68
  morph?: MorphShape;
69
69
  morph2?: MorphShape;
70
+ /** Any number of shapes flowing into this one. `morph`/`morph2` are the first two, kept so a
71
+ * caller that only ever had two does not have to change. */
72
+ morphs?: readonly MorphShape[];
70
73
  };
71
74
  /**
72
75
  * The raw pixel buffer, with no canvas involved — the part actually worth unit-testing.
@@ -157,9 +160,21 @@ type GlassBody = {
157
160
  declare function resolveBody(optics: VireGlassOptics, sample: BodyBackdrop, ink: number): GlassBody;
158
161
  /**
159
162
  * PRESENCE (§3): over a uniform backdrop there is nothing to refract and the glass honestly
160
- * disappears — right for a piece of background, wrong for a control. The sign comes from the
161
- * BACKDROP, not from ink polarity: lighter over dark, darker over light. It is a minimum, not an
162
- * addition, so a body that is already dense enough is left alone.
163
+ * disappears — right for a piece of background, wrong for a control. It is a MINIMUM, not an
164
+ * addition, so a body that already stands apart is left alone.
165
+ *
166
+ * The direction is the body's own. The previous version decided "lighter over dark, darker over
167
+ * light" from the backdrop alone, and then, when the body disagreed, clamped `tintLuma` to the
168
+ * backdrop's mean — the one value that separates from nothing. Light ink over a dark backdrop puts
169
+ * the body at 0.07 against a backdrop of 0.12: already separating, downward, and the clamp pulled
170
+ * it back onto the backdrop. Measured on the default material at presence 0.08: exactly 0.0000 at
171
+ * backdrops of 0.12, 0.20 and 0.35.
172
+ *
173
+ * What the demand is DIVIDED by is deliberately the distance to the tint's pole rather than to the
174
+ * body's own tint. Dividing by the latter is arithmetically tighter and asks for far more density
175
+ * wherever the two are close — enough to drive it to the ceiling over a dark backdrop and stop the
176
+ * glass being a window at all, which check:optics says plainly. Presence is a floor on visibility,
177
+ * not a licence to go opaque.
163
178
  */
164
179
  declare function withPresence(body: GlassBody, optics: VireGlassOptics, sample: BodyBackdrop): GlassBody;
165
180
 
@@ -321,6 +336,14 @@ type GlassMorph = {
321
336
  smoothing: number;
322
337
  shape?: MorphShape;
323
338
  shape2?: MorphShape;
339
+ /**
340
+ * Any number of shapes flowing into this one. Several independent elements becoming one whole is
341
+ * the behaviour; how many of them there are is the host's business, and it used to be the
342
+ * material's — the count lived in the field NAMES, so a fourth element had nowhere to go.
343
+ *
344
+ * `shape` and `shape2` still work and come first.
345
+ */
346
+ shapes?: readonly MorphShape[];
324
347
  };
325
348
  type GlassHandle = {
326
349
  update(): void;
package/dist/dom.js CHANGED
@@ -1,8 +1,8 @@
1
- import { shadowTint, NO_ACCESSIBILITY, createGroupState, createDeform, elasticAllowed, contrastRimLuma, concentricRadius, scrollEdgeStrength, scrollEdgeStyle, applyGlassScale, GLASS_SCALE_DEFAULT, applyAccessibility, applyAppear, accentTone, accentAmount, raiseIntoGlass } from './chunk-3PGYF2NK.js';
1
+ import { shadowTint, NO_ACCESSIBILITY, createGroupState, createDeform, elasticAllowed, contrastRimLuma, concentricRadius, scrollEdgeStrength, scrollEdgeStyle, applyGlassScale, GLASS_SCALE_DEFAULT, applyAccessibility, applyAppear, accentTone, accentAmount, raiseIntoGlass } from './chunk-CTTZNZFV.js';
2
2
  import { ambientFrom, shouldInkBeLight, INK_LIGHT, INK_DARK } from './chunk-JNRZNMJJ.js';
3
- import { touchWarp, sceneDistance, sceneGradient, sdfRoundedRect, VIREGLASS_MATERIAL, REST_LIGHT, resolveOptics, refractionStrength } from './chunk-IQOBZGCF.js';
4
- import { shadowOpacity, bevelDp, thicknessDp, rimDp, shadowOpacityFrom, shadowReachDp, halfMinDp, MAX_STRETCH, roundedRectGeometry } from './chunk-P2ZOODXD.js';
5
- import { LENS, BODY, RIM, TOUCH, SHADOW, SHADOW_GAP, SCROLL_EDGE, BEVEL, SPECTRAL, DISPERSION } from './chunk-TTOKBARR.js';
3
+ import { touchWarp, sceneDistance, sceneGradient, sdfRoundedRect, VIREGLASS_MATERIAL, REST_LIGHT, resolveOptics, refractionStrength } from './chunk-H5QTBF67.js';
4
+ import { shadowOpacity, bevelDp, thicknessDp, rimDp, shadowOpacityFrom, shadowReachDp, halfMinDp, MAX_STRETCH, roundedRectGeometry } from './chunk-SLRPHDV7.js';
5
+ import { LENS, BODY, RIM, TOUCH, SHADOW, SHADOW_GAP, SCROLL_EDGE, BEVEL, SPECTRAL, DISPERSION } from './chunk-MMPK6DCK.js';
6
6
 
7
7
  // src/dom/displacement.ts
8
8
  var SCALE_GAIN = 0.75;
@@ -29,6 +29,7 @@ function renderDisplacementPixels(optics, geometry, dpr, state = {}) {
29
29
  const rawScale = optics.refraction * thicknessDp(geometry, optics) * SCALE_GAIN;
30
30
  const scale = Math.min(rawScale, MAX_SCALE);
31
31
  const smoothing = state.smoothing ?? 0;
32
+ const shapes = [state.morph, state.morph2, ...state.morphs ?? []];
32
33
  const data = new Uint8ClampedArray(width * height * 4);
33
34
  const halfW = geometry.width / 2;
34
35
  const halfH = geometry.height / 2;
@@ -38,7 +39,7 @@ function renderDisplacementPixels(optics, geometry, dpr, state = {}) {
38
39
  const x = (px + 0.5) / density - halfW;
39
40
  const i = (py * width + px) * 4;
40
41
  const [wx, wy] = state.touch ? touchWarp(x, y, state.touch) : [x, y];
41
- const sd = sceneDistance(wx, wy, geometry.width, geometry.height, geometry.cornerRadius, smoothing, state.morph, state.morph2);
42
+ const sd = sceneDistance(wx, wy, geometry.width, geometry.height, geometry.cornerRadius, smoothing, ...shapes);
42
43
  const t = Math.min(Math.max((sd + bevel) / bevel, 0), 1);
43
44
  if (t <= 0 || scale <= 0) {
44
45
  data[i] = 128;
@@ -47,7 +48,7 @@ function renderDisplacementPixels(optics, geometry, dpr, state = {}) {
47
48
  data[i + 3] = 255;
48
49
  continue;
49
50
  }
50
- const [gx, gy] = sceneGradient(wx, wy, geometry.width, geometry.height, geometry.cornerRadius, smoothing, state.morph, state.morph2);
51
+ const [gx, gy] = sceneGradient(wx, wy, geometry.width, geometry.height, geometry.cornerRadius, smoothing, ...shapes);
51
52
  const magnitude = bevelProfile(t) * (scale / 2);
52
53
  const dx = -gx * magnitude;
53
54
  const dy = -gy * magnitude;
@@ -310,10 +311,18 @@ function sampleColorAt(el, x, y) {
310
311
  }
311
312
  const composited = compositeLayers(layers);
312
313
  if (composited) return composited;
313
- const topmost = behind[0];
314
- if (!topmost) return null;
315
- return parseColor(getComputedStyle(topmost).color);
314
+ return canvasColor(el);
316
315
  }
316
+ function canvasColor(el) {
317
+ const doc = el.ownerDocument;
318
+ for (const node of [doc.documentElement, doc.body]) {
319
+ if (!node) continue;
320
+ const bg = parseColor(getComputedStyle(node).backgroundColor);
321
+ if (bg && bg.a > 1e-3) return bg.a >= 0.999 ? bg : compositeLayers([bg, WHITE]);
322
+ }
323
+ return WHITE;
324
+ }
325
+ var WHITE = { r: 255, g: 255, b: 255, a: 1 };
317
326
  function probeBackdrop(el, opts = {}) {
318
327
  const cols = opts.cols ?? GRID_COLS;
319
328
  const rows = opts.rows ?? GRID_ROWS;
@@ -416,15 +425,17 @@ function resolveBody(optics, sample, ink) {
416
425
  function withPresence(body, optics, sample) {
417
426
  if (optics.presence <= 0) return body;
418
427
  const mean = clamp(sample.luma, 0, 1);
419
- const lighter = mean < 0.5;
420
- const target = lighter ? Math.min(mean + optics.presence, 1) : Math.max(mean - optics.presence, 0);
421
- const separation = Math.abs(target - mean);
428
+ const above = Math.abs(body.tintLuma - mean) > 1e-4 ? body.tintLuma > mean : mean < 0.5;
429
+ const pole = above ? TINT_LIGHT : TINT_DARK;
430
+ const separation = Math.min(optics.presence, Math.abs(pole - mean));
422
431
  const reached = Math.abs(body.tintLuma - mean) * body.density;
423
432
  if (reached >= separation) return body;
424
- const needed = separation / Math.max(Math.abs((lighter ? TINT_LIGHT : TINT_DARK) - mean), 1e-4);
433
+ const needed = separation / Math.max(Math.abs(pole - mean), 1e-4);
425
434
  return {
426
- density: Math.max(body.density, Math.min(needed, MAX_DEMAND)),
427
- tintLuma: lighter ? Math.max(body.tintLuma, mean) : Math.min(body.tintLuma, mean)
435
+ density: Math.max(body.density, Math.min(needed, BODY.presenceDemand)),
436
+ // Left where the tint law put it, unless it landed exactly on the backdrop — which is the
437
+ // case that used to be created here rather than avoided.
438
+ tintLuma: Math.abs(body.tintLuma - mean) > 1e-4 ? body.tintLuma : pole
428
439
  };
429
440
  }
430
441
 
@@ -659,15 +670,22 @@ function waveImpulse(g) {
659
670
  return halfMinDp(g) * MAX_STRETCH * WAVE_OF_TRAVEL;
660
671
  }
661
672
  var STYLE_ATTR = "data-vireglass-styles";
662
- function ensureRimStyles() {
663
- if (document.querySelector(`style[${STYLE_ATTR}]`)) return;
673
+ function hostFor(el) {
674
+ const root = el.getRootNode();
675
+ return root instanceof ShadowRoot ? root : el.ownerDocument ?? document;
676
+ }
677
+ function styleParent(host) {
678
+ return host instanceof ShadowRoot ? host : host.head;
679
+ }
680
+ function ensureRimStyles(host) {
681
+ if (host.querySelector(`style[${STYLE_ATTR}]`)) return;
664
682
  const style = document.createElement("style");
665
683
  style.setAttribute(STYLE_ATTR, "");
666
684
  style.textContent = `[${GLASS_ATTR}]::after{content:'';position:absolute;inset:0;border-radius:inherit;pointer-events:none;padding:var(--vireglass-rim-width,1px);background:var(--vireglass-rim,none);-webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);mask-composite:exclude}[${GLASS_ATTR}]::before{content:'';position:absolute;inset:0;border-radius:inherit;pointer-events:none;background:var(--vireglass-glow,none);opacity:var(--vireglass-glow-opacity,0)}`;
667
- document.head.appendChild(style);
685
+ styleParent(host).appendChild(style);
668
686
  }
669
- function getFilterHost() {
670
- const existing = document.querySelector(`svg[${FILTER_HOST_ATTR}]`);
687
+ function getFilterHost(host) {
688
+ const existing = host.querySelector(`svg[${FILTER_HOST_ATTR}]`);
671
689
  if (existing) return existing;
672
690
  const svg = document.createElementNS(SVG_NS, "svg");
673
691
  svg.setAttribute(FILTER_HOST_ATTR, "");
@@ -677,7 +695,7 @@ function getFilterHost() {
677
695
  svg.style.position = "absolute";
678
696
  svg.style.pointerEvents = "none";
679
697
  svg.appendChild(document.createElementNS(SVG_NS, "defs"));
680
- document.body.appendChild(svg);
698
+ (host instanceof ShadowRoot ? host : host.body).appendChild(svg);
681
699
  return svg;
682
700
  }
683
701
  function buildFilterElement(id, mapUrl, optics, scale, spectral) {
@@ -715,6 +733,7 @@ function buildFilterElement(id, mapUrl, optics, scale, spectral) {
715
733
  pass.setAttribute("xChannelSelector", "R");
716
734
  pass.setAttribute("yChannelSelector", "G");
717
735
  pass.setAttribute("result", `${name}raw`);
736
+ pass.setAttribute("data-vireglass-ratio", String(scale > 0 ? chScale / scale : 1));
718
737
  const only = document.createElementNS(SVG_NS, "feColorMatrix");
719
738
  only.setAttribute("in", `${name}raw`);
720
739
  only.setAttribute("type", "matrix");
@@ -821,7 +840,8 @@ function attachGlass(el, opts = {}) {
821
840
  apply();
822
841
  });
823
842
  warnIfNested(el);
824
- ensureRimStyles();
843
+ const host = hostFor(el);
844
+ ensureRimStyles(host);
825
845
  el.setAttribute(GLASS_ATTR, "");
826
846
  const positionWasStatic = getComputedStyle(el).position === "static";
827
847
  if (positionWasStatic) el.style.setProperty("position", "relative");
@@ -833,7 +853,7 @@ function attachGlass(el, opts = {}) {
833
853
  }
834
854
  function morphState() {
835
855
  if (!morph) return {};
836
- return { smoothing: morph.smoothing, morph: morph.shape, morph2: morph.shape2 };
856
+ return { smoothing: morph.smoothing, morph: morph.shape, morph2: morph.shape2, morphs: morph.shapes };
837
857
  }
838
858
  function morphKey() {
839
859
  if (!morph) return "0";
@@ -849,7 +869,7 @@ function attachGlass(el, opts = {}) {
849
869
  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}`;
850
870
  if (key !== mapKey || !filterEl) {
851
871
  const map = cached(`d|${key}`, () => buildDisplacementMap(opticsNow, geometry, devicePixelRatio(), morphState()));
852
- const defs = getFilterHost().querySelector("defs");
872
+ const defs = getFilterHost(host).querySelector("defs");
853
873
  const hue = hasSpectralEdge(opticsNow) ? cached(`h|${key}`, () => buildSpectralMap(opticsNow, geometry, devicePixelRatio())) : null;
854
874
  const next = buildFilterElement(id, map.url, opticsNow, map.scale, hue);
855
875
  if (filterEl?.parentNode) defs.replaceChild(next, filterEl);
@@ -915,9 +935,11 @@ function attachGlass(el, opts = {}) {
915
935
  });
916
936
  if (filterEl) {
917
937
  const feImage = filterEl.querySelector("feImage");
918
- const feDisplace = filterEl.querySelector("feDisplacementMap");
919
938
  feImage?.setAttribute("href", map.url);
920
- feDisplace?.setAttribute("scale", String(map.scale));
939
+ for (const pass of filterEl.querySelectorAll("feDisplacementMap")) {
940
+ const ratio = Number(pass.getAttribute("data-vireglass-ratio") ?? 1);
941
+ pass.setAttribute("scale", String(map.scale * (Number.isFinite(ratio) ? ratio : 1)));
942
+ }
921
943
  }
922
944
  mapKey = "";
923
945
  paintGlow(sampleOut.touchX, sampleOut.touchY, sampleOut.active * base.edgeLight, geometry);