Skip to content
Frontend
Skill

/webgl-components

Build small, always-on WebGL visuals (identity avatars, ambient orbs, glass and iridescent surfaces, animated textures) that ship inside a normal web app UI without wrecking performance, accessibility, SSR, or layout. Use whenever a shader-, GLSL-, or canvas-driven decorative

BOOST
From plugin
flornkm-skills
1142 skills
Install
$ npx -y skills add flornkm/skills --skill webgl-components --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/webgl-components

Context preview

The summary Claude sees to decide when to auto-load this skill.

Build small, always-on WebGL visuals (identity avatars, ambient orbs, glass and iridescent surfaces, animated textures) that ship inside a normal web app UI without wrecking performance, accessibility, SSR, or layout. Use whenever a shader-, GLSL-, or canvas-driven decorative

SKILL.md

webgl-components.SKILL.md
name: webgl-components
description: "Build small, always-on WebGL visuals (identity avatars, ambient orbs, glass and iridescent surfaces, animated textures) that ship inside a normal web app UI without wrecking performance, accessibility, SSR, or layout. Use whenever a shader-, GLSL-, or canvas-driven decorative element is added to a product UI, especially one rendered many times per page or per list; when reviewing or optimizing one; or when debugging one that renders blurry, aliased, all-black, or paints over the surrounding UI on machines without hardware acceleration."

WebGL Components

Lessons for embedding shader-driven visuals (identity avatars, ambient orbs, animated textures) into product UI. The failure modes are predictable, and nearly all of them come from treating the widget like a demo instead of like a component that renders fifty times in a list.

Architecture: one context, many instances

**Never create one WebGL context per component instance.** Browsers cap a document at roughly 8 to 16 live contexts, then silently evict the oldest. A list of avatars hits that cap immediately.

  • Keep **one module-level WebGL context** rendering offscreen, and give each component instance a cheap 2D canvas. Each frame: draw into the shared GL canvas, then blit the region into the instance's 2D canvas with `drawImage`.
  • The GL drawing buffer is only valid until the browser composites, so draw and blit **within the same task**. Never across an await.
  • GL's origin is bottom-left and the 2D canvas' is top-left, so blit from `canvas.height - size`, not from `0`.
  • Grow the shared canvas to fit the largest instance and never shrink it mid-session.
  • **Group identical draws.** Instances agreeing on every visual input (source, tint, size, pointer state) paint identical pixels, so draw once and blit that result to all of their canvases. A list of same-styled items then costs one draw per frame instead of one per row.

Frame loop discipline

  • One `requestAnimationFrame` loop for all instances, owned at module level. No per-instance loops.
  • Gate every instance on an `IntersectionObserver` so offscreen instances never draw.
  • Pause the loop entirely when `document.visibilityState !== "visible"`, and stop scheduling once nothing visible remains.
  • **Cap the frame rate.** Slow ambient drift gains nothing above 30fps, and an uncapped loop pins a core for as long as the widgets are on screen, twice over on a 120Hz display where rAF fires at 120.
  • Ration expensive one-time work. Generating source textures should be budgeted to roughly one per frame with the remainder rescheduled, because a page that introduces a dozen variants at once will otherwise generate them all in a single frame and stall first paint.

Cost lives in the fragment shader times area

  • **Per-pixel cost scales with widget area, not instance count.** Budget the fragment shader like a hot loop. A blur kernel sampled per pixel per frame (13, 25, 121 taps) is where these widgets die. Aim for **one texture tap plus a couple of noise evaluations** per pixel.
  • Move layered or expensive pattern generation into a **precomputed source texture**: render it once offscreen with the noise octaves, streaks, and grain baked in, then have the per-frame pass merely sample it at a warped position. Motion comes from *where* you sample, not from recomputing the pattern.
  • If the drift perturbs UVs with noise, the GPU's derivative-based mip selection over-blurs, because perturbed derivatives look larger than the real sampling density. Apply a **negative mip bias** in `texture2D(..., bias)` to pull the detail back. This presents as "why is it suddenly blurry and washed out" and is easy to misdiagnose as a texture problem.
  • Mipmaps are mandatory once nothing blurs. A 1024px source minified into a 16 to 48px widget aliases badly. WebGL1 requires power-of-two textures for `generateMipmap`, so keep generated sources power-of-two and **resample arbitrary images onto a power-of-two canvas** before upload.

Prefer real optics to painted ones

Glass, chrome, iridescence and caustics are the usual reasons these widgets exist, and the instinct is to paint them: a gaussian for the highlight, a hand-authored violet-to-red ramp for the rainbow, a `smoothstep` ring for the edge. Painted optics need a new hand-tuned term for every angle and every background, and they stop being convincing the moment anything moves. Deriving them costs about the same per pixel and holds up on its own.

  • **One normal buys everything.** On a disc, `N = normalize(vec3(p, sqrt(1 - r*r)))`. From it, Fresnel `0.04 + 0.96 * pow(1 - dot(N, V), 5.0)` and a reflection vector are two more lines, and **a Fresnel-weighted `env(reflect(-V, N))` already is the specular edge** — grazing angles drive the weight to 1 and the rim shows nothing but environment. A separate rim term added on top of this is a sign the reflection is not doing its job.
  • **The environment can be a function, not a cubemap.** A direction-to-colour function with one soft key lobe, a vertical gradient and a floor bounce is a handful of `pow`s, needs no texture upload or GPU memory, and gives correct-looking reflections everywhere the widget curves.
  • **Dispersion should be produced, not drawn.** Sample the interior at several wavelengths, each with its own index of refraction, and weight each by the colour the eye assigns it. A white emitter inside then comes out as a spectrum in the right order, and the spread automatically widens toward the rim where the glass is thick, which no hand-authored ramp does. Three taps (plain RGB) only fringe the two edges — a full spectrum needs the middle sampled too, so budget 8 to 16.
  • Two things about a real optical setup that read as bugs but are not:
  • **A sphere disperses along its own radius.** A horizontal feature separates lengthwise and stays stubbornly white. Getting the spread perpendicular to the feature needs a wedge — a tilt added to the refracting norm
Read more
Ships withflornkm-skills

There are plenty of skills out there. This library avoids slop and only ships skills that: teach agents concepts LLMs aren't trained on, because they are new or rarely used encode specific, opinionated workflows, validated across multiple codebases

Get the whole plugin
Stats
114
Stars
7
Forks
Maintained
Maintenance
HTML
Language
MIT
License
1mo ago
Last commit
2mo ago
Created
10d ago
Added

Repo: flornkm/skills

Other skills on flornkm-skills.