Inside
SOLARIS.
An imagined solar observatory. Follow the light beyond the visible. The signature technique is raw glsl solar corona.
Build the full-screen field
A fixed WebGL canvas sits behind semantic sections. An independent left-to-right scrim protects text. The Sun is deliberately offset 0.2 viewport-height units to the right.
Write the corona
Normalize fragment coordinates by viewport height, then convert to radius and angle. Five octaves of rotated value noise modulate an exponential ring. A smoothstep mask cuts the dark disc out of its center.
Map the journey
Scroll is normalized from zero to one and expands the disc radius from 0.23 to 0.32. Time advances the noise by 0.11 units per second. The fragment shader is the complete renderer; there are no textures.
Bound the work
Cap device pixel ratio at 1.5. Reduced motion freezes shader time but keeps scroll responsive. A CSS radial-gradient fallback appears when WebGL is unavailable.
Reproduce the site
- Download the complete source below and keep its name
index.html. - Open it through a static server. From its directory, run
python3 -m http.server 8000, then visithttp://localhost:8000. There is no install or build step. - The stylesheet lives in the head. The rendering and interaction code lives in the final script. Adjust one parameter at a time and compare the result at desktop and 375 px widths.
- Google Fonts requires a network connection. All visible artwork is computed locally. Native system fallbacks preserve readable text if fonts fail.
- Enable your operating system’s reduced-motion setting and repeat the interaction. Use browser developer tools to check errors, overflow, and frame timing before publishing.
The complete rendering and interaction code
const canvas=document.querySelector('#sun'),gl=canvas.getContext('webgl',{antialias:false});const reduced=matchMedia('(prefers-reduced-motion: reduce)');if(gl){const vert='attribute vec2 p;void main(){gl_Position=vec4(p,0.,1.);}';const frag=`precision highp float;uniform vec2 res;uniform float t,s;float hash(vec2 p){return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453);}float noise(vec2 p){vec2 i=floor(p),f=fract(p);f=f*f*(3.-2.*f);return mix(mix(hash(i),hash(i+vec2(1,0)),f.x),mix(hash(i+vec2(0,1)),hash(i+vec2(1,1)),f.x),f.y);}float fbm(vec2 p){float v=0.,a=.5;for(int i=0;i<5;i++){v+=a*noise(p);p=mat2(.8,-.6,.6,.8)*p*2.1;a*=.5;}return v;}void main(){vec2 uv=(gl_FragCoord.xy-.5*res)/res.y;uv.x-=.2;float r=length(uv),a=atan(uv.y,uv.x);float n=fbm(vec2(a*4.,r*14.-t*.11));float radius=.23+s*.09;float ring=exp(-abs(r-radius)*22.)*(.55+n);float rays=pow(fbm(vec2(a*8.,r*3.-t*.05)),3.)*exp(-max(0.,r-radius)*5.);float mask=smoothstep(radius-.006,radius+.018,r);vec3 color=vec3(1.,.17+s*.09,.016)*ring*2.+vec3(1.,.32,.06)*rays*2.;color*=mask;color+=vec3(.006,.009,.009);gl_FragColor=vec4(color,1.);}`;function sh(type,src){const x=gl.createShader(type);gl.shaderSource(x,src);gl.compileShader(x);if(!gl.getShaderParameter(x,gl.COMPILE_STATUS))throw Error(gl.getShaderInfoLog(x));return x}const program=gl.createProgram();gl.attachShader(program,sh(gl.VERTEX_SHADER,vert));gl.attachShader(program,sh(gl.FRAGMENT_SHADER,frag));gl.linkProgram(program);gl.useProgram(program);const b=gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER,b);gl.bufferData(gl.ARRAY_BUFFER,new Float32Array([-1,-1,1,-1,-1,1,-1,1,1,-1,1,1]),gl.STATIC_DRAW);const p=gl.getAttribLocation(program,'p');gl.enableVertexAttribArray(p);gl.vertexAttribPointer(p,2,gl.FLOAT,false,0,0);const u=['res','t','s'].map(x=>gl.getUniformLocation(program,x));function draw(time=0){const d=Math.min(devicePixelRatio,1.5);if(canvas.width!==Math.round(innerWidth*d)||canvas.height!==Math.round(innerHeight*d)){canvas.width=Math.round(innerWidth*d);canvas.height=Math.round(innerHeight*d);gl.viewport(0,0,canvas.width,canvas.height)}gl.uniform2f(u[0],canvas.width,canvas.height);gl.uniform1f(u[1],reduced.matches?0:time*.001);gl.uniform1f(u[2],Math.min(1,scrollY/(document.documentElement.scrollHeight-innerHeight)));gl.drawArrays(gl.TRIANGLES,0,6);if(!reduced.matches)requestAnimationFrame(draw)}draw();addEventListener('resize',()=>{if(reduced.matches)draw()});addEventListener('scroll',()=>{if(reduced.matches)draw()},{passive:true});reduced.addEventListener('change',()=>draw())}else{canvas.style.background='radial-gradient(circle at 70% 45%,#160900 13%,#ff6b21 14%,#7a200c 19%,#080908 42%)'}The complete visual system
:root{--body:'IBM Plex Sans',sans-serif}body{background:#080908;color:#f5e9d2;font-family:var(--body)}#sun{position:fixed;inset:0;width:100%;height:100%;z-index:0}.shade{position:fixed;inset:0;background:linear-gradient(90deg,rgba(8,9,8,.77),transparent 80%);pointer-events:none}nav{position:relative;z-index:2;display:flex;justify-content:space-between;padding:32px 5vw;align-items:center}nav b{letter-spacing:.3em;font-size:15px}nav span{font:12px monospace;color:#e0c8a9}.hero{position:relative;z-index:1;min-height:92svh;padding:11vh 5vw 8vh;display:flex;flex-direction:column;justify-content:space-between}.hero h1{font:400 clamp(80px,10vw,160px)/.86 'Instrument Serif';letter-spacing:-.04em;max-width:7ch;margin:30px 0}.hero h1 em{color:#ff8b4f}.hero p{max-width:320px;color:#e1cdb7;font-size:16px}.hero small{font-size:12px;letter-spacing:.17em}.scroll-note{margin-top:40px;display:flex;gap:18px;align-items:center;font-size:12px}.scroll-note:before{content:'';width:65px;height:1px;background:#ff8b4f}.chapter{position:relative;margin:0 5vw 8vh;padding:45px 0;border-top:1px solid #98705b;display:grid;grid-template-columns:1fr 1fr;gap:40px;min-height:70vh;align-content:center}.chapter h2{font:400 clamp(45px,6vw,90px)/1 'Instrument Serif';margin:0}.chapter p{background:rgba(8,9,8,.8);padding:25px;max-width:520px;font-size:17px}.colophon{background:#080908}@media(max-width:600px){nav span{display:none}.hero{min-height:90svh}.hero h1{font-size:90px}.chapter{grid-template-columns:1fr}.shade{background:linear-gradient(90deg,#080908c9,#08090815)}}Original fictional design study. No stock photography, model files, image textures, or sample audio.