Skip to content
cccskills
FreePublished on GitHub

shader-web

Getting a shader onto a page - the WebGL bootstrap, uniform plumbing, device pixel ratio, resize, pause on hidden tab, reduced motion, the drop-in HTML file, parameter updates over postMessage, and embedding it without a framework. Read this when the shader has to run in a browser.

The English summary is being prepared. Showing the original description.

See how to install

Files included (1)

  • SKILL.md7.7 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

Shaders on the web

shader_target target=web writes one self-contained HTML file: no framework, no build step, no request. That is the deployment model the Radiant corpus uses, and it is the right default because a page that needs nothing cannot break because something is missing.

The bootstrap, in full

Everything a fragment shader page needs, and nothing else:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shader</title>
<style>
  html, body { margin: 0; height: 100%; overflow: hidden; background: #0a0a0a; }
  canvas { display: block; width: 100vw; height: 100vh; }
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
const canvas = document.getElementById('canvas');
const gl = canvas.getContext('webgl', { alpha: false, antialias: false });
if (!gl) throw new Error('no WebGL');

const VERTEX = 'attribute vec2 a_position; void main() { gl_Position = vec4(a_position, 0.0, 1.0); }';
const FRAGMENT = `precision highp float;
uniform vec2 u_resolution;
uniform float u_time;
void main() {
  vec2 uv = gl_FragCoord.xy / u_resolution;
  gl_FragColor = vec4(uv.x, uv.y, 0.5 + 0.5 * sin(u_time), 1.0);
}`;

function compile(type, source) {
  const shader = gl.createShader(type);
  gl.shaderSource(shader, source);
  gl.compileShader(shader);
  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    throw new Error(gl.getShaderInfoLog(shader) || 'compile failed');
  }
  return shader;
}

const program = gl.createProgram();
gl.attachShader(program, compile(gl.VERTEX_SHADER, VERTEX));
gl.attachShader(program, compile(gl.FRAGMENT_SHADER, FRAGMENT));
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
  throw new Error(gl.getProgramInfoLog(program) || 'link failed');
}
gl.useProgram(program);

const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
const at = gl.getAttribLocation(program, 'a_position');
gl.enableVertexAttribArray(at);
gl.vertexAttribPointer(at, 2, gl.FLOAT, false, 0, 0);

const uTime = gl.getUniformLocation(program, 'u_time');
const uRes = gl.getUniformLocation(program, 'u_resolution');

function resize() {
  // Cap the pixel ratio: a 3x phone rendering a 4K shader is a battery fire.
  const dpr = Math.min(window.devicePixelRatio || 1, 2);
  canvas.width = Math.round(canvas.clientWidth * dpr);
  canvas.height = Math.round(canvas.clientHeight * dpr);
  gl.viewport(0, 0, canvas.width, canvas.height);
}
window.addEventListener('resize', resize);
resize();

let running = true;
const start = performance.now();
function frame(now) {
  if (!running) return;
  gl.uniform1f(uTime, (now - start) / 1000);
  gl.uniform2f(uRes, canvas.width, canvas.height);
  gl.drawArrays(gl.TRIANGLES, 0, 3);
  requestAnimationFrame(frame);
}
requestAnimationFrame(frame);

document.addEventListener('visibilitychange', () => {
  if (document.hidden) { running = false; }
  else if (!running) { running = true; requestAnimationFrame(frame); }
});
</script>
</body>
</html>

Every part of that is load-bearing:

  • preserveDrawingBuffer is not set here. Leave it off for animation (it costs performance); turn it ON if you need toDataURL() or a screenshot.
  • DPR capped at 2. canvas.width = clientWidth * devicePixelRatio uncapped renders four times the pixels on a 2x display for a difference nobody sees.
  • visibilitychange. A background tab that keeps drawing wastes a battery and is the most common complaint about shader backgrounds.
  • resize sets canvas.width, which resets the drawing buffer - so the viewport must be set again in the same handler, after the size.
  • The error is thrown with the log. A black canvas with nothing in the console is the worst outcome, and it is always avoidable.

Two contexts, two dialects

dialectcontextextra
es100 (gl_FragColor)getContext('webgl')call getExtension('OES_standard_derivatives') before compiling if you use fwidth
es300 (#version 300 es)getContext('webgl2')none
shadertoy / shader-skillsgetContext('webgl2')the page must supply iResolution, iTime, iMouse, iFrame

A WebGL 1 context rejects a #version 300 es shader with unsupported shader version 300 - which reads like a shader bug and is a context bug. shader_target target=web picks the right one for you.

Driving it from outside: the postMessage contract

A drop-in page has no API, so it takes parameter updates over postMessage. This is Radiant's own shape, and pages this row emits accept exactly it:

// on the parent page
const frame = document.querySelector('iframe');
frame.contentWindow.postMessage({ type: 'param', name: 'SPEED', value: 0.8 }, '*');

The page listens:

window.addEventListener('message', (event) => {
  const data = event.data;
  if (!data || data.type !== 'param') return;
  if (data.name === 'SPEED') speed = data.value;
});

Match on the parameter name (SPEED, as declared in // @param SPEED ...), not on the uniform name. The page owns the translation to u_speed.

Embedding it as a background

<iframe
  src="my-shader.html"
  title="Background"
  style="position: fixed; inset: 0; width: 100%; height: 100%; border: 0; z-index: -1;"
  aria-hidden="true"
  tabindex="-1"
></iframe>

Three habits: aria-hidden="true" (it is decoration, not content), z-index: -1 with the content above it, and no pointer events - an iframe over the page eats every click. If it must sit behind text, put it in a container with pointer-events: none.

Do not put a shader behind text without checking contrast: a moving background behind body copy is a readability failure waiting to happen. Either keep it behind a scrim (background: rgba(0,0,0,.55) over the canvas) or keep it in a hero band.

Inline instead of an iframe

If the page already has a canvas, the bootstrap above drops in directly: keep the program in a module, expose setParam(name, value) and resize(). The iframe is only needed when the shader is a file - which is the case for a drop-in background, and which is why the emitted artefact is a whole page.

Reduced motion is not optional

const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const time = reduced ? 0 : (now - start) / 1000;

An animation that ignores this preference is an accessibility defect, not a feature. The page this row emits freezes time at 0 when the preference is set, so the shader still shows its picture, unmoving.

What to check before you ship it

  1. It renders at the size it will be used at - not at 800x600.
  2. No console output. Warnings are still read as errors by people who look.
  3. It stops when the tab is hidden (watch requestAnimationFrame in the profiler).
  4. It survives a resize and a device rotation.
  5. It costs what you think: on a 4K display a heavy raymarcher is 8.3M pixels per frame. shader_render at your intended size is the honest measurement.
  6. Nothing reaches the network. A shader needs no font, no image, no CDN.

shader_verify proves the GLSL; the page is where the driver, the DPR and the resize behaviour get their say. Test it in the browser you are shipping to.

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Author an editorial HTML+SVG diagram with the Diagram Design kit - choose the visual type, read its reference, start from an example, and satisfy the safety and accessibility contract that diagram_write({kind:"html"}) enforces.

The English summary is being prepared. Showing the original description.

vecnode/vn-code10Updated Oct 20, 2026

Control this machine's Docker or Podman engine through the docker_* tools - read the engine's state first, list containers and images, inspect one, tail its logs, pull an image and start it, then stop, restart or remove what you started. Covers the engine that is asleep behind Docker Desktop and the exact call for each verb, the confirm rule removals obey, and the flags the tools deliberately do not have.

The English summary is being prepared. Showing the original description.

vecnode/vn-code10Updated Oct 20, 2026

Drive ffmpeg through the media_run tool as an argv array - remux instead of re-encode, map streams explicitly, trim at keyframes, scale and crop, extract audio and frames, and build GIFs, contact sheets and concatenations. Covers the flags this pack injects, reading a failure from its exit code and last log lines, and the version-dependent spellings to hedge.

The English summary is being prepared. Showing the original description.

vecnode/vn-code10Updated Oct 20, 2026

Read a media file's real structure with ffprobe through media_run - the JSON invocation and how to read it, what every family of field means, how to count frames, sample without decoding everything, detect HDR and interlacing, and diagnose a missing duration, a stream with no frames or a container that lies. Covers when media_probe's own report is already enough.

The English summary is being prepared. Showing the original description.

vecnode/vn-code10Updated Oct 20, 2026

Writing a fragment shader that looks like something - the full-screen triangle, uv space, signed distance fields, noise and fbm, domain warping, raymarching budgets, tone mapping and banding. Every example compiles. Read this when the task is "make it look good", not "make it compile".

The English summary is being prepared. Showing the original description.

vecnode/vn-code10Updated Oct 20, 2026

Use when the user wants to design, redesign, shape, critique, audit, polish, simplify, harden, animate, or otherwise improve a frontend interface: websites, landing pages, dashboards, product UI, components, forms, settings, onboarding, empty states, and native iOS or Android screens. Handles UX review, hierarchy, layout, typography, color, motion, UX copy, accessibility, responsive behavior, edge cases, and capturing or documenting design systems (DESIGN.md, tokens). Also for making bland UI bolder, loud UI quieter, or ambitious visual effects. Not for backend-only or non-UI tasks.

The English summary is being prepared. Showing the original description.

vecnode/vn-code10Updated Oct 20, 2026

See all skills by vecnode

Report a problem with this skill