Glass lens - refractive cursor magnifier
glass-lens/ · 2 files
componentskill: design-taste-frontendcanvaseffectinteractiveluxuryeditorial
Variants of canvas-lens-fx: asciify-lens
Brief
The prompt that produced this. Re-run it to generate more in the same taste.
- Aesthetic
- optical instrument - refractive, chromatic, physical, weighty, precise, quietly luxurious
- Reference
- people-lens canvasui, refractive glass over live DOM
- Intent
- give the pointer physical presence over the page, so magnification reads as an object held above the text rather than a zoom applied to it
- Guardrails
- always refract the real DOM, so what is magnified is what is there, keep a rim highlight so the lens is visible over flat backgrounds, fall back to plain content where html-in-canvas is unsupported, let the lens trail the cursor rather than snappingnever obscure content the lens is not over, simulate glass with a static image, make the effect a prerequisite for reading, assume canvas support
Install
pnpm dlx shadcn@latest add http://localhost:3040/r/glass-lens.json
Source
Meta block and library type import stripped - this is what pastes cleanly elsewhere.
'use client'
import { useState } from 'react'
import { Glass } from './glass'
const SHAPES = ['circle', 'square', 'rectangle'] as const
export default function GlassLens() {
const [shape, setShape] = useState<(typeof SHAPES)[number]>('circle')
const [ior, setIor] = useState(1.5)
return (
<main className="min-h-[100dvh] bg-[#f4f2ee] text-[#1a1a1a]">
<div className="flex flex-wrap items-center gap-4 border-[#dcd8d0] border-b px-6 py-3">
<fieldset className="flex items-center gap-2">
<legend className="sr-only">Forme de la lentille</legend>
{SHAPES.map((s) => (
<button
key={s}
type="button"
onClick={() => setShape(s)}
aria-pressed={shape === s}
className={`px-2.5 py-1 font-mono text-[11px] uppercase tracking-widest transition-colors ${
shape === s
? 'bg-[#1a1a1a] text-[#f4f2ee]'
: 'text-[#6b665e] hover:bg-[#e8e4dc] hover:text-[#1a1a1a]'
}`}
>
{s}
</button>
))}
</fieldset>
<label className="flex items-center gap-2 font-mono text-[#6b665e] text-[11px] uppercase tracking-widest">
IOR
<input
type="range"
min={1}
max={2}
step={0.1}
value={ior}
onChange={(e) => setIor(Number(e.target.value))}
className="w-28 accent-[#1a1a1a]"
/>
<span className="w-7 tabular-nums text-[#1a1a1a]">{ior.toFixed(1)}</span>
</label>
<p className="ml-auto font-mono text-[#8a8478] text-[11px] uppercase tracking-widest">
Move the pointer over the text
</p>
</div>
{/* Keyed so a shape or IOR change rebuilds the lens: the instance
snapshots its options once, on mount. */}
<Glass key={`${shape}-${ior}`} shape={shape} ior={ior} size={120} className="min-h-[70dvh]">
<div className="mx-auto max-w-3xl px-6 py-16">
<p className="mb-4 font-mono text-[#8a8478] text-[11px] uppercase tracking-widest">
Optics — 2026
</p>
<h1 className="text-balance font-semibold text-5xl leading-[1.05] tracking-[-0.03em]">
A lens has weight. A zoom does not.
</h1>
<p className="mt-6 max-w-prose text-[#4a453d] text-lg leading-relaxed">
The glass refracts the live document: the same characters, bent by an index of
refraction you can change while reading. Chromatic aberration at the rim and a fresnel
highlight keep it legible as an object, even over a flat page where clear glass would be
invisible.
</p>
<dl className="mt-10 grid grid-cols-2 gap-x-8 gap-y-4 border-[#dcd8d0] border-t pt-6 font-mono text-sm sm:grid-cols-4">
{[
['Shape', shape],
['IOR', ior.toFixed(1)],
['Size', '120 px'],
['Source', 'live DOM'],
].map(([k, v]) => (
<div key={k}>
<dt className="text-[#8a8478] text-[11px] uppercase tracking-widest">{k}</dt>
<dd className="mt-1 text-[#1a1a1a]">{v}</dd>
</div>
))}
</dl>
</div>
</Glass>
</main>
)
}