About
A skyline is rows of cells: each cell is a tower with its own height, a possible step in near the top, and a grid of windows inside it where each window has a random threshold and a slow flicker. Two rows at two scales give depth, with haze between them. In the highway look the lower third becomes a road: lanes of dashes moving left or right at their own speed, warm one way and red the other.
Use it when
- A property developer, fintech, ride or travel brand wants the city in its hero.
- A nightlife, hospitality or events page wants the night.
- A consultancy wants a metropolitan tone.
Skip it when
- The brand is rural, calm or craft.
- Text must sit over the windows.
Install
- 1Add the VibeBox registry to
components.json:{ "registries": { "@vibebox": "https://motion.vibeboxph.com/r/{name}.json" } } - 2Add the component with the shadcn CLI:
npx shadcn@latest add @vibebox/city-night - 3Use it:
import { CityNight } from "@/components/vibebox/city-night"; <CityNight preset="dusk" className="min-h-[560px] rounded-xl"> <div className="p-12 text-white"> <h1 className="text-4xl font-semibold">Live above the noise.</h1> </div> </CityNight>
Code
3 filescomponents/vibebox/city-night/city-night.client.tsx
/**
* VibeBox Motion · City Night
* Copyright (c) 2026 VibeBox. Licensed under the VibeBox Motion Free License:
* https://motion.vibeboxph.com/license
* Use it in unlimited personal, commercial and client projects.
* Do not sell or redistribute it as a component, kit, template or builder.
*/
"use client";
import type { ReactNode } from "react";
import { BackdropCanvas, type PosterSpec } from "@/lib/vibebox/backdrop-canvas";
import { cityNightPiece, presets, type CityNightPreset } from "./city-night.shader";
export type CityNightColor = "skyTop" | "skyLow" | "tower" | "towerFar" | "windowA" | "windowB" | "trailA" | "trailB";
export type CityNightValue = "heightC" | "windows" | "trails" | "road";
export type CityNightProps = {
/** What sits on the scene: a heading, a call to action, a whole section. */
children?: ReactNode;
/** A named look: `dusk`, `midnight`, `neon`, `highway`. */
preset?: CityNightPreset;
/** Overrides single palette colours with any CSS colour. */
colors?: Partial<Record<CityNightColor, string>>;
/** Multiplies the preset's speed (1 keeps it, 0 holds the scene still). */
speed?: number;
/** Multiplies the preset's brightness. */
intensity?: number;
/** The pointer and touch move the scene. */
interactive?: boolean;
/** Accessible name; leave it out when the scene is only a background. */
label?: string;
/** Hold one still frame until this is false (for a gallery card). */
still?: boolean;
className?: string;
} & Partial<Record<CityNightValue, number>>;
const POSTER: PosterSpec = { type: "horizon", top: "skyTop", horizon: "skyLow", groundTop: "tower", groundBottom: "tower", at: 72 };
export function CityNight({ children, preset, colors, speed, intensity, interactive = true, label, still, className, heightC, windows, trails, road }: CityNightProps) {
return (
<BackdropCanvas
slug="city-night"
piece={cityNightPiece}
presets={presets}
fallback="dusk"
preset={preset}
colors={colors}
values={{ heightC, windows, trails, road }}
speed={speed}
intensity={intensity}
poster={POSTER}
scale={0.5}
stillTime={3}
still={still}
interactive={interactive}
label={label}
className={className}
>
{children}
</BackdropCanvas>
);
}components/vibebox/city-night/city-night.shader.ts
/**
* VibeBox Motion · City Night
* Copyright (c) 2026 VibeBox. Licensed under the VibeBox Motion Free License:
* https://motion.vibeboxph.com/license
* Use it in unlimited personal, commercial and client projects.
* Do not sell or redistribute it as a component, kit, template or builder.
*/
import { compose, createScenePiece, type ScenePreset } from "@/lib/vibebox/engine/backdrops";
/*
* A skyline from across the water or the road: two depths of towers against a glowing sky, each
* window lit or dark and slowly changing its mind, a haze of city light low on the horizon, and a
* road in front with trails of light running along it. The pointer slides the near towers a little
* against the far ones.
*/
const SCENE = /* glsl */ `
uniform vec3 uSkyTop;
uniform vec3 uSkyLow;
uniform vec3 uTower;
uniform vec3 uTowerFar;
uniform vec3 uWindowA;
uniform vec3 uWindowB;
uniform vec3 uTrailA;
uniform vec3 uTrailB;
uniform float uHeightC;
uniform float uWindows;
uniform float uTrails;
uniform float uRoad;
uniform float uSpeed;
uniform float uIntensity;
float T;
/* One row of towers: returns (coverage, window light, window halo, edge light) for the pixel. */
vec4 towers(vec2 uv, float scale, float seed, float groundY, float heightScale) {
float px = 1.5 / uResolution.y;
float x = uv.x * scale + seed * 13.0;
float id = floor(x);
float f = fract(x);
float hgt = (0.06 + 0.3 * h1(int(id) + int(seed) * 97) * h1(int(id) + 5 + int(seed) * 31) + 0.04 * h1(int(id) + 211)) * heightScale;
// Some towers step in near the top.
float step1 = h1(int(id) * 3 + 7) > 0.62 ? 0.18 : 0.0;
float top = groundY + hgt;
float fpx = px * scale;
float xl = step1 * 0.5;
float xr = 1.0 - 0.06 - step1 * 0.5;
float inX = smoothstep(xl - fpx, xl + fpx, f) * smoothstep(xr + fpx, xr - fpx, f);
float body = smoothstep(top + px, top - px, uv.y) * step(groundY - 0.4, uv.y) * inX;
// Windows: a grid inside the tower.
vec2 wp = vec2(f * 9.0, (uv.y - groundY) * 78.0 + id);
vec2 wid = floor(wp);
vec2 wf = fract(wp);
float rnd = h2(ivec2(int(wid.x) + int(id) * 17, int(wid.y)) + ivec2(int(seed) * 5, 3));
float flick = 0.5 + 0.5 * sin(T * (0.1 + rnd * 0.4) + rnd * 60.0);
float lit = step(1.0 - uWindows * 0.38, rnd) * smoothstep(0.1, 0.5, flick);
float winShape = smoothstep(0.15, 0.25, wf.x) * smoothstep(0.85, 0.75, wf.x) * smoothstep(0.2, 0.3, wf.y) * smoothstep(0.8, 0.7, wf.y);
// The light spills onto the wall around a lit window.
vec2 hc = (wf - 0.5) * vec2(1.0, 1.35);
float halo = lit * exp(-dot(hc, hc) * 7.0);
// The edge facing the sky's glow catches a little of it.
float rim = (1.0 - smoothstep(0.0, 0.09, f)) * smoothstep(top - 0.25, top, uv.y) + smoothstep(top - 0.012, top, uv.y) * 0.8;
return vec4(body, body * lit * winShape, body * halo, body * clamp(rim, 0.0, 1.0));
}
void main() {
vec2 uv = centred(gl_FragCoord.xy);
T = uTime * uSpeed;
float par = (uPointer.x - 0.5) * 0.05 * (0.3 + uHover);
float groundY = -0.12 - 0.05 * uRoad;
// The sky: darkest overhead, and a glow of city light along the skyline.
vec3 col = lin(mix(uSkyLow, uSkyTop, smoothstep(-0.2, 0.55, uv.y)));
col += lin(uSkyLow) * exp(-(uv.y - groundY) * 5.0) * 0.55;
// Farthest towers: barely there, lost in the haze.
vec4 farthest = towers(vec2(uv.x + par * 0.2, uv.y), 11.0, 3.0, groundY - 0.005, uHeightC * 0.8);
col = mix(col, mix(lin(uSkyLow), lin(uTowerFar), 0.55), farthest.x * 0.55);
col += lin(uWindowB) * farthest.y * 0.18;
// Far towers: hazy and dim.
vec4 far = towers(vec2(uv.x + par * 0.4, uv.y), 7.5, 1.0, groundY - 0.01, uHeightC * 0.9);
col = mix(col, lin(uTowerFar), far.x);
col += lin(uWindowB) * (far.y * 0.45 + far.z * 0.08);
col += lin(uSkyLow) * far.w * 0.18;
// Haze between the depths, thicker toward the ground.
col = mix(col, lin(uSkyLow) * 1.1, 0.34 * smoothstep(groundY + 0.3, groundY, uv.y) * step(groundY, uv.y));
// Near towers.
vec4 near = towers(vec2(uv.x + par, uv.y), 3.6, 2.0, groundY - 0.03, uHeightC);
col = mix(col, lin(uTower), near.x);
vec3 windowTint = mix(lin(uWindowA), lin(uWindowB), step(0.5, h1(int(floor(uv.x * 40.0)) + 3)));
col += windowTint * (near.y * 1.1 + near.z * 0.28);
col += lin(uSkyLow) * near.w * 0.22;
// The road: dark, with trails of headlights and tail lights running along it.
if (uRoad > 0.5 && uv.y < groundY - 0.03) {
float rd = (groundY - 0.03 - uv.y);
col = lin(uTower) * 0.6 + lin(uSkyLow) * 0.12;
float lanes = 7.0;
float lane = floor(rd * lanes * 2.4 / 0.4);
float rnd = h1(int(lane) + 31);
float dir = rnd > 0.5 ? 1.0 : -1.0;
float sp = (0.25 + rnd) * dir;
float u = uv.x * (2.0 + rnd * 3.0) + T * sp * 0.9 + rnd * 20.0;
float seg = fract(u);
float on = step(1.0 - uTrails * 0.45, h1(int(floor(u)) + int(lane) * 19));
float streak = smoothstep(0.0, 0.1, seg) * smoothstep(0.7, 0.2, seg) * on;
float band = smoothstep(0.012, 0.0, abs(fract(rd * lanes * 2.4 / 0.4) - 0.5) - 0.1) * 0.7;
vec3 tc = dir > 0.0 ? lin(uTrailA) : lin(uTrailB);
col += tc * streak * (0.4 + band) * 1.4 * smoothstep(0.0, 0.05, rd);
col += tc * streak * 0.2;
}
col *= uIntensity;
col *= 1.0 - 0.3 * smoothstep(0.55, 1.2, length(uv * vec2(0.85, 1.1)));
fragColor = vec4(finish(col, gl_FragCoord.xy), 1.0);
}
`;
export const fragment = compose(SCENE);
export const PRESET_NAMES = ["dusk", "midnight", "neon", "highway"] as const;
export type CityNightPreset = (typeof PRESET_NAMES)[number];
export const presets: Record<CityNightPreset, ScenePreset> = {
dusk: {
colors: { skyTop: "#1b1a4a", skyLow: "#ff8a5e", tower: "#0b0a1c", towerFar: "#5a4a7a", windowA: "#ffd27a", windowB: "#ffb36a", trailA: "#ffd9a0", trailB: "#ff4a3a" },
values: { heightC: 0.8, windows: 0.8, trails: 0.8, road: 0, speed: 0.8, intensity: 1 },
},
midnight: {
colors: { skyTop: "#02040c", skyLow: "#14305a", tower: "#04060f", towerFar: "#10223f", windowA: "#ffe7a8", windowB: "#9fd0ff", trailA: "#fff3d0", trailB: "#ff5a4a" },
values: { heightC: 0.9, windows: 0.9, trails: 0.8, road: 0, speed: 0.7, intensity: 1.05 },
},
neon: {
colors: { skyTop: "#0a0420", skyLow: "#7a1d8a", tower: "#07031a", towerFar: "#2a1a5a", windowA: "#ff3fa4", windowB: "#35d8ff", trailA: "#35d8ff", trailB: "#ff3fa4" },
values: { heightC: 0.95, windows: 1, trails: 1, road: 0, speed: 0.9, intensity: 1.1 },
},
highway: {
colors: { skyTop: "#050818", skyLow: "#2a3a7a", tower: "#05060e", towerFar: "#18214a", windowA: "#ffe0a0", windowB: "#9fc8ff", trailA: "#fff1c8", trailB: "#ff3a3a" },
values: { heightC: 0.65, windows: 0.7, trails: 1.3, road: 1, speed: 1.1, intensity: 1.05 },
},
};
export const cityNightPiece = createScenePiece("city-night", fragment);components/vibebox/city-night/index.tsx
/**
* VibeBox Motion · City Night
* Copyright (c) 2026 VibeBox. Licensed under the VibeBox Motion Free License:
* https://motion.vibeboxph.com/license
* Use it in unlimited personal, commercial and client projects.
* Do not sell or redistribute it as a component, kit, template or builder.
*/
export { CityNight, type CityNightProps, type CityNightColor, type CityNightValue } from "./city-night.client";
export { PRESET_NAMES as CITY_NIGHT_PRESETS, presets as cityNightPresets, type CityNightPreset } from "./city-night.shader";Props
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | What sits on the scene: a heading, a call to action, a whole section. |
| preset | "dusk" | "midnight" | "neon" | "highway" | "dusk" | The look: dusk, midnight, neon, highway. |
| colors | { skyTop?: string; skyLow?: string; tower?: string; towerFar?: string; windowA?: string; windowB?: string; trailA?: string; trailB?: string } | — | Overrides single palette colours with any CSS colour: skyTop (the sky overhead), skyLow (the sky's glow at the skyline), tower (the near towers), towerFar (the far towers), windowA (the main window light), windowB (the second window light), trailA (headlight trails), trailB (taillight trails). |
| speed | number | 1 | Multiplies the preset's speed; 0 holds the scene still. |
| intensity | number | 1 | Multiplies the preset's brightness, 0.5 to 1.4. |
| heightC | number | 0.8 | How tall the towers are (0.4 to 1.4; each look sets its own). |
| windows | number | 0.8 | How many windows are lit (0.2 to 1.4; each look sets its own). |
| trails | number | 0.8 | How many light trails run on the road (0 to 2; each look sets its own). |
| road | number | 0 | 0 hides the road, 1 shows it (0 to 1; each look sets its own). |
| interactive | boolean | true | The pointer slides the near towers against the far ones. |
| label | string | — | Accessible name; leave it out when the scene is only a background. |
| still | boolean | false | Hold one composed frame until false (a gallery card that plays on hover). |
| className | string | — | Merged with cn(); give it a size and a radius. |
Accessibility and motion
- Keyboard
- Not focusable; content on it keeps its own focus order.
- Screen readers
- Decorative by default; with
labelit is an image with that name. - Touch
- A finger moves the scene like the cursor does; scrolling is never blocked.
- Reduced motion
- One still frame of the city; the windows and trails do not change.
Edge cases and limits
- The window dots are fine detail: keep text above the skyline.
- The neon look is the busiest.
- Highway needs the lower third: give the frame some height.
Related

Aurora
FreeCurtains of northern light over a night sky and a mountain ridge, drawn toward the pointer.
Backgrounds · WebGL2

Bokeh
FreeOut-of-focus lights in three depths, brighter at the rim and drifting with the pointer.
Backgrounds · WebGL2

Cloudscape
FreeA sea of cloud seen from above, or a ceiling of it seen from below, lit by a low sun.
Backgrounds · WebGL2