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.cjs CHANGED
@@ -60,7 +60,14 @@ var BODY = {
60
60
  darkSideFrom: 0.42,
61
61
  darkSideTo: 0.58,
62
62
  /** Ceiling on any single demand, so one requirement cannot make the glass opaque on its own. */
63
- maxDemand: 0.92
63
+ maxDemand: 0.92,
64
+ /**
65
+ * And a tighter one for presence specifically. It is a FLOOR on visibility, not a licence to go
66
+ * opaque: wherever the body's tint sits close to the backdrop, the density that would deliver the
67
+ * separation is the ceiling, and glass at `maxDemand` has stopped being a window. Better to
68
+ * separate as far as transparency allows and no further. UNMEASURED.
69
+ */
70
+ presenceDemand: 0.5
64
71
  };
65
72
  var SHADOW = {
66
73
  /**
@@ -438,36 +445,34 @@ function smin(a, b, k) {
438
445
  const h = Math.min(Math.max(0.5 + 0.5 * (b - a) / k, 0), 1);
439
446
  return b + (a - b) * h - k * h * (1 - h);
440
447
  }
441
- function sceneDistance(x, y, w, h, r, smoothing = 0, b, c) {
442
- const a = sdfRoundedRect(x, y, w, h, r);
443
- if (smoothing <= 0 || !b || b.width <= 0) return a;
444
- const db = sdfRoundedRect(x - b.offsetX, y - b.offsetY, b.width, b.height, b.cornerRadius);
445
- const ab = smin(a, db, smoothing);
446
- if (!c || c.width <= 0) return ab;
447
- const dc = sdfRoundedRect(x - c.offsetX, y - c.offsetY, c.width, c.height, c.cornerRadius);
448
- return smin(ab, dc, smoothing);
449
- }
450
- function sceneGradient(x, y, w, h, r, smoothing = 0, b, c) {
451
- const na = sdfRoundedRectGradient(x, y, w, h, r);
452
- if (smoothing <= 0 || !b || b.width <= 0) return na;
453
- const bx = x - b.offsetX;
454
- const by = y - b.offsetY;
455
- const da = sdfRoundedRect(x, y, w, h, r);
456
- const db = sdfRoundedRect(bx, by, b.width, b.height, b.cornerRadius);
457
- const hb = Math.min(Math.max(0.5 + 0.5 * (db - da) / smoothing, 0), 1);
458
- const nb = sdfRoundedRectGradient(bx, by, b.width, b.height, b.cornerRadius);
459
- let gx = nb[0] + (na[0] - nb[0]) * hb;
460
- let gy = nb[1] + (na[1] - nb[1]) * hb;
461
- if (c && c.width > 0) {
462
- const cx = x - c.offsetX;
463
- const cy = y - c.offsetY;
464
- const dab = smin(da, db, smoothing);
465
- const dc = sdfRoundedRect(cx, cy, c.width, c.height, c.cornerRadius);
466
- const hc = Math.min(Math.max(0.5 + 0.5 * (dc - dab) / smoothing, 0), 1);
467
- const nc = sdfRoundedRectGradient(cx, cy, c.width, c.height, c.cornerRadius);
468
- gx = nc[0] + (gx - nc[0]) * hc;
469
- gy = nc[1] + (gy - nc[1]) * hc;
448
+ function sceneDistance(x, y, w, h, r, smoothing = 0, ...shapes) {
449
+ let d = sdfRoundedRect(x, y, w, h, r);
450
+ if (smoothing <= 0) return d;
451
+ for (const s of shapes) {
452
+ if (!s || s.width <= 0) continue;
453
+ d = smin(d, sdfRoundedRect(x - s.offsetX, y - s.offsetY, s.width, s.height, s.cornerRadius), smoothing);
470
454
  }
455
+ return d;
456
+ }
457
+ function sceneGradient(x, y, w, h, r, smoothing = 0, ...shapes) {
458
+ let d = sdfRoundedRect(x, y, w, h, r);
459
+ let [gx, gy] = sdfRoundedRectGradient(x, y, w, h, r);
460
+ let blended = false;
461
+ if (smoothing > 0) {
462
+ for (const s of shapes) {
463
+ if (!s || s.width <= 0) continue;
464
+ const sx = x - s.offsetX;
465
+ const sy = y - s.offsetY;
466
+ const ds = sdfRoundedRect(sx, sy, s.width, s.height, s.cornerRadius);
467
+ const weight = Math.min(Math.max(0.5 + 0.5 * (ds - d) / smoothing, 0), 1);
468
+ const [nx, ny] = sdfRoundedRectGradient(sx, sy, s.width, s.height, s.cornerRadius);
469
+ gx = nx + (gx - nx) * weight;
470
+ gy = ny + (gy - ny) * weight;
471
+ d = smin(d, ds, smoothing);
472
+ blended = true;
473
+ }
474
+ }
475
+ if (!blended) return [gx, gy];
471
476
  gx += EPSILON;
472
477
  gy += EPSILON;
473
478
  const len = Math.hypot(gx, gy) || 1;
@@ -523,6 +528,7 @@ function renderDisplacementPixels(optics, geometry, dpr, state = {}) {
523
528
  const rawScale = optics.refraction * thicknessDp(geometry, optics) * SCALE_GAIN;
524
529
  const scale = Math.min(rawScale, MAX_SCALE);
525
530
  const smoothing = state.smoothing ?? 0;
531
+ const shapes = [state.morph, state.morph2, ...state.morphs ?? []];
526
532
  const data = new Uint8ClampedArray(width * height * 4);
527
533
  const halfW = geometry.width / 2;
528
534
  const halfH = geometry.height / 2;
@@ -532,7 +538,7 @@ function renderDisplacementPixels(optics, geometry, dpr, state = {}) {
532
538
  const x = (px + 0.5) / density - halfW;
533
539
  const i = (py * width + px) * 4;
534
540
  const [wx, wy] = state.touch ? touchWarp(x, y, state.touch) : [x, y];
535
- const sd = sceneDistance(wx, wy, geometry.width, geometry.height, geometry.cornerRadius, smoothing, state.morph, state.morph2);
541
+ const sd = sceneDistance(wx, wy, geometry.width, geometry.height, geometry.cornerRadius, smoothing, ...shapes);
536
542
  const t = Math.min(Math.max((sd + bevel) / bevel, 0), 1);
537
543
  if (t <= 0 || scale <= 0) {
538
544
  data[i] = 128;
@@ -541,7 +547,7 @@ function renderDisplacementPixels(optics, geometry, dpr, state = {}) {
541
547
  data[i + 3] = 255;
542
548
  continue;
543
549
  }
544
- const [gx, gy] = sceneGradient(wx, wy, geometry.width, geometry.height, geometry.cornerRadius, smoothing, state.morph, state.morph2);
550
+ const [gx, gy] = sceneGradient(wx, wy, geometry.width, geometry.height, geometry.cornerRadius, smoothing, ...shapes);
545
551
  const magnitude = bevelProfile(t) * (scale / 2);
546
552
  const dx = -gx * magnitude;
547
553
  const dy = -gy * magnitude;
@@ -930,10 +936,18 @@ function sampleColorAt(el, x, y) {
930
936
  }
931
937
  const composited = compositeLayers(layers);
932
938
  if (composited) return composited;
933
- const topmost = behind[0];
934
- if (!topmost) return null;
935
- return parseColor(getComputedStyle(topmost).color);
939
+ return canvasColor(el);
940
+ }
941
+ function canvasColor(el) {
942
+ const doc = el.ownerDocument;
943
+ for (const node of [doc.documentElement, doc.body]) {
944
+ if (!node) continue;
945
+ const bg = parseColor(getComputedStyle(node).backgroundColor);
946
+ if (bg && bg.a > 1e-3) return bg.a >= 0.999 ? bg : compositeLayers([bg, WHITE]);
947
+ }
948
+ return WHITE;
936
949
  }
950
+ var WHITE = { r: 255, g: 255, b: 255, a: 1 };
937
951
  function probeBackdrop(el, opts = {}) {
938
952
  const cols = opts.cols ?? GRID_COLS;
939
953
  const rows = opts.rows ?? GRID_ROWS;
@@ -1047,15 +1061,17 @@ function resolveBody(optics, sample, ink) {
1047
1061
  function withPresence(body, optics, sample) {
1048
1062
  if (optics.presence <= 0) return body;
1049
1063
  const mean = clamp3(sample.luma, 0, 1);
1050
- const lighter = mean < 0.5;
1051
- const target = lighter ? Math.min(mean + optics.presence, 1) : Math.max(mean - optics.presence, 0);
1052
- const separation = Math.abs(target - mean);
1064
+ const above = Math.abs(body.tintLuma - mean) > 1e-4 ? body.tintLuma > mean : mean < 0.5;
1065
+ const pole = above ? TINT_LIGHT : TINT_DARK;
1066
+ const separation = Math.min(optics.presence, Math.abs(pole - mean));
1053
1067
  const reached = Math.abs(body.tintLuma - mean) * body.density;
1054
1068
  if (reached >= separation) return body;
1055
- const needed = separation / Math.max(Math.abs((lighter ? TINT_LIGHT : TINT_DARK) - mean), 1e-4);
1069
+ const needed = separation / Math.max(Math.abs(pole - mean), 1e-4);
1056
1070
  return {
1057
- density: Math.max(body.density, Math.min(needed, MAX_DEMAND)),
1058
- tintLuma: lighter ? Math.max(body.tintLuma, mean) : Math.min(body.tintLuma, mean)
1071
+ density: Math.max(body.density, Math.min(needed, BODY.presenceDemand)),
1072
+ // Left where the tint law put it, unless it landed exactly on the backdrop — which is the
1073
+ // case that used to be created here rather than avoided.
1074
+ tintLuma: Math.abs(body.tintLuma - mean) > 1e-4 ? body.tintLuma : pole
1059
1075
  };
1060
1076
  }
1061
1077
 
@@ -1603,15 +1619,22 @@ function waveImpulse(g) {
1603
1619
  return halfMinDp(g) * MAX_STRETCH * WAVE_OF_TRAVEL;
1604
1620
  }
1605
1621
  var STYLE_ATTR = "data-vireglass-styles";
1606
- function ensureRimStyles() {
1607
- if (document.querySelector(`style[${STYLE_ATTR}]`)) return;
1622
+ function hostFor(el) {
1623
+ const root = el.getRootNode();
1624
+ return root instanceof ShadowRoot ? root : el.ownerDocument ?? document;
1625
+ }
1626
+ function styleParent(host) {
1627
+ return host instanceof ShadowRoot ? host : host.head;
1628
+ }
1629
+ function ensureRimStyles(host) {
1630
+ if (host.querySelector(`style[${STYLE_ATTR}]`)) return;
1608
1631
  const style = document.createElement("style");
1609
1632
  style.setAttribute(STYLE_ATTR, "");
1610
1633
  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)}`;
1611
- document.head.appendChild(style);
1634
+ styleParent(host).appendChild(style);
1612
1635
  }
1613
- function getFilterHost() {
1614
- const existing = document.querySelector(`svg[${FILTER_HOST_ATTR}]`);
1636
+ function getFilterHost(host) {
1637
+ const existing = host.querySelector(`svg[${FILTER_HOST_ATTR}]`);
1615
1638
  if (existing) return existing;
1616
1639
  const svg = document.createElementNS(SVG_NS, "svg");
1617
1640
  svg.setAttribute(FILTER_HOST_ATTR, "");
@@ -1621,7 +1644,7 @@ function getFilterHost() {
1621
1644
  svg.style.position = "absolute";
1622
1645
  svg.style.pointerEvents = "none";
1623
1646
  svg.appendChild(document.createElementNS(SVG_NS, "defs"));
1624
- document.body.appendChild(svg);
1647
+ (host instanceof ShadowRoot ? host : host.body).appendChild(svg);
1625
1648
  return svg;
1626
1649
  }
1627
1650
  function buildFilterElement(id, mapUrl, optics, scale, spectral) {
@@ -1659,6 +1682,7 @@ function buildFilterElement(id, mapUrl, optics, scale, spectral) {
1659
1682
  pass.setAttribute("xChannelSelector", "R");
1660
1683
  pass.setAttribute("yChannelSelector", "G");
1661
1684
  pass.setAttribute("result", `${name}raw`);
1685
+ pass.setAttribute("data-vireglass-ratio", String(scale > 0 ? chScale / scale : 1));
1662
1686
  const only = document.createElementNS(SVG_NS, "feColorMatrix");
1663
1687
  only.setAttribute("in", `${name}raw`);
1664
1688
  only.setAttribute("type", "matrix");
@@ -1765,7 +1789,8 @@ function attachGlass(el, opts = {}) {
1765
1789
  apply();
1766
1790
  });
1767
1791
  warnIfNested(el);
1768
- ensureRimStyles();
1792
+ const host = hostFor(el);
1793
+ ensureRimStyles(host);
1769
1794
  el.setAttribute(GLASS_ATTR, "");
1770
1795
  const positionWasStatic = getComputedStyle(el).position === "static";
1771
1796
  if (positionWasStatic) el.style.setProperty("position", "relative");
@@ -1777,7 +1802,7 @@ function attachGlass(el, opts = {}) {
1777
1802
  }
1778
1803
  function morphState() {
1779
1804
  if (!morph) return {};
1780
- return { smoothing: morph.smoothing, morph: morph.shape, morph2: morph.shape2 };
1805
+ return { smoothing: morph.smoothing, morph: morph.shape, morph2: morph.shape2, morphs: morph.shapes };
1781
1806
  }
1782
1807
  function morphKey() {
1783
1808
  if (!morph) return "0";
@@ -1793,7 +1818,7 @@ function attachGlass(el, opts = {}) {
1793
1818
  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}`;
1794
1819
  if (key !== mapKey || !filterEl) {
1795
1820
  const map = cached(`d|${key}`, () => buildDisplacementMap(opticsNow, geometry, devicePixelRatio(), morphState()));
1796
- const defs = getFilterHost().querySelector("defs");
1821
+ const defs = getFilterHost(host).querySelector("defs");
1797
1822
  const hue = hasSpectralEdge(opticsNow) ? cached(`h|${key}`, () => buildSpectralMap(opticsNow, geometry, devicePixelRatio())) : null;
1798
1823
  const next = buildFilterElement(id, map.url, opticsNow, map.scale, hue);
1799
1824
  if (filterEl?.parentNode) defs.replaceChild(next, filterEl);
@@ -1859,9 +1884,11 @@ function attachGlass(el, opts = {}) {
1859
1884
  });
1860
1885
  if (filterEl) {
1861
1886
  const feImage = filterEl.querySelector("feImage");
1862
- const feDisplace = filterEl.querySelector("feDisplacementMap");
1863
1887
  feImage?.setAttribute("href", map.url);
1864
- feDisplace?.setAttribute("scale", String(map.scale));
1888
+ for (const pass of filterEl.querySelectorAll("feDisplacementMap")) {
1889
+ const ratio = Number(pass.getAttribute("data-vireglass-ratio") ?? 1);
1890
+ pass.setAttribute("scale", String(map.scale * (Number.isFinite(ratio) ? ratio : 1)));
1891
+ }
1865
1892
  }
1866
1893
  mapKey = "";
1867
1894
  paintGlow(sampleOut.touchX, sampleOut.touchY, sampleOut.active * base.edgeLight, geometry);