Generated banner for Webgl Laser
Generated logo for Webgl Laser

Webgl Laser

Meng ToCommunityCreated by Meng To

  • Skill
  • Tool integration
  • Software development
  • MIT
  • Medium risk

From the author

Create a fixed full-screen WebGL laser background effect with a thin white-hot vertical core, restrained brand-colored halo, and soft smoky fog around the beam. Use only for laser background effects, not full page layout, copy, generic hero scenes, particles, or unrelated motion systems.
— the description by Meng To

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a raw WebGL implementation for creating a cinematic, vertical laser background. It uses a custom fragment shader to render a thin, white-hot core surrounded by a brand-colored halo and soft, drifting smoke. The implementation is designed to sit behind page content on a fixed canvas, ensuring the effect remains atmospheric without interfering with user interaction or readability.

What it does

  • Renders a thin, white-hot vertical laser beam using raw WebGL.
  • Generates a soft, atmospheric smoke effect concentrated around the beam.
  • Applies a brand-colored halo derived from a provided hex color.
  • Handles canvas resizing and device pixel ratio scaling.
  • Provides uniform controls for beam position, thickness, and smoke density.
  • Ensures the canvas is non-interactive via pointer-events: none.

When to use it

  • When you need a subtle, cinematic laser background for a hero section.
  • When you want to integrate a brand-colored light effect behind existing UI.
  • When you require a fixed background effect that does not obstruct page content.
  • When you prefer a lightweight, raw WebGL approach over heavy 3D libraries.

Advantages

  • Lightweight implementation using raw WebGL.
  • Highly customizable via shader uniforms for color, width, and density.
  • Maintains visual focus on foreground content through careful layering.
  • Includes built-in support for reduced-motion preferences.

Usage examples

“Initialize the laser background on my canvas element using the brand accent color.”
The agent sets up the WebGL context, compiles the shaders, and starts the animation loop on the specified canvas.
“Adjust the laser beam to appear slightly thicker and move it to the right side of the screen.”
The agent updates the coreWidth and xOffset uniform values in the render loop to modify the beam's appearance and position.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/mengto/webgl-laser/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip webgl-laser.zip -d .agents/skills

Documentation by Meng To

WebGL Laser

Scope

  • Apply only to the laser background effect.

  • Use a fixed full-screen canvas behind the DOM.

  • Set pointer-events: none on the canvas.

  • Keep page content in a higher stacking context.

  • Match the halo and smoke to the page's primary or strongest accent color.

Visual Target

  • Thin vertical beam: crisp white-hot inner core, narrow colored halo.

  • Atmospheric smoke: soft cloudy breakup concentrated around the beam.

  • Dark cinematic field: restrained, brand-colored, and readable behind content.

  • Slow pulse: glow breathes gently; no aggressive flicker or color cycling.

  • Light blade feel: narrow and precise, never a thick neon pillar.

Layering

<canvas class="laser-canvas" data-webgl-laser></canvas>
<main class="page-content">
  ...
</main>
.laser-canvas {
  position: fixed;
  inset: 0;
  z-index: 0;
  width: 100vw;
  height: 100vh;
  pointer-events: none;
}

.page-content {
  position: relative;
  z-index: 1;
}

Brand Color

Use the product accent as the source color. The shader keeps the core near white and derives the halo/smoke from this color.

function hexToRgb01(hex) {
  const clean = hex.replace("#", "").trim();
  const value = clean.length === 3
    ? clean.split("").map((char) => char + char).join("")
    : clean;

  return [
    parseInt(value.slice(0, 2), 16) / 255,
    parseInt(value.slice(2, 4), 16) / 255,
    parseInt(value.slice(4, 6), 16) / 255,
  ];
}

const accent = getComputedStyle(document.documentElement)
  .getPropertyValue("--brand-accent")
  .trim() || "#ff4d8d";

Raw WebGL Setup

Prefer raw WebGL with a full-screen quad unless the active file already uses another renderer.

const laserVertexShader = `
attribute vec2 a_position;
varying vec2 v_uv;

void main() {
  v_uv = a_position * 0.5 + 0.5;
  gl_Position = vec4(a_position, 0.0, 1.0);
}
`;

const laserFragmentShader = `
precision highp float;

uniform vec2 u_resolution;
uniform float u_time;
uniform vec3 u_color;
uniform float u_xOffset;
uniform float u_coreWidth;
uniform float u_glowWidth;
uniform float u_smokeDensity;

varying vec2 v_uv;

float hash(vec2 p) {
  p = fract(p * vec2(123.34, 456.21));
  p += dot(p, p + 45.32);
  return fract(p.x * p.y);
}

float noise(vec2 p) {
  vec2 i = floor(p);
  vec2 f = fract(p);
  vec2 u = f * f * (3.0 - 2.0 * f);

  float a = hash(i);
  float b = hash(i + vec2(1.0, 0.0));
  float c = hash(i + vec2(0.0, 1.0));
  float d = hash(i + vec2(1.0, 1.0));

  return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
}

float fbm(vec2 p) {
  float value = 0.0;
  float amplitude = 0.5;

  for (int i = 0; i < 5; i++) {
    value += amplitude * noise(p);
    p *= 2.02;
    amplitude *= 0.5;
  }

  return value;
}

void main() {
  vec2 aspect = vec2(u_resolution.x / u_resolution.y, 1.0);
  vec2 p = (v_uv - 0.5) * aspect;
  float x = p.x - u_xOffset;
  float distanceToBeam = abs(x);

  float core = exp(-pow(distanceToBeam / u_coreWidth, 2.0));
  float glow = exp(-pow(distanceToBeam / u_glowWidth, 1.45));
  float scatter = exp(-pow(distanceToBeam / (u_glowWidth * 5.5), 1.25));
  float pulse = 0.9 + 0.1 * sin(u_time * 1.15);

  vec2 fogUv = p * 3.1 + vec2(0.0, -u_time * 0.035);
  fogUv.x += sin(p.y * 3.5 + u_time * 0.11) * 0.14;
  float fogBase = fbm(fogUv);
  float fogFine = fbm(p * 8.0 + vec2(sin(u_time * 0.07) * 0.35, u_time * 0.05));
  float fog = smoothstep(0.30, 0.86, fogBase * 0.72 + fogFine * 0.28);
  float smoke = fog * scatter * u_smokeDensity;

  vec3 brand = clamp(u_color, 0.0, 1.0);
  vec3 haloColor = mix(brand, vec3(1.0), 0.16);
  vec3 smokeColor = mix(brand, vec3(0.55), 0.28) * 0.55;
  vec3 hotCore = vec3(1.0, 0.96, 0.90);

  vec3 color = vec3(0.006, 0.007, 0.010);
  color += smokeColor * smoke;
  color += haloColor * glow * 0.46 * pulse;
  color += hotCore * core * 1.35;

  float vignette = smoothstep(1.25, 0.18, length(p));
  color *= vignette;

  float alpha = clamp(smoke * 0.72 + glow * 0.68 + core, 0.0, 1.0);
  gl_FragColor = vec4(color, alpha);
}
`;

Initializer

Keep u_resolution synced on resize and animate through u_time.

function createShader(gl, 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) || "Shader compile failed");
  }

  return shader;
}

function createProgram(gl, vertexSource, fragmentSource) {
  const program = gl.createProgram();
  gl.attachShader(program, createShader(gl, gl.VERTEX_SHADER, vertexSource));
  gl.attachShader(program, createShader(gl, gl.FRAGMENT_SHADER, fragmentSource));
  gl.linkProgram(program);

  if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
    throw new Error(gl.getProgramInfoLog(program) || "Program link failed");
  }

  return program;
}

function initWebGLLaser(canvas, options = {}) {
  if (!canvas) return () => {};

  const gl = canvas.getContext("webgl", {
    alpha: true,
    antialias: false,
    premultipliedAlpha: false,
  });

  if (!gl) return () => {};

  const program = createProgram(gl, laserVertexShader, laserFragmentShader);
  const positionBuffer = gl.createBuffer();
  const positions = new Float32Array([
    -1, -1,
     1, -1,
    -1,  1,
    -1,  1,
     1, -1,
     1,  1,
  ]);

  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
  gl.useProgram(program);

  const positionLocation = gl.getAttribLocation(program, "a_position");
  const uniforms = {
    resolution: gl.getUniformLocation(program, "u_resolution"),
    time: gl.getUniformLocation(program, "u_time"),
    color: gl.getUniformLocation(program, "u_color"),
    xOffset: gl.getUniformLocation(program, "u_xOffset"),
    coreWidth: gl.getUniformLocation(program, "u_coreWidth"),
    glowWidth: gl.getUniformLocation(program, "u_glowWidth"),
    smokeDensity: gl.getUniformLocation(program, "u_smokeDensity"),
  };

  gl.enableVertexAttribArray(positionLocation);
  gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
  gl.enable(gl.BLEND);
  gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

  const color = options.color || hexToRgb01(accent);
  const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
  let width = 1;
  let height = 1;
  let rafId = 0;

  function resize() {
    const dpr = Math.min(window.devicePixelRatio || 1, options.maxDpr || 1.5);
    width = Math.max(1, window.innerWidth);
    height = Math.max(1, window.innerHeight);
    canvas.width = Math.floor(width * dpr);
    canvas.height = Math.floor(height * dpr);
    gl.viewport(0, 0, canvas.width, canvas.height);
  }

  function render(time = 0) {
    gl.useProgram(program);
    gl.uniform2f(uniforms.resolution, canvas.width, canvas.height);
    gl.uniform1f(uniforms.time, time * 0.001);
    gl.uniform3f(uniforms.color, color[0], color[1], color[2]);
    gl.uniform1f(uniforms.xOffset, options.xOffset || 0.0);
    gl.uniform1f(uniforms.coreWidth, options.coreWidth || 0.0045);
    gl.uniform1f(uniforms.glowWidth, options.glowWidth || 0.035);
    gl.uniform1f(uniforms.smokeDensity, options.smokeDensity || 0.52);

    gl.clearColor(0, 0, 0, 0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.drawArrays(gl.TRIANGLES, 0, 6);

    if (!reduceMotion) rafId = requestAnimationFrame(render);
  }

  function handleResize() {
    resize();
    render();
  }

  resize();
  render();
  window.addEventListener("resize", handleResize);

  return () => {
    cancelAnimationFrame(rafId);
    window.removeEventListener("resize", handleResize);
    gl.deleteBuffer(positionBuffer);
    gl.deleteProgram(program);
  };
}

const cleanupLaser = initWebGLLaser(document.querySelector("[data-webgl-laser]"), {
  color: hexToRgb01(accent),
  xOffset: 0.0,
  coreWidth: 0.0045,
  glowWidth: 0.035,
  smokeDensity: 0.52,
  maxDpr: 1.5,
});

Tuning Knobs

  • Beam position: adjust xOffset; keep it in aspect-correct centered UV space.

  • Beam thickness: tune coreWidth separately from glowWidth; keep the core extremely thin.

  • Color: derive color from the brand accent, then soften halo and smoke in shader.

  • Smoke density: tune smokeDensity, FBM scale, drift speed, scatter width, and edge falloff.

  • Performance: reduce FBM octaves or cap maxDpr before changing the visual structure.

Taste Rules

  • The hottest beam core stays near white.

  • The halo and fog use the design's primary or strongest accent color.

  • Smoke blooms near the beam and dissipates outward.

  • Pulse affects glow only; avoid rapid flicker.

  • Content readability wins over bloom, haze, or cinematic drama.

Avoid

  • Hardcoding blue when the design uses another primary color.

  • Making the beam thick enough to read as a glowing bar.

  • Generic full-screen fog that is not concentrated around the beam.

  • Turning the effect into a Three.js scene, particle explosion, or multicolor neon background.

  • Letting the canvas intercept pointer events.

  • Dense fog or extreme bloom that washes out foreground UI.

Files

demo/source.json

{
  "version": 1,
  "provider": "Neuform",
  "synced_at": "2026-07-18T06:41:09.778Z",
  "skill": {
    "id": "webgl-laser",
    "label": "WebGL Laser",
    "usage_count": 862,
    "url": "https://neuform.ai/skill/webgl-laser"
  },
  "ranking": {
    "position": 1,
    "order": [
      "view_count desc",
      "community_favorite_count desc",
      "relation_created_at desc"
    ],
    "visible_design_count": 1
  },
  "design": {
    "id": "4dcea4c1-8ffe-40da-92de-85dc3b11c6e6",
    "slug": "squish-turn-your-ideas-into-stunning-visuals",
    "title": "Squish — Turn Your Ideas Into Stunning Visuals",
    "url": "https://neuform.ai/pages/squish-turn-your-ideas-into-stunning-visuals/index.html",
    "view_count": 351,
    "favorite_count": 14,
    "remix_count": 1
  },
  "html": {
    "original_sha256": "b135590e01cd1404f3db7f5ffb50fd59a58ace389dcf61284288e5ddecb2c878",
    "bundled_sha256": "6e64593f3f24b4210835e0a4e74652adada28f9cf48cd7f56daaca54b883d92b",
    "runtime_dependencies": [
      "https://fonts.googleapis.com",
      "https://fonts.googleapis.com/css2?family=Newsreader:wght@400;500&family=Inter:wght@300;400;500;600&family=Pacifico&display=swap"
    ],
    "hardened_sha256": "3fbd0a0d7461e528123c14a7acafae53e80ce0f43cf057b6a60a4e40deb1e976",
    "sandboxed_sha256": "7abbd51e4c973de2df8ce06f7b1f24f9c20cbfc4cabb0438b88e80177d26f50e",
    "security_profile": "sandboxed-srcdoc-v1"
  },
  "assets": {
    "preview": "assets/source-preview.jpg",
    "bundled": [
      {
        "bytes": 298959,
        "file": "assets/page-01-2f563338-39fa-47ea-9761-658d4f3f84db-1600w.jpg",
        "original_url": "https://hoirqrkdgbmvpwutwuwj.supabase.co/storage/v1/object/public/assets/assets/2f563338-39fa-47ea-9761-658d4f3f84db_1600w.jpg"
      },
      {
        "bytes": 217369,
        "file": "assets/page-02-4f5668c5-fc4a-44e0-bc5e-a664189d3c31-1600w.jpg",
        "original_url": "https://hoirqrkdgbmvpwutwuwj.supabase.co/storage/v1/object/public/assets/assets/4f5668c5-fc4a-44e0-bc5e-a664189d3c31_1600w.jpg"
      },
      {
        "bytes": 362749,
        "file": "assets/page-03-77415a2e-dcbc-4748-a29d-fced4821881a-1600w.jpg",
        "original_url": "https://hoirqrkdgbmvpwutwuwj.supabase.co/storage/v1/object/public/assets/assets/77415a2e-dcbc-4748-a29d-fced4821881a_1600w.jpg"
      },
      {
        "bytes": 214106,
        "file": "assets/page-04-c92852bb-a510-405a-85ab-ffa0fde136a4-1600w.jpg",
        "original_url": "https://hoirqrkdgbmvpwutwuwj.supabase.co/storage/v1/object/public/assets/assets/c92852bb-a510-405a-85ab-ffa0fde136a4_1600w.jpg"
      },
      {
        "bytes": 407279,
        "file": "../../../../assets/runtime/runtime-ccc8894b3418-runtime.js",
        "original_url": "https://cdn.tailwindcss.com/",
        "kind": "runtime-script",
        "sha256": "176e894661aa9cdc9a5cba6c720044cbbf7b8bd80d1c9a142a7c24b1b6c50d15"
      },
      {
        "bytes": 22003,
        "file": "../../../../assets/runtime/runtime-163ebd087cfc-iconify-icon-min.js",
        "original_url": "https://code.iconify.design/iconify-icon/1.0.7/iconify-icon.min.js",
        "kind": "runtime-script",
        "sha256": "851bed7af266f96a0be3edf9918daab48d8da4ce17c46040511bae450caf3dd6"
      }
    ],
    "skipped": []
  }
}

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Security

What our scanner noticed· Medium risk

  • Large base64-encoded blobs

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
Meng To
Publisher
Meng To (Community)
Source
MengTo/Skills · agent-skills/web-design/webgl-laser · commit 5ea1661
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Meng To.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download webgl-laser.zip