Skip to content

IrisBackgroundsFiber Burst

Fiber Burst

A fan of thin blue-white strands bursting rightward from a single warm-lit chip at the left edge, each carrying its own travelling pulse of light — a direct build of the reference photo.

beam

Fiber Burst

A small chip sits lit at the left edge, warm against everything around it, a few bare leads reaching back into the dark behind it. From its face two dozen thin, near-straight strands fan out to the right, each at its own slight angle, thinning and dimming the further out it travels — the way a bundle of fibre-optic cable looks lit from one end and photographed in the dark.

The animation is the point: every strand carries its own narrow band of light travelling continuously outward along it, away from the chip, at its own speed and phase, so the bundle never pulses in sync and the light visibly originates at the source rather than just sitting lit. A soft glow independently gathers wherever the cursor rests — the one thing a field built this plainly can answer back with.

  • warm-cool
  • pointer-driven
  • circuit
  • motion-trail
Family
beam
Status
Available
Licence
Free

Install

No installation needed — self-contained, paste-in code.

Usage

Drop it straight into a page.

example.tsx
import { FiberBurstField } from "./FiberBurstField";

export default function Example() {
  return (
    // Fills its nearest positioned ancestor (it renders itself `absolute
    // inset-0`) — give it a sized, relatively positioned box.
    <div className="relative isolate h-[32rem] w-full overflow-hidden rounded-2xl">
      <FiberBurstField />
    </div>
  );
}

Component

The real source, exactly as it ships — multiple files, kept together.

"use client";

import { useEffect, useRef } from "react";
import { mountShaderSurface } from "@/lib/shader-surface";

/**
 * A live WebGL burst of light from a single lit source at the left edge —
 * a small warm-glowing chip with a few bare leads reaching back into the
 * dark, and from its face a fan of two dozen thin, near-straight blue-white
 * strands running out to the right, each at its own slight angle. A direct
 * build of the reference photo (a chip firing a bundle of fibre-optic
 * light into the dark), not a variation on the rest of the catalogue's
 * mood: warm at the source, cooling to blue-white the moment the light
 * leaves it.
 *
 * The animation IS the point: every strand carries a narrow band of light
 * travelling continuously outward along it, away from the source, so the
 * light visibly originates there and keeps flowing rather than just
 * sitting lit — each strand at its own speed and phase, so the bundle
 * never pulses in sync. The whole fan also thins and dims the further out
 * it travels, the way real fibre attenuates.
 *
 * Interactive: a soft glow independently gathers wherever the cursor
 * rests, the one thing a field like this can answer back with.
 *
 * One of the reusable background fields (`ShaftField`, `SignalGridField`,
 * …). Drop it into any `position: relative`/`isolate` parent — it fills
 * the box. Built on `lib/shader-surface.ts`, so every degradation path is
 * already handled: no WebGL, a blocked or lost context, a hidden tab, or
 * `prefers-reduced-motion` all leave the CSS `.iris-fiberburstfield__floor`
 * underneath visible.
 *
 * Reading guard: when `guardSelector` resolves to an element, the field
 * measures that block every frame and clamps its own luminance under a
 * ceiling inside that region (hue and saturation untouched). `null` (the
 * default) turns the guard off — for decorative use where nothing sits on
 * top.
 */

/* Palette, sRGB 0–1. Uniforms, not tokens — the warm source against a cool
   beam lives nowhere in the accent ramp. */
const PALETTE: Record<string, [number, number, number]> = {
  u_ground: [0.008, 0.009, 0.013], // near-black ground the beams travel through
  u_halo: [0.09, 0.15, 0.24], // the strands' soft outer bleed
  u_body: [0.42, 0.62, 0.86], // the body of each strand
  u_core: [0.88, 0.95, 1.0], // the hot near-white centreline and pulses
  u_chip: [1.0, 0.87, 0.62], // the source itself — warm, not blue
};

const FRAG = `
uniform vec2  u_res;
uniform float u_time;
uniform float u_scale;
uniform vec3  u_pointer;  /* x, y (0 bottom .. 1 top), presence 0..1 */

uniform vec3 u_ground;
uniform vec3 u_halo;
uniform vec3 u_body;
uniform vec3 u_core;
uniform vec3 u_chip;

uniform vec4  u_readA;
uniform float u_guard;

const int   N_RAYS    = 22;
const float MAX_ANGLE = 0.56;
const float MAX_LEN   = 1.7;
const float REPEATS   = 6.0;
const float TAU       = 6.2831853;

void main() {
  vec2 res = u_res / u_scale;
  vec2 uv  = gl_FragCoord.xy / u_scale / res;            /* 0..1, y up */
  vec2 sc  = vec2(res.x / max(res.y, 1.0), 1.0);
  vec2 q   = uv * sc;

  vec2 srcUV = vec2(0.115, 0.52);
  vec2 src   = srcUV * sc;

  vec3 col = u_ground;

  /* a soft ambient wash around the source, under the individual strands */
  col += u_halo * exp(-length(q - src) / 0.62) * 0.10;

  for (int i = 0; i < N_RAYS; i++) {
    float fi = (float(i) + 0.5) / float(N_RAYS);        /* 0..1 */
    float s  = fi * 2.0 - 1.0;                           /* -1..1 */
    float h1 = fract(sin(fi * 91.7 + 1.0) * 43758.5453);
    float h2 = fract(sin(fi * 131.3 + 4.7) * 12543.233);
    float h3 = fract(sin(fi * 57.9 + 9.1) * 39217.113);

    float spread = sign(s) * pow(abs(s), 0.82);
    float angle  = spread * MAX_ANGLE + (h1 - 0.5) * 0.05;
    vec2  dir    = vec2(cos(angle), sin(angle));

    vec2  rel   = q - src;
    float t     = max(dot(rel, dir), 0.0);
    float perp  = length(rel - dir * t);
    float along = clamp(t / MAX_LEN, 0.0, 1.0);

    float speed = 0.22 + h2 * 0.28;
    float phase = h3 * TAU;
    float pulse = pow(max(0.0, cos(along * REPEATS * TAU - u_time * speed * TAU + phase)), 16.0);

    float atten  = exp(-along * 1.15);
    float bright = (0.35 + 0.65 * h2) * atten;

    float coreW = mix(0.0045, 0.0011, along);
    float bodyW = mix(0.016,  0.0038, along);
    float haloR = mix(0.06,   0.016,  along);

    float coreG = exp(-perp * perp / (coreW * coreW));
    float bodyG = exp(-perp * perp / (bodyW * bodyW));
    float haloG = exp(-perp / haloR);

    col += u_halo * haloG * bright * 0.30;
    col += u_body * bodyG * bright * (0.45 + 0.55 * pulse);
    col += u_core * coreG * bright * (0.30 + 0.85 * pulse);
  }

  /* the chip itself: a small warm-lit source block with a soft bleed and a
     few bare leads reaching left into the dark behind it */
  vec2 chipHalf = vec2(0.02, 0.065);
  vec2 cd = abs(q - src) - chipHalf;
  float chipDist = length(max(cd, 0.0)) + min(max(cd.x, cd.y), 0.0);
  float chipFace = smoothstep(0.006, -0.006, chipDist);
  float chipGlow = exp(-max(chipDist, 0.0) / 0.10);
  col = mix(col, u_chip, chipFace);
  col += u_chip * chipGlow * 0.55;

  float leftEdge  = src.x - chipHalf.x;
  float tickStart = leftEdge - 0.055;
  for (int i = 0; i < 5; i++) {
    float ty = src.y + (float(i) - 2.0) * 0.024;
    float tickDist = abs(q.y - ty);
    float xMask = smoothstep(tickStart - 0.004, tickStart + 0.004, q.x)
                * smoothstep(leftEdge + 0.004, leftEdge - 0.004, q.x);
    col += u_chip * exp(-tickDist * tickDist / 0.0009) * xMask * 0.22;
  }

  /* the pointer's own small glow — the one thing this field answers back */
  vec2  toP  = q - u_pointer.xy * sc;
  float spot = u_pointer.z * exp(-dot(toP, toP) / (0.045 * 0.045));
  col += u_core * spot * 0.35;

  col = max(col, 0.0);

  /* ---- the reading guard (see TileField for the full rationale) ---- */
  vec2 rd = abs(uv - u_readA.xy) / max(u_readA.zw, vec2(0.02));
  float m = mix(max(rd.x, rd.y), length(rd), 0.4);
  float band = 1.0 - smoothstep(0.72, 2.1, m);
  col = mix(col, holdUnder(col, 0.09), band * u_guard);

  col += (bayer8(gl_FragCoord.xy) - 0.5) * (2.2 / 255.0);

  gl_FragColor = vec4(col, 1.0);
}
`;

export interface FiberBurstFieldProps {
  className?: string;
  /**
   * CSS selector for the block the reading guard should keep readable,
   * resolved against `document`. `null` (the default) turns the guard off.
   */
  guardSelector?: string | null;
}

export function FiberBurstField({
  className,
  guardSelector = null,
}: FiberBurstFieldProps) {
  const canvasRef = useRef<HTMLCanvasElement>(null);

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;

    const guardOn = guardSelector != null;
    let guardEl: Element | null | undefined;
    const readGuardEl = () => {
      if (guardEl === undefined) {
        guardEl = guardOn ? document.querySelector(guardSelector as string) : null;
      }
      return guardEl;
    };

    return mountShaderSurface(canvas, {
      fragment: FRAG,
      uniforms: [...Object.keys(PALETTE), "u_readA", "u_guard"],
      onInit: (gl, u) => {
        for (const name of Object.keys(PALETTE)) {
          if (u[name]) gl.uniform3fv(u[name], PALETTE[name]);
        }
        if (u.u_readA) gl.uniform4f(u.u_readA, 0.5, 0.5, 0.44, 0.32);
        if (u.u_guard) gl.uniform1f(u.u_guard, guardOn ? 1 : 0);
      },
      onFrame: (gl, u, s) => {
        if (!guardOn || !u.u_readA) return;
        let cx = 0.5, cy = 0.5, hw = 0.44, hh = 0.32;
        const el = readGuardEl();
        const { rect } = s;
        if (el && rect.width > 0 && rect.height > 0) {
          const r = el.getBoundingClientRect();
          const padX = rect.width * 0.09;
          const padY = rect.height * 0.11;
          cx = (r.left + r.width / 2 - rect.left) / rect.width;
          cy = 1 - (r.top + r.height / 2 - rect.top) / rect.height;
          hw = (r.width / 2 + padX) / rect.width;
          hh = (r.height / 2 + padY) / rect.height;
        }
        gl.uniform4f(u.u_readA, cx, cy, hw, hh);
      },
      onPainted: () => canvas.setAttribute("data-shader", "on"),
      /* No onIdle — same contract as ShaftField / SignalGridField: once
         painted, the last frame stays on screen while the surface is
         parked off-view, so scrolling away and back does not crossfade to
         the static floor. A lost context still clears it below. */
      onLost: () => canvas.removeAttribute("data-shader"),
      maxPixels: 2_000_000,
      dprCap: 1.5,
    });
  }, [guardSelector]);

  return (
    <div
      className={`absolute inset-0 overflow-hidden${className ? ` ${className}` : ""}`}
      aria-hidden="true"
    >
      <div className="absolute inset-0 [background:radial-gradient(_9%_16%_at_11%_52%,oklch(0.9_0.06_80)_0%,oklch(0.7_0.1_60_/_0.6)_45%,transparent_78%_),conic-gradient(_from_250deg_at_11%_52%,oklch(0.55_0.07_230_/_0.5)_0deg,transparent_55deg,transparent_305deg,oklch(0.55_0.07_230_/_0.5)_360deg_),radial-gradient(_160%_130%_at_11%_52%,oklch(0.14_0.03_230_/_0.4)_0%,transparent_55%_),oklch(0.04_0.005_240)]" />
      <canvas ref={canvasRef} className="absolute inset-0 w-full h-full opacity-0 transition-opacity duration-[--duration-slow] ease-[--ease-standard] data-[shader=on]:opacity-100" />
    </div>
  );
}

More backgrounds

Custom work

Need one that isn't in the catalogue?

Describe what you're after and I'll reply by email — no promise on turnaround yet, this is a new channel, not a service with a set price or queue.

Fardin Omor Afnan

Fardin Omor Afnan

Reads and answers every request himself

Or start with a section.

Whole page sections, composed and ready to drop in — take one and build the rest of the page around it.

Browse sections