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();
};