ProUpFork Standard LicenseNewFeatured

Infall — Black Hole Hero Section (React + WebGL)

A React hero section with a black hole ray-traced live in WebGL: real light bending, a scroll dive into the event horizon, and light and dark themes.

by UpForkComponentUpdated 4 Oct 2026

0

Code

17 files · 1 package

The code comes with the Pro plan

The files above install into your project with one command, or download as a ZIP.

See the plans

About

A landing-page hero built round a black hole that is ray-traced live in the browser. Milestones 1 to 3 made the black hole, its dust and its opening, and the hero around it (navigation, headline, buttons, readouts, three layouts, light and dark themes). Milestone 4 added the dive (scrolling on, the camera falls into the hole), the fallbacks and the UpFork listing.

The black hole

  • Real light bending: every pixel follows its ray of light round a Schwarzschild black hole. The arch over the top is the far side of the disk, bent up and over. The crescent under the shadow is light that went round underneath. Stars behind are lensed too. All of it comes from the physics, not from a painting.
  • A disk that turns: inner gas laps outer gas at Kepler speeds. Each thread orbits rigidly at its own speed, so the pattern never winds up into noise, however long the page stays open, and soft pulses of light run along each thread a little faster than its gas, so the lines flow (shimmer).
  • Light-trail threads: distinct bright threads with dark gaps between them. Each has its own width and a brightness that swells and fades along its length. Threads thinner than a pixel blur into an even glow instead of shimmering.
  • Gold dust: tens of thousands of sparks on Kepler orbits along a thin band, laid out unevenly the way dust lies: crowded in some stretches and thin in others, in clusters of every size drawn out into streaks. Each is bent by the thin-lens equation, so nothing behind the hole shows through its shadow. Sparks far from the focus (the hole) open into soft bokeh discs. The band shows mostly to the sides of the hole, as in a long-lens photograph.
  • Dust lane: a blue-grey band along the disk's plane, like the Milky Way seen from inside the galaxy. It is drawn from each ray's elevation above that plane (tilted a little against the disk), so it stays straight while the camera moves.
  • The opening: stars first. Then the gas lights from the inside out: the crescent under the shadow, then the inner disk, then the band. The light runs on through the dust and the dust lane arrives last.
  • Pointer swing: with a mouse or pen, the camera leans a few degrees toward the pointer and eases back when it leaves. The bending is recomputed every frame, so the arch and crescent change shape as you move.
  • Film look: HDR, Doppler brightening (one side runs hotter), a warm bloom pyramid, ACES or AgX tone mapping, grain and a one-level dither so dark gradients never band.

The hero

  • Navigation: logo, links, Sign in and a Get started pill. Under 900 px the links fold into a menu that closes on a click outside it or on Escape.
  • Headline: a serif headline whose last words are set in a gradient italic, with a line above it (a tag and a phrase, optionally a link) and a line under it. Everything comes in word by word, in CSS only, so the text is in the server HTML and readable without JavaScript.
  • The eclipse button: an ivory button that turns into a little black hole when hovered: a dark disc grows from its middle with a soft edge, a bright arc orbits its rim like light round the hole, and a crescent glows underneath. While it is hovered, the big disk flares and turns faster, easing in and out.
  • The film button: frosted glass whose light, inside and on its rim, follows the pointer, with a gold dot orbiting its play mark.
  • Borders that hold up: every button's border is a gradient ring cut out by a mask, so it keeps the same width all the way round the curves and stays crisp at any zoom.
  • The dive: scrolling on, the hero stays pinned while the camera falls in. The words rise and fade; the camera falls faster and faster, rising over the disk and turning round the hole as matter does on its way in, while the golden ring sweeps out past the edges of the screen. The light reddens and dies at the horizon, and the page's background opens out of the dark from the middle (on paper, like light at the end of a tunnel), where the next section starts. It is driven by the scroll position, eased, so a wheel's steps never jump, and reversible.
  • Live readouts: the camera's distance (it falls during the dive), its inclination and the disk's speed in one corner (they follow the pointer swing and the boost), a scroll cue in the other.
  • A word on the sky (optional): backdropWord paints a word far behind the hole, bent round it by gravity like the stars. Off by default.
  • Three layouts: wide (desktop), tall (tablet) and phone. Each frames the black hole differently: field of view, where the hole sits, how far the disk turns, how many sparks.
  • Light, dark or auto: theme="auto" follows a .dark class or data-theme="dark" on the page. The light theme draws the same picture in sepia ink on paper. The hole and the dark gap round it stay a window onto the night: black, with the band and the crescent glowing in it, like an eclipse. Switching themes cross-fades.

Fast and polite

  • Off the main thread: the black hole renders in a Web Worker on an OffscreenCanvas. Creating the GPU context, compiling the shaders, working out the light paths and every frame happen there, so the page's own thread never waits on them (no long tasks while the page loads). The worker starts as soon as the page's script runs, before the hero mounts. Where a browser can't draw from a worker, the black hole falls back to the main thread by itself.
  • Cheap frames: the light paths are tabulated once (a fraction of a second, on the CPU) and the noise is baked once (on the GPU), so a frame is a few texture reads per pixel. A governor trades traced resolution for a steady frame rate. It starts from what an Intel Iris Plus laptop holds at 60 fps and follows the measured frame rate; the composite always runs at full resolution.
  • When it can't run: without WebGL 2 the box shows a drawing of the black hole instead (SVG, no image files), framed and themed like the live one; it follows the dive too. If the GPU takes the context away (a driver reset, the laptop waking, too many WebGL pages open), the drawing stands in and the black hole starts again by itself, fully lit (up to three times). The canvas stays hidden until its first frame, so a light page never shows a black box while it starts.
  • Polite: rendering pauses off screen and in hidden tabs, and past the horizon, where nothing is lit. The opening waits until the words are in, so the two entrances never fight for the GPU. With prefers-reduced-motion the words appear without moving, the black hole is one still frame, fully lit, with no opening and no swing, and the hero is one screen tall, with no dive.

Built with Next.js 16 (App Router), React 19, three.js, Tailwind CSS 4 and TypeScript.

Run it

pnpm install
pnpm build
pnpm start
  • http://localhost:3000 shows the hero. Scroll down for the dive. The section it lands in and the switch near the top (light, dark or your system's theme) are the demo's, not part of the hero.
  • http://localhost:3000/launch and http://localhost:3000/studio are the same hero with other words: a launch page with a waiting list (Darkwell) and a design studio (Lensmark). The copy is in components/sections/infall-hero/data.ts.
  • http://localhost:3000/compare puts the live render and a reference picture in one frame, with a divider you can drag. Space flips between the two whole pictures, S goes back to the split, and G opens a tuning panel with every setting. Copy changed settings puts the values you moved on the clipboard as JSON. The reference is read from public/_reference/black-hole.webp. That folder is git-ignored and never ships; without the file, the page says so and shows the render alone.

next dev works too, but on a laptop it can take a while to compile the three.js chunk; judge motion and timing on the production build. pnpm lint runs ESLint.

The UpFork listing

UpFork lists components through its code editor, which takes flat .ts/.tsx files (no CSS, no pictures, at most 20) and builds a live preview from pasted demos. node scripts/registry.mjs writes that version into registry/components/: the 17 files as components/infall-*.ts(x), importing each other as @/components/infall-*, the workers addressed next to the files that start them, and the hero's stylesheet as a string it renders in a <style> element. The demos are in registry/demos/ (written by hand), and LISTING.md has the listing's title, summary, description, categories and tags. Run the script again after changing the source.

Use it

The whole hero:

import { InfallHero } from "@/components/sections/infall-hero/infall-hero";

<InfallHero
  brand={{ name: "Acme", href: "/" }}
  nav={[{ label: "Pricing", href: "/pricing" }]}
  getStarted={{ label: "Get started", href: "/signup" }}
  eyebrow={{ tag: "New", text: "version 2 is out", href: "/blog/v2" }}
  title="Everything falls into place."
  description="One line on what your product does."
  primaryCta={{ label: "Start free", href: "/signup" }}
  secondaryCta={{ label: "Watch the film", href: "#film" }}
/>;

components/sections/infall-hero/data.ts has the demo's full copy, and types.ts documents every prop:

PropWhat it does
brandThe product's name and where the logo links.
nav, signIn, getStartedThe navigation bar's links and its two buttons (all optional).
eyebrowThe line above the headline: tag, text and an optional href.
title, highlightThe headline, and the words of it set in the gradient italic (default: the last two).
descriptionThe line under the headline.
primaryCta, secondaryCtaThe eclipse button and the film button.
hud{ label, scrollCue } for the corner readouts, or false for none.
backdropWordA word painted behind the hole and bent round it (default: none).
theme"auto" (default), "light" or "dark".
diveThe scroll dive (default true). The pinned stretch is 150svh; set --ifh-dive-length on the hero to change it.
blackHoleOverrides for any black hole setting, on top of the layout's framing.
headingAs"h1" (default) or "h2" if the page already has an h1.

The black hole alone:

import { BlackHole } from "@/components/black-hole/BlackHole";

<section className="relative h-svh">
  <BlackHole className="absolute inset-0" settings={{ roll: -10, doppler: 0.3 }} />
</section>;

The box is sized by className and the canvas fills it. settings overrides any default in components/black-hole/engine/settings.ts, and later changes apply live. Other props:

  • intro={false} starts fully lit.
  • parallax={false} keeps the camera still.
  • adaptive={false} traces every pixel whatever the frame rate (useful for stills).
  • startAfter={1500} holds the first frame until 1.5 s after the page started loading.

onReady hands you the engine (setSettings, getSettings, setActive, setPointer, setBoost, setDive, getView, replayIntro, dispose). onStats reports the frame rate once a second.

How it works

FileWhat it holds
black-hole/engine/geodesics.tsThe light-path tables. Paths from infinity are integrated with RK4 in the Binet equation u'' = 1.5u² − u, one per impact parameter. The rows crowd in near the critical b = 3√3/2, and each path is resampled along its own length. Only the camera's starting angle depends on its distance.
black-hole/engine/shaders.tsThe bake passes (thread table, slow fields), the scene pass, the bloom pyramid and the composite (with the light theme). Per pixel, the scene finds where the ray pierces the disk plane: every π along its path, read from the tables. It shades each crossing front to back, then the stars and the backdrop word where the light came from.
black-hole/engine/sparkles.tsThe gold dust: clustered sparks on Kepler orbits, projected with the ray tracer's camera, bent by the thin-lens equation, drawn as points or bokeh discs into the HDR frame.
black-hole/engine/core.tsThe renderer, with no DOM: render targets, the passes, the camera and its swing, the opening, the boost, the theme, the frame-rate governor and the loop. It runs in the worker or, as a fallback, on the page.
black-hole/engine/render.worker.tsThe worker that runs the core on an OffscreenCanvas.
black-hole/engine/prepare.tsStarts that worker as soon as the page's script runs (it has no three.js in it, so it loads at once).
black-hole/engine/engine.tsWhat the page talks to: takes over the prepared worker (or starts one, or falls back to the main thread), passes sizes, settings and pointer moves on, and draws the backdrop word with the page's own fonts.
black-hole/engine/tables.tsWorks the light paths out in a worker of their own when the core runs on the page.
black-hole/engine/settings.tsEvery setting, documented, with the tuned defaults.
black-hole/BlackHole.tsxThe React wrapper: pausing off screen, the pointer, reduced motion, the stand-in and starting again after a lost context.
black-hole/stand-in.tsxThe drawing shown without WebGL 2 or while the black hole starts again: an SVG framed from the same settings.
sections/infall-hero/The hero: infall-hero.tsx puts it together (with the dive, and the framing for each layout), parts.tsx holds its parts (the mark, the bar, the headline, the two buttons, the readouts), types.ts its props, data.ts the demos' words, and infall-hero.css its styles and both themes.

Lengths are in Schwarzschild radii (horizon 1, photon sphere 1.5). The picture is tuned for looks where physics and taste disagree. The disk is cut off well inside its hottest ring (innerRadius 1.7, hotRadius 2.6), so the arch over the top hugs the shadow and the ring under it carries on the same circle. The crescent under the shadow is kept smooth and shown only below the band, and the photon ring (thinner than a pixel at hero size) is off by default (photonRing). Stars are fully lensed near the rim, less so further out (starLens), because seen this close the whole frame lies inside the Einstein radius.

Before you buy

Everything you need to know

How this component gets from the preview into your project, and what buyers ask most.

How to use it

From the preview to your project

  1. 01

    Try it in the preview

    Switch between light and dark, and resize it to phone and tablet widths, before you decide.

  2. 02

    Get it

    Free products are yours with a free account. Paid ones settle in USDT on TRON, sent straight to the wallet on your order.

  3. 03

    Install it

    Components go into your project with one shadcn command, dependencies included. Everything else downloads with a checksum.

  4. 04

    Make it yours

    It's your code now: rename it, restyle it and wire in your real data.

  5. 05

    Or hand it to your AI

    Copy the prompt and let your coding assistant place it in your project, following your conventions.

Building with an AI assistant?

Copy a ready-made prompt with the code, its dependencies and a demo, and your assistant can put it in place for you.

Selling your own work?

Get approved once, then publish components, templates and apps, paid in USDT straight to your own wallet.

Components

More like this

Browse all components