Source code
#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;

// Mathematical constants
#define tau 6.28318
#define pi 3.14159
#define ep 1e-2  // epsilon (small number to fix edge cases)

// Configurable parameters
float centralRadius = 0.1;  // Central circle radius
float satelliteRadius = 0.0375;  // Surrounding circles radius
float numLayers = 30.0;        // Number of concentric layers
float rotationSpeed = 0.15;     // Controls linear rotation speed
float spacingFactor = 1.25;

/**
 * Converts HSL color to RGB
 * @param c - vec3 with x=hue, y=saturation, z=lightness
 * @return RGB color
 */
vec3 hue(in vec3 c) {
    return c.z * (1.0 - c.y * smoothstep(2.0, 1.0, abs(mod(c.x * 6.0 + vec3(0, 4, 2), 6.0) - 3.0)));
}

/**
 * Draws a circle
 * @param xy - Current fragment coordinates
 * @param c - Circle center
 * @param r - Circle radius
 * @param fill - Whether to fill the circle or draw outline
 * @return Circle color intensity (0.0-1.0)
 */
float circle(vec2 xy, vec2 c, float r, bool fill) {
    float dist = length(xy - c) - r;
    return 1.0 - smoothstep(-2.0 / u_resolution.y, 3.0 / u_resolution.y, fill ? dist : abs(dist));
}

/**
 * Creates surrounding circles in a ring pattern
 * @param xy - Current fragment coordinates
 * @param C - Center of the entire pattern
 * @param R - Distance from center to satellite circles
 * @param r - Radius of each satellite circle
 * @param ph - Phase/rotation offset
 * @return Color for the satellite circles
 */
vec3 circles(vec2 xy, vec2 C, float R, float r, float ph) {
    // Calculate angle between each surrounding circle
    float t = 2.0 * asin(r / (R + r)) * spacingFactor;

    // Calculate number of circles that will fit
    float div = abs(tau / t) + ep;
    int n = int(div);

    // Calculate padding between circles
    float pad = fract(div) * t / float(n);

    // Apply rotation to the entire pattern
    float rt = -t / 2.0 - pad / 2.0 + ph;
    mat2 rm = mat2(cos(rt), -sin(rt), sin(rt), cos(rt));
    vec2 zw = rm * (xy - C);

    // Determine which circle this fragment belongs to
    float i = floor((atan(zw.y, zw.x)) / (t + pad));

    // Calculate center of the current circle
    vec2 c = vec2(cos(i * (t + pad) + ph), sin(i * (t + pad) + ph)) * (r + R) + C;

    // Color based on the circle's position in the sequence
    vec3 hsl = vec3(i / float(n), 1.0, 0.75);

    return vec3(circle(xy, c, r, true)) * hue(hsl);
}

void main() {
    // Normalize coordinates to center of screen with aspect ratio correction
    vec2 xy = (2.0 * gl_FragCoord.xy - u_resolution.xy) / u_resolution.y;

    // Initialize color
    vec3 col = vec3(0.0);

    // Center of the pattern
    vec2 C = vec2(0.0);
    float layerSpacing = spacingFactor * 2.; // Values > 2.0 increase space between layers

    // Calculate which layer the current fragment belongs to
    float distFromCenter = length(xy - C);
    float i = floor((min(satelliteRadius * layerSpacing * (numLayers - 1.0) + ep, distFromCenter - centralRadius)) / (satelliteRadius * layerSpacing));

    // If fragment is in a valid layer, draw the surrounding circles
    if(i >= 0.0) {
        // Linear rotation - different speeds for each layer
        // Base speed multiplied by layer number for different rotation speeds in each ring
        float phaseOffset = u_time * rotationSpeed * (1.0 + i * 0.1) * -1.;
        // phaseOffset = pi * sin(u_time * rotationSpeed) / numLayers * i;
        // phaseOffset = 0.;

        // Add colored circles for this layer
        col += circles(xy, C, centralRadius + satelliteRadius * i * layerSpacing, satelliteRadius, phaseOffset);
    }

    // Invert the colors for final output
    gl_FragColor = vec4(col * 0.9, 1.0);
}