vireglass 2.3.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -6
- package/dist/{adapters-DugC6Qri.d.cts → adapters--bXcGvhW.d.cts} +5 -3
- package/dist/{adapters-BfeAopGO.d.ts → adapters-BlMhc6pI.d.ts} +5 -3
- package/dist/{chunk-MMPK6DCK.js → chunk-CPSIY2DZ.js} +58 -8
- package/dist/chunk-CPSIY2DZ.js.map +1 -0
- package/dist/{chunk-PM2SWCSF.js → chunk-RWUR3OV6.js} +26 -6
- package/dist/chunk-RWUR3OV6.js.map +1 -0
- package/dist/{chunk-CTTZNZFV.js → chunk-VLDD655U.js} +3 -3
- package/dist/{chunk-CTTZNZFV.js.map → chunk-VLDD655U.js.map} +1 -1
- package/dist/{chunk-H5QTBF67.js → chunk-YAN2OK4H.js} +31 -38
- package/dist/chunk-YAN2OK4H.js.map +1 -0
- package/dist/{chunk-SLRPHDV7.js → chunk-Z4FKYY47.js} +3 -3
- package/dist/{chunk-SLRPHDV7.js.map → chunk-Z4FKYY47.js.map} +1 -1
- package/dist/dom.cjs +180 -55
- package/dist/dom.cjs.map +1 -1
- package/dist/dom.d.cts +72 -25
- package/dist/dom.d.ts +72 -25
- package/dist/dom.js +116 -54
- package/dist/dom.js.map +1 -1
- package/dist/{geometry-RhKd7CM5.d.cts → geometry-ip9WSlZv.d.cts} +1 -1
- package/dist/{geometry-GvXtt-_S.d.ts → geometry-pabkYV7w.d.ts} +1 -1
- package/dist/{group-model-z4N27qsC.d.ts → group-model-CQiI5cCe.d.ts} +1 -1
- package/dist/{group-model-DwtixGhG.d.cts → group-model-DX2_W9J_.d.cts} +1 -1
- package/dist/index.cjs +114 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -8
- package/dist/index.d.ts +13 -8
- package/dist/index.js +5 -5
- package/dist/law.cjs +56 -6
- package/dist/law.cjs.map +1 -1
- package/dist/law.d.cts +54 -4
- package/dist/law.d.ts +54 -4
- package/dist/law.js +1 -1
- package/dist/{material-CnUmdKg1.d.ts → material-D9m1EN47.d.cts} +9 -6
- package/dist/{material-CnUmdKg1.d.cts → material-D9m1EN47.d.ts} +9 -6
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/reference.d.cts +2 -2
- package/dist/reference.d.ts +2 -2
- package/dist/reference.js +2 -2
- package/dist/web/index.cjs +112 -40
- package/dist/web/index.cjs.map +1 -1
- package/dist/web/index.d.cts +3 -3
- package/dist/web/index.d.ts +3 -3
- package/dist/web/index.js +4 -4
- package/docs/dom.md +49 -3
- package/docs/law.md +1 -1
- package/package.json +1 -1
- package/src/adapters.ts +3 -1
- package/src/dom/body.ts +93 -39
- package/src/dom/index.ts +49 -20
- package/src/dom/probe.ts +1 -1
- package/src/dom/rim.ts +63 -28
- package/src/dom/style-hold.ts +8 -0
- package/src/law.ts +54 -4
- package/src/lens-shader.ts +26 -33
- package/src/material.ts +10 -7
- package/src/targets/glsl.ts +27 -2
- package/dist/chunk-H5QTBF67.js.map +0 -1
- package/dist/chunk-MMPK6DCK.js.map +0 -1
- package/dist/chunk-PM2SWCSF.js.map +0 -1
package/dist/web/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/law.ts","../../src/geometry.ts","../../src/sdf.ts","../../src/lens-shader.ts","../../src/material.ts","../../src/adapters.ts","../../src/surface-shader.ts","../../src/targets/glsl.ts","../../src/web/gl.ts","../../src/web/gpu-timer.ts","../../src/web/probe.ts","../../src/web/renderer.ts"],"names":["slot"],"mappings":"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","file":"index.cjs","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","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","// The single description of VireGlass geometry. Both shaders draw it from here: the SKSL surface\n// (`surface-shader.ts`) and the AGSL lens (`lens-shader.ts`, shipped as a string into the native\n// view). AGSL and SKSL are the same language, so the SDF no longer needs duplicating in Kotlin:\n// before, the surface's circle and the lens's rounded rectangle described different pieces of\n// glass, and the bevel constant had to be kept in sync by hand (docs/adr-001-rendering.md §3).\nimport { BEVEL, EPSILON, TOUCH } from './law';\n\n/** 2π. Structural, not a law — but the shader has no built-in for it. */\nconst VG_TAU = 6.28318530718;\n\nexport const VG_SDF = `\nfloat vgRoundRect(float2 p, float2 halfSize, float corner) {\n float2 q = abs(p) - halfSize + corner;\n return min(max(q.x, q.y), 0.0) + length(max(q, float2(0.0))) - corner;\n}\n\nfloat vgSmin(float a, float b, float k) {\n float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);\n return mix(b, a, h) - k * h * (1.0 - h);\n}\n\n// A scene is a single shape, and when k > 0 a smooth union with one or two neighbors. A third\n// shape exists to break the control into pieces (docs/reference.md §5): there are two bridges\n// there. It's turned off with a zero size, not its own k: the fused body shares one bridge.\nfloat vgScene(float2 p, float2 halfSize, float corner,\n float2 offsetB, float2 halfB, float cornerB, float k,\n float2 offsetC, float2 halfC, float cornerC) {\n float a = vgRoundRect(p, halfSize, corner);\n if (k <= 0.0) { return a; }\n float ab = vgSmin(a, vgRoundRect(p - offsetB, halfB, cornerB), k);\n if (halfC.x <= 0.0) { return ab; }\n return vgSmin(ab, vgRoundRect(p - offsetC, halfC, cornerC), k);\n}\n\n// Response to a finger. It's the FIELD around the touch point that deforms, not the shape's\n// bounding box: scaling width to pull the right edge would also pull the left one — liquid\n// doesn't behave that way. Here the displacement falls off with distance from the finger, so the\n// far edge stays put.\n//\n// The order of the terms matters: drag shifts the field, press pulls it toward the finger, the\n// wave rides on top of the already-shifted field — otherwise the ripple decouples from the\n// deformation and lives a life of its own.\nfloat2 vgTouchWarp(float2 p, float2 touch, float2 pull, float press, float radius,\n float waveAmp, float wavePhase) {\n if (radius <= 0.0) { return p; }\n // Under the finger the whole element grows (M 3:51; HIG: interactive \"expands\").\n p /= 1.0 + ${TOUCH.pressGrow} * press;\n float2 d = p - touch;\n float r = length(d);\n\n // The finger is a BLOB, not a point, but not a hard stamp either: a wide peak, soft edges. A\n // flat plateau with a sharp cutoff at the edge shifts the region as a block, and a straight\n // line under the glass breaks into a step — the deformation reads as rectangular even though\n // the finger is round. A double smoothstep gives the same contact width without that step.\n //\n // The influence cuts off at the radius rather than fading into an infinite tail like a\n // Gaussian: with a tail the deformation spreads into a \"rubber mattress\" bulge.\n float k = clamp((radius - r) / radius, 0.0, 1.0);\n float s = k * k * (3.0 - 2.0 * k);\n float core = s * s * (3.0 - 2.0 * s);\n // A ring around the blob: material displaced from under the finger has to end up somewhere.\n // Without this ridge the shape just balloons, and a dense medium doesn't behave that way.\n float rim = k * k * (1.0 - k) * 4.0;\n\n float2 q = p - pull * (core - ${TOUCH.ridge} * rim);\n q -= d * (press * ${TOUCH.pressPull} * core);\n\n // The wave has its own scale, twice the drag radius: the ripple has to reach the far edge,\n // otherwise it reads as jitter under the finger rather than a wave across the surface. The\n // wavelength is short: in a dense medium ripples are frequent and small, long shallow swells\n // are water.\n if (waveAmp > 0.0 && r > 0.0001) {\n float span = radius * ${TOUCH.waveSpan}.0;\n float ring = sin(r / (span * ${TOUCH.waveLength}) - wavePhase * ${VG_TAU}) * exp(-r / (span * ${TOUCH.waveDecay}));\n q -= (d / r) * ring * waveAmp;\n }\n return q;\n}\n\n// Analytical normal of a single shape: radial on the rounded part, axis-aligned on the straight\n// segments.\nfloat2 vgRoundRectNormal(float2 p, float2 halfSize, float corner) {\n float2 q = abs(p) - halfSize + corner;\n float2 g = (q.x > 0.0 && q.y > 0.0)\n ? normalize(max(q, float2(0.0001)))\n : (q.x > q.y ? float2(1.0, 0.0) : float2(0.0, 1.0));\n return g * sign(p);\n}\n\n// Normal of the union — ANALYTICAL, no more finite differences here.\n//\n// Derivation: smin has two terms, mix(b,a,h) and −k·h·(1−h). Their derivatives with respect to h\n// contain a factor of (1−2h) with opposite signs and cancel exactly, because h is linear in\n// (b−a)/k. What's left is mix(∇b, ∇a, h) — that is, the two shapes' normals, blended with the\n// same weight that blends the distances themselves.\n//\n// The previous version took four ADDITIONAL scene evaluations per pixel (each one — two shapes\n// plus smin) and was still an approximation. Here there are two shapes, exactly.\nfloat2 vgSceneNormal(float2 p, float2 halfSize, float corner,\n float2 offsetB, float2 halfB, float cornerB, float k,\n float2 offsetC, float2 halfC, float cornerC) {\n float2 na = vgRoundRectNormal(p, halfSize, corner);\n if (k <= 0.0) { return na; }\n float2 q = p - offsetB;\n float a = vgRoundRect(p, halfSize, corner);\n float b = vgRoundRect(q, halfB, cornerB);\n float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);\n float2 nab = normalize(mix(vgRoundRectNormal(q, halfB, cornerB), na, h) + float2(${EPSILON}, ${EPSILON}));\n if (halfC.x <= 0.0) { return nab; }\n // Same derivation as for the pair: normals blend with the same weight as the distances.\n float2 qc = p - offsetC;\n float ab = vgSmin(a, b, k);\n float c = vgRoundRect(qc, halfC, cornerC);\n float hc = clamp(0.5 + 0.5 * (c - ab) / k, 0.0, 1.0);\n return normalize(mix(vgRoundRectNormal(qc, halfC, cornerC), nab, hc) + float2(${EPSILON}, ${EPSILON}));\n}\n\n// Position within the bevel: 0 is the flat middle, 1 is the very edge. This one value feeds the\n// mask, thickness, refraction, aberrations and the width of the light rim.\nfloat vgBevelT(float sd, float bevel) {\n return clamp((sd + bevel) / bevel, 0.0, 1.0);\n}\n\n// Slope of the bevel profile — spherical: t / sqrt(1 - t²), like a spherical cap. The previous t²\n// kept the slope near zero through almost the whole bevel and then shot up right at the edge, so\n// all the optics bunched into a narrow ring and the element read as a PUCK — a flat top and a\n// wall around the rim. Here curvature is spread across the bevel, and the rim stops being a line.\n//\n// This still doesn't control the field of view: only the bevel bends, the middle stays flat,\n// otherwise the surface reads as a soap bubble.\nfloat vgBevelSlope(float t) {\n return min(t * inversesqrt(max(1.0 - t * t * ${BEVEL.sphere}, ${BEVEL.floor})), ${BEVEL.slopeMax});\n}\n\n// PROGRESS FRACTION. Progress is an active state turned into a FIELD: to the left of the boundary\n// the element is active, to the right it stays regular glass, and the boundary moves. A negative\n// value turns the field off entirely — zero is taken by the start of the track and can't also\n// mean off.\n//\n// The boundary is SOFT. A hard seam reads as two different materials glued together rather than\n// two states of one; the transition width is a fraction of the half-width, so it doesn't read as\n// a thread on a large element and doesn't swallow a small one whole.\nfloat vgProgress(float2 p, float2 halfSize, float progress) {\n if (progress < 0.0) { return 0.0; }\n float edge = mix(-halfSize.x, halfSize.x, clamp(progress, 0.0, 1.0));\n float soft = max(halfSize.x * 0.05, 1.0);\n return 1.0 - smoothstep(edge - soft, edge + soft, p.x);\n}\n`;\n\n/** Rate at which the offset builds toward the rim. Shared between the lens (actual sampling\n * offset) and the surface (visualizing the displacement field in debug mode) — otherwise they'd\n * drift apart. */\nexport const VG_FALLOFF = 2.6;\n\n// JS twin of `vgRoundRect`/`vgRoundRectNormal` above, for targets with no GPU to run the shader on\n// (the DOM displacement map). Term-for-term against the GLSL text: a second geometry that behaves\n// differently is exactly the class of bug `check:optics` exists to catch on the shader side, and\n// there is no such gate for this one.\n\n/** Signed distance from `(x, y)` to a rounded rect of size `w`×`h` centered on the origin. */\nexport function sdfRoundedRect(x: number, y: number, w: number, h: number, r: number): number {\n const qx = Math.abs(x) - w / 2 + r;\n const qy = Math.abs(y) - h / 2 + r;\n return Math.min(Math.max(qx, qy), 0) + Math.hypot(Math.max(qx, 0), Math.max(qy, 0)) - r;\n}\n\n/**\n * Closed-form gradient of `sdfRoundedRect`, unit length everywhere except the exact center (where\n * it's genuinely undefined — the medial axis of the shape). NOT finite differences: differencing\n * quantizes the gradient to the sampling step, and the displacement map needs sub-pixel accuracy\n * right at the rim.\n */\nexport function sdfRoundedRectGradient(x: number, y: number, w: number, h: number, r: number): [number, number] {\n const qx = Math.abs(x) - w / 2 + r;\n const qy = Math.abs(y) - h / 2 + r;\n let gx: number;\n let gy: number;\n if (qx > 0 && qy > 0) {\n const cx = Math.max(qx, 0.0001);\n const cy = Math.max(qy, 0.0001);\n const len = Math.hypot(cx, cy);\n gx = cx / len;\n gy = cy / len;\n } else if (qx > qy) {\n gx = 1;\n gy = 0;\n } else {\n gx = 0;\n gy = 1;\n }\n return [gx * Math.sign(x), gy * Math.sign(y)];\n}\n\n// ---------------------------------------------------------------------------\n// JS twins of the shader functions above. The DOM renderer needs this geometry\n// as numbers, not as a string it hands to a compiler. Each mirrors its `vg*`\n// counterpart term for term — a second geometry that drifts is the failure the\n// platform-parity gate exists to catch.\n// ---------------------------------------------------------------------------\n\n/** Mirrors `vgSmin`. */\nexport function smin(a: number, b: number, k: number): number {\n const h = Math.min(Math.max(0.5 + (0.5 * (b - a)) / k, 0), 1);\n return b + (a - b) * h - k * h * (1 - h);\n}\n\n/**\n * The smallest `k` at which `smin` joins two surfaces `gap` apart into one body. Exact for this\n * smin rather than fitted: at the midpoint the two distances are both gap/2, so h = 0.5 and the\n * result is gap/2 − k/4, which reaches zero at k = 2·gap.\n *\n * It is the number `morphBetween` was missing. A neck sized only as a fraction of the element's\n * own half-size has nothing to do with the distance it has to span, so two controls a hundred\n * pixels apart stayed two controls at every t — including t = 1, where the contract says they are\n * one shared medium.\n */\nexport function neckToBridge(gap: number): number {\n return gap > 0 ? gap * 2 : 0;\n}\n\nexport type MorphShape = {\n offsetX: number;\n offsetY: number;\n width: number;\n height: number;\n cornerRadius: number;\n};\n\n/**\n * Mirrors the scene distance: one shape, smoothly unioned with any number of neighbours when\n * `smoothing` is positive. §5's merging and splitting are this union opening and closing.\n *\n * A fold, not a pair. The count used to be in the ARGUMENT NAMES — `b` and `c`, and a fourth\n * shape would have been `d` — which is not a limit the material has. Several independent elements\n * flowing into one whole is the behaviour; how many of them there happen to be is the host's.\n */\nexport function sceneDistance(\n x: number,\n y: number,\n w: number,\n h: number,\n r: number,\n smoothing = 0,\n ...shapes: (MorphShape | undefined)[]\n): number {\n let d = sdfRoundedRect(x, y, w, h, r);\n if (smoothing <= 0) return d;\n for (const s of shapes) {\n if (!s || s.width <= 0) continue;\n d = smin(d, sdfRoundedRect(x - s.offsetX, y - s.offsetY, s.width, s.height, s.cornerRadius), smoothing);\n }\n return d;\n}\n\n/**\n * Mirrors `vgSceneNormal`: normals blend with the same weight that blends the distances, which the\n * shader derives rather than approximates. The same fold as `sceneDistance`, carrying the running\n * distance alongside the running normal because each step's weight is measured against it.\n */\nexport function sceneGradient(\n x: number,\n y: number,\n w: number,\n h: number,\n r: number,\n smoothing = 0,\n ...shapes: (MorphShape | undefined)[]\n): [number, number] {\n let d = sdfRoundedRect(x, y, w, h, r);\n let [gx, gy] = sdfRoundedRectGradient(x, y, w, h, r);\n let blended = false;\n if (smoothing > 0) {\n for (const s of shapes) {\n if (!s || s.width <= 0) continue;\n const sx = x - s.offsetX;\n const sy = y - s.offsetY;\n const ds = sdfRoundedRect(sx, sy, s.width, s.height, s.cornerRadius);\n const weight = Math.min(Math.max(0.5 + (0.5 * (ds - d)) / smoothing, 0), 1);\n const [nx, ny] = sdfRoundedRectGradient(sx, sy, s.width, s.height, s.cornerRadius);\n gx = nx + (gx - nx) * weight;\n gy = ny + (gy - ny) * weight;\n d = smin(d, ds, smoothing);\n blended = true;\n }\n }\n // With nothing blended in, the base gradient is already a unit vector and is returned untouched —\n // the shader takes the same early exit (`if (k <= 0.0) return na;`), and nudging it here instead\n // would put the two renderers a rounding step apart on every flat edge in the material.\n if (!blended) return [gx, gy];\n // The shader's `+ float2(${EPSILON}, ${EPSILON})` before normalising, and it is not decoration:\n // where two shapes meet head-on their normals cancel exactly and the blend lands on zero.\n gx += EPSILON;\n gy += EPSILON;\n const len = Math.hypot(gx, gy) || 1;\n return [gx / len, gy / len];\n}\n\nexport type TouchWarp = {\n x: number;\n y: number;\n pullX: number;\n pullY: number;\n press: number;\n radius: number;\n waveAmp: number;\n wavePhase: number;\n};\n\n/**\n * Mirrors `vgTouchWarp`. The FIELD around the finger deforms, not the bounding box: scaling the\n * width to pull the right edge would pull the left one too, and liquid does not do that.\n *\n * Term order is load-bearing — drag shifts the field, press pulls it toward the finger, the wave\n * rides on the already-shifted field. Reordered, the ripple decouples and lives its own life.\n */\nexport function touchWarp(x: number, y: number, t: TouchWarp): [number, number] {\n if (t.radius <= 0) return [x, y];\n const grow = 1 + TOUCH.pressGrow * t.press;\n let px = x / grow;\n let py = y / grow;\n const dx = px - t.x;\n const dy = py - t.y;\n const r = Math.hypot(dx, dy);\n\n const k = Math.min(Math.max((t.radius - r) / t.radius, 0), 1);\n const s = k * k * (3 - 2 * k);\n const core = s * s * (3 - 2 * s);\n // The ridge around the blob: material pushed out from under the finger has to go somewhere.\n const rim = k * k * (1 - k) * 4;\n\n px -= t.pullX * (core - TOUCH.ridge * rim);\n py -= t.pullY * (core - TOUCH.ridge * rim);\n px -= dx * (t.press * TOUCH.pressPull * core);\n py -= dy * (t.press * TOUCH.pressPull * core);\n\n if (t.waveAmp > 0 && r > 0.0001) {\n const span = t.radius * TOUCH.waveSpan;\n const ring = Math.sin(r / (span * TOUCH.waveLength) - t.wavePhase * VG_TAU) * Math.exp(-r / (span * TOUCH.waveDecay));\n px -= (dx / r) * ring * t.waveAmp;\n py -= (dy / r) * ring * t.waveAmp;\n }\n return [px, py];\n}\n","import { ACCENT, BODY, DISPERSION, LENS, MEDIUM, RIM, SPECTRAL } from './law';\nimport { VG_SDF } from './sdf';\n\n// The lens source is assembled HERE and ships to the native view as a prop: AGSL and SKSL are one\n// language, so the lens's and the surface's geometry are literally the same string (`VG_SDF`).\n//\n// Glass is a thick plate with a convex rounded edge (docs/reference.md §1). A ray from above\n// refracts at the top face and lands on the content shifted INWARD relative to the shape: the\n// middle isn't shifted, the rim shows what lies beneath the body of the element.\n//\n// Uniforms, apart from the view's own geometry, arrive over a SHARED CHANNEL (`adapters.ts` →\n// `uniformNames`/`uniformSizes`/`uniformValues`). There are no more separate Props per value:\n// those were exactly what let a typo silently turn the lens off (docs/material-lab.md E-01).\nexport const LENS_SHADER = `\nuniform shader content;\n\n// Known only to the view itself: its own size and its own place on screen.\nuniform float2 u_center;\nuniform float u_reach;\nuniform float2 u_contentMin;\nuniform float2 u_contentMax;\n// Backdrop estimate UNDER the glass from the native probe: lightness, variegation and average\n// color, already smoothed over time. Negative lightness means the probe hasn't reported yet —\n// fall back to our own samples.\nuniform float u_probeLuma;\nuniform float u_probeBusy;\n// The endpoints of the lightness range under the glass: right at a black/white border the\n// average reads as a gray that says \"everything's fine,\" while the ink drowns over the light\n// half.\nuniform float2 u_probeRange;\n// Slope of lightness across the surface, fraction per half-size: tinting is a gradient — one\n// density per element wouldn't separate the ink from both halves of the backdrop.\nuniform float2 u_probeSlope;\nuniform float3 u_probe;\n\n// Material — over the shared channel from JS, already in device pixels.\nuniform float2 u_halfSize;\nuniform float u_corner;\nuniform float u_bevel;\nuniform float u_thick;\nuniform float u_rim;\nuniform float u_ior;\nuniform float u_iorSpread;\nuniform float2 u_light;\nuniform float u_appear;\nuniform float4 u_accent;\nuniform float u_frost;\nuniform float u_ink;\nuniform float u_legibility;\nuniform float u_dim;\nuniform float u_presence;\nuniform float u_progress;\nuniform float u_adaptRadius;\nuniform float u_bodyDensity;\nuniform float u_edgeLight;\nuniform float u_fresnel;\nuniform float u_specular;\nuniform float u_reflectReach;\nuniform float u_film;\nuniform float u_iridescence;\nuniform float u_diffraction;\nuniform float u_colorPickup;\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;\nuniform float u_debug;\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\n// Sample count for the disc gather: fewer than this reads as a staircase of copies at a sharp edge.\nconst int VG_FROST_TAPS = 20;\n// The sample count grows with the disc's area: estimator noise is the spread under the glass\n// divided by the square root of the sample count. The ceiling is cost: texture sampling is the\n// most expensive thing here.\nconst int VG_FROST_TAPS_MAX = 64;\n// The radius correction for backdrop compression at the rim is capped: at the silhouette it goes\n// to infinity.\nconst float VG_FOOTPRINT_MAX = ${LENS.footprintMax};\nconst float VG_TAU = 6.28318530718;\n// Ceiling on body lightness under light ink (and the mirrored floor under dark ink). This is a\n// ceiling, not a difference: \"0.14 darker than white\" is a lightness of 0.86, at which white text\n// is invisible.\nconst float VG_BODY_CAP_LOOSE = ${BODY.capLoose};\nconst float VG_BODY_CAP_TIGHT = ${BODY.capTight};\n// Tint lightness in both directions: real glass is never coal-black or paper-white.\nconst float VG_TINT_DARK = ${BODY.tintDark};\nconst float VG_TINT_LIGHT = ${BODY.tintLight};\n// Reference channel wavelengths, nm — for diffraction and interference.\nconst float3 VG_LAMBDA = float3(${LENS.lambdaR}.0, ${LENS.lambdaG}.0, ${LENS.lambdaB}.0);\n// Film index of refraction: every thin film on glass sits around this value.\nconst float VG_FILM_IOR = ${LENS.filmIor};\n// Ceiling on the profile's slope right at the silhouette: it goes to infinity there.\nconst float VG_SLOPE_MAX = ${LENS.slopeMax}.0;\n// Fraction of the gather radius that scattering reaches over a busy backdrop under ink (M 11:47).\n// Set against the reference: there, structure under the capsule fades by a factor of 9-10, not\n// thirty.\nconst float VG_SCATTER_MAX = ${MEDIUM.scatterMax};\nconst float VG_SCATTER_BASE = ${MEDIUM.scatterBase};\n// The response to structure under the glass saturates early: what competes with the ink isn't\n// the area of foreign text but the mere fact that it's there (a line under a tile gives a busy\n// of about 0.12).\nconst float VG_STRUCTURE_GAIN = ${MEDIUM.structureGain}.0;\n// How much denser the body with ink gets over a BUSY backdrop than over a calm one. There's no\n// flat floor here: legibility picks up its own targeted requirement further down, and base\n// frosting comes from VG_MATTE_LIFT.\nconst float VG_GROUND_SPAN = ${BODY.groundSpan};\n// Scattered light on a frosted element is an ADDITION on top of the backdrop, not a fraction of\n// the way to the tint: the fraction goes to zero once the canvas reaches the tint's lightness,\n// and darkens past it (docs/benchmarks.md).\nconst float VG_MATTE_LIFT = ${MEDIUM.matteLift};\n// The fraction of backdrop spread that survives through to the body past scattering: the\n// legibility requirement is computed from this edge, not from the spot's average lightness.\nconst float VG_BUSY_EDGE = ${BODY.busyEdge};\n// Light concentration: the body is a touch lighter than what's beneath it (M 2:29).\nconst float VG_CONCENTRATE = ${MEDIUM.concentrate};\n// The lightness the medium pulls content under the glass toward, and the strength of that pull.\n// The medium both removes light and mixes in scattered light: over a light backdrop the body\n// darkens, over a dark one it lightens.\nconst float VG_MEDIUM_LUMA = ${MEDIUM.luma};\nconst float VG_MEDIUM_PULL = ${MEDIUM.pull};\n// How much ambient light reaches the body on top of the pull.\nconst float VG_AMBIENT_SPILL = ${MEDIUM.ambientSpill};\n\nfloat vgLuma(float3 c) { return dot(c, float3(0.2126, 0.7152, 0.0722)); }\n\n// Un-premultiply into ordinary color. One division, done in float: near zero, half's step is too\n// coarse, and several divisions in a row raise noise.\nfloat3 vgUnpack(half4 c) {\n float a = float(c.a);\n return a > 0.004 ? float3(c.rgb) / a : float3(0.0);\n}\n\n// The capture ends at the screen edge: samples are clamped to the rectangle where content\n// actually exists, otherwise full-width glass picked up a band of emptiness along its edges.\nfloat2 vgInContent(float2 q) { return clamp(q, u_contentMin, u_contentMax); }\n\n// Hue without lightness. Below the threshold the color has no hue, and the division blows up.\nfloat3 vgHue(float3 c) {\n float l = vgLuma(c);\n if (l < 0.02) { return float3(1.0); }\n return clamp(c / l, float3(0.0), float3(2.0));\n}\n\n// Edge profile — a convex squircle: x = 0 at the silhouette, 1 where the rounding turns flat.\nfloat vgRimQ(float x) {\n float k = 1.0 - x;\n return sqrt(sqrt(max(1.0 - k * k * k * k, 0.0)));\n}\n\nfloat vgRimQd(float x) {\n float k = 1.0 - x;\n return k * k * k / pow(max(1.0 - k * k * k * k, 1e-5), 0.75);\n}\n\n// Glass height above the content at distance e from the silhouette.\nfloat vgHeight(float e, float w) {\n return mix(u_rim, u_thick, vgRimQ(clamp(e / w, 0.0, 1.0)));\n}\n\n// Slope of the top face at the same point: the derivative of height with respect to distance\n// from the silhouette.\nfloat vgSlope(float e, float w) {\n if (e >= w) { return 0.0; }\n return min((u_thick - u_rim) / w * vgRimQd(clamp(e / w, 0.0, 1.0)), VG_SLOPE_MAX);\n}\n\n// Where a ray falling from above lands on a face with normal N: Snell's law refraction, then a\n// path of length z through the medium to the content. The convex face bends the ray toward the\n// normal — that is, inward relative to the shape.\nfloat2 vgShift(float3 N, float z, float ior) {\n float3 T = refract(float3(0.0, 0.0, -1.0), N, 1.0 / ior);\n return z * T.xy / max(-T.z, 0.05);\n}\n\n// Thin-film interference: optical path difference 2·n·d·cosθt, each channel with its own λ.\n// Returns a hue normalized to its own average — it colors, but doesn't lighten.\nfloat3 vgInterference(float cosI) {\n float sinT2 = (1.0 - cosI * cosI) / (VG_FILM_IOR * VG_FILM_IOR);\n float cosT = sqrt(max(1.0 - sinT2, 0.0));\n float opd = 2.0 * VG_FILM_IOR * u_film * cosT;\n float3 i = 0.5 + 0.5 * cos(VG_TAU * opd / VG_LAMBDA + 3.14159265);\n return i / max((i.r + i.g + i.b) / 3.0, 0.001);\n}\n\n// Edge diffraction: fringes get denser the sharper the bevel. Also a hue, not a brightness.\nfloat3 vgDiffraction(float distFromEdge, float bevel) {\n float phase = VG_TAU * ${SPECTRAL.diffractionFringes}.0 * distFromEdge / max(bevel, 1.0);\n float3 d = 0.5 + 0.5 * cos(phase * (${SPECTRAL.referenceLambda}.0 / VG_LAMBDA));\n return d / max((d.r + d.g + d.b) / 3.0, 0.001);\n}\n\nfloat vgHash(float2 p) {\n return fract(sin(dot(p, float2(12.9898, 78.233))) * 43758.5453);\n}\n\n// Soft compression instead of a hard clamp: over a white backdrop, additions used to knock a\n// channel to one, and the glass turned into a flat blob with no detail.\nfloat3 vgSoftClip(float3 c) {\n float3 over = max(c - 0.86, float3(0.0));\n return min(c, float3(0.86)) + over * 0.14 / (0.14 + over);\n}\n\n// Our own blur: the capture is left clean, the shader does the blurring — averaging in float\n// smooths banding on its own and adds no noise of its own. A golden-angle spiral, rotated to its\n// own angle at every pixel: with a shared angle, neighboring pixels took the same samples, and\n// texture under the glass read in clumps. Accumulated premultiplied — one shared un-premultiply\n// at the end.\nfloat4 vgGather(float2 q, float radius, float2 seed) {\n float4 acc = float4(content.eval(vgInContent(q)));\n if (radius <= 0.25) { return acc; }\n if (radius < 4.0) {\n float r = radius * 0.7;\n acc += float4(content.eval(vgInContent(q + float2(r, 0.0))))\n + float4(content.eval(vgInContent(q - float2(r, 0.0))))\n + float4(content.eval(vgInContent(q + float2(0.0, r))))\n + float4(content.eval(vgInContent(q - float2(0.0, r))));\n return acc * 0.2;\n }\n float ca = cos(2.39996323);\n float sa = sin(2.39996323);\n float a0 = vgHash(seed) * 6.28318530718;\n float2 dir = float2(cos(a0), sin(a0));\n float wide = radius * 0.5;\n int taps = int(clamp(float(VG_FROST_TAPS) * wide * wide, float(VG_FROST_TAPS), float(VG_FROST_TAPS_MAX)));\n for (int i = 1; i <= VG_FROST_TAPS_MAX; i++) {\n if (i > taps) { break; }\n dir = float2(dir.x * ca - dir.y * sa, dir.x * sa + dir.y * ca);\n float r = radius * sqrt(float(i) / float(taps));\n acc += float4(content.eval(vgInContent(q + dir * r)));\n }\n return acc / float(taps + 1);\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 float sd = vgScene(p, u_halfSize, u_corner, u_morphOffset, u_morphHalf, u_morphCorner, u_morphK,\n u_morph2Offset, u_morph2Half, u_morph2Corner);\n // There's nothing outside the glass — except the light it spills onto the backdrop under the\n // finger (M 3:38): the same concentrated ambient as inside, so no white halo appears over a\n // dark backdrop.\n if (sd > 1.0) {\n float spill = u_touchPress * exp(-sd / max(min(u_halfSize.x, u_halfSize.y) * 0.5, 4.0)) * 0.5 * u_appear;\n if (spill < 0.004) { return half4(0.0); }\n float3 lit = clamp((u_probeLuma >= 0.0 ? u_probe : float3(0.35)) * 1.6 + float3(0.1), float3(0.0), float3(1.0));\n return half4(half3(lit * spill), half(spill));\n }\n\n float bevel = max(u_bevel, 1.0);\n float e = max(-sd, 0.0);\n // 0 in the flat middle, 1 at the silhouette.\n float t = 1.0 - clamp(e / bevel, 0.0, 1.0);\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\n // \"backdrop\" mode hands back the content as-is — a reference point for comparing the optics.\n float on = u_debug > 5.5 && u_debug < 6.5 ? 0.0 : 1.0;\n float lens = on * u_appear;\n\n float z = vgHeight(e, bevel);\n float slope = vgSlope(e, bevel);\n float3 N = normalize(float3(n * slope, 1.0));\n float2 shift = vgShift(N, z, u_ior) * lens;\n float2 s = u_center + p + shift;\n\n // The area the lens gathers into one pixel: a one-pixel step inward on screen, measured\n // against the sampling step. At the silhouette it's large — a stripe of backdrop is compressed\n // into a line there, and a point sample gives grain.\n float e1 = e + 1.0;\n float3 N1 = normalize(float3(n * vgSlope(e1, bevel), 1.0));\n float2 shift1 = vgShift(N1, vgHeight(e1, bevel), u_ior) * lens;\n float footprint = length(shift1 - shift - n);\n float spread = max(footprint - 1.0, 0.0) * 0.5 * on;\n\n // Dispersion: the same ray with each channel's own index. Blue bends more than red.\n float2 dR = vgShift(N, z, u_ior + ${DISPERSION.redShift} * u_iorSpread) * lens - shift;\n float2 dB = vgShift(N, z, u_ior + ${DISPERSION.blueShift} * u_iorSpread) * lens - shift;\n float chroma = length(dB - dR);\n\n float3 rgb;\n float srcA;\n\n if (chroma + spread < 0.25) {\n // In the flat body all the samples would land at the same point, and the body is almost the\n // whole area of the glass.\n half4 c = content.eval(vgInContent(s));\n srcA = float(c.a);\n rgb = srcA > 0.004 ? float3(c.rgb) / srcA : float3(0.0);\n } else {\n // Accumulate premultiplied and divide once per pair: channels from different points with\n // different alpha would otherwise produce a colored fringe at content edges that isn't\n // really there.\n float2 ev = n * spread;\n half4 c0 = content.eval(vgInContent(s + dR - ev));\n half4 c1 = content.eval(vgInContent(s + dR + ev));\n half4 c2 = content.eval(vgInContent(s - ev));\n half4 c3 = content.eval(vgInContent(s + ev));\n half4 c4 = content.eval(vgInContent(s + dB - ev));\n half4 c5 = content.eval(vgInContent(s + dB + ev));\n\n float aR = float(c0.a + c1.a) * 0.5;\n float aG = float(c2.a + c3.a) * 0.5;\n float aB = float(c4.a + c5.a) * 0.5;\n rgb = float3(\n aR > 0.004 ? float(c0.r + c1.r) * 0.5 / aR : 0.0,\n aG > 0.004 ? float(c2.g + c3.g) * 0.5 / aG : 0.0,\n aB > 0.004 ? float(c4.b + c5.b) * 0.5 / aB : 0.0);\n srcA = (aR + aG + aB) / 3.0;\n }\n\n // \"backdrop\" mode checks the CAPTURE, not a simplified version of the optics. What follows are\n // layers that aren't tied to the lens (body, medium, scattering), and they used to conflate\n // \"did the pixels arrive\" with \"how did we process them.\" Here the element must DISAPPEAR: a\n // visible silhouette means a capture loss.\n if (u_debug > 5.5 && u_debug < 6.5) {\n float bypass = srcA * (1.0 - smoothstep(-1.0, 1.0, sd));\n return half4(half3(clamp(rgb, float3(0.0), float3(1.0)) * bypass), half(bypass));\n }\n\n // The transparent variant's dimming layer holds legibility instead of the body. A\n // multiplication, not a tint: the content has to stay visible, just quieter.\n rgb *= 1.0 - u_dim * u_appear;\n\n // REFLECTION. Fresnel from the face's actual slope: at the silhouette the view is grazing and\n // reflection is nearly total, which is why the rim reads as a thin line on its own. What's\n // reflected is the element's surroundings and the key light.\n float cosT = clamp(N.z, 0.0, 1.0);\n float f0 = (u_ior - 1.0) / (u_ior + 1.0);\n f0 *= f0;\n float fres = (f0 + (1.0 - f0) * pow(1.0 - cosT, 5.0)) * u_fresnel * lens;\n\n float lineW = clamp(min(u_halfSize.x, u_halfSize.y) * 0.03, 1.5, 5.0);\n float3 refl;\n if (e < lineW * 2.0 || fres > 0.01) {\n float2 around = u_center + p + n * (u_reflectReach * mix(0.35, 1.0, clamp(slope / 3.2, 0.0, 1.0)));\n // Two rings of eight samples total: a single offset sample is an undistorted COPY of\n // neighboring content inside the glass (docs/material-lab.md E-33).\n float p1 = u_reflectReach * 0.75;\n float p2 = u_reflectReach * 0.4;\n float d = 0.7071;\n float4 rsum = float4(content.eval(vgInContent(around + float2(p1, 0.0))))\n + float4(content.eval(vgInContent(around - float2(p1, 0.0))))\n + float4(content.eval(vgInContent(around + float2(0.0, p1))))\n + float4(content.eval(vgInContent(around - float2(0.0, p1))))\n + float4(content.eval(vgInContent(around + float2(p2, p2) * d)))\n + float4(content.eval(vgInContent(around + float2(-p2, p2) * d)))\n + float4(content.eval(vgInContent(around + float2(p2, -p2) * d)))\n + float4(content.eval(vgInContent(around + float2(-p2, -p2) * d)));\n float ra = rsum.a * 0.125;\n refl = ra > 0.004 ? rsum.rgb * 0.125 / ra : float3(0.0);\n } else {\n refl = u_probeLuma >= 0.0 ? u_probe : rgb;\n }\n\n // Light reaching the body is the same across the whole element: blending with the surroundings\n // over the bevel's band used to paint a ring-shaped seam on the body.\n float3 ambient = u_probeLuma >= 0.0 ? u_probe : rgb;\n\n // Light comes from wherever the surroundings are brighter (M 11:04): the lightness slope under\n // the element pulls the key light toward itself, and over a flat backdrop it stays at rest.\n float2 bright = u_probeLuma >= 0.0 ? u_probeSlope : float2(0.0);\n float brightLen = length(bright);\n float2 L = normalize(mix(u_light, bright / max(brightLen, 1e-4), smoothstep(0.03, 0.2, brightLen)) + float2(1e-5));\n // The key light's reflection uses the face's actual normal: at the flat top it points straight\n // up, and there's nothing there for direction to depend on. Using the SDF normal instead\n // painted the body as a cone.\n float facing = dot(N.xy, L);\n float key = u_specular * (pow(max(facing, 0.0), ${RIM.lobeExponent}.0) + ${RIM.opposingArc} * pow(max(-facing, 0.0), ${RIM.lobeExponent}.0));\n float rimFacing = dot(n, L);\n float rimKey = u_specular * (pow(max(rimFacing, 0.0), ${RIM.lobeExponent}.0) + ${RIM.opposingArc} * pow(max(-rimFacing, 0.0), ${RIM.lobeExponent}.0));\n // Wherever the key light doesn't fall, the face reflects its shaded surroundings — hence the\n // dark outline.\n float3 env = refl * (0.55 + 0.45 * min(key, 1.0)) + float3(1.2 * key);\n\n // Interference and diffraction live in the reflected ray at a grazing angle — a single hue\n // multiplier applied to the reflection, no extra samples.\n float3 spectral = float3(1.0);\n if (u_iridescence > 0.001) {\n spectral *= mix(float3(1.0), vgInterference(cosT), u_iridescence);\n }\n if (u_diffraction > 0.001) {\n float w = u_diffraction * smoothstep(${SPECTRAL.diffractionOnset}, 1.0, t);\n spectral *= mix(float3(1.0), vgDiffraction(e, bevel), w);\n }\n\n // The BACKDROP ESTIMATE arrives from the probe as one value per surface: computing it here\n // from samples isn't an option — a kink in density used to turn the estimate's discreteness\n // into ghost copies of text.\n float3 wide;\n float busy;\n if (u_probeLuma >= 0.0) {\n wide = u_probe;\n busy = u_probeBusy;\n } else {\n // The probe hasn't reported yet (first frames) — fall back to our own samples.\n float2 wx = float2(u_adaptRadius, 0.0);\n float2 wy = float2(0.0, u_adaptRadius);\n float3 w0 = vgUnpack(content.eval(vgInContent(s + wx)));\n float3 w1 = vgUnpack(content.eval(vgInContent(s - wx)));\n float3 w2 = vgUnpack(content.eval(vgInContent(s + wy)));\n float3 w3 = vgUnpack(content.eval(vgInContent(s - wy)));\n wide = (w0 + w1 + w2 + w3) * 0.25;\n float lw = vgLuma(wide);\n // The same statistic the probe uses (twice the mean deviation): both blur and the ink's\n // contrast margin depend on it, and the two paths' scales must not diverge.\n busy = clamp(0.5 * (abs(vgLuma(w0) - lw) + abs(vgLuma(w1) - lw)\n + abs(vgLuma(w2) - lw) + abs(vgLuma(w3) - lw)), 0.0, 1.0);\n }\n // Lightness AT THIS SPOT on the surface: the probe's plane, clamped to the measured range.\n float2 nrm = p / max(u_halfSize, float2(1.0));\n float local = u_probeLuma >= 0.0\n ? clamp(u_probeLuma + dot(u_probeSlope, nrm), u_probeRange.x, u_probeRange.y)\n : vgLuma(wide);\n float mean = u_probeLuma >= 0.0 ? u_probeLuma : vgLuma(wide);\n\n // SCATTERING. Regular mutes foreign structure under its own ink with blur, not a fill (M\n // 11:47): text under a capsule turns into blotches. Uniform across the whole lens; the radius\n // is measured in backdrop space — the lens compresses it at the silhouette.\n float structure = 1.0 - exp(-busy * VG_STRUCTURE_GAIN);\n // Under ink, Regular always scatters the backdrop; over a busy one, more strongly.\n float adaptBlur = max(\n (structure * VG_SCATTER_MAX + VG_SCATTER_BASE) * u_legibility * u_adaptRadius\n * min(max(footprint, 1.0), VG_FOOTPRINT_MAX),\n u_frost) * u_appear;\n if (adaptBlur > 0.5) {\n float reach = max(u_reach - length(p), 1.0);\n float4 g = vgGather(s, min(adaptBlur, reach), xy);\n float ga = g.a;\n float3 blurred = ga > 0.004 ? g.rgb / ga : float3(0.0);\n rgb = mix(rgb, blurred, smoothstep(0.5, 2.0, adaptBlur));\n }\n\n // AFTER scattering, not before: scattering is a property of TRANSMITTED light and replaces rgb\n // wholesale, so doing it the other way around erased the reflection over the whole element\n // (issue #106).\n rgb = mix(rgb, env * spectral, fres);\n\n // BODY. Tint and dynamic range under the glass compress just enough for ink over it to stay\n // legible (M 6:42). u_ink is ink POLARITY (1 light, 0 dark): the requirements are computed at\n // the two ends and blended by it, otherwise the glass dips toward the middle during a recolor.\n float strict = clamp(u_legibility * 2.0, 0.0, 1.0);\n float capLight = mix(VG_BODY_CAP_LOOSE, VG_BODY_CAP_TIGHT, strict);\n float floorDark = 1.0 - capLight;\n float pol = clamp(u_ink, 0.0, 1.0);\n // The requirement fades together with legibility: an element with no ink is promised\n // transparent glass by the model.\n float demand = clamp(u_legibility * 4.0, 0.0, 1.0);\n\n // Where there's no ink, the glass tints away from the backdrop. The direction is chosen by the\n // average lightness under the element, not the local spot's lightness: otherwise on a gradient\n // the threshold cuts across the body as a diagonal step.\n float darkSide = smoothstep(${BODY.darkSideFrom}, ${BODY.darkSideTo}, mean);\n float away = mix(VG_TINT_LIGHT, VG_TINT_DARK, darkSide);\n float tintLuma = mix(away, mix(VG_TINT_LIGHT, VG_TINT_DARK, pol), demand);\n\n // How much medium it takes to push lightness under the ink past the threshold — locally.\n // Computed from whichever edge of the backdrop's spread is closer to the ink: a light ink\n // drowns in a light spot right beneath it, and the spot's average lightness knows nothing\n // about that spot.\n float edge = busy * VG_BUSY_EDGE;\n float inkHi = min(local + edge, 1.0);\n float inkLo = max(local - edge, 0.0);\n float needForLight = inkHi > capLight\n ? clamp((inkHi - capLight) / max(inkHi - VG_TINT_DARK, 1e-4), 0.0, ${BODY.maxDemand})\n : 0.0;\n float needForDark = inkLo < floorDark\n ? clamp((floorDark - inkLo) / max(VG_TINT_LIGHT - inkLo, 1e-4), 0.0, ${BODY.maxDemand})\n : 0.0;\n float needForInk = mix(needForDark, needForLight, pol) * demand;\n\n float ground = structure * u_legibility * VG_GROUND_SPAN;\n float density = max(max(u_bodyDensity, ground), needForInk);\n\n // PRESENCE (§3). Over a uniform backdrop there is nothing to refract, and an element that is\n // only a rim honestly disappears — right for a piece of background, wrong for a control.\n //\n // This used to live on the rim line alone, as lit = max(rimKey, u_presence x 2), while the\n // DOM path has always implemented it as a floor on the BODY. Two renderers, one field, two\n // meanings. Measured on Apple's own control over a flat page (frames/verify/m-707), the body\n // carries 16 levels of separation there and the rim 11.6 — so the body is where it belongs,\n // and the rim's reading was the wrong one.\n // The separation asked for is capped by the range that is left: over a backdrop at 0.96 there\n // is no room to go 0.05 lighter. And what it is DIVIDED by is the distance to the tint's own\n // end, not to the tint the body currently sits at — dividing by the latter explodes wherever\n // the two are close, which is how the first version of this drove density to its ceiling at\n // both extremes and took the window down to 7%.\n bool lighter = local < 0.5;\n float target = lighter ? min(local + u_presence, 1.0) : max(local - u_presence, 0.0);\n float separation = abs(target - local);\n float reached = abs(tintLuma - local) * density;\n if (u_presence > 0.0 && reached < separation) {\n float toEnd = abs((lighter ? VG_TINT_LIGHT : VG_TINT_DARK) - local);\n density = max(density, min(separation / max(toEnd, 1e-4), ${BODY.maxDemand}));\n }\n\n // Glass has no color of its own — only the color of what's beneath it (HIG \"Color\").\n float3 tintHue = mix(float3(1.0), vgHue(wide * 0.5 + ambient * 0.5), u_colorPickup);\n // The element appears by the lens and body building up, not by fading in (M 2:55): at\n // u_appear = 0 it's indistinguishable from the backdrop beneath it.\n rgb = mix(rgb, tintHue * tintLuma, density * u_appear);\n rgb += tintHue * (u_legibility * VG_MATTE_LIFT * u_appear);\n\n // TINTING — colored glass, not a fill (M 16:31, 17:03): the backdrop's lightness drives the\n // tone — deeper over dark, lighter over light — and the content's texture still shows through\n // the color.\n if (u_accent.a > 0.0) {\n float3 tone = u_accent.rgb * mix(${ACCENT.deep}, ${ACCENT.light}, local);\n float3 through = tone * (0.85 + 0.3 * vgLuma(rgb));\n rgb = mix(rgb, clamp(through, float3(0.0), float3(1.0)), u_accent.a * u_appear);\n }\n\n // CONTRAST COMPRESSION. The medium removes some light and mixes in scattered light, so content\n // under the glass moves toward the middle rather than simply getting lighter (M 2:35). The\n // previous layer only added light and, over a light backdrop, pushed the body the opposite way\n // from the reference.\n rgb = mix(rgb, vgHue(ambient) * VG_MEDIUM_LUMA, VG_MEDIUM_PULL * u_appear);\n // Ambient light reaches the body the same way across the whole element.\n rgb += ambient * u_edgeLight * VG_AMBIENT_SPILL * u_appear;\n // Glass concentrates light; the played portion is the segment where there's more of it.\n float glow = VG_CONCENTRATE + 0.08 * vgProgress(p, u_halfSize, u_progress);\n rgb += (1.0 - rgb) * glow * lens;\n\n // Under the finger and during morphing, glass concentrates light into a spot (M 4:56, 5:11).\n // This is a CONCENTRATION of the surroundings, not the glass's own whiteness: over a dark\n // backdrop the element lightens but never turns white, and ink over it stays legible.\n if (u_touchPress > 0.001) {\n float2 fromTouch = p - u_touch;\n float spotR = max(min(u_halfSize.x, u_halfSize.y) * 1.2, 1.0);\n float spot = u_touchPress * (0.3 + 0.55 * exp(-dot(fromTouch, fromTouch) / (spotR * spotR)));\n rgb = mix(rgb, clamp(ambient * 1.6 + float3(0.1), float3(0.0), float3(1.0)), spot * lens);\n }\n\n // RIM LIGHT — a separate layer, a line about 1 pt wide along the silhouette (M 2:36, 11:04):\n // brighter where the face faces the key light, weaker opposite it; where no light falls, a\n // dark outline. This line is what holds the element's presence, not a fill on the body: over a\n // flat backdrop a control is visible along its whole silhouette because of it.\n float line = (1.0 - smoothstep(0.0, lineW, e)) * lens;\n float outline = (1.0 - smoothstep(0.0, lineW * 0.6, e)) * lens;\n float lit = clamp(max(rimKey * 1.4, u_presence * 2.0), 0.0, 1.0);\n // The dark rim is its own layer around the whole silhouette, and the highlight sits ON TOP of\n // it (iOS 27). While the outline used to fade via a (1 − lit) multiplier, the silhouette read\n // as a single arc: highlight OR shadow.\n rgb *= 1.0 - ${RIM.darkEdge} * outline;\n rgb = mix(rgb, mix(refl, float3(1.0), 0.75), line * lit);\n\n if (u_debug > 9.5 && u_debug < 10.5) { rgb = spectral * 0.5; }\n if (u_debug > 10.5 && u_debug < 11.5) { rgb = float3(density, needForInk, structure); }\n // WHAT THE PROBE SAID ABOUT THE BACKDROP — three values that drive all the tinting: the\n // average lightness under the element, the vertical lightness slope, and the lightness AT THIS\n // SPOT. The slope is shown offset and squeezed by half: it's signed and rarely exceeds a\n // magnitude of one half.\n if (u_debug > 11.5) { rgb = float3(max(u_probeLuma, 0.0), 0.5 + u_probeSlope.y * 0.5, local); }\n\n rgb = vgSoftClip(rgb);\n\n // Sample alpha has to survive to the result: un-premultiplying color by its original alpha and\n // then returning it with a different one (the shape mask) would make a transparent backdrop\n // opaque and blow it out.\n float alpha = srcA * (1.0 - smoothstep(-1.0, 1.0, sd));\n return half4(half3(clamp(rgb, float3(0.0), float3(1.0)) * alpha), half(alpha));\n}\n`;\n","// VireGlass material model.\n//\n// Two distinct objects, and they must not be confused:\n//\n// VireGlassMaterial — CAUSES. What a piece of glass is described by: index of refraction,\n// thickness, bevel width, roughness, absorption color. This is what gets\n// configured.\n// VireGlassOptics — EFFECTS. Eighteen values the shaders need. Not configured: they're\n// derived (`resolveOptics`, physics in `optics.ts`).\n//\n// Before v4, what was exposed were the effects themselves — eighteen independent sliders. Half of\n// them are physically dependent, so combining them produced states that don't occur in real\n// glass: a thin bevel with a dense white rim, rim optics as a fraction of the element's own size.\n// Every such case was patched by adding yet another slider (`edgeDensity`, a size compensation) —\n// that is, compensating for physics that wasn't there. Here there's one cause, and the effects\n// are derived.\n\nimport {\n absorption,\n blur as blurFrom,\n dispersion as dispersionFrom,\n edgeDensity as edgeDensityFrom,\n gatherRadius as gatherRadiusFrom,\n fresnelStrength,\n FRESNEL_EXPONENT,\n mediumTint,\n refractionScale as refractionScaleFrom,\n refractionStrength,\n specularPower as specularPowerFrom,\n bodyDensity as bodyDensityFrom,\n edgeLight as edgeLightFrom,\n iridescence as iridescenceFrom,\n diffraction as diffractionFrom,\n colorPickup as colorPickupFrom,\n specularStrength,\n iorSpread as iorSpreadFrom,\n} from './optics';\n\nexport type VireGlassTint = { r: number; g: number; b: number };\n\nexport type VireGlassMaterial = {\n /** Index of refraction of the medium. Water 1.33, glass 1.5, sapphire 1.77. */\n ior: number;\n /** Glass thickness, dp. Drives center magnification and absorption. */\n thickness: number;\n /** Bevel width, dp. Absolute: on real glass the rim doesn't depend on the piece's size. */\n bevel: number;\n /** Surface roughness: backdrop haziness and highlight blur. */\n roughness: number;\n /** Response to device orientation, 0 means the light is locked to the screen. */\n environment: number;\n /** Not physics but a legibility requirement: how strongly the glass has to separate its own\n * lightness from the ink drawn over it. 0 means the glass is simply transparent. */\n legibility: number;\n /** Dimming layer UNDER the glass, 0..1. For the transparent (Clear) variant this is what\n * provides legibility: that glass has no adaptation, and without dimming the ink drowns over\n * bright content.\n *\n * Not to be confused with the mobile surface's `dim` prop: that one compensates for a platform\n * capture quirk (BlurView can't see a scrim drawn over the content), while this is a property\n * of the material itself. */\n dimming: number;\n /** Lightness of what the app draws OVER the glass: 1 is light icons and text, 0 is dark. A\n * cause, not a knob: the calling screen knows it. */\n ink: number;\n /** Minimum PRESENCE of the element itself: how far its body must stand off in lightness from\n * the backdrop behind it. Not the same as `legibility` — that's about the ink OVER the glass,\n * this is about the element itself. Over a uniform backdrop the glass has nothing to refract,\n * and the element disappears; for a control that's unacceptable. 0 means no presence is\n * required.\n *\n * The sign is taken FROM THE BACKDROP, not from ink polarity: over dark the body lightens,\n * over light it darkens. So the requirement works the same way on any backdrop. */\n presence: number;\n /** Surface film thickness, nm. This is where interference comes from: the optical path\n * difference in the film becomes comparable to the wavelength, and the reflection picks up\n * iridescence. 0 means no film. */\n film: number;\n};\n\nexport const MATERIAL_RANGES = {\n ior: [1, 2],\n thickness: [0, 60],\n bevel: [0, 40],\n roughness: [0, 1],\n environment: [0, 1],\n legibility: [0, 1],\n dimming: [0, 0.5],\n ink: [0, 1],\n presence: [0, 0.6],\n film: [0, 900],\n} as const satisfies Record<string, readonly [number, number]>;\n\nexport type VireGlassNumericKey = keyof typeof MATERIAL_RANGES;\n\n/** Radius over which the LOCAL backdrop lightness is taken, dp. Not per-pixel: otherwise the\n * glass chases individual strokes and a halo appears around letters beneath it. */\nexport const ADAPT_RADIUS = 22;\n\n/** Lens dimming on product surfaces — a flat fill over the backdrop. */\nexport const PRODUCT_DIM = 0.2;\n\nconst clamp = (v: number, lo: number, hi: number) => (v < lo ? lo : v > hi ? hi : v);\n\n/** The values the shaders need. Assembled from the material, never set by hand. */\nexport type VireGlassOptics = {\n blur: number;\n refraction: number;\n refractionScale: number;\n /** Bevel width in dp — an absolute property of the medium. */\n bevelDp: number;\n /** Radius over which the rim gathers light around the element. */\n gatherRadiusDp: number;\n fresnel: number;\n fresnelPower: number;\n specular: number;\n specularPower: number;\n dispersion: number;\n tint: VireGlassTint;\n tintStrength: number;\n edgeDensity: number;\n environment: number;\n legibility: number;\n /** Dimming of the content under the glass — the very layer of the Clear variant. */\n dimming: number;\n ink: number;\n /** Minimum presence of the element against the backdrop — in units of lightness. */\n presence: number;\n adaptRadius: number;\n bodyDensity: number;\n edgeLight: number;\n /** Film thickness, nm — passed straight to the shader: the path difference is computed in the\n * same units. */\n film: number;\n /** Strength of reflection iridescence. */\n iridescence: number;\n /** Strength of the edge diffraction fringes. */\n diffraction: number;\n /** How much the body is tinted by the color of its surroundings. */\n colorPickup: number;\n /** Index of refraction: the shader computes ray bending from it via Snell's law. */\n ior: number;\n /** Plate thickness, dp — before the correction for element size. */\n thicknessDp: number;\n /** Spread of the index between the red and blue channels — dispersion. */\n iorSpread: number;\n};\n\n// The product default — \"water\": lower index of refraction, thinner medium and bevel, an almost\n// smooth surface. Chosen on-device at the bench.\nexport const VIREGLASS_MATERIAL_V4: VireGlassMaterial = {\n ior: 1.33,\n thickness: 14,\n bevel: 5,\n roughness: 0.05,\n environment: 0.27,\n legibility: 0.26,\n dimming: 0,\n ink: 1,\n presence: 0.05,\n film: 340,\n};\n\n\n/**\n * Glass, not water. v4's index of refraction, 1.33, is water: almost no reflection at the rim,\n * almost no dispersion, 2% magnification. On screen that surface honestly reads as \"nothing much\",\n * and it was being mistaken for glass that wasn't rendering at all.\n *\n * 1.5 is ordinary glass. The effects follow from it on their own: reflection at the rim twice as\n * strong, dispersion one and a half times, ambient color pickup at its ceiling. Thickness is\n * raised a little too: it drives magnification, but absorption comes along with it, and\n * absorption is blackness.\n */\nexport const VIREGLASS_MATERIAL_V5: VireGlassMaterial = {\n ior: 1.5,\n thickness: 28,\n bevel: 6,\n roughness: 0.06,\n environment: 0.27,\n legibility: 0.26,\n dimming: 0,\n ink: 1,\n presence: 0.05,\n film: 340,\n};\n\n/**\n * Frosted sheet glass. The sheet sits over a LIVE screen, and it can't be transparent: transport\n * buttons and progress used to read right through it — the layout looked broken. Dimming doesn't\n * fix this (a flat scrim over a sharp picture doesn't hide it), so roughness is raised instead: it\n * drives `u_frost`, a floor on backdrop blur that the adaptive-blur ceiling doesn't reach.\n */\nexport const VIREGLASS_SHEET_MATERIAL: VireGlassMaterial = {\n ...VIREGLASS_MATERIAL_V5,\n roughness: 0.85,\n};\n/** The product's material. Consumers use this, not a specific version by name. */\n/**\n * Glass under large text (the lyrics panel in the player).\n *\n * The product material with NO change to optics — only `legibility` is raised. That's exactly its\n * reason to exist: \"text on me must stay readable.\" It adds density locally — denser over light\n * and busy backdrops, more transparent over an even dark one.\n *\n * A dedicated thick material (thickness 52, ior 1.58) used to sit here and was removed: medium\n * absorption grows with thickness, and the panel picked up a black fringe around its perimeter —\n * a sooty plate instead of glass. Don't turn thickness into a legibility knob; that's what\n * `legibility` is for.\n */\nexport const VIREGLASS_LYRICS_MATERIAL: VireGlassMaterial = {\n ...VIREGLASS_MATERIAL_V5,\n legibility: 0.95,\n};\n\n/**\n * Glass for controls — buttons, tiles, anything that gets pressed.\n *\n * THICK AND CLEAR, not the baseline: wider bevel, thickness up by half, higher index of\n * refraction and almost no roughness. Baseline glass is a lens over a calm backdrop; a control has\n * to read as an object you can pick up, and that's not done by a fill but by volume: a wide bevel\n * gives the rim something to catch, and thickness gives it depth.\n *\n * Thickness is set by BUILDUP AT THE RIM: on the reference control the stripe image under the\n * element bulges there by a factor of 1.85; ours came out at 1.36 at 30. Saturation sets in around\n * 44, and any extra thickness past that only adds absorption — hence 44, not more.\n *\n * It also drags `tintStrength` (= `absorption(thickness)`) along with it, and on the path WITHOUT\n * a native lens that sets body density: there the element is denser by 2.4 percentage points. On\n * the web the surface has no body at all; the effect only shows on Android below 13.\n *\n * `presence` is raised for the same reason: a piece of background is allowed to disappear over a\n * uniform canvas, a control isn't — it has to be visible before it's been tapped.\n *\n * IMPORTANT about size. The bevel is specified by the medium in dp and doesn't depend on the\n * element's size, but the geometry clamps it to half the smaller half-size (`MAX_BEVEL_FRACTION`).\n * On a 52 dp button that's 13 instead of 16 — a small element made of this glass ends up being\n * bevel all the way through. That's by design: a small piece of thick glass is supposed to look\n * like a lens, not a slab.\n */\nexport const VIREGLASS_CONTROL_MATERIAL: VireGlassMaterial = {\n ...VIREGLASS_MATERIAL_V5,\n ior: 1.69,\n thickness: 44,\n bevel: 8,\n roughness: 0.035,\n presence: 0.176,\n};\n\nexport const VIREGLASS_MATERIAL = VIREGLASS_MATERIAL_V5;\n\n/**\n * An element that CARRIES app ink (an icon, a line of text, cover art) has to keep it legible —\n * that's the reason `legibility` exists. An element with no ink has nothing to separate lightness\n * from, so the requirement is turned off for it: the model just promises it plain transparent\n * glass.\n *\n * The rule lives here, not with the consumer: otherwise the same icon button would get different\n * legibility on the web and on Android.\n */\nexport function materialForInk(material: VireGlassMaterial, carriesInk: boolean): VireGlassMaterial {\n // For the transparent variant, legibility is held by the dimming layer, not the body. Raising\n // its requirement would just turn it into ordinary glass, and the variants don't mix (219 §Clear).\n if (material.dimming > 0) return material;\n return { ...material, legibility: carriesInk ? VIREGLASS_LYRICS_MATERIAL.legibility : 0 };\n}\n\n/**\n * The transparent material variant (Clear). It has no adaptation — the glass stays consistently\n * more transparent, content beneath it shows through almost as-is, and legibility of the ink is\n * held by the dimming layer instead. Fit only where the reference's three conditions hold: the\n * element sits over media, the content can tolerate dimming, and the ink over it is large and\n * bright.\n */\nexport const VIREGLASS_CLEAR_MATERIAL: VireGlassMaterial = {\n ...VIREGLASS_MATERIAL,\n legibility: 0,\n presence: 0,\n // Apple's own numbers: 0.35 in the HIG Materials text, 0.3 in its own sample code. We take the\n // lower one — it comes from working code, not from prose, and Clear has to stay a window.\n dimming: 0.3,\n};\n\n/**\n * ACTIVE STATE as a state of the MEDIUM, not a highlight drawn over it.\n *\n * An active element is denser, clearer glass: higher index of refraction, thicker body, wider\n * bevel, less roughness. A bright rim, a strong highlight and greater presence all follow from\n * these causes on their own — assembling them separately would produce a state that doesn't occur\n * in real glass.\n *\n * `environment` is deliberately left untouched here. It sets how much the body is tinted by\n * what's beneath it, and at full strength an active button over cover art turned into a colored\n * blob: the state read as \"smudged\" rather than \"selected.\" A state indicator must not depend on\n * the backdrop — otherwise any image sliding underneath the element would fake it.\n */\nexport function activeMaterial(material: VireGlassMaterial, on: number): VireGlassMaterial {\n const k = Math.min(Math.max(on, 0), 1);\n return {\n ...material,\n ior: material.ior + 0.35 * k,\n thickness: material.thickness * (1 + 0.9 * k),\n bevel: material.bevel * (1 + 1.8 * k),\n roughness: material.roughness * (1 - 0.75 * k),\n presence: material.presence + 0.12 * k,\n };\n}\n\nexport function resolveMaterial(patch: Partial<VireGlassMaterial> = {}): VireGlassMaterial {\n const m = { ...VIREGLASS_MATERIAL, ...patch };\n const out = { ...m };\n for (const key of Object.keys(MATERIAL_RANGES) as VireGlassNumericKey[]) {\n const [lo, hi] = MATERIAL_RANGES[key];\n out[key] = clamp(out[key], lo, hi);\n }\n return out;\n}\n\n/** Causes → effects. All of the transition's physics lives in `optics.ts`. */\nexport function resolveOptics(patch: Partial<VireGlassMaterial> = {}): VireGlassOptics {\n const m = resolveMaterial(patch);\n return {\n blur: blurFrom(m.roughness),\n refraction: refractionStrength(m.ior),\n refractionScale: refractionScaleFrom(m.ior, m.thickness),\n bevelDp: m.bevel,\n gatherRadiusDp: gatherRadiusFrom(m.bevel),\n fresnel: fresnelStrength(m.ior),\n fresnelPower: FRESNEL_EXPONENT,\n specular: specularStrength(m.ior, m.roughness),\n specularPower: specularPowerFrom(m.roughness),\n dispersion: dispersionFrom(m.ior),\n tint: mediumTint(m.ior),\n tintStrength: absorption(m.thickness),\n edgeDensity: edgeDensityFrom(m.thickness, m.bevel),\n environment: m.environment,\n legibility: m.legibility,\n dimming: m.dimming,\n ink: m.ink,\n presence: m.presence,\n adaptRadius: ADAPT_RADIUS,\n bodyDensity: bodyDensityFrom(m.thickness),\n edgeLight: edgeLightFrom(m.ior),\n film: m.film,\n iridescence: iridescenceFrom(m.ior, m.film),\n diffraction: diffractionFrom(m.ior),\n colorPickup: colorPickupFrom(m.ior),\n ior: m.ior,\n thicknessDp: m.thickness,\n iorSpread: iorSpreadFrom(m.ior),\n };\n}\n\n/**\n * Frozen snapshots of the EFFECTS from the previous model. Kept as reference points until v4\n * settles: they can't be converted to causes — some of the values are internally inconsistent\n * (exactly why the move was made in the first place), and any \"equivalent\" material would be a\n * lie.\n */\nexport const LEGACY_OPTICS = {\n 'v3-manual': {\n blur: 4.32,\n refraction: 0.49,\n refractionScale: 1.05,\n bevelDp: 12.6,\n gatherRadiusDp: 40,\n fresnel: 0.77,\n fresnelPower: 2.86,\n specular: 0.3,\n specularPower: 74.58,\n dispersion: 0.54,\n tint: { r: 0.4, g: 0.4, b: 0.44 },\n tintStrength: 0.35,\n edgeDensity: 1.5,\n environment: 0.27,\n legibility: 0.55,\n dimming: 0,\n ink: 1,\n presence: 0,\n adaptRadius: ADAPT_RADIUS,\n bodyDensity: 0.14,\n edgeLight: 0.35,\n film: 0,\n iridescence: 0,\n diffraction: 0,\n colorPickup: 0,\n ior: 1.5,\n thicknessDp: 16,\n iorSpread: 0,\n },\n 'v2': {\n blur: 5,\n refraction: 0.95,\n refractionScale: 1.34,\n bevelDp: 14,\n gatherRadiusDp: 40,\n fresnel: 0.72,\n fresnelPower: 2.4,\n specular: 0.38,\n specularPower: 46,\n dispersion: 0.3,\n tint: { r: 0.4, g: 0.4, b: 0.44 },\n tintStrength: 0.1,\n edgeDensity: 3.63,\n environment: 0,\n legibility: 0,\n dimming: 0,\n ink: 1,\n presence: 0,\n adaptRadius: ADAPT_RADIUS,\n bodyDensity: 0.14,\n edgeLight: 0.35,\n film: 0,\n iridescence: 0,\n diffraction: 0,\n colorPickup: 0,\n ior: 1.5,\n thicknessDp: 16,\n iorSpread: 0,\n },\n 'v1': {\n blur: 12,\n refraction: 0.55,\n refractionScale: 1.14,\n bevelDp: 10,\n gatherRadiusDp: 40,\n fresnel: 0.5,\n fresnelPower: 3.2,\n specular: 0.42,\n specularPower: 58,\n dispersion: 0.22,\n tint: { r: 0.4, g: 0.4, b: 0.44 },\n tintStrength: 0.16,\n edgeDensity: 3.63,\n environment: 0,\n legibility: 0,\n dimming: 0,\n ink: 1,\n presence: 0,\n adaptRadius: ADAPT_RADIUS,\n bodyDensity: 0.14,\n edgeLight: 0.35,\n film: 0,\n iridescence: 0,\n diffraction: 0,\n colorPickup: 0,\n ior: 1.5,\n thicknessDp: 16,\n iorSpread: 0,\n },\n} as const satisfies Record<string, VireGlassOptics>;\n\nexport type LegacyOpticsName = keyof typeof LEGACY_OPTICS;\nexport const LEGACY_NAMES = Object.keys(LEGACY_OPTICS) as LegacyOpticsName[];\n\n/** Material presets: points in the space of CAUSES, not a set of ready-made effects. */\nexport const MATERIAL_PRESETS = {\n water: VIREGLASS_MATERIAL_V4,\n glass: { ...VIREGLASS_MATERIAL_V4, ior: 1.45, thickness: 25, bevel: 12.6, roughness: 0.17 },\n crystal: { ...VIREGLASS_MATERIAL_V4, ior: 1.7, thickness: 30, bevel: 16, roughness: 0.02 },\n frosted: { ...VIREGLASS_MATERIAL_V4, roughness: 0.55 },\n thick: { ...VIREGLASS_MATERIAL_V4, thickness: 48, bevel: 22 },\n thin: { ...VIREGLASS_MATERIAL_V4, thickness: 4, bevel: 3 },\n iridescent: { ...VIREGLASS_MATERIAL_V4, ior: 1.5, thickness: 8, bevel: 6, film: 620 },\n} as const satisfies Record<string, VireGlassMaterial>;\n\nexport type MaterialPresetName = keyof typeof MATERIAL_PRESETS;\nexport const PRESET_NAMES = Object.keys(MATERIAL_PRESETS) as MaterialPresetName[];\n\nexport const EFFECTS = [\n 'backdrop',\n 'blur',\n 'refraction',\n 'fresnel',\n 'bevel',\n 'specular',\n 'dispersion',\n 'tint',\n 'environment',\n 'legibility',\n 'interference',\n 'diffraction',\n] as const;\n\nexport type VireGlassEffect = (typeof EFFECTS)[number];\nexport type VireGlassToggles = Record<VireGlassEffect, boolean>;\n\nexport const ALL_EFFECTS_ON: VireGlassToggles = EFFECTS.reduce(\n (acc, e) => ({ ...acc, [e]: true }),\n {} as VireGlassToggles,\n);\n\n/**\n * A toggle zeroes out an EFFECT, it isn't a branch in the shader: the ON/OFF comparison runs on\n * one shader variant, otherwise two different programs would be getting compared. It operates on\n * optics, not on the material: a cause can't be zeroed out — it drags half a dozen effects along\n * with it. `backdrop` isn't handled by a toggle here: it decides whether to mount the BlurView at\n * all.\n */\nexport function applyToggles(\n optics: VireGlassOptics,\n toggles: Partial<VireGlassToggles> = {},\n): VireGlassOptics {\n const on = { ...ALL_EFFECTS_ON, ...toggles };\n const o = { ...optics, tint: { ...optics.tint } };\n if (!on.blur) o.blur = 0;\n if (!on.refraction) {\n o.refraction = 0;\n o.refractionScale = 1;\n o.ior = 1;\n }\n if (!on.fresnel) o.fresnel = 0;\n if (!on.bevel) o.bevelDp = 1;\n if (!on.specular) o.specular = 0;\n if (!on.dispersion) {\n o.dispersion = 0;\n o.iorSpread = 0;\n }\n if (!on.tint) o.tintStrength = 0;\n if (!on.environment) o.environment = 0;\n if (!on.legibility) {\n o.legibility = 0;\n // The dimming layer is the same answer to the legibility requirement, just for the\n // transparent variant.\n o.dimming = 0;\n }\n if (!on.interference) o.iridescence = 0;\n if (!on.diffraction) o.diffraction = 0;\n return o;\n}\n\n/** Order = the `u_debug` value in both shaders. Bench-only. */\nexport const DEBUG_MODES = [\n 'normal',\n 'sdf',\n 'mask',\n 'edge',\n 'fresnel',\n 'refraction',\n 'backdrop',\n 'specular',\n 'dispersion',\n 'normals',\n 'spectral',\n 'adapt',\n 'probe',\n] as const;\n\nexport type VireGlassDebugMode = (typeof DEBUG_MODES)[number];\n\nexport function debugIndex(mode: VireGlassDebugMode): number {\n return DEBUG_MODES.indexOf(mode);\n}\n","import { ACCENT, MORPH } from './law';\nimport {\n bevelDp,\n bevelFraction,\n halfMinDp,\n rimDp,\n shadowReachDp,\n surfacePadDp,\n thicknessDp,\n type VireGlassGeometry,\n} from './geometry';\nimport { LENS_SHADER } from './lens-shader';\nimport { neckToBridge } from './sdf';\nimport { debugIndex, type VireGlassDebugMode, type VireGlassOptics } from './material';\n\n/** Smooth union with a second shape — bench morphing experiment only. */\nexport type VireGlassMorph = {\n offsetX: number;\n offsetY: number;\n width: number;\n height: number;\n cornerRadius: number;\n /** Seam smoothing radius in dp; 0 turns the second shape off. */\n smoothing: number;\n};\n\n\n/** Surface response to a finger: the field around the touch point deforms, not the shape's\n * bounding box (`vgTouchWarp` in `sdf.ts`). Lengths in dp; `radius` = 0 turns the response off\n * entirely. */\nexport type VireGlassTouch = {\n x: number;\n y: number;\n pullX: number;\n pullY: number;\n press: number;\n radius: number;\n waveAmp: number;\n wavePhase: number;\n};\n\nconst NO_TOUCH: VireGlassTouch = {\n x: 0, y: 0, pullX: 0, pullY: 0, press: 0, radius: 0, waveAmp: 0, wavePhase: 0,\n};\n\nconst NO_MORPH = { offsetX: 0, offsetY: 0, width: 0, height: 0, cornerRadius: 0, smoothing: 0 };\n\n/** No progress. Negative rather than zero: zero is the start of the track, a legitimate value. */\nconst NO_PROGRESS = -1;\n\n/** Tinted glass for the primary action (M 16:08). Color is RGB 0…1. */\nexport type VireGlassAccent = { color: readonly [number, number, number]; amount?: number };\n\n/** Default tint fraction: the color reads, but content beneath it is still visible. */\nexport const ACCENT_AMOUNT = ACCENT.amount;\nconst NO_ACCENT = [0, 0, 0, 0] as const;\n\n\n\n/** Magnification at the flat center. Kept separate from the uniform channel: it's also used by\n * the fallback below Android 13, where there's no RuntimeShader and only an affine magnifier is\n * left. */\nexport const lensMagnify = (o: VireGlassOptics) => 1 + (o.refractionScale - 1) * o.refraction;\n\n/**\n * Lens uniforms. Assembled as ONE channel: name, size and values travel together, so a mismatch\n * with the shader is caught immediately, with the name right there in the log. There used to be a\n * separate Kotlin `Prop` per value — Expo swallows an unknown prop silently, and the lens simply\n * never turned on (docs/material-lab.md E-01, which is how refraction ended up disabled for an\n * entire phase).\n *\n * Everything with a dimension is converted to PIXELS right here. The native view is left with\n * only the uniforms it alone knows: its own size and its own place on screen.\n */\n// The lens's uniform names and sizes are always the same — the set is fixed. They're handed over\n// as ONE instance for the whole process: otherwise every frame of a drag would send three dozen\n// fresh strings to the native side, React can't tell \"unchanged\" from \"a new array\", and motion\n// goes stepwise. The values are the only thing that actually changes.\nconst shapes = new Map<number, { names: string[]; sizes: number[] }>();\n\nfunction channel(entries: [string, number | readonly number[]][]) {\n const uniformValues: number[] = [];\n let shape = shapes.get(entries.length);\n let same = shape !== undefined;\n for (let i = 0; i < entries.length; i += 1) {\n const [name, value] = entries[i];\n const v = typeof value === 'number' ? [value] : value;\n if (same && (shape!.names[i] !== name || shape!.sizes[i] !== v.length)) same = false;\n for (const x of v) uniformValues.push(x);\n }\n if (!same) {\n shape = {\n names: entries.map((e) => e[0]),\n sizes: entries.map((e) => (typeof e[1] === 'number' ? 1 : e[1].length)),\n };\n shapes.set(entries.length, shape);\n }\n return { uniformNames: shape!.names, uniformSizes: shape!.sizes, uniformValues };\n}\n\n/** Props for the native view. Besides the uniform channel, only what the view uses itself lives\n * here: the shader source and the visible glass's size (the probe rectangle is taken from it). */\nexport function toLensProps(\n optics: VireGlassOptics,\n geometry: VireGlassGeometry,\n density: number,\n options: {\n debug?: VireGlassDebugMode;\n morph?: VireGlassMorph;\n /** Third shape of the fused body; a zero size turns it off. */\n morph2?: VireGlassMorph;\n groupProbe?: number[];\n touch?: VireGlassTouch;\n /** Progress fraction, 0…1: to the left of the boundary the element is active. `undefined` means no progress. */\n progress?: number;\n /** Key light direction in screen space; defaults to the light at rest. */\n light?: readonly [number, number];\n /** 0…1: the lens builds up as the element appears — instead of a plain fade. */\n appear?: number;\n /** Tint for the primary action: the glass color and the fraction it lays over the content. */\n accent?: VireGlassAccent;\n } = {},\n) {\n const morph = options.morph ?? NO_MORPH;\n const morph2 = options.morph2 ?? NO_MORPH;\n const touch = options.touch ?? NO_TOUCH;\n // Backdrop estimate for the whole surface group. Travels through the same channel as the\n // material and applies AFTER the lens's own estimate — that is, it simply overrides it. It\n // can't travel as a separate prop: the lens view is wrapped in an animated component.\n const g = options.groupProbe;\n const group: [string, number | readonly number[]][] =\n g && g.length >= 9\n ? [\n ['u_probeLuma', g[0]],\n ['u_probeBusy', g[1]],\n ['u_probeRange', [g[2], g[3]]],\n ['u_probeSlope', [g[4], g[5]]],\n ['u_probe', [g[6], g[7], g[8]]],\n ]\n : [];\n const d = density;\n const halfW = (geometry.width * d) / 2;\n const halfH = (geometry.height * d) / 2;\n const halfMin = Math.min(halfW, halfH);\n\n return {\n shaderSource: LENS_SHADER,\n glassWidth: geometry.width,\n glassHeight: geometry.height,\n ...channel([\n ['u_halfSize', [halfW, halfH]],\n ['u_corner', Math.min(geometry.cornerRadius * d, halfMin)],\n ['u_bevel', Math.max(bevelDp(geometry, optics) * d, 1)],\n ['u_thick', thicknessDp(geometry, optics) * d],\n ['u_rim', rimDp(geometry, optics) * d],\n ['u_ior', optics.ior],\n ['u_iorSpread', optics.iorSpread],\n ['u_light', options.light ?? REST_LIGHT],\n ['u_appear', options.appear ?? 1],\n ['u_accent', options.accent ? [...options.accent.color, options.accent.amount ?? ACCENT_AMOUNT] : NO_ACCENT],\n // Haze from surface roughness. Lives in the same disc-gather pass as adaptive scattering,\n // and fades out toward the bevel: that's a different job there — bending the ray and\n // splitting it.\n ['u_frost', optics.blur * d],\n ['u_ink', optics.ink],\n ['u_legibility', optics.legibility],\n ['u_dim', optics.dimming],\n ['u_presence', optics.presence],\n ['u_adaptRadius', optics.adaptRadius * d],\n ['u_bodyDensity', optics.bodyDensity],\n ['u_edgeLight', optics.edgeLight],\n ['u_fresnel', optics.fresnel],\n ['u_specular', optics.specular],\n // The rim gathers light from the element's vicinity — this is a radius around the shape,\n // not its bevel.\n ['u_reflectReach', optics.gatherRadiusDp * d],\n ['u_film', optics.film],\n ['u_iridescence', optics.iridescence],\n ['u_diffraction', optics.diffraction],\n ['u_colorPickup', optics.colorPickup],\n ['u_morphOffset', [morph.offsetX * d, morph.offsetY * d]],\n ['u_morphHalf', [(morph.width * d) / 2, (morph.height * d) / 2]],\n ['u_morphCorner', morph.cornerRadius * d],\n ['u_morphK', morph.smoothing * d],\n ['u_morph2Offset', [morph2.offsetX * d, morph2.offsetY * d]],\n ['u_morph2Half', [(morph2.width * d) / 2, (morph2.height * d) / 2]],\n ['u_morph2Corner', morph2.cornerRadius * d],\n ['u_touch', [touch.x * d, touch.y * d]],\n ['u_pull', [touch.pullX * d, touch.pullY * d]],\n ['u_touchPress', touch.press],\n ['u_touchRadius', touch.radius * d],\n ['u_wave', [touch.waveAmp * d, touch.wavePhase]],\n ['u_progress', options.progress ?? NO_PROGRESS],\n ['u_debug', debugIndex(options.debug ?? 'normal')],\n ...group,\n ]),\n };\n}\n\n/** Static part of the surface uniforms. Dynamics (gesture, press, light) get mixed in inside the\n * component's worklet — the adapter must stay an ordinary function. */\nexport function toSurfaceUniforms(\n optics: VireGlassOptics,\n geometry: VireGlassGeometry,\n options: {\n debug?: VireGlassDebugMode;\n morph?: VireGlassMorph;\n /** Third shape of the fused body; a zero size turns it off. */\n morph2?: VireGlassMorph;\n dragLimit?: number;\n shadow?: number;\n /** Average ambient color around the element: light from it bleeds into the shadow (reference 219 @8:22). */\n ambient?: readonly [number, number, number];\n /** The lens draws the glass body — the surface is left with the highlight, shadow and icon. */\n bodyInLens?: boolean;\n touch?: VireGlassTouch;\n /** Progress fraction, 0…1: to the left of the boundary the element is active. `undefined` means no progress. */\n progress?: number;\n /** 0…1: the element is appearing — shadow and ink build up together with the lens. */\n appear?: number;\n /** 0 — the element sinks in under the finger, 1 — it rises into glass (reference §5). */\n lift?: number;\n } = {},\n) {\n const morph = options.morph ?? NO_MORPH;\n const morph2 = options.morph2 ?? NO_MORPH;\n const touch = options.touch ?? NO_TOUCH;\n const pad = surfacePadDp(geometry, options.dragLimit ?? 0, morph);\n return {\n u_center: [geometry.width / 2 + pad, geometry.height / 2 + pad],\n u_halfSize: [geometry.width / 2, geometry.height / 2],\n u_corner: Math.min(geometry.cornerRadius, halfMinDp(geometry)),\n u_bevel: bevelDp(geometry, optics),\n u_thickness: bevelFraction(geometry, optics),\n u_morphOffset: [morph.offsetX, morph.offsetY],\n u_morphHalf: [morph.width / 2, morph.height / 2],\n u_morphCorner: morph.cornerRadius,\n u_morphK: morph.smoothing,\n u_morph2Offset: [morph2.offsetX, morph2.offsetY],\n u_morph2Half: [morph2.width / 2, morph2.height / 2],\n u_morph2Corner: morph2.cornerRadius,\n u_edgeDensity: optics.edgeDensity,\n u_dispersion: optics.dispersion,\n u_refraction: optics.refraction,\n // Tint density is suppressed when the lens is drawing the body: painting it twice would\n // double the fill, and adaptation is impossible on the surface anyway — it can't see the\n // backdrop. The color itself stays: it drives absorption at the rim.\n u_tint: [\n optics.tint.r,\n optics.tint.g,\n optics.tint.b,\n options.bodyInLens ? 0 : optics.tintStrength,\n ],\n u_shadow: options.shadow ?? 1,\n // Neutral by default: without an ambient sample, the shadow stays exactly as it was.\n u_ambient: options.ambient ?? [0, 0, 0],\n u_shadowReach: shadowReachDp(geometry),\n u_touch: [touch.x, touch.y],\n u_pull: [touch.pullX, touch.pullY],\n u_touchPress: touch.press,\n u_touchRadius: touch.radius,\n u_wave: [touch.waveAmp, touch.wavePhase],\n u_presence: optics.presence,\n u_progress: options.progress ?? NO_PROGRESS,\n u_appear: options.appear ?? 1,\n // Zero by default: buttons sink in, the way every element behaved before this rule existed.\n u_lift: options.lift ?? 0,\n\n u_debug: debugIndex(options.debug ?? 'normal'),\n };\n}\n\n/**\n * Surface fields that are LENGTHS: the adapter's contract is specified in dp, and on the web each\n * one of them has to be multiplied by screen density. The list lives here rather than in the\n * renderer, where it used to be a dozen and a half manual multiplications in a row: a missed\n * field there was only caught by eye, and the miss isn't visible everywhere — at density 1,\n * multiplying is indistinguishable from not doing it.\n */\nexport type SurfaceUniforms = ReturnType<typeof toSurfaceUniforms>;\n\nexport const SURFACE_LENGTH_UNIFORMS = [\n 'u_halfSize',\n 'u_corner',\n 'u_bevel',\n 'u_morphOffset',\n 'u_morphHalf',\n 'u_morphCorner',\n 'u_morphK',\n 'u_morph2Offset',\n 'u_morph2Half',\n 'u_morph2Corner',\n 'u_shadowReach',\n 'u_touch',\n 'u_pull',\n 'u_touchRadius',\n // `satisfies`, not just `as const`: otherwise a typo in the name compiles silently and makes it\n // to runtime — the field with the garbage name gets NaN, and the real one is left un-multiplied.\n] as const satisfies readonly (keyof SurfaceUniforms)[];\n\n/**\n * Surface uniforms in device pixels. Android draws in dp and calls the adapter as-is; the web\n * needs this conversion. `u_center` isn't part of it: the renderer knows the element's center on\n * its own.\n */\nexport function toDeviceSurfaceUniforms(raw: SurfaceUniforms, density: number): SurfaceUniforms {\n const scaled: Record<string, unknown> = { ...raw };\n for (const key of SURFACE_LENGTH_UNIFORMS) {\n const value: unknown = scaled[key];\n scaled[key] = Array.isArray(value)\n ? (value as number[]).map((v) => v * density)\n : (value as number) * density;\n }\n // Of the wave, only the amplitude has a length; the phase is in turns and knows nothing about density.\n scaled.u_wave = [raw.u_wave[0] * density, raw.u_wave[1]];\n return scaled as SurfaceUniforms;\n}\n\n/** Key light direction in screen coordinates, for when the orientation response is off. */\nexport const REST_LIGHT: readonly [number, number] = [-0.577, -0.817];\n\n/** Uniforms the component mixes in inside the worklet (press, active, light direction) and via\n * the icon layer. Listed here so a test can check the shader contract's completeness without\n * importing the component itself (it pulls in Skia and doesn't come up in a node environment). */\nexport const DYNAMIC_UNIFORMS = ['u_press', 'u_active'] as const;\n\nexport const ICON_UNIFORMS = ['u_iconOn', 'u_iconScale', 'u_inkIdle', 'u_inkActive'] as const;\n\n/** App-colored overlay layer on the glass. The `u_overlay` sampler shares its contract with the\n * ink mask and is sampled at the SAME coordinate — otherwise deformation would carry them apart. */\nexport const OVERLAY_UNIFORMS = ['u_overlayOn'] as const;\n\n/**\n * Fusing two surfaces into one continuous medium.\n *\n * The second shape is described RELATIVE to the first one's center, because both live in the\n * same shader: the union has no \"two pieces of glass,\" just one body with two bulges — the mask,\n * the refraction and the rim are all computed from a single shared scene.\n *\n * `t` — how fused the shapes are: 0 turns the second shape off before any computation, 1 gives a\n * single shared medium. The seam's smoothing radius is a fraction of the smaller half-size: on\n * large elements the bridge has to be wider, otherwise the seam is left with a sharp corner,\n * which doesn't happen in a liquid.\n */\n\n\n/**\n * How wide the neck between two lobes has to be: enough to soften the seam on a large element, and\n * enough to actually SPAN whatever is between them. The second term is the one that was missing.\n */\nfunction neckFor(\n half: number,\n aw: number,\n ah: number,\n bw: number,\n bh: number,\n offsetX: number,\n offsetY: number,\n): number {\n // Surface-to-surface, measured between the bounding boxes. The rounded corners put the true\n // surfaces slightly further apart, so this errs toward a wider neck, which is the safe side.\n const gx = Math.abs(offsetX) - (aw + bw) / 2;\n const gy = Math.abs(offsetY) - (ah + bh) / 2;\n const gap = Math.hypot(Math.max(gx, 0), Math.max(gy, 0));\n // Capped at the narrowest of the two bodies. A neck wider than what it joins is not a neck, and\n // past that width the honest answer is that these are two things rather than one — a menu that\n // has finished leaving its button, two controls too far apart to be a segmented control. Being\n // separate is a legitimate state; being joined by a blob larger than either shape is not.\n const widest = Math.min(aw, ah, bw, bh);\n return Math.min(Math.max(half * MORPH.neck, neckToBridge(gap) * MORPH.fuse), widest);\n}\n\nexport function morphBetween(\n a: VireGlassGeometry,\n b: VireGlassGeometry,\n offsetX: number,\n offsetY: number,\n t: number,\n): VireGlassMorph | undefined {\n if (t <= 0) return undefined;\n const half = Math.min(halfMinDp(a), halfMinDp(b));\n return {\n offsetX,\n offsetY,\n width: b.width,\n height: b.height,\n cornerRadius: b.cornerRadius,\n smoothing: neckFor(half, a.width, a.height, b.width, b.height, offsetX, offsetY) * Math.min(t, 1),\n };\n}\n\n/**\n * MITOSIS: the second shape grows OUT OF the first and travels to its place, instead of appearing\n * beside it at full size. A menu coming out of its button, a control dividing into segments.\n *\n * `morphBetween` is the other half — two shapes that are both already where they belong, fusing\n * into one body — and run backwards it is meiosis, the neck thinning until it breaks. What neither\n * of them may do is pop: at any `t` above zero this returns one continuous body, because that is\n * the whole of what the reference says a material transition is.\n *\n * The choreography stays the host's: what the shapes MEAN is the app's business. How a silhouette\n * emerges from another silhouette is the material's, the same way the spring under a finger is.\n */\nexport function morphOutOf(\n source: VireGlassGeometry,\n target: VireGlassGeometry,\n offsetX: number,\n offsetY: number,\n t: number,\n): VireGlassMorph | undefined {\n if (t <= 0) return undefined;\n const k = Math.min(t, 1);\n // At t = 0 the lobe IS the parent, in the parent's place: the transition starts from one body and\n // never has a first frame in which a second one exists. The parent's own surface is what the neck\n // attaches to, so nothing has to be budded off to give it something to hold.\n const width = source.width + (target.width - source.width) * k;\n const height = source.height + (target.height - source.height) * k;\n const cornerRadius = source.cornerRadius + (target.cornerRadius - source.cornerRadius) * k;\n const half = Math.min(halfMinDp(source), Math.min(width, height) / 2);\n return {\n offsetX: offsetX * k,\n offsetY: offsetY * k,\n width,\n height,\n cornerRadius,\n // The lobe travels as it grows, so the span changes every frame and the neck is measured\n // against where the lobe actually is, not against where it will end up.\n smoothing: neckFor(half, source.width, source.height, width, height, offsetX * k, offsetY * k),\n };\n}\n","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","// Low-level GL helpers for the pipeline: program compilation, textures, FBOs, the fullscreen\n// triangle. Knows nothing about VireGlass — a reusable layer on top of WebGL2.\n\n/** The same trick as in `scripts/check-glsl.mjs`: three vertices with no buffer, indexed by\n * `gl_VertexID`. One vertex shader works for EVERY pass of the pipeline, because each fragment\n * shader pulls its own coordinate from `gl_FragCoord`. */\nexport const FULLSCREEN_TRIANGLE_VERTEX_SOURCE = `#version 300 es\nconst vec2 VG_POS[3] = vec2[3](vec2(-1.0, -1.0), vec2(3.0, -1.0), vec2(-1.0, 3.0));\nvoid main() {\n gl_Position = vec4(VG_POS[gl_VertexID], 0.0, 1.0);\n}\n`;\n\nexport function compileShader(gl: WebGL2RenderingContext, type: number, source: string): WebGLShader {\n const shader = gl.createShader(type);\n if (!shader) throw new Error('vireglass/web: gl.createShader returned null');\n gl.shaderSource(shader, source);\n gl.compileShader(shader);\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n const log = gl.getShaderInfoLog(shader) ?? '(no log)';\n gl.deleteShader(shader);\n throw new Error(`vireglass/web: shader failed to compile:\\n${log}`);\n }\n return shader;\n}\n\nexport function createProgram(\n gl: WebGL2RenderingContext,\n vertexSource: string,\n fragmentSource: string,\n): WebGLProgram {\n const vertex = compileShader(gl, gl.VERTEX_SHADER, vertexSource);\n const fragment = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSource);\n const program = gl.createProgram();\n if (!program) throw new Error('vireglass/web: gl.createProgram returned null');\n gl.attachShader(program, vertex);\n gl.attachShader(program, fragment);\n gl.linkProgram(program);\n gl.deleteShader(vertex);\n gl.deleteShader(fragment);\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n const log = gl.getProgramInfoLog(program) ?? '(no log)';\n gl.deleteProgram(program);\n throw new Error(`vireglass/web: program failed to link:\\n${log}`);\n }\n return program;\n}\n\n/** Draws the fullscreen triangle with the current program. No attributes — the vertices are\n * baked into the shader, so even a VAO isn't needed. */\nexport function drawFullscreenTriangle(gl: WebGL2RenderingContext): void {\n gl.drawArrays(gl.TRIANGLES, 0, 3);\n}\n\nexport type TextureOptions = {\n width: number;\n height: number;\n /** RGBA8 by default; the probe needs float when it needs more than 8 bits of precision. */\n internalFormat?: number;\n format?: number;\n type?: number;\n /** CLAMP_TO_EDGE by default. A simulation with a periodic domain (a tiling pattern, a torus)\n * wants REPEAT on its own textures instead — applied to both S and T, there's no case here\n * that wants them to differ. */\n wrap?: number;\n};\n\n/**\n * General-purpose texture allocator: LINEAR on MIN and MAG, no mipmaps, CLAMP_TO_EDGE by default.\n * Used throughout `web/` for `contentTexture` and the probe's downsample buffer, and public so a\n * `VireGlassBackdropPass` can build its own textures the same way. LINEAR rather than NEAREST\n * because Skia's `.eval` is bilinear by default — NEAREST would break the disc gather into\n * pixel-stepped bands (spec §4); a pass with different needs overrides `internalFormat`/`format`/\n * `type`/`wrap` directly.\n */\nexport function createTexture(gl: WebGL2RenderingContext, options: TextureOptions): WebGLTexture {\n const texture = gl.createTexture();\n if (!texture) throw new Error('vireglass/web: gl.createTexture returned null');\n const internalFormat = options.internalFormat ?? gl.RGBA8;\n const format = options.format ?? gl.RGBA;\n const type = options.type ?? gl.UNSIGNED_BYTE;\n const wrap = options.wrap ?? gl.CLAMP_TO_EDGE;\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texImage2D(\n gl.TEXTURE_2D,\n 0,\n internalFormat,\n options.width,\n options.height,\n 0,\n format,\n type,\n null,\n );\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrap);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrap);\n gl.bindTexture(gl.TEXTURE_2D, null);\n return texture;\n}\n\nexport type UniformValue = number | readonly number[];\n\n/** One cache per program: `getUniformLocation` is a name lookup, and every piece re-sets every\n * uniform every frame. */\nexport function locationCache(gl: WebGL2RenderingContext, program: WebGLProgram) {\n const cache = new Map<string, WebGLUniformLocation | null>();\n return (name: string): WebGLUniformLocation | null => {\n let loc = cache.get(name);\n if (loc === undefined) {\n loc = gl.getUniformLocation(program, name);\n cache.set(name, loc);\n }\n return loc;\n };\n}\n\nexport function setUniform(\n gl: WebGL2RenderingContext,\n loc: WebGLUniformLocation | null,\n value: UniformValue,\n): void {\n if (!loc) return;\n if (typeof value === 'number') {\n gl.uniform1f(loc, value);\n return;\n }\n switch (value.length) {\n case 1:\n gl.uniform1f(loc, value[0]);\n break;\n case 2:\n gl.uniform2f(loc, value[0], value[1]);\n break;\n case 3:\n gl.uniform3f(loc, value[0], value[1], value[2]);\n break;\n case 4:\n gl.uniform4f(loc, value[0], value[1], value[2], value[3]);\n break;\n default:\n throw new Error(`vireglass/web: unsupported uniform size (${value.length})`);\n }\n}\n\nexport function createFramebuffer(gl: WebGL2RenderingContext, texture: WebGLTexture): WebGLFramebuffer {\n const fbo = gl.createFramebuffer();\n if (!fbo) throw new Error('vireglass/web: gl.createFramebuffer returned null');\n gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);\n gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);\n const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n if (status !== gl.FRAMEBUFFER_COMPLETE) {\n throw new Error(`vireglass/web: FBO incomplete, status ${status}`);\n }\n return fbo;\n}\n\n/** The one texture unit any pipeline pass needs here — a bind with no extra slot bookkeeping. */\nexport function bindTextureAt(\n gl: WebGL2RenderingContext,\n unit: number,\n texture: WebGLTexture,\n program: WebGLProgram,\n uniformName: string,\n): void {\n gl.activeTexture(gl.TEXTURE0 + unit);\n gl.bindTexture(gl.TEXTURE_2D, texture);\n const loc = gl.getUniformLocation(program, uniformName);\n gl.uniform1i(loc, unit);\n}\n","// GPU timing for `VireGlassRenderer.getLastGpuMs()`, isolated from the renderer so it can be\n// unit-tested against a fake: `EXT_disjoint_timer_query_webgl2` is unavailable in the headless\n// Chromium the shader gates run in, so nothing in this repository otherwise exercises the ring\n// logic at all.\n\n/** The two tokens the extension adds; everything else the timer needs is core WebGL2. */\nexport type GpuTimerExtension = {\n readonly TIME_ELAPSED_EXT: number;\n readonly GPU_DISJOINT_EXT: number;\n};\n\n/** The slice of WebGL2 this module needs, kept narrow so a test can fake it without a real GL\n * context. A `WebGL2RenderingContext` satisfies this structurally — no cast needed at the call\n * site in `renderer.ts`. */\nexport type GpuTimerContext = {\n readonly QUERY_RESULT_AVAILABLE: number;\n readonly QUERY_RESULT: number;\n getExtension(name: string): unknown;\n createQuery(): WebGLQuery | null;\n deleteQuery(query: WebGLQuery): void;\n beginQuery(target: number, query: WebGLQuery): void;\n endQuery(target: number): void;\n getQueryParameter(query: WebGLQuery, pname: number): unknown;\n getParameter(pname: number): unknown;\n};\n\n/** Queries in flight at once. `EXT_disjoint_timer_query_webgl2` results arrive asynchronously,\n * often a few frames late, so one query per frame would mean starting a new query before the\n * previous one's result is back — undefined by spec. */\nexport const GPU_TIMER_RING_SIZE = 3;\n\ntype TimerSlot = { query: WebGLQuery; pending: boolean };\n\nexport type GpuTimer = {\n /** Runs `fn` as one timed sample. Always calls the matching `endQuery` even if `fn` throws, so\n * a frame that fails partway through (a broken backdrop pass, a validation error) never leaves\n * a query open for the next call to trip over. */\n time<T>(fn: () => T): T;\n /** `null` without the extension or while the ring hasn't produced a non-disjoint result yet. */\n getLastMs(): number | null;\n destroy(): void;\n};\n\n/** Missing the extension (most headless/CI setups, and every browser but Chromium-family in\n * practice) degrades every method here to a no-op / `null` — never an error. */\nexport function createGpuTimer(gl: GpuTimerContext): GpuTimer {\n const ext = gl.getExtension('EXT_disjoint_timer_query_webgl2') as GpuTimerExtension | null;\n const slots: TimerSlot[] = ext\n ? Array.from({ length: GPU_TIMER_RING_SIZE }, () => ({\n query: gl.createQuery() as WebGLQuery,\n pending: false,\n }))\n : [];\n let cursor = 0;\n let active: TimerSlot | null = null;\n let lastMs: number | null = null;\n\n function poll(): void {\n if (!ext) return;\n for (const slot of slots) {\n if (!slot.pending) continue;\n if (!gl.getQueryParameter(slot.query, gl.QUERY_RESULT_AVAILABLE)) continue;\n slot.pending = false;\n // A disjoint event (e.g. the GPU clock changed) invalidates every query since the last\n // check — the sample is discarded, not reported as zero or stale.\n if (gl.getParameter(ext.GPU_DISJOINT_EXT)) continue;\n const ns = gl.getQueryParameter(slot.query, gl.QUERY_RESULT) as number;\n lastMs = ns / 1e6;\n }\n }\n\n function begin(): void {\n active = null;\n if (!ext || slots.length === 0) return;\n poll();\n const slot = slots[cursor];\n // The ring hasn't caught up (this slot's previous result isn't back yet) — skip timing this\n // frame rather than reuse a query that's still in flight.\n if (slot.pending) return;\n gl.beginQuery(ext.TIME_ELAPSED_EXT, slot.query);\n active = slot;\n }\n\n function end(): void {\n if (!ext || !active) return;\n gl.endQuery(ext.TIME_ELAPSED_EXT);\n active.pending = true;\n cursor = (cursor + 1) % slots.length;\n active = null;\n }\n\n function time<T>(fn: () => T): T {\n begin();\n try {\n return fn();\n } finally {\n end();\n }\n }\n\n function getLastMs(): number | null {\n poll();\n return lastMs;\n }\n\n function destroy(): void {\n for (const slot of slots) gl.deleteQuery(slot.query);\n }\n\n return { time, getLastMs, destroy };\n}\n","// Backdrop lightness probe under the lens — the web counterpart to the native capture in\n// ImageReader (`GlassLensView.kt`); see the web-core design spec and its Phase 3 plan.\n//\n// Downsampling the scene into a 48×96 grid runs as a GL pass (sampling `content` in the\n// downsample program, see `renderer.ts`), while READING the result happens here, through a PBO\n// with `fenceSync`: a synchronous `readPixels` stalls the pipeline, and a naive port stutters\n// while dragging sliders.\n//\n// Lagging by a frame or two isn't a bug, it's part of the contract (per the Phase 3 plan): we\n// read whatever the GPU already finished by the previous frame, rather than waiting on the\n// current one.\n\nimport { bindTextureAt, createFramebuffer, createProgram, drawFullscreenTriangle } from './gl';\n\nexport const PROBE_GRID_WIDTH = 48;\nexport const PROBE_GRID_HEIGHT = 96;\n\n/** Stats for the lens rectangle from a single probe sample. `null` until the first ready frame\n * has come in — the shader falls back to `u_probeLuma = -1` in that case (see `lens-shader.ts`). */\nexport type ProbeStats = {\n luma: number;\n busy: number;\n lo: number;\n hi: number;\n slopeX: number;\n slopeY: number;\n r: number;\n g: number;\n b: number;\n};\n\nconst DOWNSAMPLE_FRAGMENT_SOURCE = `#version 300 es\nprecision highp float;\nout vec4 fragColor;\nuniform sampler2D u_content;\nuniform vec2 u_contentSize;\nuniform vec2 u_gridSize;\n\n// Cell → content UV mapping. Does NOT correspond to the canvas's visible top/bottom — this is an\n// internal pass, its buffer is never displayed, only read back on the CPU with the same formula\n// (see rectStats below). It doesn't need to match the blit pass's orientation.\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_gridSize;\n vec3 sum = vec3(0.0);\n const int N = 5;\n vec2 block = u_contentSize / u_gridSize;\n vec2 origin = uv * u_contentSize;\n for (int y = 0; y < N; y++) {\n for (int x = 0; x < N; x++) {\n vec2 p = origin + (vec2(float(x), float(y)) + 0.5) * (block / float(N));\n sum += texture(u_content, p / u_contentSize).rgb;\n }\n }\n fragColor = vec4(sum / float(N * N), 1.0);\n}\n`;\n\ntype PboSlot = {\n buffer: WebGLBuffer;\n sync: WebGLSync | null;\n pending: boolean;\n};\n\nexport type ProbeRect = { centerX: number; centerY: number; halfWidth: number; halfHeight: number };\n\nexport type Probe = {\n /** Renders a downsample of the current `contentTexture` into its own FBO and advances the PBO\n * read pipeline. Returns stats for the lens rectangle — lagging by a frame or two, or `null`\n * until a reader is ready. */\n sample(\n contentTexture: WebGLTexture,\n contentWidth: number,\n contentHeight: number,\n rect: ProbeRect,\n ): ProbeStats | null;\n /** Stats for another rectangle from the ALREADY-captured grid — no new pass, no new read. */\n statsFor(contentWidth: number, contentHeight: number, rect: ProbeRect): ProbeStats | null;\n destroy(): void;\n};\n\n/** RGBA32F gives the probe sub-pixel averaging precision (spec §5: \"where the web has the edge\")\n * — without it, the sum of 25 eight-bit samples per cell gets requantized back to 8 bits, and a\n * near-black gradient slice bands right at this step. Without `EXT_color_buffer_float` we fall\n * back to RGBA8 — renders everywhere, just coarser. */\nfunction pickDownsampleFormat(\n gl: WebGL2RenderingContext,\n): { internalFormat: number; type: number; floatPrecision: boolean } {\n const hasFloat = gl.getExtension('EXT_color_buffer_float') !== null;\n return hasFloat\n ? { internalFormat: gl.RGBA32F, type: gl.FLOAT, floatPrecision: true }\n : { internalFormat: gl.RGBA8, type: gl.UNSIGNED_BYTE, floatPrecision: false };\n}\n\nfunction luma(r: number, g: number, b: number): number {\n return 0.2126 * r + 0.7152 * g + 0.0722 * b;\n}\n\n/**\n * Rectangle stats from the 48×96 buffer. Buffer row `r` is `(r+0.5)/H` by the same formula as in\n * `DOWNSAMPLE_FRAGMENT_SOURCE`; here that same formula is inverted, so the buffer's orientation\n * (up/down) doesn't matter — only symmetry between reading and writing does.\n */\ntype DownsampleBuffer = Float32Array | Uint8Array;\n\nfunction rectStats(\n buffer: DownsampleBuffer,\n floatPrecision: boolean,\n gridW: number,\n gridH: number,\n contentWidth: number,\n contentHeight: number,\n rect: ProbeRect,\n): ProbeStats | null {\n const x0 = rect.centerX - rect.halfWidth;\n const x1 = rect.centerX + rect.halfWidth;\n const y0 = rect.centerY - rect.halfHeight;\n const y1 = rect.centerY + rect.halfHeight;\n\n const lumas: number[] = [];\n const nx: number[] = [];\n const ny: number[] = [];\n let sr = 0;\n let sg = 0;\n let sb = 0;\n\n const norm = floatPrecision ? 1 : 255;\n for (let row = 0; row < gridH; row++) {\n const cy = ((row + 0.5) / gridH) * contentHeight;\n if (cy < y0 || cy > y1) continue;\n for (let col = 0; col < gridW; col++) {\n const cx = ((col + 0.5) / gridW) * contentWidth;\n if (cx < x0 || cx > x1) continue;\n const i = (row * gridW + col) * 4;\n const r = buffer[i] / norm;\n const g = buffer[i + 1] / norm;\n const b = buffer[i + 2] / norm;\n lumas.push(luma(r, g, b));\n nx.push((cx - rect.centerX) / Math.max(rect.halfWidth, 1));\n ny.push((cy - rect.centerY) / Math.max(rect.halfHeight, 1));\n sr += r;\n sg += g;\n sb += b;\n }\n }\n const n = lumas.length;\n if (n === 0) return null;\n\n let mean = 0;\n for (const v of lumas) mean += v;\n mean /= n;\n\n let busy = 0;\n for (const v of lumas) busy += Math.abs(v - mean);\n busy = (busy / n) * 2;\n\n const sorted = [...lumas].sort((a, b) => a - b);\n const percentile = (p: number) => {\n const idx = p * (n - 1);\n const lo = Math.floor(idx);\n const hi = Math.ceil(idx);\n return sorted[lo] + (sorted[hi] - sorted[lo]) * (idx - lo);\n };\n\n const slope = (xs: number[]) => {\n let mx = 0;\n for (const v of xs) mx += v;\n mx /= n;\n let sxx = 0;\n let sxl = 0;\n for (let i = 0; i < n; i++) {\n const dx = xs[i] - mx;\n sxx += dx * dx;\n sxl += dx * (lumas[i] - mean);\n }\n return sxx > 1e-6 ? sxl / sxx : 0;\n };\n\n return {\n luma: mean,\n busy: Math.min(busy, 1),\n lo: percentile(0.1),\n hi: percentile(0.9),\n slopeX: slope(nx),\n slopeY: slope(ny),\n r: sr / n,\n g: sg / n,\n b: sb / n,\n };\n}\n\nexport function createProbe(gl: WebGL2RenderingContext, vertexSource: string): Probe {\n const program = createProgram(gl, vertexSource, DOWNSAMPLE_FRAGMENT_SOURCE);\n const format = pickDownsampleFormat(gl);\n\n const texture = gl.createTexture();\n if (!texture) throw new Error('vireglass/web/probe: gl.createTexture returned null');\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texImage2D(\n gl.TEXTURE_2D,\n 0,\n format.internalFormat,\n PROBE_GRID_WIDTH,\n PROBE_GRID_HEIGHT,\n 0,\n gl.RGBA,\n format.type,\n null,\n );\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.bindTexture(gl.TEXTURE_2D, null);\n const fbo = createFramebuffer(gl, texture);\n\n const byteSize = PROBE_GRID_WIDTH * PROBE_GRID_HEIGHT * 4 * (format.floatPrecision ? 4 : 1);\n const slots: PboSlot[] = [0, 1].map(() => {\n const buffer = gl.createBuffer();\n if (!buffer) throw new Error('vireglass/web/probe: gl.createBuffer returned null');\n gl.bindBuffer(gl.PIXEL_PACK_BUFFER, buffer);\n gl.bufferData(gl.PIXEL_PACK_BUFFER, byteSize, gl.STREAM_READ);\n gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);\n return { buffer, sync: null, pending: false };\n });\n let cursor = 0;\n let latestRaw: DownsampleBuffer | null = null;\n\n const contentLoc = gl.getUniformLocation(program, 'u_content');\n const contentSizeLoc = gl.getUniformLocation(program, 'u_contentSize');\n const gridSizeLoc = gl.getUniformLocation(program, 'u_gridSize');\n\n function pollReady(slot: PboSlot): void {\n if (!slot.pending || !slot.sync) return;\n // The flag flushes the command queue, otherwise a fence placed after readPixels might never\n // fire at all. The wait itself is zero. WAIT_FAILED isn't \"ready\": without a separate branch\n // for it, execution fell through and read garbage out of the PBO.\n const status = gl.clientWaitSync(slot.sync, gl.SYNC_FLUSH_COMMANDS_BIT, 0);\n if (status === gl.TIMEOUT_EXPIRED || status === gl.WAIT_FAILED) return;\n gl.deleteSync(slot.sync);\n slot.sync = null;\n slot.pending = false;\n gl.bindBuffer(gl.PIXEL_PACK_BUFFER, slot.buffer);\n const out: DownsampleBuffer = format.floatPrecision\n ? new Float32Array(PROBE_GRID_WIDTH * PROBE_GRID_HEIGHT * 4)\n : new Uint8Array(PROBE_GRID_WIDTH * PROBE_GRID_HEIGHT * 4);\n gl.getBufferSubData(gl.PIXEL_PACK_BUFFER, 0, out);\n gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);\n latestRaw = out;\n }\n\n function sample(\n contentTexture: WebGLTexture,\n contentWidth: number,\n contentHeight: number,\n rect: ProbeRect,\n ): ProbeStats | null {\n // Collect finished reads from PAST frames before requesting a new one.\n for (const slot of slots) pollReady(slot);\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);\n gl.viewport(0, 0, PROBE_GRID_WIDTH, PROBE_GRID_HEIGHT);\n gl.useProgram(program);\n bindTextureAt(gl, 0, contentTexture, program, 'u_content');\n gl.uniform1i(contentLoc, 0);\n gl.uniform2f(contentSizeLoc, contentWidth, contentHeight);\n gl.uniform2f(gridSizeLoc, PROBE_GRID_WIDTH, PROBE_GRID_HEIGHT);\n gl.disable(gl.BLEND);\n drawFullscreenTriangle(gl);\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n\n const slot = slots[cursor];\n cursor = (cursor + 1) % slots.length;\n if (!slot.pending) {\n gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo);\n gl.bindBuffer(gl.PIXEL_PACK_BUFFER, slot.buffer);\n gl.readPixels(0, 0, PROBE_GRID_WIDTH, PROBE_GRID_HEIGHT, gl.RGBA, format.type, 0);\n gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);\n gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);\n slot.sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);\n slot.pending = true;\n }\n\n return statsFor(contentWidth, contentHeight, rect);\n }\n\n // The downsample is one per frame, but a frame can have many elements: each needs stats for\n // ITS OWN rectangle out of the same grid, not its own pass and its own PBO read.\n function statsFor(contentWidth: number, contentHeight: number, rect: ProbeRect): ProbeStats | null {\n if (!latestRaw) return null;\n return rectStats(\n latestRaw,\n format.floatPrecision,\n PROBE_GRID_WIDTH,\n PROBE_GRID_HEIGHT,\n contentWidth,\n contentHeight,\n rect,\n );\n }\n\n function destroy(): void {\n for (const slot of slots) {\n if (slot.sync) gl.deleteSync(slot.sync);\n gl.deleteBuffer(slot.buffer);\n }\n gl.deleteFramebuffer(fbo);\n gl.deleteTexture(texture);\n gl.deleteProgram(program);\n }\n\n return { sample, statsFor, destroy };\n}\n","// The web lab's WebGL2 pipeline (per the Phase 3 plan; web-core spec §5). Four passes, and the\n// ordering and layer split deliberately mirror Android — the lens and the surface are NOT\n// collapsed into one pass, otherwise the lab stops predicting the phone (there they're two\n// separate layers):\n//\n// 1. backdrop — fills the `content` texture one of two ways: `scene` rasterizes\n// into an offscreen 2D canvas (text and cover art are easier there\n// than in GL) and uploads it via `texSubImage2D`; `backdrop` draws\n// straight into `content` with its own GPU pass instead, no canvas,\n// no readback (see `VireGlassBackdropPass`). Either way the texture\n// plays the role of the spec's FBO(scene): later passes only SAMPLE\n// it, never redraw it.\n// 2. blit backdrop to screen — `content` is drawn as the backing; without it the screen would\n// show only the element, and the material is compared against\n// exactly what's around it.\n// 3. downsample (`probe.ts`) — stats for the lens rectangle, see there.\n// 4. lens (`toGLSL(LENS_SHADER)`) — samples `content`, draws over the backdrop.\n// 5. surface (`toGLSL(SURFACE_SHADER)`) — bevel and highlight over the lens.\n//\n// UNITS. Everything is device-px, one coordinate space for the whole canvas (web-core spec\n// §4.2): `toLensProps` already multiplies by `density`; `toSurfaceUniforms` doesn't and\n// shouldn't (its contract is shared with Android), so its result gets run through\n// `toDeviceSurfaceUniforms`.\n//\n// SHARED CENTER. On Android the lens and the surface live in THEIR OWN local spaces (different\n// `lensPadDp`/`surfacePadDp` margins), and concentricity is a convention the component\n// maintains. Here both shaders get screen coordinates directly (`gl_FragCoord`), so the only way\n// to align them is to pass the exact SAME `u_center` in canvas pixels to both: the lens gets it\n// as-is (a native uniform), the surface gets it substituted in place of the adapter's own value\n// (which is only meaningful in its own local canvas, which doesn't exist here).\nimport {\n DYNAMIC_UNIFORMS,\n ICON_UNIFORMS,\n OVERLAY_UNIFORMS,\n toLensProps,\n toDeviceSurfaceUniforms,\n toSurfaceUniforms,\n type VireGlassAccent,\n type VireGlassMorph,\n type VireGlassTouch,\n} from '../adapters';\nimport { lensPadDp, shadowOpacity, surfacePadDp, type VireGlassGeometry } from '../geometry';\nimport { LENS_SHADER } from '../lens-shader';\nimport type { VireGlassDebugMode, VireGlassOptics } from '../material';\nimport { SURFACE_SHADER } from '../surface-shader';\nimport { toGLSL } from '../targets/glsl';\nimport {\n bindTextureAt,\n createFramebuffer,\n createProgram,\n createTexture,\n drawFullscreenTriangle,\n FULLSCREEN_TRIANGLE_VERTEX_SOURCE,\n locationCache,\n setUniform,\n type UniformValue,\n} from './gl';\nimport { createGpuTimer } from './gpu-timer';\nimport { createProbe, type ProbeStats } from './probe';\n\n/** The scene draws itself with an offset: the lenses in the frame stay put while the canvas\n * underneath them slides — only this way can you actually see what refraction does to whatever\n * passes under the element. Offset in device-px. */\nexport type VireGlassSceneDrawer = (\n ctx: CanvasRenderingContext2D,\n width: number,\n height: number,\n offsetX: number,\n offsetY: number,\n /** Pixels per dp — the same value used for elements. The canvas and the glass must live at the\n * same scale: as a fraction of canvas width, a cell on desktop came out five times larger than\n * on a phone, and an element of the same size looked completely different on the lab bench\n * than on a device. */\n density: number,\n) => void;\n\n/** The target a `VireGlassBackdropPass` must finish its frame in — the FBO wraps `contentTexture`\n * itself, so drawing into it needs no readback: the very texture the lens samples gets the pixels\n * directly. */\nexport type VireGlassBackdropTarget = {\n /** Owned by the renderer — the FBO wrapping `contentTexture` itself, recreated on `resize` and\n * freed in `destroy`. The pass must not delete it, and must not hold onto it past the call:\n * the object may be gone or pointing at a different texture by the next `render()`. */\n framebuffer: WebGLFramebuffer;\n width: number;\n height: number;\n};\n\n/**\n * Alternative to `scene`: draws the backdrop with a GPU pass, in this same WebGL2 context, straight\n * into the texture the lens samples — no offscreen 2D canvas, no `texSubImage2D` readback. The\n * renderer doesn't know or care what's drawn; a fluid sim, a video frame, anything that ends up as\n * pixels.\n *\n * ORIENTATION CONTRACT. `contentTexture` carries the same layout the `scene` path has always\n * produced: uploaded from a 2D canvas without a Y flip, so texel row 0 (`v≈0`) is the scene's TOP\n * row (see `BLIT_FRAGMENT_SOURCE` below) — and that row sits at `gl_FragCoord.y = 0` in this FBO,\n * the window-coordinate BOTTOM, not the top. A pass that copies an ordinary top-row-first image\n * (one loaded the normal way, via `texImage2D` from a canvas or an `<img>`) straight across with\n * `texture(src, gl_FragCoord.xy / vec2(target.width, target.height))` reproduces it correctly,\n * with no flip of its own. A pass that instead treats INCREASING y as its own \"up\" — the natural\n * convention for a physical simulation — must flip before this final write, or its top ends up\n * stored as the scene's bottom and appears upside down once the lens samples it.\n *\n * The pass may render through as many of its own FBOs first as it likes (a multi-step simulation)\n * and leave the GL context in whatever state suits it; it must finish by drawing into\n * `target.framebuffer` at `viewport(0, 0, target.width, target.height)`. The renderer puts back\n * every piece of state its own remaining steps depend on right after — framebuffer, viewport,\n * blend, scissor, depth/stencil/cull, the vertex array binding, the active texture unit, and the\n * `UNPACK_*` pixel-store flags used to upload `iconMask`/`colorLayer` — so the pass is free to\n * change any of it without arranging its own cleanup.\n */\nexport type VireGlassBackdropPass = (\n gl: WebGL2RenderingContext,\n target: VireGlassBackdropTarget,\n) => void;\n\n/** One glass element in the frame. Each has its own material — that's how they're compared side by side. */\nexport type VireGlassPiece = {\n optics: VireGlassOptics;\n geometry: VireGlassGeometry;\n /** Element center in CANVAS PIXELS (device-px). Shared between the lens and the surface. */\n centerX: number;\n centerY: number;\n morph?: VireGlassMorph;\n /** Third shape of the same body: breaking the element into pieces uses two bridges. */\n morph2?: VireGlassMorph;\n /** Local finger response: touch point, drag, press, wave. */\n touch?: VireGlassTouch;\n /** Press response, 0…1 — the highlight blooms, the body thickens slightly. */\n press?: number;\n /** Element under the finger, 0…1 — a stronger highlight and rim light. */\n active?: number;\n /** Played fraction, 0…1: an active state expressed as a field. Omitted means no progress. */\n progress?: number;\n /** Whether to draw the icon from the frame's shared mask on this element (see `iconMask` in the frame options). */\n icon?: boolean;\n /** Whether to draw the frame's colored layer on this element (see `colorLayer`). */\n overlay?: boolean;\n /** Icon color at rest and in the active state, RGBA 0…1. */\n inkIdle?: readonly number[];\n inkActive?: readonly number[];\n /** Key light direction in screen space; defaults to the light at rest. */\n light?: readonly [number, number];\n /** 0…1: the element appearing as the lens builds up (M 2:55). */\n appear?: number;\n /** 0 — the element sinks in under the finger, 1 — it rises into glass (reference §5). */\n lift?: number;\n /** Tint for the primary action — colored glass, not a fill. */\n accent?: VireGlassAccent;\n};\n\nexport type VireGlassRenderOptions = {\n /** `devicePixelRatio` — passed as an argument rather than read globally, so the renderer stays\n * testable and isn't tied to a specific window. */\n density: number;\n debug: VireGlassDebugMode;\n pieces: readonly VireGlassPiece[];\n /** CPU-side backdrop: draws into an offscreen 2D canvas, uploaded via `texSubImage2D`. Exactly\n * one of `scene`/`backdrop` is required — `render()` throws if both or neither are given — see\n * `backdrop` for the GPU alternative. */\n scene?: VireGlassSceneDrawer;\n /** GPU-side backdrop: draws straight into the texture the lens samples, in this context, no\n * readback. Exactly one of `scene`/`backdrop` is required — see `VireGlassBackdropPass` for the\n * orientation contract it must honor. */\n backdrop?: VireGlassBackdropPass | null;\n /** Canvas offset under stationary elements, device-px. */\n offsetX?: number;\n offsetY?: number;\n /**\n * Ink mask the size of the canvas, screen coordinates, read from the GREEN channel. White on\n * BLACK: antialiasing has to live in color, not alpha, otherwise green holds at one all the way\n * to the edge inside a stroke and the icon's edges tear.\n *\n * The mask does NOT carry and must not carry a calm backing under the ink: that's a property of\n * the material itself (`legibility`), the same across the whole element. While the app used to\n * supply that backing, one element had it and another didn't, with nothing to explain the\n * difference.\n */\n iconMask?: TexImageSource | null;\n /**\n * App-colored layer the size of the canvas, screen coordinates: cover art, a thumbnail —\n * anything the app puts ON the glass in color. Separate from the ink mask, because that one\n * carries a single channel colored by polarity, while this one has its own color.\n *\n * Drawn INSIDE the material, at the same coordinate as the ink: only this way does the\n * surface's deformation carry them together. As a separate layer on top of the glass, text used\n * to shake on press while the cover art stayed put.\n */\n colorLayer?: TexImageSource | null;\n};\n\nexport type VireGlassRenderResult = {\n /** One backdrop sample per element, in the same order as `pieces`. */\n probes: readonly (ProbeStats | null)[];\n};\n\nexport type VireGlassRenderer = {\n resize(widthPx: number, heightPx: number): void;\n render(options: VireGlassRenderOptions): VireGlassRenderResult;\n /** GPU time of the last `render()` call, milliseconds, via\n * `EXT_disjoint_timer_query_webgl2`. `null` when the extension is unavailable or the sample\n * came back disjoint — never an error either way. */\n getLastGpuMs(): number | null;\n destroy(): void;\n};\n\nconst BLIT_FRAGMENT_SOURCE = `#version 300 es\nprecision highp float;\nout vec4 fragColor;\nuniform sampler2D u_content;\nuniform vec2 u_resolution;\nvoid main() {\n // The same Y flip the transpiler does for the lens/surface (targets/glsl.ts): content is\n // uploaded from the 2D canvas WITHOUT flipping, so its V=0 is the scene's top row. This keeps\n // the same order here, otherwise the backdrop and what the lens refracts through it drift\n // apart vertically.\n vec2 uv = gl_FragCoord.xy / u_resolution;\n uv.y = 1.0 - uv.y;\n fragColor = texture(u_content, uv);\n}\n`;\n\n/** The material arrives as ONE channel (web-core spec §4, \"Uniform contract\"): name/size/value\n * as three parallel arrays — exactly the layout that used to get lost with Android Props. */\nfunction applyChannel(\n gl: WebGL2RenderingContext,\n get: (name: string) => WebGLUniformLocation | null,\n names: readonly string[],\n sizes: readonly number[],\n values: readonly number[],\n): void {\n let cursor = 0;\n for (let i = 0; i < names.length; i += 1) {\n const size = sizes[i];\n setUniform(gl, get(names[i]), values.slice(cursor, cursor + size));\n cursor += size;\n }\n}\n\nfunction applyObject(\n gl: WebGL2RenderingContext,\n get: (name: string) => WebGLUniformLocation | null,\n values: Record<string, UniformValue>,\n): void {\n for (const name of Object.keys(values)) setUniform(gl, get(name), values[name]);\n}\n\nexport function createVireGlassRenderer(canvas: HTMLCanvasElement): VireGlassRenderer {\n const context = canvas.getContext('webgl2', {\n preserveDrawingBuffer: true,\n alpha: false,\n antialias: false,\n });\n if (!context) throw new Error('vireglass/web: WebGL2 unavailable');\n // Explicit non-nullable typing — otherwise the `!context` narrowing doesn't survive the\n // closures of the nested `function resize/render/destroy` below, and tsc goes back to treating\n // `gl` as `WebGL2RenderingContext | null`.\n const gl: WebGL2RenderingContext = context;\n\n const blitProgram = createProgram(gl, FULLSCREEN_TRIANGLE_VERTEX_SOURCE, BLIT_FRAGMENT_SOURCE);\n const lensProgram = createProgram(gl, FULLSCREEN_TRIANGLE_VERTEX_SOURCE, toGLSL(LENS_SHADER));\n const surfaceProgram = createProgram(\n gl,\n FULLSCREEN_TRIANGLE_VERTEX_SOURCE,\n toGLSL(SURFACE_SHADER),\n );\n const probe = createProbe(gl, FULLSCREEN_TRIANGLE_VERTEX_SOURCE);\n\n const blitLoc = locationCache(gl, blitProgram);\n const lensLoc = locationCache(gl, lensProgram);\n const surfaceLoc = locationCache(gl, surfaceProgram);\n\n let width = canvas.width;\n let height = canvas.height;\n\n let sceneCanvas = document.createElement('canvas');\n let sceneCtx = sceneCanvas.getContext('2d');\n if (!sceneCtx) throw new Error('vireglass/web: scene 2D context unavailable');\n\n // Smoothed backdrop estimate per element: keyed by its position in the pieces list.\n const settled = new Map<number, ProbeStats>();\n /** Fraction of the new sample per frame. Same magnitude as SETTLE on Android. */\n const SETTLE = 0.12;\n\n function settleStats(index: number, fresh: ProbeStats): ProbeStats {\n const prev = settled.get(index);\n if (!prev) {\n settled.set(index, fresh);\n return fresh;\n }\n const mix = (a: number, b: number) => a + (b - a) * SETTLE;\n const next: ProbeStats = {\n luma: mix(prev.luma, fresh.luma),\n busy: mix(prev.busy, fresh.busy),\n lo: mix(prev.lo, fresh.lo),\n hi: mix(prev.hi, fresh.hi),\n slopeX: mix(prev.slopeX, fresh.slopeX),\n slopeY: mix(prev.slopeY, fresh.slopeY),\n r: mix(prev.r, fresh.r),\n g: mix(prev.g, fresh.g),\n b: mix(prev.b, fresh.b),\n };\n settled.set(index, next);\n return next;\n }\n\n let contentTexture = createTexture(gl, {\n width: Math.max(width, 1),\n height: Math.max(height, 1),\n });\n\n // FBO for the `backdrop` path, wrapping `contentTexture` itself — lazy, so a `scene`-only\n // caller never pays for it. Stale after `resize` (it points at a texture that's about to be\n // deleted); dropped there and rebuilt against the new one on next use.\n let backdropFbo: WebGLFramebuffer | null = null;\n function ensureBackdropFbo(): WebGLFramebuffer {\n if (!backdropFbo) backdropFbo = createFramebuffer(gl, contentTexture);\n return backdropFbo;\n }\n\n // GPU timing for `getLastGpuMs()` — the ring buffer and disjoint handling live in\n // `gpu-timer.ts`, isolated so they can be unit-tested against a fake (this extension is\n // unavailable in the headless Chromium the shader gates run in).\n const gpuTimer = createGpuTimer(gl);\n\n // The icon mask is ONE per frame, the size of the canvas. The shader samples it at the pixel's\n // screen coordinate (`u_center + p` is exactly `xy`), so each element's icon is simply drawn\n // into the mask at its own spot: no separate texture per button is needed here.\n const iconTexture = createTexture(gl, { width: 1, height: 1 });\n const colorTexture = createTexture(gl, { width: 1, height: 1 });\n gl.bindTexture(gl.TEXTURE_2D, iconTexture);\n gl.texSubImage2D(\n gl.TEXTURE_2D,\n 0,\n 0,\n 0,\n 1,\n 1,\n gl.RGBA,\n gl.UNSIGNED_BYTE,\n new Uint8Array([0, 0, 0, 0]),\n );\n gl.bindTexture(gl.TEXTURE_2D, null);\n\n function resize(widthPx: number, heightPx: number): void {\n width = Math.max(1, Math.round(widthPx));\n height = Math.max(1, Math.round(heightPx));\n canvas.width = width;\n canvas.height = height;\n gl.viewport(0, 0, width, height);\n\n sceneCanvas = document.createElement('canvas');\n sceneCanvas.width = width;\n sceneCanvas.height = height;\n sceneCtx = sceneCanvas.getContext('2d');\n if (!sceneCtx) throw new Error('vireglass/web: scene 2D context unavailable');\n\n gl.deleteTexture(contentTexture);\n contentTexture = createTexture(gl, { width, height });\n\n if (backdropFbo) {\n gl.deleteFramebuffer(backdropFbo);\n backdropFbo = null;\n }\n }\n\n function render(options: VireGlassRenderOptions): VireGlassRenderResult {\n if (!sceneCtx) throw new Error('vireglass/web: renderer not initialized (resize was never called)');\n // Narrowed once, before the timed section: a `let` read from inside the closure below would\n // widen back to `| null`, since TS cannot see that nothing reassigns it in between.\n const ctx = sceneCtx;\n\n // Validated BEFORE the GPU timer starts, not inside it: a bad call here has to throw cleanly\n // with no query left open, which an error thrown mid-timing would risk.\n if (options.backdrop && options.scene) {\n throw new Error(\n 'vireglass/web: render() needs exactly one of options.scene or options.backdrop, not both',\n );\n }\n if (!options.backdrop && !options.scene) {\n throw new Error('vireglass/web: render() needs either options.scene or options.backdrop');\n }\n\n // The rest of the frame, including a caller-supplied `backdrop` pass that may itself throw\n // (a shader typo, a bad uniform), runs inside `time`'s own try/finally — `endQuery` always\n // pairs with `beginQuery` no matter how this exits.\n return gpuTimer.time((): VireGlassRenderResult => {\n // 1. Backdrop. Exactly one of two paths fills `contentTexture` — everything after this only\n // SAMPLES it, never redraws it.\n if (options.backdrop) {\n // GPU pass, straight into `contentTexture` via its own FBO: no 2D canvas, no readback. See\n // `VireGlassBackdropPass` for the orientation contract the pass has to honor.\n const fbo = ensureBackdropFbo();\n gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);\n gl.viewport(0, 0, width, height);\n options.backdrop(gl, { framebuffer: fbo, width, height });\n // The pass is free to leave the context in ANY state (per its own JSDoc) — put back\n // everything the rest of this render depends on, not just the obvious framebuffer/\n // viewport pair: a pass that enables depth/stencil/face culling, binds its own VAO, or\n // changes the unpack flags used to upload `iconMask`/`colorLayer` just below would\n // otherwise corrupt a step it never touched directly.\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n gl.viewport(0, 0, width, height);\n gl.disable(gl.BLEND);\n gl.disable(gl.SCISSOR_TEST);\n gl.disable(gl.DEPTH_TEST);\n gl.disable(gl.CULL_FACE);\n gl.disable(gl.STENCIL_TEST);\n gl.colorMask(true, true, true, true);\n gl.depthMask(true);\n gl.bindVertexArray(null);\n gl.activeTexture(gl.TEXTURE0);\n gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);\n gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);\n gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.BROWSER_DEFAULT_WEBGL);\n } else {\n // CPU-side: a 2D canvas is easier for text and \"cover art\" (task, item 3), and the texture\n // itself plays the role of the plan's FBO(scene).\n ctx.clearRect(0, 0, width, height);\n options.scene?.(ctx, width, height, options.offsetX ?? 0, options.offsetY ?? 0, options.density);\n gl.bindTexture(gl.TEXTURE_2D, contentTexture);\n gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, sceneCanvas);\n gl.bindTexture(gl.TEXTURE_2D, null);\n }\n\n if (options.colorLayer) {\n gl.bindTexture(gl.TEXTURE_2D, colorTexture);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, options.colorLayer);\n }\n if (options.iconMask) {\n gl.bindTexture(gl.TEXTURE_2D, iconTexture);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, options.iconMask);\n gl.bindTexture(gl.TEXTURE_2D, null);\n }\n\n // 2. Backdrop to screen — the backing the element is seen against.\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n gl.viewport(0, 0, width, height);\n gl.disable(gl.BLEND);\n gl.disable(gl.SCISSOR_TEST);\n gl.useProgram(blitProgram);\n bindTextureAt(gl, 0, contentTexture, blitProgram, 'u_content');\n setUniform(gl, blitLoc('u_resolution'), [width, height]);\n drawFullscreenTriangle(gl);\n\n const probes: (ProbeStats | null)[] = [];\n\n for (let index = 0; index < options.pieces.length; index += 1) {\n const piece = options.pieces[index];\n\n // 3. Downsample + stats for the rectangle of VISIBLE glass (no lens/surface margins — the\n // same `glassWidth`/`glassHeight` that go into toLensProps). Only the first element\n // captures the grid: it's shared for the frame, the rest just need their own rectangle out\n // of it.\n const halfWidth = (piece.geometry.width * options.density) / 2;\n const halfHeight = (piece.geometry.height * options.density) / 2;\n const rect = {\n centerX: piece.centerX,\n centerY: piece.centerY,\n halfWidth,\n halfHeight,\n };\n const fresh =\n index === 0\n ? probe.sample(contentTexture, width, height, rect)\n : probe.statsFor(width, height, rect);\n // The backdrop estimate eases toward the new value instead of jumping to it. On Android\n // the native view does this (`GlassLensView.kt`, a per-frame SETTLE); nobody did it on the\n // web: under a moving canvas, body density used to jerk with the instantaneous sample, and\n // on screen that read as lighting artifacts.\n const stats = fresh ? settleStats(index, fresh) : null;\n probes.push(stats);\n // Restore the viewport and the content texture binding — the probe pass reuses TEXTURE0\n // and its own FBO, unbinds them itself, but does change the viewport.\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n gl.viewport(0, 0, width, height);\n\n gl.enable(gl.BLEND);\n // Premultiplied-over: both the lens and the surface emit premultiplied alpha (see `return\n // half4(half3(...) * alpha, alpha)` in both shaders).\n gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);\n\n // Both passes draw a fullscreen triangle, but the element covers only a small part of the\n // frame: without scissoring, a row of samples multiplies cost by the number of elements (on\n // a headless software renderer this pushed a frame into seconds). Margin is added on both\n // sides — the shadow, bevel and light gathering all reach past the element's own bounds.\n // The morph drop travels past the element's bounds — its travel is part of the margin,\n // otherwise scissoring would cut off the tail right where it matters.\n const reachOf = (m?: VireGlassMorph) =>\n m ? Math.hypot(m.offsetX, m.offsetY) + Math.max(m.width, m.height) / 2 + m.smoothing : 0;\n const morphReach = Math.max(reachOf(piece.morph), reachOf(piece.morph2));\n // Field deformation pushes the element's edge past its own bounds — drag, wave amplitude\n // and growth on press. Without this term, scissoring cuts off exactly the part being\n // dragged toward.\n const touchReach = piece.touch\n ? Math.hypot(piece.touch.pullX, piece.touch.pullY) +\n piece.touch.waveAmp * 2 +\n Math.max(piece.geometry.width, piece.geometry.height) * 0.05 * piece.touch.press\n : 0;\n const padPx =\n (lensPadDp(piece.geometry, piece.optics) + surfacePadDp(piece.geometry, 0) + morphReach + touchReach) *\n options.density;\n const left = Math.max(0, Math.floor(piece.centerX - halfWidth - padPx));\n const right = Math.min(width, Math.ceil(piece.centerX + halfWidth + padPx));\n const top = Math.max(0, Math.floor(height - (piece.centerY + halfHeight + padPx)));\n const bottom = Math.min(height, Math.ceil(height - (piece.centerY - halfHeight - padPx)));\n gl.enable(gl.SCISSOR_TEST);\n gl.scissor(left, top, Math.max(0, right - left), Math.max(0, bottom - top));\n\n // 4. Lens.\n gl.useProgram(lensProgram);\n bindTextureAt(gl, 0, contentTexture, lensProgram, 'content');\n setUniform(gl, lensLoc('u_contentSize'), [width, height]);\n setUniform(gl, lensLoc('u_resolution'), [width, height]);\n setUniform(gl, lensLoc('u_center'), [piece.centerX, piece.centerY]);\n // There's no local-view margin here (one shared coordinate space) — on Android `u_reach`\n // capped adaptive-blur gathering at the edge of the lens view's PADDING; there's no such\n // boundary here, so the value is chosen deliberately larger than any real gather radius, so\n // the `reach - length(p)` condition never triggers before `u_contentMin/Max` does.\n setUniform(gl, lensLoc('u_reach'), Math.max(width, height));\n setUniform(gl, lensLoc('u_contentMin'), [1, 1]);\n setUniform(gl, lensLoc('u_contentMax'), [width - 1, height - 1]);\n if (stats) {\n setUniform(gl, lensLoc('u_probeLuma'), stats.luma);\n setUniform(gl, lensLoc('u_probeBusy'), stats.busy);\n setUniform(gl, lensLoc('u_probeRange'), [stats.lo, stats.hi]);\n setUniform(gl, lensLoc('u_probeSlope'), [stats.slopeX, stats.slopeY]);\n setUniform(gl, lensLoc('u_probe'), [stats.r, stats.g, stats.b]);\n } else {\n // The probe hasn't reported yet (the first frame or two) — the -1 sentinel keeps the\n // shader on its own point samples (see the comment in `lens-shader.ts`).\n setUniform(gl, lensLoc('u_probeLuma'), -1);\n }\n const lens = toLensProps(piece.optics, piece.geometry, options.density, {\n debug: options.debug,\n morph: piece.morph,\n morph2: piece.morph2,\n touch: piece.touch,\n progress: piece.progress,\n light: piece.light,\n appear: piece.appear,\n accent: piece.accent,\n });\n applyChannel(gl, lensLoc, lens.uniformNames, lens.uniformSizes, lens.uniformValues);\n drawFullscreenTriangle(gl);\n\n // 5. Surface — shares its center with the lens (see the comment at the top of this file).\n // `toSurfaceUniforms`'s contract is dp, shared with Android; the core converts it to pixels.\n gl.useProgram(surfaceProgram);\n const rawSurface = toSurfaceUniforms(piece.optics, piece.geometry, {\n debug: options.debug,\n morph: piece.morph,\n morph2: piece.morph2,\n bodyInLens: true,\n touch: piece.touch,\n progress: piece.progress,\n // The shadow is denser over text, weaker over a flat backdrop (M 11:47). The rule lives\n // in the core: while it lived here, Android didn't have it at all.\n shadow: stats ? shadowOpacity(stats.busy) : 1,\n // The probe has already computed the ambient color — the shadow gets it for free. Taken\n // as-is: `settleStats` has already smoothed it, and rounding would bring back exactly the\n // steps the smoothing was written to remove.\n ambient: stats ? ([stats.r, stats.g, stats.b] as const) : undefined,\n appear: piece.appear,\n lift: piece.lift,\n });\n // The dp → device pixel conversion lives in the core (`toDeviceSurfaceUniforms`): one list\n // of lengths for every field, so a new field can't be forgotten here by oversight.\n applyObject(gl, surfaceLoc, {\n ...toDeviceSurfaceUniforms(rawSurface, options.density),\n u_center: [piece.centerX, piece.centerY],\n });\n // What's worklet dynamics on Android (press, active, tilt) arrives here as fields on the element.\n setUniform(gl, surfaceLoc(DYNAMIC_UNIFORMS[0]), piece.press ?? 0);\n setUniform(gl, surfaceLoc(DYNAMIC_UNIFORMS[1]), piece.active ?? 0);\n const hasIcon = Boolean(piece.icon && options.iconMask);\n setUniform(gl, surfaceLoc(ICON_UNIFORMS[0]), hasIcon ? 1 : 0);\n // Scale 1: the coordinate is already in screen space, the sampler itself normalizes by size.\n setUniform(gl, surfaceLoc(ICON_UNIFORMS[1]), 1);\n setUniform(gl, surfaceLoc(ICON_UNIFORMS[2]), piece.inkIdle ?? [1, 1, 1, 1]);\n setUniform(gl, surfaceLoc(ICON_UNIFORMS[3]), piece.inkActive ?? [1, 1, 1, 1]);\n bindTextureAt(gl, 1, iconTexture, surfaceProgram, 'u_icon');\n setUniform(gl, surfaceLoc('u_iconSize'), hasIcon ? [width, height] : [1, 1]);\n const hasColor = Boolean(piece.overlay && options.colorLayer);\n setUniform(gl, surfaceLoc(OVERLAY_UNIFORMS[0]), hasColor ? 1 : 0);\n bindTextureAt(gl, 2, colorTexture, surfaceProgram, 'u_overlay');\n setUniform(gl, surfaceLoc('u_overlaySize'), hasColor ? [width, height] : [1, 1]);\n setUniform(gl, surfaceLoc('u_resolution'), [width, height]);\n drawFullscreenTriangle(gl);\n }\n gl.disable(gl.SCISSOR_TEST);\n\n return { probes };\n });\n }\n\n function destroy(): void {\n probe.destroy();\n gl.deleteProgram(blitProgram);\n gl.deleteProgram(lensProgram);\n gl.deleteProgram(surfaceProgram);\n gl.deleteTexture(contentTexture);\n gl.deleteTexture(iconTexture);\n gl.deleteTexture(colorTexture);\n if (backdropFbo) gl.deleteFramebuffer(backdropFbo);\n gpuTimer.destroy();\n }\n\n return { resize, render, getLastGpuMs: gpuTimer.getLastMs, destroy };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/law.ts","../../src/geometry.ts","../../src/sdf.ts","../../src/lens-shader.ts","../../src/material.ts","../../src/adapters.ts","../../src/surface-shader.ts","../../src/targets/glsl.ts","../../src/web/gl.ts","../../src/web/gpu-timer.ts","../../src/web/probe.ts","../../src/web/renderer.ts"],"names":["slot"],"mappings":"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","file":"index.cjs","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 /**\n * How strongly the line is drawn, by the lightness around the element. The rim is a reflection\n * of the surroundings, so it is faint over a dark one and plain over a bright one, not white at\n * full strength everywhere (§2, §6).\n *\n * MEASURED off the controls of A 2:17 (over dark video), M 2:52 (over sky) and A 2:18 (over\n * grass), as the brightest rim bin's OKLab lightness over the surround's: 0.24 to 0.27 over dark,\n * 0.25 over sky, 0.37 over grass. Through the DOM path onto the same backdrops these land within\n * 0.08 of the smooth rows (docs/dom.md); a line white at full strength reads 0.70 to 0.80 over the\n * dark frames.\n */\n strengthDark: 0.37,\n strengthLight: 0.95,\n /**\n * The surround lightness between which the strength moves from one to the other. UNMEASURED: the\n * two ends are, and the controls in between were not looked for.\n */\n ambientFrom: 0.15,\n ambientTo: 0.45,\n /**\n * The share of the line's width, from the silhouette inward, that the light line leaves to the\n * dark edge. UNMEASURED: without it the line covers the dark edge and an element over a bright\n * flat backdrop loses the one mark that holds it there.\n */\n lineInset: 0.3,\n /**\n * The share of the peak that the line keeps along the whole contour, away from the key light.\n * MEASURED: the reference line goes all the way round. Its dimmest bin sits 0.06 to 0.12 of\n * lightness over the surround on the dark frames, against a peak of 0.24 to 0.27 (A 2:17).\n */\n contour: 0.3,\n /** How far the line's colour is lifted from the surroundings' toward white. UNMEASURED: the\n * lens always had 0.75 here, and this is that figure with a name. */\n whiteShare: 0.75,\n /**\n * The reflectance (`fresnelStrength`) at which the line is drawn at the strengths above: that\n * of ordinary glass, index 1.5. A denser medium does not draw it brighter than they say; a\n * thinner one draws it fainter. UNMEASURED.\n */\n reflectanceRef: 0.7,\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 /**\n * The spread under the element at which the ink's legibility requirement starts to bind, and the\n * spread where it binds fully. Over a flat backdrop polarity flips before the ink drowns, so\n * the body does not darken under a light glyph there; over texture it does. UNMEASURED: the\n * reference shows a lighter body under a white glyph over sky, nothing about texture.\n */\n inkBusyFrom: 0.02,\n inkBusyTo: 0.12,\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 /**\n * The density at which the body is wholly the ink's own pole. Between `presenceDemand` and this\n * the body moves from the light a thin body carries to the pole: a density no presence floor\n * reaches is one the ink's legibility, or a system setting, has asked for. UNMEASURED.\n */\n inkPoleTo: 0.8,\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","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","// The single description of VireGlass geometry. Both shaders draw it from here: the SKSL surface\n// (`surface-shader.ts`) and the AGSL lens (`lens-shader.ts`, shipped as a string into the native\n// view). AGSL and SKSL are the same language, so the SDF no longer needs duplicating in Kotlin:\n// before, the surface's circle and the lens's rounded rectangle described different pieces of\n// glass, and the bevel constant had to be kept in sync by hand (docs/adr-001-rendering.md §3).\nimport { BEVEL, EPSILON, TOUCH } from './law';\n\n/** 2π. Structural, not a law — but the shader has no built-in for it. */\nconst VG_TAU = 6.28318530718;\n\nexport const VG_SDF = `\nfloat vgRoundRect(float2 p, float2 halfSize, float corner) {\n float2 q = abs(p) - halfSize + corner;\n return min(max(q.x, q.y), 0.0) + length(max(q, float2(0.0))) - corner;\n}\n\nfloat vgSmin(float a, float b, float k) {\n float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);\n return mix(b, a, h) - k * h * (1.0 - h);\n}\n\n// A scene is a single shape, and when k > 0 a smooth union with one or two neighbors. A third\n// shape exists to break the control into pieces (docs/reference.md §5): there are two bridges\n// there. It's turned off with a zero size, not its own k: the fused body shares one bridge.\nfloat vgScene(float2 p, float2 halfSize, float corner,\n float2 offsetB, float2 halfB, float cornerB, float k,\n float2 offsetC, float2 halfC, float cornerC) {\n float a = vgRoundRect(p, halfSize, corner);\n if (k <= 0.0) { return a; }\n float ab = vgSmin(a, vgRoundRect(p - offsetB, halfB, cornerB), k);\n if (halfC.x <= 0.0) { return ab; }\n return vgSmin(ab, vgRoundRect(p - offsetC, halfC, cornerC), k);\n}\n\n// Response to a finger. It's the FIELD around the touch point that deforms, not the shape's\n// bounding box: scaling width to pull the right edge would also pull the left one — liquid\n// doesn't behave that way. Here the displacement falls off with distance from the finger, so the\n// far edge stays put.\n//\n// The order of the terms matters: drag shifts the field, press pulls it toward the finger, the\n// wave rides on top of the already-shifted field — otherwise the ripple decouples from the\n// deformation and lives a life of its own.\nfloat2 vgTouchWarp(float2 p, float2 touch, float2 pull, float press, float radius,\n float waveAmp, float wavePhase) {\n if (radius <= 0.0) { return p; }\n // Under the finger the whole element grows (M 3:51; HIG: interactive \"expands\").\n p /= 1.0 + ${TOUCH.pressGrow} * press;\n float2 d = p - touch;\n float r = length(d);\n\n // The finger is a BLOB, not a point, but not a hard stamp either: a wide peak, soft edges. A\n // flat plateau with a sharp cutoff at the edge shifts the region as a block, and a straight\n // line under the glass breaks into a step — the deformation reads as rectangular even though\n // the finger is round. A double smoothstep gives the same contact width without that step.\n //\n // The influence cuts off at the radius rather than fading into an infinite tail like a\n // Gaussian: with a tail the deformation spreads into a \"rubber mattress\" bulge.\n float k = clamp((radius - r) / radius, 0.0, 1.0);\n float s = k * k * (3.0 - 2.0 * k);\n float core = s * s * (3.0 - 2.0 * s);\n // A ring around the blob: material displaced from under the finger has to end up somewhere.\n // Without this ridge the shape just balloons, and a dense medium doesn't behave that way.\n float rim = k * k * (1.0 - k) * 4.0;\n\n float2 q = p - pull * (core - ${TOUCH.ridge} * rim);\n q -= d * (press * ${TOUCH.pressPull} * core);\n\n // The wave has its own scale, twice the drag radius: the ripple has to reach the far edge,\n // otherwise it reads as jitter under the finger rather than a wave across the surface. The\n // wavelength is short: in a dense medium ripples are frequent and small, long shallow swells\n // are water.\n if (waveAmp > 0.0 && r > 0.0001) {\n float span = radius * ${TOUCH.waveSpan}.0;\n float ring = sin(r / (span * ${TOUCH.waveLength}) - wavePhase * ${VG_TAU}) * exp(-r / (span * ${TOUCH.waveDecay}));\n q -= (d / r) * ring * waveAmp;\n }\n return q;\n}\n\n// Analytical normal of a single shape: radial on the rounded part, axis-aligned on the straight\n// segments.\nfloat2 vgRoundRectNormal(float2 p, float2 halfSize, float corner) {\n float2 q = abs(p) - halfSize + corner;\n float2 g = (q.x > 0.0 && q.y > 0.0)\n ? normalize(max(q, float2(0.0001)))\n : (q.x > q.y ? float2(1.0, 0.0) : float2(0.0, 1.0));\n return g * sign(p);\n}\n\n// Normal of the union — ANALYTICAL, no more finite differences here.\n//\n// Derivation: smin has two terms, mix(b,a,h) and −k·h·(1−h). Their derivatives with respect to h\n// contain a factor of (1−2h) with opposite signs and cancel exactly, because h is linear in\n// (b−a)/k. What's left is mix(∇b, ∇a, h) — that is, the two shapes' normals, blended with the\n// same weight that blends the distances themselves.\n//\n// The previous version took four ADDITIONAL scene evaluations per pixel (each one — two shapes\n// plus smin) and was still an approximation. Here there are two shapes, exactly.\nfloat2 vgSceneNormal(float2 p, float2 halfSize, float corner,\n float2 offsetB, float2 halfB, float cornerB, float k,\n float2 offsetC, float2 halfC, float cornerC) {\n float2 na = vgRoundRectNormal(p, halfSize, corner);\n if (k <= 0.0) { return na; }\n float2 q = p - offsetB;\n float a = vgRoundRect(p, halfSize, corner);\n float b = vgRoundRect(q, halfB, cornerB);\n float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);\n float2 nab = normalize(mix(vgRoundRectNormal(q, halfB, cornerB), na, h) + float2(${EPSILON}, ${EPSILON}));\n if (halfC.x <= 0.0) { return nab; }\n // Same derivation as for the pair: normals blend with the same weight as the distances.\n float2 qc = p - offsetC;\n float ab = vgSmin(a, b, k);\n float c = vgRoundRect(qc, halfC, cornerC);\n float hc = clamp(0.5 + 0.5 * (c - ab) / k, 0.0, 1.0);\n return normalize(mix(vgRoundRectNormal(qc, halfC, cornerC), nab, hc) + float2(${EPSILON}, ${EPSILON}));\n}\n\n// Position within the bevel: 0 is the flat middle, 1 is the very edge. This one value feeds the\n// mask, thickness, refraction, aberrations and the width of the light rim.\nfloat vgBevelT(float sd, float bevel) {\n return clamp((sd + bevel) / bevel, 0.0, 1.0);\n}\n\n// Slope of the bevel profile — spherical: t / sqrt(1 - t²), like a spherical cap. The previous t²\n// kept the slope near zero through almost the whole bevel and then shot up right at the edge, so\n// all the optics bunched into a narrow ring and the element read as a PUCK — a flat top and a\n// wall around the rim. Here curvature is spread across the bevel, and the rim stops being a line.\n//\n// This still doesn't control the field of view: only the bevel bends, the middle stays flat,\n// otherwise the surface reads as a soap bubble.\nfloat vgBevelSlope(float t) {\n return min(t * inversesqrt(max(1.0 - t * t * ${BEVEL.sphere}, ${BEVEL.floor})), ${BEVEL.slopeMax});\n}\n\n// PROGRESS FRACTION. Progress is an active state turned into a FIELD: to the left of the boundary\n// the element is active, to the right it stays regular glass, and the boundary moves. A negative\n// value turns the field off entirely — zero is taken by the start of the track and can't also\n// mean off.\n//\n// The boundary is SOFT. A hard seam reads as two different materials glued together rather than\n// two states of one; the transition width is a fraction of the half-width, so it doesn't read as\n// a thread on a large element and doesn't swallow a small one whole.\nfloat vgProgress(float2 p, float2 halfSize, float progress) {\n if (progress < 0.0) { return 0.0; }\n float edge = mix(-halfSize.x, halfSize.x, clamp(progress, 0.0, 1.0));\n float soft = max(halfSize.x * 0.05, 1.0);\n return 1.0 - smoothstep(edge - soft, edge + soft, p.x);\n}\n`;\n\n/** Rate at which the offset builds toward the rim. Shared between the lens (actual sampling\n * offset) and the surface (visualizing the displacement field in debug mode) — otherwise they'd\n * drift apart. */\nexport const VG_FALLOFF = 2.6;\n\n// JS twin of `vgRoundRect`/`vgRoundRectNormal` above, for targets with no GPU to run the shader on\n// (the DOM displacement map). Term-for-term against the GLSL text: a second geometry that behaves\n// differently is exactly the class of bug `check:optics` exists to catch on the shader side, and\n// there is no such gate for this one.\n\n/** Signed distance from `(x, y)` to a rounded rect of size `w`×`h` centered on the origin. */\nexport function sdfRoundedRect(x: number, y: number, w: number, h: number, r: number): number {\n const qx = Math.abs(x) - w / 2 + r;\n const qy = Math.abs(y) - h / 2 + r;\n return Math.min(Math.max(qx, qy), 0) + Math.hypot(Math.max(qx, 0), Math.max(qy, 0)) - r;\n}\n\n/**\n * Closed-form gradient of `sdfRoundedRect`, unit length everywhere except the exact center (where\n * it's genuinely undefined — the medial axis of the shape). NOT finite differences: differencing\n * quantizes the gradient to the sampling step, and the displacement map needs sub-pixel accuracy\n * right at the rim.\n */\nexport function sdfRoundedRectGradient(x: number, y: number, w: number, h: number, r: number): [number, number] {\n const qx = Math.abs(x) - w / 2 + r;\n const qy = Math.abs(y) - h / 2 + r;\n let gx: number;\n let gy: number;\n if (qx > 0 && qy > 0) {\n const cx = Math.max(qx, 0.0001);\n const cy = Math.max(qy, 0.0001);\n const len = Math.hypot(cx, cy);\n gx = cx / len;\n gy = cy / len;\n } else if (qx > qy) {\n gx = 1;\n gy = 0;\n } else {\n gx = 0;\n gy = 1;\n }\n return [gx * Math.sign(x), gy * Math.sign(y)];\n}\n\n// ---------------------------------------------------------------------------\n// JS twins of the shader functions above. The DOM renderer needs this geometry\n// as numbers, not as a string it hands to a compiler. Each mirrors its `vg*`\n// counterpart term for term — a second geometry that drifts is the failure the\n// platform-parity gate exists to catch.\n// ---------------------------------------------------------------------------\n\n/** Mirrors `vgSmin`. */\nexport function smin(a: number, b: number, k: number): number {\n const h = Math.min(Math.max(0.5 + (0.5 * (b - a)) / k, 0), 1);\n return b + (a - b) * h - k * h * (1 - h);\n}\n\n/**\n * The smallest `k` at which `smin` joins two surfaces `gap` apart into one body. Exact for this\n * smin rather than fitted: at the midpoint the two distances are both gap/2, so h = 0.5 and the\n * result is gap/2 − k/4, which reaches zero at k = 2·gap.\n *\n * It is the number `morphBetween` was missing. A neck sized only as a fraction of the element's\n * own half-size has nothing to do with the distance it has to span, so two controls a hundred\n * pixels apart stayed two controls at every t — including t = 1, where the contract says they are\n * one shared medium.\n */\nexport function neckToBridge(gap: number): number {\n return gap > 0 ? gap * 2 : 0;\n}\n\nexport type MorphShape = {\n offsetX: number;\n offsetY: number;\n width: number;\n height: number;\n cornerRadius: number;\n};\n\n/**\n * Mirrors the scene distance: one shape, smoothly unioned with any number of neighbours when\n * `smoothing` is positive. §5's merging and splitting are this union opening and closing.\n *\n * A fold, not a pair. The count used to be in the ARGUMENT NAMES — `b` and `c`, and a fourth\n * shape would have been `d` — which is not a limit the material has. Several independent elements\n * flowing into one whole is the behaviour; how many of them there happen to be is the host's.\n */\nexport function sceneDistance(\n x: number,\n y: number,\n w: number,\n h: number,\n r: number,\n smoothing = 0,\n ...shapes: (MorphShape | undefined)[]\n): number {\n let d = sdfRoundedRect(x, y, w, h, r);\n if (smoothing <= 0) return d;\n for (const s of shapes) {\n if (!s || s.width <= 0) continue;\n d = smin(d, sdfRoundedRect(x - s.offsetX, y - s.offsetY, s.width, s.height, s.cornerRadius), smoothing);\n }\n return d;\n}\n\n/**\n * Mirrors `vgSceneNormal`: normals blend with the same weight that blends the distances, which the\n * shader derives rather than approximates. The same fold as `sceneDistance`, carrying the running\n * distance alongside the running normal because each step's weight is measured against it.\n */\nexport function sceneGradient(\n x: number,\n y: number,\n w: number,\n h: number,\n r: number,\n smoothing = 0,\n ...shapes: (MorphShape | undefined)[]\n): [number, number] {\n let d = sdfRoundedRect(x, y, w, h, r);\n let [gx, gy] = sdfRoundedRectGradient(x, y, w, h, r);\n let blended = false;\n if (smoothing > 0) {\n for (const s of shapes) {\n if (!s || s.width <= 0) continue;\n const sx = x - s.offsetX;\n const sy = y - s.offsetY;\n const ds = sdfRoundedRect(sx, sy, s.width, s.height, s.cornerRadius);\n const weight = Math.min(Math.max(0.5 + (0.5 * (ds - d)) / smoothing, 0), 1);\n const [nx, ny] = sdfRoundedRectGradient(sx, sy, s.width, s.height, s.cornerRadius);\n gx = nx + (gx - nx) * weight;\n gy = ny + (gy - ny) * weight;\n d = smin(d, ds, smoothing);\n blended = true;\n }\n }\n // With nothing blended in, the base gradient is already a unit vector and is returned untouched —\n // the shader takes the same early exit (`if (k <= 0.0) return na;`), and nudging it here instead\n // would put the two renderers a rounding step apart on every flat edge in the material.\n if (!blended) return [gx, gy];\n // The shader's `+ float2(${EPSILON}, ${EPSILON})` before normalising, and it is not decoration:\n // where two shapes meet head-on their normals cancel exactly and the blend lands on zero.\n gx += EPSILON;\n gy += EPSILON;\n const len = Math.hypot(gx, gy) || 1;\n return [gx / len, gy / len];\n}\n\nexport type TouchWarp = {\n x: number;\n y: number;\n pullX: number;\n pullY: number;\n press: number;\n radius: number;\n waveAmp: number;\n wavePhase: number;\n};\n\n/**\n * Mirrors `vgTouchWarp`. The FIELD around the finger deforms, not the bounding box: scaling the\n * width to pull the right edge would pull the left one too, and liquid does not do that.\n *\n * Term order is load-bearing — drag shifts the field, press pulls it toward the finger, the wave\n * rides on the already-shifted field. Reordered, the ripple decouples and lives its own life.\n */\nexport function touchWarp(x: number, y: number, t: TouchWarp): [number, number] {\n if (t.radius <= 0) return [x, y];\n const grow = 1 + TOUCH.pressGrow * t.press;\n let px = x / grow;\n let py = y / grow;\n const dx = px - t.x;\n const dy = py - t.y;\n const r = Math.hypot(dx, dy);\n\n const k = Math.min(Math.max((t.radius - r) / t.radius, 0), 1);\n const s = k * k * (3 - 2 * k);\n const core = s * s * (3 - 2 * s);\n // The ridge around the blob: material pushed out from under the finger has to go somewhere.\n const rim = k * k * (1 - k) * 4;\n\n px -= t.pullX * (core - TOUCH.ridge * rim);\n py -= t.pullY * (core - TOUCH.ridge * rim);\n px -= dx * (t.press * TOUCH.pressPull * core);\n py -= dy * (t.press * TOUCH.pressPull * core);\n\n if (t.waveAmp > 0 && r > 0.0001) {\n const span = t.radius * TOUCH.waveSpan;\n const ring = Math.sin(r / (span * TOUCH.waveLength) - t.wavePhase * VG_TAU) * Math.exp(-r / (span * TOUCH.waveDecay));\n px -= (dx / r) * ring * t.waveAmp;\n py -= (dy / r) * ring * t.waveAmp;\n }\n return [px, py];\n}\n","import { ACCENT, BODY, DISPERSION, LENS, MEDIUM, RIM, SPECTRAL } from './law';\nimport { VG_SDF } from './sdf';\n\n// The lens source is assembled HERE and ships to the native view as a prop: AGSL and SKSL are one\n// language, so the lens's and the surface's geometry are literally the same string (`VG_SDF`).\n//\n// Glass is a thick plate with a convex rounded edge (docs/reference.md §1). A ray from above\n// refracts at the top face and lands on the content shifted INWARD relative to the shape: the\n// middle isn't shifted, the rim shows what lies beneath the body of the element.\n//\n// Uniforms, apart from the view's own geometry, arrive over a SHARED CHANNEL (`adapters.ts` →\n// `uniformNames`/`uniformSizes`/`uniformValues`). There are no more separate Props per value:\n// those were exactly what let a typo silently turn the lens off (docs/material-lab.md E-01).\nexport const LENS_SHADER = `\nuniform shader content;\n\n// Known only to the view itself: its own size and its own place on screen.\nuniform float2 u_center;\nuniform float u_reach;\nuniform float2 u_contentMin;\nuniform float2 u_contentMax;\n// Backdrop estimate UNDER the glass from the native probe: lightness, variegation and average\n// color, already smoothed over time. Negative lightness means the probe hasn't reported yet —\n// fall back to our own samples.\nuniform float u_probeLuma;\nuniform float u_probeBusy;\n// The endpoints of the lightness range under the glass: right at a black/white border the\n// average reads as a gray that says \"everything's fine,\" while the ink drowns over the light\n// half.\nuniform float2 u_probeRange;\n// Slope of lightness across the surface, fraction per half-size: tinting is a gradient — one\n// density per element wouldn't separate the ink from both halves of the backdrop.\nuniform float2 u_probeSlope;\nuniform float3 u_probe;\n\n// Material — over the shared channel from JS, already in device pixels.\nuniform float2 u_halfSize;\nuniform float u_corner;\nuniform float u_bevel;\nuniform float u_thick;\nuniform float u_rim;\nuniform float u_ior;\nuniform float u_iorSpread;\nuniform float2 u_light;\nuniform float u_appear;\nuniform float4 u_accent;\nuniform float u_frost;\nuniform float u_ink;\nuniform float u_legibility;\nuniform float u_dim;\nuniform float u_presence;\nuniform float u_progress;\nuniform float u_adaptRadius;\nuniform float u_bodyDensity;\nuniform float u_edgeLight;\nuniform float u_fresnel;\nuniform float u_specular;\nuniform float u_reflectReach;\nuniform float u_film;\nuniform float u_iridescence;\nuniform float u_diffraction;\nuniform float u_colorPickup;\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;\nuniform float u_debug;\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\n// Sample count for the disc gather: fewer than this reads as a staircase of copies at a sharp edge.\nconst int VG_FROST_TAPS = 20;\n// The sample count grows with the disc's area: estimator noise is the spread under the glass\n// divided by the square root of the sample count. The ceiling is cost: texture sampling is the\n// most expensive thing here.\nconst int VG_FROST_TAPS_MAX = 64;\n// The radius correction for backdrop compression at the rim is capped: at the silhouette it goes\n// to infinity.\nconst float VG_FOOTPRINT_MAX = ${LENS.footprintMax};\nconst float VG_TAU = 6.28318530718;\n// Ceiling on body lightness under light ink (and the mirrored floor under dark ink). This is a\n// ceiling, not a difference: \"0.14 darker than white\" is a lightness of 0.86, at which white text\n// is invisible.\nconst float VG_BODY_CAP_LOOSE = ${BODY.capLoose};\nconst float VG_BODY_CAP_TIGHT = ${BODY.capTight};\n// Tint lightness in both directions: real glass is never coal-black or paper-white.\nconst float VG_TINT_DARK = ${BODY.tintDark};\nconst float VG_TINT_LIGHT = ${BODY.tintLight};\n// Reference channel wavelengths, nm — for diffraction and interference.\nconst float3 VG_LAMBDA = float3(${LENS.lambdaR}.0, ${LENS.lambdaG}.0, ${LENS.lambdaB}.0);\n// Film index of refraction: every thin film on glass sits around this value.\nconst float VG_FILM_IOR = ${LENS.filmIor};\n// Ceiling on the profile's slope right at the silhouette: it goes to infinity there.\nconst float VG_SLOPE_MAX = ${LENS.slopeMax}.0;\n// Fraction of the gather radius that scattering reaches over a busy backdrop under ink (M 11:47).\n// Set against the reference: there, structure under the capsule fades by a factor of 9-10, not\n// thirty.\nconst float VG_SCATTER_MAX = ${MEDIUM.scatterMax};\nconst float VG_SCATTER_BASE = ${MEDIUM.scatterBase};\n// The response to structure under the glass saturates early: what competes with the ink isn't\n// the area of foreign text but the mere fact that it's there (a line under a tile gives a busy\n// of about 0.12).\nconst float VG_STRUCTURE_GAIN = ${MEDIUM.structureGain}.0;\n// How much denser the body with ink gets over a BUSY backdrop than over a calm one. There's no\n// flat floor here: legibility picks up its own targeted requirement further down, and base\n// frosting comes from VG_MATTE_LIFT.\nconst float VG_GROUND_SPAN = ${BODY.groundSpan};\n// Scattered light on a frosted element is an ADDITION on top of the backdrop, not a fraction of\n// the way to the tint: the fraction goes to zero once the canvas reaches the tint's lightness,\n// and darkens past it (docs/benchmarks.md).\nconst float VG_MATTE_LIFT = ${MEDIUM.matteLift};\n// The fraction of backdrop spread that survives through to the body past scattering: the\n// legibility requirement is computed from this edge, not from the spot's average lightness.\nconst float VG_BUSY_EDGE = ${BODY.busyEdge};\n// Light concentration: the body is a touch lighter than what's beneath it (M 2:29).\nconst float VG_CONCENTRATE = ${MEDIUM.concentrate};\n// The lightness the medium pulls content under the glass toward, and the strength of that pull.\n// The medium both removes light and mixes in scattered light: over a light backdrop the body\n// darkens, over a dark one it lightens.\nconst float VG_MEDIUM_LUMA = ${MEDIUM.luma};\nconst float VG_MEDIUM_PULL = ${MEDIUM.pull};\n// How much ambient light reaches the body on top of the pull.\nconst float VG_AMBIENT_SPILL = ${MEDIUM.ambientSpill};\n\nfloat vgLuma(float3 c) { return dot(c, float3(0.2126, 0.7152, 0.0722)); }\n\n// Un-premultiply into ordinary color. One division, done in float: near zero, half's step is too\n// coarse, and several divisions in a row raise noise.\nfloat3 vgUnpack(half4 c) {\n float a = float(c.a);\n return a > 0.004 ? float3(c.rgb) / a : float3(0.0);\n}\n\n// The capture ends at the screen edge: samples are clamped to the rectangle where content\n// actually exists, otherwise full-width glass picked up a band of emptiness along its edges.\nfloat2 vgInContent(float2 q) { return clamp(q, u_contentMin, u_contentMax); }\n\n// Hue without lightness. Below the threshold the color has no hue, and the division blows up.\nfloat3 vgHue(float3 c) {\n float l = vgLuma(c);\n if (l < 0.02) { return float3(1.0); }\n return clamp(c / l, float3(0.0), float3(2.0));\n}\n\n// Edge profile — a convex squircle: x = 0 at the silhouette, 1 where the rounding turns flat.\nfloat vgRimQ(float x) {\n float k = 1.0 - x;\n return sqrt(sqrt(max(1.0 - k * k * k * k, 0.0)));\n}\n\nfloat vgRimQd(float x) {\n float k = 1.0 - x;\n return k * k * k / pow(max(1.0 - k * k * k * k, 1e-5), 0.75);\n}\n\n// Glass height above the content at distance e from the silhouette.\nfloat vgHeight(float e, float w) {\n return mix(u_rim, u_thick, vgRimQ(clamp(e / w, 0.0, 1.0)));\n}\n\n// Slope of the top face at the same point: the derivative of height with respect to distance\n// from the silhouette.\nfloat vgSlope(float e, float w) {\n if (e >= w) { return 0.0; }\n return min((u_thick - u_rim) / w * vgRimQd(clamp(e / w, 0.0, 1.0)), VG_SLOPE_MAX);\n}\n\n// Where a ray falling from above lands on a face with normal N: Snell's law refraction, then a\n// path of length z through the medium to the content. The convex face bends the ray toward the\n// normal — that is, inward relative to the shape.\nfloat2 vgShift(float3 N, float z, float ior) {\n float3 T = refract(float3(0.0, 0.0, -1.0), N, 1.0 / ior);\n return z * T.xy / max(-T.z, 0.05);\n}\n\n// Thin-film interference: optical path difference 2·n·d·cosθt, each channel with its own λ.\n// Returns a hue normalized to its own average — it colors, but doesn't lighten.\nfloat3 vgInterference(float cosI) {\n float sinT2 = (1.0 - cosI * cosI) / (VG_FILM_IOR * VG_FILM_IOR);\n float cosT = sqrt(max(1.0 - sinT2, 0.0));\n float opd = 2.0 * VG_FILM_IOR * u_film * cosT;\n float3 i = 0.5 + 0.5 * cos(VG_TAU * opd / VG_LAMBDA + 3.14159265);\n return i / max((i.r + i.g + i.b) / 3.0, 0.001);\n}\n\n// Edge diffraction: fringes get denser the sharper the bevel. Also a hue, not a brightness.\nfloat3 vgDiffraction(float distFromEdge, float bevel) {\n float phase = VG_TAU * ${SPECTRAL.diffractionFringes}.0 * distFromEdge / max(bevel, 1.0);\n float3 d = 0.5 + 0.5 * cos(phase * (${SPECTRAL.referenceLambda}.0 / VG_LAMBDA));\n return d / max((d.r + d.g + d.b) / 3.0, 0.001);\n}\n\nfloat vgHash(float2 p) {\n return fract(sin(dot(p, float2(12.9898, 78.233))) * 43758.5453);\n}\n\n// Soft compression instead of a hard clamp: over a white backdrop, additions used to knock a\n// channel to one, and the glass turned into a flat blob with no detail.\nfloat3 vgSoftClip(float3 c) {\n float3 over = max(c - 0.86, float3(0.0));\n return min(c, float3(0.86)) + over * 0.14 / (0.14 + over);\n}\n\n// Our own blur: the capture is left clean, the shader does the blurring — averaging in float\n// smooths banding on its own and adds no noise of its own. A golden-angle spiral, rotated to its\n// own angle at every pixel: with a shared angle, neighboring pixels took the same samples, and\n// texture under the glass read in clumps. Accumulated premultiplied — one shared un-premultiply\n// at the end.\nfloat4 vgGather(float2 q, float radius, float2 seed) {\n float4 acc = float4(content.eval(vgInContent(q)));\n if (radius <= 0.25) { return acc; }\n if (radius < 4.0) {\n float r = radius * 0.7;\n acc += float4(content.eval(vgInContent(q + float2(r, 0.0))))\n + float4(content.eval(vgInContent(q - float2(r, 0.0))))\n + float4(content.eval(vgInContent(q + float2(0.0, r))))\n + float4(content.eval(vgInContent(q - float2(0.0, r))));\n return acc * 0.2;\n }\n float ca = cos(2.39996323);\n float sa = sin(2.39996323);\n float a0 = vgHash(seed) * 6.28318530718;\n float2 dir = float2(cos(a0), sin(a0));\n float wide = radius * 0.5;\n int taps = int(clamp(float(VG_FROST_TAPS) * wide * wide, float(VG_FROST_TAPS), float(VG_FROST_TAPS_MAX)));\n for (int i = 1; i <= VG_FROST_TAPS_MAX; i++) {\n if (i > taps) { break; }\n dir = float2(dir.x * ca - dir.y * sa, dir.x * sa + dir.y * ca);\n float r = radius * sqrt(float(i) / float(taps));\n acc += float4(content.eval(vgInContent(q + dir * r)));\n }\n return acc / float(taps + 1);\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 float sd = vgScene(p, u_halfSize, u_corner, u_morphOffset, u_morphHalf, u_morphCorner, u_morphK,\n u_morph2Offset, u_morph2Half, u_morph2Corner);\n // There's nothing outside the glass — except the light it spills onto the backdrop under the\n // finger (M 3:38): the same concentrated ambient as inside, so no white halo appears over a\n // dark backdrop.\n if (sd > 1.0) {\n float spill = u_touchPress * exp(-sd / max(min(u_halfSize.x, u_halfSize.y) * 0.5, 4.0)) * 0.5 * u_appear;\n if (spill < 0.004) { return half4(0.0); }\n float3 lit = clamp((u_probeLuma >= 0.0 ? u_probe : float3(0.35)) * 1.6 + float3(0.1), float3(0.0), float3(1.0));\n return half4(half3(lit * spill), half(spill));\n }\n\n float bevel = max(u_bevel, 1.0);\n float e = max(-sd, 0.0);\n // 0 in the flat middle, 1 at the silhouette.\n float t = 1.0 - clamp(e / bevel, 0.0, 1.0);\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\n // \"backdrop\" mode hands back the content as-is — a reference point for comparing the optics.\n float on = u_debug > 5.5 && u_debug < 6.5 ? 0.0 : 1.0;\n float lens = on * u_appear;\n\n float z = vgHeight(e, bevel);\n float slope = vgSlope(e, bevel);\n float3 N = normalize(float3(n * slope, 1.0));\n float2 shift = vgShift(N, z, u_ior) * lens;\n float2 s = u_center + p + shift;\n\n // The area the lens gathers into one pixel: a one-pixel step inward on screen, measured\n // against the sampling step. At the silhouette it's large — a stripe of backdrop is compressed\n // into a line there, and a point sample gives grain.\n float e1 = e + 1.0;\n float3 N1 = normalize(float3(n * vgSlope(e1, bevel), 1.0));\n float2 shift1 = vgShift(N1, vgHeight(e1, bevel), u_ior) * lens;\n float footprint = length(shift1 - shift - n);\n float spread = max(footprint - 1.0, 0.0) * 0.5 * on;\n\n // Dispersion: the same ray with each channel's own index. Blue bends more than red.\n float2 dR = vgShift(N, z, u_ior + ${DISPERSION.redShift} * u_iorSpread) * lens - shift;\n float2 dB = vgShift(N, z, u_ior + ${DISPERSION.blueShift} * u_iorSpread) * lens - shift;\n float chroma = length(dB - dR);\n\n float3 rgb;\n float srcA;\n\n if (chroma + spread < 0.25) {\n // In the flat body all the samples would land at the same point, and the body is almost the\n // whole area of the glass.\n half4 c = content.eval(vgInContent(s));\n srcA = float(c.a);\n rgb = srcA > 0.004 ? float3(c.rgb) / srcA : float3(0.0);\n } else {\n // Accumulate premultiplied and divide once per pair: channels from different points with\n // different alpha would otherwise produce a colored fringe at content edges that isn't\n // really there.\n float2 ev = n * spread;\n half4 c0 = content.eval(vgInContent(s + dR - ev));\n half4 c1 = content.eval(vgInContent(s + dR + ev));\n half4 c2 = content.eval(vgInContent(s - ev));\n half4 c3 = content.eval(vgInContent(s + ev));\n half4 c4 = content.eval(vgInContent(s + dB - ev));\n half4 c5 = content.eval(vgInContent(s + dB + ev));\n\n float aR = float(c0.a + c1.a) * 0.5;\n float aG = float(c2.a + c3.a) * 0.5;\n float aB = float(c4.a + c5.a) * 0.5;\n rgb = float3(\n aR > 0.004 ? float(c0.r + c1.r) * 0.5 / aR : 0.0,\n aG > 0.004 ? float(c2.g + c3.g) * 0.5 / aG : 0.0,\n aB > 0.004 ? float(c4.b + c5.b) * 0.5 / aB : 0.0);\n srcA = (aR + aG + aB) / 3.0;\n }\n\n // \"backdrop\" mode checks the CAPTURE, not a simplified version of the optics. What follows are\n // layers that aren't tied to the lens (body, medium, scattering), and they used to conflate\n // \"did the pixels arrive\" with \"how did we process them.\" Here the element must DISAPPEAR: a\n // visible silhouette means a capture loss.\n if (u_debug > 5.5 && u_debug < 6.5) {\n float bypass = srcA * (1.0 - smoothstep(-1.0, 1.0, sd));\n return half4(half3(clamp(rgb, float3(0.0), float3(1.0)) * bypass), half(bypass));\n }\n\n // The transparent variant's dimming layer holds legibility instead of the body. A\n // multiplication, not a tint: the content has to stay visible, just quieter.\n rgb *= 1.0 - u_dim * u_appear;\n\n // REFLECTION. Fresnel from the face's actual slope: at the silhouette the view is grazing and\n // reflection is nearly total, which is why the rim reads as a thin line on its own. What's\n // reflected is the element's surroundings and the key light.\n float cosT = clamp(N.z, 0.0, 1.0);\n float f0 = (u_ior - 1.0) / (u_ior + 1.0);\n f0 *= f0;\n float fres = (f0 + (1.0 - f0) * pow(1.0 - cosT, 5.0)) * u_fresnel * lens;\n\n float lineW = clamp(min(u_halfSize.x, u_halfSize.y) * 0.03, 1.5, 5.0);\n float3 refl;\n if (e < lineW * 2.0 || fres > 0.01) {\n float2 around = u_center + p + n * (u_reflectReach * mix(0.35, 1.0, clamp(slope / 3.2, 0.0, 1.0)));\n // Two rings of eight samples total: a single offset sample is an undistorted COPY of\n // neighboring content inside the glass (docs/material-lab.md E-33).\n float p1 = u_reflectReach * 0.75;\n float p2 = u_reflectReach * 0.4;\n float d = 0.7071;\n float4 rsum = float4(content.eval(vgInContent(around + float2(p1, 0.0))))\n + float4(content.eval(vgInContent(around - float2(p1, 0.0))))\n + float4(content.eval(vgInContent(around + float2(0.0, p1))))\n + float4(content.eval(vgInContent(around - float2(0.0, p1))))\n + float4(content.eval(vgInContent(around + float2(p2, p2) * d)))\n + float4(content.eval(vgInContent(around + float2(-p2, p2) * d)))\n + float4(content.eval(vgInContent(around + float2(p2, -p2) * d)))\n + float4(content.eval(vgInContent(around + float2(-p2, -p2) * d)));\n float ra = rsum.a * 0.125;\n refl = ra > 0.004 ? rsum.rgb * 0.125 / ra : float3(0.0);\n } else {\n refl = u_probeLuma >= 0.0 ? u_probe : rgb;\n }\n\n // Light reaching the body is the same across the whole element: blending with the surroundings\n // over the bevel's band used to paint a ring-shaped seam on the body.\n float3 ambient = u_probeLuma >= 0.0 ? u_probe : rgb;\n\n // Light comes from wherever the surroundings are brighter (M 11:04): the lightness slope under\n // the element pulls the key light toward itself, and over a flat backdrop it stays at rest.\n float2 bright = u_probeLuma >= 0.0 ? u_probeSlope : float2(0.0);\n float brightLen = length(bright);\n float2 L = normalize(mix(u_light, bright / max(brightLen, 1e-4), smoothstep(0.03, 0.2, brightLen)) + float2(1e-5));\n // The key light's reflection uses the face's actual normal: at the flat top it points straight\n // up, and there's nothing there for direction to depend on. Using the SDF normal instead\n // painted the body as a cone.\n float facing = dot(N.xy, L);\n float key = u_specular * (pow(max(facing, 0.0), ${RIM.lobeExponent}.0) + ${RIM.opposingArc} * pow(max(-facing, 0.0), ${RIM.lobeExponent}.0));\n float rimFacing = dot(n, L);\n float rimKey = u_specular * (pow(max(rimFacing, 0.0), ${RIM.lobeExponent}.0) + ${RIM.opposingArc} * pow(max(-rimFacing, 0.0), ${RIM.lobeExponent}.0));\n // Wherever the key light doesn't fall, the face reflects its shaded surroundings — hence the\n // dark outline.\n // The key light's glint is a reflection of the surroundings too: it is scaled by the same\n // ambient strength as the rim line, so it is not white at full strength over a dark backdrop.\n float rimStrength = mix(${RIM.strengthDark}, ${RIM.strengthLight},\n smoothstep(${RIM.ambientFrom}, ${RIM.ambientTo}, vgLuma(ambient)));\n float3 env = refl * (0.55 + 0.45 * min(key, 1.0)) + float3(1.2 * key * rimStrength / ${RIM.strengthLight});\n\n // Interference and diffraction live in the reflected ray at a grazing angle — a single hue\n // multiplier applied to the reflection, no extra samples.\n float3 spectral = float3(1.0);\n if (u_iridescence > 0.001) {\n spectral *= mix(float3(1.0), vgInterference(cosT), u_iridescence);\n }\n if (u_diffraction > 0.001) {\n float w = u_diffraction * smoothstep(${SPECTRAL.diffractionOnset}, 1.0, t);\n spectral *= mix(float3(1.0), vgDiffraction(e, bevel), w);\n }\n\n // The BACKDROP ESTIMATE arrives from the probe as one value per surface: computing it here\n // from samples isn't an option — a kink in density used to turn the estimate's discreteness\n // into ghost copies of text.\n float3 wide;\n float busy;\n if (u_probeLuma >= 0.0) {\n wide = u_probe;\n busy = u_probeBusy;\n } else {\n // The probe hasn't reported yet (first frames) — fall back to our own samples.\n float2 wx = float2(u_adaptRadius, 0.0);\n float2 wy = float2(0.0, u_adaptRadius);\n float3 w0 = vgUnpack(content.eval(vgInContent(s + wx)));\n float3 w1 = vgUnpack(content.eval(vgInContent(s - wx)));\n float3 w2 = vgUnpack(content.eval(vgInContent(s + wy)));\n float3 w3 = vgUnpack(content.eval(vgInContent(s - wy)));\n wide = (w0 + w1 + w2 + w3) * 0.25;\n float lw = vgLuma(wide);\n // The same statistic the probe uses (twice the mean deviation): both blur and the ink's\n // contrast margin depend on it, and the two paths' scales must not diverge.\n busy = clamp(0.5 * (abs(vgLuma(w0) - lw) + abs(vgLuma(w1) - lw)\n + abs(vgLuma(w2) - lw) + abs(vgLuma(w3) - lw)), 0.0, 1.0);\n }\n // Lightness AT THIS SPOT on the surface: the probe's plane, clamped to the measured range.\n float2 nrm = p / max(u_halfSize, float2(1.0));\n float local = u_probeLuma >= 0.0\n ? clamp(u_probeLuma + dot(u_probeSlope, nrm), u_probeRange.x, u_probeRange.y)\n : vgLuma(wide);\n float mean = u_probeLuma >= 0.0 ? u_probeLuma : vgLuma(wide);\n\n // SCATTERING. Regular mutes foreign structure under its own ink with blur, not a fill (M\n // 11:47): text under a capsule turns into blotches. Uniform across the whole lens; the radius\n // is measured in backdrop space — the lens compresses it at the silhouette.\n float structure = 1.0 - exp(-busy * VG_STRUCTURE_GAIN);\n // Under ink, Regular always scatters the backdrop; over a busy one, more strongly.\n float adaptBlur = max(\n (structure * VG_SCATTER_MAX + VG_SCATTER_BASE) * u_legibility * u_adaptRadius\n * min(max(footprint, 1.0), VG_FOOTPRINT_MAX),\n u_frost) * u_appear;\n if (adaptBlur > 0.5) {\n float reach = max(u_reach - length(p), 1.0);\n float4 g = vgGather(s, min(adaptBlur, reach), xy);\n float ga = g.a;\n float3 blurred = ga > 0.004 ? g.rgb / ga : float3(0.0);\n rgb = mix(rgb, blurred, smoothstep(0.5, 2.0, adaptBlur));\n }\n\n // AFTER scattering, not before: scattering is a property of TRANSMITTED light and replaces rgb\n // wholesale, so doing it the other way around erased the reflection over the whole element\n // (issue #106).\n rgb = mix(rgb, env * spectral, fres);\n\n // BODY. Tint and dynamic range under the glass compress just enough for ink over it to stay\n // legible (M 6:42). u_ink is ink POLARITY (1 light, 0 dark): the requirements are computed at\n // the two ends and blended by it, otherwise the glass dips toward the middle during a recolor.\n float strict = clamp(u_legibility * 2.0, 0.0, 1.0);\n float capLight = mix(VG_BODY_CAP_LOOSE, VG_BODY_CAP_TIGHT, strict);\n float floorDark = 1.0 - capLight;\n float pol = clamp(u_ink, 0.0, 1.0);\n // The requirement fades together with legibility: an element with no ink is promised\n // transparent glass by the model.\n float demand = clamp(u_legibility * 4.0, 0.0, 1.0);\n\n // The body is lighter than its surroundings whatever the glyph on it is: glass concentrates\n // light (M 2:29), and the reference keeps a lighter body under a white glyph over sky as much as\n // over dark. Only a requirement of the ink pulls it to the ink's own pole, below.\n float inkPole = mix(VG_TINT_LIGHT, VG_TINT_DARK, pol);\n\n // How much medium it takes to push lightness under the ink past the threshold — locally.\n // Computed from whichever edge of the backdrop's spread is closer to the ink: a light ink\n // drowns in a light spot right beneath it, and the spot's average lightness knows nothing\n // about that spot.\n float edge = busy * VG_BUSY_EDGE;\n float inkHi = min(local + edge, 1.0);\n float inkLo = max(local - edge, 0.0);\n float needForLight = inkHi > capLight\n ? clamp((inkHi - capLight) / max(inkHi - VG_TINT_DARK, 1e-4), 0.0, ${BODY.maxDemand})\n : 0.0;\n float needForDark = inkLo < floorDark\n ? clamp((floorDark - inkLo) / max(VG_TINT_LIGHT - inkLo, 1e-4), 0.0, ${BODY.maxDemand})\n : 0.0;\n // Over a flat backdrop the ink asks for nothing: polarity flips before it drowns. It is spread\n // under the ink that the body has to answer.\n float structured = smoothstep(${BODY.inkBusyFrom}, ${BODY.inkBusyTo}, busy);\n float needForInk = mix(needForDark, needForLight, pol) * demand * structured;\n\n float ground = structure * u_legibility * VG_GROUND_SPAN;\n float density = max(max(u_bodyDensity, ground), needForInk);\n\n // PRESENCE (§3). Over a uniform backdrop there is nothing to refract, and an element that is\n // only a rim honestly disappears — right for a piece of background, wrong for a control. A floor\n // on the density, toward light: the lift it gives is presence times the room left under the\n // light pole, so it fades out as the room does and has no step anywhere on the scale.\n if (u_presence > 0.0) {\n density = max(density, min(u_presence / VG_TINT_LIGHT, ${BODY.presenceDemand}));\n }\n float tintLuma = mix(VG_TINT_LIGHT, inkPole, max(clamp(needForInk / max(density, 1e-4), 0.0, 1.0), smoothstep(${BODY.presenceDemand}, ${BODY.inkPoleTo}, density)));\n\n // Glass has no color of its own — only the color of what's beneath it (HIG \"Color\").\n float3 tintHue = mix(float3(1.0), vgHue(wide * 0.5 + ambient * 0.5), u_colorPickup);\n // The element appears by the lens and body building up, not by fading in (M 2:55): at\n // u_appear = 0 it's indistinguishable from the backdrop beneath it.\n rgb = mix(rgb, tintHue * tintLuma, density * u_appear);\n rgb += tintHue * (u_legibility * VG_MATTE_LIFT * u_appear);\n\n // TINTING — colored glass, not a fill (M 16:31, 17:03): the backdrop's lightness drives the\n // tone — deeper over dark, lighter over light — and the content's texture still shows through\n // the color.\n if (u_accent.a > 0.0) {\n float3 tone = u_accent.rgb * mix(${ACCENT.deep}, ${ACCENT.light}, local);\n float3 through = tone * (0.85 + 0.3 * vgLuma(rgb));\n rgb = mix(rgb, clamp(through, float3(0.0), float3(1.0)), u_accent.a * u_appear);\n }\n\n // CONTRAST COMPRESSION. The medium removes some light and mixes in scattered light, so content\n // under the glass moves toward the middle rather than simply getting lighter (M 2:35). The\n // previous layer only added light and, over a light backdrop, pushed the body the opposite way\n // from the reference.\n rgb = mix(rgb, vgHue(ambient) * VG_MEDIUM_LUMA, VG_MEDIUM_PULL * u_appear);\n // Ambient light reaches the body the same way across the whole element.\n rgb += ambient * u_edgeLight * VG_AMBIENT_SPILL * u_appear;\n // Glass concentrates light; the played portion is the segment where there's more of it.\n float glow = VG_CONCENTRATE + 0.08 * vgProgress(p, u_halfSize, u_progress);\n rgb += (1.0 - rgb) * glow * lens;\n\n // Under the finger and during morphing, glass concentrates light into a spot (M 4:56, 5:11).\n // This is a CONCENTRATION of the surroundings, not the glass's own whiteness: over a dark\n // backdrop the element lightens but never turns white, and ink over it stays legible.\n if (u_touchPress > 0.001) {\n float2 fromTouch = p - u_touch;\n float spotR = max(min(u_halfSize.x, u_halfSize.y) * 1.2, 1.0);\n float spot = u_touchPress * (0.3 + 0.55 * exp(-dot(fromTouch, fromTouch) / (spotR * spotR)));\n rgb = mix(rgb, clamp(ambient * 1.6 + float3(0.1), float3(0.0), float3(1.0)), spot * lens);\n }\n\n // RIM LIGHT — a separate layer, a line about 1 pt wide along the silhouette (M 2:36, 11:04):\n // brighter where the face faces the key light, weaker opposite it, and never absent: the line\n // runs the whole contour. It is a reflection of the surroundings, so it is faint over a dark\n // one and plain over a bright one (§2, §6).\n float line = smoothstep(0.0, lineW * ${RIM.lineInset}, e) * (1.0 - smoothstep(lineW * ${RIM.lineInset}, lineW, e)) * lens;\n float outline = (1.0 - smoothstep(0.0, lineW * 0.6, e)) * lens;\n float reflectN = min(u_fresnel / ${RIM.reflectanceRef}, 1.0);\n float lit = clamp(rimStrength * reflectN * (${RIM.contour} + (1.0 - ${RIM.contour}) * rimKey / max(u_fresnel, 0.001)), 0.0, 1.0);\n // The dark rim is its own layer around the whole silhouette, and the highlight sits ON TOP of\n // it (iOS 27). While the outline used to fade via a (1 − lit) multiplier, the silhouette read\n // as a single arc: highlight OR shadow.\n rgb *= 1.0 - ${RIM.darkEdge} * outline;\n rgb = mix(rgb, mix(refl, float3(1.0), ${RIM.whiteShare}), line * lit);\n\n if (u_debug > 9.5 && u_debug < 10.5) { rgb = spectral * 0.5; }\n if (u_debug > 10.5 && u_debug < 11.5) { rgb = float3(density, needForInk, structure); }\n // WHAT THE PROBE SAID ABOUT THE BACKDROP — three values that drive all the tinting: the\n // average lightness under the element, the vertical lightness slope, and the lightness AT THIS\n // SPOT. The slope is shown offset and squeezed by half: it's signed and rarely exceeds a\n // magnitude of one half.\n if (u_debug > 11.5) { rgb = float3(max(u_probeLuma, 0.0), 0.5 + u_probeSlope.y * 0.5, local); }\n\n rgb = vgSoftClip(rgb);\n\n // Sample alpha has to survive to the result: un-premultiplying color by its original alpha and\n // then returning it with a different one (the shape mask) would make a transparent backdrop\n // opaque and blow it out.\n float alpha = srcA * (1.0 - smoothstep(-1.0, 1.0, sd));\n return half4(half3(clamp(rgb, float3(0.0), float3(1.0)) * alpha), half(alpha));\n}\n`;\n","// VireGlass material model.\n//\n// Two distinct objects, and they must not be confused:\n//\n// VireGlassMaterial — CAUSES. What a piece of glass is described by: index of refraction,\n// thickness, bevel width, roughness, absorption color. This is what gets\n// configured.\n// VireGlassOptics — EFFECTS. Eighteen values the shaders need. Not configured: they're\n// derived (`resolveOptics`, physics in `optics.ts`).\n//\n// Before v4, what was exposed were the effects themselves — eighteen independent sliders. Half of\n// them are physically dependent, so combining them produced states that don't occur in real\n// glass: a thin bevel with a dense white rim, rim optics as a fraction of the element's own size.\n// Every such case was patched by adding yet another slider (`edgeDensity`, a size compensation) —\n// that is, compensating for physics that wasn't there. Here there's one cause, and the effects\n// are derived.\n\nimport {\n absorption,\n blur as blurFrom,\n dispersion as dispersionFrom,\n edgeDensity as edgeDensityFrom,\n gatherRadius as gatherRadiusFrom,\n fresnelStrength,\n FRESNEL_EXPONENT,\n mediumTint,\n refractionScale as refractionScaleFrom,\n refractionStrength,\n specularPower as specularPowerFrom,\n bodyDensity as bodyDensityFrom,\n edgeLight as edgeLightFrom,\n iridescence as iridescenceFrom,\n diffraction as diffractionFrom,\n colorPickup as colorPickupFrom,\n specularStrength,\n iorSpread as iorSpreadFrom,\n} from './optics';\n\nexport type VireGlassTint = { r: number; g: number; b: number };\n\nexport type VireGlassMaterial = {\n /** Index of refraction of the medium. Water 1.33, glass 1.5, sapphire 1.77. */\n ior: number;\n /** Glass thickness, dp. Drives center magnification and absorption. */\n thickness: number;\n /** Bevel width, dp. Absolute: on real glass the rim doesn't depend on the piece's size. */\n bevel: number;\n /** Surface roughness: backdrop haziness and highlight blur. */\n roughness: number;\n /** Response to device orientation, 0 means the light is locked to the screen. */\n environment: number;\n /** Not physics but a legibility requirement: how strongly the glass has to separate its own\n * lightness from the ink drawn over it. 0 means the glass is simply transparent. */\n legibility: number;\n /** Dimming layer UNDER the glass, 0..1. For the transparent (Clear) variant this is what\n * provides legibility: that glass has no adaptation, and without dimming the ink drowns over\n * bright content.\n *\n * Not to be confused with the mobile surface's `dim` prop: that one compensates for a platform\n * capture quirk (BlurView can't see a scrim drawn over the content), while this is a property\n * of the material itself. */\n dimming: number;\n /** Lightness of what the app draws OVER the glass: 1 is light icons and text, 0 is dark. A\n * cause, not a knob: the calling screen knows it. */\n ink: number;\n /** Minimum PRESENCE of the element itself: a floor on its body density, tinted toward light and\n * fading as the backdrop nears the light pole. Not the same as `legibility` — that's about the ink OVER the glass,\n * this is about the element itself. Over a uniform backdrop the glass has nothing to refract,\n * and the element disappears; for a control that's unacceptable. 0 means no presence is\n * required.\n *\n * The direction is always toward light, whatever the ink: the reference body is lighter than\n * its surroundings from dark video to sky, under a white glyph as much as a dark one, and only\n * as far as the range allows. It never darkens the element on its own account. What can send\n * the body to the ink's pole is the ink's legibility over texture, a system setting (reduced\n * transparency, increased contrast) or a density past `BODY.inkPoleTo`. */\n presence: number;\n /** Surface film thickness, nm. This is where interference comes from: the optical path\n * difference in the film becomes comparable to the wavelength, and the reflection picks up\n * iridescence. 0 means no film. */\n film: number;\n};\n\nexport const MATERIAL_RANGES = {\n ior: [1, 2],\n thickness: [0, 60],\n bevel: [0, 40],\n roughness: [0, 1],\n environment: [0, 1],\n legibility: [0, 1],\n dimming: [0, 0.5],\n ink: [0, 1],\n presence: [0, 0.6],\n film: [0, 900],\n} as const satisfies Record<string, readonly [number, number]>;\n\nexport type VireGlassNumericKey = keyof typeof MATERIAL_RANGES;\n\n/** Radius over which the LOCAL backdrop lightness is taken, dp. Not per-pixel: otherwise the\n * glass chases individual strokes and a halo appears around letters beneath it. */\nexport const ADAPT_RADIUS = 22;\n\n/** Lens dimming on product surfaces — a flat fill over the backdrop. */\nexport const PRODUCT_DIM = 0.2;\n\nconst clamp = (v: number, lo: number, hi: number) => (v < lo ? lo : v > hi ? hi : v);\n\n/** The values the shaders need. Assembled from the material, never set by hand. */\nexport type VireGlassOptics = {\n blur: number;\n refraction: number;\n refractionScale: number;\n /** Bevel width in dp — an absolute property of the medium. */\n bevelDp: number;\n /** Radius over which the rim gathers light around the element. */\n gatherRadiusDp: number;\n fresnel: number;\n fresnelPower: number;\n specular: number;\n specularPower: number;\n dispersion: number;\n tint: VireGlassTint;\n tintStrength: number;\n edgeDensity: number;\n environment: number;\n legibility: number;\n /** Dimming of the content under the glass — the very layer of the Clear variant. */\n dimming: number;\n ink: number;\n /** Minimum presence of the element against the backdrop — in units of lightness. */\n presence: number;\n adaptRadius: number;\n bodyDensity: number;\n edgeLight: number;\n /** Film thickness, nm — passed straight to the shader: the path difference is computed in the\n * same units. */\n film: number;\n /** Strength of reflection iridescence. */\n iridescence: number;\n /** Strength of the edge diffraction fringes. */\n diffraction: number;\n /** How much the body is tinted by the color of its surroundings. */\n colorPickup: number;\n /** Index of refraction: the shader computes ray bending from it via Snell's law. */\n ior: number;\n /** Plate thickness, dp — before the correction for element size. */\n thicknessDp: number;\n /** Spread of the index between the red and blue channels — dispersion. */\n iorSpread: number;\n};\n\n// The product default — \"water\": lower index of refraction, thinner medium and bevel, an almost\n// smooth surface. Chosen on-device at the bench.\nexport const VIREGLASS_MATERIAL_V4: VireGlassMaterial = {\n ior: 1.33,\n thickness: 14,\n bevel: 5,\n roughness: 0.05,\n environment: 0.27,\n legibility: 0.26,\n dimming: 0,\n ink: 1,\n presence: 0.05,\n film: 340,\n};\n\n\n/**\n * Glass, not water. v4's index of refraction, 1.33, is water: almost no reflection at the rim,\n * almost no dispersion, 2% magnification. On screen that surface honestly reads as \"nothing much\",\n * and it was being mistaken for glass that wasn't rendering at all.\n *\n * 1.5 is ordinary glass. The effects follow from it on their own: reflection at the rim twice as\n * strong, dispersion one and a half times, ambient color pickup at its ceiling. Thickness is\n * raised a little too: it drives magnification, but absorption comes along with it, and\n * absorption is blackness.\n */\nexport const VIREGLASS_MATERIAL_V5: VireGlassMaterial = {\n ior: 1.5,\n thickness: 28,\n bevel: 6,\n roughness: 0.06,\n environment: 0.27,\n legibility: 0.26,\n dimming: 0,\n ink: 1,\n presence: 0.05,\n film: 340,\n};\n\n/**\n * Frosted sheet glass. The sheet sits over a LIVE screen, and it can't be transparent: transport\n * buttons and progress used to read right through it — the layout looked broken. Dimming doesn't\n * fix this (a flat scrim over a sharp picture doesn't hide it), so roughness is raised instead: it\n * drives `u_frost`, a floor on backdrop blur that the adaptive-blur ceiling doesn't reach.\n */\nexport const VIREGLASS_SHEET_MATERIAL: VireGlassMaterial = {\n ...VIREGLASS_MATERIAL_V5,\n roughness: 0.85,\n};\n/** The product's material. Consumers use this, not a specific version by name. */\n/**\n * Glass under large text (the lyrics panel in the player).\n *\n * The product material with NO change to optics — only `legibility` is raised. That's exactly its\n * reason to exist: \"text on me must stay readable.\" It adds density locally — denser over light\n * and busy backdrops, more transparent over an even dark one.\n *\n * A dedicated thick material (thickness 52, ior 1.58) used to sit here and was removed: medium\n * absorption grows with thickness, and the panel picked up a black fringe around its perimeter —\n * a sooty plate instead of glass. Don't turn thickness into a legibility knob; that's what\n * `legibility` is for.\n */\nexport const VIREGLASS_LYRICS_MATERIAL: VireGlassMaterial = {\n ...VIREGLASS_MATERIAL_V5,\n legibility: 0.95,\n};\n\n/**\n * Glass for controls — buttons, tiles, anything that gets pressed.\n *\n * THICK AND CLEAR, not the baseline: wider bevel, thickness up by half, higher index of\n * refraction and almost no roughness. Baseline glass is a lens over a calm backdrop; a control has\n * to read as an object you can pick up, and that's not done by a fill but by volume: a wide bevel\n * gives the rim something to catch, and thickness gives it depth.\n *\n * Thickness is set by BUILDUP AT THE RIM: on the reference control the stripe image under the\n * element bulges there by a factor of 1.85; ours came out at 1.36 at 30. Saturation sets in around\n * 44, and any extra thickness past that only adds absorption — hence 44, not more.\n *\n * It also drags `tintStrength` (= `absorption(thickness)`) along with it, and on the path WITHOUT\n * a native lens that sets body density: there the element is denser by 2.4 percentage points. On\n * the web the surface has no body at all; the effect only shows on Android below 13.\n *\n * `presence` is left at none: the medium's own lift already separates the body from a uniform\n * backdrop as far as the reference does (docs/dom.md), and the rim carries the silhouette.\n *\n * IMPORTANT about size. The bevel is specified by the medium in dp and doesn't depend on the\n * element's size, but the geometry clamps it to half the smaller half-size (`MAX_BEVEL_FRACTION`).\n * On a 52 dp button that's 13 instead of 16 — a small element made of this glass ends up being\n * bevel all the way through. That's by design: a small piece of thick glass is supposed to look\n * like a lens, not a slab.\n */\nexport const VIREGLASS_CONTROL_MATERIAL: VireGlassMaterial = {\n ...VIREGLASS_MATERIAL_V5,\n ior: 1.69,\n thickness: 44,\n bevel: 8,\n roughness: 0.035,\n presence: 0,\n};\n\nexport const VIREGLASS_MATERIAL = VIREGLASS_MATERIAL_V5;\n\n/**\n * An element that CARRIES app ink (an icon, a line of text, cover art) has to keep it legible —\n * that's the reason `legibility` exists. An element with no ink has nothing to separate lightness\n * from, so the requirement is turned off for it: the model just promises it plain transparent\n * glass.\n *\n * The rule lives here, not with the consumer: otherwise the same icon button would get different\n * legibility on the web and on Android.\n */\nexport function materialForInk(material: VireGlassMaterial, carriesInk: boolean): VireGlassMaterial {\n // For the transparent variant, legibility is held by the dimming layer, not the body. Raising\n // its requirement would just turn it into ordinary glass, and the variants don't mix (219 §Clear).\n if (material.dimming > 0) return material;\n return { ...material, legibility: carriesInk ? VIREGLASS_LYRICS_MATERIAL.legibility : 0 };\n}\n\n/**\n * The transparent material variant (Clear). It has no adaptation — the glass stays consistently\n * more transparent, content beneath it shows through almost as-is, and legibility of the ink is\n * held by the dimming layer instead. Fit only where the reference's three conditions hold: the\n * element sits over media, the content can tolerate dimming, and the ink over it is large and\n * bright.\n */\nexport const VIREGLASS_CLEAR_MATERIAL: VireGlassMaterial = {\n ...VIREGLASS_MATERIAL,\n legibility: 0,\n presence: 0,\n // Apple's own numbers: 0.35 in the HIG Materials text, 0.3 in its own sample code. We take the\n // lower one — it comes from working code, not from prose, and Clear has to stay a window.\n dimming: 0.3,\n};\n\n/**\n * ACTIVE STATE as a state of the MEDIUM, not a highlight drawn over it.\n *\n * An active element is denser, clearer glass: higher index of refraction, thicker body, wider\n * bevel, less roughness. A bright rim, a strong highlight and greater presence all follow from\n * these causes on their own — assembling them separately would produce a state that doesn't occur\n * in real glass.\n *\n * `environment` is deliberately left untouched here. It sets how much the body is tinted by\n * what's beneath it, and at full strength an active button over cover art turned into a colored\n * blob: the state read as \"smudged\" rather than \"selected.\" A state indicator must not depend on\n * the backdrop — otherwise any image sliding underneath the element would fake it.\n */\nexport function activeMaterial(material: VireGlassMaterial, on: number): VireGlassMaterial {\n const k = Math.min(Math.max(on, 0), 1);\n return {\n ...material,\n ior: material.ior + 0.35 * k,\n thickness: material.thickness * (1 + 0.9 * k),\n bevel: material.bevel * (1 + 1.8 * k),\n roughness: material.roughness * (1 - 0.75 * k),\n presence: material.presence + 0.12 * k,\n };\n}\n\nexport function resolveMaterial(patch: Partial<VireGlassMaterial> = {}): VireGlassMaterial {\n const m = { ...VIREGLASS_MATERIAL, ...patch };\n const out = { ...m };\n for (const key of Object.keys(MATERIAL_RANGES) as VireGlassNumericKey[]) {\n const [lo, hi] = MATERIAL_RANGES[key];\n out[key] = clamp(out[key], lo, hi);\n }\n return out;\n}\n\n/** Causes → effects. All of the transition's physics lives in `optics.ts`. */\nexport function resolveOptics(patch: Partial<VireGlassMaterial> = {}): VireGlassOptics {\n const m = resolveMaterial(patch);\n return {\n blur: blurFrom(m.roughness),\n refraction: refractionStrength(m.ior),\n refractionScale: refractionScaleFrom(m.ior, m.thickness),\n bevelDp: m.bevel,\n gatherRadiusDp: gatherRadiusFrom(m.bevel),\n fresnel: fresnelStrength(m.ior),\n fresnelPower: FRESNEL_EXPONENT,\n specular: specularStrength(m.ior, m.roughness),\n specularPower: specularPowerFrom(m.roughness),\n dispersion: dispersionFrom(m.ior),\n tint: mediumTint(m.ior),\n tintStrength: absorption(m.thickness),\n edgeDensity: edgeDensityFrom(m.thickness, m.bevel),\n environment: m.environment,\n legibility: m.legibility,\n dimming: m.dimming,\n ink: m.ink,\n presence: m.presence,\n adaptRadius: ADAPT_RADIUS,\n bodyDensity: bodyDensityFrom(m.thickness),\n edgeLight: edgeLightFrom(m.ior),\n film: m.film,\n iridescence: iridescenceFrom(m.ior, m.film),\n diffraction: diffractionFrom(m.ior),\n colorPickup: colorPickupFrom(m.ior),\n ior: m.ior,\n thicknessDp: m.thickness,\n iorSpread: iorSpreadFrom(m.ior),\n };\n}\n\n/**\n * Frozen snapshots of the EFFECTS from the previous model. Kept as reference points until v4\n * settles: they can't be converted to causes — some of the values are internally inconsistent\n * (exactly why the move was made in the first place), and any \"equivalent\" material would be a\n * lie.\n */\nexport const LEGACY_OPTICS = {\n 'v3-manual': {\n blur: 4.32,\n refraction: 0.49,\n refractionScale: 1.05,\n bevelDp: 12.6,\n gatherRadiusDp: 40,\n fresnel: 0.77,\n fresnelPower: 2.86,\n specular: 0.3,\n specularPower: 74.58,\n dispersion: 0.54,\n tint: { r: 0.4, g: 0.4, b: 0.44 },\n tintStrength: 0.35,\n edgeDensity: 1.5,\n environment: 0.27,\n legibility: 0.55,\n dimming: 0,\n ink: 1,\n presence: 0,\n adaptRadius: ADAPT_RADIUS,\n bodyDensity: 0.14,\n edgeLight: 0.35,\n film: 0,\n iridescence: 0,\n diffraction: 0,\n colorPickup: 0,\n ior: 1.5,\n thicknessDp: 16,\n iorSpread: 0,\n },\n 'v2': {\n blur: 5,\n refraction: 0.95,\n refractionScale: 1.34,\n bevelDp: 14,\n gatherRadiusDp: 40,\n fresnel: 0.72,\n fresnelPower: 2.4,\n specular: 0.38,\n specularPower: 46,\n dispersion: 0.3,\n tint: { r: 0.4, g: 0.4, b: 0.44 },\n tintStrength: 0.1,\n edgeDensity: 3.63,\n environment: 0,\n legibility: 0,\n dimming: 0,\n ink: 1,\n presence: 0,\n adaptRadius: ADAPT_RADIUS,\n bodyDensity: 0.14,\n edgeLight: 0.35,\n film: 0,\n iridescence: 0,\n diffraction: 0,\n colorPickup: 0,\n ior: 1.5,\n thicknessDp: 16,\n iorSpread: 0,\n },\n 'v1': {\n blur: 12,\n refraction: 0.55,\n refractionScale: 1.14,\n bevelDp: 10,\n gatherRadiusDp: 40,\n fresnel: 0.5,\n fresnelPower: 3.2,\n specular: 0.42,\n specularPower: 58,\n dispersion: 0.22,\n tint: { r: 0.4, g: 0.4, b: 0.44 },\n tintStrength: 0.16,\n edgeDensity: 3.63,\n environment: 0,\n legibility: 0,\n dimming: 0,\n ink: 1,\n presence: 0,\n adaptRadius: ADAPT_RADIUS,\n bodyDensity: 0.14,\n edgeLight: 0.35,\n film: 0,\n iridescence: 0,\n diffraction: 0,\n colorPickup: 0,\n ior: 1.5,\n thicknessDp: 16,\n iorSpread: 0,\n },\n} as const satisfies Record<string, VireGlassOptics>;\n\nexport type LegacyOpticsName = keyof typeof LEGACY_OPTICS;\nexport const LEGACY_NAMES = Object.keys(LEGACY_OPTICS) as LegacyOpticsName[];\n\n/** Material presets: points in the space of CAUSES, not a set of ready-made effects. */\nexport const MATERIAL_PRESETS = {\n water: VIREGLASS_MATERIAL_V4,\n glass: { ...VIREGLASS_MATERIAL_V4, ior: 1.45, thickness: 25, bevel: 12.6, roughness: 0.17 },\n crystal: { ...VIREGLASS_MATERIAL_V4, ior: 1.7, thickness: 30, bevel: 16, roughness: 0.02 },\n frosted: { ...VIREGLASS_MATERIAL_V4, roughness: 0.55 },\n thick: { ...VIREGLASS_MATERIAL_V4, thickness: 48, bevel: 22 },\n thin: { ...VIREGLASS_MATERIAL_V4, thickness: 4, bevel: 3 },\n iridescent: { ...VIREGLASS_MATERIAL_V4, ior: 1.5, thickness: 8, bevel: 6, film: 620 },\n} as const satisfies Record<string, VireGlassMaterial>;\n\nexport type MaterialPresetName = keyof typeof MATERIAL_PRESETS;\nexport const PRESET_NAMES = Object.keys(MATERIAL_PRESETS) as MaterialPresetName[];\n\nexport const EFFECTS = [\n 'backdrop',\n 'blur',\n 'refraction',\n 'fresnel',\n 'bevel',\n 'specular',\n 'dispersion',\n 'tint',\n 'environment',\n 'legibility',\n 'interference',\n 'diffraction',\n] as const;\n\nexport type VireGlassEffect = (typeof EFFECTS)[number];\nexport type VireGlassToggles = Record<VireGlassEffect, boolean>;\n\nexport const ALL_EFFECTS_ON: VireGlassToggles = EFFECTS.reduce(\n (acc, e) => ({ ...acc, [e]: true }),\n {} as VireGlassToggles,\n);\n\n/**\n * A toggle zeroes out an EFFECT, it isn't a branch in the shader: the ON/OFF comparison runs on\n * one shader variant, otherwise two different programs would be getting compared. It operates on\n * optics, not on the material: a cause can't be zeroed out — it drags half a dozen effects along\n * with it. `backdrop` isn't handled by a toggle here: it decides whether to mount the BlurView at\n * all.\n */\nexport function applyToggles(\n optics: VireGlassOptics,\n toggles: Partial<VireGlassToggles> = {},\n): VireGlassOptics {\n const on = { ...ALL_EFFECTS_ON, ...toggles };\n const o = { ...optics, tint: { ...optics.tint } };\n if (!on.blur) o.blur = 0;\n if (!on.refraction) {\n o.refraction = 0;\n o.refractionScale = 1;\n o.ior = 1;\n }\n if (!on.fresnel) o.fresnel = 0;\n if (!on.bevel) o.bevelDp = 1;\n if (!on.specular) o.specular = 0;\n if (!on.dispersion) {\n o.dispersion = 0;\n o.iorSpread = 0;\n }\n if (!on.tint) o.tintStrength = 0;\n if (!on.environment) o.environment = 0;\n if (!on.legibility) {\n o.legibility = 0;\n // The dimming layer is the same answer to the legibility requirement, just for the\n // transparent variant.\n o.dimming = 0;\n }\n if (!on.interference) o.iridescence = 0;\n if (!on.diffraction) o.diffraction = 0;\n return o;\n}\n\n/** Order = the `u_debug` value in both shaders. Bench-only. */\nexport const DEBUG_MODES = [\n 'normal',\n 'sdf',\n 'mask',\n 'edge',\n 'fresnel',\n 'refraction',\n 'backdrop',\n 'specular',\n 'dispersion',\n 'normals',\n 'spectral',\n 'adapt',\n 'probe',\n] as const;\n\nexport type VireGlassDebugMode = (typeof DEBUG_MODES)[number];\n\nexport function debugIndex(mode: VireGlassDebugMode): number {\n return DEBUG_MODES.indexOf(mode);\n}\n","import { ACCENT, MORPH } from './law';\nimport {\n bevelDp,\n bevelFraction,\n halfMinDp,\n rimDp,\n shadowReachDp,\n surfacePadDp,\n thicknessDp,\n type VireGlassGeometry,\n} from './geometry';\nimport { LENS_SHADER } from './lens-shader';\nimport { neckToBridge } from './sdf';\nimport { debugIndex, type VireGlassDebugMode, type VireGlassOptics } from './material';\n\n/** A second shape joined to the element by a smooth union: the geometry that merges and growth\n * transitions are built on (a capsule drawn into a drop, then grown into its menu). Offsets from\n * the element's centre and sizes in dp. */\nexport type VireGlassMorph = {\n offsetX: number;\n offsetY: number;\n width: number;\n height: number;\n cornerRadius: number;\n /** Seam smoothing radius in dp; 0 turns the second shape off. */\n smoothing: number;\n};\n\n\n/** Surface response to a finger: the field around the touch point deforms, not the shape's\n * bounding box (`vgTouchWarp` in `sdf.ts`). Lengths in dp; `radius` = 0 turns the response off\n * entirely. */\nexport type VireGlassTouch = {\n x: number;\n y: number;\n pullX: number;\n pullY: number;\n press: number;\n radius: number;\n waveAmp: number;\n wavePhase: number;\n};\n\nconst NO_TOUCH: VireGlassTouch = {\n x: 0, y: 0, pullX: 0, pullY: 0, press: 0, radius: 0, waveAmp: 0, wavePhase: 0,\n};\n\nconst NO_MORPH = { offsetX: 0, offsetY: 0, width: 0, height: 0, cornerRadius: 0, smoothing: 0 };\n\n/** No progress. Negative rather than zero: zero is the start of the track, a legitimate value. */\nconst NO_PROGRESS = -1;\n\n/** Tinted glass for the primary action (M 16:08). Color is RGB 0…1. */\nexport type VireGlassAccent = { color: readonly [number, number, number]; amount?: number };\n\n/** Default tint fraction: the color reads, but content beneath it is still visible. */\nexport const ACCENT_AMOUNT = ACCENT.amount;\nconst NO_ACCENT = [0, 0, 0, 0] as const;\n\n\n\n/** Magnification at the flat center. Kept separate from the uniform channel: it's also used by\n * the fallback below Android 13, where there's no RuntimeShader and only an affine magnifier is\n * left. */\nexport const lensMagnify = (o: VireGlassOptics) => 1 + (o.refractionScale - 1) * o.refraction;\n\n/**\n * Lens uniforms. Assembled as ONE channel: name, size and values travel together, so a mismatch\n * with the shader is caught immediately, with the name right there in the log. There used to be a\n * separate Kotlin `Prop` per value — Expo swallows an unknown prop silently, and the lens simply\n * never turned on (docs/material-lab.md E-01, which is how refraction ended up disabled for an\n * entire phase).\n *\n * Everything with a dimension is converted to PIXELS right here. The native view is left with\n * only the uniforms it alone knows: its own size and its own place on screen.\n */\n// The lens's uniform names and sizes are always the same — the set is fixed. They're handed over\n// as ONE instance for the whole process: otherwise every frame of a drag would send three dozen\n// fresh strings to the native side, React can't tell \"unchanged\" from \"a new array\", and motion\n// goes stepwise. The values are the only thing that actually changes.\nconst shapes = new Map<number, { names: string[]; sizes: number[] }>();\n\nfunction channel(entries: [string, number | readonly number[]][]) {\n const uniformValues: number[] = [];\n let shape = shapes.get(entries.length);\n let same = shape !== undefined;\n for (let i = 0; i < entries.length; i += 1) {\n const [name, value] = entries[i];\n const v = typeof value === 'number' ? [value] : value;\n if (same && (shape!.names[i] !== name || shape!.sizes[i] !== v.length)) same = false;\n for (const x of v) uniformValues.push(x);\n }\n if (!same) {\n shape = {\n names: entries.map((e) => e[0]),\n sizes: entries.map((e) => (typeof e[1] === 'number' ? 1 : e[1].length)),\n };\n shapes.set(entries.length, shape);\n }\n return { uniformNames: shape!.names, uniformSizes: shape!.sizes, uniformValues };\n}\n\n/** Props for the native view. Besides the uniform channel, only what the view uses itself lives\n * here: the shader source and the visible glass's size (the probe rectangle is taken from it). */\nexport function toLensProps(\n optics: VireGlassOptics,\n geometry: VireGlassGeometry,\n density: number,\n options: {\n debug?: VireGlassDebugMode;\n morph?: VireGlassMorph;\n /** Third shape of the fused body; a zero size turns it off. */\n morph2?: VireGlassMorph;\n groupProbe?: number[];\n touch?: VireGlassTouch;\n /** Progress fraction, 0…1: to the left of the boundary the element is active. `undefined` means no progress. */\n progress?: number;\n /** Key light direction in screen space; defaults to the light at rest. */\n light?: readonly [number, number];\n /** 0…1: the lens builds up as the element appears — instead of a plain fade. */\n appear?: number;\n /** Tint for the primary action: the glass color and the fraction it lays over the content. */\n accent?: VireGlassAccent;\n } = {},\n) {\n const morph = options.morph ?? NO_MORPH;\n const morph2 = options.morph2 ?? NO_MORPH;\n const touch = options.touch ?? NO_TOUCH;\n // Backdrop estimate for the whole surface group. Travels through the same channel as the\n // material and applies AFTER the lens's own estimate — that is, it simply overrides it. It\n // can't travel as a separate prop: the lens view is wrapped in an animated component.\n const g = options.groupProbe;\n const group: [string, number | readonly number[]][] =\n g && g.length >= 9\n ? [\n ['u_probeLuma', g[0]],\n ['u_probeBusy', g[1]],\n ['u_probeRange', [g[2], g[3]]],\n ['u_probeSlope', [g[4], g[5]]],\n ['u_probe', [g[6], g[7], g[8]]],\n ]\n : [];\n const d = density;\n const halfW = (geometry.width * d) / 2;\n const halfH = (geometry.height * d) / 2;\n const halfMin = Math.min(halfW, halfH);\n\n return {\n shaderSource: LENS_SHADER,\n glassWidth: geometry.width,\n glassHeight: geometry.height,\n ...channel([\n ['u_halfSize', [halfW, halfH]],\n ['u_corner', Math.min(geometry.cornerRadius * d, halfMin)],\n ['u_bevel', Math.max(bevelDp(geometry, optics) * d, 1)],\n ['u_thick', thicknessDp(geometry, optics) * d],\n ['u_rim', rimDp(geometry, optics) * d],\n ['u_ior', optics.ior],\n ['u_iorSpread', optics.iorSpread],\n ['u_light', options.light ?? REST_LIGHT],\n ['u_appear', options.appear ?? 1],\n ['u_accent', options.accent ? [...options.accent.color, options.accent.amount ?? ACCENT_AMOUNT] : NO_ACCENT],\n // Haze from surface roughness. Lives in the same disc-gather pass as adaptive scattering,\n // and fades out toward the bevel: that's a different job there — bending the ray and\n // splitting it.\n ['u_frost', optics.blur * d],\n ['u_ink', optics.ink],\n ['u_legibility', optics.legibility],\n ['u_dim', optics.dimming],\n ['u_presence', optics.presence],\n ['u_adaptRadius', optics.adaptRadius * d],\n ['u_bodyDensity', optics.bodyDensity],\n ['u_edgeLight', optics.edgeLight],\n ['u_fresnel', optics.fresnel],\n ['u_specular', optics.specular],\n // The rim gathers light from the element's vicinity — this is a radius around the shape,\n // not its bevel.\n ['u_reflectReach', optics.gatherRadiusDp * d],\n ['u_film', optics.film],\n ['u_iridescence', optics.iridescence],\n ['u_diffraction', optics.diffraction],\n ['u_colorPickup', optics.colorPickup],\n ['u_morphOffset', [morph.offsetX * d, morph.offsetY * d]],\n ['u_morphHalf', [(morph.width * d) / 2, (morph.height * d) / 2]],\n ['u_morphCorner', morph.cornerRadius * d],\n ['u_morphK', morph.smoothing * d],\n ['u_morph2Offset', [morph2.offsetX * d, morph2.offsetY * d]],\n ['u_morph2Half', [(morph2.width * d) / 2, (morph2.height * d) / 2]],\n ['u_morph2Corner', morph2.cornerRadius * d],\n ['u_touch', [touch.x * d, touch.y * d]],\n ['u_pull', [touch.pullX * d, touch.pullY * d]],\n ['u_touchPress', touch.press],\n ['u_touchRadius', touch.radius * d],\n ['u_wave', [touch.waveAmp * d, touch.wavePhase]],\n ['u_progress', options.progress ?? NO_PROGRESS],\n ['u_debug', debugIndex(options.debug ?? 'normal')],\n ...group,\n ]),\n };\n}\n\n/** Static part of the surface uniforms. Dynamics (gesture, press, light) get mixed in inside the\n * component's worklet — the adapter must stay an ordinary function. */\nexport function toSurfaceUniforms(\n optics: VireGlassOptics,\n geometry: VireGlassGeometry,\n options: {\n debug?: VireGlassDebugMode;\n morph?: VireGlassMorph;\n /** Third shape of the fused body; a zero size turns it off. */\n morph2?: VireGlassMorph;\n dragLimit?: number;\n shadow?: number;\n /** Average ambient color around the element: light from it bleeds into the shadow (reference 219 @8:22). */\n ambient?: readonly [number, number, number];\n /** The lens draws the glass body — the surface is left with the highlight, shadow and icon. */\n bodyInLens?: boolean;\n touch?: VireGlassTouch;\n /** Progress fraction, 0…1: to the left of the boundary the element is active. `undefined` means no progress. */\n progress?: number;\n /** 0…1: the element is appearing — shadow and ink build up together with the lens. */\n appear?: number;\n /** 0 — the element sinks in under the finger, 1 — it rises into glass (reference §5). */\n lift?: number;\n } = {},\n) {\n const morph = options.morph ?? NO_MORPH;\n const morph2 = options.morph2 ?? NO_MORPH;\n const touch = options.touch ?? NO_TOUCH;\n const pad = surfacePadDp(geometry, options.dragLimit ?? 0, morph);\n return {\n u_center: [geometry.width / 2 + pad, geometry.height / 2 + pad],\n u_halfSize: [geometry.width / 2, geometry.height / 2],\n u_corner: Math.min(geometry.cornerRadius, halfMinDp(geometry)),\n u_bevel: bevelDp(geometry, optics),\n u_thickness: bevelFraction(geometry, optics),\n u_morphOffset: [morph.offsetX, morph.offsetY],\n u_morphHalf: [morph.width / 2, morph.height / 2],\n u_morphCorner: morph.cornerRadius,\n u_morphK: morph.smoothing,\n u_morph2Offset: [morph2.offsetX, morph2.offsetY],\n u_morph2Half: [morph2.width / 2, morph2.height / 2],\n u_morph2Corner: morph2.cornerRadius,\n u_edgeDensity: optics.edgeDensity,\n u_dispersion: optics.dispersion,\n u_refraction: optics.refraction,\n // Tint density is suppressed when the lens is drawing the body: painting it twice would\n // double the fill, and adaptation is impossible on the surface anyway — it can't see the\n // backdrop. The color itself stays: it drives absorption at the rim.\n u_tint: [\n optics.tint.r,\n optics.tint.g,\n optics.tint.b,\n options.bodyInLens ? 0 : optics.tintStrength,\n ],\n u_shadow: options.shadow ?? 1,\n // Neutral by default: without an ambient sample, the shadow stays exactly as it was.\n u_ambient: options.ambient ?? [0, 0, 0],\n u_shadowReach: shadowReachDp(geometry),\n u_touch: [touch.x, touch.y],\n u_pull: [touch.pullX, touch.pullY],\n u_touchPress: touch.press,\n u_touchRadius: touch.radius,\n u_wave: [touch.waveAmp, touch.wavePhase],\n u_presence: optics.presence,\n u_progress: options.progress ?? NO_PROGRESS,\n u_appear: options.appear ?? 1,\n // Zero by default: buttons sink in, the way every element behaved before this rule existed.\n u_lift: options.lift ?? 0,\n\n u_debug: debugIndex(options.debug ?? 'normal'),\n };\n}\n\n/**\n * Surface fields that are LENGTHS: the adapter's contract is specified in dp, and on the web each\n * one of them has to be multiplied by screen density. The list lives here rather than in the\n * renderer, where it used to be a dozen and a half manual multiplications in a row: a missed\n * field there was only caught by eye, and the miss isn't visible everywhere — at density 1,\n * multiplying is indistinguishable from not doing it.\n */\nexport type SurfaceUniforms = ReturnType<typeof toSurfaceUniforms>;\n\nexport const SURFACE_LENGTH_UNIFORMS = [\n 'u_halfSize',\n 'u_corner',\n 'u_bevel',\n 'u_morphOffset',\n 'u_morphHalf',\n 'u_morphCorner',\n 'u_morphK',\n 'u_morph2Offset',\n 'u_morph2Half',\n 'u_morph2Corner',\n 'u_shadowReach',\n 'u_touch',\n 'u_pull',\n 'u_touchRadius',\n // `satisfies`, not just `as const`: otherwise a typo in the name compiles silently and makes it\n // to runtime — the field with the garbage name gets NaN, and the real one is left un-multiplied.\n] as const satisfies readonly (keyof SurfaceUniforms)[];\n\n/**\n * Surface uniforms in device pixels. Android draws in dp and calls the adapter as-is; the web\n * needs this conversion. `u_center` isn't part of it: the renderer knows the element's center on\n * its own.\n */\nexport function toDeviceSurfaceUniforms(raw: SurfaceUniforms, density: number): SurfaceUniforms {\n const scaled: Record<string, unknown> = { ...raw };\n for (const key of SURFACE_LENGTH_UNIFORMS) {\n const value: unknown = scaled[key];\n scaled[key] = Array.isArray(value)\n ? (value as number[]).map((v) => v * density)\n : (value as number) * density;\n }\n // Of the wave, only the amplitude has a length; the phase is in turns and knows nothing about density.\n scaled.u_wave = [raw.u_wave[0] * density, raw.u_wave[1]];\n return scaled as SurfaceUniforms;\n}\n\n/** Key light direction in screen coordinates, for when the orientation response is off. */\nexport const REST_LIGHT: readonly [number, number] = [-0.577, -0.817];\n\n/** Uniforms the component mixes in inside the worklet (press, active, light direction) and via\n * the icon layer. Listed here so a test can check the shader contract's completeness without\n * importing the component itself (it pulls in Skia and doesn't come up in a node environment). */\nexport const DYNAMIC_UNIFORMS = ['u_press', 'u_active'] as const;\n\nexport const ICON_UNIFORMS = ['u_iconOn', 'u_iconScale', 'u_inkIdle', 'u_inkActive'] as const;\n\n/** App-colored overlay layer on the glass. The `u_overlay` sampler shares its contract with the\n * ink mask and is sampled at the SAME coordinate — otherwise deformation would carry them apart. */\nexport const OVERLAY_UNIFORMS = ['u_overlayOn'] as const;\n\n/**\n * Fusing two surfaces into one continuous medium.\n *\n * The second shape is described RELATIVE to the first one's center, because both live in the\n * same shader: the union has no \"two pieces of glass,\" just one body with two bulges — the mask,\n * the refraction and the rim are all computed from a single shared scene.\n *\n * `t` — how fused the shapes are: 0 turns the second shape off before any computation, 1 gives a\n * single shared medium. The seam's smoothing radius is a fraction of the smaller half-size: on\n * large elements the bridge has to be wider, otherwise the seam is left with a sharp corner,\n * which doesn't happen in a liquid.\n */\n\n\n/**\n * How wide the neck between two lobes has to be: enough to soften the seam on a large element, and\n * enough to actually SPAN whatever is between them. The second term is the one that was missing.\n */\nfunction neckFor(\n half: number,\n aw: number,\n ah: number,\n bw: number,\n bh: number,\n offsetX: number,\n offsetY: number,\n): number {\n // Surface-to-surface, measured between the bounding boxes. The rounded corners put the true\n // surfaces slightly further apart, so this errs toward a wider neck, which is the safe side.\n const gx = Math.abs(offsetX) - (aw + bw) / 2;\n const gy = Math.abs(offsetY) - (ah + bh) / 2;\n const gap = Math.hypot(Math.max(gx, 0), Math.max(gy, 0));\n // Capped at the narrowest of the two bodies. A neck wider than what it joins is not a neck, and\n // past that width the honest answer is that these are two things rather than one — a menu that\n // has finished leaving its button, two controls too far apart to be a segmented control. Being\n // separate is a legitimate state; being joined by a blob larger than either shape is not.\n const widest = Math.min(aw, ah, bw, bh);\n return Math.min(Math.max(half * MORPH.neck, neckToBridge(gap) * MORPH.fuse), widest);\n}\n\nexport function morphBetween(\n a: VireGlassGeometry,\n b: VireGlassGeometry,\n offsetX: number,\n offsetY: number,\n t: number,\n): VireGlassMorph | undefined {\n if (t <= 0) return undefined;\n const half = Math.min(halfMinDp(a), halfMinDp(b));\n return {\n offsetX,\n offsetY,\n width: b.width,\n height: b.height,\n cornerRadius: b.cornerRadius,\n smoothing: neckFor(half, a.width, a.height, b.width, b.height, offsetX, offsetY) * Math.min(t, 1),\n };\n}\n\n/**\n * MITOSIS: the second shape grows OUT OF the first and travels to its place, instead of appearing\n * beside it at full size. A menu coming out of its button, a control dividing into segments.\n *\n * `morphBetween` is the other half — two shapes that are both already where they belong, fusing\n * into one body — and run backwards it is meiosis, the neck thinning until it breaks. What neither\n * of them may do is pop: at any `t` above zero this returns one continuous body, because that is\n * the whole of what the reference says a material transition is.\n *\n * The choreography stays the host's: what the shapes MEAN is the app's business. How a silhouette\n * emerges from another silhouette is the material's, the same way the spring under a finger is.\n */\nexport function morphOutOf(\n source: VireGlassGeometry,\n target: VireGlassGeometry,\n offsetX: number,\n offsetY: number,\n t: number,\n): VireGlassMorph | undefined {\n if (t <= 0) return undefined;\n const k = Math.min(t, 1);\n // At t = 0 the lobe IS the parent, in the parent's place: the transition starts from one body and\n // never has a first frame in which a second one exists. The parent's own surface is what the neck\n // attaches to, so nothing has to be budded off to give it something to hold.\n const width = source.width + (target.width - source.width) * k;\n const height = source.height + (target.height - source.height) * k;\n const cornerRadius = source.cornerRadius + (target.cornerRadius - source.cornerRadius) * k;\n const half = Math.min(halfMinDp(source), Math.min(width, height) / 2);\n return {\n offsetX: offsetX * k,\n offsetY: offsetY * k,\n width,\n height,\n cornerRadius,\n // The lobe travels as it grows, so the span changes every frame and the neck is measured\n // against where the lobe actually is, not against where it will end up.\n smoothing: neckFor(half, source.width, source.height, width, height, offsetX * k, offsetY * k),\n };\n}\n","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/** Comments are prose, and the rewrites here are regexes over raw text: a comment mentioning\n * \"return\" became one, and a brace in a comment ended the entry point early. Newlines inside\n * block comments survive, so a compile error still points at the source line. */\nexport function stripComments(src: string): string {\n let out = '';\n let i = 0;\n while (i < src.length) {\n if (src.startsWith('//', i)) {\n const end = src.indexOf('\\n', i);\n i = end < 0 ? src.length : end;\n } else if (src.startsWith('/*', i)) {\n const end = src.indexOf('*/', i + 2);\n if (end < 0) throw new Error('vireglass/targets/glsl: unterminated block comment');\n const stop = end + 2;\n out += src.slice(i, stop).replace(/[^\\n]/g, '');\n i = stop;\n } else {\n out += src[i];\n i += 1;\n }\n }\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 once\n * comments are gone (`stripComments`).\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 = stripComments(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","// Low-level GL helpers for the pipeline: program compilation, textures, FBOs, the fullscreen\n// triangle. Knows nothing about VireGlass — a reusable layer on top of WebGL2.\n\n/** The same trick as in `scripts/check-glsl.mjs`: three vertices with no buffer, indexed by\n * `gl_VertexID`. One vertex shader works for EVERY pass of the pipeline, because each fragment\n * shader pulls its own coordinate from `gl_FragCoord`. */\nexport const FULLSCREEN_TRIANGLE_VERTEX_SOURCE = `#version 300 es\nconst vec2 VG_POS[3] = vec2[3](vec2(-1.0, -1.0), vec2(3.0, -1.0), vec2(-1.0, 3.0));\nvoid main() {\n gl_Position = vec4(VG_POS[gl_VertexID], 0.0, 1.0);\n}\n`;\n\nexport function compileShader(gl: WebGL2RenderingContext, type: number, source: string): WebGLShader {\n const shader = gl.createShader(type);\n if (!shader) throw new Error('vireglass/web: gl.createShader returned null');\n gl.shaderSource(shader, source);\n gl.compileShader(shader);\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n const log = gl.getShaderInfoLog(shader) ?? '(no log)';\n gl.deleteShader(shader);\n throw new Error(`vireglass/web: shader failed to compile:\\n${log}`);\n }\n return shader;\n}\n\nexport function createProgram(\n gl: WebGL2RenderingContext,\n vertexSource: string,\n fragmentSource: string,\n): WebGLProgram {\n const vertex = compileShader(gl, gl.VERTEX_SHADER, vertexSource);\n const fragment = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSource);\n const program = gl.createProgram();\n if (!program) throw new Error('vireglass/web: gl.createProgram returned null');\n gl.attachShader(program, vertex);\n gl.attachShader(program, fragment);\n gl.linkProgram(program);\n gl.deleteShader(vertex);\n gl.deleteShader(fragment);\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n const log = gl.getProgramInfoLog(program) ?? '(no log)';\n gl.deleteProgram(program);\n throw new Error(`vireglass/web: program failed to link:\\n${log}`);\n }\n return program;\n}\n\n/** Draws the fullscreen triangle with the current program. No attributes — the vertices are\n * baked into the shader, so even a VAO isn't needed. */\nexport function drawFullscreenTriangle(gl: WebGL2RenderingContext): void {\n gl.drawArrays(gl.TRIANGLES, 0, 3);\n}\n\nexport type TextureOptions = {\n width: number;\n height: number;\n /** RGBA8 by default; the probe needs float when it needs more than 8 bits of precision. */\n internalFormat?: number;\n format?: number;\n type?: number;\n /** CLAMP_TO_EDGE by default. A simulation with a periodic domain (a tiling pattern, a torus)\n * wants REPEAT on its own textures instead — applied to both S and T, there's no case here\n * that wants them to differ. */\n wrap?: number;\n};\n\n/**\n * General-purpose texture allocator: LINEAR on MIN and MAG, no mipmaps, CLAMP_TO_EDGE by default.\n * Used throughout `web/` for `contentTexture` and the probe's downsample buffer, and public so a\n * `VireGlassBackdropPass` can build its own textures the same way. LINEAR rather than NEAREST\n * because Skia's `.eval` is bilinear by default — NEAREST would break the disc gather into\n * pixel-stepped bands (spec §4); a pass with different needs overrides `internalFormat`/`format`/\n * `type`/`wrap` directly.\n */\nexport function createTexture(gl: WebGL2RenderingContext, options: TextureOptions): WebGLTexture {\n const texture = gl.createTexture();\n if (!texture) throw new Error('vireglass/web: gl.createTexture returned null');\n const internalFormat = options.internalFormat ?? gl.RGBA8;\n const format = options.format ?? gl.RGBA;\n const type = options.type ?? gl.UNSIGNED_BYTE;\n const wrap = options.wrap ?? gl.CLAMP_TO_EDGE;\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texImage2D(\n gl.TEXTURE_2D,\n 0,\n internalFormat,\n options.width,\n options.height,\n 0,\n format,\n type,\n null,\n );\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrap);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrap);\n gl.bindTexture(gl.TEXTURE_2D, null);\n return texture;\n}\n\nexport type UniformValue = number | readonly number[];\n\n/** One cache per program: `getUniformLocation` is a name lookup, and every piece re-sets every\n * uniform every frame. */\nexport function locationCache(gl: WebGL2RenderingContext, program: WebGLProgram) {\n const cache = new Map<string, WebGLUniformLocation | null>();\n return (name: string): WebGLUniformLocation | null => {\n let loc = cache.get(name);\n if (loc === undefined) {\n loc = gl.getUniformLocation(program, name);\n cache.set(name, loc);\n }\n return loc;\n };\n}\n\nexport function setUniform(\n gl: WebGL2RenderingContext,\n loc: WebGLUniformLocation | null,\n value: UniformValue,\n): void {\n if (!loc) return;\n if (typeof value === 'number') {\n gl.uniform1f(loc, value);\n return;\n }\n switch (value.length) {\n case 1:\n gl.uniform1f(loc, value[0]);\n break;\n case 2:\n gl.uniform2f(loc, value[0], value[1]);\n break;\n case 3:\n gl.uniform3f(loc, value[0], value[1], value[2]);\n break;\n case 4:\n gl.uniform4f(loc, value[0], value[1], value[2], value[3]);\n break;\n default:\n throw new Error(`vireglass/web: unsupported uniform size (${value.length})`);\n }\n}\n\nexport function createFramebuffer(gl: WebGL2RenderingContext, texture: WebGLTexture): WebGLFramebuffer {\n const fbo = gl.createFramebuffer();\n if (!fbo) throw new Error('vireglass/web: gl.createFramebuffer returned null');\n gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);\n gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);\n const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n if (status !== gl.FRAMEBUFFER_COMPLETE) {\n throw new Error(`vireglass/web: FBO incomplete, status ${status}`);\n }\n return fbo;\n}\n\n/** The one texture unit any pipeline pass needs here — a bind with no extra slot bookkeeping. */\nexport function bindTextureAt(\n gl: WebGL2RenderingContext,\n unit: number,\n texture: WebGLTexture,\n program: WebGLProgram,\n uniformName: string,\n): void {\n gl.activeTexture(gl.TEXTURE0 + unit);\n gl.bindTexture(gl.TEXTURE_2D, texture);\n const loc = gl.getUniformLocation(program, uniformName);\n gl.uniform1i(loc, unit);\n}\n","// GPU timing for `VireGlassRenderer.getLastGpuMs()`, isolated from the renderer so it can be\n// unit-tested against a fake: `EXT_disjoint_timer_query_webgl2` is unavailable in the headless\n// Chromium the shader gates run in, so nothing in this repository otherwise exercises the ring\n// logic at all.\n\n/** The two tokens the extension adds; everything else the timer needs is core WebGL2. */\nexport type GpuTimerExtension = {\n readonly TIME_ELAPSED_EXT: number;\n readonly GPU_DISJOINT_EXT: number;\n};\n\n/** The slice of WebGL2 this module needs, kept narrow so a test can fake it without a real GL\n * context. A `WebGL2RenderingContext` satisfies this structurally — no cast needed at the call\n * site in `renderer.ts`. */\nexport type GpuTimerContext = {\n readonly QUERY_RESULT_AVAILABLE: number;\n readonly QUERY_RESULT: number;\n getExtension(name: string): unknown;\n createQuery(): WebGLQuery | null;\n deleteQuery(query: WebGLQuery): void;\n beginQuery(target: number, query: WebGLQuery): void;\n endQuery(target: number): void;\n getQueryParameter(query: WebGLQuery, pname: number): unknown;\n getParameter(pname: number): unknown;\n};\n\n/** Queries in flight at once. `EXT_disjoint_timer_query_webgl2` results arrive asynchronously,\n * often a few frames late, so one query per frame would mean starting a new query before the\n * previous one's result is back — undefined by spec. */\nexport const GPU_TIMER_RING_SIZE = 3;\n\ntype TimerSlot = { query: WebGLQuery; pending: boolean };\n\nexport type GpuTimer = {\n /** Runs `fn` as one timed sample. Always calls the matching `endQuery` even if `fn` throws, so\n * a frame that fails partway through (a broken backdrop pass, a validation error) never leaves\n * a query open for the next call to trip over. */\n time<T>(fn: () => T): T;\n /** `null` without the extension or while the ring hasn't produced a non-disjoint result yet. */\n getLastMs(): number | null;\n destroy(): void;\n};\n\n/** Missing the extension (most headless/CI setups, and every browser but Chromium-family in\n * practice) degrades every method here to a no-op / `null` — never an error. */\nexport function createGpuTimer(gl: GpuTimerContext): GpuTimer {\n const ext = gl.getExtension('EXT_disjoint_timer_query_webgl2') as GpuTimerExtension | null;\n const slots: TimerSlot[] = ext\n ? Array.from({ length: GPU_TIMER_RING_SIZE }, () => ({\n query: gl.createQuery() as WebGLQuery,\n pending: false,\n }))\n : [];\n let cursor = 0;\n let active: TimerSlot | null = null;\n let lastMs: number | null = null;\n\n function poll(): void {\n if (!ext) return;\n for (const slot of slots) {\n if (!slot.pending) continue;\n if (!gl.getQueryParameter(slot.query, gl.QUERY_RESULT_AVAILABLE)) continue;\n slot.pending = false;\n // A disjoint event (e.g. the GPU clock changed) invalidates every query since the last\n // check — the sample is discarded, not reported as zero or stale.\n if (gl.getParameter(ext.GPU_DISJOINT_EXT)) continue;\n const ns = gl.getQueryParameter(slot.query, gl.QUERY_RESULT) as number;\n lastMs = ns / 1e6;\n }\n }\n\n function begin(): void {\n active = null;\n if (!ext || slots.length === 0) return;\n poll();\n const slot = slots[cursor];\n // The ring hasn't caught up (this slot's previous result isn't back yet) — skip timing this\n // frame rather than reuse a query that's still in flight.\n if (slot.pending) return;\n gl.beginQuery(ext.TIME_ELAPSED_EXT, slot.query);\n active = slot;\n }\n\n function end(): void {\n if (!ext || !active) return;\n gl.endQuery(ext.TIME_ELAPSED_EXT);\n active.pending = true;\n cursor = (cursor + 1) % slots.length;\n active = null;\n }\n\n function time<T>(fn: () => T): T {\n begin();\n try {\n return fn();\n } finally {\n end();\n }\n }\n\n function getLastMs(): number | null {\n poll();\n return lastMs;\n }\n\n function destroy(): void {\n for (const slot of slots) gl.deleteQuery(slot.query);\n }\n\n return { time, getLastMs, destroy };\n}\n","// Backdrop lightness probe under the lens — the web counterpart to the native capture in\n// ImageReader (`GlassLensView.kt`); see the web-core design spec and its Phase 3 plan.\n//\n// Downsampling the scene into a 48×96 grid runs as a GL pass (sampling `content` in the\n// downsample program, see `renderer.ts`), while READING the result happens here, through a PBO\n// with `fenceSync`: a synchronous `readPixels` stalls the pipeline, and a naive port stutters\n// while dragging sliders.\n//\n// Lagging by a frame or two isn't a bug, it's part of the contract (per the Phase 3 plan): we\n// read whatever the GPU already finished by the previous frame, rather than waiting on the\n// current one.\n\nimport { bindTextureAt, createFramebuffer, createProgram, drawFullscreenTriangle } from './gl';\n\nexport const PROBE_GRID_WIDTH = 48;\nexport const PROBE_GRID_HEIGHT = 96;\n\n/** Stats for the lens rectangle from a single probe sample. `null` until the first ready frame\n * has come in — the shader falls back to `u_probeLuma = -1` in that case (see `lens-shader.ts`). */\nexport type ProbeStats = {\n luma: number;\n busy: number;\n lo: number;\n hi: number;\n slopeX: number;\n slopeY: number;\n r: number;\n g: number;\n b: number;\n};\n\nconst DOWNSAMPLE_FRAGMENT_SOURCE = `#version 300 es\nprecision highp float;\nout vec4 fragColor;\nuniform sampler2D u_content;\nuniform vec2 u_contentSize;\nuniform vec2 u_gridSize;\n\n// Cell → content UV mapping. Does NOT correspond to the canvas's visible top/bottom — this is an\n// internal pass, its buffer is never displayed, only read back on the CPU with the same formula\n// (see rectStats below). It doesn't need to match the blit pass's orientation.\nvoid main() {\n vec2 uv = gl_FragCoord.xy / u_gridSize;\n vec3 sum = vec3(0.0);\n const int N = 5;\n vec2 block = u_contentSize / u_gridSize;\n vec2 origin = uv * u_contentSize;\n for (int y = 0; y < N; y++) {\n for (int x = 0; x < N; x++) {\n vec2 p = origin + (vec2(float(x), float(y)) + 0.5) * (block / float(N));\n sum += texture(u_content, p / u_contentSize).rgb;\n }\n }\n fragColor = vec4(sum / float(N * N), 1.0);\n}\n`;\n\ntype PboSlot = {\n buffer: WebGLBuffer;\n sync: WebGLSync | null;\n pending: boolean;\n};\n\nexport type ProbeRect = { centerX: number; centerY: number; halfWidth: number; halfHeight: number };\n\nexport type Probe = {\n /** Renders a downsample of the current `contentTexture` into its own FBO and advances the PBO\n * read pipeline. Returns stats for the lens rectangle — lagging by a frame or two, or `null`\n * until a reader is ready. */\n sample(\n contentTexture: WebGLTexture,\n contentWidth: number,\n contentHeight: number,\n rect: ProbeRect,\n ): ProbeStats | null;\n /** Stats for another rectangle from the ALREADY-captured grid — no new pass, no new read. */\n statsFor(contentWidth: number, contentHeight: number, rect: ProbeRect): ProbeStats | null;\n destroy(): void;\n};\n\n/** RGBA32F gives the probe sub-pixel averaging precision (spec §5: \"where the web has the edge\")\n * — without it, the sum of 25 eight-bit samples per cell gets requantized back to 8 bits, and a\n * near-black gradient slice bands right at this step. Without `EXT_color_buffer_float` we fall\n * back to RGBA8 — renders everywhere, just coarser. */\nfunction pickDownsampleFormat(\n gl: WebGL2RenderingContext,\n): { internalFormat: number; type: number; floatPrecision: boolean } {\n const hasFloat = gl.getExtension('EXT_color_buffer_float') !== null;\n return hasFloat\n ? { internalFormat: gl.RGBA32F, type: gl.FLOAT, floatPrecision: true }\n : { internalFormat: gl.RGBA8, type: gl.UNSIGNED_BYTE, floatPrecision: false };\n}\n\nfunction luma(r: number, g: number, b: number): number {\n return 0.2126 * r + 0.7152 * g + 0.0722 * b;\n}\n\n/**\n * Rectangle stats from the 48×96 buffer. Buffer row `r` is `(r+0.5)/H` by the same formula as in\n * `DOWNSAMPLE_FRAGMENT_SOURCE`; here that same formula is inverted, so the buffer's orientation\n * (up/down) doesn't matter — only symmetry between reading and writing does.\n */\ntype DownsampleBuffer = Float32Array | Uint8Array;\n\nfunction rectStats(\n buffer: DownsampleBuffer,\n floatPrecision: boolean,\n gridW: number,\n gridH: number,\n contentWidth: number,\n contentHeight: number,\n rect: ProbeRect,\n): ProbeStats | null {\n const x0 = rect.centerX - rect.halfWidth;\n const x1 = rect.centerX + rect.halfWidth;\n const y0 = rect.centerY - rect.halfHeight;\n const y1 = rect.centerY + rect.halfHeight;\n\n const lumas: number[] = [];\n const nx: number[] = [];\n const ny: number[] = [];\n let sr = 0;\n let sg = 0;\n let sb = 0;\n\n const norm = floatPrecision ? 1 : 255;\n for (let row = 0; row < gridH; row++) {\n const cy = ((row + 0.5) / gridH) * contentHeight;\n if (cy < y0 || cy > y1) continue;\n for (let col = 0; col < gridW; col++) {\n const cx = ((col + 0.5) / gridW) * contentWidth;\n if (cx < x0 || cx > x1) continue;\n const i = (row * gridW + col) * 4;\n const r = buffer[i] / norm;\n const g = buffer[i + 1] / norm;\n const b = buffer[i + 2] / norm;\n lumas.push(luma(r, g, b));\n nx.push((cx - rect.centerX) / Math.max(rect.halfWidth, 1));\n ny.push((cy - rect.centerY) / Math.max(rect.halfHeight, 1));\n sr += r;\n sg += g;\n sb += b;\n }\n }\n const n = lumas.length;\n if (n === 0) return null;\n\n let mean = 0;\n for (const v of lumas) mean += v;\n mean /= n;\n\n let busy = 0;\n for (const v of lumas) busy += Math.abs(v - mean);\n busy = (busy / n) * 2;\n\n const sorted = [...lumas].sort((a, b) => a - b);\n const percentile = (p: number) => {\n const idx = p * (n - 1);\n const lo = Math.floor(idx);\n const hi = Math.ceil(idx);\n return sorted[lo] + (sorted[hi] - sorted[lo]) * (idx - lo);\n };\n\n const slope = (xs: number[]) => {\n let mx = 0;\n for (const v of xs) mx += v;\n mx /= n;\n let sxx = 0;\n let sxl = 0;\n for (let i = 0; i < n; i++) {\n const dx = xs[i] - mx;\n sxx += dx * dx;\n sxl += dx * (lumas[i] - mean);\n }\n return sxx > 1e-6 ? sxl / sxx : 0;\n };\n\n return {\n luma: mean,\n busy: Math.min(busy, 1),\n lo: percentile(0.1),\n hi: percentile(0.9),\n slopeX: slope(nx),\n slopeY: slope(ny),\n r: sr / n,\n g: sg / n,\n b: sb / n,\n };\n}\n\nexport function createProbe(gl: WebGL2RenderingContext, vertexSource: string): Probe {\n const program = createProgram(gl, vertexSource, DOWNSAMPLE_FRAGMENT_SOURCE);\n const format = pickDownsampleFormat(gl);\n\n const texture = gl.createTexture();\n if (!texture) throw new Error('vireglass/web/probe: gl.createTexture returned null');\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texImage2D(\n gl.TEXTURE_2D,\n 0,\n format.internalFormat,\n PROBE_GRID_WIDTH,\n PROBE_GRID_HEIGHT,\n 0,\n gl.RGBA,\n format.type,\n null,\n );\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.bindTexture(gl.TEXTURE_2D, null);\n const fbo = createFramebuffer(gl, texture);\n\n const byteSize = PROBE_GRID_WIDTH * PROBE_GRID_HEIGHT * 4 * (format.floatPrecision ? 4 : 1);\n const slots: PboSlot[] = [0, 1].map(() => {\n const buffer = gl.createBuffer();\n if (!buffer) throw new Error('vireglass/web/probe: gl.createBuffer returned null');\n gl.bindBuffer(gl.PIXEL_PACK_BUFFER, buffer);\n gl.bufferData(gl.PIXEL_PACK_BUFFER, byteSize, gl.STREAM_READ);\n gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);\n return { buffer, sync: null, pending: false };\n });\n let cursor = 0;\n let latestRaw: DownsampleBuffer | null = null;\n\n const contentLoc = gl.getUniformLocation(program, 'u_content');\n const contentSizeLoc = gl.getUniformLocation(program, 'u_contentSize');\n const gridSizeLoc = gl.getUniformLocation(program, 'u_gridSize');\n\n function pollReady(slot: PboSlot): void {\n if (!slot.pending || !slot.sync) return;\n // The flag flushes the command queue, otherwise a fence placed after readPixels might never\n // fire at all. The wait itself is zero. WAIT_FAILED isn't \"ready\": without a separate branch\n // for it, execution fell through and read garbage out of the PBO.\n const status = gl.clientWaitSync(slot.sync, gl.SYNC_FLUSH_COMMANDS_BIT, 0);\n if (status === gl.TIMEOUT_EXPIRED || status === gl.WAIT_FAILED) return;\n gl.deleteSync(slot.sync);\n slot.sync = null;\n slot.pending = false;\n gl.bindBuffer(gl.PIXEL_PACK_BUFFER, slot.buffer);\n const out: DownsampleBuffer = format.floatPrecision\n ? new Float32Array(PROBE_GRID_WIDTH * PROBE_GRID_HEIGHT * 4)\n : new Uint8Array(PROBE_GRID_WIDTH * PROBE_GRID_HEIGHT * 4);\n gl.getBufferSubData(gl.PIXEL_PACK_BUFFER, 0, out);\n gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);\n latestRaw = out;\n }\n\n function sample(\n contentTexture: WebGLTexture,\n contentWidth: number,\n contentHeight: number,\n rect: ProbeRect,\n ): ProbeStats | null {\n // Collect finished reads from PAST frames before requesting a new one.\n for (const slot of slots) pollReady(slot);\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);\n gl.viewport(0, 0, PROBE_GRID_WIDTH, PROBE_GRID_HEIGHT);\n gl.useProgram(program);\n bindTextureAt(gl, 0, contentTexture, program, 'u_content');\n gl.uniform1i(contentLoc, 0);\n gl.uniform2f(contentSizeLoc, contentWidth, contentHeight);\n gl.uniform2f(gridSizeLoc, PROBE_GRID_WIDTH, PROBE_GRID_HEIGHT);\n gl.disable(gl.BLEND);\n drawFullscreenTriangle(gl);\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n\n const slot = slots[cursor];\n cursor = (cursor + 1) % slots.length;\n if (!slot.pending) {\n gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo);\n gl.bindBuffer(gl.PIXEL_PACK_BUFFER, slot.buffer);\n gl.readPixels(0, 0, PROBE_GRID_WIDTH, PROBE_GRID_HEIGHT, gl.RGBA, format.type, 0);\n gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);\n gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);\n slot.sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);\n slot.pending = true;\n }\n\n return statsFor(contentWidth, contentHeight, rect);\n }\n\n // The downsample is one per frame, but a frame can have many elements: each needs stats for\n // ITS OWN rectangle out of the same grid, not its own pass and its own PBO read.\n function statsFor(contentWidth: number, contentHeight: number, rect: ProbeRect): ProbeStats | null {\n if (!latestRaw) return null;\n return rectStats(\n latestRaw,\n format.floatPrecision,\n PROBE_GRID_WIDTH,\n PROBE_GRID_HEIGHT,\n contentWidth,\n contentHeight,\n rect,\n );\n }\n\n function destroy(): void {\n for (const slot of slots) {\n if (slot.sync) gl.deleteSync(slot.sync);\n gl.deleteBuffer(slot.buffer);\n }\n gl.deleteFramebuffer(fbo);\n gl.deleteTexture(texture);\n gl.deleteProgram(program);\n }\n\n return { sample, statsFor, destroy };\n}\n","// The web lab's WebGL2 pipeline (per the Phase 3 plan; web-core spec §5). Four passes, and the\n// ordering and layer split deliberately mirror Android — the lens and the surface are NOT\n// collapsed into one pass, otherwise the lab stops predicting the phone (there they're two\n// separate layers):\n//\n// 1. backdrop — fills the `content` texture one of two ways: `scene` rasterizes\n// into an offscreen 2D canvas (text and cover art are easier there\n// than in GL) and uploads it via `texSubImage2D`; `backdrop` draws\n// straight into `content` with its own GPU pass instead, no canvas,\n// no readback (see `VireGlassBackdropPass`). Either way the texture\n// plays the role of the spec's FBO(scene): later passes only SAMPLE\n// it, never redraw it.\n// 2. blit backdrop to screen — `content` is drawn as the backing; without it the screen would\n// show only the element, and the material is compared against\n// exactly what's around it.\n// 3. downsample (`probe.ts`) — stats for the lens rectangle, see there.\n// 4. lens (`toGLSL(LENS_SHADER)`) — samples `content`, draws over the backdrop.\n// 5. surface (`toGLSL(SURFACE_SHADER)`) — bevel and highlight over the lens.\n//\n// UNITS. Everything is device-px, one coordinate space for the whole canvas (web-core spec\n// §4.2): `toLensProps` already multiplies by `density`; `toSurfaceUniforms` doesn't and\n// shouldn't (its contract is shared with Android), so its result gets run through\n// `toDeviceSurfaceUniforms`.\n//\n// SHARED CENTER. On Android the lens and the surface live in THEIR OWN local spaces (different\n// `lensPadDp`/`surfacePadDp` margins), and concentricity is a convention the component\n// maintains. Here both shaders get screen coordinates directly (`gl_FragCoord`), so the only way\n// to align them is to pass the exact SAME `u_center` in canvas pixels to both: the lens gets it\n// as-is (a native uniform), the surface gets it substituted in place of the adapter's own value\n// (which is only meaningful in its own local canvas, which doesn't exist here).\nimport {\n DYNAMIC_UNIFORMS,\n ICON_UNIFORMS,\n OVERLAY_UNIFORMS,\n toLensProps,\n toDeviceSurfaceUniforms,\n toSurfaceUniforms,\n type VireGlassAccent,\n type VireGlassMorph,\n type VireGlassTouch,\n} from '../adapters';\nimport { lensPadDp, shadowOpacity, surfacePadDp, type VireGlassGeometry } from '../geometry';\nimport { LENS_SHADER } from '../lens-shader';\nimport type { VireGlassDebugMode, VireGlassOptics } from '../material';\nimport { SURFACE_SHADER } from '../surface-shader';\nimport { toGLSL } from '../targets/glsl';\nimport {\n bindTextureAt,\n createFramebuffer,\n createProgram,\n createTexture,\n drawFullscreenTriangle,\n FULLSCREEN_TRIANGLE_VERTEX_SOURCE,\n locationCache,\n setUniform,\n type UniformValue,\n} from './gl';\nimport { createGpuTimer } from './gpu-timer';\nimport { createProbe, type ProbeStats } from './probe';\n\n/** The scene draws itself with an offset: the lenses in the frame stay put while the canvas\n * underneath them slides — only this way can you actually see what refraction does to whatever\n * passes under the element. Offset in device-px. */\nexport type VireGlassSceneDrawer = (\n ctx: CanvasRenderingContext2D,\n width: number,\n height: number,\n offsetX: number,\n offsetY: number,\n /** Pixels per dp — the same value used for elements. The canvas and the glass must live at the\n * same scale: as a fraction of canvas width, a cell on desktop came out five times larger than\n * on a phone, and an element of the same size looked completely different on the lab bench\n * than on a device. */\n density: number,\n) => void;\n\n/** The target a `VireGlassBackdropPass` must finish its frame in — the FBO wraps `contentTexture`\n * itself, so drawing into it needs no readback: the very texture the lens samples gets the pixels\n * directly. */\nexport type VireGlassBackdropTarget = {\n /** Owned by the renderer — the FBO wrapping `contentTexture` itself, recreated on `resize` and\n * freed in `destroy`. The pass must not delete it, and must not hold onto it past the call:\n * the object may be gone or pointing at a different texture by the next `render()`. */\n framebuffer: WebGLFramebuffer;\n width: number;\n height: number;\n};\n\n/**\n * Alternative to `scene`: draws the backdrop with a GPU pass, in this same WebGL2 context, straight\n * into the texture the lens samples — no offscreen 2D canvas, no `texSubImage2D` readback. The\n * renderer doesn't know or care what's drawn; a fluid sim, a video frame, anything that ends up as\n * pixels.\n *\n * ORIENTATION CONTRACT. `contentTexture` carries the same layout the `scene` path has always\n * produced: uploaded from a 2D canvas without a Y flip, so texel row 0 (`v≈0`) is the scene's TOP\n * row (see `BLIT_FRAGMENT_SOURCE` below) — and that row sits at `gl_FragCoord.y = 0` in this FBO,\n * the window-coordinate BOTTOM, not the top. A pass that copies an ordinary top-row-first image\n * (one loaded the normal way, via `texImage2D` from a canvas or an `<img>`) straight across with\n * `texture(src, gl_FragCoord.xy / vec2(target.width, target.height))` reproduces it correctly,\n * with no flip of its own. A pass that instead treats INCREASING y as its own \"up\" — the natural\n * convention for a physical simulation — must flip before this final write, or its top ends up\n * stored as the scene's bottom and appears upside down once the lens samples it.\n *\n * The pass may render through as many of its own FBOs first as it likes (a multi-step simulation)\n * and leave the GL context in whatever state suits it; it must finish by drawing into\n * `target.framebuffer` at `viewport(0, 0, target.width, target.height)`. The renderer puts back\n * every piece of state its own remaining steps depend on right after — framebuffer, viewport,\n * blend, scissor, depth/stencil/cull, the vertex array binding, the active texture unit, and the\n * `UNPACK_*` pixel-store flags used to upload `iconMask`/`colorLayer` — so the pass is free to\n * change any of it without arranging its own cleanup.\n */\nexport type VireGlassBackdropPass = (\n gl: WebGL2RenderingContext,\n target: VireGlassBackdropTarget,\n) => void;\n\n/** One glass element in the frame. Each has its own material — that's how they're compared side by side. */\nexport type VireGlassPiece = {\n optics: VireGlassOptics;\n geometry: VireGlassGeometry;\n /** Element center in CANVAS PIXELS (device-px). Shared between the lens and the surface. */\n centerX: number;\n centerY: number;\n morph?: VireGlassMorph;\n /** Third shape of the same body: breaking the element into pieces uses two bridges. */\n morph2?: VireGlassMorph;\n /** Local finger response: touch point, drag, press, wave. */\n touch?: VireGlassTouch;\n /** Press response, 0…1 — the highlight blooms, the body thickens slightly. */\n press?: number;\n /** Element under the finger, 0…1 — a stronger highlight and rim light. */\n active?: number;\n /** Played fraction, 0…1: an active state expressed as a field. Omitted means no progress. */\n progress?: number;\n /** Whether to draw the icon from the frame's shared mask on this element (see `iconMask` in the frame options). */\n icon?: boolean;\n /** Whether to draw the frame's colored layer on this element (see `colorLayer`). */\n overlay?: boolean;\n /** Icon color at rest and in the active state, RGBA 0…1. */\n inkIdle?: readonly number[];\n inkActive?: readonly number[];\n /** Key light direction in screen space; defaults to the light at rest. */\n light?: readonly [number, number];\n /** 0…1: the element appearing as the lens builds up (M 2:55). */\n appear?: number;\n /** 0 — the element sinks in under the finger, 1 — it rises into glass (reference §5). */\n lift?: number;\n /** Tint for the primary action — colored glass, not a fill. */\n accent?: VireGlassAccent;\n};\n\nexport type VireGlassRenderOptions = {\n /** `devicePixelRatio` — passed as an argument rather than read globally, so the renderer stays\n * testable and isn't tied to a specific window. */\n density: number;\n debug: VireGlassDebugMode;\n pieces: readonly VireGlassPiece[];\n /** CPU-side backdrop: draws into an offscreen 2D canvas, uploaded via `texSubImage2D`. Exactly\n * one of `scene`/`backdrop` is required — `render()` throws if both or neither are given — see\n * `backdrop` for the GPU alternative. */\n scene?: VireGlassSceneDrawer;\n /** GPU-side backdrop: draws straight into the texture the lens samples, in this context, no\n * readback. Exactly one of `scene`/`backdrop` is required — see `VireGlassBackdropPass` for the\n * orientation contract it must honor. */\n backdrop?: VireGlassBackdropPass | null;\n /** Canvas offset under stationary elements, device-px. */\n offsetX?: number;\n offsetY?: number;\n /**\n * Ink mask the size of the canvas, screen coordinates, read from the GREEN channel. White on\n * BLACK: antialiasing has to live in color, not alpha, otherwise green holds at one all the way\n * to the edge inside a stroke and the icon's edges tear.\n *\n * The mask does NOT carry and must not carry a calm backing under the ink: that's a property of\n * the material itself (`legibility`), the same across the whole element. While the app used to\n * supply that backing, one element had it and another didn't, with nothing to explain the\n * difference.\n */\n iconMask?: TexImageSource | null;\n /**\n * App-colored layer the size of the canvas, screen coordinates: cover art, a thumbnail —\n * anything the app puts ON the glass in color. Separate from the ink mask, because that one\n * carries a single channel colored by polarity, while this one has its own color.\n *\n * Drawn INSIDE the material, at the same coordinate as the ink: only this way does the\n * surface's deformation carry them together. As a separate layer on top of the glass, text used\n * to shake on press while the cover art stayed put.\n */\n colorLayer?: TexImageSource | null;\n};\n\nexport type VireGlassRenderResult = {\n /** One backdrop sample per element, in the same order as `pieces`. */\n probes: readonly (ProbeStats | null)[];\n};\n\nexport type VireGlassRenderer = {\n resize(widthPx: number, heightPx: number): void;\n render(options: VireGlassRenderOptions): VireGlassRenderResult;\n /** GPU time of the last `render()` call, milliseconds, via\n * `EXT_disjoint_timer_query_webgl2`. `null` when the extension is unavailable or the sample\n * came back disjoint — never an error either way. */\n getLastGpuMs(): number | null;\n destroy(): void;\n};\n\nconst BLIT_FRAGMENT_SOURCE = `#version 300 es\nprecision highp float;\nout vec4 fragColor;\nuniform sampler2D u_content;\nuniform vec2 u_resolution;\nvoid main() {\n // The same Y flip the transpiler does for the lens/surface (targets/glsl.ts): content is\n // uploaded from the 2D canvas WITHOUT flipping, so its V=0 is the scene's top row. This keeps\n // the same order here, otherwise the backdrop and what the lens refracts through it drift\n // apart vertically.\n vec2 uv = gl_FragCoord.xy / u_resolution;\n uv.y = 1.0 - uv.y;\n fragColor = texture(u_content, uv);\n}\n`;\n\n/** The material arrives as ONE channel (web-core spec §4, \"Uniform contract\"): name/size/value\n * as three parallel arrays — exactly the layout that used to get lost with Android Props. */\nfunction applyChannel(\n gl: WebGL2RenderingContext,\n get: (name: string) => WebGLUniformLocation | null,\n names: readonly string[],\n sizes: readonly number[],\n values: readonly number[],\n): void {\n let cursor = 0;\n for (let i = 0; i < names.length; i += 1) {\n const size = sizes[i];\n setUniform(gl, get(names[i]), values.slice(cursor, cursor + size));\n cursor += size;\n }\n}\n\nfunction applyObject(\n gl: WebGL2RenderingContext,\n get: (name: string) => WebGLUniformLocation | null,\n values: Record<string, UniformValue>,\n): void {\n for (const name of Object.keys(values)) setUniform(gl, get(name), values[name]);\n}\n\nexport function createVireGlassRenderer(canvas: HTMLCanvasElement): VireGlassRenderer {\n const context = canvas.getContext('webgl2', {\n preserveDrawingBuffer: true,\n alpha: false,\n antialias: false,\n });\n if (!context) throw new Error('vireglass/web: WebGL2 unavailable');\n // Explicit non-nullable typing — otherwise the `!context` narrowing doesn't survive the\n // closures of the nested `function resize/render/destroy` below, and tsc goes back to treating\n // `gl` as `WebGL2RenderingContext | null`.\n const gl: WebGL2RenderingContext = context;\n\n const blitProgram = createProgram(gl, FULLSCREEN_TRIANGLE_VERTEX_SOURCE, BLIT_FRAGMENT_SOURCE);\n const lensProgram = createProgram(gl, FULLSCREEN_TRIANGLE_VERTEX_SOURCE, toGLSL(LENS_SHADER));\n const surfaceProgram = createProgram(\n gl,\n FULLSCREEN_TRIANGLE_VERTEX_SOURCE,\n toGLSL(SURFACE_SHADER),\n );\n const probe = createProbe(gl, FULLSCREEN_TRIANGLE_VERTEX_SOURCE);\n\n const blitLoc = locationCache(gl, blitProgram);\n const lensLoc = locationCache(gl, lensProgram);\n const surfaceLoc = locationCache(gl, surfaceProgram);\n\n let width = canvas.width;\n let height = canvas.height;\n\n let sceneCanvas = document.createElement('canvas');\n let sceneCtx = sceneCanvas.getContext('2d');\n if (!sceneCtx) throw new Error('vireglass/web: scene 2D context unavailable');\n\n // Smoothed backdrop estimate per element: keyed by its position in the pieces list.\n const settled = new Map<number, ProbeStats>();\n /** Fraction of the new sample per frame. Same magnitude as SETTLE on Android. */\n const SETTLE = 0.12;\n\n function settleStats(index: number, fresh: ProbeStats): ProbeStats {\n const prev = settled.get(index);\n if (!prev) {\n settled.set(index, fresh);\n return fresh;\n }\n const mix = (a: number, b: number) => a + (b - a) * SETTLE;\n const next: ProbeStats = {\n luma: mix(prev.luma, fresh.luma),\n busy: mix(prev.busy, fresh.busy),\n lo: mix(prev.lo, fresh.lo),\n hi: mix(prev.hi, fresh.hi),\n slopeX: mix(prev.slopeX, fresh.slopeX),\n slopeY: mix(prev.slopeY, fresh.slopeY),\n r: mix(prev.r, fresh.r),\n g: mix(prev.g, fresh.g),\n b: mix(prev.b, fresh.b),\n };\n settled.set(index, next);\n return next;\n }\n\n let contentTexture = createTexture(gl, {\n width: Math.max(width, 1),\n height: Math.max(height, 1),\n });\n\n // FBO for the `backdrop` path, wrapping `contentTexture` itself — lazy, so a `scene`-only\n // caller never pays for it. Stale after `resize` (it points at a texture that's about to be\n // deleted); dropped there and rebuilt against the new one on next use.\n let backdropFbo: WebGLFramebuffer | null = null;\n function ensureBackdropFbo(): WebGLFramebuffer {\n if (!backdropFbo) backdropFbo = createFramebuffer(gl, contentTexture);\n return backdropFbo;\n }\n\n // GPU timing for `getLastGpuMs()` — the ring buffer and disjoint handling live in\n // `gpu-timer.ts`, isolated so they can be unit-tested against a fake (this extension is\n // unavailable in the headless Chromium the shader gates run in).\n const gpuTimer = createGpuTimer(gl);\n\n // The icon mask is ONE per frame, the size of the canvas. The shader samples it at the pixel's\n // screen coordinate (`u_center + p` is exactly `xy`), so each element's icon is simply drawn\n // into the mask at its own spot: no separate texture per button is needed here.\n const iconTexture = createTexture(gl, { width: 1, height: 1 });\n const colorTexture = createTexture(gl, { width: 1, height: 1 });\n gl.bindTexture(gl.TEXTURE_2D, iconTexture);\n gl.texSubImage2D(\n gl.TEXTURE_2D,\n 0,\n 0,\n 0,\n 1,\n 1,\n gl.RGBA,\n gl.UNSIGNED_BYTE,\n new Uint8Array([0, 0, 0, 0]),\n );\n gl.bindTexture(gl.TEXTURE_2D, null);\n\n function resize(widthPx: number, heightPx: number): void {\n width = Math.max(1, Math.round(widthPx));\n height = Math.max(1, Math.round(heightPx));\n canvas.width = width;\n canvas.height = height;\n gl.viewport(0, 0, width, height);\n\n sceneCanvas = document.createElement('canvas');\n sceneCanvas.width = width;\n sceneCanvas.height = height;\n sceneCtx = sceneCanvas.getContext('2d');\n if (!sceneCtx) throw new Error('vireglass/web: scene 2D context unavailable');\n\n gl.deleteTexture(contentTexture);\n contentTexture = createTexture(gl, { width, height });\n\n if (backdropFbo) {\n gl.deleteFramebuffer(backdropFbo);\n backdropFbo = null;\n }\n }\n\n function render(options: VireGlassRenderOptions): VireGlassRenderResult {\n if (!sceneCtx) throw new Error('vireglass/web: renderer not initialized (resize was never called)');\n // Narrowed once, before the timed section: a `let` read from inside the closure below would\n // widen back to `| null`, since TS cannot see that nothing reassigns it in between.\n const ctx = sceneCtx;\n\n // Validated BEFORE the GPU timer starts, not inside it: a bad call here has to throw cleanly\n // with no query left open, which an error thrown mid-timing would risk.\n if (options.backdrop && options.scene) {\n throw new Error(\n 'vireglass/web: render() needs exactly one of options.scene or options.backdrop, not both',\n );\n }\n if (!options.backdrop && !options.scene) {\n throw new Error('vireglass/web: render() needs either options.scene or options.backdrop');\n }\n\n // The rest of the frame, including a caller-supplied `backdrop` pass that may itself throw\n // (a shader typo, a bad uniform), runs inside `time`'s own try/finally — `endQuery` always\n // pairs with `beginQuery` no matter how this exits.\n return gpuTimer.time((): VireGlassRenderResult => {\n // 1. Backdrop. Exactly one of two paths fills `contentTexture` — everything after this only\n // SAMPLES it, never redraws it.\n if (options.backdrop) {\n // GPU pass, straight into `contentTexture` via its own FBO: no 2D canvas, no readback. See\n // `VireGlassBackdropPass` for the orientation contract the pass has to honor.\n const fbo = ensureBackdropFbo();\n gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);\n gl.viewport(0, 0, width, height);\n options.backdrop(gl, { framebuffer: fbo, width, height });\n // The pass is free to leave the context in ANY state (per its own JSDoc) — put back\n // everything the rest of this render depends on, not just the obvious framebuffer/\n // viewport pair: a pass that enables depth/stencil/face culling, binds its own VAO, or\n // changes the unpack flags used to upload `iconMask`/`colorLayer` just below would\n // otherwise corrupt a step it never touched directly.\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n gl.viewport(0, 0, width, height);\n gl.disable(gl.BLEND);\n gl.disable(gl.SCISSOR_TEST);\n gl.disable(gl.DEPTH_TEST);\n gl.disable(gl.CULL_FACE);\n gl.disable(gl.STENCIL_TEST);\n gl.colorMask(true, true, true, true);\n gl.depthMask(true);\n gl.bindVertexArray(null);\n gl.activeTexture(gl.TEXTURE0);\n gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);\n gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);\n gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.BROWSER_DEFAULT_WEBGL);\n } else {\n // CPU-side: a 2D canvas is easier for text and \"cover art\" (task, item 3), and the texture\n // itself plays the role of the plan's FBO(scene).\n ctx.clearRect(0, 0, width, height);\n options.scene?.(ctx, width, height, options.offsetX ?? 0, options.offsetY ?? 0, options.density);\n gl.bindTexture(gl.TEXTURE_2D, contentTexture);\n gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, sceneCanvas);\n gl.bindTexture(gl.TEXTURE_2D, null);\n }\n\n if (options.colorLayer) {\n gl.bindTexture(gl.TEXTURE_2D, colorTexture);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, options.colorLayer);\n }\n if (options.iconMask) {\n gl.bindTexture(gl.TEXTURE_2D, iconTexture);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, options.iconMask);\n gl.bindTexture(gl.TEXTURE_2D, null);\n }\n\n // 2. Backdrop to screen — the backing the element is seen against.\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n gl.viewport(0, 0, width, height);\n gl.disable(gl.BLEND);\n gl.disable(gl.SCISSOR_TEST);\n gl.useProgram(blitProgram);\n bindTextureAt(gl, 0, contentTexture, blitProgram, 'u_content');\n setUniform(gl, blitLoc('u_resolution'), [width, height]);\n drawFullscreenTriangle(gl);\n\n const probes: (ProbeStats | null)[] = [];\n\n for (let index = 0; index < options.pieces.length; index += 1) {\n const piece = options.pieces[index];\n\n // 3. Downsample + stats for the rectangle of VISIBLE glass (no lens/surface margins — the\n // same `glassWidth`/`glassHeight` that go into toLensProps). Only the first element\n // captures the grid: it's shared for the frame, the rest just need their own rectangle out\n // of it.\n const halfWidth = (piece.geometry.width * options.density) / 2;\n const halfHeight = (piece.geometry.height * options.density) / 2;\n const rect = {\n centerX: piece.centerX,\n centerY: piece.centerY,\n halfWidth,\n halfHeight,\n };\n const fresh =\n index === 0\n ? probe.sample(contentTexture, width, height, rect)\n : probe.statsFor(width, height, rect);\n // The backdrop estimate eases toward the new value instead of jumping to it. On Android\n // the native view does this (`GlassLensView.kt`, a per-frame SETTLE); nobody did it on the\n // web: under a moving canvas, body density used to jerk with the instantaneous sample, and\n // on screen that read as lighting artifacts.\n const stats = fresh ? settleStats(index, fresh) : null;\n probes.push(stats);\n // Restore the viewport and the content texture binding — the probe pass reuses TEXTURE0\n // and its own FBO, unbinds them itself, but does change the viewport.\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n gl.viewport(0, 0, width, height);\n\n gl.enable(gl.BLEND);\n // Premultiplied-over: both the lens and the surface emit premultiplied alpha (see `return\n // half4(half3(...) * alpha, alpha)` in both shaders).\n gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);\n\n // Both passes draw a fullscreen triangle, but the element covers only a small part of the\n // frame: without scissoring, a row of samples multiplies cost by the number of elements (on\n // a headless software renderer this pushed a frame into seconds). Margin is added on both\n // sides — the shadow, bevel and light gathering all reach past the element's own bounds.\n // The morph drop travels past the element's bounds — its travel is part of the margin,\n // otherwise scissoring would cut off the tail right where it matters.\n const reachOf = (m?: VireGlassMorph) =>\n m ? Math.hypot(m.offsetX, m.offsetY) + Math.max(m.width, m.height) / 2 + m.smoothing : 0;\n const morphReach = Math.max(reachOf(piece.morph), reachOf(piece.morph2));\n // Field deformation pushes the element's edge past its own bounds — drag, wave amplitude\n // and growth on press. Without this term, scissoring cuts off exactly the part being\n // dragged toward.\n const touchReach = piece.touch\n ? Math.hypot(piece.touch.pullX, piece.touch.pullY) +\n piece.touch.waveAmp * 2 +\n Math.max(piece.geometry.width, piece.geometry.height) * 0.05 * piece.touch.press\n : 0;\n const padPx =\n (lensPadDp(piece.geometry, piece.optics) + surfacePadDp(piece.geometry, 0) + morphReach + touchReach) *\n options.density;\n const left = Math.max(0, Math.floor(piece.centerX - halfWidth - padPx));\n const right = Math.min(width, Math.ceil(piece.centerX + halfWidth + padPx));\n const top = Math.max(0, Math.floor(height - (piece.centerY + halfHeight + padPx)));\n const bottom = Math.min(height, Math.ceil(height - (piece.centerY - halfHeight - padPx)));\n gl.enable(gl.SCISSOR_TEST);\n gl.scissor(left, top, Math.max(0, right - left), Math.max(0, bottom - top));\n\n // 4. Lens.\n gl.useProgram(lensProgram);\n bindTextureAt(gl, 0, contentTexture, lensProgram, 'content');\n setUniform(gl, lensLoc('u_contentSize'), [width, height]);\n setUniform(gl, lensLoc('u_resolution'), [width, height]);\n setUniform(gl, lensLoc('u_center'), [piece.centerX, piece.centerY]);\n // There's no local-view margin here (one shared coordinate space) — on Android `u_reach`\n // capped adaptive-blur gathering at the edge of the lens view's PADDING; there's no such\n // boundary here, so the value is chosen deliberately larger than any real gather radius, so\n // the `reach - length(p)` condition never triggers before `u_contentMin/Max` does.\n setUniform(gl, lensLoc('u_reach'), Math.max(width, height));\n setUniform(gl, lensLoc('u_contentMin'), [1, 1]);\n setUniform(gl, lensLoc('u_contentMax'), [width - 1, height - 1]);\n if (stats) {\n setUniform(gl, lensLoc('u_probeLuma'), stats.luma);\n setUniform(gl, lensLoc('u_probeBusy'), stats.busy);\n setUniform(gl, lensLoc('u_probeRange'), [stats.lo, stats.hi]);\n setUniform(gl, lensLoc('u_probeSlope'), [stats.slopeX, stats.slopeY]);\n setUniform(gl, lensLoc('u_probe'), [stats.r, stats.g, stats.b]);\n } else {\n // The probe hasn't reported yet (the first frame or two) — the -1 sentinel keeps the\n // shader on its own point samples (see the comment in `lens-shader.ts`).\n setUniform(gl, lensLoc('u_probeLuma'), -1);\n }\n const lens = toLensProps(piece.optics, piece.geometry, options.density, {\n debug: options.debug,\n morph: piece.morph,\n morph2: piece.morph2,\n touch: piece.touch,\n progress: piece.progress,\n light: piece.light,\n appear: piece.appear,\n accent: piece.accent,\n });\n applyChannel(gl, lensLoc, lens.uniformNames, lens.uniformSizes, lens.uniformValues);\n drawFullscreenTriangle(gl);\n\n // 5. Surface — shares its center with the lens (see the comment at the top of this file).\n // `toSurfaceUniforms`'s contract is dp, shared with Android; the core converts it to pixels.\n gl.useProgram(surfaceProgram);\n const rawSurface = toSurfaceUniforms(piece.optics, piece.geometry, {\n debug: options.debug,\n morph: piece.morph,\n morph2: piece.morph2,\n bodyInLens: true,\n touch: piece.touch,\n progress: piece.progress,\n // The shadow is denser over text, weaker over a flat backdrop (M 11:47). The rule lives\n // in the core: while it lived here, Android didn't have it at all.\n shadow: stats ? shadowOpacity(stats.busy) : 1,\n // The probe has already computed the ambient color — the shadow gets it for free. Taken\n // as-is: `settleStats` has already smoothed it, and rounding would bring back exactly the\n // steps the smoothing was written to remove.\n ambient: stats ? ([stats.r, stats.g, stats.b] as const) : undefined,\n appear: piece.appear,\n lift: piece.lift,\n });\n // The dp → device pixel conversion lives in the core (`toDeviceSurfaceUniforms`): one list\n // of lengths for every field, so a new field can't be forgotten here by oversight.\n applyObject(gl, surfaceLoc, {\n ...toDeviceSurfaceUniforms(rawSurface, options.density),\n u_center: [piece.centerX, piece.centerY],\n });\n // What's worklet dynamics on Android (press, active, tilt) arrives here as fields on the element.\n setUniform(gl, surfaceLoc(DYNAMIC_UNIFORMS[0]), piece.press ?? 0);\n setUniform(gl, surfaceLoc(DYNAMIC_UNIFORMS[1]), piece.active ?? 0);\n const hasIcon = Boolean(piece.icon && options.iconMask);\n setUniform(gl, surfaceLoc(ICON_UNIFORMS[0]), hasIcon ? 1 : 0);\n // Scale 1: the coordinate is already in screen space, the sampler itself normalizes by size.\n setUniform(gl, surfaceLoc(ICON_UNIFORMS[1]), 1);\n setUniform(gl, surfaceLoc(ICON_UNIFORMS[2]), piece.inkIdle ?? [1, 1, 1, 1]);\n setUniform(gl, surfaceLoc(ICON_UNIFORMS[3]), piece.inkActive ?? [1, 1, 1, 1]);\n bindTextureAt(gl, 1, iconTexture, surfaceProgram, 'u_icon');\n setUniform(gl, surfaceLoc('u_iconSize'), hasIcon ? [width, height] : [1, 1]);\n const hasColor = Boolean(piece.overlay && options.colorLayer);\n setUniform(gl, surfaceLoc(OVERLAY_UNIFORMS[0]), hasColor ? 1 : 0);\n bindTextureAt(gl, 2, colorTexture, surfaceProgram, 'u_overlay');\n setUniform(gl, surfaceLoc('u_overlaySize'), hasColor ? [width, height] : [1, 1]);\n setUniform(gl, surfaceLoc('u_resolution'), [width, height]);\n drawFullscreenTriangle(gl);\n }\n gl.disable(gl.SCISSOR_TEST);\n\n return { probes };\n });\n }\n\n function destroy(): void {\n probe.destroy();\n gl.deleteProgram(blitProgram);\n gl.deleteProgram(lensProgram);\n gl.deleteProgram(surfaceProgram);\n gl.deleteTexture(contentTexture);\n gl.deleteTexture(iconTexture);\n gl.deleteTexture(colorTexture);\n if (backdropFbo) gl.deleteFramebuffer(backdropFbo);\n gpuTimer.destroy();\n }\n\n return { resize, render, getLastGpuMs: gpuTimer.getLastMs, destroy };\n}\n"]}
|