const elemHeader = document.querySelector('p.header')! const elemDescription = document.querySelector('p.description')! const elemRedirect = document.querySelector('a.redirect')! function createImage(src: string): HTMLImageElement { const i = document.createElement('img') i.src = src return i } const slides = new Array<{ image: HTMLImageElement url?: string | null tooltip?: string header: string description: string }>({ image: createImage('/index/slide-1.gif'), header: 'Tech, Chunibyo & Other Delusions', url: '/secret-picross.html', tooltip: 'Picross', description: 'Welcome to my personal homepage, use the buttons at the top of the page to visit other sections of the site.', }) const CODE_VERTEX = ` attribute vec2 p; varying vec2 uv; void main(){ uv = p * 0.5 + 0.5; gl_Position = vec4(p, 0, 1); } ` const CODE_SHADER = ` precision highp float; varying vec2 uv; uniform vec2 res; uniform sampler2D texA; uniform sampler2D texB; uniform float transition, degauss, time, barrel, scan, mask, chroma, bloom, vig, noise, flicker, jitter, bright, sat, corner; float rand(vec2 co){ return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453); } vec2 barrelD(vec2 u, float k) { vec2 c = u - 0.5; return u + c * dot(c, c) * k; } vec3 sampleChroma(sampler2D t, vec2 u) { float ca = chroma * (1. + dot(u - .5, u - .5) * 2.); float r = texture2D(t, barrelD(u + vec2(ca, 0), barrel)).r; float g = texture2D(t, barrelD(u, barrel)).g; float b = texture2D(t, barrelD(u - vec2(ca, 0), barrel)).b; return vec3(r, g, b); } vec3 phosphorBloom(sampler2D t, vec2 u) { vec3 glow = vec3(0.0); float w = 0.0; // two rings — tight inner glow + wide outer halo for (int x = -4; x <= 4; x++) { for (int y = -4; y <= 4; y++) { vec2 off = vec2(float(x), float(y)) / res * 3.5; vec3 s = texture2D(t, barrelD(u + off, barrel)).rgb; // only bright pixels contribute — threshold at 0.6 luma float lum = dot(s, vec3(0.299, 0.587, 0.114)); float thresh = max(lum - 0.55, 0.0) * 2.5; s *= thresh; float wt = exp(-dot(vec2(x, y), vec2(x, y)) * 0.18); glow += s * wt; w += wt; } } glow /= w; // wide halo pass at 3x the offset vec3 halo = vec3(0.0); float hw = 0.0; for (int x = -3; x <= 3; x++) { for (int y = -3; y <= 3; y++) { vec2 off = vec2(float(x), float(y)) / res * 10.0; vec3 s = texture2D(t, barrelD(u + off, barrel)).rgb; float lum = dot(s, vec3(0.299, 0.587, 0.114)); float thresh = max(lum - 0.7, 0.0) * 4.0; s *= thresh; float wt = exp(-dot(vec2(x, y), vec2(x, y)) * 0.25); halo += s * wt; hw += wt; } } halo /= hw; // phosphor tint — warm yellow-green inner, cooler blue outer halo glow *= vec3(1.1, 1.05, 0.85); halo *= vec3(0.85, 0.95, 1.15); return glow * bloom + halo * bloom * 0.4; } vec3 shadowMask(vec2 u) { float px = mod(u.x * res.x, 3.); vec3 m = vec3(step(px, 1.), step(1., px) * step(px, 2.), step(2., px)); return mix(vec3(1.), m * 1.6 + .15, mask * .5); } float scanlineW(vec2 u) { return mix(1., max(0., sin(u.y * res.y * 3.14159)) * .5 + .5, scan); } void main() { vec2 u = uv; u.x += sin(u.y * 200. + time * 3.) * (jitter * .5) + sin(u.y * 50. + time * 7.) * jitter; // degauss vec2 centered = u - 0.5; float dist = length(centered); float wavefront = degauss * 0.9; float ring = exp(-pow((dist - wavefront) * 12.0, 2.0)); float push = ring * 0.08 * sin(degauss * 3.14159); u += normalize(centered + 0.0001) * push; vec2 bd = barrelD(u, barrel); if (bd.x < 0. || bd.x > 1. || bd.y < 0. || bd.y > 1.) { gl_FragColor = vec4(0, 0, 0, 1); return; } vec2 cr = vec2(corner * res.y / res.x, corner); vec2 e = smoothstep(vec2(0.), cr, bd) * smoothstep(vec2(1.), vec2(1.) - cr, bd); if (e.x * e.y < .01) { gl_FragColor = vec4(0, 0, 0, 1); return; } // chroma boost at ring float ca_boost = chroma + ring * 0.03 * sin(degauss * 3.14159); // old texture float rA = texture2D(texA, barrelD(u + vec2(ca_boost, 0.), barrel)).r; float gA = texture2D(texA, barrelD(u, barrel)).g; float bA = texture2D(texA, barrelD(u - vec2(ca_boost, 0.), barrel)).b; // new texture float rB = texture2D(texB, barrelD(u + vec2(ca_boost, 0.), barrel)).r; float gB = texture2D(texB, barrelD(u, barrel)).g; float bB = texture2D(texB, barrelD(u - vec2(ca_boost, 0.), barrel)).b; // wipe: pixels closer to center than wavefront show new texture float wipe = smoothstep(wavefront - 0.05, wavefront + 0.05, dist); vec3 col = mix(vec3(rB, gB, bB), vec3(rA, gA, bA), wipe); // rainbow tint at ring col += ring * 0.4 * vec3( sin(degauss * 6.28 + 0.0), sin(degauss * 6.28 + 2.09), sin(degauss * 6.28 + 4.18) ) * sin(degauss * 3.14159); vec3 bl = mix(phosphorBloom(texB, u), phosphorBloom(texA, u), wipe); col += bl; float lum = dot(col, vec3(.299, .587, .114)); col = mix(col, bl, bloom * (lum * lum * .5 + .1)); col *= shadowMask(bd); col *= scanlineW(bd); float v = 1. - dot(bd - .5, bd - .5) * vig * 2.; col *= clamp(v, 0., 1.); float fl = 1. - flicker * .5 + flicker * sin(time * 73. + bd.y * 100.) * .5; col *= fl; col += (rand(bd + fract(time * .1)) - .5) * noise; col *= bright; float l2 = dot(col, vec3(.299, .587, .114)); col = mix(vec3(l2), col, sat); col = pow(max(col, vec3(0.)), vec3(1. / 2.2)); col *= e.x * e.y; gl_FragColor = vec4(clamp(col, 0., 1.), 0); } ` const canvas = document.querySelector('canvas')! const W = canvas.clientWidth const H = canvas.clientHeight let degauss = 0, noise = 0, slideIndex = -1, slideCounter = 0 canvas.width = W canvas.height = H const gl = canvas.getContext('webgl')! const program = gl.createProgram() function createShader(shaderType: number, shaderCode: string) { const shader = gl.createShader(shaderType)! gl.shaderSource(shader, shaderCode) gl.compileShader(shader) return shader } gl.attachShader(program, createShader(gl.VERTEX_SHADER, CODE_VERTEX)) gl.attachShader(program, createShader(gl.FRAGMENT_SHADER, CODE_SHADER)) gl.linkProgram(program) gl.useProgram(program) const bf = gl.createBuffer() gl.bindBuffer(gl.ARRAY_BUFFER, bf) gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW) const ap = gl.getAttribLocation(program, 'p') gl.enableVertexAttribArray(ap) gl.vertexAttribPointer(ap, 2, gl.FLOAT, false, 0, 0) const U = { transition: gl.getUniformLocation(program, 'transition'), degauss: /**/ gl.getUniformLocation(program, 'degauss'), time: /*---*/ gl.getUniformLocation(program, 'time'), res: /*----*/ gl.getUniformLocation(program, 'res'), texA: /*---*/ gl.getUniformLocation(program, 'texA'), texB: /*---*/ gl.getUniformLocation(program, 'texB'), barrel: /*-*/ gl.getUniformLocation(program, 'barrel'), scan: /*---*/ gl.getUniformLocation(program, 'scan'), mask: /*---*/ gl.getUniformLocation(program, 'mask'), chroma: /*-*/ gl.getUniformLocation(program, 'chroma'), bloom: /*--*/ gl.getUniformLocation(program, 'bloom'), vig: /*----*/ gl.getUniformLocation(program, 'vig'), noise: /*--*/ gl.getUniformLocation(program, 'noise'), flicker: /**/ gl.getUniformLocation(program, 'flicker'), jitter: /*-*/ gl.getUniformLocation(program, 'jitter'), bright: /*-*/ gl.getUniformLocation(program, 'bright'), sat: /*----*/ gl.getUniformLocation(program, 'sat'), corner: /*-*/ gl.getUniformLocation(program, 'corner'), } const textureA = gl.createTexture() const textureCanvasA = document.createElement('canvas') const textureContextA = textureCanvasA.getContext('2d')! { textureCanvasA.width = W textureCanvasA.height = H gl.bindTexture(gl.TEXTURE_2D, textureA) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) } const textureB = gl.createTexture() const textureCanvasB = document.createElement('canvas') const textureContextB = textureCanvasB.getContext('2d')! { textureCanvasB.width = W textureCanvasB.height = H gl.bindTexture(gl.TEXTURE_2D, textureB) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) } // function renderSMPTE() { // const canvas = textureCanvasA // const ctx = textureContextA // canvas.width = W // canvas.height = H // const bars = [ // ['#c0c0c0', '#c0c000', '#00c0c0', '#00c000', '#c000c0', '#c00000', '#0000c0'], // ['#0000c0', '#101010', '#c000c0', '#101010', '#00c0c0', '#101010', '#c0c0c0'], // ['#001b4b', '#ffffff', '#2e006a', '#0b0b0b', '#0b0b0b', '#0b0b0b'], // ] // let bw = W / 7 // bars[0].forEach((c, i) => { // ctx.fillStyle = c // ctx.fillRect(i * bw, 0, bw, H * 0.675) // }) // bars[1].forEach((c, i) => { // ctx.fillStyle = c // ctx.fillRect(i * bw, H * 0.675, bw, H * 0.125) // }) // bw = W / 6 // bars[2].forEach((c, i) => { // ctx.fillStyle = c // ctx.fillRect(i * bw, H * 0.75, bw, H * 0.25) // }) // ctx.fillStyle = '#fff' // ctx.fillRect(W * 0.625, H * 0.8, W * 0.25, H * 0.12) // ctx.font = 'bold 24px monospace' // ctx.fillStyle = '#000' // ctx.fillText('PLEASE STAND BY', W * 0.65, H * 0.875) // gl.activeTexture(gl.TEXTURE0) // gl.bindTexture(gl.TEXTURE_2D, textureA) // gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true) // gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas) // } function renderFrame(t: number) { gl.uniform1f(U.time, t) gl.uniform2f(U.res, W, H) gl.uniform1f(U.degauss, degauss) gl.uniform1i(U.texA, 0) gl.uniform1i(U.texB, 1) gl.uniform1f(U.barrel, /*-*/ 0.25) gl.uniform1f(U.scan, /*---*/ 0.25) gl.uniform1f(U.mask, /*---*/ 1) gl.uniform1f(U.chroma, /*-*/ 0.0025) gl.uniform1f(U.bloom, /*--*/ 0.5) gl.uniform1f(U.vig, /*----*/ 1) gl.uniform1f(U.noise, /*--*/ noise) gl.uniform1f(U.flicker, /**/ 0.0375) gl.uniform1f(U.jitter, /*-*/ 0.0005) gl.uniform1f(U.bright, /*-*/ 0.8) gl.uniform1f(U.sat, /*----*/ 1.0) gl.uniform1f(U.corner, /*-*/ 0.1) gl.viewport(0, 0, W, H) gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4) requestAnimationFrame(renderFrame) } async function nextSlide(init = false) { const transitionTime = 1000 const nextIndex = slideCounter++ % slides.length if (nextIndex === slideIndex) return slideIndex = nextIndex const next = slides[nextIndex] elemHeader.textContent = next.header elemDescription.textContent = next.description if (next.url) { elemRedirect.href = next.url elemRedirect.style.opacity = '1' elemRedirect.style.pointerEvents = 'all' } else { elemRedirect.style.opacity = '0' elemRedirect.style.pointerEvents = 'none' } elemRedirect.textContent = `Discover ${next.tooltip || 'More'} >>>` function tween(from: number, to: number, ms: number, fn: (v: number) => void) { return new Promise((resolve) => { const start = performance.now() function tick() { const t = Math.min((performance.now() - start) / ms, 1) const e = t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t // easing fn(from + (to - from) * e) if (t < 1) requestAnimationFrame(tick) else resolve() } requestAnimationFrame(tick) }) } textureContextB.drawImage(next.image!, 0, 0, W, H) gl.activeTexture(gl.TEXTURE1) gl.bindTexture(gl.TEXTURE_2D, textureB) gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true) gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textureCanvasB) if (init) { tween(0, 0.025, transitionTime, (t) => (noise = t)) setInterval(nextSlide, transitionTime + 8000) } await tween(0, 1, transitionTime, (t) => (degauss = t)) textureContextA.drawImage(next.image!, 0, 0, W, H) gl.activeTexture(gl.TEXTURE0) gl.bindTexture(gl.TEXTURE_2D, textureA) gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true) gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textureCanvasA) degauss = 0 } // Startup Sequence (Blank Screen) slides[0].image.onload = () => { nextSlide(true) requestAnimationFrame(renderFrame) } gl.activeTexture(gl.TEXTURE0) gl.bindTexture(gl.TEXTURE_2D, textureA) gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true) gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas)