Skip to content

IrisBackgroundsAurum Trail

Aurum Trail

A single fan of gold light trails rising from the bottom edge and bending right into a bright crest, each core rimmed with a thin violet chromatic fringe.

trail

Aurum Trail

Built to one specific long-exposure reference photo: unlike `ignis`'s two-sided sweep into a centred flare, this is one fan of strands, mostly straight along the bottom, that bends as a single group toward a crest sitting up and to the right — the read is closer to a road curving away than a starburst. Colour stays almost entirely gold, near-white at each strand's core, with two strands turned magenta for variety; the fringe is the more particular detail — a thin violet-blue line riding just outside every gold core, the way a bright highlight disperses through a real lens rather than staying one clean colour.

No pointer interaction — the fan holds its shape regardless of the cursor, same reasoning as `gargantua` and `singularity`.

  • warm
  • no-pointer
  • light-trails
  • perspective
Family
trail
Status
Available
Licence
Free

Install

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

Usage

Drop it straight into a page.

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

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">
      <AurumTrailField />
    </div>
  );
}

Component

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

"use client";

import { useEffect, useRef } from "react";

/**
 * Rebuild of a long-exposure light-trail photo: a wide bundle of warm gold
 * strands, spread across most of the bottom edge, in genuine perspective —
 * a single `invZ` depth term (1 at the camera, ~0 at the crest) drives both
 * the bundle's width and its centre line, the way `RoadField` derives its
 * converging lane bundle, rather than each strand bending toward a shared
 * target independently. That's what makes the strands narrow smoothly to a
 * single point exactly at the crest instead of merging early and coasting
 * on as a solid column. The bundle's centre itself drifts from off-centre
 * at the camera to further right at the crest, which is the "bending
 * right" read — a real depth cue, not a per-strand curve. A soft white-hot
 * flare sits at the crest (`IgnisField`'s move), every strand rimmed with a
 * thin violet/blue chromatic fringe the way a real lens disperses a bright
 * highlight, plus a couple of dedicated magenta strands for variety.
 *
 * Deliberately NOT built on `lib/shader-surface.ts` — this owns its own GL
 * context, resize handling, and animation loop rather than sharing the
 * catalogue's scaffolding.
 */

const VERTEX = `
attribute vec2 a_pos;
void main() { gl_Position = vec4(a_pos, 0.0, 1.0); }
`;

const FRAGMENT = `
precision highp float;
uniform vec2 u_res;
uniform float u_time;

float hash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123); }

const float VP_X   = 0.34;  /* crest, P-space (right of centre) */
const float VP_Y   = 0.76;  /* crest height, uv space */
const float BASE_X = -0.12; /* bundle centre at the camera (bottom edge) */
const float FAN_W  = 1.05;  /* bundle half-width at the camera */
const float MIN_BW = 0.006; /* half-width floor so the bundle never collapses to zero */

void main() {
  vec2 uv = gl_FragCoord.xy / u_res; // 0..1, y up
  float aspect = u_res.x / u_res.y;
  vec2 P = (uv - 0.5) * vec2(aspect, 1.0);

  /* ground: near-black navy, gathering a soft warmth around the crest */
  vec3 col = vec3(0.008, 0.011, 0.024);

  vec2 vpP = vec2(VP_X, VP_Y - 0.5);
  float distVP = length(P - vpP);
  col += vec3(0.4, 0.18, 0.1) * exp(-distVP * distVP * 9.0) * 0.5;

  /* depth: 1 at the camera (bottom edge), ~0 exactly at the crest — a real
     projection, the same term RoadField derives its lane bundle from,
     rather than each strand bending toward a shared x independently. */
  float d = clamp(uv.y / VP_Y, 0.0, 1.0);
  float invZ = pow(clamp(1.0 - d, 0.0, 1.0), 1.3);
  float aboveCrest = 1.0 - smoothstep(0.9, 1.04, d);

  /* the bundle's own centre line and half-width, both driven by invZ: the
     width shrinks to (almost) nothing exactly at the crest, and the centre
     drifts from BASE_X at the camera to VP_X at the crest — that drift,
     not a per-strand curve, is the "bending right" read. */
  float bundleX = mix(VP_X, BASE_X, invZ);
  float bw = FAN_W * invZ + MIN_BW;

  const int N = 18;
  for (int i = 0; i < N; i++) {
    float fi = float(i);
    float t = fi / float(N - 1);
    float h1 = hash(vec2(fi, 4.0));
    float h2 = hash(vec2(fi, 11.0));
    float h3 = hash(vec2(fi, 18.0));

    /* each strand is a fixed fraction of the bundle's own half-width, so
       every one of them converges on the same point together */
    float lane = mix(-1.05, 0.95, t) + (h1 - 0.5) * 0.12;
    float laneX = bundleX + bw * lane;

    /* thickness as a fraction of the spacing BETWEEN lanes, not of the
       bundle's total width — sizing it off the width instead (as a first
       pass did) makes every strand overlap its neighbours into one blob
       once there are more than a handful of them */
    float spacing = 2.0 * bw / float(N - 1);
    float thickness = mix(0.07, 0.13, h3) * spacing + 0.0006;
    float dx = P.x - laneX;
    float core = exp(-dx * dx / (thickness * thickness));
    float glow = exp(-dx * dx / (thickness * thickness * 4.0)) * 0.22;

    /* chromatic fringe: a thin violet/blue line riding just outside the
       gold core, the way a bright highlight disperses through a lens —
       kept tight and dim so it reads as a rim, not a colour wash */
    float fringeOffset = thickness * 1.6;
    float fdx = P.x - (laneX - fringeOffset);
    float fringe = exp(-fdx * fdx / (thickness * thickness * 0.35)) * 0.22;

    float wz = (1.0 - invZ) / max(invZ, 0.02);
    float flicker = 0.85 + 0.15 * sin(wz * 0.6 - u_time * (0.4 + h2 * 0.5) + fi * 2.3);

    /* the travelling light: a bright pulse riding each strand outward from
       the camera toward the crest. Phased in world depth (wz), not screen
       space, so it speeds up and compresses correctly as it approaches
       the vanishing point instead of crawling at a constant screen-space
       rate — same discipline as RoadField's dash spacing. */
    float travelSpeed = mix(0.7, 1.6, h2);
    float phase = fract(wz * 0.85 - u_time * travelSpeed - fi * 0.41);
    float travel = smoothstep(0.0, 0.22, phase) * (1.0 - smoothstep(0.5, 0.86, phase));

    vec3 gold = mix(vec3(1.0, 0.56, 0.13), vec3(1.0, 0.8, 0.4), h2);
    vec3 magenta = vec3(0.86, 0.28, 0.62);
    vec3 strandColor = mix(gold, magenta, step(0.96, h1));
    vec3 fringeColor = vec3(0.42, 0.34, 0.92);

    col += strandColor * (core + glow) * aboveCrest * flicker;
    col += vec3(1.0, 0.97, 0.9) * core * aboveCrest * flicker * (1.0 - invZ) * 0.6;
    col += fringeColor * fringe * aboveCrest * flicker;
    col += vec3(1.0, 0.94, 0.78) * core * travel * aboveCrest * 0.9;
    col += strandColor * glow * travel * aboveCrest * 0.5;
  }

  /* the crest flare: a soft wide bloom plus a tight hot core, independent
     of the strand loop so it stays bright even where the strands
     themselves have already faded into it */
  float flareGlow = exp(-distVP * distVP * 22.0);
  float flareCore = exp(-distVP * distVP * 160.0);
  col += vec3(1.0, 0.78, 0.42) * flareGlow * 0.55;
  col += vec3(1.0, 0.97, 0.9) * flareCore * 1.1;

  /* vignette, darker toward the corners */
  vec2 vc = uv - 0.5;
  float vig = length(vc * vec2(1.0, 1.05));
  col *= mix(1.0, 0.62, smoothstep(0.45, 1.1, vig));

  col = max(col, 0.0);

  /* dither against 8-bit banding on the dark ground */
  float grain = hash(gl_FragCoord.xy + fract(u_time)) - 0.5;
  col += grain * (2.5 / 255.0);

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

function compile(gl: WebGLRenderingContext, type: number, src: string) {
  const sh = gl.createShader(type);
  if (!sh) return null;
  gl.shaderSource(sh, src);
  gl.compileShader(sh);
  if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
    console.error(gl.getShaderInfoLog(sh));
    gl.deleteShader(sh);
    return null;
  }
  return sh;
}

export interface AurumTrailFieldProps {
  className?: string;
}

export function AurumTrailField({ className }: AurumTrailFieldProps) {
  const canvasRef = useRef<HTMLCanvasElement>(null);

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

    /* The context is created lazily, the first time the canvas actually
       comes near the viewport — not at mount. This page can carry many
       canvases at once (this hero plus every card in the "similar pieces"
       board below it), and a browser caps how many WebGL contexts a page
       may hold open at a time (Chrome: ~16); creating one per mounted
       canvas regardless of visibility means the excess is silently evicted
       once that cap is hit — indistinguishable, on screen, from the
       surface just being broken. */
    let gl: WebGLRenderingContext | null = null;
    let program: WebGLProgram | null = null;
    let buffer: WebGLBuffer | null = null;
    let u_res: WebGLUniformLocation | null = null;
    let u_time: WebGLUniformLocation | null = null;
    let started = false;
    let dead = false;

    const motionQuery = window.matchMedia("(prefers-reduced-motion: reduce)");

    let width = 0;
    let height = 0;
    const resize = () => {
      if (!gl) return false;
      const dpr = Math.min(window.devicePixelRatio || 1, 1.5);
      const w = Math.max(1, Math.round(canvas.clientWidth * dpr));
      const h = Math.max(1, Math.round(canvas.clientHeight * dpr));
      if (w === width && h === height) return true;
      width = w;
      height = h;
      canvas.width = w;
      canvas.height = h;
      gl.viewport(0, 0, w, h);
      gl.uniform2f(u_res, w, h);
      return true;
    };

    /* Idempotent: latches on the first call, so a surface that turns out
       to have no usable WebGL (or loses the context race for one) doesn't
       retry every time it re-enters view. */
    const setup = (): boolean => {
      if (started) return gl != null;
      started = true;

      gl = (canvas.getContext("webgl", {
        alpha: false,
        antialias: false,
        depth: false,
        stencil: false,
        preserveDrawingBuffer: false,
        powerPreference: "low-power",
      }) || canvas.getContext("experimental-webgl")) as WebGLRenderingContext | null;
      if (!gl) return false;

      const vs = compile(gl, gl.VERTEX_SHADER, VERTEX);
      const fs = compile(gl, gl.FRAGMENT_SHADER, FRAGMENT);
      if (!vs || !fs) {
        gl = null;
        return false;
      }

      program = gl.createProgram();
      if (!program) {
        gl = null;
        return false;
      }
      gl.attachShader(program, vs);
      gl.attachShader(program, fs);
      gl.linkProgram(program);
      if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
        console.error(gl.getProgramInfoLog(program));
        gl = null;
        program = null;
        return false;
      }
      gl.useProgram(program);
      gl.deleteShader(vs);
      gl.deleteShader(fs);

      buffer = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
      /* one oversized triangle covers the viewport with no diagonal seam */
      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
      const attr = gl.getAttribLocation(program, "a_pos");
      gl.enableVertexAttribArray(attr);
      gl.vertexAttribPointer(attr, 2, gl.FLOAT, false, 0, 0);

      u_res = gl.getUniformLocation(program, "u_res");
      u_time = gl.getUniformLocation(program, "u_time");
      width = height = 0;
      resize();
      return true;
    };

    let raf = 0;

    const draw = (time: number) => {
      if (!gl || !resize()) return;
      gl.uniform1f(u_time, time);
      gl.drawArrays(gl.TRIANGLES, 0, 3);
    };

    const loop = (now: number) => {
      raf = requestAnimationFrame(loop);
      draw(now / 1000);
    };

    let visible = false;

    const start = () => {
      if (dead || !visible || document.hidden || raf) return;
      if (!started && !setup()) return; // no WebGL at all — nothing to play
      if (motionQuery.matches) {
        draw(12.5);
        return;
      }
      raf = requestAnimationFrame(loop);
    };
    const stop = () => {
      if (raf) cancelAnimationFrame(raf);
      raf = 0;
    };

    const io = new IntersectionObserver(
      ([entry]) => {
        visible = entry.isIntersecting;
        if (visible) start();
        else stop();
      },
      { rootMargin: "160px" }
    );
    io.observe(canvas);

    const ro = new ResizeObserver(() => {
      if (motionQuery.matches) draw(12.5);
    });
    ro.observe(canvas);

    const onVisibility = () => (document.hidden ? stop() : start());
    document.addEventListener("visibilitychange", onVisibility);
    const onMotionChange = () => {
      if (motionQuery.matches) {
        stop();
        draw(12.5);
      } else {
        start();
      }
    };
    motionQuery.addEventListener("change", onMotionChange);

    const onContextLost = (e: Event) => {
      e.preventDefault();
      dead = true;
      stop();
    };
    const onContextRestored = () => {
      dead = false;
      started = false;
      width = height = 0;
      start();
    };
    canvas.addEventListener("webglcontextlost", onContextLost);
    canvas.addEventListener("webglcontextrestored", onContextRestored);

    return () => {
      stop();
      ro.disconnect();
      io.disconnect();
      document.removeEventListener("visibilitychange", onVisibility);
      motionQuery.removeEventListener("change", onMotionChange);
      canvas.removeEventListener("webglcontextlost", onContextLost);
      canvas.removeEventListener("webglcontextrestored", onContextRestored);
      if (gl) {
        gl.deleteBuffer(buffer);
        gl.deleteProgram(program);
        gl.getExtension("WEBGL_lose_context")?.loseContext();
      }
    };
  }, []);

  return (
    <div
      className={`aurum-trail-field${className ? ` ${className}` : ""}`}
      aria-hidden="true"
      style={{ position: "absolute", inset: 0, overflow: "hidden", background: "#050810" }}
    >
      <canvas ref={canvasRef} style={{ width: "100%", height: "100%", display: "block" }} />
    </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