← Effects

Aurora curtain

Light · WebGL shader with a CSS fallback

Curtains of northern light hang from the top of the page: fine vertical streaks of glowing colour that sway sideways and shimmer, brightest along their lower hem and fading upward into the sky. A faint glow lights the bottom edge like a horizon. It belongs on dark backgrounds, where the light reads as light.

✓ (3×) · claude-haiku-5-5 → claude-opus-5-5
# Build the "Aurora curtain" background effect (`aurora-curtain`)

Family: Light · WebGL fragment shader over a CSS fallback. Northern-lights curtains sway from the top of the page and shimmer into a dark sky.

## Look
Curtains of northern light hang from the top of the page: fine vertical streaks of glowing colour that sway sideways and shimmer, brightest along their lower hem and fading upward into the sky. A faint glow lights the bottom edge like a horizon. It belongs on dark backgrounds, where the light reads as light.

## Parameters (current values)
- Speed `--fx-speed`: 0.4 (range 0.1–2)
- Intensity `--fx-intensity`: 0.9 (range 0–1.5) — brightness of the curtains
- Reach `--fx-height`: 0.6 (range 0.3–0.8) — how far down the hem hangs, as a share of the height
- Colour A `--fx-color-a`: the `accent` colour token, `var(--c-accent)`
- Colour B `--fx-color-b`: the `primary` colour token, `var(--c-primary)`
- Sky `--fx-color-bg`: the `bg` colour token, `var(--c-bg)`

## Recipe
- Coordinates: `uv = gl_FragCoord.xy / u_resolution` (y = 1 at the top); `x = uv.x * u_resolution.x / u_resolution.y`; `t = u_time`.
- Sky: `col = mix(u_color_bg, u_color_bg * 0.7, uv.y)`, plus a horizon glow `col += u_color_a * 0.12 * u_intensity * exp(-uv.y * 6.0)`.
- Three curtain layers, k = 0, 1, 2 (k = 0 nearest). Wavy hem: `hem = 1.0 - u_height * (1.0 - 0.15 * k) + 0.06 * sin(x * 1.7 + t * 0.6 + k * 2.1) + 0.03 * sin(x * 4.3 - t * 0.9 + k)`.
- Glow around the hem: `d = uv.y - hem`; `glow = smoothstep(-0.12, 0.03, d) * exp(-max(d, 0.0) * (3.0 + 2.0 * k))` — brightest just above the hem, fading upward, with a soft fade-out below it (never a hard silhouette).
- Streaks: a slow 1D wobble `n(v)` in 0–1, made from two sines at unrelated frequencies added together and rescaled (no hash or value noise); `rays = 0.65 + 0.35 * sin(x * 38.0 + n(x * 3.0 + k * 7.0 + t * 0.25) * 12.0)`, multiplied by `0.6 + 0.4 * n(x * 9.0 - t * 0.4 + k * 3.0)` so streaks shimmer slowly.
- Layer colour: `mix(u_color_a, u_color_b, 0.5 + 0.5 * sin(x * 0.8 + k * 1.3 + t * 0.2))`; add `layerColour * glow * rays * u_intensity * 0.6 / (1.0 + k * 0.6)` to `col`.
- Output `vec4(min(col, 1.0), 1.0)`.

## CSS fallback
The element's background: a horizon glow `linear-gradient(to top, color-mix(in srgb, var(--fx-color-a) 18%, transparent), transparent 40%)` over `var(--fx-color-bg)`. A `::before` layer (`content: ''; position: absolute; inset: 0`) draws soft streaks with no hard stops: `repeating-linear-gradient(90deg, color-mix(in srgb, var(--fx-color-a) 35%, transparent) 0px, transparent 14px, color-mix(in srgb, var(--fx-color-b) 30%, transparent) 30px, transparent 46px, color-mix(in srgb, var(--fx-color-a) 35%, transparent) 64px)`, masked (`mask-image` and `-webkit-mask-image`) with `linear-gradient(to bottom, transparent 0%, #000 calc(var(--fx-height) * 100% - 8%), transparent calc(var(--fx-height) * 100% + 6%))` so the streaks are brightest just above the hem and fade upward and below it. Static.

## Runtime rules
- Mount it in one element that fills a fixed, `pointer-events: none`, `aria-hidden` layer behind the page content; every parameter above is a CSS custom property on that element.
- Colours come only from the colour-token variables above (mix them with `color-mix()` or alpha), never hard-coded hex, so the effect follows any palette and light or dark mode.
- Two layers. A CSS background on the element is the fallback and must look complete alone (described above): it is all that shows without WebGL or after a lost context. A full-size `<canvas>` on top draws the shader.
- Plain WebGL 1: one fragment shader over a full-screen triangle; no libraries, CDN, textures or images; script under ~8 KB.
- Uniforms: `u_resolution` (canvas size in pixels), `u_time` (seconds, already multiplied by the speed parameter) and one per parameter except speed: `u_intensity` (float), `u_height` (float), `u_color_a` (vec3 RGB 0–1, read from the CSS variable), `u_color_b` (vec3 RGB 0–1, read from the CSS variable), `u_color_bg` (vec3 RGB 0–1, read from the CSS variable). Output opaque colour.
- Cap the device pixel ratio at 2 (1.5 on phones) and the frame rate at 30 fps on phones; pause when the tab is hidden or the canvas is off screen; release the context when it leaves the screen.
- With prefers-reduced-motion, or a `?shot` URL flag (screenshots), draw one frame at `u_time = 2` and stop. No WebGL, or a lost context: remove the canvas and leave the CSS fallback.

## Avoid
- Horizontal banding across the content area
- Pure white rays or a full-brightness hem
- Curtains hanging lower than 85% of the height
- Flicker: a streak should take over 3 seconds to change at speed 1
- Using it on a light background without lowering the intensity
- Hash-based noise, or any code or constants lifted from published shaders or tutorials
Copied!

↑ ↓ to move · Enter to open · Esc to close