Neon Bars
Neon Bars is a modern WebGL remake of the classic Amiga “copper bars” demo effect. Sine-driven bands of glowing color sweep across the screen, rendered in a single fragment shader with additive glow blending, CRT scanlines, vignette, and film grain.
It respects prefers-reduced-motion: if the visitor has reduced motion enabled in their OS or browser settings, the animation renders a single static frame instead of moving.
OldSchool Effect
OldSchool Effect
This is brilliant
This is colorful
This is magic
This is shining
This is fun
Instructions
Note on units: the scene renders in "logical pixels" — the canvas is scaled so its height equals 200 logical pixels (scale = H / 200). So values like thick, amp, and glowRadius are in that 200px-tall coordinate space, and they scale proportionally with the canvas size.
Bar geometry & motion
bars(default10) — How many bars to draw. It's rounded and clamped to at least 1 in JS, and the shader hard-caps it at 32 (MAX_BARS). Bars are evenly distributed; the first bar is at the top of the wave stack, the last at the bottom.thick(default12) — Thickness of each bar in logical pixels. Clamped to a minimum of 0.25 so bars never fully disappear. In the shader, the fragment's distance from the bar's center line is divided by this value to decide if the pixel is inside the bar.amp(default78) — Amplitude of the sine-wave motion, in logical pixels. This is the maximum vertical travel of a bar before taper/randomness scaling is applied.taper(default0.75) — Controls how much the amplitude grows from the first bar to the last. Each bar's amplitude is scaled by(1 - taper) + taper × f, wherefgoes from 0 (first bar) to 1 (last bar). With0.75, the top bar swings at 25% amplitude and the bottom at 100%, creating the classic "fan-out" copper-bars look.0= all bars move identically;1= the first bar barely moves.angle(default0) — Rotation of the whole scene in degrees. The shader rotates the coordinate system around the screen center, so90turns the horizontal bars into vertical ones. This also affects scanlines/vignette since they use the rotated position.wav(default1.6) — Wave frequency: the bars' position issin(time × wav + …). It's an angular frequency, so higher values mean faster oscillation.phase(default0.55) — Phase offset between consecutive bars, in radians (+ barIndex × phase). This is what makes the bars move as a traveling wave rather than in unison.0= all bars bounce together; larger values create a tighter, more compressed ripple.speed(default1) — Time multiplier for the animation loop (t += dt × speed).2runs twice as fast,0freezes the motion, negative values play it in reverse. It's applied in JS, not in the shader.seed(default0; the boot code uses a random integer) — Randomness seed.seed: 0gives the deterministic, classic layout (no per-bar jitter). Any non-zero value activates per-bar randomness: each bar gets a pseudo-random phase offset and a "wobble" factor that scales its travel between roughly 35% and 165% of normal. It's deterministic — the same seed always produces the same arrangement.
Color
hue(default190) — Base hue in degrees (0–360) for the first bar.190is a cyan/teal. Bars are drawn at full saturation, lightness 0.62.spread(default26) — Hue shift in degrees added per bar (hue + barIndex × spread). With 10 bars and 26°, you get a smooth cyan→blue→purple→pink gradient across the stack.0makes all bars the same color.hueCycle(default0) — Speed at which the base hue rotates over time (hue + time × hueCycle), in degrees per animation-second.0keeps colors static; a small value like10creates a slow rainbow drift.alpha(default1) — Overall opacity of the bars. The bar's brightness profile (a triangle shape across its thickness, strongest at the center) is multiplied by this before blending, so lower values make bars more translucent against the background.blend(default"add") — How bars are combined with what's already drawn. One of"normal","add","screen","multiply","overlay","darken","lighten"."add"is the classic Amiga copper-bars look — overlapping bars sum their colors and blow out toward white where they cross.background(default"#05060d") — Background color as a hex string (#rgbor#rrggbb). Parsed once into RGB floats. Also serves as the reference point for the glow effect (only brightness above the background blooms).
Post-effects
glow(default0.9) — Bloom intensity. When > 0, the shader samples the scene at 8 points around each pixel, averages the light exceeding the background, and adds it back. Higher values give a stronger neon halo around the bars.0disables it.glowRadius(default10) — Radius, in logical pixels, of the circle those 8 glow samples are taken at. Larger = wider, softer halo; smaller = tight glow hugging the bars.noise(default0) — Amount of animated TV-style grain. Each pixel gets a random offset of(random − 0.5) × noiseadded to its color, so0.1is subtle static and larger values get snowy.0disables it.noiseSpeed(default18) — How fast the grain pattern shifts/refreshes over time. It offsets the noise's sample coordinates bytime × noiseSpeed. Only matters whennoise > 0.scanlines(defaultfalse) — Boolean toggle for CRT scanlines. Whentrue, certain logical pixel rows are darkened.scanGap(default3) — Scanline period in logical pixels: every Nth row is darkened (row 0, 3, 6, … with gap 3). Minimum forced to 1.scanAlpha(default0.34) — How strongly the scanline rows are darkened (0–1). The darkened rows are multiplied by1 − scanAlpha, so0.34means those rows render at 66% brightness.vignette(defaulttrue) — Boolean toggle for edge darkening. The falloff starts at 30% of the smaller screen dimension from center and reaches full strength at 75% of the larger dimension.vigStrength(default0.6) — Maximum darkening at the corners/edges (0–1).0.6means the far corners can be darkened by up to 60%.
Extra (boot-only)
reducedMotion(set totruein the auto-boot call, not inDEFAULTS) — Accessibility switch. When notfalse, the code checks the browser'sprefers-reduced-motion: reducesetting: if the user prefers reduced motion, it renders a single static frame (draw()) instead of starting the animation loop (start()).
Anything you omit falls back to its DEFAULTS value, and you can change options later at runtime via copperBars.set({ ... }) — e.g. set({ hue: 320, bars: 6 }).