# Glass lens (goodtabs UI)

A round lens that follows the pointer over an image and refracts it through a bent rim.

## Install

```bash
npx shadcn@latest add https://ui.goodtabs.co/r/glass-lens.json
```

Or, with the @goodtabs namespace configured in components.json: `npx shadcn@latest add @goodtabs/glass-lens`

## Usage

```tsx
import { GlassLens } from "@/components/ui/glass-lens"

<GlassLens src="/photo.jpg" alt="What the photo shows" radius={96} className="rounded-lg" />
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| src | `string` |  | The image. Same origin, or served with CORS headers. |
| alt | `string` |  | Alt text for the real image, which stays in the DOM. |
| radius | `number` | `96` | Lens radius in CSS pixels. Capped at 38 percent of the short side of the box. |
| zoom | `number` | `1.6` | Magnification through the middle. |
| rest | `readonly [number, number]` | `[0.618, 0.382]` | Where the lens rests, as fractions of the width and height from the top left. |
| lensLabel | `string` | `"Lens position, move it with the arrow keys"` | Accessible name of the lens control. |

## When to use

- A detailed image people want to look into, such as a texture or a plan
- A landing page piece where the pointer is the invitation
- Beside copy that names a detail worth finding

## When not to use

- As a product zoom that must show exact pixels: the rim distorts on purpose
- Several times on one page, since each lens holds a WebGL context
- On small thumbnails, where the lens covers most of the image

## Notes for agents

- It defaults to a 3:2 box with container-type: size, so the box needs a definite height. Pass aspect-auto and a height to fill something else
- The lens is a role=slider control: arrow keys move it by 16px, Shift by 64px and Home sends it back to rest. Name it in another language with lensLabel
- Only a fine pointer moves it, so touch keeps native scrolling. Under reduced motion it rests and arrow keys jump it with no glide
- Without WebGL or JavaScript a CSS lens shows the same crop at the rest position. It installs components/ui/shader-canvas.tsx and uses no GSAP

## The maths

- The dome is the superellipse h = (1 - r⁸)^(1/8). Its slope is under 0.1 at 70 percent of the radius and reaches 1.8 at 95, so the bend stays in a thin rim.
- Refraction is Snell's law with n = 1.5, a typical index for glass, so the shader passes eta = 1 / 1.5 = 0.667.
- The rest position is 0.618 and 0.382 of the box, the golden section on each axis.
- Glide is 1 - e^(-12 dt), a time constant of 83ms that holds at any refresh rate.

Docs: https://ui.goodtabs.co/components/glass-lens