Every space weather site on the internet feels like it’s from 2003, because most of them are.
I wanted something you could glance at and immediately know what the Sun is doing, so I built Sunspots.
It’s an interactive 3D sun in the browser, plotting real sunspot locations and solar flares using live NASA/NOAA data. No API keys, all free public endpoints.
The sun surface is fully procedural GLSL, five layers of noise (domain-warped plasma, convection cells, granulation, turbulence, supergranulation) blended with real NASA SUVI 304A chromosphere imagery. Click any active region and the camera flies over to show you details like magnetic classification and area relative to Earth:
The Dither Effect
The part I want to talk about most is the custom postprocessing dither. The scene runs through bloom, chromatic aberration, vignette, and ACES filmic tone mapping, but it was still missing something. It looked too clean for what’s basically a ball of plasma.
So I wrote a custom 8x8 ordered Bayer dither as a postprocessing effect:
float bayerDither8(ivec2 p) {
float b = 0.0;
b += bayerDither2(p >> 2) / 1.0;
b += bayerDither2(p >> 1) / 4.0;
b += bayerDither2(p) / 16.0;
return b * (64.0 / 63.0);
}
void mainImage(const in vec4 inputColor, const in vec2 uv,
out vec4 outputColor) {
vec3 color = inputColor.rgb;
// Fade dither strength based on luminance
float lum = dot(color, vec3(0.299, 0.587, 0.114));
float strength = smoothstep(0.02, 0.25, lum);
ivec2 pixelCoord = ivec2(floor(gl_FragCoord.xy / DITHER_SCALE));
float threshold = bayerDither8(pixelCoord) - 0.5;
vec3 dithered = color + threshold / DITHER_LEVELS;
dithered = floor(dithered * DITHER_LEVELS + 0.5) / DITHER_LEVELS;
dithered = clamp(dithered, 0.0, 1.0);
outputColor = vec4(mix(color, dithered, strength), inputColor.a);
}
The trick is the luminance-based strength. The dither is basically invisible in the dark background and space around the sun, and only kicks in on the bright surface where it adds this subtle retro texture. Without it the sun looks like a stock 3D render. With it, it has character.
The DITHER_SCALE and DITHER_LEVELS are compile-time defines so you can tune the look without any runtime cost. I landed on scale 1.5 and 14 quantization levels after a lot of tweaking.
Mobile & Haptics
Works on phones too. The UI collapses into togglable panels and the timeline is touch-friendly.
For tactile feedback I used web-haptics, which is seriously underrated. It wraps the Vibration API into semantic patterns like light, medium, heavy, selection, detent that feel native. Scrubbing the timeline gives little detent taps for each day marker, flying to a sunspot gives a medium vibration on landing. If you’re building anything mobile-facing and want it to feel less like a web page, check it out.
Links
Live at sunspots.asynchronous.win, code on Github.