Imported library

← Imported library
Free sampleMIT licence

Retro-futurist scrolling wireframe terrain hero background (value-noise heights, horizon glow)

3d scene

3d scene from AleBrito124356/threejs-hero-collection.

From prompt to live site

  1. 01

    Copy the code

    Grab every file as one text, each carrying its licence notice.

  2. 02

    Paste into your AI

    Drop it into Claude, Cursor or v0 — or straight into your own project.

  3. 03

    Launch it live

    Adapt the copy, wire your data, and ship — with the notice kept alongside.

Read the full code
Retro-futurist scrolling wireframe terrain hero background (value-noise heights, horizon glow)
AleBrito124356/threejs-hero-collection@08e9325ce258 · 1 code file(s)

Licence notice — required by the licence; keep it with this code:

MIT License

Copyright (c) 2026 Alejandro Brito

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

--- terrain.html ---
<!DOCTYPE html>
<!--
MIT License

Copyright (c) 2026 Alejandro Brito

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-->
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Scrolling terrain hero — threejs-hero-collection</title>
<style>
  :root {
    --bg: #0A0D14;
    --border: #1E293B;
    --accent: #2563EB;
    --accent-2: #38BDF8;
    --text: #F1F5F9;
    --body: #94A3B8;
    --muted: #64748B;
    --mono: 'Cascadia Code', Consolas, Menlo, monospace;
  }
  * { box-sizing: border-box; margin: 0; padding: 0; }
  html, body { height: 100%; }
  body {
    background: var(--bg);
    color: var(--body);
    font-family: -apple-system, 'Segoe UI', Inter, Roboto, sans-serif;
    line-height: 1.6;
    overflow-x: hidden;
  }
  #scene { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }

  .site-header {
    position: fixed; top: 0; left: 0; right: 0; z-index: 2;
    display: flex; align-items: baseline; gap: 16px;
    padding: 14px 24px;
    background: rgba(10, 13, 20, 0.72);
    border-bottom: 1px solid var(--border);
  }
  .site-header .repo { font-family: var(--mono); color: var(--accent-2); text-decoration: none; font-weight: 600; }
  .site-header .tag { color: var(--muted); font-size: 0.85rem; flex: 1; min-width: 0;
                      white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .site-header nav { display: flex; gap: 14px; }
  .site-header nav a { color: var(--body); text-decoration: none; font-size: 0.85rem; }
  .site-header nav a:hover { color: var(--accent-2); }

  .hero {
    position: relative; z-index: 1;
    min-height: 100vh;
    display: flex; flex-direction: column; justify-content: center; align-items: center;
    text-align: center; padding: 96px 24px 64px;
  }
  .hero h1 {
    color: var(--text);
    font-size: clamp(2rem, 6vw, 3.6rem);
    font-weight: 700; letter-spacing: -0.02em; line-height: 1.1;
    max-width: 720px;
    text-shadow: 0 2px 24px rgba(10, 13, 20, 0.8);
  }
  .hero p {
    margin-top: 18px; max-width: 560px; font-size: 1.05rem;
    text-shadow: 0 1px 12px rgba(10, 13, 20, 0.8);
  }
  .cta {
    margin-top: 32px; display: inline-block;
    padding: 12px 28px; border-radius: 8px;
    background: linear-gradient(135deg, var(--accent), var(--accent-2));
    color: #fff; text-decoration: none; font-weight: 600; font-size: 0.95rem;
    transition: filter 0.2s ease, transform 0.2s ease;
  }
  .cta:hover { filter: brightness(1.12); transform: translateY(-1px); }
  a:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 3px; }

  @media (prefers-reduced-motion: reduce) {
    .cta { transition: none; }
    .cta:hover { transform: none; }
  }
  @media (max-width: 480px) {
    .site-header .tag { display: none; }
  }
</style>

<!-- The ONLY external dependency of this repo: three.js, pinned to 0.170.0.
     To self-host, download three.module.js and point this import map at it. -->
<script type="importmap">
{
  "imports": {
    "three": "https://unpkg.com/three@0.170.0/build/three.module.js"
  }
}
</script>
</head>
<body>
<header class="site-header">
  <a class="repo" href="index.html">threejs-hero-collection</a>
  <span class="tag">Drop-in animated hero backgrounds built with Three.js</span>
  <nav aria-label="Repository links">
    <a href="index.html">All heroes</a>
    <a href="https://github.com/AleBrito124356/threejs-hero-collection">GitHub</a>
  </nav>
</header>

<canvas id="scene" aria-hidden="true"></canvas>

<main class="hero">
  <h1>The road ahead renders itself</h1>
  <p>An endless wireframe terrain flowing toward the camera — heights sampled from value noise, so the landscape never repeats.</p>
  <a class="cta" href="https://github.com/AleBrito124356/threejs-hero-collection">Explore the code</a>
</main>

<script type="module">
import * as THREE from 'three';

/* =============================== customize =============================== */
const COLORS = {
  background: '#0A0D14',
  grid:       '#2563EB',   // wireframe lines
  ridge:      '#38BDF8',   // brightest peaks
  glow:       '#1D4ED8',   // horizon glow (kept deep blue, not neon)
};
const TERRAIN = {
  width: 70,          // world units across
  depth: 90,          // world units toward the horizon
  segmentsX: 96,
  segmentsZ: 110,
  height: 3.4,        // max ridge height
  valleyWidth: 5.0,   // flat corridor down the middle (the "road")
  scrollSpeed: 2.4,   // world units per second toward the camera
  noiseScale: 0.09,   // lower = wider mountains
};
/* ========================================================================= */

const canvas = document.getElementById('scene');
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
// DPR cap: see README "Performance notes".
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(window.innerWidth, window.innerHeight);

const scene = new THREE.Scene();
scene.background = new THREE.Color(COLORS.background);
// Fog swallows the far rows of the grid so the terrain dissolves into the
// horizon instead of ending at a visible edge.
scene.fog = new THREE.Fog(COLORS.background, 20, 62);

const camera = new THREE.PerspectiveCamera(62, window.innerWidth / window.innerHeight, 0.1, 120);
camera.position.set(0, 2.6, 9);
camera.lookAt(0, 1.0, -30);

/* --- Value noise in plain JavaScript -------------------------------------
   Same construction as the GLSL version in aurora.html: hashed lattice
   values, bilinearly interpolated with a smoothstep weight, then stacked
   into fbm octaves. Deterministic — the same (x, z) always returns the
   same height, which is what makes seamless scrolling possible.          */
function hash(x, z) {
  const s = Math.sin(x * 127.1 + z * 311.7) * 43758.5453123;
  return s - Math.floor(s);
}
function smooth(t) { return t * t * (3 - 2 * t); }
function noise(x, z) {
  const xi = Math.floor(x), zi = Math.floor(z);
  const xf = x - xi,        zf = z - zi;
  const u = smooth(xf), v = smooth(zf);
  const a = hash(xi, zi),     b = hash(xi + 1, zi);
  const c = hash(xi, zi + 1), d = hash(xi + 1, zi + 1);
  return a + (b - a) * u + (c - a) * v + (a - b - c + d) * u * v;
}
function fbm(x, z) {
  let value = 0, amplitude = 0.5;
  for (let o = 0; o < 4; o++) {
    value += amplitude * noise(x, z);
    x = x * 2.03 + 19.19; z = z * 2.03 + 7.77;
    amplitude *= 0.5;
  }
  return value;
}

/* --- The scrolling trick -------------------------------------------------
   The MESH never moves. Each frame we advance a scroll offset and re-sample
   the noise at (x, z + offset): heights flow toward the camera while the
   grid lines stay put — the classic retro "infinite drive" illusion, with
   no geometry recycling and no visible loop seam (noise never repeats).  */
const geometry = new THREE.PlaneGeometry(
  TERRAIN.width, TERRAIN.depth, TERRAIN.segmentsX, TERRAIN.segmentsZ
);
geometry.rotateX(-Math.PI / 2);                 // put the plane in XZ, Y up
geometry.translate(0, 0, -TERRAIN.depth / 2 + 10); // extend away from camera

const positionAttr = geometry.attributes.position;
const vertexCount = positionAttr.count;
const baseX = new Float32Array(vertexCount);
const baseZ = new Float32Array(vertexCount);
for (let i = 0; i < vertexCount; i++) {
  baseX[i] = positionAttr.getX(i);
  baseZ[i] = positionAttr.getZ(i);
}

// Static vertex colors: peaks get the bright ridge color. Recomputed each
// frame together with heights (same loop, nearly free).
const colorAttr = new THREE.BufferAttribute(new Float32Array(vertexCount * 3), 3);
geometry.setAttribute('color', colorAttr);
const gridColor = new THREE.Color(COLORS.grid);
const ridgeColor = new THREE.Color(COLORS.ridge);
const tmpColor = new THREE.Color();

const material = new THREE.MeshBasicMaterial({
  wireframe: true,
  vertexColors: true,
  transparent: true,
  opacity: 0.8
});
scene.add(new THREE.Mesh(geometry, material));

function heightAt(x, z, offset) {
  let h = fbm(x * TERRAIN.noiseScale, (z - offset) * TERRAIN.noiseScale) * TERRAIN.height;
  // Carve the valley: smoothstep from 0 at the center line to 1 at the
  // valley edge, so mountains rise on both sides of a flat corridor.
  const edge = Math.min(Math.abs(x) / TERRAIN.valleyWidth, 1);
  h *= edge * edge * (3 - 2 * edge);
  return h;
}

function update(delta, elapsed) {
  const offset = elapsed * TERRAIN.scrollSpeed;
  for (let i = 0; i < vertexCount; i++) {
    const y = heightAt(baseX[i], baseZ[i], offset);
    positionAttr.setY(i, y);

    const ridge = Math.min(y / TERRAIN.height, 1);
    tmpColor.copy(gridColor).lerp(ridgeColor, ridge * ridge);
    colorAttr.setXYZ(i, tmpColor.r, tmpColor.g, tmpColor.b);
  }
  positionAttr.needsUpdate = true;
  colorAttr.needsUpdate = true;
}

/* --- Horizon glow --------------------------------------------------------
   A radial-gradient texture painted on a 2D canvas at runtime (CanvasTexture)
   — zero external assets. Mounted on a plane just above the horizon with
   additive blending; fog does not touch it because fog only applies to
   fog-enabled materials, and we disable it for this one.                 */
function makeGlowTexture() {
  const size = 256;
  const c = document.createElement('canvas');
  c.width = c.height = size;
  const ctx = c.getContext('2d');
  const g = ctx.createRadialGradient(size / 2, size / 2, 0, size / 2, size / 2, size / 2);
  g.addColorStop(0, 'rgba(56, 189, 248, 0.55)');   // cyan core
  g.addColorStop(0.35, COLORS.glow + '66');        // deep blue mid (40% alpha)
  g.addColorStop(1, 'rgba(10, 13, 20, 0)');        // transparent edge
  ctx.fillStyle = g;
  ctx.fillRect(0, 0, size, size);
  return new THREE.CanvasTexture(c);
}

const glowMaterial = new THREE.MeshBasicMaterial({
  map: makeGlowTexture(),
  transparent: true,
  blending: THREE.AdditiveBlending,
  depthWrite: false,
  fog: false   // keep the glow crisp; fog would gray it out at that distance
});
const glow = new THREE.Mesh(new THREE.PlaneGeometry(90, 30), glowMaterial);
glow.position.set(0, 6, -58);   // hugs the horizon line behind the terrain
scene.add(glow);

/* --- Render loop: DPR-capped, pauses when hidden, static under
       prefers-reduced-motion. This block is identical in every hero. ------ */
const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
let rafId = null;
let last = null;
let elapsed = 0;

function frame(now) {
  if (last === null) last = now;
  const delta = Math.min((now - last) / 1000, 0.1);
  last = now;
  elapsed += delta;

  update(delta, elapsed);
  renderer.render(scene, camera);
  rafId = requestAnimationFrame(frame);
}

function start() {
  if (rafId === null && !reducedMotion.matches && !document.hidden) {
    last = null;
    rafId = requestAnimationFrame(frame);
  }
}
function stop() {
  if (rafId !== null) { cancelAnimationFrame(rafId); rafId = null; }
}

// Reduced motion: one still landscape — mountains, valley and glow intact.
function renderStatic() {
  update(0, 0);
  renderer.render(scene, camera);
}

document.addEventListener('visibilitychange', () => {
  if (document.hidden) stop(); else start();
});
reducedMotion.addEventListener('change', () => {
  if (reducedMotion.matches) { stop(); renderStatic(); } else { start(); }
});
window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
  if (reducedMotion.matches) renderStatic();
});

/* SPA embedding? Dispose on unmount: geometry.dispose(),
   material.dispose(), glowMaterial.map.dispose(), renderer.dispose().    */

if (reducedMotion.matches) renderStatic(); else start();
</script>
</body>
</html>
Source and licence

Source: AleBrito124356/threejs-hero-collection@08e9325ce258

Author: 2026 Alejandro Brito

Entry: terrain.html

  • loads three.js from unpkg.com via import map (README recommends self-hosting)
  • hero copy links back to github.com/AleBrito124356/threejs-hero-collection
MIT License

Copyright (c) 2026 Alejandro Brito

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Files (1)
  • terrain.html (11,962 bytes)

Want the whole library?

Every item's prompt, code and instructions, with a CreativePower subscription.

See plans

More 3d scene templates