К основному содержанию
скилл CX Codex

webgl-laser — скилл для Claude Code: лазерный фон на WebGL

Скилл рисует фиксированный полноэкранный фон с тонким вертикальным лучом, цветным ореолом и дымкой. Пригодится, когда нужен кинематографичный задник под контентом.

Установить в Claude Code · Через Skills CLI (Vercel) Все способы и платформы ↓
npx skills add MengTo/Skills --skill webgl-laser -a claude-code
Открыть на GitHub

Что делает webgl-laser

Скилл описывает один эффект: вертикальный луч с белым ядром, узким ореолом в акцентном цвете страницы и мягким дымом вокруг. Канвас ставится position: fixed на весь экран, получает pointer-events: none и уходит под контент, а сам контент поднимается в отдельный слой по z-index. Цвет ореола скилл советует брать из переменной --brand-accent через getComputedStyle, ядро шейдер держит почти белым.

Внутри — вершинный и фрагментный шейдеры для raw WebGL и функция initWebGLLaser, которая компилирует программу, заводит буфер на два треугольника и синхронизирует u_resolution при ресайзе. Дым строится на шуме и fbm, пульсация задаётся синусом по u_time, так что мерцания и перебора цвета не будет. Есть ветка для prefers-reduced-motion и ограничение плотности пикселей через maxDpr.

Скилл прямо ограничивает область применения: только фон с лучом. Вёрстку страницы, тексты, обычные hero-сцены, частицы и другие анимации он не трогает, поэтому подключать его к задаче вроде «сделай красивый лендинг» бессмысленно — получите шейдер не там, где он нужен. Если в проекте уже есть свой рендерер, скилл советует не тащить второй, а встроить эффект в текущий.

Какие задачи решает

  • сделать фиксированный лазерный фон под контентом страницы
  • подобрать ореол луча под акцентный цвет бренда
  • добавить медленную пульсацию свечения без мерцания
  • уложить дым вокруг луча на шуме и fbm
  • учесть prefers-reduced-motion и плотность пикселей экрана

Примеры запросов агенту

  • сделай лазерный фон на WebGL по скиллу webgl-laser
  • подключи webgl-laser и возьми цвет ореола из --brand-accent
  • проверь, что канвас webgl-laser не перехватывает клики по странице

Как установить webgl-laser

1Через Skills CLI (Vercel)

npx skills add MengTo/Skills --skill webgl-laser -a claude-code

2Вручную: положить файлы

~/.claude/skills/webgl-laser/SKILL.md

Папка скилла целиком; для одного проекта — .claude/skills/webgl-laser/

По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов

Из того же набора144

dark-glass-clean-layout Скилл задаёт тёмную стеклянную систему интерфейса с колонками, плавающими карточками и спокойной глубиной. Нужен для рабочих пространств и консолей. CX Codex +1 1 400установок clean-minimal-beige-light-mode Задаёт направление дизайн-системы: тёплые бежевые поверхности, тихие модульные сетки и сдержанный акцент. Пригодится, когда интерфейс должен выглядеть спокойно и собранно. CX Codex +1 1 400установок mesh-gradient-dark-blue-clean Набор дизайн-правил, CSS-токенов и разметки для тёмно-синего интерфейса с процедурным градиентом на Canvas. Пригодится при вёрстке hero-секций и технологичных лендингов. CX Codex +1 1 400установок framed-grid-layout Скилл задаёт правила вёрстки страниц с тонкими видимыми границами секций, L-образными уголками и строгой привязкой к сетке. CX Codex +1 1 400установок image-first-grid-layout Задаёт правила вёрстки экранов с полноэкранными фото, полупрозрачными блоками и направляющими линиями сетки. Пригодится для кинематографичных промо-страниц. CX Codex +1 1 400установок dither-background Скилл для генерации тёмного монохромного фона с крупными квадратными пикселями и упорядоченным дизерингом Байера. CX Codex +1 1 400установок globe-particles Создаёт трёхмерную визуализацию сферы и орбитального кольца из светящихся частиц на WebGL и Three.js. Пригодится для космических тем и витрин данных. CX Codex +1 1 400установок agency-grid-layout-minimal Задаёт направление для минималистичного сайта агентства: строгая сетка, крупная типографика и сдержанные изображения. Пригодится при сборке редакционного интерфейса без карточной перегрузки. CX Codex +1 1 400установок

Похожие

code-slice-hero heygen-com Анимирует смену заголовка сеткой плиток с переворотом ячеек через WebGL 2 и Canvas 2D. Пригодится для сборки витрин и заставок с хронометражем 8 s в разрешении 1920×1080. CX Codex +1 10 000установок particle-system iart-ai Скилл для ИИ-кодинга, который помогает агенту собрать систему частиц: конфетти, снег, дым, искры, сетку из точек и GPU-частицы на Three.js Points. CX Codex +1 1 400установок threejs-materials calesthio Справочник по материалам Three.js: PBR, базовые, фонговские, кастомные шейдеры. Помогает быстро выбрать нужный материал для сетки, настроить текстуры и оптимизировать рендеринг. CX Codex +1 1 000установок threejs-webgl freshtechbro Скилл учит агента настраивать сцены Three.js, рендереры WebGL и WebGPU, свет, материалы и загрузку glTF-моделей. Пригодится для 3D-визуализаций и конфигураторов. CX Codex +1 4 600установок webgl martinholovsky Скилл по WebGL для кастомных шейдеров и визуальных эффектов: голограммы, постобработка, системы частиц. Пригодится, если делаете 3D-интерфейс. CX Codex +1 555установок design-dna zanwei Разбирает референсы на токены, стиль и визуальные эффекты, затем применяет профиль к макету. Нужен для работы со скриншотами, изображениями и URL. CX Codex +1 6 500установок

Рядом в теме «Фронтенд и UI»

Всё по теме «Фронтенд и UI» →

Вопросы о webgl-laser

Как установить webgl-laser в Claude?

Выполните npx skills add MengTo/Skills --skill webgl-laser -a claude-code. Скилл окажется в ~/.claude/skills/webgl-laser/SKILL.md.

Что делает webgl-laser?

Описывает полноэкранный WebGL-фон с тонким вертикальным лучом: белое ядро, узкий ореол в акцентном цвете и дымка вокруг. Канвас фиксируется за DOM и не ловит события мыши.

Для каких задач webgl-laser не подходит?

Он ограничен лазерным фоном. Вёрстку, тексты, обычные hero-сцены, частицы и посторонние анимации скилл не покрывает.

Исходный текст

SKILL.md · MIT
---
name: webgl-laser
description: 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.
---

# 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

```html
<canvas class="laser-canvas" data-webgl-laser></canvas>
<main class="page-content">
  ...
</main>
```

```css
.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.

```js
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.

```js
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`.

```js
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.

© MengTo · MIT · MengTo/Skills

Смотрите также

Спонсоры проекта

Компании, которые помогают держать аналитику открытой