Skip to main content

major ui overhaul! - the physics behind my site's new ui

/3 min read

rebuilt the entire ui. every card, the navbar, the search, all of it runs a 4-layer glass material with physics-based refraction. not just blur. the whole system is available as an npm packagenpm install @sohumsuthar/liquid-glass.

drag either lens around. the frosted one has light blur. the clear one shows the raw displacement - how real curved glass bends light at the edges.

backdrop-filter: blur(20px) is frosted glass. it scatters light uniformly. real glass bends it. the edges of a curved panel displace the content behind them inward, the flat interior doesn't. i wanted that.

the math

light bends when it enters a denser medium. snell's law:

$$n_1 \sin(\theta_1) = n_2 \sin(\theta_2)$$

air is $n_1 = 1.0$, glass is $n_2 = 1.5$. for thin glass viewed head-on, this produces a lateral displacement of the background at the edges.

the displacement depends on the surface slope. i model the glass cross-section as a height function from the outer edge ($x=0$) to the flat interior ($x=1$):

$$f(x) = \sqrt[4]{1 - (1-x)^4}$$

convex squircle. same curve apple uses. smoother flat-to-curve transition than a circular arc $f(x) = \sqrt{1 - (1-x)^2}$, no visible seam when stretched into rectangles.

the slope $f'(x)$ feeds into the displacement magnitude:

$$d(x) = \frac{f'(x)}{1 + f'(x)^2}$$

peaks at the edge, zero at the interior. 127 precomputed samples along one radius, matching the 8-bit channel limit of svg displacement maps.

the displacement map

for every pixel in a 512x512 image:

  1. compute distance to the nearest border of a rounded rectangle (signed distance field)

  2. if inside the bezel zone, look up displacement magnitude from the precomputed table

  3. direction = SDF gradient $\nabla\text{SDF}(p)$, always perpendicular to the nearest edge

  4. encode as RGB: $R = 128 + d_x \cdot 127$, $G = 128 + d_y \cdot 127$, 128 = neutral

the result is a png with colored bands around the bezel and gray everywhere else. inlined as base64 in an svg filter, then chained:

backdrop-filter: blur(28px) saturate(180%) url(#lg-refract);

scale 0.06 = 6% of element width. ~42px displacement on a 700px panel. chrome only - safari/firefox fall back to blur without refraction.

the layers

four layers per glass surface:

  • effect - backdrop-filter (blur + saturation + displacement on macros)

  • tint - solid $\text{rgba}(28,28,32,0.72)$. glass needs a visible base color or it vanishes on black backgrounds

  • shine - four inset box-shadows. top highlight 0.18, bottom shadow 0.25. that $\approx 1:1.4$ ratio reads as convex

  • content - z-indexed above the material

plus ::before for static noise grain and ::after for cursor-tracking glow.

keeping it fast

20+ backdrop-filter elements over an animated particle canvas at 120fps:

  • content-visibility: auto skips off-screen glass entirely

  • svg displacement only on the 3-5 macro wrappers, not every card

  • blur capped at 28px (safari downsamples past 25)

  • particle canvas throttled to 30fps so backdrop-filter can cache between updates

  • touch devices skip all cursor/scroll/particle effects

  • fps guard auto-disables glass if a device sustains below 24fps for 4 seconds

there's a toggle in the bottom-left to turn glass off entirely.

code is in the repo. reusable system at sohumsuthar/liquid-glass, published on npm as @sohumsuthar/liquid-glass.