Building the cursor reveal: a small interactive flourish for a quiet site
Why I built the cursor reveal on ctrlsze.studio's homepage, how I worked out the interaction in plain English before writing any code, and what makes a small effect like this earn its keep.
If you've hovered over the homepage of ctrlsze.studio, you've probably noticed it: a soft circular lens follows your cursor, and inside the lens the text quietly switches from sans-serif to a warm serif — like you're looking through a magnifying glass that prefers a different typeface. It's small, it's playful, and it was the most enjoyable thing I built that weekend.
This is the story of how it came to be — the why, the design thinking before any code, and the small choices that made it feel intentional rather than gimmicky.
Why I built it
The studio site is intentionally restrained. Lots of negative space, monospace metadata, careful typography, very little motion. That restraint is the point — it suits a tech studio and it ages well. But restraint without any signal of personality reads as cold. I wanted something on the homepage that signalled there is a person behind this site, and they care about details — without breaking the visual quiet.
I had three constraints in mind:
It had to be interactive but easy to deploy.No WebGL, no canvas, no library. CSS and a few lines of JavaScript. If it required a build-time toolchain or a third-party dependency, it wasn't going on the site.
It had to add fun without dominating.The homepage is a CV, a writing index, and a project showcase first. Anything I added needed to be discoverable but skippable — something a casual visitor wouldn't notice immediately, but someone who lingered would smile at.
It had to demonstrate something. Founders building tech studios are often judged on the website before the product. A small, well-executed interactive moment on the homepage signals that the person who built it can handle the details — which is roughly what you want a prospective client, collaborator, or investor to think.
Designing the interaction in plain English first
Before writing any code I wrote out what the effect should do, in normal sentences. This sounds obvious but it's the step almost everyone skips, and skipping it is why so many web animations feel slightly off — the developer was thinking in code, not in interaction.
Here's roughly what I wrote:
Five sentences. Every sentence corresponds to a real decision. Reading it back, I knew immediately:
— "Soft-edged" meant a radial gradient mask, not a clipping path. A clipping path has a hard edge, and the brief called for a fade.
— "Alternate version of the page text" meant rendering the content twice in the DOM — once as the default, once as the revealed layer. Not swapping fonts in a region, which would require something like Houdini or Canvas.
— "Follow the cursor smoothly" meant linear interpolation in a requestAnimationFrame loop. Not direct cursor-tracking. Direct tracking jitters; lerping smooths it.
— "Rest gently rather than freeze" meant the loop should suspend itself when the cursor stops, but not stop immediately — let it ease into rest.
— "On touch devices... should not exist" meant a media-query check on (pointer: fine) and prefers-reduced-motion, with the entire effect gated behind a state flag.
Each line of the brief gave me a corresponding piece of the implementation. I didn't write code from a blank page — I wrote code from a list.
How the radius works
The effect's shape is the most distinctive thing about it. Most cursor effects use a hard circle or a fixed-size element that follows the mouse. I wanted something softer — closer to a flashlight beam than a tracker.
The trick is a CSS mask-image: radial-gradient(...). The mask is applied to a fullscreen layer that contains the alternate text. At the cursor position, the mask is fully opaque (the alternate text shows). At the edge of the radius, it's fully transparent (the alternate text disappears). Between the two, the mask fades — that's the "feather." A 35% feather looks like a lens; a 5% feather looks like a coin; a 70% feather looks like a cloud. I tuned it visually rather than by formula, sliding the value up and down until it felt right.
The cursor position is fed into the mask via two CSS custom properties — --lens-x and --lens-y— which JavaScript updates each animation frame. The browser recomputes the gradient with the new centre, but it does so on the GPU as part of the existing composited layer, so there's no repaint. That's the difference between a smooth sixty-frames-per-second feel and a janky one.
The small choices that mattered most
Lerping instead of direct tracking. Reading the cursor position once per frame and applying it directly produces a perfectly responsive but visually nervous effect. Lerping — moving 15% of the distance toward the target each frame — smooths the motion into something that feels alive. The eased lag is part of the personality. Anything faster than 25% feels sharp; slower than 8% feels laggy. I landed on 15% by feel.
Pause regions.The reveal effect is delightful for the first ten seconds and quietly annoying when you're trying to read. So I made any element with a data-cursor-reveal-pauseattribute (or any ancestor that has it) suppress the reveal while the cursor is over it. The footer, the contact card, anything with dense information — all marked as pause regions. The effect happens where it's decorative; it gets out of the way where it's functional.
Suspending the animation loop.When the cursor stops moving and the lens has caught up, the rAF loop cancels itself. A still page costs literally zero CPU. The loop wakes back up the next time the cursor moves. This is the difference between an effect that's nice for an audience and an effect that's nice for a battery.
Accessibility. The reveal layer is aria-hidden and inert. Screen readers and keyboard navigation never see it, even though the DOM contains duplicate text. Reduced-motion users see only the default layer. The effect exists for the people who can enjoy it; it doesn't penalise the people who can't.
Why it earns its place
Most homepage interactivity falls into one of two failure modes. Either it's flashy enough to upstage the content (rotating 3D hero scenes, parallax that fights the scroll, blob that chases the mouse for no reason) or it's so subtle that it registers as accidental rather than designed. The space between — interactive enough to delight, restrained enough to respect the content — is small and worth aiming for.
The cursor reveal sits in that space because it has a reason. The reason is: there's a different typographic personality available, and you can see it if you look. That's a small idea, but small ideas, well-built, do more for a site's character than big ideas, half-built.
What I'd do differently
Two small things. First, I'd build the pause-region system before adding the effect to the page, not after. I had to retrofit pause regions onto the footer and the contact card once the reveal was live, which was easy but felt backwards.
Second, I'd expose the radius and feather as design tokens rather than props on the component. They're currently passed in per usage, which makes it slightly harder to keep the feel consistent across the site if I add more reveal regions later. A small architectural call I'd make differently if I were starting again.
Frequently asked
- Does the cursor reveal hurt SEO?
- No. The reveal layer renders the same text twice in the DOM — once as the visible default, once inside the masked layer for the reveal. Both are real text, both are crawlable, neither is hidden via display:none or visibility:hidden. Search engines see duplicate text on the page, which matches what users see, so there's no cloaking risk. The hidden layer is also marked aria-hidden and inert, so screen readers and keyboard users skip it cleanly.
- Does it work on phones?
- It's deliberately disabled on touch devices. The effect only renders when the browser reports (pointer: fine) — i.e. a mouse or trackpad. On phones and tablets you just see the default text. The same applies if you've turned on prefers-reduced-motion: the reveal stays off so the page doesn't animate against your settings.
- Why a circular mask instead of, say, a rectangle following the cursor?
- A circle reads as a 'spotlight' or 'lens' — a familiar metaphor that doesn't need explaining. A rectangle would feel like a selection tool, which implies you can interact with it. The circular gradient also has a soft edge that fades the reveal smoothly into the page, which a rectangular mask with hard edges can't do without extra work. Smaller decisions like this matter more than the technical implementation.
- How performant is it really?
- Cheap. The mask is a CSS radial-gradient applied to a single GPU-composited layer, so the browser doesn't repaint anything when the cursor moves — it just shifts the mask. The rAF loop self-suspends when the cursor stops moving, so a static page costs zero CPU. On a 2019 MacBook Air I measured under 1% CPU during continuous mouse movement and 0% when idle.
Notes from the build will keep landing here. The next post is on the two-design-system trade-off — when it's worth maintaining two visual languages and when it isn't.
Sze. (2026, May 5). Building the cursor reveal: a small interactive flourish for a quiet site. CTRLSZE. https://ctrlsze.studio/blog/building-a-cursor-reveal
https://ctrlsze.studio/blog/building-a-cursor-reveal
›BibTeX
@misc{ctrlsze-building-a-cursor-reveal-2026,
author = {Sze},
title = {Building the cursor reveal: a small interactive flourish for a quiet site},
year = {2026},
month = {May},
url = {https://ctrlsze.studio/blog/building-a-cursor-reveal},
note = {CTRLSZE}
}