Pure WebGL component — full-screen canvas, transitions between images with a "zoom blur" (radial blur converging toward the center), scroll/click/arrow keys to navigate, blur center follows the mouse.
WHAT
Purpose and context
- Purpose: attaches to the page a full-screen WebGL canvas that transitions between images with a zoom-blur effect (outgoing image fades while converging toward the center, incoming image sharpens), navigation via scroll/click/keyboard, blur center follows the mouse.
- Trigger (when to use):
- Hero section or image slideshow needs a MORE PREMIUM transition effect than a regular crossfade (zoom-blur transition).
- User says "image blur effect", "webgl blur", "zoom blur style gallery".
- Non-goals: NOT for STATIC blur/frosted-glass of UI cards/panels (that is
backdrop-filter: blur()regular CSS, ordocs-site-macosliquid-glass) — this is a MOTION effect between images, far more GPU-intensive. Does not change theme (that is the job ofdark-mode-maker).
Mental model
mảng images → mỗi ảnh = PlaneBufferGeometry + shader ZoomBlurImage → progress/targetProgress lerp → uStrength 2 layer đan chéo → frame render; input (wheel/click/phím) đổi targetProgress; chuột đổi uCenter.
Input and output contract
| Field | Required? | Meaning | |
|---|---|---|---|
| In | image list | yes | replace array images in assets/blur.js |
| In | speed / easing | no | adjust duration/easing, default kept as in the asset |
| In | containing page has gsap global |
yes | initScene() calls gsap.fromTo(...) without importing |
| Out | blur.html + blur.css + blur.js adapted |
yes | full-screen canvas that runs within the target page |
| Out | verify evidence | yes | before/after screenshots when scrolling + clean console (via playwright-verify) |
Rules and capabilities
- RULE-01 (MUST): This is a GPU-HEAVY effect (shader runs every frame across the whole viewport) — do not use for multiple instances at once on 1 page, and test on weak/mobile devices before finalizing (WebGL context has a browser limit on simultaneous instances).
- RULE-02 (MUST): Verify with
playwright-verify— capture before/after when scrolling, read the console (WebGL context errors are often silent at the canvas, no immediately visible JS error thrown). - RULE-03 (MUST): Read the "Known technical debt" section BEFORE using in production — vendor the CDN import
useThreebefore delivering to a client/real product. - Capabilities: read the skill's assets; write HTML/CSS/JS files into the target project; run a headless browser to capture + read the console.
Failure boundaries
- Containing page is missing
<script>gsap →gsap.fromTo(...)throws an error as soon as the image finishes loading → blocked until a script is added. - CodePen/unpkg CDN fails to load → blank canvas → failed; the fix is to vendor it (section Technical Debt).
- Canvas does not render but there is no JS error → treat as failed until the console/screenshot proves otherwise (silent WebGL errors).
- The request is for a static blur for a card/panel → out of scope, switch to
backdrop-filter/docs-site-macos.
HOW
Main workflow
| Step | Type | Inputs | Action | Outputs/exit | Failure/next |
|---|---|---|---|---|---|
| W01 | judgment | user request | Confirm this is image transition, not static UI blur | continue / switch to another skill | static blur → stop, suggest backdrop-filter |
| W02 | deterministic | assets/blur.* |
Read assets/blur.html + assets/blur.css + assets/blur.js |
asset content | — |
| W03 | effect | asset, user's image | ADAPT: change the array images, adjust duration/easing; make sure the page has gsap global |
file in the target project | — |
| W04 | judgment | target uses (prototype/production) | Review the Technical Debt section; production → B01 | vendoring decision | — |
| W05 | deterministic | page already wired up | Verify with playwright-verify: capture before/after scroll, read console |
image + console log | error/blank canvas → fix then repeat W05 |
Branches
| ID | Kind | Guard | Behavior | Skip / failure | Rejoin |
|---|---|---|---|---|---|
| B01 | conditional_required | used for real customers/products | vendor the pen useThree into a file .js in assets/, remove the CDN import (rewrite it yourself if the pen license is unclear); if there is a bundler, install three via npm |
prototype → skip | W05 |
| B02 | recovery | console reports gsap is not defined |
add <script src="gsap..."> to the containing page |
— | W05 |
Validation and stopping
Done when the before/after scroll screenshots differ (the image transition happens) and the console has no WebGL/JS errors. Do not loop W05 infinitely: after 2 rounds still failing → tell the user along with the console log.
Examples
- Positive: "make a hero slideshow with 4 images in a zoom blur style" → replace
imageswith 4 URLs, page already has gsap → playwright captures before/after scroll sees image 1→image 2, clean console → deliver. - Boundary/failure: target page doesn't load gsap → console
gsap is not definedright when the image loads → B02 adds the script, verify again; user wants "glass-style blurry card" → W01 stops, suggestsbackdrop-filter: blur().
Reference — Assets
assets/blur.html + assets/blur.css + assets/blur.js — read then ADAPT (change the array images, adjust the speed duration/easing).
Reference — ⚠️ Known technical debt — read BEFORE using in production
assets/blur.js line 12 imports helper useThree from someone else's CodePen pen (codepen.io/soju22/pen/...js) — this is a CDN dependency on a resource we don't own, which can change/be removed at any time without notice. The original snippet is a reference demo, not hardened for production.
- ponytail: global external dependency not yet vendored, upgrade when real production is needed — before using it for real clients/products: (1) read that pen's contents, vendor it into a separate
.jsfile inassets/, remove the CDN import; (2) rewriteuseThreeyourself (Three.js scene/camera/renderer boilerplate) if the original pen's license is unclear. import ... from 'https://unpkg.com/three@0.120.0/...'(lines 1-10) is also a CDN runtime — acceptable for a prototype, but the project has a bundler so installthreevia npm then import locally, avoiding a network dependency at runtime.gsapused ininitScene()but NOT imported in the file — it assumes a global variable already exists (the script<script src="gsap...">on the page that contains it). Without that<script>,gsap.fromTo(...)throws an error right when the image finishes loading.
Reference — Mechanism
- Each image is a
PlaneBufferGeometrycustom shader overlay (ZoomBlurImage) — the fragment shader samples the image along the toward-center direction vector, Bezier weights, creating a convergent blur effect whenuStrengthis non-zero. - Image transition:
progress/targetProgressgradually lerp (updateProgress),uStrengthof 2 cross-faded image layers (outgoing image fades out, incoming image fades in). - Input: mouse scroll (
wheel) changestargetProgressin steps of 1/20; clicking the top/bottom half of the screen = back/forward; arrow keys similarly. - Blur center (
uCenter) lerps to the mouse position each frame → "eye follows the cursor" effect.
Origin
- Compressed from
llmwiki/patterns/blur/(reference snippet not attached to any skill, never tracked in git) into a real skill per user request 2026-09-17 — same batch astimeline.