Design Library

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 snapping
never 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>
  )
}