Liquid Glass for the web: the rim refraction, light and motion of iOS 27 glass on any rounded element. React bindings and pill tabs. MIT.
| describe("toneFilters", () => { |
| 5 | it("writes each layer's filter as literal values", () => { |
| 6 | const filters = toneFilters(TONES.regular.dark); |
| 7 | expect(filters.body.full).toBe("blur(1px) saturate(1.325) contrast(0.781) brightness(0.7915)"); |
| 8 | expect(filters.shade.full).toBe("brightness(1.33)"); |
| 9 | expect(filters.glow.full).toBe("saturate(1.1) brightness(1.3)"); |
| 10 | expect(filters.edge.full).toBe("brightness(0.66)"); |
| 11 | expect(filters.highlight.full).toBe("contrast(1.1) brightness(2.4)"); |
| 12 | }); |
| 13 |
| 14 | it("diffuses floating glass more", () => { |
| 15 | expect(toneFilters(TONES.regular.dark, true).body.full).toBe("blur(6px) saturate(1.45) contrast(0.724) brightness(0.7445)"); |
| 16 | expect(toneFilters(TONES.regular.light, true).body.full).toBe("blur(4.5px) saturate(1.35) contrast(0.5) brightness(1.355)"); |
| 17 | }); |
| 18 |
| 19 | it("drops identity functions and clears the same list", () => { |
| 20 | expect(toneFilters(TONES.regular.light).body).toEqual({ full: "blur(1px) saturate(1.3)", clear: "blur(0px) saturate(1)" }); |
| 21 | expect(toneFilters(TONES.clear.dark).body).toEqual({ |
| 22 | full: "saturate(1.05) contrast(0.97) brightness(0.96)", |
| 23 | clear: "saturate(1) contrast(1) brightness(1)", |
| 24 | }); |
| 25 | expect(toneFilters(TONES.clear.light).glow).toEqual({ full: "brightness(1.02)", clear: "brightness(1)" }); |
| 26 | }); |
| 27 |
| 28 | it("leaves a layer unfiltered when every value is identity", () => { |
| 29 | expect(toneFilters(TONES.lens.light).body).toEqual({ full: "none", clear: "none" }); |
| 30 | }); |
| 31 | }); |
| 32 |
| 33 | describe("levelTone", () => { |
| 34 | it("runs from More Clear through the default to More Tinted", () => { |
| 35 | expect(levelTone("light", 0)).toMatchObject({ blur: 0, tint: 0.04 }); |
| 36 | expect(levelTone("light", 0.5)).toMatchObject({ blur: 1, saturate: 1.3, tint: 0.175 }); |
| 37 | expect(levelTone("light", 1)).toMatchObject({ blur: 5, contrast: 0.744, brightness: 1.173, tint: 0.4 }); |
| 38 | expect(levelTone("dark", 1).brightness).toBeLessThan(levelTone("dark", 0).brightness); |
| 39 | }); |
| 40 |
| 41 | it("keeps floating glass frosted at every level", () => { |
| 42 | expect(levelTone("dark", 0).floatingBlur).toBeGreaterThan(0); |
| 43 | expect(levelTone("light", 0).floatingBlur).toBeGreaterThan(0); |
| 44 | }); |
| 45 |
| 46 | it("clamps levels outside 0–1", () => { |
| 47 | expect(levelTone("dark", -1)).toEqual(levelTone("dark", 0)); |
| 48 | expect(levelTone("dark", 3)).toEqual(levelTone("dark", 1)); |
| 49 | }); |
| 50 | }); |
| 51 |
| 52 | describe("resolveTone", () => { |
| 53 | it("overrides only the given appearance", () => { |
| 54 | const overrides = { dark: { tint: 0.5 } }; |
| 55 | expect(resolveTone("regular", "dark", overrides).tint).toBe(0.5); |
| 56 | expect(resolveTone("regular", "light", overrides).tint).toBe(TONES.regular.light.tint); |
| 57 | }); |
| 58 |
| 59 | it("takes regular glass's level, and leaves the other variants as they are", () => { |
| 60 | expect(resolveTone("regular", "light", undefined, 1).tint).toBe(0.4); |
| 61 | expect(resolveTone("clear", "light", undefined, 1)).toEqual(TONES.clear.light); |
| 62 | }); |
| 63 | }); |
| 64 |
| 65 | describe("toneWash", () => { |
| 66 | it("mixes the tint by the tone's share", () => { |
| 67 | expect(toneWash(TONES.regular.light, "var(--surface)")).toBe("color-mix(in srgb, var(--surface) 17.5%, transparent)"); |
| 68 | expect(toneWash(TONES.regular.dark, "black")).toBe("color-mix(in srgb, black 23.5%, transparent)"); |
| 69 | expect(toneWash(TONES.regular.dark, "black", true)).toBe("color-mix(in srgb, black 31%, transparent)"); |
| 70 | expect(toneWash(TONES.lens.dark, "black")).toBe("transparent"); |
| 71 | }); |
| 72 | }); |
| 73 |
| 74 | describe("hueMatrix", () => { |
| 75 | it("leaves neutral colours untinted", () => { |
| 76 | expect(hueMatrix([128, 128, 128], 1)).toBeNull(); |
| 77 | expect(hueMatrix([255, 255, 255], 1)).toBeNull(); |
| 78 | }); |
| 79 |
| 80 | it("keeps alpha and shifts channels toward the hue", () => { |
| 81 | const rows = hueMatrix([20, 30, 60], 1)?.split(" ").map(Number) ?? []; |
| 82 | expect(rows).toHaveLength(20); |
| 83 | expect(rows.slice(15)).toEqual([0, 0, 0, 1, 0]); |
| 84 | // Blue gains, red loses: the blue row's own weight rises above red's. |
| 85 | expect(rows[12]).toBeGreaterThan(rows[0] ?? 0); |
| 86 | }); |
| 87 |
| 88 | it("scales with strength", () => { |
| 89 | const full = hueMatrix([20, 30, 60], 1)?.split(" ").map(Number) ?? []; |
| 90 | const half = hueMatrix([20, 30, 60], 0.5)?.split(" ").map(Number) ?? []; |
| 91 | expect(Math.abs((half[12] ?? 0) - 1)).toBeCloseTo(Math.abs((full[12] ?? 0) - 1) / 2, 3); |
| 92 | }); |
| 93 | }); |
| 94 |