-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathshader-bg.js
More file actions
155 lines (133 loc) · 5.81 KB
/
Copy pathshader-bg.js
File metadata and controls
155 lines (133 loc) · 5.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// Footer background shader — a self-contained WebGL take on the saved
// "Untitled" shader (Aurora, all-white, waviness 0 + Dither in source
// mode): a soft drifting luminance field rendered as an ordered-dither
// dot pattern. No libraries.
//
// Degrades to nothing without WebGL; renders one static frame under
// prefers-reduced-motion; pauses offscreen and in background tabs.
// Dot color follows the --border-mid token, so themes just work.
(function () {
'use strict';
var host = document.querySelector('.site-footer');
if (!host) return;
var REDUCED = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var canvas = document.createElement('canvas');
canvas.className = 'shader-bg';
canvas.setAttribute('aria-hidden', 'true');
host.insertBefore(canvas, host.firstChild);
var gl = canvas.getContext('webgl', { alpha: true, premultipliedAlpha: true, antialias: false });
if (!gl) { canvas.remove(); return; }
var VERT = [
'attribute vec2 a_pos;',
'void main() { gl_Position = vec4(a_pos, 0.0, 1.0); }'
].join('\n');
// Aurora: round (waviness 0) gaussian blobs drifting on independent
// orbits. Dither: 4x4 Bayer threshold on the field -> dots of u_color.
var FRAG = [
'precision highp float;',
'uniform vec2 u_res;',
'uniform float u_time;',
'uniform vec3 u_color;',
'uniform float u_px;',
'',
'float blob(vec2 uv, vec2 c, float r) {',
' float d = length(uv - c);',
' return exp(-d * d / (r * r));',
'}',
'',
'// Composed for a short, wide strip: six small aspect-corrected',
'// blobs spread along the band, drifting on independent orbits.',
'float aurora(vec2 uv, float aspect, float t) {',
' vec2 p = uv * vec2(aspect, 1.0);',
' float v = 0.0;',
' v += blob(p, vec2(aspect * (0.08 + 0.06 * sin(t * 0.31)), 0.55 + 0.30 * cos(t * 0.23)), 0.75);',
' v += blob(p, vec2(aspect * (0.24 + 0.05 * cos(t * 0.27)), 0.35 + 0.28 * sin(t * 0.19)), 0.60);',
' v += blob(p, vec2(aspect * (0.42 + 0.06 * sin(t * 0.22)), 0.65 + 0.25 * cos(t * 0.29)), 0.85);',
' v += blob(p, vec2(aspect * (0.58 + 0.05 * cos(t * 0.24)), 0.40 + 0.30 * sin(t * 0.26)), 0.65);',
' v += blob(p, vec2(aspect * (0.76 + 0.06 * sin(t * 0.18)), 0.60 + 0.28 * cos(t * 0.21)), 0.80);',
' v += blob(p, vec2(aspect * (0.92 + 0.05 * cos(t * 0.30)), 0.45 + 0.26 * sin(t * 0.17)), 0.60);',
' return v * 0.65;',
'}',
'',
'float bayer2(vec2 a) { a = floor(a); return fract(a.x / 2.0 + a.y * a.y * 0.75); }',
'float bayer4(vec2 a) { return bayer2(0.5 * a) * 0.25 + bayer2(a); }',
'',
'void main() {',
' vec2 uv = gl_FragCoord.xy / u_res;',
' float aspect = u_res.x / u_res.y;',
' float lum = aurora(uv, aspect, u_time * 4.5);',
' float fade = 1.0 - smoothstep(0.10, 0.60, uv.y);', // dense at the bottom, gone by mid-band
' lum = lum * fade;',
' lum = clamp(lum, 0.0, 0.85);', // never fully solid: keep the halftone texture
' float threshold = bayer4(gl_FragCoord.xy / u_px);',
' float on = step(threshold + 0.02, lum);',
' float a = on * 0.65;',
' gl_FragColor = vec4(u_color * a, a);',
'}'
].join('\n');
function compile(type, src) {
var s = gl.createShader(type);
gl.shaderSource(s, src);
gl.compileShader(s);
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) return null;
return s;
}
var vs = compile(gl.VERTEX_SHADER, VERT);
var fs = compile(gl.FRAGMENT_SHADER, FRAG);
if (!vs || !fs) { canvas.remove(); return; }
var prog = gl.createProgram();
gl.attachShader(prog, vs);
gl.attachShader(prog, fs);
gl.linkProgram(prog);
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) { canvas.remove(); return; }
gl.useProgram(prog);
var buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
var aPos = gl.getAttribLocation(prog, 'a_pos');
gl.enableVertexAttribArray(aPos);
gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);
var uRes = gl.getUniformLocation(prog, 'u_res');
var uTime = gl.getUniformLocation(prog, 'u_time');
var uColor = gl.getUniformLocation(prog, 'u_color');
var uPx = gl.getUniformLocation(prog, 'u_px');
var DPR = Math.min(window.devicePixelRatio || 1, 2);
function resize() {
var w = host.clientWidth, h = host.clientHeight;
canvas.width = Math.max(1, Math.round(w * DPR));
canvas.height = Math.max(1, Math.round(h * DPR));
gl.viewport(0, 0, canvas.width, canvas.height);
gl.uniform2f(uRes, canvas.width, canvas.height);
gl.uniform1f(uPx, 2 * DPR); // 2px dither cells
}
// Dot color from the theme token; re-read when the theme flips.
function setColor() {
var hex = getComputedStyle(document.documentElement).getPropertyValue('--border-mid').trim() || '#D9D9D9';
var n = parseInt(hex.slice(1), 16);
gl.uniform3f(uColor, ((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255);
}
var start = performance.now();
function now() { return (performance.now() - start) / 1000; }
function draw(t) {
gl.uniform1f(uTime, t);
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, 3);
}
resize();
setColor();
new MutationObserver(setColor).observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
window.addEventListener('resize', resize);
if (REDUCED) { draw(3.0); return; } // one static frame
var visible = true;
if ('IntersectionObserver' in window) {
new IntersectionObserver(function (entries) {
visible = entries[0].isIntersecting;
}).observe(host);
}
draw(now()); // first frame synchronously — rAF can stall in throttled webviews
(function loop() {
if (visible && !document.hidden) draw(now());
requestAnimationFrame(loop);
})();
})();