Code viewer for World: Pegasus Galaxy (webgl example)
const css = `
* {
  margin: 0;
  padding: 0;
  overflow: hidden;
}
body {
  width: 100vw;
  height: 100vh;
}
canvas {
  display: block;
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
}
`;

// Vertex shader - creates a full-screen quad
const vertexShaderSource = `
            attribute vec2 a_position;
            void main() {
                gl_Position = vec4(a_position, 0.0, 1.0);
            }
        `;

// Fragment shader - Galaxy by Frank Hugenroth (adapted from Shadertoy)
const fragmentShaderSource = `
            precision highp float;
            uniform vec2 u_resolution;
            uniform float u_time;
            
            #define SCREEN_EFFECT 0

            // Random/hash function              
            float hash(float n) {
                return fract(cos(n) * 41415.92653);
            }

            // 2D noise function
            float noise(in vec2 x) {
                vec2 p = floor(x);
                vec2 f = smoothstep(0.0, 1.0, fract(x));
                float n = p.x + p.y * 57.0;
                return mix(mix(hash(n + 0.0), hash(n + 1.0), f.x),
                           mix(hash(n + 57.0), hash(n + 58.0), f.x), f.y);
            }

            // 3D noise function
            float noise(in vec3 x) {
                vec3 p = floor(x);
                vec3 f = smoothstep(0.0, 1.0, fract(x));
                float n = p.x + p.y * 57.0 + 113.0 * p.z;
                return mix(mix(mix(hash(n + 0.0), hash(n + 1.0), f.x),
                               mix(hash(n + 57.0), hash(n + 58.0), f.x), f.y),
                           mix(mix(hash(n + 113.0), hash(n + 114.0), f.x),
                               mix(hash(n + 170.0), hash(n + 171.0), f.x), f.y), f.z);
            }

            mat3 m = mat3(0.00, 1.60, 1.20, -1.60, 0.72, -0.96, -1.20, -0.96, 1.28);

            // Fractional Brownian motion (slow)
            float fbmslow(vec3 p) {
                float f = 0.5000 * noise(p); p = m * p * 1.2;
                f += 0.2500 * noise(p); p = m * p * 1.3;
                f += 0.1666 * noise(p); p = m * p * 1.4;
                f += 0.0834 * noise(p);
                return f;
            }

            // Fractional Brownian motion (fast)
            float fbm(vec3 p) {
                float f = 0.0, a = 1.0, s = 0.0;
                f += a * noise(p); p = m * p * 1.149; s += a; a *= 0.75;
                f += a * noise(p); p = m * p * 1.41; s += a; a *= 0.75;
                f += a * noise(p); p = m * p * 1.51; s += a; a *= 0.65;
                f += a * noise(p); p = m * p * 1.21; s += a; a *= 0.35;
                f += a * noise(p); p = m * p * 1.41; s += a; a *= 0.75;
                f += a * noise(p); 
                return f / s;
            }

            void main() {
                vec2 fragCoord = gl_FragCoord.xy;
                vec2 iResolution = u_resolution;
                float iTime = u_time;
                float time = iTime * 0.1;

                // Correct aspect ratio calculation
                vec2 xy = (2.0 * fragCoord.xy - iResolution.xy) / min(iResolution.x, iResolution.y);

                // Fade in/out effects
                float fade = min(1.0, time);
                float fade2 = max(0.0, time - 10.0) * 0.37;
                float glow = clamp(max(-0.25, 1.0 + pow(fade2, 10.0) - 0.001 * pow(fade2, 25.0)), 0.3, 0.8);
                
                // Camera setup
                vec3 campos = vec3(500.0, 850.0, -cos((time - 1.4) / 2.0) * 2000.0);
                vec3 camtar = vec3(0.0, 0.0, 0.0);
                float roll = 0.34;
                
                vec3 cw = normalize(camtar - campos);
                vec3 cp = vec3(sin(roll), cos(roll), 0.0);
                vec3 cu = normalize(cross(cw, cp));
                vec3 cv = normalize(cross(cu, cw));
                vec3 rd = normalize(xy.x * cu + xy.y * cv + 1.6 * cw);

                vec3 light = normalize(vec3(0.0, 0.0, 0.0) - campos);
                float sundot = clamp(dot(light, rd), 0.0, 1.0);

                // Galaxy center glow
                vec3 col = glow * 1.2 * min(vec3(1.0), vec3(2.0, 1.0, 0.5) * pow(sundot, 100.0));
                col += 0.3 * vec3(0.8, 0.9, 1.2) * pow(sundot, 8.0);

                // Stars
                vec3 stars = 85.5 * vec3(pow(fbmslow(rd.xyz * 312.0), 7.0)) * 
                             vec3(pow(fbmslow(rd.zxy * 440.3), 8.0));
                
                // Moving background fog
                vec3 cpos = 1500.0 * rd + vec3(831.0 - time * 30.0, 321.0, 1000.0);
                col += vec3(0.4, 0.5, 1.0) * (fbmslow(cpos * 0.0035) - 0.5);

                cpos += vec3(831.0 - time * 33.0, 321.0, 999.0);
                col += vec3(0.6, 0.3, 0.6) * 10.0 * pow(fbmslow(cpos * 0.0045), 10.0);

                cpos += vec3(3831.0 - time * 39.0, 221.0, 999.0);
                col += 0.03 * vec3(0.6, 0.0, 0.0) * 10.0 * pow(fbmslow(cpos * 0.0145), 2.0);

                // Add stars
                cpos = 1500.0 * rd + vec3(831.0, 321.0, 999.0);
                col += stars * fbm(cpos * 0.0021);
                
                // Cloud layers
                vec2 shift = vec2(time * 100.0, time * 180.0);
                vec4 sum = vec4(0.0);
                float c = campos.y / rd.y;
                vec3 cpos2 = campos - c * rd;
                float radius = length(cpos2.xz) / 1000.0;

                if (radius < 1.8) {
                    // First cloud layer (outer)
                    for (int q = 10; q > -10; q--) {
                        if (sum.w > 0.999) continue;
                        
                        float c = (float(q) * 8.0 - campos.y) / rd.y;
                        vec3 cpos = campos + c * rd;
                        float see = dot(normalize(cpos), normalize(campos));
                        vec3 shine = mix(vec3(1.3, 1.2, 1.2), vec3(0.0), smoothstep(0.0, 1.0, see));

                        float radius = length(cpos.xz) / 999.0;
                        if (radius > 1.0) continue;

                        float rot = 3.0 * radius - time;
                        cpos.xz = cpos.xz * mat2(cos(rot), -sin(rot), sin(rot), cos(rot));
                 
                        cpos += vec3(831.0 + shift.x, 321.0 + float(q) * mix(250.0, 50.0, radius) - shift.x * 0.2, 1330.0 + shift.y);
                        cpos *= mix(0.0025, 0.0028, radius);
                        
                        float alpha = smoothstep(0.5, 1.0, fbm(cpos));
                        alpha *= 1.3 * pow(smoothstep(1.0, 0.0, radius), 0.3);
                        
                        vec3 dustcolor = mix(vec3(2.0, 1.3, 1.0), vec3(0.1, 0.2, 0.3), pow(radius, 0.5));
                        vec3 localcolor = mix(dustcolor, shine, alpha);
                        
                        // Add stars and holes
                        localcolor += vec3(1.0, 0.6, 0.3) * 2.0 * pow(noise(cpos * 21.4), 22.0);
                        localcolor += vec3(1.0, 1.0, 0.7) * 3.0 * pow(noise(cpos * 26.55), 34.0);
                        localcolor -= pow(noise(cpos * 11.55), 14.0);
                        
                        alpha = (1.0 - sum.w) * alpha;
                        sum += vec4(localcolor * alpha, alpha);
                    }
                    
                    // Second cloud layer (inner dark)
                    for (int q = 0; q < 20; q++) {
                        if (sum.w > 0.999) continue;
                        
                        float c = (float(q) * 4.0 - campos.y) / rd.y;
                        vec3 cpos = campos + c * rd;
                        
                        float radius = length(cpos.xz) / 200.0;
                        if (radius > 1.0) continue;

                        float rot = 3.2 * radius - time * 1.1;
                        cpos.xz = cpos.xz * mat2(cos(rot), -sin(rot), sin(rot), cos(rot));
                 
                        cpos += vec3(831.0 + shift.x, 321.0 + float(q) * mix(250.0, 50.0, radius) - shift.x * 0.2, 1330.0 + shift.y);
                        
                        float alpha = 0.1 + smoothstep(0.6, 1.0, fbm(cpos));
                        alpha *= 1.2 * (pow(smoothstep(1.0, 0.0, radius), 0.72) - pow(smoothstep(1.0, 0.0, radius * 1.875), 0.2));
                        
                        alpha = (1.0 - sum.w) * alpha;
                        sum += vec4(vec3(0.0) * alpha, alpha);
                    }
                }
                
                // Composite clouds
                float alpha = smoothstep(1.0 - radius * 0.5, 1.0, sum.w);
                sum.rgb /= sum.w + 0.0001;
                sum.rgb -= 0.2 * vec3(0.8, 0.75, 0.7) * pow(sundot, 10.0) * alpha;
                sum.rgb += min(glow, 10.0) * 0.2 * vec3(1.2) * pow(sundot, 5.0) * (1.0 - alpha);
                col = mix(col, sum.rgb, sum.w);

                // Atmospheric haze
                col = fade * mix(col, vec3(0.3, 0.5, 0.9), 
                      29.0 * (pow(sundot, 50.0) - pow(sundot, 60.0)) / (2.0 + 9.0 * abs(rd.y)));

                // Vignette
                vec2 xy2 = gl_FragCoord.xy / iResolution.xy;
                col *= vec3(0.5) + 0.25 * pow(100.0 * xy2.x * xy2.y * (1.0 - xy2.x) * (1.0 - xy2.y), 0.5);

                gl_FragColor = vec4(col, 1.0);
            }
        `;

const createShader = (gl, type, source) => {
    const shader = gl.createShader(type);
    gl.shaderSource(shader, source);
    gl.compileShader(shader);
    
    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        console.error('Shader compile error:', gl.getShaderInfoLog(shader));
        gl.deleteShader(shader);
        return null;
    }
    return shader;
}

const createProgram = (gl, vertexShader, fragmentShader) => {
    const program = gl.createProgram();
    gl.attachShader(program, vertexShader);
    gl.attachShader(program, fragmentShader);
    gl.linkProgram(program);
    
    if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
        console.error('Program link error:', gl.getProgramInfoLog(program));
        gl.deleteProgram(program);
        return null;
    }
    return program;
}

AB.world.newRun = function()
{
    const style = document.createElement('style');
    style.textContent = css;
    document.head.appendChild(style);
	let canvas = document.createElement("canvas");
	// canvas.id = "canvas";
	document.body.appendChild(canvas);
	
    const gl = canvas.getContext('webgl', { preserveDrawingBuffer: true }) 
            || canvas.getContext('experimental-webgl', { preserveDrawingBuffer: true });

    if (!gl) {
        alert('WebGL not supported');
        throw new Error('WebGL not supported');
    }
        
    let resolutionUniformLocation = null;
    let program = null;
    
    const resizeCanvas = () => {
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;
        gl.viewport(0, 0, canvas.width, canvas.height);
        
        if (resolutionUniformLocation && program) {
            gl.useProgram(program);
            gl.uniform2f(resolutionUniformLocation, canvas.width, canvas.height);
        }
    }
    
    window.addEventListener('resize', resizeCanvas);
    
    const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
    const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
    program = createProgram(gl, vertexShader, fragmentShader);
    
    // Set up full-screen quad
    const positionBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
        -1, -1,  1, -1,  -1,  1,  1,  1
    ]), gl.STATIC_DRAW);
    
    const positionAttributeLocation = gl.getAttribLocation(program, 'a_position');
    gl.enableVertexAttribArray(positionAttributeLocation);
    gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
    
    // Get uniform locations
    resolutionUniformLocation = gl.getUniformLocation(program, 'u_resolution');
    const timeUniformLocation = gl.getUniformLocation(program, 'u_time');
    
    gl.useProgram(program);
    resizeCanvas();
    
    // Animation loop
    const startTime = Date.now();
    
    const render = () => {
        const currentTime = (Date.now() - startTime) / 1000;
        
        gl.uniform2f(resolutionUniformLocation, canvas.width, canvas.height);
        gl.uniform1f(timeUniformLocation, currentTime);
        
        gl.clearColor(0, 0, 0, 1);
        gl.clear(gl.COLOR_BUFFER_BIT);
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
        
        requestAnimationFrame(render);
    }
    
    render();
};