diff --git a/prototyp-app.html b/prototyp-app.html index 6cd222b..51a37ed 100644 --- a/prototyp-app.html +++ b/prototyp-app.html @@ -255,6 +255,13 @@ .c-scroll::-webkit-scrollbar{display:none;} .c-scroll.kb-open{padding-bottom:380px;} .c-optlabel{font-size:11px; font-weight:700; letter-spacing:.12em; color:rgba(255,255,255,.4); margin-bottom:9px;} + /* Dithering-Shader-Chips (Testeffekt auf den Modell-Chips) */ + .chip.dither{position:relative; overflow:hidden; background:#000; color:#fff; + border:1px solid rgba(255,255,255,.14);} + .chip.dither.on{background:#000; color:#fff; border-color:rgba(255,255,255,.55);} + .chip.dither canvas{position:absolute; inset:0; width:100%; height:100%;} + .chip.dither > *:not(canvas){position:relative; z-index:1;} + .chip.dither span{text-shadow:0 1px 2px rgba(0,0,0,.7);} .c-behind{position:absolute; top:0; left:50%; border-radius:22px; background:#1b1b1b; border:1px solid rgba(255,255,255,.09); transform-origin:top center; z-index:0;} .c-preview{position:relative; border-radius:22px; overflow:hidden; z-index:1; @@ -948,6 +955,7 @@ function go(id){ document.querySelectorAll('.nav button').forEach(b=>b.classList.remove('on')); if(navMap[id]) document.getElementById(navMap[id]).classList.add('on'); document.body.classList.toggle('noscroll', id==='explore'||id==='create'); + if(id==='create') requestAnimationFrame(initCreateDither); window.scrollTo(0,0); } function pick(el){ @@ -1265,6 +1273,209 @@ document.getElementById('remakeBtn').onclick=function(){ },900); }; +/* ---------- Dithering-Shader (WebGL2-Port von designali-in/dithering-shader) ---------- + Testweise auf den Modell-Chips im Create-Screen: Sphere/Random, + ausgewaehlt = weiss/silber, nicht ausgewaehlt = dunkelgrau. */ +var DITHER_VERT='#version 300 es\nprecision mediump float;\nlayout(location = 0) in vec4 a_position;\nvoid main(){ gl_Position = a_position; }'; +var DITHER_FRAG=[ +'#version 300 es', +'precision mediump float;', +'uniform float u_time;', +'uniform vec2 u_resolution;', +'uniform vec4 u_colorBack;', +'uniform vec4 u_colorFront;', +'uniform float u_shape;', +'uniform float u_type;', +'uniform float u_pxSize;', +'out vec4 fragColor;', +'vec3 permute(vec3 x) { return mod(((x * 34.0) + 1.0) * x, 289.0); }', +'float snoise(vec2 v) {', +' const vec4 C = vec4(0.211324865405187, 0.366025403784439, -0.577350269189626, 0.024390243902439);', +' vec2 i = floor(v + dot(v, C.yy));', +' vec2 x0 = v - i + dot(i, C.xx);', +' vec2 i1;', +' i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);', +' vec4 x12 = x0.xyxy + C.xxzz;', +' x12.xy -= i1;', +' i = mod(i, 289.0);', +' vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0));', +' vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0);', +' m = m * m;', +' m = m * m;', +' vec3 x = 2.0 * fract(p * C.www) - 1.0;', +' vec3 h = abs(x) - 0.5;', +' vec3 ox = floor(x + 0.5);', +' vec3 a0 = x - ox;', +' m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h);', +' vec3 g;', +' g.x = a0.x * x0.x + h.x * x0.y;', +' g.yz = a0.yz * x12.xz + h.yz * x12.yw;', +' return 130.0 * dot(m, g);', +'}', +'#define TWO_PI 6.28318530718', +'#define PI 3.14159265358979323846', +'float hash11(float p) {', +' p = fract(p * 0.3183099) + 0.1;', +' p *= p + 19.19;', +' return fract(p * p);', +'}', +'float hash21(vec2 p) {', +' p = fract(p * vec2(0.3183099, 0.3678794)) + 0.1;', +' p += dot(p, p + 19.19);', +' return fract(p.x * p.y);', +'}', +'float getSimplexNoise(vec2 uv, float t) {', +' float noise = .5 * snoise(uv - vec2(0., .3 * t));', +' noise += .5 * snoise(2. * uv + vec2(0., .32 * t));', +' return noise;', +'}', +'const int bayer2x2[4] = int[4](0, 2, 3, 1);', +'const int bayer4x4[16] = int[16](0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5);', +'const int bayer8x8[64] = int[64](0, 32, 8, 40, 2, 34, 10, 42, 48, 16, 56, 24, 50, 18, 58, 26, 12, 44, 4, 36, 14, 46, 6, 38, 60, 28, 52, 20, 62, 30, 54, 22, 3, 35, 11, 43, 1, 33, 9, 41, 51, 19, 59, 27, 49, 17, 57, 25, 15, 47, 7, 39, 13, 45, 5, 37, 63, 31, 55, 23, 61, 29, 53, 21);', +'float getBayerValue(vec2 uv, int size) {', +' ivec2 pos = ivec2(mod(uv, float(size)));', +' int index = pos.y * size + pos.x;', +' if (size == 2) { return float(bayer2x2[index]) / 4.0; }', +' else if (size == 4) { return float(bayer4x4[index]) / 16.0; }', +' else if (size == 8) { return float(bayer8x8[index]) / 64.0; }', +' return 0.0;', +'}', +'void main() {', +' float t = .5 * u_time;', +' vec2 uv = gl_FragCoord.xy / u_resolution.xy;', +' uv -= .5;', +' float pxSize = u_pxSize;', +' vec2 pxSizeUv = gl_FragCoord.xy;', +' pxSizeUv -= .5 * u_resolution;', +' pxSizeUv /= pxSize;', +' vec2 pixelizedUv = floor(pxSizeUv) * pxSize / u_resolution.xy;', +' pixelizedUv += .5;', +' pixelizedUv -= .5;', +' vec2 shape_uv = pixelizedUv;', +' vec2 dithering_uv = pxSizeUv;', +' vec2 ditheringNoise_uv = uv * u_resolution;', +' float shape = 0.;', +' if (u_shape < 1.5) {', +' shape_uv *= .001;', +' shape = 0.5 + 0.5 * getSimplexNoise(shape_uv, t);', +' shape = smoothstep(0.3, 0.9, shape);', +' } else if (u_shape < 2.5) {', +' shape_uv *= .003;', +' for (float i = 1.0; i < 6.0; i++) {', +' shape_uv.x += 0.6 / i * cos(i * 2.5 * shape_uv.y + t);', +' shape_uv.y += 0.6 / i * cos(i * 1.5 * shape_uv.x + t);', +' }', +' shape = .15 / abs(sin(t - shape_uv.y - shape_uv.x));', +' shape = smoothstep(0.02, 1., shape);', +' } else if (u_shape < 3.5) {', +' shape_uv *= .05;', +' float stripeIdx = floor(2. * shape_uv.x / TWO_PI);', +' float rand = hash11(stripeIdx * 10.);', +' rand = sign(rand - .5) * pow(.1 + abs(rand), .4);', +' shape = sin(shape_uv.x) * cos(shape_uv.y - 5. * rand * t);', +' shape = pow(abs(shape), 6.);', +' } else if (u_shape < 4.5) {', +' shape_uv *= 4.;', +' float wave = cos(.5 * shape_uv.x - 2. * t) * sin(1.5 * shape_uv.x + t) * (.75 + .25 * cos(3. * t));', +' shape = 1. - smoothstep(-1., 1., shape_uv.y + wave);', +' } else if (u_shape < 5.5) {', +' float dist = length(shape_uv);', +' float waves = sin(pow(dist, 1.7) * 7. - 3. * t) * .5 + .5;', +' shape = waves;', +' } else if (u_shape < 6.5) {', +' float l = length(shape_uv);', +' float angle = 6. * atan(shape_uv.y, shape_uv.x) + 4. * t;', +' float twist = 1.2;', +' float offset = pow(l, -twist) + angle / TWO_PI;', +' float mid = smoothstep(0., 1., pow(l, twist));', +' shape = mix(0., fract(offset), mid);', +' } else {', +' shape_uv *= 2.;', +' float d = 1. - pow(length(shape_uv), 2.);', +' vec3 pos = vec3(shape_uv, sqrt(d));', +' vec3 lightPos = normalize(vec3(cos(1.5 * t), .8, sin(1.25 * t)));', +' shape = .5 + .5 * dot(lightPos, pos);', +' shape *= step(0., d);', +' }', +' int type = int(floor(u_type));', +' float dithering = 0.0;', +' switch (type) {', +' case 1: {', +' dithering = step(hash21(ditheringNoise_uv), shape);', +' } break;', +' case 2:', +' dithering = getBayerValue(dithering_uv, 2);', +' break;', +' case 3:', +' dithering = getBayerValue(dithering_uv, 4);', +' break;', +' default:', +' dithering = getBayerValue(dithering_uv, 8);', +' break;', +' }', +' dithering -= .5;', +' float res = step(.5, shape + dithering);', +' vec3 fgColor = u_colorFront.rgb * u_colorFront.a;', +' float fgOpacity = u_colorFront.a;', +' vec3 bgColor = u_colorBack.rgb * u_colorBack.a;', +' float bgOpacity = u_colorBack.a;', +' vec3 color = fgColor * res;', +' float opacity = fgOpacity * res;', +' color += bgColor * (1. - opacity);', +' opacity += bgOpacity * (1. - opacity);', +' fragColor = vec4(color, opacity);', +'}' +].join('\n'); +function ditherHexToRgba(hex){ + var m=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + if(!m) return [0,0,0,1]; + return [parseInt(m[1],16)/255, parseInt(m[2],16)/255, parseInt(m[3],16)/255, 1]; +} +function mountDither(canvas, opts){ + var gl=canvas.getContext('webgl2'); + if(!gl) return null; + function sh(type,src){ + var 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; + } + var vs=sh(gl.VERTEX_SHADER,DITHER_VERT), fs=sh(gl.FRAGMENT_SHADER,DITHER_FRAG); + if(!vs||!fs) return null; + var 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 null; } + var L={}; + ['u_time','u_resolution','u_colorBack','u_colorFront','u_shape','u_type','u_pxSize'].forEach(function(n){ L[n]=gl.getUniformLocation(prog,n); }); + var buf=gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER,buf); + gl.bufferData(gl.ARRAY_BUFFER,new Float32Array([-1,-1,1,-1,-1,1,-1,1,1,-1,1,1]),gl.STATIC_DRAW); + gl.enableVertexAttribArray(0); + gl.vertexAttribPointer(0,2,gl.FLOAT,false,0,0); + var dpr=window.devicePixelRatio||1; + var r=canvas.parentElement.getBoundingClientRect(); + canvas.width=Math.max(2,Math.round((r.width||120)*dpr)); + canvas.height=Math.max(2,Math.round((r.height||36)*dpr)); + gl.viewport(0,0,canvas.width,canvas.height); + var front=ditherHexToRgba(opts.front||'#ffffff'); + var back=ditherHexToRgba(opts.back||'#000000'); + var t0=Date.now(); + (function frame(){ + var t=(Date.now()-t0)*.001*(opts.speed||1.5); + gl.useProgram(prog); + gl.uniform1f(L.u_time,t); + gl.uniform2f(L.u_resolution,canvas.width,canvas.height); + gl.uniform4fv(L.u_colorBack,back); + gl.uniform4fv(L.u_colorFront,front); + gl.uniform1f(L.u_shape,opts.shape||7); + gl.uniform1f(L.u_type,opts.type||1); + gl.uniform1f(L.u_pxSize,opts.pxSize||2); + gl.drawArrays(gl.TRIANGLES,0,6); + requestAnimationFrame(frame); + })(); + return { setFront:function(hex){ front=ditherHexToRgba(hex); } }; +} + /* ---------- Create: Vorschau-Stack / Optionen / Prompt-Beam / Fake-Tastatur ---------- */ var C={model:0, count:1, format:'4:5', mediaType:'image', optOpen:false, kbOpen:false, generating:0}; var C_MODELS=[ @@ -1320,13 +1531,34 @@ function renderCreateOpts(){ pills('countRow',[{v:'1',label:'1 Bild'},{v:'3',label:'3er'},{v:'5',label:'5er'}],String(C.count),function(v){C.count=+v; renderCreateOpts(); renderCreateStack();}); } function toggleOpts(){ C.optOpen=!C.optOpen; renderCreateStack(); } +var DCHIPS={built:false, mounted:false, insts:[]}; +var DITHER_ON='#f2f2f2', DITHER_OFF='#555555'; function renderCreateModels(){ var host=document.getElementById('createModels'); - host.innerHTML=C_MODELS.map(function(m,i){ - return '
'+m.name+'
'; - }).join(''); - host.querySelectorAll('.chip').forEach(function(c){ - c.onclick=function(){ C.model=+c.dataset.i; renderCreateModels(); }; + if(!DCHIPS.built){ + host.innerHTML=C_MODELS.map(function(m,i){ + return '
'+m.name+'
'; + }).join(''); + host.querySelectorAll('.chip').forEach(function(c){ + c.onclick=function(){ C.model=+c.dataset.i; renderCreateModels(); }; + }); + DCHIPS.built=true; + } + host.querySelectorAll('.chip').forEach(function(c,i){ + var on=(i===C.model); + c.classList.toggle('on',on); + c.querySelector('.dot').style.background=on?'var(--accent)':'rgba(255,255,255,.3)'; + if(DCHIPS.insts[i]) DCHIPS.insts[i].setFront(on?DITHER_ON:DITHER_OFF); + }); +} +/* Shader erst mounten, wenn der Create-Screen sichtbar ist (sonst Groesse 0) */ +function initCreateDither(){ + if(DCHIPS.mounted) return; + var chips=document.querySelectorAll('#createModels .chip.dither'); + if(!chips.length) return; + DCHIPS.mounted=true; + chips.forEach(function(c,i){ + DCHIPS.insts[i]=mountDither(c.querySelector('canvas'), {front:(i===C.model)?DITHER_ON:DITHER_OFF, shape:7, type:1, pxSize:2, speed:1.5}); }); } /* Prompt-Editing über die Fake-Tastatur (Caret-genau wie im Design) */