Vetrel is a fictional freelance motion and 3D designer (Ines Vetrel, Lisbon). The page follows the “name as a monument” direction: the name is set edge to edge in Hanken Grotesk 600 at a fitted size, with 14px Hanken Grotesk 500 sentence-case labels (year, discipline, availability) as the small end of the scale; Martian Mono is kept only for the real timecodes. The first screen says plainly what it is — “Motion designer and 3D artist in Lisbon”, the sub-line “Product films, animated identities and 3D visuals for brands.”, a “Start a project” button and an “Available for projects from March 2027” status — with a slim typographic row of invented client names after the hero. The work is shown through ten public-domain / CC0 photographs (Wikimedia Commons CC0, Art Institute of Chicago and Met open access) colour-graded with orange and cobalt gels for this template (public/assets/vetrel-studio/, credits in LICENSE.txt): flowers (amaryllis ×2, poppies, a black-and-white tulip, a lily) and museum sculpture (a Roman marble head, Rodin’s Sorrow and Adam, Watts’s Clytie, Canova’s Vestal), cast as frames from a museum campaign film, a night flower-festival title sequence, a perfume launch film and a music video. Image slots: hero frame 4:5 (six stills, cover-cropped; the IMG map in JS can add a per-image crop [scale, x, y] that the CSS and the WebGL overlay both apply — used to hide the amaryllis stem and the lily’s cut petal), work tiles 3:4 and 16:9, reel poster 16:9, one 3:4 about frame, the service previews and the process stills — every <img> has width/height and lazy-loads below the fold, so a buyer swaps the files and keeps the ratios.
On top of the photos sits a live WebGL layer. One offscreen THREE.WebGLRenderer draws a photo shader (a looping fbm flow field, a lens around the pointer and a hair of RGB split, sampling the still as a texture) plus four GLSL loops (two generative identities shown as “live code” tiles, a window-light study and a waveform-bar row in the page’s own ink and accent in the about section) and copies each frame into an ordinary 2D canvas with drawImage, so the whole page costs one WebGL context. Hovering a still fades the overlay in and the pointer’s x scrubs its 8-second loop with a timecode and an accent bar (also horizontal drag and arrow keys — the frames are role="slider"). The hero pins for slideLength% of the viewport while a scrubbed proxy moves a 4:5 frame through the name: the frame swaps places with each letter in turn and cuts (upward clip-path wipe) to another project at every gap; on phones the name is up to three screen widths wide, starts with the first letter whole and only then pans with the frame (no pan under reduced motion — the word is fitted to the width). The reel plays on hover with a cursor-following button (8 shots × 3 s), the services index shows a cursor-following preview, and the process accordion shows one still as a palette, a still, a grey 8 fps animatic and the flowing final. One motion language: power3.out entrances, expo.inOut wipes and state changes, ease: 'none' on everything scrubbed.
Gotchas: textures load lazily (first hover / reel play) with colorSpace = NoColorSpace because the shader writes raw sRGB; overlays only render while visible (amt > 0) and the <img> underneath stays the real picture, so without WebGL the page is simply the photos; shader programs compile with renderer.compileAsync the first time a view needs them; software GL (SwiftShader/llvmpipe) drops to a 420px render cap, half the frame rate and two views per frame; drawImage reads the bottom-left w×h of the GL canvas (sy = CAP − h); the monument is fitted from measured letter widths after document.fonts.ready and the frame stands on a measured baseline; the palettes redefine --bg --surface --field --text --muted inside .vtl[data-palette] only. Under reduced motion nothing pins, loops or flows on its own: the frame sits in the middle gap, counters show their totals, the marquee is a static line, the about frames sit in a 3-column grid under a static title and the stills stay still — the reel still plays if the visitor presses play.