Custom Code Experiment •
LQ: Face
Source Code View HTML / CSS / JavaScript
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Liquid crystal face</title>
<style>
:root{
--bg:#e7e5ef;
--field:#fbfaff;
--halo:.20;
color-scheme:light;
box-sizing:border-box;
padding-top:env(safe-area-inset-top,0px);
padding-bottom:env(safe-area-inset-bottom,0px);
}
@media (prefers-color-scheme: dark){
:root:not([data-theme="light"]){
--bg:#0a0818;
--field:#1b1638;
--halo:.28;
color-scheme:dark;
}
}
:root[data-theme="dark"]{
--bg:#0a0818;
--field:#1b1638;
--halo:.28;
color-scheme:dark;
}
*,*::before,*::after{box-sizing:inherit}
html{height:100%;scroll-padding-top:env(safe-area-inset-top,0px)}
body{
margin:0;
height:100%;
overflow:auto;
background-color:var(--bg);
background-image:radial-gradient(circle at 50% 50%, var(--field) 0, var(--bg) min(70vmin, 620px));
display:grid;
place-items:center;
}
.stage{
display:grid;
place-items:center;
padding:24px;
}
.orb{
position:relative;
width:min(84vmin, 560px);
aspect-ratio:1 / 1;
}
.orb canvas{
position:absolute;
inset:0;
width:100%;
height:100%;
display:block;
}
.halo{
position:absolute;
inset:20%;
border-radius:50%;
background:conic-gradient(from 0deg, #c3459e, #f2d64b, #35c9b5, #3a6ff0, #8a3cc8, #c3459e);
filter:blur(44px);
opacity:calc(var(--halo) * (0.55 + var(--melt, 1) * 0.6));
animation:spin 16s linear infinite;
}
.blob{
display:none;
position:absolute;
inset:26%;
border-radius:42% 58% 55% 45% / 48% 42% 58% 52%;
background:conic-gradient(from 200deg, #8a3cc8, #3a6ff0, #35c9b5, #f2d64b, #f07a6b, #c3459e, #8a3cc8);
animation:morph 5s ease-in-out infinite alternate, spin 12s linear infinite;
}
.no-gl canvas{display:none}
.no-gl .blob{display:block}
@keyframes spin{to{transform:rotate(360deg)}}
@keyframes morph{
0%{border-radius:42% 58% 55% 45% / 48% 42% 58% 52%}
100%{border-radius:58% 42% 40% 60% / 40% 60% 44% 56%}
}
@media (prefers-reduced-motion: reduce){
.halo{animation:none}
.blob{animation:none}
}
</style>
</head>
<body>
<main class="stage">
<div class="orb" id="orb" role="img" aria-label="A glassy, iridescent face that melts into liquid, then gathers back into a faceted crystal and smiles">
<div class="halo" aria-hidden="true"></div>
<canvas id="lc" aria-hidden="true"></canvas>
<div class="blob" aria-hidden="true"></div>
</div>
</main>
<script id="vs" type="x-shader/x-vertex">
attribute vec2 aPos;
void main(){ gl_Position = vec4(aPos, 0.0, 1.0); }
</script>
<script id="fs" type="x-shader/x-fragment">
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
uniform vec2 uRes;
uniform float uTime;
uniform float uOrder; // 0 = liquid, 1 = crystal
uniform vec2 uTilt; // head turn toward the pointer
uniform float uLight;
uniform float uSweep;
uniform float uGlint;
uniform float uBlink; // 1 = eyes open, 0 = closed
const vec3 HEADR = vec3(0.60, 0.72, 0.56);
const float TABLE = 0.49; // flat front facet the face sits on when crystalline
mat2 gTy; mat2 gTx; mat2 gSpin; mat2 gWob;
float gMelt; float gDropOn;
vec3 gD0; vec3 gD1; vec3 gD2; vec3 gD3; vec4 gDR;
vec3 gEyeL; vec3 gEyeRt; vec3 gEyeSize;
float gMouthY; float gMouthRa; vec2 gMouthSC; float gMouthW;
mat2 rot(float a){ float c = cos(a), s = sin(a); return mat2(c, -s, s, c); }
float smin(float a, float b, float k){
float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);
return mix(b, a, h) - k * h * (1.0 - h);
}
float sdEll(vec3 p, vec3 r){
float k0 = length(p / r);
float k1 = length(p / (r * r));
return k0 * (k0 - 1.0) / (k1 + 1e-5);
}
vec3 tiltP(vec3 p){ p.yz = gTy * p.yz; p.xz = gTx * p.xz; return p; }
vec3 spinP(vec3 p){ p.xz = gSpin * p.xz; p.xy = gWob * p.xy; return p; }
// height of the face surface at (x, y): flat facet when crystal, rounded when liquid
float surfZ(vec2 xy){
vec2 n = xy / HEADR.xy;
float ez = HEADR.z * sqrt(max(0.0, 1.0 - dot(n, n)));
return mix(TABLE, ez, gMelt);
}
// Faceted head: dodecahedron + icosahedron planes on an ellipsoid, with a flat face table
float crystalHead(vec3 p){
vec3 q = abs(p / HEADR);
float dod = max(max(0.525731 * q.y + 0.850651 * q.z, 0.525731 * q.x + 0.850651 * q.y),
0.850651 * q.x + 0.525731 * q.z);
float ico = max(max(0.57735 * (q.x + q.y + q.z), 0.356822 * q.y + 0.934172 * q.z),
max(0.356822 * q.x + 0.934172 * q.y, 0.934172 * q.x + 0.356822 * q.z));
float d = max(dod - 1.0, ico - 1.04) * 0.56;
return max(d, p.z - TABLE);
}
// Rippling liquid head (calmer on the face so the features stay readable)
float liquidHead(vec3 p){
float t = uTime;
float w = sin(p.x * 4.0 + t * 1.7) * sin(p.y * 3.5 - t * 1.3) * sin(p.z * 4.0 + t * 1.1);
w += 0.6 * sin(p.x * 7.0 - t * 2.3 + p.y * 2.0) * sin(p.z * 6.5 + t * 1.9 - p.y * 1.5);
w *= 1.0 - 0.7 * smoothstep(0.15, 0.45, p.z);
return sdEll(p, HEADR) - 0.045 * w;
}
float headD(vec3 p){
float d = crystalHead(p);
if (gMelt > 0.002) d = mix(d, liquidHead(p), gMelt);
return d;
}
// Mouth: a tube bent along an arc, from a small "o" (liquid) to a smile (crystal)
float mouthD(vec3 p){
vec2 a = p.xy - vec2(0.0, gMouthY + gMouthRa);
a.y = -a.y;
a.x = abs(a.x);
float d2 = (gMouthSC.y * a.x > gMouthSC.x * a.y) ? length(a - gMouthSC * gMouthRa)
: abs(length(a) - gMouthRa);
float zc = surfZ(p.xy) - 0.004;
return length(vec2(d2, p.z - zc)) - gMouthW;
}
float featD(vec3 p){
if (p.z < 0.3) return 0.4 - p.z;
float e1 = sdEll(p - gEyeL, gEyeSize);
float e2 = sdEll(p - gEyeRt, gEyeSize);
return min(min(e1, e2), mouthD(p));
}
// x = glass head (with droplets), y = dark features (eyes, mouth)
vec2 map2(vec3 pw){
vec3 q = tiltP(pw);
vec3 p = spinP(q);
float h = headD(p);
if (gDropOn > 0.5){
h = smin(h, length(q - gD0) - gDR.x, 0.2);
h = smin(h, length(q - gD1) - gDR.y, 0.2);
h = smin(h, length(q - gD2) - gDR.z, 0.2);
h = smin(h, length(q - gD3) - gDR.w, 0.2);
}
return vec2(h, featD(p));
}
float map(vec3 pw){ vec2 m = map2(pw); return min(m.x, m.y); }
vec3 dropPos(float fi, float orbit){
float a = uTime * (0.75 + 0.18 * fi) + fi * 1.5708;
float R = orbit * (0.9 + 0.1 * sin(uTime * 1.1 + fi * 2.1));
return vec3(cos(a) * R, sin(a * 1.3 + fi * 1.7) * 0.30 * orbit, sin(a) * R);
}
float dropRad(float fi, float base){ return base * (0.75 + 0.3 * sin(uTime * 1.9 + fi * 2.7)); }
vec3 calcN(vec3 p){
const float e = 0.0015;
vec2 k = vec2(1.0, -1.0);
return normalize(k.xyy * map(p + k.xyy * e) + k.yyx * map(p + k.yyx * e) +
k.yxy * map(p + k.yxy * e) + k.xxx * map(p + k.xxx * e));
}
float lobe(float a, float c, float w){ float x = (a - c) * w; return exp(-x * x); }
// Procedural studio: softbox, strip lights, coloured rims
vec3 env(vec3 d){
float y = d.y;
float a = atan(d.z, d.x);
vec3 lo = mix(vec3(0.012, 0.010, 0.030), vec3(0.50, 0.50, 0.58), uLight);
vec3 hi = mix(vec3(0.080, 0.070, 0.150), vec3(0.95, 0.95, 0.99), uLight);
vec3 col = mix(lo, hi, smoothstep(-1.0, 1.0, y));
col += vec3(1.5) * smoothstep(0.62, 0.93, y);
float band = smoothstep(-0.35, 0.2, y) * (1.0 - smoothstep(0.5, 0.8, y));
col += vec3(1.5, 1.45, 1.38) * lobe(a, 0.7, 6.0) * band;
col += vec3(1.05, 1.15, 1.4) * lobe(a, -2.3, 6.0) * band;
float hz = 1.0 - smoothstep(0.0, 0.5, abs(y));
col += vec3(1.0, 0.22, 0.62) * lobe(a, -0.8, 2.4) * hz;
col += vec3(0.12, 0.72, 1.0) * lobe(a, 2.4, 2.4) * hz;
col += vec3(0.4, 1.0, 0.55) * lobe(a, -1.7, 3.0) * hz * 0.35;
return col;
}
vec3 thinFilm(float cosT, float d){
float n = 1.38;
float ct = sqrt(max(0.0, 1.0 - (1.0 - cosT * cosT) / (n * n)));
float opd = 2.0 * n * d * ct;
return 0.5 + 0.5 * cos(6.2831853 * opd / vec3(640.0, 545.0, 460.0));
}
float ml1(float G, float l){ float s = sin(3.14159265 * G / l); return s * s; }
vec3 michelLevy(float G){
vec3 c = vec3(0.35, 0.0, 1.0) * ml1(G, 430.0)
+ vec3(0.0, 0.3, 1.0) * ml1(G, 470.0)
+ vec3(0.0, 0.9, 0.6) * ml1(G, 510.0)
+ vec3(0.3, 1.0, 0.1) * ml1(G, 550.0)
+ vec3(1.0, 0.75, 0.0) * ml1(G, 590.0)
+ vec3(1.0, 0.25, 0.0) * ml1(G, 630.0)
+ vec3(0.8, 0.0, 0.05) * ml1(G, 670.0);
return c / vec3(3.45, 3.2, 2.75);
}
vec3 march(vec3 ro, vec3 rd, float t0, float t1){
float t = t0, md = 1e4, tm = t0;
bool exited = false;
for (int i = 0; i < 110; i++){
float d = map(ro + rd * t);
if (d < md){ md = d; tm = t; }
if (d < 0.0007) return vec3(t, 0.0, t);
t += d * 0.68;
if (t > t1){ exited = true; break; }
}
if (!exited && md < 0.02) return vec3(tm, 0.0, tm);
return vec3(-1.0, md, tm);
}
// march inside the glass until it leaves the glass or meets an eye / the mouth
float traceIn(vec3 ro, vec3 rd){
float t = 0.0;
for (int i = 0; i < 40; i++){
vec2 m = map2(ro + rd * t);
float d = min(-m.x, m.y);
if (d < 0.002) break;
t += max(d * 0.85, 0.008);
if (t > 2.6) break;
}
return t;
}
vec3 shade(vec3 p, vec3 rd, vec2 uv){
vec3 n = calcN(p);
vec3 po = spinP(tiltP(p));
vec2 mat = map2(p);
float cosT = clamp(dot(-rd, n), 0.0, 1.0);
vec3 R = reflect(rd, n);
float sx = (uv.x * 0.8 + uv.y - uSweep) * 7.0;
float glint = exp(-sx * sx) * uGlint;
if (mat.y < mat.x){
// eyes and mouth: dark glossy liquid-crystal black with a faint oil-slick sheen
float F = 0.04 + 0.96 * pow(1.0 - cosT, 5.0);
vec3 film = thinFilm(cosT, 320.0 + 120.0 * sin(po.x * 9.0 + uTime));
vec3 col = vec3(0.010, 0.008, 0.022);
col += env(R) * mix(0.06, 0.9, F) * (0.5 + 0.8 * film);
float spec = pow(max(dot(R, normalize(vec3(-0.35, 0.8, 0.6))), 0.0), 160.0);
col += spec * 2.2 + glint * 0.3;
return col;
}
float F = 0.05 + 0.95 * pow(1.0 - cosT, 4.0);
float thick = 380.0 + 170.0 * sin(po.y * 4.2 + uTime * 0.7)
+ 110.0 * sin(dot(po, vec3(3.3, 1.9, 2.6)) - uTime * 1.2);
vec3 film = thinFilm(cosT, thick);
vec3 refl = env(R) * (0.35 + 1.3 * film);
vec3 ri = refract(rd, n, 0.6667);
if (dot(ri, ri) < 1e-4) ri = rd;
vec3 pin = p - n * 0.012;
float ti = traceIn(pin, ri);
vec3 pe = pin + ri * ti;
vec2 me = map2(pe);
float G = ti * 1050.0
+ 230.0 * sin(dot(po, vec3(2.4, 3.2, 1.4)) * 1.3 + uTime * 0.9)
+ 150.0 * sin(po.y * 6.5 - po.x * 2.0 - uTime * 1.4);
vec3 ml = michelLevy(max(G, 0.0));
vec3 tr;
if (me.y < -me.x){
// looking through the glass at the back of an eye or the mouth
tr = vec3(0.012, 0.01, 0.024) + ml * 0.08;
} else {
vec3 ne = calcN(pe);
vec3 tir = reflect(ri, -ne);
vec3 oR = refract(ri, -ne, 1.47); if (dot(oR, oR) < 1e-4) oR = tir;
vec3 oG = refract(ri, -ne, 1.50); if (dot(oG, oG) < 1e-4) oG = tir;
vec3 oB = refract(ri, -ne, 1.54); if (dot(oB, oB) < 1e-4) oB = tir;
tr = vec3(env(oR).r, env(oG).g, env(oB).b);
tr = tr * (0.4 + 0.9 * ml) + ml * mix(0.42, 0.5, uLight);
}
vec3 col = mix(tr, refl, F);
float spec = pow(max(dot(R, normalize(vec3(-0.45, 0.85, 0.55))), 0.0), 90.0);
col += spec * (1.4 + film);
col += pow(1.0 - cosT, 3.0) * film * 0.2;
col += glint * (0.5 + 0.7 * film);
return col;
}
void main(){
vec2 uv = (gl_FragCoord.xy - 0.5 * uRes) / uRes.y;
gTy = rot(uTilt.y);
gTx = rot(uTilt.x);
gSpin = rot(0.32 * sin(uTime * 0.45));
gWob = rot(0.07 * sin(uTime * 0.37));
float m = clamp(1.0 - uOrder, 0.0, 1.0);
gMelt = m * m * (3.0 - 2.0 * m);
float orbit = mix(1.0, 0.0, smoothstep(0.05, 0.8, uOrder));
float dr = 0.11 * (1.0 - smoothstep(0.55, 0.92, uOrder));
gDropOn = dr > 0.002 ? 1.0 : 0.0;
gD0 = dropPos(0.0, orbit); gD1 = dropPos(1.0, orbit);
gD2 = dropPos(2.0, orbit); gD3 = dropPos(3.0, orbit);
gDR = vec4(dropRad(0.0, dr), dropRad(1.0, dr), dropRad(2.0, dr), dropRad(3.0, dr));
// eyes
float ex = 0.19, ey = 0.10;
gEyeSize = vec3(mix(0.066, 0.074, gMelt), mix(0.088, 0.08, gMelt) * max(uBlink, 0.1), 0.055);
float jig = 0.012 * gMelt;
gEyeL = vec3(-ex + jig * sin(uTime * 2.1), ey + jig * sin(uTime * 1.7), surfZ(vec2(-ex, ey)) - 0.022);
gEyeRt = vec3( ex + jig * sin(uTime * 1.9 + 1.0), ey + jig * sin(uTime * 2.3 + 2.0), surfZ(vec2( ex, ey)) - 0.022);
// mouth: "o" while liquid, widening into a smile as it crystallises
float ta = mix(3.14159, 0.78, smoothstep(0.0, 0.6, uOrder));
gMouthSC = vec2(sin(ta), cos(ta));
gMouthRa = mix(0.05, 0.2, smoothstep(0.2, 1.0, uOrder));
gMouthY = -0.17;
gMouthW = 0.026;
vec3 ro = vec3(0.0, 0.0, 4.0);
vec3 rd = normalize(vec3(uv, -1.65));
float b = dot(ro, rd);
float c = dot(ro, ro) - 1.69;
float h = b * b - c;
if (h < 0.0){ gl_FragColor = vec4(0.0); return; }
h = sqrt(h);
vec3 res = march(ro, rd, max(-b - h, 0.0), -b + h);
vec3 col;
float alpha = 1.0;
if (res.x > 0.0){
col = shade(ro + rd * res.x, rd, uv);
} else {
float w = res.z / (uRes.y * 1.65) * 1.6;
if (res.y > w){ gl_FragColor = vec4(0.0); return; }
alpha = 1.0 - smoothstep(0.0, w, res.y);
col = shade(ro + rd * res.z, rd, uv);
}
col *= mix(1.15, 1.4, uLight);
col = 1.0 - exp(-col);
gl_FragColor = vec4(col * alpha, alpha);
}
</script>
<script>
(() => {
const root = document.documentElement;
const orb = document.getElementById('orb');
const canvas = document.getElementById('lc');
const reduceMQ = matchMedia('(prefers-reduced-motion: reduce)');
const darkMQ = matchMedia('(prefers-color-scheme: dark)');
const isLight = () => {
const t = root.getAttribute('data-theme');
if (t === 'light') return true;
if (t === 'dark') return false;
return !darkMQ.matches;
};
// ---------- Animation cycle (seconds): liquid → crystallise → crystal → melt ----------
const LIQUID = 1.8, FORM = 3.2, SOLID = 2.6, MELT = 1.8;
const PERIOD = LIQUID + FORM + SOLID + MELT;
const FORMED = LIQUID + FORM;
const ease = x => x * x * (3 - 2 * x);
function orderAt(c) {
if (c < LIQUID) return 0;
c -= LIQUID;
if (c < FORM) return ease(c / FORM);
c -= FORM;
if (c < SOLID) return 1;
c -= SOLID;
return 1 - ease(Math.min(c / MELT, 1));
}
// ---------- WebGL setup ----------
let gl = null, prog = null, U = {};
try {
gl = canvas.getContext('webgl', { alpha: true, premultipliedAlpha: true, antialias: false, powerPreference: 'high-performance' });
} catch (e) { gl = null; }
function compile(type, src) {
const s = gl.createShader(type);
gl.shaderSource(s, src);
gl.compileShader(s);
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(s));
return null;
}
return s;
}
function initGL() {
if (!gl) return false;
const vs = compile(gl.VERTEX_SHADER, document.getElementById('vs').textContent);
const fs = compile(gl.FRAGMENT_SHADER, document.getElementById('fs').textContent);
if (!vs || !fs) return false;
prog = gl.createProgram();
gl.attachShader(prog, vs);
gl.attachShader(prog, fs);
gl.linkProgram(prog);
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
console.error(gl.getProgramInfoLog(prog));
return false;
}
gl.useProgram(prog);
const 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);
const loc = gl.getAttribLocation(prog, 'aPos');
gl.enableVertexAttribArray(loc);
gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
['uRes', 'uTime', 'uOrder', 'uTilt', 'uLight', 'uSweep', 'uGlint', 'uBlink'].forEach(n => U[n] = gl.getUniformLocation(prog, n));
gl.clearColor(0, 0, 0, 0);
return true;
}
let glOK = initGL();
if (!glOK) orb.classList.add('no-gl');
canvas.addEventListener('webglcontextlost', e => {
e.preventDefault();
glOK = false;
orb.classList.add('no-gl');
});
// ---------- Sizing with adaptive quality ----------
let scale = Math.min(window.devicePixelRatio || 1, 1.75);
function resize() {
const r = canvas.getBoundingClientRect();
const w = Math.max(1, Math.round(r.width * scale));
const h = Math.max(1, Math.round(r.height * scale));
if (canvas.width !== w || canvas.height !== h) {
canvas.width = w;
canvas.height = h;
}
}
if ('ResizeObserver' in window) new ResizeObserver(resize).observe(canvas);
window.addEventListener('resize', resize);
resize();
// ---------- The face turns to look at the pointer ----------
const tilt = { x: 0, y: 0 }, tiltT = { x: 0, y: 0 };
window.addEventListener('pointermove', e => {
tiltT.x = -(e.clientX / window.innerWidth - 0.5) * 1.1;
tiltT.y = (e.clientY / window.innerHeight - 0.5) * 0.7;
});
document.addEventListener('pointerleave', () => { tiltT.x = 0; tiltT.y = 0; });
// ---------- Loop ----------
let last = performance.now();
let clock = 0, shaderTime = 0, cycle = 0, light = isLight() ? 1 : 0;
let glintAt = -1;
let nextBlink = 1.4, blinkT = -1, doubleBlink = false;
const BLINK = 0.16, BLINK_GAP = 0.08;
let statSum = 0, statN = 0, warm = 0;
function frame(now) {
const rawDt = (now - last) / 1000;
const dt = Math.min(rawDt, 0.1);
last = now;
clock += dt;
const reduce = reduceMQ.matches;
const speed = reduce ? 0.3 : 1;
shaderTime += dt * speed;
const prev = cycle;
cycle = (cycle + dt * speed) % PERIOD;
if (prev < FORMED && cycle >= FORMED && !reduce) {
glintAt = now;
const target = glOK ? canvas : orb.querySelector('.blob');
if (target.animate) {
target.animate(
[{ transform: 'scale(1)' }, { transform: 'scale(1.04)', offset: 0.35 }, { transform: 'scale(1)' }],
{ duration: 800, easing: 'ease-out' }
);
}
}
const order = orderAt(cycle);
// blinking, now and then a double blink
if (blinkT < 0 && clock >= nextBlink) { blinkT = 0; doubleBlink = Math.random() < 0.25; }
let open = 1;
if (blinkT >= 0) {
blinkT += dt;
const total = doubleBlink ? BLINK * 2 + BLINK_GAP : BLINK;
let k = blinkT;
if (doubleBlink && k > BLINK + BLINK_GAP) k -= BLINK + BLINK_GAP;
if (k <= BLINK) open = 1 - Math.sin(Math.PI * k / BLINK);
if (blinkT >= total) { blinkT = -1; nextBlink = clock + 2.4 + Math.random() * 3.2; }
}
light += ((isLight() ? 1 : 0) - light) * (1 - Math.exp(-dt * 6));
tilt.x += (tiltT.x - tilt.x) * (1 - Math.exp(-dt * 3));
tilt.y += (tiltT.y - tilt.y) * (1 - Math.exp(-dt * 3));
let sweep = 0, glint = 0;
if (glintAt >= 0) {
const g = (now - glintAt) / 900;
if (g <= 1) { sweep = -1.4 + 2.8 * g; glint = Math.sin(Math.PI * g); }
else glintAt = -1;
}
orb.style.setProperty('--melt', (1 - order).toFixed(3));
if (glOK) {
gl.viewport(0, 0, canvas.width, canvas.height);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.uniform2f(U.uRes, canvas.width, canvas.height);
gl.uniform1f(U.uTime, shaderTime);
gl.uniform1f(U.uOrder, order);
gl.uniform2f(U.uTilt, tilt.x, 0.05 + tilt.y);
gl.uniform1f(U.uLight, light);
gl.uniform1f(U.uSweep, sweep);
gl.uniform1f(U.uGlint, glint);
gl.uniform1f(U.uBlink, open);
gl.drawArrays(gl.TRIANGLES, 0, 3);
// drop resolution on slower GPUs (skip the first second and tab-switch spikes)
warm += dt;
if (warm > 1 && rawDt < 0.09) {
statSum += rawDt; statN++;
if (statN >= 45) {
if (statSum / statN > 0.026 && scale > 0.6) {
scale = Math.max(0.6, scale * 0.82);
resize();
}
statSum = 0; statN = 0;
}
}
}
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
})();
</script>
</body>
</html>