vireglass 2.1.0 → 2.3.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.
- package/README.md +41 -1
- package/dist/{chunk-3PGYF2NK.js → chunk-CTTZNZFV.js} +3 -3
- package/dist/{chunk-3PGYF2NK.js.map → chunk-CTTZNZFV.js.map} +1 -1
- package/dist/{chunk-FL5UZPY5.js → chunk-H5QTBF67.js} +4 -4
- package/dist/{chunk-FL5UZPY5.js.map → chunk-H5QTBF67.js.map} +1 -1
- package/dist/{chunk-TTOKBARR.js → chunk-MMPK6DCK.js} +10 -3
- package/dist/chunk-MMPK6DCK.js.map +1 -0
- package/dist/{chunk-BZC2WHCK.js → chunk-PM2SWCSF.js} +4 -4
- package/dist/{chunk-BZC2WHCK.js.map → chunk-PM2SWCSF.js.map} +1 -1
- package/dist/{chunk-P2ZOODXD.js → chunk-SLRPHDV7.js} +3 -3
- package/dist/{chunk-P2ZOODXD.js.map → chunk-SLRPHDV7.js.map} +1 -1
- package/dist/dom.cjs +48 -20
- package/dist/dom.cjs.map +1 -1
- package/dist/dom.d.cts +15 -3
- package/dist/dom.d.ts +15 -3
- package/dist/dom.js +44 -23
- package/dist/dom.js.map +1 -1
- package/dist/index.cjs +1 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -5
- package/dist/law.cjs +8 -1
- package/dist/law.cjs.map +1 -1
- package/dist/law.d.cts +7 -0
- package/dist/law.d.ts +7 -0
- package/dist/law.js +1 -1
- package/dist/reference.js +2 -2
- package/dist/web/index.cjs +262 -153
- package/dist/web/index.cjs.map +1 -1
- package/dist/web/index.d.cts +88 -2
- package/dist/web/index.d.ts +88 -2
- package/dist/web/index.js +257 -156
- package/dist/web/index.js.map +1 -1
- package/docs/law.md +2 -2
- package/package.json +13 -9
- package/src/dom/body.ts +24 -9
- package/src/dom/index.ts +44 -10
- package/src/dom/probe.ts +27 -3
- package/src/law.ts +7 -0
- package/src/targets/glsl.ts +4 -0
- package/src/web/gl.ts +57 -4
- package/src/web/gpu-timer.ts +111 -0
- package/src/web/index.ts +1 -0
- package/src/web/renderer.ts +305 -218
- package/dist/chunk-TTOKBARR.js.map +0 -1
|
@@ -42,7 +42,14 @@ var BODY = {
|
|
|
42
42
|
darkSideFrom: 0.42,
|
|
43
43
|
darkSideTo: 0.58,
|
|
44
44
|
/** Ceiling on any single demand, so one requirement cannot make the glass opaque on its own. */
|
|
45
|
-
maxDemand: 0.92
|
|
45
|
+
maxDemand: 0.92,
|
|
46
|
+
/**
|
|
47
|
+
* And a tighter one for presence specifically. It is a FLOOR on visibility, not a licence to go
|
|
48
|
+
* opaque: wherever the body's tint sits close to the backdrop, the density that would deliver the
|
|
49
|
+
* separation is the ceiling, and glass at `maxDemand` has stopped being a window. Better to
|
|
50
|
+
* separate as far as transparency allows and no further. UNMEASURED.
|
|
51
|
+
*/
|
|
52
|
+
presenceDemand: 0.5
|
|
46
53
|
};
|
|
47
54
|
var SHADOW = {
|
|
48
55
|
/**
|
|
@@ -452,5 +459,5 @@ var SCROLL_EDGE = {
|
|
|
452
459
|
};
|
|
453
460
|
|
|
454
461
|
export { ACCENT, ACCESSIBILITY, BEVEL, BODY, DERIVE, DISPERSION, EPSILON, INK, LENS, MEDIUM, MORPH, RIM, SCALE, SCROLL_EDGE, SHADOW, SHADOW_DENSITY, SHADOW_GAP, SIZE, SPECTRAL, SPRING, TOUCH };
|
|
455
|
-
//# sourceMappingURL=chunk-
|
|
456
|
-
//# sourceMappingURL=chunk-
|
|
462
|
+
//# sourceMappingURL=chunk-MMPK6DCK.js.map
|
|
463
|
+
//# sourceMappingURL=chunk-MMPK6DCK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/law.ts"],"names":[],"mappings":"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","file":"chunk-MMPK6DCK.js","sourcesContent":["/**\n * THE LAW: every calibrated number the material obeys, in one place.\n *\n * A value here is written ONCE and read by everything that needs it — the shader text interpolates\n * it, the JS twins import it. The alternative is what this file replaces: the same number typed\n * into a GLSL string and again into a TypeScript function, drifting apart the first time one side\n * is tuned. `VG_FALLOFF` already worked this way; the rest now does too.\n *\n * Each entry carries where it came from. `§N` is a section of `docs/reference.md`, which is itself\n * drawn from Apple's public sessions; `M 2:32` and the like are the timestamps those sections cite.\n * A number with no provenance is marked as such, and that marking is a debt, not a decoration.\n *\n * Structural constants — 0, 1, 0.5, 2, π — are not laws and do not belong here.\n */\n\n/** Rim optics: the silhouette's light (§2). */\nexport const RIM = {\n /**\n * The key-light lobe's exponent. Two opposing arcs come out of one expression, which is why\n * they are opposed rather than placed: `pow(max(facing, 0), n) + 0.45 * pow(max(-facing, 0), n)`.\n *\n * MEASURED off two close-ups of real end caps, frames/crops/cap158-left and cap158-right. The\n * bright arc's full width at half its peak is 53 degrees in the first and 30 in the second; this\n * exponent's own width is `2·acos(0.5^(1/n))`, so they imply 6 and 20. It was 3, whose arc is 75\n * degrees wide — broader than either measurement, and the error is in the same direction in both.\n *\n * 6 is the conservative end: it reproduces the wider crop exactly, and a broader arc is the more\n * forgiving error. One exponent cannot serve both, and that is not noise — a specular lobe's\n * width depends on the light's angular size as well as the surface, and those are two different\n * scenes. The model has no term for the light's size.\n */\n lobeExponent: 6,\n /** The far arc, weaker than the one facing the light (§2). */\n opposingArc: 0.45,\n /**\n * The dark edge along the whole silhouette, as a multiplicative darkening. iOS 27 made it a\n * layer of its own, and it does not rule out the highlight sitting on top of it (§2).\n */\n darkEdge: 0.22,\n /** Width of the hairline, CSS px. \"About a point, not a band and not a bevel\" (§2). */\n widthPx: 1,\n} as const;\n\n/** How the medium stands between the backdrop and the eye (§3). */\nexport const BODY = {\n /** Lightness ceiling for the body when legibility is loose. */\n capLoose: 0.62,\n /** …and when it is strict. The model is calibrated at these two, not in between (E-43). */\n capTight: 0.38,\n /** The two ends the body tints toward. Glass has no colour of its own (HIG \"Color\"). */\n tintDark: 0.07,\n tintLight: 0.94,\n /** How far the backdrop's spread reaches past its mean when deciding what the ink needs. */\n busyEdge: 0.75,\n /** Fine texture the body has to stop being a window for. */\n groundSpan: 0.06,\n /** Where the tint flips direction, by the element's AVERAGE lightness — a local threshold cuts\n * a diagonal step across the body on a gradient. */\n darkSideFrom: 0.42,\n darkSideTo: 0.58,\n /** Ceiling on any single demand, so one requirement cannot make the glass opaque on its own. */\n maxDemand: 0.92,\n /**\n * And a tighter one for presence specifically. It is a FLOOR on visibility, not a licence to go\n * opaque: wherever the body's tint sits close to the backdrop, the density that would deliver the\n * separation is the ceiling, and glass at `maxDemand` has stopped being a window. Better to\n * separate as far as transparency allows and no further. UNMEASURED.\n */\n presenceDemand: 0.5,\n} as const;\n\n/** Shadow (§4). The density law lives in `geometry.ts`; these are its measured ends. */\nexport const SHADOW = {\n /**\n * Measured off reference frames 711–723: 4.0% over a flat light backdrop, 19.9% over text.\n *\n * REPRODUCED independently off frames/verify/m-707 and m-711 — the same capsule in the same scene\n * at the same scale, once below the text over plain background and once scrolled up over it.\n * Comparing the rows under the capsule against the same rows far to either side, so the\n * background's own gradient cancels: 3.5% and 19.2%, peaking about 140 px below the capsule.\n * Two passes, different frames and a different method, agreeing to within 0.7 of a point.\n */\n alphaFlat: 0.04,\n alphaBusy: 0.199,\n} as const;\n\n/** Dispersion: the channels refract at different indices (§1, Cauchy). */\nexport const DISPERSION = {\n /** Red bends less than the base index, blue more — and not symmetrically. */\n redShift: -0.4,\n blueShift: 0.6,\n} as const;\n\n/** Response to a finger (§5). The springs and decay live in `touch-response.ts`; these shape the\n * field the finger deforms. */\nexport const TOUCH = {\n /**\n * The element grows under pressure (M 3:51; HIG: interactive \"expands\").\n *\n * CHECKED, not measured: across frames/knob/switch-press, a 4x5 sheet of one switch being\n * pressed, the control's extent runs 764 px in the three settled frames and 787 to 822 under the\n * finger — 3 to 8 per cent. This value sits inside that, which is a consistency check and not a\n * derivation: the measurement cannot separate the knob stretching from the track growing, and the\n * settled frames agreeing to the pixel is the only part of it that is tight.\n */\n pressGrow: 0.06,\n /** The ridge around the contact blob: material displaced from under the finger ends up\n * somewhere. Without it the shape balloons, and a dense medium does not do that. */\n ridge: 0.42,\n /** How far press pulls the field toward the finger. */\n pressPull: 0.16,\n /** Wavelength and decay of the ripple, as fractions of its span. Short and quick: in a dense\n * medium ripples are frequent and small; long shallow swells are water. */\n waveLength: 0.17,\n waveDecay: 0.7,\n /** The ripple's span, as a multiple of the contact radius. It has to reach the far edge, or it\n * reads as jitter under the finger rather than a wave across the surface. */\n waveSpan: 2,\n /**\n * Contact radius as a fraction of the element's half-size. UNMEASURED: §5 says the response has\n * to be visible UNDER the finger, which is why the blob is wide, but gives no figure.\n */\n radiusOfHalfSize: 1.1,\n /**\n * Wave impulse as a fraction of the travel limit. UNMEASURED: the core carries the ripple's\n * decay and frequency, but nothing says how hard a finger strikes it.\n */\n waveOfTravel: 0.18,\n /** The lift-off ring is weaker than the one from touching down (§5). UNMEASURED ratio. */\n releaseWave: 0.6,\n /**\n * How far a glow reaches past the element it started on, as a multiple of that element's\n * half-size. UNMEASURED: §5 says the glow spreads \"onto any Liquid Glass elements nearby\" and\n * names no distance. This is the reach at which a neighbour in a tab bar still lights and one\n * across the screen does not.\n */\n glowReach: 3,\n /**\n * How much of an element's \"active\" state a touch alone raises — the fraction the glow and the\n * ink lift ride on when nothing else is driving them. UNMEASURED.\n *\n * It was a private constant on the Android surface whose comment said it matched the web's, and\n * nothing enforced that: the web renderer takes `active` from the host as a field and has no\n * constant to match. Two renderers agreeing by a comment is exactly what this file is for.\n */\n activeOnTouch: 0.3,\n} as const;\n\n/** The bevel's profile — spherical, like a cap: `t / sqrt(1 - t² · k)`, clamped. A plain `t²` kept\n * the slope near zero through most of the bevel and then shot up at the edge, so the optics\n * bunched into a ring and the element read as a puck. */\nexport const BEVEL = {\n sphere: 0.94,\n floor: 0.02,\n slopeMax: 3.2,\n} as const;\n\n/** Where two shapes meet head-on their normals cancel exactly and the blend lands on zero. */\nexport const EPSILON = 1e-5;\n\n/** What the medium does to light passing through it (§3). */\nexport const MEDIUM = {\n /** The lightness content under the glass is pulled toward, and how hard. The medium both removes\n * light and mixes scattered light back in: over a light backdrop the body darkens, over a dark\n * one it lightens. A layer that only ADDED light pushed the body the wrong way over light\n * backdrops (M 2:35). */\n luma: 0.4,\n pull: 0.07,\n /** Ambient light reaching the body on top of that pull. */\n ambientSpill: 0.01,\n /** Glass concentrates light: the body is a touch lighter than what lies beneath it (M 2:29). */\n concentrate: 0.01,\n /** Scattered light on a frosted element is an ADDITION over the backdrop, not a fraction of the\n * way to the tint — a fraction goes to zero once the backdrop reaches the tint's lightness and\n * darkens past it (docs/benchmarks.md). */\n matteLift: 0.1,\n /** How far scattering reaches, as a fraction of the gather radius, over a busy backdrop under\n * ink. Set against the reference: structure under the capsule fades by a factor of nine or ten\n * there, not thirty (M 11:47). */\n scatterMax: 0.2,\n scatterBase: 0.1,\n /** The response to structure saturates early: what competes with the ink is not the AREA of\n * foreign text but the fact of it — a line under a tile measures about 0.12 of busy. */\n structureGain: 20,\n /** Tint density in the flat middle; the bevel multiplies it by the edge density. */\n bodyDensityFlat: 0.19,\n} as const;\n\n/** The lens itself (§1). */\nexport const LENS = {\n /** Backdrop compression at the rim goes to infinity at the silhouette, so the radius correction\n * is capped. */\n footprintMax: 1.8,\n /** Same reason, for the profile's slope. */\n slopeMax: 40,\n /** Every thin film on glass sits around this index. */\n filmIor: 1.35,\n /** Reference channel wavelengths in nm — red, green, blue — for diffraction and interference. */\n lambdaR: 610,\n lambdaG: 550,\n lambdaB: 460,\n} as const;\n\n/** The shadow's shape in the gap under the element (§4). */\nexport const SHADOW_GAP = {\n /** How much weaker the shadow is right at the outline than under the middle of the gap, and what\n * fraction of the offset it takes to reach full depth. In the reference the shadow's minimum\n * sits BELOW the rim, not at it. */\n light: 0.76,\n reach: 0.2,\n /** Fraction of the ambient hue the shadow carries. It has to stay a shadow, not a coloured blob. */\n tint: 1,\n} as const;\n\n/** Ink drawn inside the material (§5). */\nexport const INK = {\n /** Depth beneath the surface, dp. The normal displaces it by exactly this much at the rim. */\n depth: 4,\n /** Defocus under a finger, as a FRACTION of the touch blob rather than a length: the blob\n * already arrives in device units, so this follows both screen density and element size. At\n * rest it is zero and the sample stays a single one. */\n defocus: 0.08,\n} as const;\n\n/** The spectral edge (§1): dispersion, diffraction and interference are one cause — a dependence\n * on wavelength — and one hue multiplier, normalised to its own mean. They colour the reflection\n * without brightening it. */\nexport const SPECTRAL = {\n /** How far into the bevel the fringes reach, as a fraction of it measured from the silhouette.\n * Diffraction is an EDGE effect; past this the bevel is plain glass. */\n diffractionOnset: 0.45,\n /**\n * The same window for interference, which had none. §2 puts the iridescence on the opposing ARC —\n * \"the opposing arc carries an iridescent fringe\" — and a fringe is a thing at an edge.\n *\n * Without it the term applies wherever the surface angle puts it, and across the inner bevel the\n * angle barely changes, so a film that should read as a fringe reads as a flat colour filter.\n * Measured on a 680x64 bar with the default preset: a multiplier of 1.68, 1.26, 0.05 held CONSTANT\n * from a third of the way through the bevel to its inner edge — red boosted by two thirds and blue\n * removed outright. UNMEASURED window; wider than diffraction's because interference reaches\n * further in than the fringes do.\n */\n iridescenceOnset: 0.25,\n /** Fringes across the bevel. Denser as the bevel sharpens, which is what the phase term does. */\n diffractionFringes: 4,\n /** The channel the hue is measured against, nm. Green is the eye's own reference. */\n referenceLambda: 550,\n} as const;\n\n/** Tinting (§7): a colour that is a property of the medium, not a fill over it. */\nexport const ACCENT = {\n /** The tone the colour takes over a dark backdrop and over a light one. \"A range of tones mapped\n * to content brightness underneath\", rather than one flat colour. */\n deep: 0.88,\n light: 1.08,\n /** How much of the medium the colour occupies by default. Short of one, because the content has\n * to keep coming through: a fill \"breaks the visual character of Liquid Glass\" (@17:03). */\n amount: 0.8,\n} as const;\n\n/**\n * The system accessibility settings (§9). They are modifiers on the material's layers, so their\n * calibration belongs in the law next to the layers they move — not as private constants in the\n * module that happens to apply them.\n */\nexport const ACCESSIBILITY = {\n /** Frost floor under reduced transparency: the glass gets \"frostier\" (§9). */\n frostMinDp: 14,\n /**\n * Body floor under reduced transparency. B @10:07 is the stricter statement of the two — a bar\n * \"gets a background when reduce transparency is enabled\" — so this is a background, not a\n * thicker haze. UNMEASURED: neither source gives a figure.\n */\n obscureMin: 0.82,\n /** Under increased contrast the element is \"predominantly black or white\" (§9): the body goes to\n * whichever pole its ink already points at, at a density that leaves it reading as that pole. */\n contrastDensity: 0.94,\n /** …and separates from the backdrop on its own, whatever is behind it. */\n contrastPresence: 0.5,\n /**\n * The contrasting border §9 asks for, in units of lightness away from the body's own pole. The\n * rim stops taking its colour from the environment and takes it from the element instead — an\n * ambient hairline is not a contrasting border. UNMEASURED.\n */\n contrastRim: 0.85,\n /**\n * Press that survives reduced motion. §9 \"decreases the intensity of some effects and disables\n * any elastic properties\": the spring and the ripple are elastic and go to zero, the press is an\n * ease that carries the element's light, so it is damped rather than removed — an element that\n * stops answering a finger altogether is not reduced motion, it is no feedback. UNMEASURED.\n */\n stillPress: 0.6,\n} as const;\n\n/**\n * Morphing (§5). Apple's design leads name their own model for it out loud (S 1:25:30, fireside\n * chat with the Apple design team): \"we also had a refresher from biology class. It's called\n * mitosis and meiosis. When these things are coming together or materialization and\n * dematerialization and morphing.\"\n *\n * Which is the right physics and not a metaphor: a dividing cell is one body throughout. It never\n * has a moment of being two bodies that happen to touch, and it never pops a second body into\n * existence beside the first. The neck is continuous from start to finish, and it is the neck that\n * carries the whole transition.\n */\nexport const MORPH = {\n /**\n * Width of the neck between two lobes, as a fraction of the smaller one's half-size. On a large\n * element the bridge has to be wider or the seam keeps a sharp corner, which a liquid does not\n * have. UNMEASURED: the reference names the behaviour and no figure.\n */\n neck: 0.35,\n /**\n * How far past merely touching a completed fusion goes, as a multiple of the bridge width that\n * just closes the gap. At exactly the bridge width the two surfaces meet at a point, which is two\n * shapes kissing rather than one body.\n *\n * MEASURED, frames/morph/s03, the one panel where the necks are still attached. Apple's\n * silhouette there: lobes 72 and 82 px with their centres 92 px apart — 15 px of space between\n * their surfaces — joined by a neck 28 px tall. Rendering the same geometry through this model\n * gives 29.0. The measurement discriminates: 1.2 gives 23.5, 1.4 gives 33.5, and at 1.0 the neck\n * closes to nothing.\n */\n fuse: 1.3,\n} as const;\n\n/**\n * CAUSE TO EFFECT. The normalisations that turn what the medium IS — index, thickness, bevel,\n * roughness, film — into what a renderer needs. They lived as literals inside `optics.ts`, half of\n * them not even named, which meant the heart of the model was the one part of it the law could not\n * see.\n *\n * Almost all of them are stylisations and say so. Literal physical values put every effect at the\n * threshold of visibility: real glass reflects four per cent at normal incidence, and a rim drawn\n * at four per cent is not a rim. Each is monotonic in its own cause, so \"denser medium, brighter\n * rim\" holds whatever the constant is; what no one has measured is where on the scale Apple's\n * material sits. Hence UNMEASURED on nearly all of it — that is the honest state of this file, and\n * the gate now says so out loud instead of the number hiding in a module.\n */\nexport const DERIVE = {\n /** Physical 4% reflectance into the render's working range. UNMEASURED. */\n fresnelGain: 17.5,\n /** Schlick's exponent. A constant of the approximation, not a knob. */\n fresnelExponent: 5,\n /** The index at which ray bending is at full strength, measured from air. UNMEASURED. */\n iorFullBend: 0.6,\n /** Magnification behind a plane-parallel plate, per dp of thickness. UNMEASURED. */\n magnifyPerDp: 0.006,\n /** In ordinary glasses the Abbe number falls as the index rises, so dispersion grows with it.\n * UNMEASURED slope. */\n dispersionPerIor: 1.1,\n /** Index spread between the red and blue channels. The physical value (~0.01 at an Abbe number\n * of 55) is at the threshold of visibility. UNMEASURED. */\n iorSpreadPerDispersion: 0.045,\n /** Beer–Lambert absorption per dp of path. UNMEASURED. */\n absorbPerDp: 0.017,\n /** Ceiling on how much denser the bevel may read than the body. The rim's ray travels further\n * through the medium; past this the rim is milk rather than glass. UNMEASURED. */\n edgeDensityMax: 4,\n /**\n * Frost ceiling, dp. Deliberately low: blur is a SUPPORTING device here, and past about a dozen\n * dp letters under the glass stop being letters. Separating content from ink is the body's job.\n *\n * UNMEASURED, and an attempt is recorded here so the next one does not repeat it.\n * frames/verify/m-711 has the same paragraph sharp beside the capsule and behind it, which looks\n * like the measurement: match the blur that takes the sharp patch's detail down to the glassed\n * one's. It gives 2 to 3 px at that frame's scale, and the figure is not trustworthy. Detail is\n * lost to three things at once there — the frost, the magnification (`refractionScale` lowers the\n * text's spatial frequency without blurring anything), and the body veiling its contrast — and\n * the method cannot separate them. What it does bound is the total: the text behind the glass\n * keeps 68% of the detail it has beside it.\n *\n * A clean measurement needs a frame where the glass does not magnify, or a feature of known size\n * behind it to normalise the magnification out.\n */\n blurMaxDp: 12,\n /** Highlight width from a smooth surface and from a fully rough one, as a specular exponent.\n * UNMEASURED. */\n specularPowerSmooth: 160,\n specularPowerRough: 8,\n /** How much roughness damps the highlight's brightness. UNMEASURED. */\n specularRoughDamping: 0.6,\n /**\n * The medium's own hue by density. Water absorbs red and skews cool, ordinary glass is nearly\n * neutral with a faint green, dense high-index media skew warm. Glass has no colour of its own\n * (HIG \"Color\"), so this is never set by hand — it is tied to the index.\n */\n hueCool: [0.86, 1.0, 1.08],\n hueNeutral: [1.0, 1.02, 0.99],\n hueWarm: [1.08, 1.0, 0.88],\n /** The index range the hue sweeps across, and where it starts. UNMEASURED. */\n hueFromIor: 1.2,\n hueSpan: 0.55,\n /** Body lightness follows reflectance: the denser the medium, the more of its surroundings it\n * returns. UNMEASURED base and slope. */\n bodyLiftBase: 0.34,\n bodyLiftPerF0: 2.4,\n /**\n * How far past the bevel the rim gathers its surroundings, as a multiple of the bevel. At a\n * grazing angle the eye receives the vicinity rather than what is under the glass, and without\n * this, glass on an empty black background has no source at all. UNMEASURED.\n */\n gatherPerBevel: 4,\n /** Floor and ceiling on that radius, dp. Unbounded, a wide bevel gathers from fifty dp away and\n * a READABLE copy of the neighbourhood appears inside the glass — eight samples cannot blur a\n * disc that large (E-33, E-37). */\n gatherMinDp: 4,\n gatherMaxDp: 28,\n /** The body's own density from absorption through its thickness. Small: the body only moves\n * toward the tint under the legibility requirement, otherwise it dims the content. UNMEASURED. */\n bodyDensity: 0.05,\n /**\n * Rim light on the physical scale rather than the rim highlight's. `fresnelGain` saturates at one\n * for dense media, and applying it here too would raise body lightness harder than the glass had\n * just separated it from the ink — eating its own legibility work. UNMEASURED.\n */\n edgeLightGain: 6,\n /**\n * Iridescence rides the REFLECTED ray, so it is tied to Fresnel and has no knob of its own.\n *\n * MEASURED against how coloured a real rim is. In frames/crops the brightest rim pixels have a\n * channel spread — (max − min) / max — of 13% in cap158-left and 20% in cap158-right, against\n * their own backdrops' 14.6% and 14.3%. So the rim is no more coloured than the scene it stands\n * in: §2's \"iridescent fringe\" adds a few points at most, and in one of the two crops it adds\n * nothing measurable.\n *\n * At 1.6 this put ordinary glass at 0.945 iridescence and the spectral multiplier reached a 97%\n * channel spread — red boosted by two thirds and blue removed. Rendered over a coloured page that\n * is a neon tube around every control, which is what sent me to measure it.\n */\n iridescencePerFresnel: 0.042,\n /** Edge diffraction shares dispersion's λ-dependence, at the stylised amplitude: at the physical\n * one the fringes are not visible at all. MEASURED with the same crops and the same target as\n * iridescence — on its own it was reaching a 93% channel spread. */\n diffractionPerDispersion: 0.05,\n /** Multiple internal reflection — the denser the medium, the more light it circulates and the\n * more it is tinted by its surroundings. UNMEASURED slope. */\n colorPickupPerFresnel: 0.9,\n /**\n * The ceiling, which keeps it a medium rather than a fill.\n *\n * MEASURED, frames/concentric/shadow-zoom: the same sidebar twice, over a yellow cover and over a\n * pink one, so the difference between the panels is the pickup with the sidebar's own colour\n * subtracted out. Sampling a band of rows clear of the text, the bar's hue offset against the\n * cover's own reaches 0.31 of it at the edge nearest the content in the yellow panel and 0.28 in\n * the pink one. It was 0.42, which nothing measured.\n *\n * Reality is not uniform: the bleed falls to nothing about ninety pixels in, and both panels show\n * the same falloff. The DOM path has one backdrop sample per element and cannot reproduce that,\n * so it applies the ceiling across the whole body — right at the edge, too strong in the middle.\n * That limit is in docs/dom.md rather than papered over with a lower number.\n */\n colorPickupMax: 0.3,\n} as const;\n\n/**\n * SIZE AND SHADOW. What changes when an element gets bigger, and how dense its shadow reads over\n * what is behind it. 219 @6:36: as glass \"flexes and morphs to larger sizes, it simulates a\n * thicker material with deeper shadows and more pronounced lensing and refraction\".\n */\nexport const SIZE = {\n /** Half-size at which bevel and thickness are specified as given; they grow as its square root\n * from there. UNMEASURED reference size. */\n referenceDp: 24,\n /** Floor and ceiling on that growth. UNMEASURED. */\n gainMin: 0.8,\n gainMax: 2.4,\n /** A bevel wider than this fraction of the half-size breaks the SDF — the roundings converge in\n * the middle. A property of the geometry, not a taste. */\n maxBevelFraction: 0.65,\n} as const;\n\n/**\n * Shadow density by what is behind the element. 219 @11:47, verbatim: it \"increases the opacity of\n * its shadow when it is over text… lowers the opacity of its shadow when it is over a solid light\n * background.\" About content BEHIND the element, so one value per element rather than a field over\n * the shadow's area. Endpoints checked against the reference by the depth of the dip under the\n * element (docs/benchmarks.md).\n */\nexport const SHADOW_DENSITY = {\n /** Over a flat background. */\n flat: 0.8,\n /** How fast it rises with structure behind the element, and how far it may rise. */\n busyGain: 6,\n busyRise: 1.2,\n /** Quantisation of the emitted value: a shadow that changes on every pixel of scroll re-lays-out\n * the native view every frame. */\n step: 0.05,\n} as const;\n\n/**\n * THE MEDIUM'S RESPONSE TO A FINGER (§5). Part of the material, exactly as `ior` is: keeping these\n * with the consumer would mean web and Android ended up with two different glasses under one name.\n *\n * Press is viscous — it dents shallow and releases slowly. Drag is thick: the body noticeably lags\n * the finger, travel is short, and on release it snaps back fast with almost no overshoot, because\n * a wide springy overshoot would be liquid jelly rather than dense glass.\n */\nexport const SPRING = {\n /** Stiffness and damping while the finger is down. UNMEASURED. */\n holdStiffness: 260,\n holdDamping: 46,\n /** …and after it lifts. Stiffer, and damped hard: a dense medium snaps back without ringing. */\n releaseStiffness: 420,\n releaseDamping: 34,\n /** Time constants for the press dent, in and out, seconds. UNMEASURED. */\n pressAttack: 0.07,\n pressRelease: 0.16,\n /** How fast the touch point catches up to the finger. Not instant: an instant jump tears the\n * deformation, and on rapid taps that reads as jitter. */\n pointFollow: 0.045,\n /** …and how fast the element registers as being touched at all, which is what the glow rides. */\n activeFollow: 0.09,\n /** The ripple decays within a quarter second rather than oscillating: in a viscous medium ripples\n * are short and weak. UNMEASURED. */\n waveDecay: 0.22,\n waveTurnsPerSecond: 3,\n /** Ceilings on the accumulated ripple, as multiples of a single impulse. Impulses add up rather\n * than restart — restarting cuts a running wave mid-period, which is what jolts on rapid taps. */\n waveCapOnGrab: 1.6,\n waveCapOnRelease: 2,\n} as const;\n\n/** The user's clear-to-tinted preference (§3). */\nexport const SCALE = {\n /** Where on the scale the material stays exactly as it is by default. */\n default: 0.35,\n /** Body density at the fully tinted end: content under the glass has to be hidden. UNMEASURED. */\n tintedDensity: 0.9,\n} as const;\n\n/**\n * The scroll edge (§10). 219 @9:16: as content scrolls under a glass element \"the effect gently\n * DISSOLVES the content into the background, lifting the glass visually above the moving content,\n * and allowing floating elements like titles to always remain clear.\"\n *\n * 356 @11:32 says what it is not, and the first implementation here did exactly that: \"they don't\n * block or darken like overlays. They simply clarify where UI and content meet.\"\n */\nexport const SCROLL_EDGE = {\n /** How far the content has to slide under the panel for the effect to fully engage, dp. */\n engageDp: 24,\n /**\n * How far the blur reaches BELOW the bar's lower lip, as a multiple of the bar's own height.\n *\n * MEASURED, frames/scroll-edge/z576 at 320x413. High-frequency energy (mean |laplacian| per row,\n * which a brightness gradient in the content cannot fake) reads 1.4 through the zone under the\n * bar and 14 in the sharp content below — a factor of ten. The bar's lip is at y≈45 and the zone\n * runs to y≈130, so the reach is about 1.9 times the bar's own height.\n *\n * The same frame confirms 219 @9:22 in passing: the floating title sits inside that zone at 32,\n * twenty times its surroundings. Titles stay crisp while everything behind them dissolves, which\n * is why the effect belongs behind the glass in the stack rather than over the content.\n */\n reachOfBarHeight: 1.9,\n /** The blur the content dissolves into at the edge, dp. This is the effect itself — the content\n * goes out of focus into the background rather than being covered by anything. UNMEASURED. */\n dissolveBlurDp: 10,\n /**\n * Over dark content the glass turns dark and 219 @9:33 switches the effect \"to apply a subtle\n * dimming instead\". Dimming is a reduction in luminance: dark content under dark glass merges\n * with it, and pushing the content down is what lets the glass sit above it. UNMEASURED.\n */\n dimAlpha: 0.1,\n /** A pinned view under the panel (column headers) gets a flat band instead of a gradual fade\n * (219 @9:41) — \"a stronger, more opaque boundary\" (356 @12:12). UNMEASURED. */\n hardBlurDp: 14,\n hardAlpha: 0.18,\n} as const;\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { VG_SDF, VG_FALLOFF } from './chunk-
|
|
2
|
-
import { MEDIUM, SHADOW_GAP, INK } from './chunk-
|
|
1
|
+
import { VG_SDF, VG_FALLOFF } from './chunk-H5QTBF67.js';
|
|
2
|
+
import { MEDIUM, SHADOW_GAP, INK } from './chunk-MMPK6DCK.js';
|
|
3
3
|
|
|
4
4
|
// src/surface-shader.ts
|
|
5
5
|
var SURFACE_SHADER = `
|
|
@@ -373,5 +373,5 @@ function toGLSL(shaderSource) {
|
|
|
373
373
|
}
|
|
374
374
|
|
|
375
375
|
export { SURFACE_SHADER, addPrologue, convertEntryPoint, convertEvalCalls, convertHalfTypes, convertReturns, convertShaderUniforms, convertVecTypes, toGLSL };
|
|
376
|
-
//# sourceMappingURL=chunk-
|
|
377
|
-
//# sourceMappingURL=chunk-
|
|
376
|
+
//# sourceMappingURL=chunk-PM2SWCSF.js.map
|
|
377
|
+
//# sourceMappingURL=chunk-PM2SWCSF.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/surface-shader.ts","../src/targets/glsl.ts"],"names":[],"mappings":"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","file":"chunk-BZC2WHCK.js","sourcesContent":["import { INK, MEDIUM, SHADOW_GAP } from './law';\nimport { VG_FALLOFF, VG_SDF } from './sdf';\n\n/** Order matches `DEBUG_MODES` in `material.ts`: the index goes straight into `u_debug`. */\nexport const SURFACE_SHADER = `\nuniform shader u_icon;\n/** App-colored content ON the glass — cover art, a thumbnail. A separate layer from the ink\n * mask: that one is single-channel and colored by polarity, this one carries its own color\n * as-is. */\nuniform shader u_overlay;\n\nuniform float2 u_center;\nuniform float2 u_halfSize;\nuniform float u_corner;\nuniform float u_bevel;\nuniform float u_thickness;\nuniform float2 u_morphOffset;\nuniform float2 u_morphHalf;\nuniform float u_morphCorner;\nuniform float u_morphK;\nuniform float2 u_morph2Offset;\nuniform float2 u_morph2Half;\nuniform float u_morph2Corner;\n\nuniform float u_press;\nuniform float u_active;\n/* 0 — the element sinks in under the finger, 1 — it rises into glass (reference §5). */\nuniform float u_lift;\n\nuniform float u_edgeDensity;\nuniform float u_dispersion;\nuniform float u_refraction;\nuniform float4 u_tint;\nuniform float u_shadow;\nuniform float u_shadowReach;\nuniform float3 u_ambient;\nuniform float u_presence;\nuniform float u_progress;\nuniform float u_appear;\nuniform float u_debug;\n\nuniform float u_iconOn;\nuniform float u_overlayOn;\nuniform float u_iconScale;\nuniform float4 u_inkIdle;\nuniform float4 u_inkActive;\nuniform float2 u_touch;\nuniform float2 u_pull;\nuniform float u_touchPress;\nuniform float u_touchRadius;\nuniform float2 u_wave;\n\n${VG_SDF}\n\nconst float VG_FALLOFF = ${VG_FALLOFF};\n\n// Tint density in the flat middle; at the bevel it's multiplied by u_edgeDensity.\nconst float VG_BODY_DENSITY = ${MEDIUM.bodyDensityFlat};\n/* Fraction of the ambient hue in the shadow. The shadow has to stay a shadow, not a colored\n * blob. */\nconst float VG_SHADOW_TINT = ${SHADOW_GAP.tint}.0;\n// How much weaker the shadow is right at the outline than under the middle of the gap, and what\n// fraction of the offset it takes to reach full depth. In the reference the shadow's minimum\n// sits below the rim, not right at it.\nconst float VG_GAP_LIGHT = ${SHADOW_GAP.light};\nconst float VG_GAP_REACH = ${SHADOW_GAP.reach};\n/* Depth of the ink beneath the surface, dp. The normal displaces it by exactly this much right\n * at the rim. */\nconst float VG_INK_DEPTH = ${INK.depth}.0;\n/* Ink defocus under the finger (reference §6): a glyph loses its edge and drowns in milk. Given\n * as a FRACTION of the touch blob, not a number: the blob already arrives in device units, so\n * the radius automatically follows both screen density and the element's size. At rest it's\n * zero, and the sample stays a single one. */\nconst float VG_INK_DEFOCUS = ${INK.defocus};\n\nhalf4 vgPack(half3 c, float a) { return half4(c * half(a), half(a)); }\n\n// What bleeds into the shadow is the COLOR of the surroundings, not its brightness: the hue's own\n// lightness is subtracted out of it, otherwise the shadow pales and loses depth instead of\n// warming up. Strength is set by how much light there is around: over a nearly black backdrop\n// there's nothing to bleed into the shadow.\n// JS twin in shadow-tint.ts, which the DOM renderer reads: same derivation, same reason.\nfloat3 vgShadowTint(float3 ambient) {\n float peak = max(max(ambient.r, ambient.g), max(ambient.b, 0.001));\n float3 hue = ambient / peak;\n float lumaWeights = dot(ambient, float3(0.2126, 0.7152, 0.0722));\n return (hue - dot(hue, float3(0.2126, 0.7152, 0.0722))) * lumaWeights;\n}\n\nhalf3 vgHeat(float v) {\n float x = clamp(v, 0.0, 1.0);\n return half3(half(clamp(x * 2.2 - 0.2, 0.0, 1.0)),\n half(clamp(1.0 - abs(x - 0.5) * 2.2, 0.0, 1.0)),\n half(clamp(1.2 - x * 2.4, 0.0, 1.0)));\n}\n\nhalf4 main(float2 xy) {\n float2 p = vgTouchWarp(xy - u_center, u_touch, u_pull, u_touchPress, u_touchRadius, u_wave.x, u_wave.y);\n\n // Inverse deformation: stretch A along the pull vector, compression 1/sqrt(A) across it. This\n // exact law mirrors the transform of the live backdrop under the canvas, otherwise the two\n // would drift apart. There's NO motion geometry here — no pull, no press-driven bulge. All of\n // that is done by a single wrapper transform, the same one that carries the native lens: the\n // shader doesn't touch it, and two pipelines driving one motion would drift apart by a frame,\n // making the layers visible as separate.\n\n float halfMin = max(min(u_halfSize.x, u_halfSize.y), 1.0);\n float bevel = max(u_bevel, 1.0);\n\n float sd = vgScene(p, u_halfSize, u_corner, u_morphOffset, u_morphHalf, u_morphCorner, u_morphK,\n u_morph2Offset, u_morph2Half, u_morph2Corner);\n float t = vgBevelT(sd, bevel);\n float2 n = vgSceneNormal(p, u_halfSize, u_corner, u_morphOffset, u_morphHalf, u_morphCorner, u_morphK,\n u_morph2Offset, u_morph2Half, u_morph2Corner);\n float3 N = normalize(float3(n * vgBevelSlope(t), 1.0));\n\n // Progress is an active state turned into a field: the played fraction shines and glows\n // exactly as much as an active element shines whole. This does NOT touch ink: recoloring text\n // as the track advances would mean changing it mid-word.\n float lit = max(u_active, vgProgress(p, u_halfSize, u_progress));\n\n // The highlight and the rim are computed by the lens: reflection is a function of the\n // surroundings, and only the lens can see them.\n\n if (u_debug > 0.5) {\n float inMask = 1.0 - smoothstep(-1.0, 1.0, sd);\n if (u_debug < 1.5) {\n float band = abs(fract(sd / (halfMin * 0.22)) - 0.5) * 2.0;\n half3 c = sd < 0.0 ? half3(0.20, 0.62, 1.0) : half3(1.0, 0.42, 0.22);\n return vgPack(c * half(0.25 + band * 0.75), 1.0);\n }\n if (u_debug < 2.5) { return vgPack(half3(1.0), inMask); }\n if (u_debug < 3.5) { return vgPack(vgHeat(t), inMask); }\n // Fresnel now lives in the lens; here we show its SHAPE — how grazing the view is.\n if (u_debug < 4.5) { return vgPack(vgHeat(1.0 - clamp(N.z, 0.0, 1.0)), inMask); }\n if (u_debug < 5.5) {\n float push = pow(t, VG_FALLOFF) * u_refraction;\n return vgPack(vgHeat(push), inMask);\n }\n if (u_debug < 6.5) { return half4(0.0); }\n if (u_debug < 7.5) { return half4(0.0); }\n // Dispersion is computed by the lens; here is the field it grows along.\n if (u_debug < 8.5) { return vgPack(vgHeat(u_dispersion * t * t), inMask); }\n if (u_debug < 9.5) { return vgPack(half3(N * 0.5 + 0.5), inMask); }\n // spectral and adapt are shown by the LENS — the surface has to get out of the way.\n return half4(0.0);\n }\n\n // The shadow and the halo live OUTSIDE the shape: inside, their place is taken by the glass\n // itself. Separation from the content rests entirely on the shadow — without it the surface\n // would sit ON the picture, not above it. The shadow only lives at the rim and outside it: at\n // sd < −1 the outside multiplier is already zero. Computing it deep inside the shape would be\n // a wasted FULL SDF evaluation for every such pixel, and the body covers almost the whole\n // area. The branch here is on the coordinate, but only the threads right at the rim actually\n // diverge.\n float shade = 0.0;\n float halo = 0.0;\n if (sd > -1.0) {\n float outside = smoothstep(-1.0, 1.0, sd);\n // Under the finger a button moves TOWARD the backdrop, and the shadow tightens: it's exactly\n // the gap between the two. A control rising into glass (a switch knob, a slider handle —\n // reference §5) moves AWAY from it, and then the shadow spreads out instead. Only the app\n // knows which one applies, so the direction arrives as a fraction: 0 is sinking in, the way\n // every element behaved before this rule existed.\n float reach = u_shadowReach * (1.0 + mix(-0.25, 0.55, u_lift) * u_press);\n // The shadow is soft and wide, shifted downward (M 11:58): it's what holds the element's\n // separation from the content.\n float sdDrop = vgScene(p - float2(0.0, reach * 0.35), u_halfSize, u_corner,\n u_morphOffset, u_morphHalf, u_morphCorner, u_morphK,\n u_morph2Offset, u_morph2Half, u_morph2Corner);\n float amb = 1.0 - smoothstep(-reach * 0.3, reach, sdDrop);\n // THE GAP UNDER A RAISED ELEMENT IS LIT: right at the outline the shadow is WEAKER than\n // further down. Otherwise the minimum sits right against the silhouette — both parts of the\n // shadow are monotonic in distance.\n float gap = smoothstep(0.0, max(reach * VG_GAP_REACH, 1.0), max(sd, 0.0));\n shade = amb * amb * 0.13 * mix(VG_GAP_LIGHT, 1.0, gap) * outside * u_shadow * u_appear;\n halo = 1.0 - smoothstep(0.0, reach * 0.30, max(sd, 0.0));\n halo = halo * halo * lit * 0.10 * outside * u_appear;\n }\n\n if (sd > 1.0) {\n // Ambient light bleeds into the shadow (219 @8:22): it isn't a black hole, it's tinted by\n // whatever lies nearby. Otherwise an element over colored content would hang over a gray\n // blob.\n half3 spill = max(half3(half(halo)) + half3(vgShadowTint(u_ambient)) * half(VG_SHADOW_TINT * shade * (1.0 - halo)), half3(0.0));\n return half4(spill, half(halo + shade * (1.0 - halo)));\n }\n\n float density = u_tint.w;\n float inner = clamp(-sd / halfMin, 0.0, 1.0);\n half3 col = half3(u_tint.rgb);\n\n // The tint is LIGHT and weak, not dark: dark glass over dark content disappears, and has to be\n // held together with a heavy rim — which makes the surface read as a chrome bead. Tint density\n // at the bevel is a separate value: physically the bevel bends light harder, but it doesn't\n // get hazier. Coupled together, the two produced a milky ring around the whole outline.\n float body = mix(VG_BODY_DENSITY, VG_BODY_DENSITY * u_edgeDensity, smoothstep(0.10, 0.62, t)) * density;\n float vignette = smoothstep(0.15, 1.0, inner) * 0.19 * density;\n // There's NO density boost on press here. It was meant as \"the element stands out more under\n // the finger,\" but it pulls the body toward the tint, and the tint depends on polarity: over a\n // light backdrop (dark polarity) press ended up DARKENING the element. Presence is shown\n // instead by the vgTouchWarp field's deformation and the highlight blooming below — neither\n // cares about the sign of polarity.\n float a = max(body, vignette);\n\n // Active state does NOT touch the body's color. Mixing in a fixed gray here flipped the\n // effect's sign depending on the backdrop: over dark the element lightened, over light it\n // darkened, for the exact same state. Active state is shown instead by the highlight and rim\n // lighting above: neither cares about the backdrop.\n\n // THE ICON SITS BENEATH THE SURFACE, it isn't stuck on top of it: it used to be mixed in last,\n // over the highlight, and read as a flat sticker on volumetric glass. At the rim the normal\n // displaces it, like everything seen through the glass, while the highlight sits ON TOP — it\n // lives on the surface itself.\n //\n // There is NO shadow under the icon here. It used to be made from the difference of two offset\n // mask samples and produced a second outline along the edge — the icon's borders looked\n // ragged.\n // THE INK'S SHIFT IS SET BY ITS OWN DEPTH, NOT BY BEVEL WIDTH. The ink sits right at the\n // surface, and it's displaced by exactly the thin layer of glass above it — not by how wide a\n // bevel this particular piece of glass happens to have. It used to be a fraction of the bevel:\n // on thin glass the shift came out to 4 dp and everything lined up, but on thick glass it was\n // 8, and cover art inset from the edge by 6 stretched toward the rim and spilled past its\n // bounds. The ceiling is absolute: ink has one depth regardless of the glass.\n float2 inkShift = n * (min(u_bevel * 0.5, VG_INK_DEPTH) * t);\n float2 inkUv = u_center + p - inkShift;\n\n // INK LOSES SHARPNESS UNDER THE FINGER (reference §6): in the reference, a glyph under\n // pressure drowns in milk rather than simply getting lighter. This branch is uniform across\n // the whole element: press arrives as a uniform, so defocus costs nothing while the element\n // isn't being touched.\n float defocus = VG_INK_DEFOCUS * u_touchRadius * u_touchPress;\n float2 inkDx = float2(defocus, 0.0);\n float2 inkDy = float2(0.0, defocus);\n\n half4 ink;\n half4 over;\n if (defocus > 0.01) {\n // A cross around the center with double weight in the middle: the ink mask is\n // high-contrast, and four samples are enough for a stroke to stop holding its edge.\n ink = (u_icon.eval((inkUv - inkDx) * u_iconScale)\n + u_icon.eval((inkUv + inkDx) * u_iconScale)\n + u_icon.eval((inkUv - inkDy) * u_iconScale)\n + u_icon.eval((inkUv + inkDy) * u_iconScale)\n + u_icon.eval(inkUv * u_iconScale) * 2.0) * half(u_iconOn / 6.0);\n // Colored content lives in the same plane as the ink and blurs together with it: different\n // sharpness for two layers in one plane would read as a defect, not a press.\n over = (u_overlay.eval((inkUv - inkDx) * u_iconScale)\n + u_overlay.eval((inkUv + inkDx) * u_iconScale)\n + u_overlay.eval((inkUv - inkDy) * u_iconScale)\n + u_overlay.eval((inkUv + inkDy) * u_iconScale)\n + u_overlay.eval(inkUv * u_iconScale) * 2.0) * half(u_overlayOn * u_appear / 6.0);\n } else {\n ink = u_icon.eval(inkUv * u_iconScale) * half(u_iconOn);\n over = u_overlay.eval(inkUv * u_iconScale) * half(u_overlayOn * u_appear);\n }\n\n // The backing under the ink is held by the LENS (body density from the probe), not the\n // surface: legibility is a property of the material, and computing it here a second time\n // would give two different answers to one question.\n\n // Layers are composited premultiplied-over: in straight alpha, a nearly transparent dark body\n // used to pull the color of semi-transparent ink and the finger's light toward itself — letter\n // edges grayed out, pressing darkened things.\n half3 pm = col * half(a);\n half inkA = ink.g * half(mix(0.82, 1.0, u_active) * u_appear);\n half3 inkCol = mix(half3(u_inkIdle.rgb), half3(u_inkActive.rgb), half(u_active));\n pm = pm * (1.0 - inkA) + inkCol * inkA;\n a = a + float(inkA) * (1.0 - a);\n\n // COLORED CONTENT LIVES IN THE SAME PLACE AS THE INK — inside the material, at the same\n // coordinate. Otherwise deformation would carry them apart: on press, the title and artist\n // name would shake together with the surface while the cover art stayed put, because it used\n // to be a separate layer on top of the glass. Polarity doesn't touch it — it has its own\n // color, and there's nothing to swap it for.\n pm = pm * (1.0 - over.a) + over.rgb * over.a;\n a = a + float(over.a) * (1.0 - a);\n\n // Light from the finger is gathered by the LENS: it's a concentration of the surroundings, not\n // the glass's own whiteness. The surface is left with only what the glass spills outward — the\n // halo above.\n\n float mask = 1.0 - smoothstep(-1.0, 1.0, sd);\n pm = clamp(pm, half3(0.0), half3(1.0)) * half(mask);\n a *= mask;\n pm = max(pm + half3(vgShadowTint(u_ambient)) * half(VG_SHADOW_TINT * shade * (1.0 - a)), half3(0.0));\n return half4(pm, half(a + shade * (1.0 - a)));\n}\n`;\n","// WebGL2 target. Translates the same AGSL text (SDF + body, see `targets/agsl.ts`) into GLSL\n// ES 3.0. Each conversion rule is its own function with its own test in\n// `__tests__/targets.test.ts`. Three spots are NOT mechanical: the coordinate origin, units of\n// measure (settled in the web renderer's own setup, not here), and sampler filtering (also the\n// renderer's concern). What's here is text conversion only.\n\n/**\n * The sampler-size uniform's name, for translating `NAME.eval(coord)` → `texture(NAME, coord / SIZE)`.\n * A new uniform — there's no equivalent in AGSL: `.eval()` has the size implicitly known to the\n * shader, while GLSL's `texture()` needs a normalized coordinate. One per `uniform shader`/\n * `uniform sampler2D`, its name derived from the sampler's name rather than hardcoded.\n */\nfunction sizeUniformName(sampler: string): string {\n return sampler.startsWith('u_') ? `${sampler}Size` : `u_${sampler}Size`;\n}\n\n/** float2/float3/float4 → vec2/vec3/vec4. */\nexport function convertVecTypes(src: string): string {\n return src.replace(/\\bfloat([234])\\b/g, 'vec$1');\n}\n\n/** half/half2/half3/half4 → float/vec2/vec3/vec4, including half(x) → float(x) casts. */\nexport function convertHalfTypes(src: string): string {\n return src.replace(/\\bhalf([234])\\b/g, 'vec$1').replace(/\\bhalf\\b/g, 'float');\n}\n\n/** `uniform shader NAME;` → `uniform sampler2D NAME;` + a new `uniform vec2 <sizeUniform>;`. */\nexport function convertShaderUniforms(src: string): string {\n return src.replace(/uniform\\s+shader\\s+(\\w+)\\s*;/g, (_match, name: string) => {\n return `uniform sampler2D ${name};\\nuniform vec2 ${sizeUniformName(name)};`;\n });\n}\n\n/**\n * `NAME.eval(coord)` → `texture(NAME, (coord) / <sizeUniform>)` for each declared\n * `uniform sampler2D`. The bounds of `coord` are found by scanning parens, not with a regex: the\n * arguments are nested calls (`content.eval(vgInContent(s + dR - e))`) with commas and\n * parentheses inside.\n */\nexport function convertEvalCalls(src: string): string {\n const samplers = new Set<string>();\n const declRe = /uniform\\s+sampler2D\\s+(\\w+)\\s*;/g;\n for (let m = declRe.exec(src); m; m = declRe.exec(src)) samplers.add(m[1]);\n if (samplers.size === 0) return src;\n\n const callRe = /(\\w+)\\.eval\\(/g;\n let out = '';\n let cursor = 0;\n for (let m = callRe.exec(src); m; m = callRe.exec(src)) {\n const name = m[1];\n if (!samplers.has(name)) continue;\n const argStart = m.index + m[0].length;\n let depth = 1;\n let i = argStart;\n while (i < src.length && depth > 0) {\n if (src[i] === '(') depth += 1;\n else if (src[i] === ')') depth -= 1;\n i += 1;\n }\n const args = src.slice(argStart, i - 1);\n out += src.slice(cursor, m.index);\n out += `texture(${name}, (${args}) / ${sizeUniformName(name)})`;\n cursor = i;\n callRe.lastIndex = i;\n }\n out += src.slice(cursor);\n return out;\n}\n\n/**\n * Early `return <expression>;` inside `main` → `fragColor = <expression>; return;`. Takes ONLY\n * the body of main (see `convertEntryPoint`) — ordinary `return`s in helper functions live\n * outside this text and are untouched. Expressions never contain a `;` of their own (shaders have\n * none — the only `;` lives inside `for(...)`, which isn't a return), so `[^;]+` is safe.\n */\nexport function convertReturns(mainBody: string): string {\n return mainBody.replace(/\\breturn\\s+([^;]+);/g, 'fragColor = $1; return;');\n}\n\nconst ENTRY_RE = /half4\\s+main\\s*\\(\\s*float2\\s+(\\w+)\\s*\\)\\s*\\{/;\n\n/**\n * `half4 main(float2 xy) { ... }` → `void main() { vec2 xy = <Y-flipped coordinate>; ...; }`.\n * AGSL gives `xy` in screen pixels, top-left origin, y down; `gl_FragCoord` is bottom-left\n * origin, y up. The flip via `u_resolution.y - gl_FragCoord.y` restores the original convention\n * (both coordinate systems center the pixel at `+0.5`, so no X shift or constant is needed).\n * `out vec4 fragColor` and `uniform vec2 u_resolution` are declared in the prologue (`addPrologue`).\n */\nexport function convertEntryPoint(src: string): string {\n const match = ENTRY_RE.exec(src);\n if (!match) {\n throw new Error('vireglass/targets/glsl: entry point \"half4 main(float2 xy)\" not found');\n }\n const bodyStart = match.index + match[0].length;\n let depth = 1;\n let i = bodyStart;\n while (i < src.length && depth > 0) {\n if (src[i] === '{') depth += 1;\n else if (src[i] === '}') depth -= 1;\n i += 1;\n }\n const bodyEnd = i - 1;\n const param = match[1];\n const body = convertReturns(src.slice(bodyStart, bodyEnd));\n const head = src.slice(0, match.index);\n const tail = src.slice(i);\n return (\n `${head}void main() {\\n` +\n ` vec2 ${param} = vec2(gl_FragCoord.x, u_resolution.y - gl_FragCoord.y);\\n` +\n `${body}}${tail}`\n );\n}\n\n/** `#version 300 es` + `precision highp float;` — no equivalent in AGSL, WebGL2 requires them as\n * the file's first lines. `out vec4 fragColor` and `uniform vec2 u_resolution` ride along here\n * too: both new, with no counterpart in the source, needed by exactly one entry point\n * (`convertEntryPoint`). */\nexport function addPrologue(src: string): string {\n return (\n '#version 300 es\\n' +\n 'precision highp float;\\n\\n' +\n 'out vec4 fragColor;\\n' +\n 'uniform vec2 u_resolution;\\n\\n' +\n src\n );\n}\n\n/** Full translation AGSL/SkSL → GLSL ES 3.0. Order matters: the entry point is found by its\n * literal AGSL signature BEFORE type conversion, otherwise `half4 main(float2 xy)` can't be\n * found. */\nexport function toGLSL(shaderSource: string): string {\n let out = shaderSource;\n out = convertEntryPoint(out);\n out = convertShaderUniforms(out);\n out = convertEvalCalls(out);\n out = convertVecTypes(out);\n out = convertHalfTypes(out);\n out = addPrologue(out);\n return out;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/surface-shader.ts","../src/targets/glsl.ts"],"names":[],"mappings":"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","file":"chunk-PM2SWCSF.js","sourcesContent":["import { INK, MEDIUM, SHADOW_GAP } from './law';\nimport { VG_FALLOFF, VG_SDF } from './sdf';\n\n/** Order matches `DEBUG_MODES` in `material.ts`: the index goes straight into `u_debug`. */\nexport const SURFACE_SHADER = `\nuniform shader u_icon;\n/** App-colored content ON the glass — cover art, a thumbnail. A separate layer from the ink\n * mask: that one is single-channel and colored by polarity, this one carries its own color\n * as-is. */\nuniform shader u_overlay;\n\nuniform float2 u_center;\nuniform float2 u_halfSize;\nuniform float u_corner;\nuniform float u_bevel;\nuniform float u_thickness;\nuniform float2 u_morphOffset;\nuniform float2 u_morphHalf;\nuniform float u_morphCorner;\nuniform float u_morphK;\nuniform float2 u_morph2Offset;\nuniform float2 u_morph2Half;\nuniform float u_morph2Corner;\n\nuniform float u_press;\nuniform float u_active;\n/* 0 — the element sinks in under the finger, 1 — it rises into glass (reference §5). */\nuniform float u_lift;\n\nuniform float u_edgeDensity;\nuniform float u_dispersion;\nuniform float u_refraction;\nuniform float4 u_tint;\nuniform float u_shadow;\nuniform float u_shadowReach;\nuniform float3 u_ambient;\nuniform float u_presence;\nuniform float u_progress;\nuniform float u_appear;\nuniform float u_debug;\n\nuniform float u_iconOn;\nuniform float u_overlayOn;\nuniform float u_iconScale;\nuniform float4 u_inkIdle;\nuniform float4 u_inkActive;\nuniform float2 u_touch;\nuniform float2 u_pull;\nuniform float u_touchPress;\nuniform float u_touchRadius;\nuniform float2 u_wave;\n\n${VG_SDF}\n\nconst float VG_FALLOFF = ${VG_FALLOFF};\n\n// Tint density in the flat middle; at the bevel it's multiplied by u_edgeDensity.\nconst float VG_BODY_DENSITY = ${MEDIUM.bodyDensityFlat};\n/* Fraction of the ambient hue in the shadow. The shadow has to stay a shadow, not a colored\n * blob. */\nconst float VG_SHADOW_TINT = ${SHADOW_GAP.tint}.0;\n// How much weaker the shadow is right at the outline than under the middle of the gap, and what\n// fraction of the offset it takes to reach full depth. In the reference the shadow's minimum\n// sits below the rim, not right at it.\nconst float VG_GAP_LIGHT = ${SHADOW_GAP.light};\nconst float VG_GAP_REACH = ${SHADOW_GAP.reach};\n/* Depth of the ink beneath the surface, dp. The normal displaces it by exactly this much right\n * at the rim. */\nconst float VG_INK_DEPTH = ${INK.depth}.0;\n/* Ink defocus under the finger (reference §6): a glyph loses its edge and drowns in milk. Given\n * as a FRACTION of the touch blob, not a number: the blob already arrives in device units, so\n * the radius automatically follows both screen density and the element's size. At rest it's\n * zero, and the sample stays a single one. */\nconst float VG_INK_DEFOCUS = ${INK.defocus};\n\nhalf4 vgPack(half3 c, float a) { return half4(c * half(a), half(a)); }\n\n// What bleeds into the shadow is the COLOR of the surroundings, not its brightness: the hue's own\n// lightness is subtracted out of it, otherwise the shadow pales and loses depth instead of\n// warming up. Strength is set by how much light there is around: over a nearly black backdrop\n// there's nothing to bleed into the shadow.\n// JS twin in shadow-tint.ts, which the DOM renderer reads: same derivation, same reason.\nfloat3 vgShadowTint(float3 ambient) {\n float peak = max(max(ambient.r, ambient.g), max(ambient.b, 0.001));\n float3 hue = ambient / peak;\n float lumaWeights = dot(ambient, float3(0.2126, 0.7152, 0.0722));\n return (hue - dot(hue, float3(0.2126, 0.7152, 0.0722))) * lumaWeights;\n}\n\nhalf3 vgHeat(float v) {\n float x = clamp(v, 0.0, 1.0);\n return half3(half(clamp(x * 2.2 - 0.2, 0.0, 1.0)),\n half(clamp(1.0 - abs(x - 0.5) * 2.2, 0.0, 1.0)),\n half(clamp(1.2 - x * 2.4, 0.0, 1.0)));\n}\n\nhalf4 main(float2 xy) {\n float2 p = vgTouchWarp(xy - u_center, u_touch, u_pull, u_touchPress, u_touchRadius, u_wave.x, u_wave.y);\n\n // Inverse deformation: stretch A along the pull vector, compression 1/sqrt(A) across it. This\n // exact law mirrors the transform of the live backdrop under the canvas, otherwise the two\n // would drift apart. There's NO motion geometry here — no pull, no press-driven bulge. All of\n // that is done by a single wrapper transform, the same one that carries the native lens: the\n // shader doesn't touch it, and two pipelines driving one motion would drift apart by a frame,\n // making the layers visible as separate.\n\n float halfMin = max(min(u_halfSize.x, u_halfSize.y), 1.0);\n float bevel = max(u_bevel, 1.0);\n\n float sd = vgScene(p, u_halfSize, u_corner, u_morphOffset, u_morphHalf, u_morphCorner, u_morphK,\n u_morph2Offset, u_morph2Half, u_morph2Corner);\n float t = vgBevelT(sd, bevel);\n float2 n = vgSceneNormal(p, u_halfSize, u_corner, u_morphOffset, u_morphHalf, u_morphCorner, u_morphK,\n u_morph2Offset, u_morph2Half, u_morph2Corner);\n float3 N = normalize(float3(n * vgBevelSlope(t), 1.0));\n\n // Progress is an active state turned into a field: the played fraction shines and glows\n // exactly as much as an active element shines whole. This does NOT touch ink: recoloring text\n // as the track advances would mean changing it mid-word.\n float lit = max(u_active, vgProgress(p, u_halfSize, u_progress));\n\n // The highlight and the rim are computed by the lens: reflection is a function of the\n // surroundings, and only the lens can see them.\n\n if (u_debug > 0.5) {\n float inMask = 1.0 - smoothstep(-1.0, 1.0, sd);\n if (u_debug < 1.5) {\n float band = abs(fract(sd / (halfMin * 0.22)) - 0.5) * 2.0;\n half3 c = sd < 0.0 ? half3(0.20, 0.62, 1.0) : half3(1.0, 0.42, 0.22);\n return vgPack(c * half(0.25 + band * 0.75), 1.0);\n }\n if (u_debug < 2.5) { return vgPack(half3(1.0), inMask); }\n if (u_debug < 3.5) { return vgPack(vgHeat(t), inMask); }\n // Fresnel now lives in the lens; here we show its SHAPE — how grazing the view is.\n if (u_debug < 4.5) { return vgPack(vgHeat(1.0 - clamp(N.z, 0.0, 1.0)), inMask); }\n if (u_debug < 5.5) {\n float push = pow(t, VG_FALLOFF) * u_refraction;\n return vgPack(vgHeat(push), inMask);\n }\n if (u_debug < 6.5) { return half4(0.0); }\n if (u_debug < 7.5) { return half4(0.0); }\n // Dispersion is computed by the lens; here is the field it grows along.\n if (u_debug < 8.5) { return vgPack(vgHeat(u_dispersion * t * t), inMask); }\n if (u_debug < 9.5) { return vgPack(half3(N * 0.5 + 0.5), inMask); }\n // spectral and adapt are shown by the LENS — the surface has to get out of the way.\n return half4(0.0);\n }\n\n // The shadow and the halo live OUTSIDE the shape: inside, their place is taken by the glass\n // itself. Separation from the content rests entirely on the shadow — without it the surface\n // would sit ON the picture, not above it. The shadow only lives at the rim and outside it: at\n // sd < −1 the outside multiplier is already zero. Computing it deep inside the shape would be\n // a wasted FULL SDF evaluation for every such pixel, and the body covers almost the whole\n // area. The branch here is on the coordinate, but only the threads right at the rim actually\n // diverge.\n float shade = 0.0;\n float halo = 0.0;\n if (sd > -1.0) {\n float outside = smoothstep(-1.0, 1.0, sd);\n // Under the finger a button moves TOWARD the backdrop, and the shadow tightens: it's exactly\n // the gap between the two. A control rising into glass (a switch knob, a slider handle —\n // reference §5) moves AWAY from it, and then the shadow spreads out instead. Only the app\n // knows which one applies, so the direction arrives as a fraction: 0 is sinking in, the way\n // every element behaved before this rule existed.\n float reach = u_shadowReach * (1.0 + mix(-0.25, 0.55, u_lift) * u_press);\n // The shadow is soft and wide, shifted downward (M 11:58): it's what holds the element's\n // separation from the content.\n float sdDrop = vgScene(p - float2(0.0, reach * 0.35), u_halfSize, u_corner,\n u_morphOffset, u_morphHalf, u_morphCorner, u_morphK,\n u_morph2Offset, u_morph2Half, u_morph2Corner);\n float amb = 1.0 - smoothstep(-reach * 0.3, reach, sdDrop);\n // THE GAP UNDER A RAISED ELEMENT IS LIT: right at the outline the shadow is WEAKER than\n // further down. Otherwise the minimum sits right against the silhouette — both parts of the\n // shadow are monotonic in distance.\n float gap = smoothstep(0.0, max(reach * VG_GAP_REACH, 1.0), max(sd, 0.0));\n shade = amb * amb * 0.13 * mix(VG_GAP_LIGHT, 1.0, gap) * outside * u_shadow * u_appear;\n halo = 1.0 - smoothstep(0.0, reach * 0.30, max(sd, 0.0));\n halo = halo * halo * lit * 0.10 * outside * u_appear;\n }\n\n if (sd > 1.0) {\n // Ambient light bleeds into the shadow (219 @8:22): it isn't a black hole, it's tinted by\n // whatever lies nearby. Otherwise an element over colored content would hang over a gray\n // blob.\n half3 spill = max(half3(half(halo)) + half3(vgShadowTint(u_ambient)) * half(VG_SHADOW_TINT * shade * (1.0 - halo)), half3(0.0));\n return half4(spill, half(halo + shade * (1.0 - halo)));\n }\n\n float density = u_tint.w;\n float inner = clamp(-sd / halfMin, 0.0, 1.0);\n half3 col = half3(u_tint.rgb);\n\n // The tint is LIGHT and weak, not dark: dark glass over dark content disappears, and has to be\n // held together with a heavy rim — which makes the surface read as a chrome bead. Tint density\n // at the bevel is a separate value: physically the bevel bends light harder, but it doesn't\n // get hazier. Coupled together, the two produced a milky ring around the whole outline.\n float body = mix(VG_BODY_DENSITY, VG_BODY_DENSITY * u_edgeDensity, smoothstep(0.10, 0.62, t)) * density;\n float vignette = smoothstep(0.15, 1.0, inner) * 0.19 * density;\n // There's NO density boost on press here. It was meant as \"the element stands out more under\n // the finger,\" but it pulls the body toward the tint, and the tint depends on polarity: over a\n // light backdrop (dark polarity) press ended up DARKENING the element. Presence is shown\n // instead by the vgTouchWarp field's deformation and the highlight blooming below — neither\n // cares about the sign of polarity.\n float a = max(body, vignette);\n\n // Active state does NOT touch the body's color. Mixing in a fixed gray here flipped the\n // effect's sign depending on the backdrop: over dark the element lightened, over light it\n // darkened, for the exact same state. Active state is shown instead by the highlight and rim\n // lighting above: neither cares about the backdrop.\n\n // THE ICON SITS BENEATH THE SURFACE, it isn't stuck on top of it: it used to be mixed in last,\n // over the highlight, and read as a flat sticker on volumetric glass. At the rim the normal\n // displaces it, like everything seen through the glass, while the highlight sits ON TOP — it\n // lives on the surface itself.\n //\n // There is NO shadow under the icon here. It used to be made from the difference of two offset\n // mask samples and produced a second outline along the edge — the icon's borders looked\n // ragged.\n // THE INK'S SHIFT IS SET BY ITS OWN DEPTH, NOT BY BEVEL WIDTH. The ink sits right at the\n // surface, and it's displaced by exactly the thin layer of glass above it — not by how wide a\n // bevel this particular piece of glass happens to have. It used to be a fraction of the bevel:\n // on thin glass the shift came out to 4 dp and everything lined up, but on thick glass it was\n // 8, and cover art inset from the edge by 6 stretched toward the rim and spilled past its\n // bounds. The ceiling is absolute: ink has one depth regardless of the glass.\n float2 inkShift = n * (min(u_bevel * 0.5, VG_INK_DEPTH) * t);\n float2 inkUv = u_center + p - inkShift;\n\n // INK LOSES SHARPNESS UNDER THE FINGER (reference §6): in the reference, a glyph under\n // pressure drowns in milk rather than simply getting lighter. This branch is uniform across\n // the whole element: press arrives as a uniform, so defocus costs nothing while the element\n // isn't being touched.\n float defocus = VG_INK_DEFOCUS * u_touchRadius * u_touchPress;\n float2 inkDx = float2(defocus, 0.0);\n float2 inkDy = float2(0.0, defocus);\n\n half4 ink;\n half4 over;\n if (defocus > 0.01) {\n // A cross around the center with double weight in the middle: the ink mask is\n // high-contrast, and four samples are enough for a stroke to stop holding its edge.\n ink = (u_icon.eval((inkUv - inkDx) * u_iconScale)\n + u_icon.eval((inkUv + inkDx) * u_iconScale)\n + u_icon.eval((inkUv - inkDy) * u_iconScale)\n + u_icon.eval((inkUv + inkDy) * u_iconScale)\n + u_icon.eval(inkUv * u_iconScale) * 2.0) * half(u_iconOn / 6.0);\n // Colored content lives in the same plane as the ink and blurs together with it: different\n // sharpness for two layers in one plane would read as a defect, not a press.\n over = (u_overlay.eval((inkUv - inkDx) * u_iconScale)\n + u_overlay.eval((inkUv + inkDx) * u_iconScale)\n + u_overlay.eval((inkUv - inkDy) * u_iconScale)\n + u_overlay.eval((inkUv + inkDy) * u_iconScale)\n + u_overlay.eval(inkUv * u_iconScale) * 2.0) * half(u_overlayOn * u_appear / 6.0);\n } else {\n ink = u_icon.eval(inkUv * u_iconScale) * half(u_iconOn);\n over = u_overlay.eval(inkUv * u_iconScale) * half(u_overlayOn * u_appear);\n }\n\n // The backing under the ink is held by the LENS (body density from the probe), not the\n // surface: legibility is a property of the material, and computing it here a second time\n // would give two different answers to one question.\n\n // Layers are composited premultiplied-over: in straight alpha, a nearly transparent dark body\n // used to pull the color of semi-transparent ink and the finger's light toward itself — letter\n // edges grayed out, pressing darkened things.\n half3 pm = col * half(a);\n half inkA = ink.g * half(mix(0.82, 1.0, u_active) * u_appear);\n half3 inkCol = mix(half3(u_inkIdle.rgb), half3(u_inkActive.rgb), half(u_active));\n pm = pm * (1.0 - inkA) + inkCol * inkA;\n a = a + float(inkA) * (1.0 - a);\n\n // COLORED CONTENT LIVES IN THE SAME PLACE AS THE INK — inside the material, at the same\n // coordinate. Otherwise deformation would carry them apart: on press, the title and artist\n // name would shake together with the surface while the cover art stayed put, because it used\n // to be a separate layer on top of the glass. Polarity doesn't touch it — it has its own\n // color, and there's nothing to swap it for.\n pm = pm * (1.0 - over.a) + over.rgb * over.a;\n a = a + float(over.a) * (1.0 - a);\n\n // Light from the finger is gathered by the LENS: it's a concentration of the surroundings, not\n // the glass's own whiteness. The surface is left with only what the glass spills outward — the\n // halo above.\n\n float mask = 1.0 - smoothstep(-1.0, 1.0, sd);\n pm = clamp(pm, half3(0.0), half3(1.0)) * half(mask);\n a *= mask;\n pm = max(pm + half3(vgShadowTint(u_ambient)) * half(VG_SHADOW_TINT * shade * (1.0 - a)), half3(0.0));\n return half4(pm, half(a + shade * (1.0 - a)));\n}\n`;\n","// WebGL2 target. Translates the same AGSL text (SDF + body, see `targets/agsl.ts`) into GLSL\n// ES 3.0. Each conversion rule is its own function with its own test in\n// `__tests__/targets.test.ts`. Three spots are NOT mechanical: the coordinate origin, units of\n// measure (settled in the web renderer's own setup, not here), and sampler filtering (also the\n// renderer's concern). What's here is text conversion only.\n\n/**\n * The sampler-size uniform's name, for translating `NAME.eval(coord)` → `texture(NAME, coord / SIZE)`.\n * A new uniform — there's no equivalent in AGSL: `.eval()` has the size implicitly known to the\n * shader, while GLSL's `texture()` needs a normalized coordinate. One per `uniform shader`/\n * `uniform sampler2D`, its name derived from the sampler's name rather than hardcoded.\n */\nfunction sizeUniformName(sampler: string): string {\n return sampler.startsWith('u_') ? `${sampler}Size` : `u_${sampler}Size`;\n}\n\n/** float2/float3/float4 → vec2/vec3/vec4. */\nexport function convertVecTypes(src: string): string {\n return src.replace(/\\bfloat([234])\\b/g, 'vec$1');\n}\n\n/** half/half2/half3/half4 → float/vec2/vec3/vec4, including half(x) → float(x) casts. */\nexport function convertHalfTypes(src: string): string {\n return src.replace(/\\bhalf([234])\\b/g, 'vec$1').replace(/\\bhalf\\b/g, 'float');\n}\n\n/** `uniform shader NAME;` → `uniform sampler2D NAME;` + a new `uniform vec2 <sizeUniform>;`. */\nexport function convertShaderUniforms(src: string): string {\n return src.replace(/uniform\\s+shader\\s+(\\w+)\\s*;/g, (_match, name: string) => {\n return `uniform sampler2D ${name};\\nuniform vec2 ${sizeUniformName(name)};`;\n });\n}\n\n/**\n * `NAME.eval(coord)` → `texture(NAME, (coord) / <sizeUniform>)` for each declared\n * `uniform sampler2D`. The bounds of `coord` are found by scanning parens, not with a regex: the\n * arguments are nested calls (`content.eval(vgInContent(s + dR - e))`) with commas and\n * parentheses inside.\n */\nexport function convertEvalCalls(src: string): string {\n const samplers = new Set<string>();\n const declRe = /uniform\\s+sampler2D\\s+(\\w+)\\s*;/g;\n for (let m = declRe.exec(src); m; m = declRe.exec(src)) samplers.add(m[1]);\n if (samplers.size === 0) return src;\n\n const callRe = /(\\w+)\\.eval\\(/g;\n let out = '';\n let cursor = 0;\n for (let m = callRe.exec(src); m; m = callRe.exec(src)) {\n const name = m[1];\n if (!samplers.has(name)) continue;\n const argStart = m.index + m[0].length;\n let depth = 1;\n let i = argStart;\n while (i < src.length && depth > 0) {\n if (src[i] === '(') depth += 1;\n else if (src[i] === ')') depth -= 1;\n i += 1;\n }\n const args = src.slice(argStart, i - 1);\n out += src.slice(cursor, m.index);\n out += `texture(${name}, (${args}) / ${sizeUniformName(name)})`;\n cursor = i;\n callRe.lastIndex = i;\n }\n out += src.slice(cursor);\n return out;\n}\n\n/**\n * Early `return <expression>;` inside `main` → `fragColor = <expression>; return;`. Takes ONLY\n * the body of main (see `convertEntryPoint`) — ordinary `return`s in helper functions live\n * outside this text and are untouched. Expressions never contain a `;` of their own (shaders have\n * none — the only `;` lives inside `for(...)`, which isn't a return), so `[^;]+` is safe.\n */\nexport function convertReturns(mainBody: string): string {\n return mainBody.replace(/\\breturn\\s+([^;]+);/g, 'fragColor = $1; return;');\n}\n\nconst ENTRY_RE = /half4\\s+main\\s*\\(\\s*float2\\s+(\\w+)\\s*\\)\\s*\\{/;\n\n/**\n * `half4 main(float2 xy) { ... }` → `void main() { vec2 xy = <Y-flipped coordinate>; ...; }`.\n * AGSL gives `xy` in screen pixels, top-left origin, y down; `gl_FragCoord` is bottom-left\n * origin, y up. The flip via `u_resolution.y - gl_FragCoord.y` restores the original convention\n * (both coordinate systems center the pixel at `+0.5`, so no X shift or constant is needed).\n * `out vec4 fragColor` and `uniform vec2 u_resolution` are declared in the prologue (`addPrologue`).\n *\n * `xy` and `.eval()` are flipped together — but only inside a shader THIS function translates. A\n * `VireGlassBackdropPass` (`web/renderer.ts`) that reads its own previous frame from an FBO is raw\n * `gl_FragCoord`, unflipped; reusing AGSL-style helpers there needs an explicit flip of its own.\n */\nexport function convertEntryPoint(src: string): string {\n const match = ENTRY_RE.exec(src);\n if (!match) {\n throw new Error('vireglass/targets/glsl: entry point \"half4 main(float2 xy)\" not found');\n }\n const bodyStart = match.index + match[0].length;\n let depth = 1;\n let i = bodyStart;\n while (i < src.length && depth > 0) {\n if (src[i] === '{') depth += 1;\n else if (src[i] === '}') depth -= 1;\n i += 1;\n }\n const bodyEnd = i - 1;\n const param = match[1];\n const body = convertReturns(src.slice(bodyStart, bodyEnd));\n const head = src.slice(0, match.index);\n const tail = src.slice(i);\n return (\n `${head}void main() {\\n` +\n ` vec2 ${param} = vec2(gl_FragCoord.x, u_resolution.y - gl_FragCoord.y);\\n` +\n `${body}}${tail}`\n );\n}\n\n/** `#version 300 es` + `precision highp float;` — no equivalent in AGSL, WebGL2 requires them as\n * the file's first lines. `out vec4 fragColor` and `uniform vec2 u_resolution` ride along here\n * too: both new, with no counterpart in the source, needed by exactly one entry point\n * (`convertEntryPoint`). */\nexport function addPrologue(src: string): string {\n return (\n '#version 300 es\\n' +\n 'precision highp float;\\n\\n' +\n 'out vec4 fragColor;\\n' +\n 'uniform vec2 u_resolution;\\n\\n' +\n src\n );\n}\n\n/** Full translation AGSL/SkSL → GLSL ES 3.0. Order matters: the entry point is found by its\n * literal AGSL signature BEFORE type conversion, otherwise `half4 main(float2 xy)` can't be\n * found. */\nexport function toGLSL(shaderSource: string): string {\n let out = shaderSource;\n out = convertEntryPoint(out);\n out = convertShaderUniforms(out);\n out = convertEvalCalls(out);\n out = convertVecTypes(out);\n out = convertHalfTypes(out);\n out = addPrologue(out);\n return out;\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SIZE, SHADOW_DENSITY } from './chunk-
|
|
1
|
+
import { SIZE, SHADOW_DENSITY } from './chunk-MMPK6DCK.js';
|
|
2
2
|
|
|
3
3
|
// src/geometry.ts
|
|
4
4
|
var circleGeometry = (size) => ({
|
|
@@ -54,5 +54,5 @@ function shadowOpacityFrom(sample) {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
export { MAX_BEVEL_FRACTION, MAX_STRETCH, RIM_FRACTION, bevelDp, bevelFraction, capsuleGeometry, circleGeometry, halfMinDp, lensPadDp, morphReachDp, rimDp, roundedRectGeometry, shadowOpacity, shadowOpacityFrom, shadowReachDp, sizeDp, sizeGain, surfacePadDp, thicknessDp };
|
|
57
|
-
//# sourceMappingURL=chunk-
|
|
58
|
-
//# sourceMappingURL=chunk-
|
|
57
|
+
//# sourceMappingURL=chunk-SLRPHDV7.js.map
|
|
58
|
+
//# sourceMappingURL=chunk-SLRPHDV7.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/geometry.ts"],"names":[],"mappings":"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","file":"chunk-P2ZOODXD.js","sourcesContent":["import type { VireGlassOptics } from './material';\nimport { SHADOW_DENSITY, SIZE } from './law';\n\n/** Glass shape in dp. Circle and capsule are special cases of a rounded rectangle. */\nexport type VireGlassGeometry = {\n width: number;\n height: number;\n cornerRadius: number;\n};\n\nexport const circleGeometry = (size: number): VireGlassGeometry => ({\n width: size,\n height: size,\n cornerRadius: size / 2,\n});\n\nexport const capsuleGeometry = (width: number, height: number): VireGlassGeometry => ({\n width,\n height,\n cornerRadius: Math.min(width, height) / 2,\n});\n\nexport const roundedRectGeometry = (\n width: number,\n height: number,\n cornerRadius: number,\n): VireGlassGeometry => ({ width, height, cornerRadius });\n\nexport const halfMinDp = (g: VireGlassGeometry) => Math.max(Math.min(g.width, g.height) / 2, 1);\n\n/** Viewport margin for a drop that overshoots past the finger: a fraction of the element's half\n * size beyond the drag travel itself. The drop overshoots the body by no more than `dragLimit`,\n * and stitching adds smoothing on top of that — this term covers it too. */\nexport const MAX_STRETCH = 0.34;\n\n/**\n * How big this element reads, for the purpose of how thick its glass is.\n *\n * Not `halfMinDp`, which is half the narrower side and is the right measure for a bevel limit or a\n * contact radius but not for this: a 390x420 sheet and a 390x780 one have the same narrow side and\n * are not the same surface. Not the area either: a 900x8 rule has more area than a toolbar button\n * and is the thinnest glass in the interface.\n *\n * The geometric mean of the two respects both facts — the narrow side caps how much glass there\n * can be, and among shapes that share it, more surface reads as more glass. Derived, not\n * calibrated: it introduces no constant.\n */\nexport const sizeDp = (g: VireGlassGeometry) =>\n Math.sqrt(halfMinDp(g) * Math.max(Math.sqrt(g.width * g.height) / 2, 1));\n\n/**\n * Bigger element, thicker glass: a stronger lens, a deeper shadow (219 @6:36 — as glass \"flexes and\n * morphs to larger sizes, it simulates a thicker material with deeper shadows and more pronounced\n * lensing and refraction effects\"). Bevel and material thickness are specified for an element at a\n * reference size and grow as the square root of it.\n *\n * The ceiling is approached, never reached. A hard clip made every surface above it the same glass,\n * and above it was where the interface lives: a half-open sheet, a full-screen sheet, an iPad\n * sidebar and a 900px Mac panel all sat at exactly 2.4. The reference names sidebars as the LARGE\n * end of the range, so a model that stops responding before them has stopped obeying it.\n *\n * The curve below is the same square root; the join at `gainMin` is continuous in value and in\n * slope, and the asymptote is `gainMax` exactly as before. Both constants keep their values —\n * what changed is that saturation is smooth instead of a clip.\n */\nexport const sizeGain = (g: VireGlassGeometry) => {\n const raw = Math.sqrt(sizeDp(g) / SIZE.referenceDp);\n if (raw <= SIZE.gainMin) return SIZE.gainMin;\n const span = SIZE.gainMax - SIZE.gainMin;\n return SIZE.gainMin + span * (1 - Math.exp(-(raw - SIZE.gainMin) / span));\n};\n\n/** A bevel wider than this fraction of the half-size breaks the SDF: the roundings converge in\n * the middle. */\nexport const MAX_BEVEL_FRACTION = SIZE.maxBevelFraction;\n\nexport const bevelFraction = (g: VireGlassGeometry, o: VireGlassOptics) =>\n Math.min(MAX_BEVEL_FRACTION, (o.bevelDp * sizeGain(g)) / halfMinDp(g));\n\nexport const bevelDp = (g: VireGlassGeometry, o: VireGlassOptics) =>\n Math.max(bevelFraction(g, o) * halfMinDp(g), 1);\n\nexport const thicknessDp = (g: VireGlassGeometry, o: VireGlassOptics) =>\n o.thicknessDp * sizeGain(g);\n\n/** Glass height right at the silhouette, a fraction of thickness: without it there's nothing at\n * the edge to bend the ray. */\nexport const RIM_FRACTION = 0.85;\n\nexport const rimDp = (g: VireGlassGeometry, o: VireGlassOptics) => thicknessDp(g, o) * RIM_FRACTION;\n\n/** Bigger element, deeper and wider shadow (M 7:14). */\nexport const shadowReachDp = (g: VireGlassGeometry) =>\n Math.min(Math.max(halfMinDp(g) * 0.65, 6), 36);\n\n/** How far the second morph shape reaches past the first one's bounds. Without this margin the\n * fused shape gets clipped by the canvas edge and the experiment shows the wrong thing. */\nexport function morphReachDp(\n g: VireGlassGeometry,\n morph?: { offsetX: number; offsetY: number; width: number; height: number; smoothing: number },\n): number {\n if (!morph || morph.smoothing <= 0) return 0;\n return Math.max(\n 0,\n Math.abs(morph.offsetX) + morph.width / 2 - g.width / 2,\n Math.abs(morph.offsetY) + morph.height / 2 - g.height / 2,\n );\n}\n\n/**\n * Quantization step for the margin. The margin only needs to cover the sampling — its exact value\n * doesn't matter, but changing it every frame is expensive: the native lens view gets re-laid-out\n * and re-sets its `RenderEffect`. While dragging a panel or morphing, the margin used to drift\n * continuously, and that produced visible stutter. Round up to the step — size changes rarely.\n */\nconst PAD_STEP = 8;\nconst quantise = (v: number) => Math.ceil(v / PAD_STEP) * PAD_STEP;\n\n/**\n * Margin around the lens view on each side: beyond it, the shader has nothing to sample.\n *\n * FOUR things have to fit in here. The refraction sample with aberrations. The blur radius —\n * it also samples around the point. The gather radius — the rim picks it up from outside the\n * shape, and without the margin it goes black exactly where it should be catching the\n * surroundings. And drag travel: the lens view moves with the finger, but its bounds don't, and\n * the sample falls off the edge, which is how layers from under the glass leak through.\n */\nexport function lensPadDp(\n g: VireGlassGeometry,\n o: VireGlassOptics,\n morph?: Parameters<typeof morphReachDp>[1],\n dragLimit = 0,\n): number {\n const sampling = Math.max(o.blur, o.gatherRadiusDp);\n const stretch = halfMinDp(g) * MAX_STRETCH;\n return quantise(sampling + dragLimit + stretch + morphReachDp(g, morph) + 2);\n}\n\n/**\n * Margin around the surface canvas: the shadow reaches outside the shape, and dragging shifts it\n * further still.\n *\n * The multiplier is smaller than the shadow's full reach: under the finger, the shader for a\n * rising element multiplies the radius by 1.55, and the very end of that tail falls outside the\n * margin. Left that way on purpose — at the margin's edge it's at 1.2% alpha (2.5% before the\n * shadow was softened), and raising the multiplier means growing every surface view's area. If\n * that tail ever shows up on a device, fix it here.\n */\nexport function surfacePadDp(\n g: VireGlassGeometry,\n dragLimit = 0,\n morph?: Parameters<typeof morphReachDp>[1],\n): number {\n return quantise(shadowReachDp(g) * 1.2 + dragLimit + morphReachDp(g, morph) + 2);\n}\n\n/**\n * SHADOW DENSITY BY WHAT'S UNDER THE ELEMENT. Reference 219 @11:47, verbatim: \"increases the\n * opacity of its shadow when it is over text… lowers the opacity of its shadow when it is over a\n * solid light background.\" This is about content BEHIND THE ELEMENT, so it's a single value per\n * element, not a field over the shadow's area.\n *\n * The law lives here, not at the bench: while it was baked into the web renderer, shadows on\n * Android never adapted at all. The endpoints were checked against the reference by the depth of\n * the dip under the element — docs/benchmarks.md.\n */\nexport function shadowOpacity(busy: number): number {\n return (\n SHADOW_DENSITY.flat +\n Math.min(Math.max(busy, 0) * SHADOW_DENSITY.busyGain, SHADOW_DENSITY.busyRise)\n );\n}\n\n/** Same thing for platforms where the measurement flows through state: quantized coarsely, like\n * the ambient color, otherwise every probe sample would trigger a surface repaint. */\nexport function shadowOpacityFrom(sample: { busy: number }): number {\n return Math.round(shadowOpacity(sample.busy) / SHADOW_DENSITY.step) * SHADOW_DENSITY.step;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/geometry.ts"],"names":[],"mappings":"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","file":"chunk-SLRPHDV7.js","sourcesContent":["import type { VireGlassOptics } from './material';\nimport { SHADOW_DENSITY, SIZE } from './law';\n\n/** Glass shape in dp. Circle and capsule are special cases of a rounded rectangle. */\nexport type VireGlassGeometry = {\n width: number;\n height: number;\n cornerRadius: number;\n};\n\nexport const circleGeometry = (size: number): VireGlassGeometry => ({\n width: size,\n height: size,\n cornerRadius: size / 2,\n});\n\nexport const capsuleGeometry = (width: number, height: number): VireGlassGeometry => ({\n width,\n height,\n cornerRadius: Math.min(width, height) / 2,\n});\n\nexport const roundedRectGeometry = (\n width: number,\n height: number,\n cornerRadius: number,\n): VireGlassGeometry => ({ width, height, cornerRadius });\n\nexport const halfMinDp = (g: VireGlassGeometry) => Math.max(Math.min(g.width, g.height) / 2, 1);\n\n/** Viewport margin for a drop that overshoots past the finger: a fraction of the element's half\n * size beyond the drag travel itself. The drop overshoots the body by no more than `dragLimit`,\n * and stitching adds smoothing on top of that — this term covers it too. */\nexport const MAX_STRETCH = 0.34;\n\n/**\n * How big this element reads, for the purpose of how thick its glass is.\n *\n * Not `halfMinDp`, which is half the narrower side and is the right measure for a bevel limit or a\n * contact radius but not for this: a 390x420 sheet and a 390x780 one have the same narrow side and\n * are not the same surface. Not the area either: a 900x8 rule has more area than a toolbar button\n * and is the thinnest glass in the interface.\n *\n * The geometric mean of the two respects both facts — the narrow side caps how much glass there\n * can be, and among shapes that share it, more surface reads as more glass. Derived, not\n * calibrated: it introduces no constant.\n */\nexport const sizeDp = (g: VireGlassGeometry) =>\n Math.sqrt(halfMinDp(g) * Math.max(Math.sqrt(g.width * g.height) / 2, 1));\n\n/**\n * Bigger element, thicker glass: a stronger lens, a deeper shadow (219 @6:36 — as glass \"flexes and\n * morphs to larger sizes, it simulates a thicker material with deeper shadows and more pronounced\n * lensing and refraction effects\"). Bevel and material thickness are specified for an element at a\n * reference size and grow as the square root of it.\n *\n * The ceiling is approached, never reached. A hard clip made every surface above it the same glass,\n * and above it was where the interface lives: a half-open sheet, a full-screen sheet, an iPad\n * sidebar and a 900px Mac panel all sat at exactly 2.4. The reference names sidebars as the LARGE\n * end of the range, so a model that stops responding before them has stopped obeying it.\n *\n * The curve below is the same square root; the join at `gainMin` is continuous in value and in\n * slope, and the asymptote is `gainMax` exactly as before. Both constants keep their values —\n * what changed is that saturation is smooth instead of a clip.\n */\nexport const sizeGain = (g: VireGlassGeometry) => {\n const raw = Math.sqrt(sizeDp(g) / SIZE.referenceDp);\n if (raw <= SIZE.gainMin) return SIZE.gainMin;\n const span = SIZE.gainMax - SIZE.gainMin;\n return SIZE.gainMin + span * (1 - Math.exp(-(raw - SIZE.gainMin) / span));\n};\n\n/** A bevel wider than this fraction of the half-size breaks the SDF: the roundings converge in\n * the middle. */\nexport const MAX_BEVEL_FRACTION = SIZE.maxBevelFraction;\n\nexport const bevelFraction = (g: VireGlassGeometry, o: VireGlassOptics) =>\n Math.min(MAX_BEVEL_FRACTION, (o.bevelDp * sizeGain(g)) / halfMinDp(g));\n\nexport const bevelDp = (g: VireGlassGeometry, o: VireGlassOptics) =>\n Math.max(bevelFraction(g, o) * halfMinDp(g), 1);\n\nexport const thicknessDp = (g: VireGlassGeometry, o: VireGlassOptics) =>\n o.thicknessDp * sizeGain(g);\n\n/** Glass height right at the silhouette, a fraction of thickness: without it there's nothing at\n * the edge to bend the ray. */\nexport const RIM_FRACTION = 0.85;\n\nexport const rimDp = (g: VireGlassGeometry, o: VireGlassOptics) => thicknessDp(g, o) * RIM_FRACTION;\n\n/** Bigger element, deeper and wider shadow (M 7:14). */\nexport const shadowReachDp = (g: VireGlassGeometry) =>\n Math.min(Math.max(halfMinDp(g) * 0.65, 6), 36);\n\n/** How far the second morph shape reaches past the first one's bounds. Without this margin the\n * fused shape gets clipped by the canvas edge and the experiment shows the wrong thing. */\nexport function morphReachDp(\n g: VireGlassGeometry,\n morph?: { offsetX: number; offsetY: number; width: number; height: number; smoothing: number },\n): number {\n if (!morph || morph.smoothing <= 0) return 0;\n return Math.max(\n 0,\n Math.abs(morph.offsetX) + morph.width / 2 - g.width / 2,\n Math.abs(morph.offsetY) + morph.height / 2 - g.height / 2,\n );\n}\n\n/**\n * Quantization step for the margin. The margin only needs to cover the sampling — its exact value\n * doesn't matter, but changing it every frame is expensive: the native lens view gets re-laid-out\n * and re-sets its `RenderEffect`. While dragging a panel or morphing, the margin used to drift\n * continuously, and that produced visible stutter. Round up to the step — size changes rarely.\n */\nconst PAD_STEP = 8;\nconst quantise = (v: number) => Math.ceil(v / PAD_STEP) * PAD_STEP;\n\n/**\n * Margin around the lens view on each side: beyond it, the shader has nothing to sample.\n *\n * FOUR things have to fit in here. The refraction sample with aberrations. The blur radius —\n * it also samples around the point. The gather radius — the rim picks it up from outside the\n * shape, and without the margin it goes black exactly where it should be catching the\n * surroundings. And drag travel: the lens view moves with the finger, but its bounds don't, and\n * the sample falls off the edge, which is how layers from under the glass leak through.\n */\nexport function lensPadDp(\n g: VireGlassGeometry,\n o: VireGlassOptics,\n morph?: Parameters<typeof morphReachDp>[1],\n dragLimit = 0,\n): number {\n const sampling = Math.max(o.blur, o.gatherRadiusDp);\n const stretch = halfMinDp(g) * MAX_STRETCH;\n return quantise(sampling + dragLimit + stretch + morphReachDp(g, morph) + 2);\n}\n\n/**\n * Margin around the surface canvas: the shadow reaches outside the shape, and dragging shifts it\n * further still.\n *\n * The multiplier is smaller than the shadow's full reach: under the finger, the shader for a\n * rising element multiplies the radius by 1.55, and the very end of that tail falls outside the\n * margin. Left that way on purpose — at the margin's edge it's at 1.2% alpha (2.5% before the\n * shadow was softened), and raising the multiplier means growing every surface view's area. If\n * that tail ever shows up on a device, fix it here.\n */\nexport function surfacePadDp(\n g: VireGlassGeometry,\n dragLimit = 0,\n morph?: Parameters<typeof morphReachDp>[1],\n): number {\n return quantise(shadowReachDp(g) * 1.2 + dragLimit + morphReachDp(g, morph) + 2);\n}\n\n/**\n * SHADOW DENSITY BY WHAT'S UNDER THE ELEMENT. Reference 219 @11:47, verbatim: \"increases the\n * opacity of its shadow when it is over text… lowers the opacity of its shadow when it is over a\n * solid light background.\" This is about content BEHIND THE ELEMENT, so it's a single value per\n * element, not a field over the shadow's area.\n *\n * The law lives here, not at the bench: while it was baked into the web renderer, shadows on\n * Android never adapted at all. The endpoints were checked against the reference by the depth of\n * the dip under the element — docs/benchmarks.md.\n */\nexport function shadowOpacity(busy: number): number {\n return (\n SHADOW_DENSITY.flat +\n Math.min(Math.max(busy, 0) * SHADOW_DENSITY.busyGain, SHADOW_DENSITY.busyRise)\n );\n}\n\n/** Same thing for platforms where the measurement flows through state: quantized coarsely, like\n * the ambient color, otherwise every probe sample would trigger a surface repaint. */\nexport function shadowOpacityFrom(sample: { busy: number }): number {\n return Math.round(shadowOpacity(sample.busy) / SHADOW_DENSITY.step) * SHADOW_DENSITY.step;\n}\n"]}
|
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
|
/**
|
|
@@ -929,10 +936,18 @@ function sampleColorAt(el, x, y) {
|
|
|
929
936
|
}
|
|
930
937
|
const composited = compositeLayers(layers);
|
|
931
938
|
if (composited) return composited;
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
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;
|
|
935
949
|
}
|
|
950
|
+
var WHITE = { r: 255, g: 255, b: 255, a: 1 };
|
|
936
951
|
function probeBackdrop(el, opts = {}) {
|
|
937
952
|
const cols = opts.cols ?? GRID_COLS;
|
|
938
953
|
const rows = opts.rows ?? GRID_ROWS;
|
|
@@ -1046,15 +1061,17 @@ function resolveBody(optics, sample, ink) {
|
|
|
1046
1061
|
function withPresence(body, optics, sample) {
|
|
1047
1062
|
if (optics.presence <= 0) return body;
|
|
1048
1063
|
const mean = clamp3(sample.luma, 0, 1);
|
|
1049
|
-
const
|
|
1050
|
-
const
|
|
1051
|
-
const separation = Math.abs(
|
|
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));
|
|
1052
1067
|
const reached = Math.abs(body.tintLuma - mean) * body.density;
|
|
1053
1068
|
if (reached >= separation) return body;
|
|
1054
|
-
const needed = separation / Math.max(Math.abs(
|
|
1069
|
+
const needed = separation / Math.max(Math.abs(pole - mean), 1e-4);
|
|
1055
1070
|
return {
|
|
1056
|
-
density: Math.max(body.density, Math.min(needed,
|
|
1057
|
-
|
|
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
|
|
1058
1075
|
};
|
|
1059
1076
|
}
|
|
1060
1077
|
|
|
@@ -1602,15 +1619,22 @@ function waveImpulse(g) {
|
|
|
1602
1619
|
return halfMinDp(g) * MAX_STRETCH * WAVE_OF_TRAVEL;
|
|
1603
1620
|
}
|
|
1604
1621
|
var STYLE_ATTR = "data-vireglass-styles";
|
|
1605
|
-
function
|
|
1606
|
-
|
|
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;
|
|
1607
1631
|
const style = document.createElement("style");
|
|
1608
1632
|
style.setAttribute(STYLE_ATTR, "");
|
|
1609
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)}`;
|
|
1610
|
-
|
|
1634
|
+
styleParent(host).appendChild(style);
|
|
1611
1635
|
}
|
|
1612
|
-
function getFilterHost() {
|
|
1613
|
-
const existing =
|
|
1636
|
+
function getFilterHost(host) {
|
|
1637
|
+
const existing = host.querySelector(`svg[${FILTER_HOST_ATTR}]`);
|
|
1614
1638
|
if (existing) return existing;
|
|
1615
1639
|
const svg = document.createElementNS(SVG_NS, "svg");
|
|
1616
1640
|
svg.setAttribute(FILTER_HOST_ATTR, "");
|
|
@@ -1620,7 +1644,7 @@ function getFilterHost() {
|
|
|
1620
1644
|
svg.style.position = "absolute";
|
|
1621
1645
|
svg.style.pointerEvents = "none";
|
|
1622
1646
|
svg.appendChild(document.createElementNS(SVG_NS, "defs"));
|
|
1623
|
-
|
|
1647
|
+
(host instanceof ShadowRoot ? host : host.body).appendChild(svg);
|
|
1624
1648
|
return svg;
|
|
1625
1649
|
}
|
|
1626
1650
|
function buildFilterElement(id, mapUrl, optics, scale, spectral) {
|
|
@@ -1658,6 +1682,7 @@ function buildFilterElement(id, mapUrl, optics, scale, spectral) {
|
|
|
1658
1682
|
pass.setAttribute("xChannelSelector", "R");
|
|
1659
1683
|
pass.setAttribute("yChannelSelector", "G");
|
|
1660
1684
|
pass.setAttribute("result", `${name}raw`);
|
|
1685
|
+
pass.setAttribute("data-vireglass-ratio", String(scale > 0 ? chScale / scale : 1));
|
|
1661
1686
|
const only = document.createElementNS(SVG_NS, "feColorMatrix");
|
|
1662
1687
|
only.setAttribute("in", `${name}raw`);
|
|
1663
1688
|
only.setAttribute("type", "matrix");
|
|
@@ -1764,7 +1789,8 @@ function attachGlass(el, opts = {}) {
|
|
|
1764
1789
|
apply();
|
|
1765
1790
|
});
|
|
1766
1791
|
warnIfNested(el);
|
|
1767
|
-
|
|
1792
|
+
const host = hostFor(el);
|
|
1793
|
+
ensureRimStyles(host);
|
|
1768
1794
|
el.setAttribute(GLASS_ATTR, "");
|
|
1769
1795
|
const positionWasStatic = getComputedStyle(el).position === "static";
|
|
1770
1796
|
if (positionWasStatic) el.style.setProperty("position", "relative");
|
|
@@ -1792,7 +1818,7 @@ function attachGlass(el, opts = {}) {
|
|
|
1792
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}`;
|
|
1793
1819
|
if (key !== mapKey || !filterEl) {
|
|
1794
1820
|
const map = cached(`d|${key}`, () => buildDisplacementMap(opticsNow, geometry, devicePixelRatio(), morphState()));
|
|
1795
|
-
const defs = getFilterHost().querySelector("defs");
|
|
1821
|
+
const defs = getFilterHost(host).querySelector("defs");
|
|
1796
1822
|
const hue = hasSpectralEdge(opticsNow) ? cached(`h|${key}`, () => buildSpectralMap(opticsNow, geometry, devicePixelRatio())) : null;
|
|
1797
1823
|
const next = buildFilterElement(id, map.url, opticsNow, map.scale, hue);
|
|
1798
1824
|
if (filterEl?.parentNode) defs.replaceChild(next, filterEl);
|
|
@@ -1858,9 +1884,11 @@ function attachGlass(el, opts = {}) {
|
|
|
1858
1884
|
});
|
|
1859
1885
|
if (filterEl) {
|
|
1860
1886
|
const feImage = filterEl.querySelector("feImage");
|
|
1861
|
-
const feDisplace = filterEl.querySelector("feDisplacementMap");
|
|
1862
1887
|
feImage?.setAttribute("href", map.url);
|
|
1863
|
-
|
|
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
|
+
}
|
|
1864
1892
|
}
|
|
1865
1893
|
mapKey = "";
|
|
1866
1894
|
paintGlow(sampleOut.touchX, sampleOut.touchY, sampleOut.active * base.edgeLight, geometry);
|