IrisBackgroundsHorizon
Horizon
A giant circle sits just off the bottom edge — only its upper rim curves into view, bright where the rim itself falls, dark above and below it.
Horizon
Reverse-measured, pixel by pixel, off one reference screenshot rather than guessed: the rim's on-screen curve, both falloff widths, the left/right dimming and the five colour stops are all sampled straight off the source PNG outside its text boxes. The shape is a ring, not a blob — every pixel's distance to a circle centred just below the bottom edge is compared against that circle's radius, and the glow is a gaussian of the difference, so brightness peaks exactly on the rim rather than at a point. The falloff either side is asymmetric — tight above the rim so the top of the frame reads near-black, a little wider below it — and the rim itself dims toward the left and right edges, the way a lit sphere's rim falls away from the point facing the viewer.
The rim leans a few percent toward the cursor and tips its height slightly, eased, plus a small bloom rides the pointer directly; with no pointer it idles on a slow, barely-there drift. Fixed palette, not the token ramp — the measured hottest pixel is a pale sky blue, not white, and that exact horizon lives nowhere in the site's own accent ramp, so the CSS floor carries the same colours as a wide, flat radial gradient.
Install
No installation needed — self-contained, paste-in code.
Usage
Drop it straight into a page.
import { HorizonField } from "./HorizonField";
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">
<HorizonField />
</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 field reverse-measured, pixel by pixel, off one reference
* screenshot: a near-black frame with a huge circle whose centre sits just
* *below* the bottom edge, so only its upper rim curves into view — bright
* where the rim itself falls, fading fast above it (the nav reads
* near-black) and rather faster below it too (a real gaussian ring, not a
* blob glow rising from the centre the way `EmberRidgeField` works).
*
* The geometry (`u_center`/`u_radius`) and both falloff widths were fit by
* sampling the source PNG outside its text boxes: distance from a fitted
* circle predicts the rim's on-screen path to within a couple of pixels
* across the whole width, confirming it really is one circle, not a warped
* curve. Brightness along that rim isn't uniform, though — it tapers off
* toward the left/right edges (`u_taper`), the way a lit sphere's rim would
* naturally dim away from the point facing the viewer. The five palette
* stops are the actual measured colours at five brightness levels of that
* same reverse-fit, not a guessed ramp — the hottest measured pixel in the
* source lands at (185, 233, 246), a pale sky blue, never pure white.
*
* Fixed palette, not the token ramp — same reasoning as `EmberRidgeField`:
* this is one specific reference's blue-white horizon, not a themeable
* accent. The CSS floor below approximates the same rim as a wide, flat
* radial gradient, so no-WebGL, a lost context, a hidden tab or
* `prefers-reduced-motion` all leave a close composition on screen.
*
* The pointer leans the rim a few percent horizontally and tips its height
* slightly, eased, plus drops a small bloom of its own — the reference has
* no pointer of its own to match, so this stays a light touch. With no
* pointer the rim idles on a slow, barely-there drift.
*
* Drop it into any `position: relative`/`isolate` parent — it fills the
* box. Reading guard: when `guardSelector` resolves to an element, the
* field clamps its own luminance under a ceiling inside that region every
* frame (hue untouched), so copy laid over it holds contrast. `null` (the
* default) turns the guard off.
*/
const PALETTE: Record<string, [number, number, number]> = {
u_ground: [0.0039, 0.0392, 0.0667], // t=0.00 — measured background
u_deep: [0.0941, 0.2627, 0.3961], // t=0.25
u_blue: [0.1804, 0.5451, 0.7333], // t=0.50
u_cyan: [0.4824, 0.7843, 0.9098], // t=0.75
u_hot: [0.7255, 0.9137, 0.9647], // t=1.00 — the actual brightest pixel measured
};
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; /* t=0.00 */
uniform vec3 u_deep; /* t=0.25 */
uniform vec3 u_blue; /* t=0.50 */
uniform vec3 u_cyan; /* t=0.75 */
uniform vec3 u_hot; /* t=1.00, right at the rim's brightest point */
uniform float u_speed; /* idle drift speed */
uniform vec4 u_readA;
uniform float u_guard;
void main() {
vec2 res = u_res / u_scale;
vec2 uv = gl_FragCoord.xy / u_scale / res; /* 0..1, y up */
float aspect = res.x / max(res.y, 1.0);
vec2 P = (uv - 0.5) * vec2(aspect, 1.0);
/* a huge circle centred just below the bottom edge; only its upper rim
ever enters view. the pointer leans it a little and tips the rim height */
float t = u_time * u_speed;
vec2 drift = vec2(sin(t * 0.6), cos(t * 0.5)) * 0.006;
vec2 lean = vec2(
(u_pointer.x - 0.5) * aspect * 0.05,
(u_pointer.y - 0.5) * 0.02
) * u_pointer.z;
vec2 center = vec2(0.0, -0.742) + drift + lean;
float radius = 1.01;
float dist = distance(P, center);
float ringDist = dist - radius;
/* asymmetric gaussian ring, both widths fit from the reference: a tight
fade above the rim (into the nav band) and a slightly wider one below
it (the wash the reference carries a little way down the page) */
float sigma = ringDist > 0.0 ? 0.04 : 0.085;
float ring = exp(-(ringDist * ringDist) / (2.0 * sigma * sigma));
/* the rim dims toward the left/right edges, like a lit sphere's rim
falling away from the point facing the viewer */
float taper = exp(-(P.x * P.x) / (2.0 * 0.677 * 0.677));
float g = ring * taper;
/* the measured five-stop ramp: ground, deep, blue, cyan, hot at
t = 0, 0.25, 0.5, 0.75, 1.0 */
vec3 col = u_ground;
col = mix(col, u_deep, smoothstep(0.0, 0.35, g));
col = mix(col, u_blue, smoothstep(0.3, 0.6, g));
col = mix(col, u_cyan, smoothstep(0.55, 0.85, g));
col = mix(col, u_hot, smoothstep(0.8, 1.05, g));
/* a small bloom that rides the pointer directly, on top of the base rim */
vec2 pd = P - (u_pointer.xy - 0.5) * vec2(aspect, 1.0);
col += u_cyan * u_pointer.z * exp(-dot(pd, pd) * 7.0) * 0.06;
col = max(col, 0.0);
/* ---- the reading guard ---- */
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 guardBand = 1.0 - smoothstep(0.72, 2.1, m);
col = mix(col, holdUnder(col, 0.18), guardBand * u_guard);
col += (bayer8(gl_FragCoord.xy) - 0.5) * (1.8 / 255.0);
gl_FragColor = vec4(col, 1.0);
}
`;
export interface HorizonFieldProps {
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;
/** Idle drift speed; 0 freezes the rim in place. */
speed?: number;
}
export function HorizonField({
className,
guardSelector = null,
speed = 0.05,
}: HorizonFieldProps) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const values = { speed };
const valuesRef = useRef(values);
valuesRef.current = values;
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_speed", "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_speed) gl.uniform1f(u.u_speed, valuesRef.current.speed);
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 (u.u_speed) gl.uniform1f(u.u_speed, valuesRef.current.speed);
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"),
onLost: () => canvas.removeAttribute("data-shader"),
maxPixels: 2_200_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(_130%_46%_at_50%_23%,#b9e9f6_0%,#7bc8e8_14%,#2e8bbb_30%,#184365_46%,#010a11_64%,#010a11_100%_)]" />
<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>
);
}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
Reads and answers every request himself