# Realistic emboss

Words and shapes pressed hard into tinted plaster, with the light catching every edge. Live at https://design.how/vault#emboss.

Install the skill, which carries the template, the brief and a check script:

```sh
npx skills add https://design.how/vault/emboss
```

## What it is

A white card holds three rounded plaster panels side by side, off white, yellow and violet. Into each one something is pressed, the word beep, a five-petal flower and the word boop, hard enough that the light catches a bright rim on one side of every stroke and a shadow on the other. The surface is a photographed plaster, so the grain and the small pits read as a real wall, and a grunge layer of fine scratches sits over the letters too, which keeps them from looking pasted on.

The relief is a height field, not geometry. Each panel's content is drawn white on a 2D canvas, once crisp and once blurred with a canvas filter at that panel's bevel width, and the two are packed into the red and green channels of one texture, at most 1500 pixels wide. The fragment shader takes the slope of the blurred channel from four neighbouring samples, scales it by the panel's depth, and builds a normal from it. Its dot product with one fixed light, set at 73 degrees, splits into a highlight where it is positive and a shadow where it is negative, each weighted by the slope so flat plaster stays untouched.

The crisp channel darkens the pressed face by 14%, so the inside of a letter sits a little lower than the wall. Each panel samples its own clamped window of the plaster at its own zoom, so the three never show the same grain, and nothing tiles or seams. The pointer adds a faint wash centred on itself to the panel it is over. The card draws only when something changes and idles otherwise.

## How it works

- Rasterise each panel's word or SVG in white, once crisp and once blurred by the panel's bevel width with a 2D canvas filter, and pack crisp coverage into red and blurred height into green.
- In the fragment shader, take the slope of the height from four neighbours, scale it by depth, and build the normal (-slope.x, -slope.y, 1).
- Light it from one fixed direction. Add the positive part of N dot L as the highlight and subtract the negative part as the shadow, both times the slope length.
- Multiply a tinted, contrast-adjusted plaster sample by the panel brightness, darken the crisp face to 86%, and blend a grunge texture over it in overlay mode at 0.38.
- Draw only on a change and idle after the pointer wash settles; rebuild the height field in an idle callback, and only when its size or the face changed.
- Stop on webglcontextlost, and on destroy delete the program, the buffer and the three textures and lose the context.
- Under reduced motion, build the field and draw the pressed panels once.

## What you can change

- `STYLES` (beep, a flower, boop): Each panel's word or SVG, bevel width, depth, highlight, shadow, grain, brightness, tint and plaster window.
- `REST_ANGLE` (73 degrees): The direction the light comes from.
- `GRUNGE_AMT` (0.38): How strongly the scratch and grain overlay shows.
- `BUILTIN_SVGS` (eleven solid shapes): The shapes a panel can press instead of a word.

Last updated: 2026-11-02
