Text reveal

Two small serif text blocks that un-blur into focus through a cloudy mask, corner by corner, then clear and return.

Two short lines of dark serif text in the top-left corner and two in the bottom-right, on a pale warm-grey plate.
npx skills add https://design.how/vault/text-reveal

Installs the template, the brief and a check script as one skill.

What it is

A pale warm-grey plate with two small blocks of dark serif text, two lines in the top-left corner and two in the bottom-right, right-aligned. The text arrives through fog: a soft, wispy front sweeps from top-left to bottom-right, and every part of a letter starts blurred and drifting slightly and settles into focus as the front passes it, so the top-left block resolves first and the bottom-right after it. The pair holds for about a third of a second, breathing faintly, then clears the same way in reverse, a little quicker, and the next of three phrase pairs comes in.

The text is drawn once per pair into a canvas texture and revealed in one fragment shader. The mask is mostly a steepened diagonal, so the corners stage one after the other, textured with domain-warped fbm for tendrils and a fine stipple so the leading edge breaks into grain. Where the mask is ahead of the progress the pixel is hidden; near the front the texture is sampled with a 17-tap ring blur of up to 16 texels, scaled up a touch and drifted toward the centre, which decays to nothing as it resolves. A faint glow and a hair of colour split ride only on the front band.

Progress is a slightly overdamped spring toward 1 for the reveal and toward 0 for the clear, with a stiffer spring on the way out, so it accelerates and settles the same at any frame rate. Behind the canvas an eight-layer white radial wash drifts over 48 seconds and a fractal-noise grain sits at 5% opacity. The pointer leans each corner toward itself by its offset from that corner's anchor, and pushes the mask down near it so text wipes into focus under the hand.

How it works

  • Render each phrase pair once into a 2D canvas at the card size, fitting the font so no line is wider than about half the card, and upload it as a texture.
  • In one fragment shader over one quad, build the mask from a steepened diagonal plus domain-warped fbm and a fine stipple; flip the diagonal while clearing.
  • Hide pixels the front has not reached; near the front, sample with a ring blur, a small scale-up and a drift toward centre, all scaled by how unresolved the pixel is.
  • Add a faint glow and a slight per-channel offset only on the front band.
  • Drive progress with a damped spring toward 1 or 0, gated by thresholds into in, hold and out phases, with a stiffer spring out.
  • Ease the pointer and a hover weight; lean each corner by the cursor's offset from its anchor and push the mask down near the cursor.
  • Drift the white wash from the same loop, so it pauses with everything else.
  • Cap the pixel ratio at 2, stop the loop on webglcontextlost, and on destroy delete the program, buffer and texture and lose the context.
  • Under reduced motion, draw the first pair fully revealed, in focus, with the wash at rest.

What you can change

  • TEXT_PAIRS (three {top, bottom} pairs of two lines): The phrases. Keep lines short: a block spans at most about half the card.
  • BG, INK, EDGE (#fdfdfb, #242320, #f2f1ec): The plate, the text and the glow on the front.
  • HOLD_MS (320): How long the revealed pair lingers before it clears.
  • K_IN, K_OUT, DAMP (16, 22, 1.12): Spring stiffness in and out, and damping. Lower is slower and softer.
  • MAX_BLUR (16): Blur radius at the front, in texels.
  • PARALLAX_AMP (0.006): How far each corner leans toward the pointer.
  • WASH_SECONDS (48): How long the white wash takes to drift there and back.
  • FONT_CSS, FONT_WEIGHT (var(--visual-display), Georgia, serif; 500): The face and weight.