vireglass 2.3.0 → 3.0.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 (61) hide show
  1. package/README.md +37 -6
  2. package/dist/{adapters-DugC6Qri.d.cts → adapters--bXcGvhW.d.cts} +5 -3
  3. package/dist/{adapters-BfeAopGO.d.ts → adapters-BlMhc6pI.d.ts} +5 -3
  4. package/dist/{chunk-MMPK6DCK.js → chunk-CPSIY2DZ.js} +58 -8
  5. package/dist/chunk-CPSIY2DZ.js.map +1 -0
  6. package/dist/{chunk-PM2SWCSF.js → chunk-RWUR3OV6.js} +26 -6
  7. package/dist/chunk-RWUR3OV6.js.map +1 -0
  8. package/dist/{chunk-CTTZNZFV.js → chunk-VLDD655U.js} +3 -3
  9. package/dist/{chunk-CTTZNZFV.js.map → chunk-VLDD655U.js.map} +1 -1
  10. package/dist/{chunk-H5QTBF67.js → chunk-YAN2OK4H.js} +31 -38
  11. package/dist/chunk-YAN2OK4H.js.map +1 -0
  12. package/dist/{chunk-SLRPHDV7.js → chunk-Z4FKYY47.js} +3 -3
  13. package/dist/{chunk-SLRPHDV7.js.map → chunk-Z4FKYY47.js.map} +1 -1
  14. package/dist/dom.cjs +180 -55
  15. package/dist/dom.cjs.map +1 -1
  16. package/dist/dom.d.cts +72 -25
  17. package/dist/dom.d.ts +72 -25
  18. package/dist/dom.js +116 -54
  19. package/dist/dom.js.map +1 -1
  20. package/dist/{geometry-RhKd7CM5.d.cts → geometry-ip9WSlZv.d.cts} +1 -1
  21. package/dist/{geometry-GvXtt-_S.d.ts → geometry-pabkYV7w.d.ts} +1 -1
  22. package/dist/{group-model-z4N27qsC.d.ts → group-model-CQiI5cCe.d.ts} +1 -1
  23. package/dist/{group-model-DwtixGhG.d.cts → group-model-DX2_W9J_.d.cts} +1 -1
  24. package/dist/index.cjs +114 -41
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +13 -8
  27. package/dist/index.d.ts +13 -8
  28. package/dist/index.js +5 -5
  29. package/dist/law.cjs +56 -6
  30. package/dist/law.cjs.map +1 -1
  31. package/dist/law.d.cts +54 -4
  32. package/dist/law.d.ts +54 -4
  33. package/dist/law.js +1 -1
  34. package/dist/{material-CnUmdKg1.d.ts → material-D9m1EN47.d.cts} +9 -6
  35. package/dist/{material-CnUmdKg1.d.cts → material-D9m1EN47.d.ts} +9 -6
  36. package/dist/react.d.cts +1 -1
  37. package/dist/react.d.ts +1 -1
  38. package/dist/reference.d.cts +2 -2
  39. package/dist/reference.d.ts +2 -2
  40. package/dist/reference.js +2 -2
  41. package/dist/web/index.cjs +112 -40
  42. package/dist/web/index.cjs.map +1 -1
  43. package/dist/web/index.d.cts +3 -3
  44. package/dist/web/index.d.ts +3 -3
  45. package/dist/web/index.js +4 -4
  46. package/docs/dom.md +49 -3
  47. package/docs/law.md +1 -1
  48. package/package.json +1 -1
  49. package/src/adapters.ts +3 -1
  50. package/src/dom/body.ts +93 -39
  51. package/src/dom/index.ts +49 -20
  52. package/src/dom/probe.ts +1 -1
  53. package/src/dom/rim.ts +63 -28
  54. package/src/dom/style-hold.ts +8 -0
  55. package/src/law.ts +54 -4
  56. package/src/lens-shader.ts +26 -33
  57. package/src/material.ts +10 -7
  58. package/src/targets/glsl.ts +27 -2
  59. package/dist/chunk-H5QTBF67.js.map +0 -1
  60. package/dist/chunk-MMPK6DCK.js.map +0 -1
  61. package/dist/chunk-PM2SWCSF.js.map +0 -1
package/src/dom/rim.ts CHANGED
@@ -1,15 +1,20 @@
1
- // Rim light (docs/reference.md §2): a hairline along the silhouette — "not a band and not a
2
- // bevel" — carrying two opposing arcs, the rest of it outlined by a thin dark line, and its colour
3
- // taken from the environment.
1
+ // Rim light (docs/reference.md §2): a hairline along the whole silhouette — "not a band and not a
2
+ // bevel" — brighter toward the key light, with a second, weaker arc opposite it, outlined by a thin
3
+ // dark line, its colour taken from the environment and its strength from how light that is.
4
4
  //
5
- // Every number here is the lens shader's own. The key-light lobe is `lens-shader.ts` line 375
6
- // verbatim (exponent 3, opposing arc at 0.45), and the dark edge is its line 528 (a 0.22
7
- // darkening at the silhouette). The only thing that differs is the primitive: a conic gradient
8
- // sampled at fixed angles instead of a per-pixel normal.
5
+ // Every number here is the lens shader's own, read from the law. The only thing that differs is
6
+ // the primitive: a conic gradient sampled at fixed angles instead of a per-pixel normal.
9
7
  import { RIM } from '../law';
10
8
  import type { VireGlassOptics } from '../material';
11
9
 
12
- const { lobeExponent: LOBE_EXPONENT, opposingArc: OPPOSING_ARC, darkEdge: DARK_EDGE } = RIM;
10
+ const {
11
+ lobeExponent: LOBE_EXPONENT,
12
+ opposingArc: OPPOSING_ARC,
13
+ darkEdge: DARK_EDGE,
14
+ contour: CONTOUR,
15
+ whiteShare: WHITE_SHARE,
16
+ reflectanceRef: REFLECTANCE_REF,
17
+ } = RIM;
13
18
 
14
19
  /** One stop every 7.5°. Fine enough that a cubic lobe reads as a smooth arc rather than a fan. */
15
20
  const STOPS = 48;
@@ -39,13 +44,45 @@ const to255 = (v: number) => Math.round(clamp01(v) * 255);
39
44
 
40
45
  export type RimAmbient = readonly [number, number, number];
41
46
 
47
+ const mixTo = (a: number, b: number, t: number) => a + (b - a) * t;
48
+
49
+ function smoothstep(edge0: number, edge1: number, x: number): number {
50
+ const t = clamp01((x - edge0) / (edge1 - edge0));
51
+ return t * t * (3 - 2 * t);
52
+ }
53
+
54
+ /** Lightness of an ambient colour, the weights the lens uses. */
55
+ export function ambientLuma(ambient: RimAmbient): number {
56
+ return 0.2126 * ambient[0] + 0.7152 * ambient[1] + 0.0722 * ambient[2];
57
+ }
58
+
59
+ /** How strongly the line is drawn over a surround of this lightness (§2, §6). */
60
+ export function rimStrength(luma: number): number {
61
+ return mixTo(RIM.strengthDark, RIM.strengthLight, smoothstep(RIM.ambientFrom, RIM.ambientTo, luma));
62
+ }
63
+
42
64
  /**
43
- * The gradient for the hairline. Colour comes from the environment — "over a yellow flower the rim
44
- * is yellow, over sky it is blue" (§2) — lifted toward white where the key light lands. Away from
45
- * both arcs only the dark edge remains.
65
+ * The line's opacity where the rim's normal sits `deltaDeg` from the light: a floor all the way
66
+ * round and the key-light lobe on top of it, all of it scaled by the medium's reflectance up to
67
+ * the reference material's.
68
+ */
69
+ export function rimLineAlpha(deltaDeg: number, optics: VireGlassOptics, luma: number): number {
70
+ const reflect = Math.min(optics.fresnel / REFLECTANCE_REF, 1);
71
+ const damping = optics.fresnel > 0 ? optics.specular / optics.fresnel : 0;
72
+ const lobe = rimLobe(deltaDeg, damping);
73
+ return clamp01(rimStrength(luma) * reflect * (CONTOUR + (1 - CONTOUR) * lobe));
74
+ }
75
+
76
+ /**
77
+ * The gradient for the hairline: a light line round the whole contour, brighter toward the key
78
+ * light, drawn over the dark edge. Its colour comes from the environment — "over a yellow flower
79
+ * the rim is yellow, over sky it is blue" (§2) — lifted toward white, and its strength follows how
80
+ * light the environment is.
46
81
  *
47
82
  * The opposing arc carries an iridescent fringe (§2), so `iridescence` pulls its channels apart
48
83
  * rather than lifting them together.
84
+ *
85
+ * Two layers, the line first: `--vireglass-rim` is a background list and paints as one.
49
86
  */
50
87
  export function rimGradientCss(
51
88
  optics: VireGlassOptics,
@@ -56,31 +93,29 @@ export function rimGradientCss(
56
93
  const present = clamp01(appear);
57
94
  const start = lightConicAngle(light);
58
95
  const [ar, ag, ab] = ambient;
59
- const stops: string[] = [];
96
+ const luma = ambientLuma(ambient);
97
+ const line: string[] = [];
60
98
 
61
99
  for (let i = 0; i <= STOPS; i += 1) {
62
100
  const angle = (i / STOPS) * 360;
63
- const key = rimLobe(angle, optics.specular);
64
101
  const facing = Math.cos((angle * Math.PI) / 180);
65
102
  // The far arc is where dispersion shows as a fringe; the near one is a clean specular.
66
103
  const fringe = facing < 0 ? optics.iridescence * OPPOSING_ARC : 0;
67
- const lift = clamp01(key * optics.fresnel);
68
-
69
- const r = ar + (1 - ar) * clamp01(lift + fringe);
70
- const g = ag + (1 - ag) * lift;
71
- const b = ab + (1 - ab) * clamp01(lift - fringe);
72
-
73
- // Scaled, not floored: the dark edge is the one part of the rim that survives facing away from
74
- // the light, so leaving it unscaled leaves a hairline drawn around an element that is not there.
75
- const alpha = clamp01(DARK_EDGE + lift * (1 - DARK_EDGE)) * present;
76
- // Below the arcs the line is the dark edge itself, not a dim copy of the environment.
77
- const colour = lift > 0.001
78
- ? `rgba(${to255(r)},${to255(g)},${to255(b)},${alpha.toFixed(3)})`
79
- : `rgba(0,0,0,${alpha.toFixed(3)})`;
80
- stops.push(`${colour} ${angle.toFixed(1)}deg`);
104
+
105
+ const r = ar + (1 - ar) * clamp01(WHITE_SHARE + fringe);
106
+ const g = ag + (1 - ag) * WHITE_SHARE;
107
+ const b = ab + (1 - ab) * clamp01(WHITE_SHARE - fringe);
108
+ const alpha = rimLineAlpha(angle, optics, luma) * present;
109
+ line.push(`rgba(${to255(r)},${to255(g)},${to255(b)},${alpha.toFixed(3)}) ${angle.toFixed(1)}deg`);
81
110
  }
82
111
 
83
- return `conic-gradient(from ${start.toFixed(1)}deg, ${stops.join(', ')})`;
112
+ // Scaled by presence, not floored: the dark edge would otherwise outline an element that is not
113
+ // there yet.
114
+ const dark = `rgba(0,0,0,${(DARK_EDGE * present).toFixed(3)})`;
115
+ return (
116
+ `conic-gradient(from ${start.toFixed(1)}deg, ${line.join(', ')}), ` +
117
+ `conic-gradient(${dark} 0deg, ${dark} 360deg)`
118
+ );
84
119
  }
85
120
 
86
121
  export const RIM_WIDTH_PX = RIM.widthPx;
@@ -0,0 +1,8 @@
1
+ /** The inline value an element had for a property before the glass took it over, as a way back. */
2
+ export function holdProperty(style: Pick<CSSStyleDeclaration, 'getPropertyValue' | 'setProperty' | 'removeProperty'>, name: string): () => void {
3
+ const saved = style.getPropertyValue(name);
4
+ return () => {
5
+ if (saved) style.setProperty(name, saved);
6
+ else style.removeProperty(name);
7
+ };
8
+ }
package/src/law.ts CHANGED
@@ -39,6 +39,46 @@ export const RIM = {
39
39
  darkEdge: 0.22,
40
40
  /** Width of the hairline, CSS px. "About a point, not a band and not a bevel" (§2). */
41
41
  widthPx: 1,
42
+ /**
43
+ * How strongly the line is drawn, by the lightness around the element. The rim is a reflection
44
+ * of the surroundings, so it is faint over a dark one and plain over a bright one, not white at
45
+ * full strength everywhere (§2, §6).
46
+ *
47
+ * MEASURED off the controls of A 2:17 (over dark video), M 2:52 (over sky) and A 2:18 (over
48
+ * grass), as the brightest rim bin's OKLab lightness over the surround's: 0.24 to 0.27 over dark,
49
+ * 0.25 over sky, 0.37 over grass. Through the DOM path onto the same backdrops these land within
50
+ * 0.08 of the smooth rows (docs/dom.md); a line white at full strength reads 0.70 to 0.80 over the
51
+ * dark frames.
52
+ */
53
+ strengthDark: 0.37,
54
+ strengthLight: 0.95,
55
+ /**
56
+ * The surround lightness between which the strength moves from one to the other. UNMEASURED: the
57
+ * two ends are, and the controls in between were not looked for.
58
+ */
59
+ ambientFrom: 0.15,
60
+ ambientTo: 0.45,
61
+ /**
62
+ * The share of the line's width, from the silhouette inward, that the light line leaves to the
63
+ * dark edge. UNMEASURED: without it the line covers the dark edge and an element over a bright
64
+ * flat backdrop loses the one mark that holds it there.
65
+ */
66
+ lineInset: 0.3,
67
+ /**
68
+ * The share of the peak that the line keeps along the whole contour, away from the key light.
69
+ * MEASURED: the reference line goes all the way round. Its dimmest bin sits 0.06 to 0.12 of
70
+ * lightness over the surround on the dark frames, against a peak of 0.24 to 0.27 (A 2:17).
71
+ */
72
+ contour: 0.3,
73
+ /** How far the line's colour is lifted from the surroundings' toward white. UNMEASURED: the
74
+ * lens always had 0.75 here, and this is that figure with a name. */
75
+ whiteShare: 0.75,
76
+ /**
77
+ * The reflectance (`fresnelStrength`) at which the line is drawn at the strengths above: that
78
+ * of ordinary glass, index 1.5. A denser medium does not draw it brighter than they say; a
79
+ * thinner one draws it fainter. UNMEASURED.
80
+ */
81
+ reflectanceRef: 0.7,
42
82
  } as const;
43
83
 
44
84
  /** How the medium stands between the backdrop and the eye (§3). */
@@ -54,10 +94,14 @@ export const BODY = {
54
94
  busyEdge: 0.75,
55
95
  /** Fine texture the body has to stop being a window for. */
56
96
  groundSpan: 0.06,
57
- /** Where the tint flips direction, by the element's AVERAGE lightness — a local threshold cuts
58
- * a diagonal step across the body on a gradient. */
59
- darkSideFrom: 0.42,
60
- darkSideTo: 0.58,
97
+ /**
98
+ * The spread under the element at which the ink's legibility requirement starts to bind, and the
99
+ * spread where it binds fully. Over a flat backdrop polarity flips before the ink drowns, so
100
+ * the body does not darken under a light glyph there; over texture it does. UNMEASURED: the
101
+ * reference shows a lighter body under a white glyph over sky, nothing about texture.
102
+ */
103
+ inkBusyFrom: 0.02,
104
+ inkBusyTo: 0.12,
61
105
  /** Ceiling on any single demand, so one requirement cannot make the glass opaque on its own. */
62
106
  maxDemand: 0.92,
63
107
  /**
@@ -67,6 +111,12 @@ export const BODY = {
67
111
  * separate as far as transparency allows and no further. UNMEASURED.
68
112
  */
69
113
  presenceDemand: 0.5,
114
+ /**
115
+ * The density at which the body is wholly the ink's own pole. Between `presenceDemand` and this
116
+ * the body moves from the light a thin body carries to the pole: a density no presence floor
117
+ * reaches is one the ink's legibility, or a system setting, has asked for. UNMEASURED.
118
+ */
119
+ inkPoleTo: 0.8,
70
120
  } as const;
71
121
 
72
122
  /** Shadow (§4). The density law lives in `geometry.ts`; these are its measured ends. */
@@ -378,7 +378,11 @@ half4 main(float2 xy) {
378
378
  float rimKey = u_specular * (pow(max(rimFacing, 0.0), ${RIM.lobeExponent}.0) + ${RIM.opposingArc} * pow(max(-rimFacing, 0.0), ${RIM.lobeExponent}.0));
379
379
  // Wherever the key light doesn't fall, the face reflects its shaded surroundings — hence the
380
380
  // dark outline.
381
- float3 env = refl * (0.55 + 0.45 * min(key, 1.0)) + float3(1.2 * key);
381
+ // The key light's glint is a reflection of the surroundings too: it is scaled by the same
382
+ // ambient strength as the rim line, so it is not white at full strength over a dark backdrop.
383
+ float rimStrength = mix(${RIM.strengthDark}, ${RIM.strengthLight},
384
+ smoothstep(${RIM.ambientFrom}, ${RIM.ambientTo}, vgLuma(ambient)));
385
+ float3 env = refl * (0.55 + 0.45 * min(key, 1.0)) + float3(1.2 * key * rimStrength / ${RIM.strengthLight});
382
386
 
383
387
  // Interference and diffraction live in the reflected ray at a grazing angle — a single hue
384
388
  // multiplier applied to the reflection, no extra samples.
@@ -454,12 +458,10 @@ half4 main(float2 xy) {
454
458
  // transparent glass by the model.
455
459
  float demand = clamp(u_legibility * 4.0, 0.0, 1.0);
456
460
 
457
- // Where there's no ink, the glass tints away from the backdrop. The direction is chosen by the
458
- // average lightness under the element, not the local spot's lightness: otherwise on a gradient
459
- // the threshold cuts across the body as a diagonal step.
460
- float darkSide = smoothstep(${BODY.darkSideFrom}, ${BODY.darkSideTo}, mean);
461
- float away = mix(VG_TINT_LIGHT, VG_TINT_DARK, darkSide);
462
- float tintLuma = mix(away, mix(VG_TINT_LIGHT, VG_TINT_DARK, pol), demand);
461
+ // The body is lighter than its surroundings whatever the glyph on it is: glass concentrates
462
+ // light (M 2:29), and the reference keeps a lighter body under a white glyph over sky as much as
463
+ // over dark. Only a requirement of the ink pulls it to the ink's own pole, below.
464
+ float inkPole = mix(VG_TINT_LIGHT, VG_TINT_DARK, pol);
463
465
 
464
466
  // How much medium it takes to push lightness under the ink past the threshold — locally.
465
467
  // Computed from whichever edge of the backdrop's spread is closer to the ink: a light ink
@@ -474,32 +476,22 @@ half4 main(float2 xy) {
474
476
  float needForDark = inkLo < floorDark
475
477
  ? clamp((floorDark - inkLo) / max(VG_TINT_LIGHT - inkLo, 1e-4), 0.0, ${BODY.maxDemand})
476
478
  : 0.0;
477
- float needForInk = mix(needForDark, needForLight, pol) * demand;
479
+ // Over a flat backdrop the ink asks for nothing: polarity flips before it drowns. It is spread
480
+ // under the ink that the body has to answer.
481
+ float structured = smoothstep(${BODY.inkBusyFrom}, ${BODY.inkBusyTo}, busy);
482
+ float needForInk = mix(needForDark, needForLight, pol) * demand * structured;
478
483
 
479
484
  float ground = structure * u_legibility * VG_GROUND_SPAN;
480
485
  float density = max(max(u_bodyDensity, ground), needForInk);
481
486
 
482
487
  // PRESENCE (§3). Over a uniform backdrop there is nothing to refract, and an element that is
483
- // only a rim honestly disappears — right for a piece of background, wrong for a control.
484
- //
485
- // This used to live on the rim line alone, as lit = max(rimKey, u_presence x 2), while the
486
- // DOM path has always implemented it as a floor on the BODY. Two renderers, one field, two
487
- // meanings. Measured on Apple's own control over a flat page (frames/verify/m-707), the body
488
- // carries 16 levels of separation there and the rim 11.6 — so the body is where it belongs,
489
- // and the rim's reading was the wrong one.
490
- // The separation asked for is capped by the range that is left: over a backdrop at 0.96 there
491
- // is no room to go 0.05 lighter. And what it is DIVIDED by is the distance to the tint's own
492
- // end, not to the tint the body currently sits at — dividing by the latter explodes wherever
493
- // the two are close, which is how the first version of this drove density to its ceiling at
494
- // both extremes and took the window down to 7%.
495
- bool lighter = local < 0.5;
496
- float target = lighter ? min(local + u_presence, 1.0) : max(local - u_presence, 0.0);
497
- float separation = abs(target - local);
498
- float reached = abs(tintLuma - local) * density;
499
- if (u_presence > 0.0 && reached < separation) {
500
- float toEnd = abs((lighter ? VG_TINT_LIGHT : VG_TINT_DARK) - local);
501
- density = max(density, min(separation / max(toEnd, 1e-4), ${BODY.maxDemand}));
488
+ // only a rim honestly disappears — right for a piece of background, wrong for a control. A floor
489
+ // on the density, toward light: the lift it gives is presence times the room left under the
490
+ // light pole, so it fades out as the room does and has no step anywhere on the scale.
491
+ if (u_presence > 0.0) {
492
+ density = max(density, min(u_presence / VG_TINT_LIGHT, ${BODY.presenceDemand}));
502
493
  }
494
+ float tintLuma = mix(VG_TINT_LIGHT, inkPole, max(clamp(needForInk / max(density, 1e-4), 0.0, 1.0), smoothstep(${BODY.presenceDemand}, ${BODY.inkPoleTo}, density)));
503
495
 
504
496
  // Glass has no color of its own — only the color of what's beneath it (HIG "Color").
505
497
  float3 tintHue = mix(float3(1.0), vgHue(wide * 0.5 + ambient * 0.5), u_colorPickup);
@@ -539,17 +531,18 @@ half4 main(float2 xy) {
539
531
  }
540
532
 
541
533
  // RIM LIGHT — a separate layer, a line about 1 pt wide along the silhouette (M 2:36, 11:04):
542
- // brighter where the face faces the key light, weaker opposite it; where no light falls, a
543
- // dark outline. This line is what holds the element's presence, not a fill on the body: over a
544
- // flat backdrop a control is visible along its whole silhouette because of it.
545
- float line = (1.0 - smoothstep(0.0, lineW, e)) * lens;
534
+ // brighter where the face faces the key light, weaker opposite it, and never absent: the line
535
+ // runs the whole contour. It is a reflection of the surroundings, so it is faint over a dark
536
+ // one and plain over a bright one (§2, §6).
537
+ float line = smoothstep(0.0, lineW * ${RIM.lineInset}, e) * (1.0 - smoothstep(lineW * ${RIM.lineInset}, lineW, e)) * lens;
546
538
  float outline = (1.0 - smoothstep(0.0, lineW * 0.6, e)) * lens;
547
- float lit = clamp(max(rimKey * 1.4, u_presence * 2.0), 0.0, 1.0);
539
+ float reflectN = min(u_fresnel / ${RIM.reflectanceRef}, 1.0);
540
+ float lit = clamp(rimStrength * reflectN * (${RIM.contour} + (1.0 - ${RIM.contour}) * rimKey / max(u_fresnel, 0.001)), 0.0, 1.0);
548
541
  // The dark rim is its own layer around the whole silhouette, and the highlight sits ON TOP of
549
542
  // it (iOS 27). While the outline used to fade via a (1 − lit) multiplier, the silhouette read
550
543
  // as a single arc: highlight OR shadow.
551
544
  rgb *= 1.0 - ${RIM.darkEdge} * outline;
552
- rgb = mix(rgb, mix(refl, float3(1.0), 0.75), line * lit);
545
+ rgb = mix(rgb, mix(refl, float3(1.0), ${RIM.whiteShare}), line * lit);
553
546
 
554
547
  if (u_debug > 9.5 && u_debug < 10.5) { rgb = spectral * 0.5; }
555
548
  if (u_debug > 10.5 && u_debug < 11.5) { rgb = float3(density, needForInk, structure); }
package/src/material.ts CHANGED
@@ -63,14 +63,17 @@ export type VireGlassMaterial = {
63
63
  /** Lightness of what the app draws OVER the glass: 1 is light icons and text, 0 is dark. A
64
64
  * cause, not a knob: the calling screen knows it. */
65
65
  ink: number;
66
- /** Minimum PRESENCE of the element itself: how far its body must stand off in lightness from
67
- * the backdrop behind it. Not the same as `legibility` — that's about the ink OVER the glass,
66
+ /** Minimum PRESENCE of the element itself: a floor on its body density, tinted toward light and
67
+ * fading as the backdrop nears the light pole. Not the same as `legibility` — that's about the ink OVER the glass,
68
68
  * this is about the element itself. Over a uniform backdrop the glass has nothing to refract,
69
69
  * and the element disappears; for a control that's unacceptable. 0 means no presence is
70
70
  * required.
71
71
  *
72
- * The sign is taken FROM THE BACKDROP, not from ink polarity: over dark the body lightens,
73
- * over light it darkens. So the requirement works the same way on any backdrop. */
72
+ * The direction is always toward light, whatever the ink: the reference body is lighter than
73
+ * its surroundings from dark video to sky, under a white glyph as much as a dark one, and only
74
+ * as far as the range allows. It never darkens the element on its own account. What can send
75
+ * the body to the ink's pole is the ink's legibility over texture, a system setting (reduced
76
+ * transparency, increased contrast) or a density past `BODY.inkPoleTo`. */
74
77
  presence: number;
75
78
  /** Surface film thickness, nm. This is where interference comes from: the optical path
76
79
  * difference in the film becomes comparable to the wavelength, and the reflection picks up
@@ -229,8 +232,8 @@ export const VIREGLASS_LYRICS_MATERIAL: VireGlassMaterial = {
229
232
  * a native lens that sets body density: there the element is denser by 2.4 percentage points. On
230
233
  * the web the surface has no body at all; the effect only shows on Android below 13.
231
234
  *
232
- * `presence` is raised for the same reason: a piece of background is allowed to disappear over a
233
- * uniform canvas, a control isn't — it has to be visible before it's been tapped.
235
+ * `presence` is left at none: the medium's own lift already separates the body from a uniform
236
+ * backdrop as far as the reference does (docs/dom.md), and the rim carries the silhouette.
234
237
  *
235
238
  * IMPORTANT about size. The bevel is specified by the medium in dp and doesn't depend on the
236
239
  * element's size, but the geometry clamps it to half the smaller half-size (`MAX_BEVEL_FRACTION`).
@@ -244,7 +247,7 @@ export const VIREGLASS_CONTROL_MATERIAL: VireGlassMaterial = {
244
247
  thickness: 44,
245
248
  bevel: 8,
246
249
  roughness: 0.035,
247
- presence: 0.176,
250
+ presence: 0,
248
251
  };
249
252
 
250
253
  export const VIREGLASS_MATERIAL = VIREGLASS_MATERIAL_V5;
@@ -67,11 +67,36 @@ export function convertEvalCalls(src: string): string {
67
67
  return out;
68
68
  }
69
69
 
70
+ /** Comments are prose, and the rewrites here are regexes over raw text: a comment mentioning
71
+ * "return" became one, and a brace in a comment ended the entry point early. Newlines inside
72
+ * block comments survive, so a compile error still points at the source line. */
73
+ export function stripComments(src: string): string {
74
+ let out = '';
75
+ let i = 0;
76
+ while (i < src.length) {
77
+ if (src.startsWith('//', i)) {
78
+ const end = src.indexOf('\n', i);
79
+ i = end < 0 ? src.length : end;
80
+ } else if (src.startsWith('/*', i)) {
81
+ const end = src.indexOf('*/', i + 2);
82
+ if (end < 0) throw new Error('vireglass/targets/glsl: unterminated block comment');
83
+ const stop = end + 2;
84
+ out += src.slice(i, stop).replace(/[^\n]/g, '');
85
+ i = stop;
86
+ } else {
87
+ out += src[i];
88
+ i += 1;
89
+ }
90
+ }
91
+ return out;
92
+ }
93
+
70
94
  /**
71
95
  * Early `return <expression>;` inside `main` → `fragColor = <expression>; return;`. Takes ONLY
72
96
  * the body of main (see `convertEntryPoint`) — ordinary `return`s in helper functions live
73
97
  * outside this text and are untouched. Expressions never contain a `;` of their own (shaders have
74
- * none — the only `;` lives inside `for(...)`, which isn't a return), so `[^;]+` is safe.
98
+ * none — the only `;` lives inside `for(...)`, which isn't a return), so `[^;]+` is safe once
99
+ * comments are gone (`stripComments`).
75
100
  */
76
101
  export function convertReturns(mainBody: string): string {
77
102
  return mainBody.replace(/\breturn\s+([^;]+);/g, 'fragColor = $1; return;');
@@ -133,7 +158,7 @@ export function addPrologue(src: string): string {
133
158
  * literal AGSL signature BEFORE type conversion, otherwise `half4 main(float2 xy)` can't be
134
159
  * found. */
135
160
  export function toGLSL(shaderSource: string): string {
136
- let out = shaderSource;
161
+ let out = stripComments(shaderSource);
137
162
  out = convertEntryPoint(out);
138
163
  out = convertShaderUniforms(out);
139
164
  out = convertEvalCalls(out);