vireglass 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/{chunk-BZC2WHCK.js → chunk-2XJJO7SC.js} +4 -4
- package/dist/{chunk-BZC2WHCK.js.map → chunk-2XJJO7SC.js.map} +1 -1
- package/dist/{chunk-3PGYF2NK.js → chunk-CTTZNZFV.js} +3 -3
- package/dist/{chunk-3PGYF2NK.js.map → chunk-CTTZNZFV.js.map} +1 -1
- package/dist/{chunk-FL5UZPY5.js → chunk-H5QTBF67.js} +4 -4
- package/dist/{chunk-FL5UZPY5.js.map → chunk-H5QTBF67.js.map} +1 -1
- package/dist/{chunk-TTOKBARR.js → chunk-MMPK6DCK.js} +10 -3
- package/dist/chunk-MMPK6DCK.js.map +1 -0
- package/dist/{chunk-P2ZOODXD.js → chunk-SLRPHDV7.js} +3 -3
- package/dist/{chunk-P2ZOODXD.js.map → chunk-SLRPHDV7.js.map} +1 -1
- package/dist/dom.cjs +48 -20
- package/dist/dom.cjs.map +1 -1
- package/dist/dom.d.cts +15 -3
- package/dist/dom.d.ts +15 -3
- package/dist/dom.js +44 -23
- package/dist/dom.js.map +1 -1
- package/dist/index.cjs +1 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5 -5
- package/dist/law.cjs +8 -1
- package/dist/law.cjs.map +1 -1
- package/dist/law.d.cts +7 -0
- package/dist/law.d.ts +7 -0
- package/dist/law.js +1 -1
- package/dist/reference.js +2 -2
- package/dist/web/index.cjs +1 -2
- package/dist/web/index.cjs.map +1 -1
- package/dist/web/index.js +4 -4
- package/docs/law.md +2 -2
- package/package.json +11 -9
- package/src/dom/body.ts +24 -9
- package/src/dom/index.ts +43 -10
- package/src/dom/probe.ts +27 -3
- package/src/law.ts +7 -0
- package/dist/chunk-TTOKBARR.js.map +0 -1
|
@@ -42,7 +42,14 @@ var BODY = {
|
|
|
42
42
|
darkSideFrom: 0.42,
|
|
43
43
|
darkSideTo: 0.58,
|
|
44
44
|
/** Ceiling on any single demand, so one requirement cannot make the glass opaque on its own. */
|
|
45
|
-
maxDemand: 0.92
|
|
45
|
+
maxDemand: 0.92,
|
|
46
|
+
/**
|
|
47
|
+
* And a tighter one for presence specifically. It is a FLOOR on visibility, not a licence to go
|
|
48
|
+
* opaque: wherever the body's tint sits close to the backdrop, the density that would deliver the
|
|
49
|
+
* separation is the ceiling, and glass at `maxDemand` has stopped being a window. Better to
|
|
50
|
+
* separate as far as transparency allows and no further. UNMEASURED.
|
|
51
|
+
*/
|
|
52
|
+
presenceDemand: 0.5
|
|
46
53
|
};
|
|
47
54
|
var SHADOW = {
|
|
48
55
|
/**
|
|
@@ -452,5 +459,5 @@ var SCROLL_EDGE = {
|
|
|
452
459
|
};
|
|
453
460
|
|
|
454
461
|
export { ACCENT, ACCESSIBILITY, BEVEL, BODY, DERIVE, DISPERSION, EPSILON, INK, LENS, MEDIUM, MORPH, RIM, SCALE, SCROLL_EDGE, SHADOW, SHADOW_DENSITY, SHADOW_GAP, SIZE, SPECTRAL, SPRING, TOUCH };
|
|
455
|
-
//# sourceMappingURL=chunk-
|
|
456
|
-
//# sourceMappingURL=chunk-
|
|
462
|
+
//# sourceMappingURL=chunk-MMPK6DCK.js.map
|
|
463
|
+
//# sourceMappingURL=chunk-MMPK6DCK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/law.ts"],"names":[],"mappings":"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","file":"chunk-MMPK6DCK.js","sourcesContent":["/**\n * THE LAW: every calibrated number the material obeys, in one place.\n *\n * A value here is written ONCE and read by everything that needs it — the shader text interpolates\n * it, the JS twins import it. The alternative is what this file replaces: the same number typed\n * into a GLSL string and again into a TypeScript function, drifting apart the first time one side\n * is tuned. `VG_FALLOFF` already worked this way; the rest now does too.\n *\n * Each entry carries where it came from. `§N` is a section of `docs/reference.md`, which is itself\n * drawn from Apple's public sessions; `M 2:32` and the like are the timestamps those sections cite.\n * A number with no provenance is marked as such, and that marking is a debt, not a decoration.\n *\n * Structural constants — 0, 1, 0.5, 2, π — are not laws and do not belong here.\n */\n\n/** Rim optics: the silhouette's light (§2). */\nexport const RIM = {\n /**\n * The key-light lobe's exponent. Two opposing arcs come out of one expression, which is why\n * they are opposed rather than placed: `pow(max(facing, 0), n) + 0.45 * pow(max(-facing, 0), n)`.\n *\n * MEASURED off two close-ups of real end caps, frames/crops/cap158-left and cap158-right. The\n * bright arc's full width at half its peak is 53 degrees in the first and 30 in the second; this\n * exponent's own width is `2·acos(0.5^(1/n))`, so they imply 6 and 20. It was 3, whose arc is 75\n * degrees wide — broader than either measurement, and the error is in the same direction in both.\n *\n * 6 is the conservative end: it reproduces the wider crop exactly, and a broader arc is the more\n * forgiving error. One exponent cannot serve both, and that is not noise — a specular lobe's\n * width depends on the light's angular size as well as the surface, and those are two different\n * scenes. The model has no term for the light's size.\n */\n lobeExponent: 6,\n /** The far arc, weaker than the one facing the light (§2). */\n opposingArc: 0.45,\n /**\n * The dark edge along the whole silhouette, as a multiplicative darkening. iOS 27 made it a\n * layer of its own, and it does not rule out the highlight sitting on top of it (§2).\n */\n darkEdge: 0.22,\n /** Width of the hairline, CSS px. \"About a point, not a band and not a bevel\" (§2). */\n widthPx: 1,\n} as const;\n\n/** How the medium stands between the backdrop and the eye (§3). */\nexport const BODY = {\n /** Lightness ceiling for the body when legibility is loose. */\n capLoose: 0.62,\n /** …and when it is strict. The model is calibrated at these two, not in between (E-43). */\n capTight: 0.38,\n /** The two ends the body tints toward. Glass has no colour of its own (HIG \"Color\"). */\n tintDark: 0.07,\n tintLight: 0.94,\n /** How far the backdrop's spread reaches past its mean when deciding what the ink needs. */\n busyEdge: 0.75,\n /** Fine texture the body has to stop being a window for. */\n groundSpan: 0.06,\n /** Where the tint flips direction, by the element's AVERAGE lightness — a local threshold cuts\n * a diagonal step across the body on a gradient. */\n darkSideFrom: 0.42,\n darkSideTo: 0.58,\n /** Ceiling on any single demand, so one requirement cannot make the glass opaque on its own. */\n maxDemand: 0.92,\n /**\n * And a tighter one for presence specifically. It is a FLOOR on visibility, not a licence to go\n * opaque: wherever the body's tint sits close to the backdrop, the density that would deliver the\n * separation is the ceiling, and glass at `maxDemand` has stopped being a window. Better to\n * separate as far as transparency allows and no further. UNMEASURED.\n */\n presenceDemand: 0.5,\n} as const;\n\n/** Shadow (§4). The density law lives in `geometry.ts`; these are its measured ends. */\nexport const SHADOW = {\n /**\n * Measured off reference frames 711–723: 4.0% over a flat light backdrop, 19.9% over text.\n *\n * REPRODUCED independently off frames/verify/m-707 and m-711 — the same capsule in the same scene\n * at the same scale, once below the text over plain background and once scrolled up over it.\n * Comparing the rows under the capsule against the same rows far to either side, so the\n * background's own gradient cancels: 3.5% and 19.2%, peaking about 140 px below the capsule.\n * Two passes, different frames and a different method, agreeing to within 0.7 of a point.\n */\n alphaFlat: 0.04,\n alphaBusy: 0.199,\n} as const;\n\n/** Dispersion: the channels refract at different indices (§1, Cauchy). */\nexport const DISPERSION = {\n /** Red bends less than the base index, blue more — and not symmetrically. */\n redShift: -0.4,\n blueShift: 0.6,\n} as const;\n\n/** Response to a finger (§5). The springs and decay live in `touch-response.ts`; these shape the\n * field the finger deforms. */\nexport const TOUCH = {\n /**\n * The element grows under pressure (M 3:51; HIG: interactive \"expands\").\n *\n * CHECKED, not measured: across frames/knob/switch-press, a 4x5 sheet of one switch being\n * pressed, the control's extent runs 764 px in the three settled frames and 787 to 822 under the\n * finger — 3 to 8 per cent. This value sits inside that, which is a consistency check and not a\n * derivation: the measurement cannot separate the knob stretching from the track growing, and the\n * settled frames agreeing to the pixel is the only part of it that is tight.\n */\n pressGrow: 0.06,\n /** The ridge around the contact blob: material displaced from under the finger ends up\n * somewhere. Without it the shape balloons, and a dense medium does not do that. */\n ridge: 0.42,\n /** How far press pulls the field toward the finger. */\n pressPull: 0.16,\n /** Wavelength and decay of the ripple, as fractions of its span. Short and quick: in a dense\n * medium ripples are frequent and small; long shallow swells are water. */\n waveLength: 0.17,\n waveDecay: 0.7,\n /** The ripple's span, as a multiple of the contact radius. It has to reach the far edge, or it\n * reads as jitter under the finger rather than a wave across the surface. */\n waveSpan: 2,\n /**\n * Contact radius as a fraction of the element's half-size. UNMEASURED: §5 says the response has\n * to be visible UNDER the finger, which is why the blob is wide, but gives no figure.\n */\n radiusOfHalfSize: 1.1,\n /**\n * Wave impulse as a fraction of the travel limit. UNMEASURED: the core carries the ripple's\n * decay and frequency, but nothing says how hard a finger strikes it.\n */\n waveOfTravel: 0.18,\n /** The lift-off ring is weaker than the one from touching down (§5). UNMEASURED ratio. */\n releaseWave: 0.6,\n /**\n * How far a glow reaches past the element it started on, as a multiple of that element's\n * half-size. UNMEASURED: §5 says the glow spreads \"onto any Liquid Glass elements nearby\" and\n * names no distance. This is the reach at which a neighbour in a tab bar still lights and one\n * across the screen does not.\n */\n glowReach: 3,\n /**\n * How much of an element's \"active\" state a touch alone raises — the fraction the glow and the\n * ink lift ride on when nothing else is driving them. UNMEASURED.\n *\n * It was a private constant on the Android surface whose comment said it matched the web's, and\n * nothing enforced that: the web renderer takes `active` from the host as a field and has no\n * constant to match. Two renderers agreeing by a comment is exactly what this file is for.\n */\n activeOnTouch: 0.3,\n} as const;\n\n/** The bevel's profile — spherical, like a cap: `t / sqrt(1 - t² · k)`, clamped. A plain `t²` kept\n * the slope near zero through most of the bevel and then shot up at the edge, so the optics\n * bunched into a ring and the element read as a puck. */\nexport const BEVEL = {\n sphere: 0.94,\n floor: 0.02,\n slopeMax: 3.2,\n} as const;\n\n/** Where two shapes meet head-on their normals cancel exactly and the blend lands on zero. */\nexport const EPSILON = 1e-5;\n\n/** What the medium does to light passing through it (§3). */\nexport const MEDIUM = {\n /** The lightness content under the glass is pulled toward, and how hard. The medium both removes\n * light and mixes scattered light back in: over a light backdrop the body darkens, over a dark\n * one it lightens. A layer that only ADDED light pushed the body the wrong way over light\n * backdrops (M 2:35). */\n luma: 0.4,\n pull: 0.07,\n /** Ambient light reaching the body on top of that pull. */\n ambientSpill: 0.01,\n /** Glass concentrates light: the body is a touch lighter than what lies beneath it (M 2:29). */\n concentrate: 0.01,\n /** Scattered light on a frosted element is an ADDITION over the backdrop, not a fraction of the\n * way to the tint — a fraction goes to zero once the backdrop reaches the tint's lightness and\n * darkens past it (docs/benchmarks.md). */\n matteLift: 0.1,\n /** How far scattering reaches, as a fraction of the gather radius, over a busy backdrop under\n * ink. Set against the reference: structure under the capsule fades by a factor of nine or ten\n * there, not thirty (M 11:47). */\n scatterMax: 0.2,\n scatterBase: 0.1,\n /** The response to structure saturates early: what competes with the ink is not the AREA of\n * foreign text but the fact of it — a line under a tile measures about 0.12 of busy. */\n structureGain: 20,\n /** Tint density in the flat middle; the bevel multiplies it by the edge density. */\n bodyDensityFlat: 0.19,\n} as const;\n\n/** The lens itself (§1). */\nexport const LENS = {\n /** Backdrop compression at the rim goes to infinity at the silhouette, so the radius correction\n * is capped. */\n footprintMax: 1.8,\n /** Same reason, for the profile's slope. */\n slopeMax: 40,\n /** Every thin film on glass sits around this index. */\n filmIor: 1.35,\n /** Reference channel wavelengths in nm — red, green, blue — for diffraction and interference. */\n lambdaR: 610,\n lambdaG: 550,\n lambdaB: 460,\n} as const;\n\n/** The shadow's shape in the gap under the element (§4). */\nexport const SHADOW_GAP = {\n /** How much weaker the shadow is right at the outline than under the middle of the gap, and what\n * fraction of the offset it takes to reach full depth. In the reference the shadow's minimum\n * sits BELOW the rim, not at it. */\n light: 0.76,\n reach: 0.2,\n /** Fraction of the ambient hue the shadow carries. It has to stay a shadow, not a coloured blob. */\n tint: 1,\n} as const;\n\n/** Ink drawn inside the material (§5). */\nexport const INK = {\n /** Depth beneath the surface, dp. The normal displaces it by exactly this much at the rim. */\n depth: 4,\n /** Defocus under a finger, as a FRACTION of the touch blob rather than a length: the blob\n * already arrives in device units, so this follows both screen density and element size. At\n * rest it is zero and the sample stays a single one. */\n defocus: 0.08,\n} as const;\n\n/** The spectral edge (§1): dispersion, diffraction and interference are one cause — a dependence\n * on wavelength — and one hue multiplier, normalised to its own mean. They colour the reflection\n * without brightening it. */\nexport const SPECTRAL = {\n /** How far into the bevel the fringes reach, as a fraction of it measured from the silhouette.\n * Diffraction is an EDGE effect; past this the bevel is plain glass. */\n diffractionOnset: 0.45,\n /**\n * The same window for interference, which had none. §2 puts the iridescence on the opposing ARC —\n * \"the opposing arc carries an iridescent fringe\" — and a fringe is a thing at an edge.\n *\n * Without it the term applies wherever the surface angle puts it, and across the inner bevel the\n * angle barely changes, so a film that should read as a fringe reads as a flat colour filter.\n * Measured on a 680x64 bar with the default preset: a multiplier of 1.68, 1.26, 0.05 held CONSTANT\n * from a third of the way through the bevel to its inner edge — red boosted by two thirds and blue\n * removed outright. UNMEASURED window; wider than diffraction's because interference reaches\n * further in than the fringes do.\n */\n iridescenceOnset: 0.25,\n /** Fringes across the bevel. Denser as the bevel sharpens, which is what the phase term does. */\n diffractionFringes: 4,\n /** The channel the hue is measured against, nm. Green is the eye's own reference. */\n referenceLambda: 550,\n} as const;\n\n/** Tinting (§7): a colour that is a property of the medium, not a fill over it. */\nexport const ACCENT = {\n /** The tone the colour takes over a dark backdrop and over a light one. \"A range of tones mapped\n * to content brightness underneath\", rather than one flat colour. */\n deep: 0.88,\n light: 1.08,\n /** How much of the medium the colour occupies by default. Short of one, because the content has\n * to keep coming through: a fill \"breaks the visual character of Liquid Glass\" (@17:03). */\n amount: 0.8,\n} as const;\n\n/**\n * The system accessibility settings (§9). They are modifiers on the material's layers, so their\n * calibration belongs in the law next to the layers they move — not as private constants in the\n * module that happens to apply them.\n */\nexport const ACCESSIBILITY = {\n /** Frost floor under reduced transparency: the glass gets \"frostier\" (§9). */\n frostMinDp: 14,\n /**\n * Body floor under reduced transparency. B @10:07 is the stricter statement of the two — a bar\n * \"gets a background when reduce transparency is enabled\" — so this is a background, not a\n * thicker haze. UNMEASURED: neither source gives a figure.\n */\n obscureMin: 0.82,\n /** Under increased contrast the element is \"predominantly black or white\" (§9): the body goes to\n * whichever pole its ink already points at, at a density that leaves it reading as that pole. */\n contrastDensity: 0.94,\n /** …and separates from the backdrop on its own, whatever is behind it. */\n contrastPresence: 0.5,\n /**\n * The contrasting border §9 asks for, in units of lightness away from the body's own pole. The\n * rim stops taking its colour from the environment and takes it from the element instead — an\n * ambient hairline is not a contrasting border. UNMEASURED.\n */\n contrastRim: 0.85,\n /**\n * Press that survives reduced motion. §9 \"decreases the intensity of some effects and disables\n * any elastic properties\": the spring and the ripple are elastic and go to zero, the press is an\n * ease that carries the element's light, so it is damped rather than removed — an element that\n * stops answering a finger altogether is not reduced motion, it is no feedback. UNMEASURED.\n */\n stillPress: 0.6,\n} as const;\n\n/**\n * Morphing (§5). Apple's design leads name their own model for it out loud (S 1:25:30, fireside\n * chat with the Apple design team): \"we also had a refresher from biology class. It's called\n * mitosis and meiosis. When these things are coming together or materialization and\n * dematerialization and morphing.\"\n *\n * Which is the right physics and not a metaphor: a dividing cell is one body throughout. It never\n * has a moment of being two bodies that happen to touch, and it never pops a second body into\n * existence beside the first. The neck is continuous from start to finish, and it is the neck that\n * carries the whole transition.\n */\nexport const MORPH = {\n /**\n * Width of the neck between two lobes, as a fraction of the smaller one's half-size. On a large\n * element the bridge has to be wider or the seam keeps a sharp corner, which a liquid does not\n * have. UNMEASURED: the reference names the behaviour and no figure.\n */\n neck: 0.35,\n /**\n * How far past merely touching a completed fusion goes, as a multiple of the bridge width that\n * just closes the gap. At exactly the bridge width the two surfaces meet at a point, which is two\n * shapes kissing rather than one body.\n *\n * MEASURED, frames/morph/s03, the one panel where the necks are still attached. Apple's\n * silhouette there: lobes 72 and 82 px with their centres 92 px apart — 15 px of space between\n * their surfaces — joined by a neck 28 px tall. Rendering the same geometry through this model\n * gives 29.0. The measurement discriminates: 1.2 gives 23.5, 1.4 gives 33.5, and at 1.0 the neck\n * closes to nothing.\n */\n fuse: 1.3,\n} as const;\n\n/**\n * CAUSE TO EFFECT. The normalisations that turn what the medium IS — index, thickness, bevel,\n * roughness, film — into what a renderer needs. They lived as literals inside `optics.ts`, half of\n * them not even named, which meant the heart of the model was the one part of it the law could not\n * see.\n *\n * Almost all of them are stylisations and say so. Literal physical values put every effect at the\n * threshold of visibility: real glass reflects four per cent at normal incidence, and a rim drawn\n * at four per cent is not a rim. Each is monotonic in its own cause, so \"denser medium, brighter\n * rim\" holds whatever the constant is; what no one has measured is where on the scale Apple's\n * material sits. Hence UNMEASURED on nearly all of it — that is the honest state of this file, and\n * the gate now says so out loud instead of the number hiding in a module.\n */\nexport const DERIVE = {\n /** Physical 4% reflectance into the render's working range. UNMEASURED. */\n fresnelGain: 17.5,\n /** Schlick's exponent. A constant of the approximation, not a knob. */\n fresnelExponent: 5,\n /** The index at which ray bending is at full strength, measured from air. UNMEASURED. */\n iorFullBend: 0.6,\n /** Magnification behind a plane-parallel plate, per dp of thickness. UNMEASURED. */\n magnifyPerDp: 0.006,\n /** In ordinary glasses the Abbe number falls as the index rises, so dispersion grows with it.\n * UNMEASURED slope. */\n dispersionPerIor: 1.1,\n /** Index spread between the red and blue channels. The physical value (~0.01 at an Abbe number\n * of 55) is at the threshold of visibility. UNMEASURED. */\n iorSpreadPerDispersion: 0.045,\n /** Beer–Lambert absorption per dp of path. UNMEASURED. */\n absorbPerDp: 0.017,\n /** Ceiling on how much denser the bevel may read than the body. The rim's ray travels further\n * through the medium; past this the rim is milk rather than glass. UNMEASURED. */\n edgeDensityMax: 4,\n /**\n * Frost ceiling, dp. Deliberately low: blur is a SUPPORTING device here, and past about a dozen\n * dp letters under the glass stop being letters. Separating content from ink is the body's job.\n *\n * UNMEASURED, and an attempt is recorded here so the next one does not repeat it.\n * frames/verify/m-711 has the same paragraph sharp beside the capsule and behind it, which looks\n * like the measurement: match the blur that takes the sharp patch's detail down to the glassed\n * one's. It gives 2 to 3 px at that frame's scale, and the figure is not trustworthy. Detail is\n * lost to three things at once there — the frost, the magnification (`refractionScale` lowers the\n * text's spatial frequency without blurring anything), and the body veiling its contrast — and\n * the method cannot separate them. What it does bound is the total: the text behind the glass\n * keeps 68% of the detail it has beside it.\n *\n * A clean measurement needs a frame where the glass does not magnify, or a feature of known size\n * behind it to normalise the magnification out.\n */\n blurMaxDp: 12,\n /** Highlight width from a smooth surface and from a fully rough one, as a specular exponent.\n * UNMEASURED. */\n specularPowerSmooth: 160,\n specularPowerRough: 8,\n /** How much roughness damps the highlight's brightness. UNMEASURED. */\n specularRoughDamping: 0.6,\n /**\n * The medium's own hue by density. Water absorbs red and skews cool, ordinary glass is nearly\n * neutral with a faint green, dense high-index media skew warm. Glass has no colour of its own\n * (HIG \"Color\"), so this is never set by hand — it is tied to the index.\n */\n hueCool: [0.86, 1.0, 1.08],\n hueNeutral: [1.0, 1.02, 0.99],\n hueWarm: [1.08, 1.0, 0.88],\n /** The index range the hue sweeps across, and where it starts. UNMEASURED. */\n hueFromIor: 1.2,\n hueSpan: 0.55,\n /** Body lightness follows reflectance: the denser the medium, the more of its surroundings it\n * returns. UNMEASURED base and slope. */\n bodyLiftBase: 0.34,\n bodyLiftPerF0: 2.4,\n /**\n * How far past the bevel the rim gathers its surroundings, as a multiple of the bevel. At a\n * grazing angle the eye receives the vicinity rather than what is under the glass, and without\n * this, glass on an empty black background has no source at all. UNMEASURED.\n */\n gatherPerBevel: 4,\n /** Floor and ceiling on that radius, dp. Unbounded, a wide bevel gathers from fifty dp away and\n * a READABLE copy of the neighbourhood appears inside the glass — eight samples cannot blur a\n * disc that large (E-33, E-37). */\n gatherMinDp: 4,\n gatherMaxDp: 28,\n /** The body's own density from absorption through its thickness. Small: the body only moves\n * toward the tint under the legibility requirement, otherwise it dims the content. UNMEASURED. */\n bodyDensity: 0.05,\n /**\n * Rim light on the physical scale rather than the rim highlight's. `fresnelGain` saturates at one\n * for dense media, and applying it here too would raise body lightness harder than the glass had\n * just separated it from the ink — eating its own legibility work. UNMEASURED.\n */\n edgeLightGain: 6,\n /**\n * Iridescence rides the REFLECTED ray, so it is tied to Fresnel and has no knob of its own.\n *\n * MEASURED against how coloured a real rim is. In frames/crops the brightest rim pixels have a\n * channel spread — (max − min) / max — of 13% in cap158-left and 20% in cap158-right, against\n * their own backdrops' 14.6% and 14.3%. So the rim is no more coloured than the scene it stands\n * in: §2's \"iridescent fringe\" adds a few points at most, and in one of the two crops it adds\n * nothing measurable.\n *\n * At 1.6 this put ordinary glass at 0.945 iridescence and the spectral multiplier reached a 97%\n * channel spread — red boosted by two thirds and blue removed. Rendered over a coloured page that\n * is a neon tube around every control, which is what sent me to measure it.\n */\n iridescencePerFresnel: 0.042,\n /** Edge diffraction shares dispersion's λ-dependence, at the stylised amplitude: at the physical\n * one the fringes are not visible at all. MEASURED with the same crops and the same target as\n * iridescence — on its own it was reaching a 93% channel spread. */\n diffractionPerDispersion: 0.05,\n /** Multiple internal reflection — the denser the medium, the more light it circulates and the\n * more it is tinted by its surroundings. UNMEASURED slope. */\n colorPickupPerFresnel: 0.9,\n /**\n * The ceiling, which keeps it a medium rather than a fill.\n *\n * MEASURED, frames/concentric/shadow-zoom: the same sidebar twice, over a yellow cover and over a\n * pink one, so the difference between the panels is the pickup with the sidebar's own colour\n * subtracted out. Sampling a band of rows clear of the text, the bar's hue offset against the\n * cover's own reaches 0.31 of it at the edge nearest the content in the yellow panel and 0.28 in\n * the pink one. It was 0.42, which nothing measured.\n *\n * Reality is not uniform: the bleed falls to nothing about ninety pixels in, and both panels show\n * the same falloff. The DOM path has one backdrop sample per element and cannot reproduce that,\n * so it applies the ceiling across the whole body — right at the edge, too strong in the middle.\n * That limit is in docs/dom.md rather than papered over with a lower number.\n */\n colorPickupMax: 0.3,\n} as const;\n\n/**\n * SIZE AND SHADOW. What changes when an element gets bigger, and how dense its shadow reads over\n * what is behind it. 219 @6:36: as glass \"flexes and morphs to larger sizes, it simulates a\n * thicker material with deeper shadows and more pronounced lensing and refraction\".\n */\nexport const SIZE = {\n /** Half-size at which bevel and thickness are specified as given; they grow as its square root\n * from there. UNMEASURED reference size. */\n referenceDp: 24,\n /** Floor and ceiling on that growth. UNMEASURED. */\n gainMin: 0.8,\n gainMax: 2.4,\n /** A bevel wider than this fraction of the half-size breaks the SDF — the roundings converge in\n * the middle. A property of the geometry, not a taste. */\n maxBevelFraction: 0.65,\n} as const;\n\n/**\n * Shadow density by what is behind the element. 219 @11:47, verbatim: it \"increases the opacity of\n * its shadow when it is over text… lowers the opacity of its shadow when it is over a solid light\n * background.\" About content BEHIND the element, so one value per element rather than a field over\n * the shadow's area. Endpoints checked against the reference by the depth of the dip under the\n * element (docs/benchmarks.md).\n */\nexport const SHADOW_DENSITY = {\n /** Over a flat background. */\n flat: 0.8,\n /** How fast it rises with structure behind the element, and how far it may rise. */\n busyGain: 6,\n busyRise: 1.2,\n /** Quantisation of the emitted value: a shadow that changes on every pixel of scroll re-lays-out\n * the native view every frame. */\n step: 0.05,\n} as const;\n\n/**\n * THE MEDIUM'S RESPONSE TO A FINGER (§5). Part of the material, exactly as `ior` is: keeping these\n * with the consumer would mean web and Android ended up with two different glasses under one name.\n *\n * Press is viscous — it dents shallow and releases slowly. Drag is thick: the body noticeably lags\n * the finger, travel is short, and on release it snaps back fast with almost no overshoot, because\n * a wide springy overshoot would be liquid jelly rather than dense glass.\n */\nexport const SPRING = {\n /** Stiffness and damping while the finger is down. UNMEASURED. */\n holdStiffness: 260,\n holdDamping: 46,\n /** …and after it lifts. Stiffer, and damped hard: a dense medium snaps back without ringing. */\n releaseStiffness: 420,\n releaseDamping: 34,\n /** Time constants for the press dent, in and out, seconds. UNMEASURED. */\n pressAttack: 0.07,\n pressRelease: 0.16,\n /** How fast the touch point catches up to the finger. Not instant: an instant jump tears the\n * deformation, and on rapid taps that reads as jitter. */\n pointFollow: 0.045,\n /** …and how fast the element registers as being touched at all, which is what the glow rides. */\n activeFollow: 0.09,\n /** The ripple decays within a quarter second rather than oscillating: in a viscous medium ripples\n * are short and weak. UNMEASURED. */\n waveDecay: 0.22,\n waveTurnsPerSecond: 3,\n /** Ceilings on the accumulated ripple, as multiples of a single impulse. Impulses add up rather\n * than restart — restarting cuts a running wave mid-period, which is what jolts on rapid taps. */\n waveCapOnGrab: 1.6,\n waveCapOnRelease: 2,\n} as const;\n\n/** The user's clear-to-tinted preference (§3). */\nexport const SCALE = {\n /** Where on the scale the material stays exactly as it is by default. */\n default: 0.35,\n /** Body density at the fully tinted end: content under the glass has to be hidden. UNMEASURED. */\n tintedDensity: 0.9,\n} as const;\n\n/**\n * The scroll edge (§10). 219 @9:16: as content scrolls under a glass element \"the effect gently\n * DISSOLVES the content into the background, lifting the glass visually above the moving content,\n * and allowing floating elements like titles to always remain clear.\"\n *\n * 356 @11:32 says what it is not, and the first implementation here did exactly that: \"they don't\n * block or darken like overlays. They simply clarify where UI and content meet.\"\n */\nexport const SCROLL_EDGE = {\n /** How far the content has to slide under the panel for the effect to fully engage, dp. */\n engageDp: 24,\n /**\n * How far the blur reaches BELOW the bar's lower lip, as a multiple of the bar's own height.\n *\n * MEASURED, frames/scroll-edge/z576 at 320x413. High-frequency energy (mean |laplacian| per row,\n * which a brightness gradient in the content cannot fake) reads 1.4 through the zone under the\n * bar and 14 in the sharp content below — a factor of ten. The bar's lip is at y≈45 and the zone\n * runs to y≈130, so the reach is about 1.9 times the bar's own height.\n *\n * The same frame confirms 219 @9:22 in passing: the floating title sits inside that zone at 32,\n * twenty times its surroundings. Titles stay crisp while everything behind them dissolves, which\n * is why the effect belongs behind the glass in the stack rather than over the content.\n */\n reachOfBarHeight: 1.9,\n /** The blur the content dissolves into at the edge, dp. This is the effect itself — the content\n * goes out of focus into the background rather than being covered by anything. UNMEASURED. */\n dissolveBlurDp: 10,\n /**\n * Over dark content the glass turns dark and 219 @9:33 switches the effect \"to apply a subtle\n * dimming instead\". Dimming is a reduction in luminance: dark content under dark glass merges\n * with it, and pushing the content down is what lets the glass sit above it. UNMEASURED.\n */\n dimAlpha: 0.1,\n /** A pinned view under the panel (column headers) gets a flat band instead of a gradual fade\n * (219 @9:41) — \"a stronger, more opaque boundary\" (356 @12:12). UNMEASURED. */\n hardBlurDp: 14,\n hardAlpha: 0.18,\n} as const;\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SIZE, SHADOW_DENSITY } from './chunk-
|
|
1
|
+
import { SIZE, SHADOW_DENSITY } from './chunk-MMPK6DCK.js';
|
|
2
2
|
|
|
3
3
|
// src/geometry.ts
|
|
4
4
|
var circleGeometry = (size) => ({
|
|
@@ -54,5 +54,5 @@ function shadowOpacityFrom(sample) {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
export { MAX_BEVEL_FRACTION, MAX_STRETCH, RIM_FRACTION, bevelDp, bevelFraction, capsuleGeometry, circleGeometry, halfMinDp, lensPadDp, morphReachDp, rimDp, roundedRectGeometry, shadowOpacity, shadowOpacityFrom, shadowReachDp, sizeDp, sizeGain, surfacePadDp, thicknessDp };
|
|
57
|
-
//# sourceMappingURL=chunk-
|
|
58
|
-
//# sourceMappingURL=chunk-
|
|
57
|
+
//# sourceMappingURL=chunk-SLRPHDV7.js.map
|
|
58
|
+
//# sourceMappingURL=chunk-SLRPHDV7.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/geometry.ts"],"names":[],"mappings":"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","file":"chunk-P2ZOODXD.js","sourcesContent":["import type { VireGlassOptics } from './material';\nimport { SHADOW_DENSITY, SIZE } from './law';\n\n/** Glass shape in dp. Circle and capsule are special cases of a rounded rectangle. */\nexport type VireGlassGeometry = {\n width: number;\n height: number;\n cornerRadius: number;\n};\n\nexport const circleGeometry = (size: number): VireGlassGeometry => ({\n width: size,\n height: size,\n cornerRadius: size / 2,\n});\n\nexport const capsuleGeometry = (width: number, height: number): VireGlassGeometry => ({\n width,\n height,\n cornerRadius: Math.min(width, height) / 2,\n});\n\nexport const roundedRectGeometry = (\n width: number,\n height: number,\n cornerRadius: number,\n): VireGlassGeometry => ({ width, height, cornerRadius });\n\nexport const halfMinDp = (g: VireGlassGeometry) => Math.max(Math.min(g.width, g.height) / 2, 1);\n\n/** Viewport margin for a drop that overshoots past the finger: a fraction of the element's half\n * size beyond the drag travel itself. The drop overshoots the body by no more than `dragLimit`,\n * and stitching adds smoothing on top of that — this term covers it too. */\nexport const MAX_STRETCH = 0.34;\n\n/**\n * How big this element reads, for the purpose of how thick its glass is.\n *\n * Not `halfMinDp`, which is half the narrower side and is the right measure for a bevel limit or a\n * contact radius but not for this: a 390x420 sheet and a 390x780 one have the same narrow side and\n * are not the same surface. Not the area either: a 900x8 rule has more area than a toolbar button\n * and is the thinnest glass in the interface.\n *\n * The geometric mean of the two respects both facts — the narrow side caps how much glass there\n * can be, and among shapes that share it, more surface reads as more glass. Derived, not\n * calibrated: it introduces no constant.\n */\nexport const sizeDp = (g: VireGlassGeometry) =>\n Math.sqrt(halfMinDp(g) * Math.max(Math.sqrt(g.width * g.height) / 2, 1));\n\n/**\n * Bigger element, thicker glass: a stronger lens, a deeper shadow (219 @6:36 — as glass \"flexes and\n * morphs to larger sizes, it simulates a thicker material with deeper shadows and more pronounced\n * lensing and refraction effects\"). Bevel and material thickness are specified for an element at a\n * reference size and grow as the square root of it.\n *\n * The ceiling is approached, never reached. A hard clip made every surface above it the same glass,\n * and above it was where the interface lives: a half-open sheet, a full-screen sheet, an iPad\n * sidebar and a 900px Mac panel all sat at exactly 2.4. The reference names sidebars as the LARGE\n * end of the range, so a model that stops responding before them has stopped obeying it.\n *\n * The curve below is the same square root; the join at `gainMin` is continuous in value and in\n * slope, and the asymptote is `gainMax` exactly as before. Both constants keep their values —\n * what changed is that saturation is smooth instead of a clip.\n */\nexport const sizeGain = (g: VireGlassGeometry) => {\n const raw = Math.sqrt(sizeDp(g) / SIZE.referenceDp);\n if (raw <= SIZE.gainMin) return SIZE.gainMin;\n const span = SIZE.gainMax - SIZE.gainMin;\n return SIZE.gainMin + span * (1 - Math.exp(-(raw - SIZE.gainMin) / span));\n};\n\n/** A bevel wider than this fraction of the half-size breaks the SDF: the roundings converge in\n * the middle. */\nexport const MAX_BEVEL_FRACTION = SIZE.maxBevelFraction;\n\nexport const bevelFraction = (g: VireGlassGeometry, o: VireGlassOptics) =>\n Math.min(MAX_BEVEL_FRACTION, (o.bevelDp * sizeGain(g)) / halfMinDp(g));\n\nexport const bevelDp = (g: VireGlassGeometry, o: VireGlassOptics) =>\n Math.max(bevelFraction(g, o) * halfMinDp(g), 1);\n\nexport const thicknessDp = (g: VireGlassGeometry, o: VireGlassOptics) =>\n o.thicknessDp * sizeGain(g);\n\n/** Glass height right at the silhouette, a fraction of thickness: without it there's nothing at\n * the edge to bend the ray. */\nexport const RIM_FRACTION = 0.85;\n\nexport const rimDp = (g: VireGlassGeometry, o: VireGlassOptics) => thicknessDp(g, o) * RIM_FRACTION;\n\n/** Bigger element, deeper and wider shadow (M 7:14). */\nexport const shadowReachDp = (g: VireGlassGeometry) =>\n Math.min(Math.max(halfMinDp(g) * 0.65, 6), 36);\n\n/** How far the second morph shape reaches past the first one's bounds. Without this margin the\n * fused shape gets clipped by the canvas edge and the experiment shows the wrong thing. */\nexport function morphReachDp(\n g: VireGlassGeometry,\n morph?: { offsetX: number; offsetY: number; width: number; height: number; smoothing: number },\n): number {\n if (!morph || morph.smoothing <= 0) return 0;\n return Math.max(\n 0,\n Math.abs(morph.offsetX) + morph.width / 2 - g.width / 2,\n Math.abs(morph.offsetY) + morph.height / 2 - g.height / 2,\n );\n}\n\n/**\n * Quantization step for the margin. The margin only needs to cover the sampling — its exact value\n * doesn't matter, but changing it every frame is expensive: the native lens view gets re-laid-out\n * and re-sets its `RenderEffect`. While dragging a panel or morphing, the margin used to drift\n * continuously, and that produced visible stutter. Round up to the step — size changes rarely.\n */\nconst PAD_STEP = 8;\nconst quantise = (v: number) => Math.ceil(v / PAD_STEP) * PAD_STEP;\n\n/**\n * Margin around the lens view on each side: beyond it, the shader has nothing to sample.\n *\n * FOUR things have to fit in here. The refraction sample with aberrations. The blur radius —\n * it also samples around the point. The gather radius — the rim picks it up from outside the\n * shape, and without the margin it goes black exactly where it should be catching the\n * surroundings. And drag travel: the lens view moves with the finger, but its bounds don't, and\n * the sample falls off the edge, which is how layers from under the glass leak through.\n */\nexport function lensPadDp(\n g: VireGlassGeometry,\n o: VireGlassOptics,\n morph?: Parameters<typeof morphReachDp>[1],\n dragLimit = 0,\n): number {\n const sampling = Math.max(o.blur, o.gatherRadiusDp);\n const stretch = halfMinDp(g) * MAX_STRETCH;\n return quantise(sampling + dragLimit + stretch + morphReachDp(g, morph) + 2);\n}\n\n/**\n * Margin around the surface canvas: the shadow reaches outside the shape, and dragging shifts it\n * further still.\n *\n * The multiplier is smaller than the shadow's full reach: under the finger, the shader for a\n * rising element multiplies the radius by 1.55, and the very end of that tail falls outside the\n * margin. Left that way on purpose — at the margin's edge it's at 1.2% alpha (2.5% before the\n * shadow was softened), and raising the multiplier means growing every surface view's area. If\n * that tail ever shows up on a device, fix it here.\n */\nexport function surfacePadDp(\n g: VireGlassGeometry,\n dragLimit = 0,\n morph?: Parameters<typeof morphReachDp>[1],\n): number {\n return quantise(shadowReachDp(g) * 1.2 + dragLimit + morphReachDp(g, morph) + 2);\n}\n\n/**\n * SHADOW DENSITY BY WHAT'S UNDER THE ELEMENT. Reference 219 @11:47, verbatim: \"increases the\n * opacity of its shadow when it is over text… lowers the opacity of its shadow when it is over a\n * solid light background.\" This is about content BEHIND THE ELEMENT, so it's a single value per\n * element, not a field over the shadow's area.\n *\n * The law lives here, not at the bench: while it was baked into the web renderer, shadows on\n * Android never adapted at all. The endpoints were checked against the reference by the depth of\n * the dip under the element — docs/benchmarks.md.\n */\nexport function shadowOpacity(busy: number): number {\n return (\n SHADOW_DENSITY.flat +\n Math.min(Math.max(busy, 0) * SHADOW_DENSITY.busyGain, SHADOW_DENSITY.busyRise)\n );\n}\n\n/** Same thing for platforms where the measurement flows through state: quantized coarsely, like\n * the ambient color, otherwise every probe sample would trigger a surface repaint. */\nexport function shadowOpacityFrom(sample: { busy: number }): number {\n return Math.round(shadowOpacity(sample.busy) / SHADOW_DENSITY.step) * SHADOW_DENSITY.step;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/geometry.ts"],"names":[],"mappings":"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","file":"chunk-SLRPHDV7.js","sourcesContent":["import type { VireGlassOptics } from './material';\nimport { SHADOW_DENSITY, SIZE } from './law';\n\n/** Glass shape in dp. Circle and capsule are special cases of a rounded rectangle. */\nexport type VireGlassGeometry = {\n width: number;\n height: number;\n cornerRadius: number;\n};\n\nexport const circleGeometry = (size: number): VireGlassGeometry => ({\n width: size,\n height: size,\n cornerRadius: size / 2,\n});\n\nexport const capsuleGeometry = (width: number, height: number): VireGlassGeometry => ({\n width,\n height,\n cornerRadius: Math.min(width, height) / 2,\n});\n\nexport const roundedRectGeometry = (\n width: number,\n height: number,\n cornerRadius: number,\n): VireGlassGeometry => ({ width, height, cornerRadius });\n\nexport const halfMinDp = (g: VireGlassGeometry) => Math.max(Math.min(g.width, g.height) / 2, 1);\n\n/** Viewport margin for a drop that overshoots past the finger: a fraction of the element's half\n * size beyond the drag travel itself. The drop overshoots the body by no more than `dragLimit`,\n * and stitching adds smoothing on top of that — this term covers it too. */\nexport const MAX_STRETCH = 0.34;\n\n/**\n * How big this element reads, for the purpose of how thick its glass is.\n *\n * Not `halfMinDp`, which is half the narrower side and is the right measure for a bevel limit or a\n * contact radius but not for this: a 390x420 sheet and a 390x780 one have the same narrow side and\n * are not the same surface. Not the area either: a 900x8 rule has more area than a toolbar button\n * and is the thinnest glass in the interface.\n *\n * The geometric mean of the two respects both facts — the narrow side caps how much glass there\n * can be, and among shapes that share it, more surface reads as more glass. Derived, not\n * calibrated: it introduces no constant.\n */\nexport const sizeDp = (g: VireGlassGeometry) =>\n Math.sqrt(halfMinDp(g) * Math.max(Math.sqrt(g.width * g.height) / 2, 1));\n\n/**\n * Bigger element, thicker glass: a stronger lens, a deeper shadow (219 @6:36 — as glass \"flexes and\n * morphs to larger sizes, it simulates a thicker material with deeper shadows and more pronounced\n * lensing and refraction effects\"). Bevel and material thickness are specified for an element at a\n * reference size and grow as the square root of it.\n *\n * The ceiling is approached, never reached. A hard clip made every surface above it the same glass,\n * and above it was where the interface lives: a half-open sheet, a full-screen sheet, an iPad\n * sidebar and a 900px Mac panel all sat at exactly 2.4. The reference names sidebars as the LARGE\n * end of the range, so a model that stops responding before them has stopped obeying it.\n *\n * The curve below is the same square root; the join at `gainMin` is continuous in value and in\n * slope, and the asymptote is `gainMax` exactly as before. Both constants keep their values —\n * what changed is that saturation is smooth instead of a clip.\n */\nexport const sizeGain = (g: VireGlassGeometry) => {\n const raw = Math.sqrt(sizeDp(g) / SIZE.referenceDp);\n if (raw <= SIZE.gainMin) return SIZE.gainMin;\n const span = SIZE.gainMax - SIZE.gainMin;\n return SIZE.gainMin + span * (1 - Math.exp(-(raw - SIZE.gainMin) / span));\n};\n\n/** A bevel wider than this fraction of the half-size breaks the SDF: the roundings converge in\n * the middle. */\nexport const MAX_BEVEL_FRACTION = SIZE.maxBevelFraction;\n\nexport const bevelFraction = (g: VireGlassGeometry, o: VireGlassOptics) =>\n Math.min(MAX_BEVEL_FRACTION, (o.bevelDp * sizeGain(g)) / halfMinDp(g));\n\nexport const bevelDp = (g: VireGlassGeometry, o: VireGlassOptics) =>\n Math.max(bevelFraction(g, o) * halfMinDp(g), 1);\n\nexport const thicknessDp = (g: VireGlassGeometry, o: VireGlassOptics) =>\n o.thicknessDp * sizeGain(g);\n\n/** Glass height right at the silhouette, a fraction of thickness: without it there's nothing at\n * the edge to bend the ray. */\nexport const RIM_FRACTION = 0.85;\n\nexport const rimDp = (g: VireGlassGeometry, o: VireGlassOptics) => thicknessDp(g, o) * RIM_FRACTION;\n\n/** Bigger element, deeper and wider shadow (M 7:14). */\nexport const shadowReachDp = (g: VireGlassGeometry) =>\n Math.min(Math.max(halfMinDp(g) * 0.65, 6), 36);\n\n/** How far the second morph shape reaches past the first one's bounds. Without this margin the\n * fused shape gets clipped by the canvas edge and the experiment shows the wrong thing. */\nexport function morphReachDp(\n g: VireGlassGeometry,\n morph?: { offsetX: number; offsetY: number; width: number; height: number; smoothing: number },\n): number {\n if (!morph || morph.smoothing <= 0) return 0;\n return Math.max(\n 0,\n Math.abs(morph.offsetX) + morph.width / 2 - g.width / 2,\n Math.abs(morph.offsetY) + morph.height / 2 - g.height / 2,\n );\n}\n\n/**\n * Quantization step for the margin. The margin only needs to cover the sampling — its exact value\n * doesn't matter, but changing it every frame is expensive: the native lens view gets re-laid-out\n * and re-sets its `RenderEffect`. While dragging a panel or morphing, the margin used to drift\n * continuously, and that produced visible stutter. Round up to the step — size changes rarely.\n */\nconst PAD_STEP = 8;\nconst quantise = (v: number) => Math.ceil(v / PAD_STEP) * PAD_STEP;\n\n/**\n * Margin around the lens view on each side: beyond it, the shader has nothing to sample.\n *\n * FOUR things have to fit in here. The refraction sample with aberrations. The blur radius —\n * it also samples around the point. The gather radius — the rim picks it up from outside the\n * shape, and without the margin it goes black exactly where it should be catching the\n * surroundings. And drag travel: the lens view moves with the finger, but its bounds don't, and\n * the sample falls off the edge, which is how layers from under the glass leak through.\n */\nexport function lensPadDp(\n g: VireGlassGeometry,\n o: VireGlassOptics,\n morph?: Parameters<typeof morphReachDp>[1],\n dragLimit = 0,\n): number {\n const sampling = Math.max(o.blur, o.gatherRadiusDp);\n const stretch = halfMinDp(g) * MAX_STRETCH;\n return quantise(sampling + dragLimit + stretch + morphReachDp(g, morph) + 2);\n}\n\n/**\n * Margin around the surface canvas: the shadow reaches outside the shape, and dragging shifts it\n * further still.\n *\n * The multiplier is smaller than the shadow's full reach: under the finger, the shader for a\n * rising element multiplies the radius by 1.55, and the very end of that tail falls outside the\n * margin. Left that way on purpose — at the margin's edge it's at 1.2% alpha (2.5% before the\n * shadow was softened), and raising the multiplier means growing every surface view's area. If\n * that tail ever shows up on a device, fix it here.\n */\nexport function surfacePadDp(\n g: VireGlassGeometry,\n dragLimit = 0,\n morph?: Parameters<typeof morphReachDp>[1],\n): number {\n return quantise(shadowReachDp(g) * 1.2 + dragLimit + morphReachDp(g, morph) + 2);\n}\n\n/**\n * SHADOW DENSITY BY WHAT'S UNDER THE ELEMENT. Reference 219 @11:47, verbatim: \"increases the\n * opacity of its shadow when it is over text… lowers the opacity of its shadow when it is over a\n * solid light background.\" This is about content BEHIND THE ELEMENT, so it's a single value per\n * element, not a field over the shadow's area.\n *\n * The law lives here, not at the bench: while it was baked into the web renderer, shadows on\n * Android never adapted at all. The endpoints were checked against the reference by the depth of\n * the dip under the element — docs/benchmarks.md.\n */\nexport function shadowOpacity(busy: number): number {\n return (\n SHADOW_DENSITY.flat +\n Math.min(Math.max(busy, 0) * SHADOW_DENSITY.busyGain, SHADOW_DENSITY.busyRise)\n );\n}\n\n/** Same thing for platforms where the measurement flows through state: quantized coarsely, like\n * the ambient color, otherwise every probe sample would trigger a surface repaint. */\nexport function shadowOpacityFrom(sample: { busy: number }): number {\n return Math.round(shadowOpacity(sample.busy) / SHADOW_DENSITY.step) * SHADOW_DENSITY.step;\n}\n"]}
|
package/dist/dom.cjs
CHANGED
|
@@ -60,7 +60,14 @@ var BODY = {
|
|
|
60
60
|
darkSideFrom: 0.42,
|
|
61
61
|
darkSideTo: 0.58,
|
|
62
62
|
/** Ceiling on any single demand, so one requirement cannot make the glass opaque on its own. */
|
|
63
|
-
maxDemand: 0.92
|
|
63
|
+
maxDemand: 0.92,
|
|
64
|
+
/**
|
|
65
|
+
* And a tighter one for presence specifically. It is a FLOOR on visibility, not a licence to go
|
|
66
|
+
* opaque: wherever the body's tint sits close to the backdrop, the density that would deliver the
|
|
67
|
+
* separation is the ceiling, and glass at `maxDemand` has stopped being a window. Better to
|
|
68
|
+
* separate as far as transparency allows and no further. UNMEASURED.
|
|
69
|
+
*/
|
|
70
|
+
presenceDemand: 0.5
|
|
64
71
|
};
|
|
65
72
|
var SHADOW = {
|
|
66
73
|
/**
|
|
@@ -929,10 +936,18 @@ function sampleColorAt(el, x, y) {
|
|
|
929
936
|
}
|
|
930
937
|
const composited = compositeLayers(layers);
|
|
931
938
|
if (composited) return composited;
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
939
|
+
return canvasColor(el);
|
|
940
|
+
}
|
|
941
|
+
function canvasColor(el) {
|
|
942
|
+
const doc = el.ownerDocument;
|
|
943
|
+
for (const node of [doc.documentElement, doc.body]) {
|
|
944
|
+
if (!node) continue;
|
|
945
|
+
const bg = parseColor(getComputedStyle(node).backgroundColor);
|
|
946
|
+
if (bg && bg.a > 1e-3) return bg.a >= 0.999 ? bg : compositeLayers([bg, WHITE]);
|
|
947
|
+
}
|
|
948
|
+
return WHITE;
|
|
935
949
|
}
|
|
950
|
+
var WHITE = { r: 255, g: 255, b: 255, a: 1 };
|
|
936
951
|
function probeBackdrop(el, opts = {}) {
|
|
937
952
|
const cols = opts.cols ?? GRID_COLS;
|
|
938
953
|
const rows = opts.rows ?? GRID_ROWS;
|
|
@@ -1046,15 +1061,17 @@ function resolveBody(optics, sample, ink) {
|
|
|
1046
1061
|
function withPresence(body, optics, sample) {
|
|
1047
1062
|
if (optics.presence <= 0) return body;
|
|
1048
1063
|
const mean = clamp3(sample.luma, 0, 1);
|
|
1049
|
-
const
|
|
1050
|
-
const
|
|
1051
|
-
const separation = Math.abs(
|
|
1064
|
+
const above = Math.abs(body.tintLuma - mean) > 1e-4 ? body.tintLuma > mean : mean < 0.5;
|
|
1065
|
+
const pole = above ? TINT_LIGHT : TINT_DARK;
|
|
1066
|
+
const separation = Math.min(optics.presence, Math.abs(pole - mean));
|
|
1052
1067
|
const reached = Math.abs(body.tintLuma - mean) * body.density;
|
|
1053
1068
|
if (reached >= separation) return body;
|
|
1054
|
-
const needed = separation / Math.max(Math.abs(
|
|
1069
|
+
const needed = separation / Math.max(Math.abs(pole - mean), 1e-4);
|
|
1055
1070
|
return {
|
|
1056
|
-
density: Math.max(body.density, Math.min(needed,
|
|
1057
|
-
|
|
1071
|
+
density: Math.max(body.density, Math.min(needed, BODY.presenceDemand)),
|
|
1072
|
+
// Left where the tint law put it, unless it landed exactly on the backdrop — which is the
|
|
1073
|
+
// case that used to be created here rather than avoided.
|
|
1074
|
+
tintLuma: Math.abs(body.tintLuma - mean) > 1e-4 ? body.tintLuma : pole
|
|
1058
1075
|
};
|
|
1059
1076
|
}
|
|
1060
1077
|
|
|
@@ -1602,15 +1619,22 @@ function waveImpulse(g) {
|
|
|
1602
1619
|
return halfMinDp(g) * MAX_STRETCH * WAVE_OF_TRAVEL;
|
|
1603
1620
|
}
|
|
1604
1621
|
var STYLE_ATTR = "data-vireglass-styles";
|
|
1605
|
-
function
|
|
1606
|
-
|
|
1622
|
+
function hostFor(el) {
|
|
1623
|
+
const root = el.getRootNode();
|
|
1624
|
+
return root instanceof ShadowRoot ? root : el.ownerDocument ?? document;
|
|
1625
|
+
}
|
|
1626
|
+
function styleParent(host) {
|
|
1627
|
+
return host instanceof ShadowRoot ? host : host.head;
|
|
1628
|
+
}
|
|
1629
|
+
function ensureRimStyles(host) {
|
|
1630
|
+
if (host.querySelector(`style[${STYLE_ATTR}]`)) return;
|
|
1607
1631
|
const style = document.createElement("style");
|
|
1608
1632
|
style.setAttribute(STYLE_ATTR, "");
|
|
1609
1633
|
style.textContent = `[${GLASS_ATTR}]::after{content:'';position:absolute;inset:0;border-radius:inherit;pointer-events:none;padding:var(--vireglass-rim-width,1px);background:var(--vireglass-rim,none);-webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);mask-composite:exclude}[${GLASS_ATTR}]::before{content:'';position:absolute;inset:0;border-radius:inherit;pointer-events:none;background:var(--vireglass-glow,none);opacity:var(--vireglass-glow-opacity,0)}`;
|
|
1610
|
-
|
|
1634
|
+
styleParent(host).appendChild(style);
|
|
1611
1635
|
}
|
|
1612
|
-
function getFilterHost() {
|
|
1613
|
-
const existing =
|
|
1636
|
+
function getFilterHost(host) {
|
|
1637
|
+
const existing = host.querySelector(`svg[${FILTER_HOST_ATTR}]`);
|
|
1614
1638
|
if (existing) return existing;
|
|
1615
1639
|
const svg = document.createElementNS(SVG_NS, "svg");
|
|
1616
1640
|
svg.setAttribute(FILTER_HOST_ATTR, "");
|
|
@@ -1620,7 +1644,7 @@ function getFilterHost() {
|
|
|
1620
1644
|
svg.style.position = "absolute";
|
|
1621
1645
|
svg.style.pointerEvents = "none";
|
|
1622
1646
|
svg.appendChild(document.createElementNS(SVG_NS, "defs"));
|
|
1623
|
-
|
|
1647
|
+
(host instanceof ShadowRoot ? host : host.body).appendChild(svg);
|
|
1624
1648
|
return svg;
|
|
1625
1649
|
}
|
|
1626
1650
|
function buildFilterElement(id, mapUrl, optics, scale, spectral) {
|
|
@@ -1658,6 +1682,7 @@ function buildFilterElement(id, mapUrl, optics, scale, spectral) {
|
|
|
1658
1682
|
pass.setAttribute("xChannelSelector", "R");
|
|
1659
1683
|
pass.setAttribute("yChannelSelector", "G");
|
|
1660
1684
|
pass.setAttribute("result", `${name}raw`);
|
|
1685
|
+
pass.setAttribute("data-vireglass-ratio", String(scale > 0 ? chScale / scale : 1));
|
|
1661
1686
|
const only = document.createElementNS(SVG_NS, "feColorMatrix");
|
|
1662
1687
|
only.setAttribute("in", `${name}raw`);
|
|
1663
1688
|
only.setAttribute("type", "matrix");
|
|
@@ -1764,7 +1789,8 @@ function attachGlass(el, opts = {}) {
|
|
|
1764
1789
|
apply();
|
|
1765
1790
|
});
|
|
1766
1791
|
warnIfNested(el);
|
|
1767
|
-
|
|
1792
|
+
const host = hostFor(el);
|
|
1793
|
+
ensureRimStyles(host);
|
|
1768
1794
|
el.setAttribute(GLASS_ATTR, "");
|
|
1769
1795
|
const positionWasStatic = getComputedStyle(el).position === "static";
|
|
1770
1796
|
if (positionWasStatic) el.style.setProperty("position", "relative");
|
|
@@ -1792,7 +1818,7 @@ function attachGlass(el, opts = {}) {
|
|
|
1792
1818
|
const key = `a${appear.toFixed(3)}|m${morphKey()}|${geometry.width}x${geometry.height}r${geometry.cornerRadius}@${devicePixelRatio()}:${opticsNow.refraction}:${opticsNow.refractionScale}:${opticsNow.bevelDp}:${opticsNow.blur}`;
|
|
1793
1819
|
if (key !== mapKey || !filterEl) {
|
|
1794
1820
|
const map = cached(`d|${key}`, () => buildDisplacementMap(opticsNow, geometry, devicePixelRatio(), morphState()));
|
|
1795
|
-
const defs = getFilterHost().querySelector("defs");
|
|
1821
|
+
const defs = getFilterHost(host).querySelector("defs");
|
|
1796
1822
|
const hue = hasSpectralEdge(opticsNow) ? cached(`h|${key}`, () => buildSpectralMap(opticsNow, geometry, devicePixelRatio())) : null;
|
|
1797
1823
|
const next = buildFilterElement(id, map.url, opticsNow, map.scale, hue);
|
|
1798
1824
|
if (filterEl?.parentNode) defs.replaceChild(next, filterEl);
|
|
@@ -1858,9 +1884,11 @@ function attachGlass(el, opts = {}) {
|
|
|
1858
1884
|
});
|
|
1859
1885
|
if (filterEl) {
|
|
1860
1886
|
const feImage = filterEl.querySelector("feImage");
|
|
1861
|
-
const feDisplace = filterEl.querySelector("feDisplacementMap");
|
|
1862
1887
|
feImage?.setAttribute("href", map.url);
|
|
1863
|
-
|
|
1888
|
+
for (const pass of filterEl.querySelectorAll("feDisplacementMap")) {
|
|
1889
|
+
const ratio = Number(pass.getAttribute("data-vireglass-ratio") ?? 1);
|
|
1890
|
+
pass.setAttribute("scale", String(map.scale * (Number.isFinite(ratio) ? ratio : 1)));
|
|
1891
|
+
}
|
|
1864
1892
|
}
|
|
1865
1893
|
mapKey = "";
|
|
1866
1894
|
paintGlow(sampleOut.touchX, sampleOut.touchY, sampleOut.active * base.edgeLight, geometry);
|