Files
wehub-resource-sync 070959e133
landing-page-staging / Deploy landing page to staging (push) Has been skipped
landing-page-ci / Validate landing page (push) Failing after 4s
visual-baseline / Capture visual baselines (push) Has been cancelled
bake-plugin-previews / Bake plugin previews (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:00:47 +08:00

99 lines
5.5 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Halftone Drift — Open Design</title>
<!--
Self-contained WebGL2 hero. A domain-warped flow field is quantised through a
rotated halftone dot screen (variable dot radius by luminance) into a duotone
print aesthetic. The pointer bends the flow. No meshes, no textures, no build.
-->
<style>
:root{ color-scheme:dark; }
html,body{ margin:0; height:100%; overflow:hidden; background:#0b0a12;
font-family:'Albert Sans',ui-sans-serif,system-ui,-apple-system,sans-serif; color:#f6f1ff; }
#gl{ position:fixed; inset:0; width:100%; height:100%; display:block; }
.overlay{ position:fixed; inset:0; z-index:2; pointer-events:none; display:flex; flex-direction:column; justify-content:space-between; padding:5vw; }
.top{ display:flex; justify-content:space-between; font-size:clamp(11px,1vw,14px); letter-spacing:0.32em; text-transform:uppercase; color:rgba(246,241,255,0.72); }
.top .dot{ width:8px; height:8px; border-radius:50%; background:#ff7a45; box-shadow:0 0 14px #ff7a45; display:inline-block; margin-right:10px; vertical-align:middle; }
h1{ margin:0; font-size:clamp(44px,11vw,168px); line-height:0.86; font-weight:800; letter-spacing:-0.04em; mix-blend-mode:difference; }
h1 .thin{ font-weight:300; }
.bottom{ display:flex; justify-content:space-between; align-items:flex-end; gap:4vw; }
.sub{ font-size:clamp(14px,1.5vw,19px); max-width:42ch; color:rgba(246,241,255,0.82); line-height:1.45; margin:0; mix-blend-mode:difference; }
.badge{ font-size:11px; letter-spacing:0.28em; text-transform:uppercase; color:rgba(246,241,255,0.6); border:1px solid rgba(246,241,255,0.22); padding:9px 15px; border-radius:999px; backdrop-filter:blur(8px); white-space:nowrap; }
</style>
</head>
<body>
<canvas id="gl"></canvas>
<div class="overlay">
<div class="top"><span><span class="dot"></span>Open Design · WebGL Preview</span><span>Real-time · GPU</span></div>
<div class="bottom">
<div><h1>HALFTONE<br><span class="thin">DRIFT</span></h1>
<p class="sub">A flowing field screened through a rotated halftone dot grid — print-shop duotone, running live. Move the cursor to bend the drift.</p></div>
<div class="badge">Move the cursor ↗</div>
</div>
</div>
<script>
(() => {
const canvas=document.getElementById('gl');
const gl=canvas.getContext('webgl2',{antialias:true});
if (!gl) { document.body.innerHTML='<p style="color:#fff;padding:2rem;font-family:sans-serif">WebGL2 not available.</p>'; return; }
const VERT=`#version 300 es
void main(){ vec2 p=vec2((gl_VertexID<<1)&2, gl_VertexID&2); gl_Position=vec4(p*2.0-1.0,0.0,1.0); }`;
const FRAG=`#version 300 es
precision highp float;
out vec4 o;
uniform vec2 u_res; uniform float u_time; uniform vec2 u_mouse;
mat2 rot(float a){ float c=cos(a),s=sin(a); return mat2(c,-s,s,c); }
float hash(vec2 p){ return fract(sin(dot(p,vec2(41.3,289.1)))*43758.5453); }
float noise(vec2 p){ vec2 i=floor(p),f=fract(p); vec2 u=f*f*(3.0-2.0*f);
return mix(mix(hash(i),hash(i+vec2(1,0)),u.x),mix(hash(i+vec2(0,1)),hash(i+vec2(1,1)),u.x),u.y); }
float fbm(vec2 p){ float v=0.0,a=0.55; for(int i=0;i<5;i++){ v+=a*noise(p); p=rot(0.7)*p*1.9+7.0; a*=0.55; } return v; }
// duotone palette (ink → paper with a warm accent)
vec3 duo(float t){
vec3 ink=vec3(0.08,0.06,0.13), paper=vec3(0.98,0.93,0.86), accent=vec3(1.0,0.45,0.22);
vec3 c=mix(ink,accent,smoothstep(0.15,0.6,t));
return mix(c,paper,smoothstep(0.55,1.0,t));
}
void main(){
vec2 uv=(gl_FragCoord.xy-0.5*u_res)/u_res.y;
float t=u_time*0.05;
vec2 m=(u_mouse-0.5)*vec2(u_res.x/u_res.y,1.0);
vec2 pull=(m-uv); float grab=0.3/(dot(pull,pull)+0.3);
// flow field
vec2 q=vec2(fbm(uv*1.3+vec2(0.0,t)), fbm(uv*1.3+vec2(4.0,-t)));
float v=fbm(uv*1.3+2.4*q + grab*pull + t);
v=smoothstep(0.15,0.95,v);
// rotated halftone screen: dot radius grows with value
float cells=u_res.y/9.0;
vec2 sc=rot(0.4)*(gl_FragCoord.xy/u_res.y)*cells;
vec2 g=fract(sc)-0.5;
float dotr=sqrt(v)*0.72;
float dotm=smoothstep(dotr, dotr-0.09, length(g)); // 1 inside dot
vec3 col=mix(duo(v)*0.35, duo(v), dotm);
col+=(hash(gl_FragCoord.xy+u_time)-0.5)*0.02;
col*=0.7+0.5*smoothstep(1.4,0.1,length(uv));
o=vec4(pow(max(col,0.0),vec3(0.9)),1.0);
}`;
function sh(t,s){const x=gl.createShader(t);gl.shaderSource(x,s);gl.compileShader(x);if(!gl.getShaderParameter(x,gl.COMPILE_STATUS))throw gl.getShaderInfoLog(x);return x;}
const pr=gl.createProgram();gl.attachShader(pr,sh(gl.VERTEX_SHADER,VERT));gl.attachShader(pr,sh(gl.FRAGMENT_SHADER,FRAG));gl.linkProgram(pr);
if(!gl.getProgramParameter(pr,gl.LINK_STATUS))throw gl.getProgramInfoLog(pr);
gl.useProgram(pr);
const uRes=gl.getUniformLocation(pr,'u_res'),uTime=gl.getUniformLocation(pr,'u_time'),uMouse=gl.getUniformLocation(pr,'u_mouse');
let mouse=[0.5,0.5],target=[0.5,0.5];
addEventListener('pointermove',e=>{target=[e.clientX/innerWidth,1.0-e.clientY/innerHeight];});
function resize(){const d=Math.min(devicePixelRatio||1,2),w=innerWidth*d|0,h=innerHeight*d|0;if(w===canvas.width&&h===canvas.height)return;canvas.width=w;canvas.height=h;gl.viewport(0,0,w,h);}
addEventListener('resize',resize);resize();
const t0=performance.now();
function frame(now){ resize(); mouse[0]+=(target[0]-mouse[0])*0.06; mouse[1]+=(target[1]-mouse[1])*0.06;
gl.uniform2f(uRes,canvas.width,canvas.height); gl.uniform1f(uTime,(now-t0)/1000); gl.uniform2f(uMouse,mouse[0],mouse[1]);
gl.drawArrays(gl.TRIANGLES,0,3); requestAnimationFrame(frame); }
requestAnimationFrame(frame);
})();
</script>
</body>
</html>