diff --git a/IDEA.md b/IDEA.md new file mode 100644 index 0000000..7f114c8 --- /dev/null +++ b/IDEA.md @@ -0,0 +1 @@ +Мой сайт портфолио diff --git a/_fab_ds.png b/_fab_ds.png new file mode 100644 index 0000000..98a3963 Binary files /dev/null and b/_fab_ds.png differ diff --git a/_fab_lunacy.png b/_fab_lunacy.png new file mode 100644 index 0000000..65b3b80 Binary files /dev/null and b/_fab_lunacy.png differ diff --git a/_menu2.mjs b/_menu2.mjs new file mode 100644 index 0000000..ef7677c --- /dev/null +++ b/_menu2.mjs @@ -0,0 +1,18 @@ +import puppeteer from "puppeteer"; +const b = await puppeteer.launch({ args: ["--no-sandbox","--disable-dev-shm-usage"] }); +const p = await b.newPage(); +await p.setViewport({ width: 768, height: 900, deviceScaleFactor: 1 }); +await p.goto("http://localhost:4175/", { waitUntil: "networkidle0" }); +await p.evaluate(() => window.scrollTo(0, 400)); +await new Promise(r=>setTimeout(r,400)); +await p.evaluate(() => document.querySelector("[data-nav-more-btn]")?.click()); +await new Promise(r=>setTimeout(r,300)); +const m = await p.evaluate(() => { + const menu = document.querySelector("#nav-more-menu-floating"); + if (!menu || menu.hidden) return { hidden: true }; + const mr = menu.getBoundingClientRect(); + const vw = document.documentElement.clientWidth; + return { rightFromEdge: Math.round(vw - mr.right), edgePad: getComputedStyle(document.documentElement).getPropertyValue("--edge-pad").trim() }; +}); +console.log("MENU@768:", JSON.stringify(m)); +await b.close(); diff --git a/allsh.mjs b/allsh.mjs new file mode 100644 index 0000000..dd12e25 --- /dev/null +++ b/allsh.mjs @@ -0,0 +1,23 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +const errs=[]; p.on('pageerror',e=>errs.push(e.message)); p.on('console',m=>{if(m.type()==='error')errs.push(m.text());}); +await p.setViewport({width:1100,height:1500}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1500)); +await p.evaluate(()=>document.querySelector('#sec-all-shaders')?.scrollIntoView()); +await new Promise(r=>setTimeout(r,1500)); +const r = await p.evaluate(()=>{ + const cells=[...document.querySelectorAll('[data-shader-cell]')]; + const cellInfo=cells.map(c=>{const m=c.querySelector('.card-shader')?.paperShaderMount; return {key:c.dataset.shaderCell, mounted:!!m, speed:m?.currentSpeed, frame:m?Math.round(m.getCurrentFrame()):null};}); + const demo=document.querySelector('[data-shader-demo]'); + const demoMounted=!!demo.querySelector('.card-shader')?.paperShaderMount; + // переключим на spiral и проверим label + const btn=document.querySelector('[data-shader-switch="spiral"]'); + let labelAfter=null; + if(btn){ btn.click(); labelAfter=document.querySelector('[data-shader-label]')?.textContent; } + return { cellCount: cells.length, cellInfo, demoMounted, labelAfter }; +}); +console.log(JSON.stringify(r,null,1)); +console.log('ERRS', errs.slice(0,8)); +await b.close(); diff --git a/anim.mjs b/anim.mjs new file mode 100644 index 0000000..d26f97a --- /dev/null +++ b/anim.mjs @@ -0,0 +1,20 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1200)); +await p.evaluate(()=>document.querySelector('[data-shader-demo]').scrollIntoView({block:'center'})); +await new Promise(r=>setTimeout(r,600)); +const snap = ()=>p.evaluate(()=>{ + const cv=document.querySelector('[data-shader-demo] canvas'); + const gl=cv.getContext('webgl2',{preserveDrawingBuffer:true})||cv.getContext('webgl',{preserveDrawingBuffer:true}); + const a=new Uint8Array(4*9); let k=0; + for(const [x,y] of [[20,20],[100,80],[200,150],[50,180],[300,40]]){ gl.readPixels(x,y,1,1,gl.RGBA,gl.UNSIGNED_BYTE,a.subarray(k,k+4)); k+=4; } + return Array.from(a).join(','); +}); +const s1=await snap(); await new Promise(r=>setTimeout(r,700)); const s2=await snap(); +console.log('frame1:', s1); +console.log('frame2:', s2); +console.log('MOVING:', s1!==s2); +await b.close(); diff --git a/anim2.mjs b/anim2.mjs new file mode 100644 index 0000000..07b83ec --- /dev/null +++ b/anim2.mjs @@ -0,0 +1,18 @@ +import puppeteer from 'puppeteer'; +import sharp from 'sharp'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1200)); +await p.evaluate(()=>document.querySelector('[data-shader-demo]').scrollIntoView({block:'center'})); +await new Promise(r=>setTimeout(r,600)); +async function shot(){ const el=await p.$('[data-shader-demo]'); await el.screenshot({path:'/tmp/a.png'}); + const {data,info}=await sharp('/tmp/a.png').raw().toBuffer({resolveWithObject:true}); + return {w:info.width,h:info.height,data}; } +const s1=await shot(); await new Promise(r=>setTimeout(r,800)); const s2=await shot(); +// сравним центральные пиксели +function center(d,info){ const i=((info.height>>1)*info.width+(info.width>>1))*3; return [d[i],d[i+1],d[i+2]]; } +const c1=center(s1.data,s1), c2=center(s2.data,s2); +console.log('center px t1:', c1, ' t2:', c2, ' MOVED:', JSON.stringify(c1)!==JSON.stringify(c2)); +await b.close(); diff --git a/anim3.mjs b/anim3.mjs new file mode 100644 index 0000000..d7bbb2b --- /dev/null +++ b/anim3.mjs @@ -0,0 +1,22 @@ +import puppeteer from 'puppeteer'; +import sharp from 'sharp'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1200)); +const top = await p.evaluate(()=>{ const r=document.querySelector('[data-shader-demo]').getBoundingClientRect(); return r.top+window.scrollY; }); +await p.evaluate((t)=>window.scrollTo(0, t-150), top); +await new Promise(r=>setTimeout(r,700)); +async function region(){ await p.screenshot({path:'/tmp/r.png'}); + const rect = await p.evaluate(()=>{ const r=document.querySelector('[data-shader-demo]').getBoundingClientRect(); return {x:r.x,y:r.y,w:r.width,h:r.height}; }); + const {data,info}=await sharp('/tmp/r.png').raw().toBuffer({resolveWithObject:true}); + // хеш центральной полосы + const samples=[]; const y=Math.floor(rect.y+rect.h*0.4); + for(let x=Math.floor(rect.x+20); xsetTimeout(r,800)); const c=await region(); +console.log('moved:', a!==c); +console.log('t1 head:', a.slice(0,60)); console.log('t2 head:', c.slice(0,60)); +await b.close(); diff --git a/animlong.mjs b/animlong.mjs new file mode 100644 index 0000000..d8e68d8 --- /dev/null +++ b/animlong.mjs @@ -0,0 +1,22 @@ +import puppeteer from 'puppeteer'; +import sharp from 'sharp'; +import crypto from 'crypto'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1200)); +const top = await p.evaluate(()=>{ const r=document.querySelector('[data-shader-demo]').getBoundingClientRect(); return r.top+window.scrollY; }); +await p.evaluate((t)=>window.scrollTo(0, t-150), top); +await new Promise(r=>setTimeout(r,700)); +async function hash(){ await p.screenshot({path:'/tmp/rl.png'}); + const rect = await p.evaluate(()=>{ const r=document.querySelector('[data-shader-demo]').getBoundingClientRect(); return {x:r.x,y:r.y,w:r.width,h:r.height}; }); + const {data,info}=await sharp('/tmp/rl.png').raw().toBuffer({resolveWithObject:true}); + const x0=Math.floor(rect.x),y0=Math.floor(rect.y),x1=Math.floor(rect.x+rect.w),y1=Math.floor(rect.y+rect.h); + const buf=Buffer.alloc((x1-x0)*(y1-y0)*3); + let k=0; for(let y=y0;ysetTimeout(r,3000)); const c=await hash(); +console.log('hash1', a); console.log('hash2', c); console.log('ANIMATED:', a!==c); +await b.close(); diff --git a/basicshader.mjs b/basicshader.mjs new file mode 100644 index 0000000..8c7dc5c --- /dev/null +++ b/basicshader.mjs @@ -0,0 +1,26 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:800,height:600}); +await p.goto('about:blank',{waitUntil:'load'}); +await new Promise(r=>setTimeout(r,300)); +const r = await p.evaluate(()=>{ + const cv=document.createElement('canvas'); cv.width=64; cv.height=64; document.body.appendChild(cv); + const gl=cv.getContext('webgl2'); + if(!gl) return {webgl2:false}; + const vs=`#version 300 es\nin vec4 a_position; void main(){ gl_Position=a_position; }`; + const fs=`#version 300 es\nprecision mediump float; out vec4 o; void main(){ o=vec4(1.0,0.3,0.0,1.0); }`; + const mk=(s)=>{const sh=gl.createShader(s.includes('main(){ gl_Position')?gl.VERTEX_SHADER:gl.FRAGMENT_SHADER);gl.shaderSource(sh,s);gl.compileShader(sh);return {sh,ok:gl.getShaderParameter(sh,gl.COMPILE_STATUS),log:gl.getShaderInfoLog(sh)};}; + const v=mk(vs), f=mk(fs); + const prog=gl.createProgram(); gl.attachShader(prog,v.sh); gl.attachShader(prog,f.sh); gl.linkProgram(prog); + const linked=gl.getProgramParameter(prog,gl.LINK_STATUS); + gl.useProgram(prog); + const 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); + const loc=gl.getAttribLocation(prog,'a_position'); gl.enableVertexAttribArray(loc); gl.vertexAttribPointer(loc,2,gl.FLOAT,false,0,0); + gl.clearColor(0,0,0,1); gl.clear(gl.COLOR_BUFFER_BIT); + gl.drawArrays(gl.TRIANGLES,0,6); + const px=new Uint8Array(4); gl.readPixels(32,32,1,1,gl.RGBA,gl.UNSIGNED_BYTE,px); + return { webgl2:true, vsOk:v.ok, fsOk:f.ok, fsLog:f.log, linked, center:Array.from(px) }; +}); +console.log(JSON.stringify(r,null,1)); +await b.close(); diff --git a/bright.mjs b/bright.mjs new file mode 100644 index 0000000..b22f7f3 --- /dev/null +++ b/bright.mjs @@ -0,0 +1,18 @@ +import puppeteer from 'puppeteer'; +import sharp from 'sharp'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1200)); +const top = await p.evaluate(()=>{ const r=document.querySelector('[data-shader-demo]').getBoundingClientRect(); return r.top+window.scrollY; }); +await p.evaluate((t)=>window.scrollTo(0, t-150), top); +await new Promise(r=>setTimeout(r,800)); +await p.screenshot({path:'/tmp/sec.png'}); +const rect = await p.evaluate(()=>{ const r=document.querySelector('[data-shader-demo]').getBoundingClientRect(); return {x:r.x,y:r.y,w:r.width,h:r.height}; }); +const {data,info}=await sharp('/tmp/sec.png').raw().toBuffer({resolveWithObject:true}); +const x0=Math.floor(rect.x), y0=Math.floor(rect.y), x1=Math.floor(rect.x+rect.w), y1=Math.floor(rect.y+rect.h); +let maxR=0,maxG=0,maxB=0, brightPx=0, n=0; +for(let y=Math.max(0,y0);y250) brightPx++; n++; } +console.log('max channel values in demo card:', maxR, maxG, maxB, 'bright pixels:', brightPx, '/', n); +await b.close(); diff --git a/checkshadows.mjs b/checkshadows.mjs new file mode 100644 index 0000000..44aaf7a --- /dev/null +++ b/checkshadows.mjs @@ -0,0 +1,21 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +const errs=[]; p.on('pageerror',e=>errs.push(e.message)); p.on('console',m=>{if(m.type()==='error')errs.push(m.text());}); +await p.setViewport({width:1100,height:1500}); +await p.goto('http://10.200.201.3:4175/',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,800)); +const r = await p.evaluate(()=>{ + const pick = (sel)=>{ const el=document.querySelector(sel); if(!el) return null; + const cs=getComputedStyle(el); + return { boxShadow: cs.boxShadow.slice(0,60), shadowLen: cs.boxShadow.length, transform: cs.transform, bg: cs.backgroundImage.slice(0,40) }; }; + return { + mp: pick('.mp-card'), + portfolio: pick('.portfolio-card'), + skill: pick('.skill-task-card'), + theme: document.documentElement.getAttribute('data-theme'), + }; +}); +console.log(JSON.stringify(r,null,1)); +console.log('ERRS', errs.slice(0,6)); +await b.close(); diff --git a/chk2.mjs b/chk2.mjs new file mode 100644 index 0000000..a78b186 --- /dev/null +++ b/chk2.mjs @@ -0,0 +1,18 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader'] }); +const p = await b.newPage(); +const errs=[]; p.on('console',m=>{ if(m.type()==='error') errs.push(m.text()); }); +p.on('pageerror',e=>errs.push('PAGEERR '+e.message)); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +const cards = await p.$$eval('#sec-contact .contact-card', els => els.map(e=>{ + const cs = getComputedStyle(e); + const before = getComputedStyle(e,'::before'); + const img = e.querySelector('img'); + return { name:e.querySelector('.contact-card__name')?.textContent, + boxShadow: cs.boxShadow.slice(0,40), + beforeBg: before.backgroundImage.slice(0,60), + borderW: cs.borderTopWidth, + imgLoaded: img ? (img.complete && img.naturalWidth>0) : 'no-img' }; +})); +console.log(JSON.stringify({count:cards.length, cards, errs}, null, 1)); +await b.close(); diff --git a/chk3.mjs b/chk3.mjs new file mode 100644 index 0000000..3b841be --- /dev/null +++ b/chk3.mjs @@ -0,0 +1,13 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader'] }); +const p = await b.newPage(); +const errs=[]; p.on('console',m=>{ if(m.type()==='error') errs.push(m.text()); }); +p.on('pageerror',e=>errs.push('PAGEERR '+e.message)); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +// dark +const dark = await p.$$eval('#sec-contact .contact-card', els => els.map(e=>({n:e.querySelector('.contact-card__name')?.textContent, bs:e.style.boxShadow?'inline':'', shadow:getComputedStyle(e).boxShadow.length}))); +// light theme +await p.evaluate(()=>document.documentElement.setAttribute('data-theme','light')); +const light = await p.$$eval('#sec-contact .contact-card', els => els.map(e=>({n:e.querySelector('.contact-card__name')?.textContent, shadow:getComputedStyle(e).boxShadow.slice(0,30)}))); +console.log(JSON.stringify({darkCount:dark.length, light, errs}, null, 1)); +await b.close(); diff --git a/clean.mjs b/clean.mjs new file mode 100644 index 0000000..3facf85 --- /dev/null +++ b/clean.mjs @@ -0,0 +1,22 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +const errs=[]; p.on('pageerror',e=>errs.push(e.message)); p.on('console',m=>{if(m.type()==='error')errs.push(m.text());}); +await p.setViewport({width:1100,height:1400}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1500)); +const r = await p.evaluate(()=>{ + const shaderCards=[...document.querySelectorAll('.card-shader[data-shader]')]; + const mounted=shaderCards.map(w=>!!w.paperShaderMount); + return { + hasSecShader: !!document.querySelector('#sec-shader'), + hasSecShaderGrid: !!document.querySelector('#sec-shader-grid'), + hasSecContact: !!document.querySelector('#sec-contact'), + totalShaderWraps: shaderCards.length, + allMounted: mounted.every(Boolean), + mountedCount: mounted.filter(Boolean).length, + }; +}); +console.log(JSON.stringify(r,null,1)); +console.log('ERRS', errs.slice(0,5)); +await b.close(); diff --git a/cmp.mjs b/cmp.mjs new file mode 100644 index 0000000..39b8e6c --- /dev/null +++ b/cmp.mjs @@ -0,0 +1,23 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader'] }); +const p = await b.newPage(); +await p.setViewport({width:1280,height:1000}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); + +const probe = async (sel) => p.evaluate((s)=>{ + const e = document.querySelector(s); + const cs = getComputedStyle(e); + const bs = cs.boxShadow.split('),').map(x=>x.trim()); + return { + bg: cs.backgroundColor, + boxShadow: bs, + }; +}, sel); + +const neu = await probe('#sec-final .neu-card'); +const cc = await probe('#sec-contact .contact-card'); +console.log('=== NEU-CARD (Финал) ==='); +console.log(JSON.stringify(neu,null,1)); +console.log('=== CONTACT-CARD ==='); +console.log(JSON.stringify(cc,null,1)); +await b.close(); diff --git a/cmp2.mjs b/cmp2.mjs new file mode 100644 index 0000000..a7e8f12 --- /dev/null +++ b/cmp2.mjs @@ -0,0 +1,16 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader'] }); +const p = await b.newPage(); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +const probe = async (sel) => p.evaluate((s)=>{ + const e = document.querySelector(s); const cs=getComputedStyle(e); + return { bg: cs.backgroundColor, bgImg: cs.backgroundImage.slice(0,55), shadow: cs.boxShadow.split('),').map(x=>x.trim().replace(/rgba\([^)]*\)/,'C')) }; +}, sel); +console.log('DARK'); +console.log(' neu:', JSON.stringify(await probe('#sec-final .neu-card'))); +console.log(' cc :', JSON.stringify(await probe('#sec-contact .contact-card'))); +await p.evaluate(()=>document.documentElement.setAttribute('data-theme','light')); +console.log('LIGHT'); +console.log(' neu:', JSON.stringify(await probe('#sec-final .neu-card'))); +console.log(' cc :', JSON.stringify(await probe('#sec-contact .contact-card'))); +await b.close(); diff --git a/cmp3.mjs b/cmp3.mjs new file mode 100644 index 0000000..cfbc9df --- /dev/null +++ b/cmp3.mjs @@ -0,0 +1,11 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader'] }); +const p = await b.newPage(); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +const probe = async (sel) => p.evaluate((s)=>{ + const e = document.querySelector(s); const cs=getComputedStyle(e); + return { bgImg: cs.backgroundImage.slice(0,60) }; +}, sel); +console.log('DARK neu bgImg:', (await probe('#sec-final .neu-card')).bgImg); +console.log('DARK cc bgImg:', (await probe('#sec-contact .contact-card')).bgImg); +await b.close(); diff --git a/cmpf.mjs b/cmpf.mjs new file mode 100644 index 0000000..7aa2351 --- /dev/null +++ b/cmpf.mjs @@ -0,0 +1,19 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader'] }); +const p = await b.newPage(); +const errs=[]; p.on('console',m=>{if(m.type()==='error')errs.push(m.text())}); p.on('pageerror',e=>errs.push('PE '+e.message)); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +const probe = async (sel) => p.evaluate((s)=>{ + const e=document.querySelector(s); const cs=getComputedStyle(e); + return { bg: cs.backgroundColor, bgImg: cs.backgroundImage.slice(0,45), shadowLen: cs.boxShadow.length, + dir: cs.flexDirection, pad: cs.paddingTop }; +}, sel); +const neu = await probe('#sec-final .neu-card'); +const cc = await probe('#sec-contact .contact-card'); +const neuShadow = await p.evaluate(()=>getComputedStyle(document.querySelector('#sec-final .neu-card')).boxShadow); +const ccShadow = await p.evaluate(()=>getComputedStyle(document.querySelector('#sec-contact .contact-card')).boxShadow); +console.log('neu:', JSON.stringify(neu)); +console.log('cc :', JSON.stringify(cc)); +console.log('shadows equal:', neuShadow===ccShadow); +console.log('errs:', errs); +await b.close(); diff --git a/console.mjs b/console.mjs new file mode 100644 index 0000000..e544346 --- /dev/null +++ b/console.mjs @@ -0,0 +1,22 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +const logs=[]; +p.on('console', m=>logs.push('['+m.type()+'] '+m.text())); +p.on('pageerror', e=>logs.push('[pageerror] '+e.message)); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,800)); +const r = await p.evaluate(async ()=>{ + const mod = await import('/vendor/paper-shaders/index.js'); + const { ShaderMount, meshGradientFragmentShader, getShaderColorFromString } = mod; + const host = document.createElement('div'); host.style.cssText='width:300px;height:200px;position:relative'; + document.body.appendChild(host); + const colors = ['#7c5cff','#00c2c7','#ff5c8a','#ffb347'].map(getShaderColorFromString); + const uniforms = { u_colors: colors, u_colorsCount: colors.length, u_distortion:1.2, u_swirl:0.8, u_grainMixer:0.08, u_grainOverlay:0.04 }; + const m = new ShaderMount(host, meshGradientFragmentShader, uniforms, { preserveDrawingBuffer:true, antialias:true }, 1.0); + return { keys: Object.keys(m.uniformLocations), uColorsLoc: m.uniformLocations.u_colors, uColorsCountLoc: m.uniformLocations.u_colorsCount }; +}); +console.log('RESULT', JSON.stringify(r)); +console.log('LOGS:\n'+logs.join('\n')); +await b.close(); diff --git a/correct.mjs b/correct.mjs new file mode 100644 index 0000000..83adb3f --- /dev/null +++ b/correct.mjs @@ -0,0 +1,32 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,800)); +const r = await p.evaluate(async ()=>{ + const mod = await import('/vendor/paper-shaders/index.js'); + const { ShaderMount, meshGradientFragmentShader, getShaderColorFromString } = mod; + const host = document.createElement('div'); host.style.cssText='width:300px;height:200px;position:relative'; + document.body.appendChild(host); + const colors = ['#7c5cff','#00c2c7','#ff5c8a','#ffb347'].map(getShaderColorFromString); + const m = new ShaderMount(host, meshGradientFragmentShader, { u_colors: colors, u_colorsCount: colors.length, u_distortion:1.2, u_swirl:0.8, u_grainMixer:0.08, u_grainOverlay:0.04 }, { preserveDrawingBuffer:true, antialias:true }, 1.0); + const gl=m.gl, pr=m.program; + // правильное чтение: getUniform ВОЗВРАЩАЕТ значение + const gf=(n)=>gl.getUniform(pr, gl.getUniformLocation(pr,n)); + const colorsWritten = gf('u_colors[0]'); // Float32Array(4) + const countWritten = gf('u_colorsCount'); // number + const swirlWritten = gf('u_swirl'); + // readPixels скан + m.render(performance.now()); m.render(performance.now()); + const W=m.canvasElement.width,H=m.canvasElement.height; const buf=new Uint8Array(W*H*4); gl.readPixels(0,0,W,H,gl.RGBA,gl.UNSIGNED_BYTE,buf); + let bright=0,maxc=0; for(let i=0;i120)bright++; maxc=Math.max(maxc,mx);} + return { + u_colors0: colorsWritten ? Array.from(colorsWritten).map(x=>+x.toFixed(2)) : null, + u_colorsCount: countWritten, + u_swirl: swirlWritten, + brightPixels: bright, maxChannel: maxc, + }; +}); +console.log(JSON.stringify(r,null,1)); +await b.close(); diff --git a/ctor.mjs b/ctor.mjs new file mode 100644 index 0000000..05abcc2 --- /dev/null +++ b/ctor.mjs @@ -0,0 +1,25 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,800)); +const r = await p.evaluate(async ()=>{ + const mod = await import('/vendor/paper-shaders/index.js'); + const { ShaderMount, meshGradientFragmentShader, getShaderColorFromString } = mod; + const host = document.createElement('div'); host.style.cssText='width:300px;height:200px;position:relative'; + document.body.appendChild(host); + const colors = ['#7c5cff','#00c2c7','#ff5c8a','#ffb347'].map(getShaderColorFromString); + const uniforms = { u_colors: colors, u_colorsCount: colors.length, u_distortion:1.2, u_swirl:0.8, u_grainMixer:0.08, u_grainOverlay:0.04 }; + console.log('uniforms passed:', JSON.stringify(uniforms.u_colors), 'count', uniforms.u_colorsCount); + const m = new ShaderMount(host, meshGradientFragmentShader, uniforms, { preserveDrawingBuffer:true, antialias:true }, 1.0); + m.render(performance.now()); m.render(performance.now()); + const gl=m.gl; + const cnt=new Float32Array(1); gl.getUniform(m.program, gl.getUniformLocation(m.program,'u_colorsCount'), cnt); + const c0=new Float32Array(4); gl.getUniform(m.program, gl.getUniformLocation(m.program,'u_colors[0]'), c0); + const W=m.canvasElement.width,H=m.canvasElement.height; const buf=new Uint8Array(W*H*4); gl.readPixels(0,0,W,H,gl.RGBA,gl.UNSIGNED_BYTE,buf); + let bright=0,maxc=0; for(let i=0;i120)bright++; maxc=Math.max(maxc,mx);} + return { count:cnt[0], c0:Array.from(c0).map(x=>+x.toFixed(2)), bright, maxc }; +}); +console.log(JSON.stringify(r,null,1)); +await b.close(); diff --git a/demochk.mjs b/demochk.mjs new file mode 100644 index 0000000..51c17a5 --- /dev/null +++ b/demochk.mjs @@ -0,0 +1,24 @@ +import puppeteer from 'puppeteer'; +import sharp from 'sharp'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1280,height:1200}); +const errs=[]; p.on('console',m=>{if(m.type()==='error')errs.push(m.text())}); p.on('pageerror',e=>errs.push('PE '+e.message)); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1200)); +const demo = await p.$('[data-shader-demo]'); +await demo.evaluate(el=>el.scrollIntoView({block:'center'})); +await new Promise(r=>setTimeout(r,400)); +async function probe(key){ + if(key){ await p.click(`[data-shader-switch="${key}"]`); await new Promise(r=>setTimeout(r,700)); } + const el = await p.$('[data-shader-demo]'); + const box = await el.boundingBox(); + await el.screenshot({path:'/tmp/demo.png'}); + const {data,info}=await sharp('/tmp/demo.png').raw().toBuffer({resolveWithObject:true}); + let n=info.width*info.height, rs=0,gs=0,bs=0; const cols=new Set(); + for(let i=0;i>5},${data[i+1]>>5},${data[i+2]>>5}`); } + return { key: key||'mesh', avg:[Math.round(rs/n),Math.round(gs/n),Math.round(bs/n)], distinct: cols.size }; +} +const r1=await probe(); const r2=await probe('grain'); const r3=await probe('voronoi'); const r4=await probe('staticMesh'); +console.log(JSON.stringify({r1,r2,r3,r4,errs},null,1)); +await b.close(); diff --git a/democrop.mjs b/democrop.mjs new file mode 100644 index 0000000..2485ec6 --- /dev/null +++ b/democrop.mjs @@ -0,0 +1,21 @@ +import puppeteer from 'puppeteer'; +import sharp from 'sharp'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1500)); +await p.evaluate(()=>document.querySelector('#sec-shader').scrollIntoView()); +await new Promise(r=>setTimeout(r,900)); +// координаты демо-карточки +const rect = await p.evaluate(()=>{ const r=document.querySelector('[data-shader-demo]').getBoundingClientRect(); return {x:r.x,y:r.y,w:r.width,h:r.height}; }); +await p.screenshot({path:'/tmp/full.png'}); +const img = sharp('/tmp/full.png'); +const {data,info}=await img.raw().toBuffer({resolveWithObject:true}); +// вырежем центр демо-карточки (по координатам вьюпорта) +const x0=Math.floor(rect.x+rect.w*0.2), y0=Math.floor(rect.y+rect.h*0.2); +const x1=Math.floor(rect.x+rect.w*0.8), y1=Math.floor(rect.y+rect.h*0.8); +let rs=0,gs=0,bs=0,n=0; const cols=new Set(); +for(let y=y0;y>5},${data[i+1]>>5},${data[i+2]>>5}`); n++; } } +console.log('demo card region avg:', [Math.round(rs/n),Math.round(gs/n),Math.round(bs/n)], 'distinct coarse colors:', cols.size); +await b.close(); diff --git a/demodata.mjs b/demodata.mjs new file mode 100644 index 0000000..2c14e87 --- /dev/null +++ b/demodata.mjs @@ -0,0 +1,34 @@ +import puppeteer from 'puppeteer'; +import sharp from 'sharp'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1200)); +await p.evaluate(()=>document.querySelector('#sec-shader').scrollIntoView()); +await new Promise(r=>setTimeout(r,600)); +async function probe(key){ + if(key){ await p.click(`[data-shader-switch="${key}"]`); await new Promise(r=>setTimeout(r,900)); } + // снимок canvas напрямую через toDataURL -> png + const dataUrl = await p.evaluate(()=>{ + const cv=document.querySelector('[data-shader-demo] canvas'); + if(!cv) return null; + try { return cv.toDataURL('image/png').slice(0,30); } catch(e){ return 'ERR '+e.message; } + }); + const buf = await p.evaluate(()=>{ + const cv=document.querySelector('[data-shader-demo] canvas'); + return cv ? cv.toDataURL('image/png') : null; + }); + let distinct=0, avg=[0,0,0]; + if(buf){ const b64=buf.split(',')[1]; const {data,info}=await sharp(Buffer.from(b64,'base64')).raw().toBuffer({resolveWithObject:true}); + let n=info.width*info.height,rs=0,gs=0,bs=0; const cols=new Set(); + for(let i=0;i>5},${data[i+1]>>5},${data[i+2]>>5}`); } + avg=[Math.round(rs/n),Math.round(gs/n),Math.round(bs/n)]; distinct=cols.size; } + return { key:key||'mesh', dataUrlHead:dataUrl, avg, distinct }; +} +console.log(JSON.stringify(await probe(),null,0)); +console.log(JSON.stringify(await probe('grain'),null,0)); +console.log(JSON.stringify(await probe('voronoi'),null,0)); +console.log(JSON.stringify(await probe('staticMesh'),null,0)); +await b.close(); diff --git a/demofull.mjs b/demofull.mjs new file mode 100644 index 0000000..36f5522 --- /dev/null +++ b/demofull.mjs @@ -0,0 +1,20 @@ +import puppeteer from 'puppeteer'; +import sharp from 'sharp'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1200)); +// прокрутим ровно к карточке через window.scrollTo по абсолютному смещению +const top = await p.evaluate(()=>{ const r=document.querySelector('[data-shader-demo]').getBoundingClientRect(); return r.top+window.scrollY; }); +await p.evaluate((t)=>window.scrollTo(0, t-200), top); +await new Promise(r=>setTimeout(r,900)); +await p.screenshot({path:'/tmp/full2.png'}); +const {data,info}=await sharp('/tmp/full2.png').raw().toBuffer({resolveWithObject:true}); +const rect = await p.evaluate(()=>{ const r=document.querySelector('[data-shader-demo]').getBoundingClientRect(); return {x:r.x,y:r.y,w:r.width,h:r.height}; }); +const x0=Math.max(0,Math.floor(rect.x+20)), y0=Math.max(0,Math.floor(rect.y+20)); +const x1=Math.min(info.width,Math.floor(rect.x+rect.w-20)), y1=Math.min(info.height,Math.floor(rect.y+rect.h-20)); +let rs=0,gs=0,bs=0,n=0; const cols=new Set(); +if(x1>x0&&y1>y0){ for(let y=y0;y>5},${data[i+1]>>5},${data[i+2]>>5}`); n++; } } } +console.log('region avg', n?[Math.round(rs/n),Math.round(gs/n),Math.round(bs/n)]:'empty', 'distinct', cols.size, 'rect', rect, 'imgsize', info.width+'x'+info.height); +await b.close(); diff --git a/demolast.mjs b/demolast.mjs new file mode 100644 index 0000000..3745aab --- /dev/null +++ b/demolast.mjs @@ -0,0 +1,21 @@ +import puppeteer from 'puppeteer'; +import sharp from 'sharp'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1500)); +async function probe(key){ + if(key){ await p.click(`[data-shader-switch="${key}"]`); await new Promise(r=>setTimeout(r,1000)); } + const el = await p.$('[data-shader-demo]'); + await el.screenshot({path:'/tmp/demo.png'}); + const {data,info}=await sharp('/tmp/demo.png').raw().toBuffer({resolveWithObject:true}); + let n=info.width*info.height,rs=0,gs=0,bs=0; const cols=new Set(); + for(let i=0;i>5},${data[i+1]>>5},${data[i+2]>>5}`); } + return { key:key||'mesh', avg:[Math.round(rs/n),Math.round(gs/n),Math.round(bs/n)], distinct:cols.size }; +} +console.log(JSON.stringify(await probe())); +console.log(JSON.stringify(await probe('grain'))); +console.log(JSON.stringify(await probe('voronoi'))); +console.log(JSON.stringify(await probe('staticMesh'))); +await b.close(); diff --git a/demook.mjs b/demook.mjs new file mode 100644 index 0000000..20a0423 --- /dev/null +++ b/demook.mjs @@ -0,0 +1,26 @@ +import puppeteer from 'puppeteer'; +import sharp from 'sharp'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +const errs=[]; p.on('console',m=>{if(m.type()==='error')errs.push(m.text())}); p.on('pageerror',e=>errs.push('PE '+e.message)); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1200)); +await p.evaluate(()=>document.querySelector('#sec-shader').scrollIntoView()); +await new Promise(r=>setTimeout(r,700)); +async function probe(key){ + if(key){ await p.click(`[data-shader-switch="${key}"]`); await new Promise(r=>setTimeout(r,900)); } + const buf = await p.evaluate(()=>{ const cv=document.querySelector('[data-shader-demo] canvas'); return cv?cv.toDataURL('image/png'):null; }); + let avg=[0,0,0], distinct=0; + if(buf){ const {data,info}=await sharp(Buffer.from(buf.split(',')[1],'base64')).raw().toBuffer({resolveWithObject:true}); + let n=info.width*info.height,rs=0,gs=0,bs=0; const cols=new Set(); + for(let i=0;i>5},${data[i+1]>>5},${data[i+2]>>5}`); } + avg=[Math.round(rs/n),Math.round(gs/n),Math.round(bs/n)]; distinct=cols.size; } + return { key:key||'mesh', avg, distinct, hasCanvas:!!buf }; +} +console.log(JSON.stringify(await probe(),null,0)); +console.log(JSON.stringify(await probe('grain'),null,0)); +console.log(JSON.stringify(await probe('voronoi'),null,0)); +console.log(JSON.stringify(await probe('staticMesh'),null,0)); +console.log('errs:', errs); +await b.close(); diff --git a/demopic.mjs b/demopic.mjs new file mode 100644 index 0000000..276700a --- /dev/null +++ b/demopic.mjs @@ -0,0 +1,11 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300,deviceScaleFactor:1}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1500)); +await p.evaluate(()=>document.querySelector('#sec-shader').scrollIntoView()); +await new Promise(r=>setTimeout(r,800)); +await p.screenshot({path:'/tmp/sec-shader.png'}); +console.log('saved'); +await b.close(); diff --git a/demovis.mjs b/demovis.mjs new file mode 100644 index 0000000..573a639 --- /dev/null +++ b/demovis.mjs @@ -0,0 +1,28 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1280,height:1200}); +const errs=[]; p.on('console',m=>{if(m.type()==='error')errs.push(m.text())}); p.on('pageerror',e=>errs.push('PE '+e.message)); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1000)); +await p.evaluate(()=>document.querySelector('[data-shader-demo]').scrollIntoView({block:'center'})); +await new Promise(r=>setTimeout(r,500)); +async function probe(key){ + if(key){ await p.click(`[data-shader-switch="${key}"]`); await new Promise(r=>setTimeout(r,800)); } + return await p.evaluate(()=>{ + const el=document.querySelector('[data-shader-demo]'); + const cv=el.querySelector('canvas'); + if(!cv) return {canvas:false}; + const gl=cv.getContext('webgl2')||cv.getContext('webgl'); + const px=new Uint8Array(4); + // читаем угол (0,0) — там обычно цвет градиента + gl.readPixels(2,2,1,1,gl.RGBA,gl.UNSIGNED_BYTE,px); + return { canvas:true, w:cv.width, h:cv.height, corner:Array.from(px), key: el.dataset.shaderKey }; + }); +} +console.log('mesh:', JSON.stringify(await probe())); +console.log('grain:', JSON.stringify(await probe('grain'))); +console.log('voronoi:', JSON.stringify(await probe('voronoi'))); +console.log('staticMesh:', JSON.stringify(await probe('staticMesh'))); +console.log('errs:', errs); +await b.close(); diff --git a/diag.mjs b/diag.mjs new file mode 100644 index 0000000..ac131bb --- /dev/null +++ b/diag.mjs @@ -0,0 +1,30 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +const errs=[]; p.on('console',m=>{ if(m.type()==='error'||m.type()==='warning') errs.push(m.type()+': '+m.text()); }); +p.on('pageerror',e=>errs.push('PAGEERR: '+e.message)); +p.on('requestfailed',r=>errs.push('REQFAIL: '+r.url()+' '+r.failure()?.errorText)); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1500)); +const diag = await p.evaluate(()=>{ + const sec=document.querySelector('#sec-shader'); + const demo=document.querySelector('[data-shader-demo]'); + const canvas=document.querySelector('#sec-shader canvas'); + const sw=document.querySelectorAll('[data-shader-switch]').length; + return { + secExists: !!sec, + secH: sec?Math.round(sec.getBoundingClientRect().height):0, + demoExists: !!demo, + demoH: demo?Math.round(demo.getBoundingClientRect().height):0, + canvasCount: document.querySelectorAll('#sec-shader canvas').length, + swCount: sw, + swVisible: sw?getComputedStyle(document.querySelector('[data-shader-switch]')).display:'n/a', + }; +}); +// проверим доступен ли модуль +const modOk = await p.evaluate(async()=>{ + try { const m = await import('/vendor/paper-shaders/index.js'); return 'OK keys:'+Object.keys(m).length; } + catch(e){ return 'IMPORT ERR: '+e.message; } +}); +console.log(JSON.stringify({diag, modOk, errs: errs.slice(0,10)}, null, 1)); +await b.close(); diff --git a/doccheck.mjs b/doccheck.mjs new file mode 100644 index 0000000..932d6c2 --- /dev/null +++ b/doccheck.mjs @@ -0,0 +1,21 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1200)); +const r = await p.evaluate(()=>{ + const m=document.querySelector('[data-shader-demo] .card-shader').paperShaderMount; + const gl=m.gl; + // два render (drawArrays инициализируется лениво на 1-м) + m.render(performance.now()); m.render(performance.now()); + const W=m.canvasElement.width, H=m.canvasElement.height; + const buf=new Uint8Array(W*H*4); gl.readPixels(0,0,W,H,gl.RGBA,gl.UNSIGNED_BYTE,buf); + let bright=0, maxc=0; + for(let i=0;i120) bright++; maxc=Math.max(maxc,mx); } + const cntLoc=gl.getUniformLocation(m.program,'u_colorsCount'); const cv=new Float32Array(1); gl.getUniform(m.program,cntLoc,cv); + const c0=new Float32Array(4); gl.getUniform(m.program,gl.getUniformLocation(m.program,'u_colors[0]'),c0); + return { brightPixels: bright, maxChannel: maxc, uColorsCountFloat: cv[0], uColor0: Array.from(c0).map(x=>+x.toFixed(2)) }; +}); +console.log(JSON.stringify(r,null,1)); +await b.close(); diff --git a/final.mjs b/final.mjs new file mode 100644 index 0000000..3616ac3 --- /dev/null +++ b/final.mjs @@ -0,0 +1,16 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +const errs=[]; +p.on('pageerror', e=>errs.push('PE '+e.message)); +p.on('console', m=>{ if(m.type()==='error') errs.push('CE '+m.text()); }); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1200)); +const r = await p.evaluate(()=>{ + const m=document.querySelector('[data-shader-demo] .card-shader')?.paperShaderMount; + return { hasMount: !!m, speed: m?.currentSpeed, isVp: m?.isInViewport }; +}); +console.log('MOUNT', JSON.stringify(r)); +console.log('ERRS', errs.slice(0,5)); +await b.close(); diff --git a/final2.mjs b/final2.mjs new file mode 100644 index 0000000..537d022 --- /dev/null +++ b/final2.mjs @@ -0,0 +1,16 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +const errs=[]; +p.on('pageerror', e=>errs.push('PE '+e.message)); +p.on('console', m=>{ if(m.type()==='error') errs.push('CE '+m.text()); }); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1000)); +const a = await p.evaluate(()=>{ const m=document.querySelector('[data-shader-demo] .card-shader')?.paperShaderMount; return m?{speed:m.currentSpeed,frame:m.getCurrentFrame(),inVp:m.isInViewport}:null; }); +await new Promise(r=>setTimeout(r,2000)); +const c = await p.evaluate(()=>{ const m=document.querySelector('[data-shader-demo] .card-shader')?.paperShaderMount; return m?{speed:m.currentSpeed,frame:m.getCurrentFrame(),inVp:m.isInViewport}:null; }); +console.log('A',JSON.stringify(a),'C',JSON.stringify(c)); +console.log('ANIMATING:', c && a && c.frame>a.frame && c.speed>0); +console.log('ERRS', errs.slice(0,5)); +await b.close(); diff --git a/fixcheck.mjs b/fixcheck.mjs new file mode 100644 index 0000000..beb7677 --- /dev/null +++ b/fixcheck.mjs @@ -0,0 +1,12 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,800)); // просто ждём, НЕ скроллим к секции +const a = await p.evaluate(()=>{ const m=document.querySelector('[data-shader-demo] .card-shader')?.paperShaderMount; return m?{frame:m.getCurrentFrame(),speed:m.currentSpeed,inVp:m.isInViewport}:null; }); +await new Promise(r=>setTimeout(r,2000)); +const c = await p.evaluate(()=>{ const m=document.querySelector('[data-shader-demo] .card-shader')?.paperShaderMount; return m?{frame:m.getCurrentFrame(),speed:m.currentSpeed,inVp:m.isInViewport}:null; }); +console.log('A',JSON.stringify(a)); console.log('C',JSON.stringify(c)); +console.log('ANIMATING:', !!c && c.frame>a.frame && c.speed>0); +await b.close(); diff --git a/frametest.mjs b/frametest.mjs new file mode 100644 index 0000000..5d1659f --- /dev/null +++ b/frametest.mjs @@ -0,0 +1,20 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1200)); +await p.evaluate(()=>document.querySelector('[data-shader-demo]').scrollIntoView({block:'center'})); +await new Promise(r=>setTimeout(r,500)); +// достанем mount через глобальный хук: ShaderMount сохраняет себя в parentElement.paperShaderMount +const r = await p.evaluate(async()=>{ + const wrap=document.querySelector('[data-shader-demo] .card-shader'); + const m=wrap.paperShaderMount; + if(!m) return {hasMount:false}; + const f1=m.getCurrentFrame(); + await new Promise(r=>setTimeout(r,600)); + const f2=m.getCurrentFrame(); + return { hasMount:true, frame1:f1, frame2:f2, rafRunning: f2>f1, speed: m.speed, inViewport: m.isInViewport, currentSpeed: m.currentSpeed }; +}); +console.log(JSON.stringify(r,null,1)); +await b.close(); diff --git a/glerr.mjs b/glerr.mjs new file mode 100644 index 0000000..a134c0d --- /dev/null +++ b/glerr.mjs @@ -0,0 +1,19 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,800)); +const r = await p.evaluate(()=>{ + const m=document.querySelector('[data-shader-demo] .card-shader').paperShaderMount; + const gl=m.gl; + const errs=[]; + // проверим статус программы и шейдеров + const vs = gl.getProgramParameter(m.program, gl.LINK_STATUS); + // проверим, залит ли canvas хоть чем-то: clearColor потом readPixels + gl.clearColor(1,0,0,1); gl.clear(gl.COLOR_BUFFER_BIT); + const px=new Uint8Array(4); gl.readPixels(10,10,1,1,gl.RGBA,gl.UNSIGNED_BYTE,px); + return { linkStatus: vs, afterClearRed: Array.from(px), glError: gl.getError() }; +}); +console.log(JSON.stringify(r,null,1)); +await b.close(); diff --git a/glerr2.mjs b/glerr2.mjs new file mode 100644 index 0000000..64431aa --- /dev/null +++ b/glerr2.mjs @@ -0,0 +1,19 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1200)); +const r = await p.evaluate(()=>{ + const m=document.querySelector('[data-shader-demo] .card-shader').paperShaderMount; + const gl=m.gl; + gl.getError(); // сброс + m.render(performance.now()); + const e = gl.getError(); + const px=new Uint8Array(4); gl.readPixels(360,220,1,1,gl.RGBA,gl.UNSIGNED_BYTE,px); + // проверим a_position location + const aLoc = gl.getAttribLocation(m.program, 'a_position'); + return { glErrorAfterRender: e, centerPx: Array.from(px), aPosLoc: aLoc, clearC: (()=>{const c=new Float32Array(4); gl.getClearColor?.(c); return Array.from(c);})() }; +}); +console.log(JSON.stringify(r,null,1)); +await b.close(); diff --git a/grid.mjs b/grid.mjs new file mode 100644 index 0000000..caf20d3 --- /dev/null +++ b/grid.mjs @@ -0,0 +1,24 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1200)); +const r = await p.evaluate(()=>{ + const m=document.querySelector('[data-shader-demo] .card-shader').paperShaderMount; + const gl=m.gl; + const flat=[0.486,0.361,1,1, 0,0.761,0.78,1, 1,0.361,0.541,1, 1,0.702,0.278,1]; + gl.useProgram(m.program); + gl.uniform4fv(gl.getUniformLocation(m.program,'u_colors'), new Float32Array(flat)); + gl.uniform1f(gl.getUniformLocation(m.program,'u_colorsCount'),4); + gl.drawArrays(gl.TRIANGLES,0,6); + const W=m.canvasElement.width,H=m.canvasElement.height; + const pts=[[100,100],[360,100],[620,100],[100,220],[360,220],[620,220],[100,340],[360,340],[620,340]]; + const out=pts.map(([x,y])=>{const px=new Uint8Array(4);gl.readPixels(x,y,1,1,gl.RGBA,gl.UNSIGNED_BYTE,px);return [x,y,Array.from(px)];}); + // макс яркость по всему canvas + const buf=new Uint8Array(W*H*4); gl.readPixels(0,0,W,H,gl.RGBA,gl.UNSIGNED_BYTE,buf); + let maxc=0; for(let i=0;isetTimeout(r,1200)); +const r = await p.evaluate(()=>{ + const m=document.querySelector('[data-shader-demo] .card-shader').paperShaderMount; + const keys=['u_time','u_colors','u_colorsCount','u_distortion','u_swirl','u_grainMixer','u_grainOverlay']; + const out={}; + keys.forEach(k=>{ const l=m.uniformLocations[k]; out[k]= l===null?'NULL':(l===undefined?'UNDEF':typeof l); }); + // также посмотрим this.providedUniforms + const pu = m.providedUniforms ? Object.keys(m.providedUniforms) : 'none'; + return { locations: out, providedKeys: pu }; +}); +console.log(JSON.stringify(r,null,1)); +await b.close(); diff --git a/manual.mjs b/manual.mjs new file mode 100644 index 0000000..a5bae2a --- /dev/null +++ b/manual.mjs @@ -0,0 +1,21 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1200)); +const r = await p.evaluate(()=>{ + const m=document.querySelector('[data-shader-demo] .card-shader').paperShaderMount; + const gl=m.gl; + const loc0 = gl.getUniformLocation(m.program, 'u_colors'); + const locB = gl.getUniformLocation(m.program, 'u_colors[0]'); + // вручную зададим 4 цвета vec4 + const flat = [0.486,0.361,1,1, 0,0.761,0.78,1, 1,0.361,0.541,1, 1,0.702,0.278,1]; + gl.useProgram(m.program); + if (loc0) gl.uniform4fv(loc0, flat); else if (locB) gl.uniform4fv(locB, flat); + gl.drawArrays(gl.TRIANGLES,0,6); + const px=new Uint8Array(4); gl.readPixels(360,220,1,1,gl.RGBA,gl.UNSIGNED_BYTE,px); + return { loc0null: loc0===null, locBnull: locB===null, centerAfterManual: Array.from(px) }; +}); +console.log(JSON.stringify(r,null,1)); +await b.close(); diff --git a/matrix.mjs b/matrix.mjs new file mode 100644 index 0000000..13eeae9 --- /dev/null +++ b/matrix.mjs @@ -0,0 +1,23 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +const errs=[]; p.on('pageerror',e=>errs.push(e.message)); p.on('console',m=>{if(m.type()==='error')errs.push(m.text());}); +await p.setViewport({width:1100,height:1400}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1500)); +await p.evaluate(()=>document.querySelector('#sec-shader-grid')?.scrollIntoView()); +await new Promise(r=>setTimeout(r,1200)); +const r = await p.evaluate(()=>{ + const cards=[...document.querySelectorAll('#sec-shader-grid .neu-card--shader')]; + const info=cards.map(c=>{ + const sh=c.querySelector('.card-shader'); + const m=sh?.paperShaderMount; + const cs=getComputedStyle(c); + const beforeZ=getComputedStyle(c,'::before').zIndex; + return { hasShaderWrap:!!sh, mounted:!!m, speed:m?.currentSpeed, frame:m?Math.round(m.getCurrentFrame()):null, bg:cs.backgroundColor, beforeZ }; + }); + return { count: cards.length, info }; +}); +console.log(JSON.stringify(r,null,1)); +console.log('ERRS', errs.slice(0,5)); +await b.close(); diff --git a/meshcheck.mjs b/meshcheck.mjs new file mode 100644 index 0000000..d16091f --- /dev/null +++ b/meshcheck.mjs @@ -0,0 +1,18 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +const errs=[]; p.on('pageerror',e=>errs.push(e.message)); p.on('console',m=>{if(m.type()==='error')errs.push(m.text());}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1500)); +await p.evaluate(()=>document.querySelector('#sec-all-shaders')?.scrollIntoView()); +await new Promise(r=>setTimeout(r,1200)); +const r = await p.evaluate(()=>{ + const cards=[...document.querySelectorAll('.card-shader[data-shader]')]; + const info=cards.map(c=>({brand:c.dataset.shader, mounted:!!c.paperShaderMount, frag:c.paperShaderMount?.fragmentShader?.name||null})); + const f0=cards.find(c=>c.dataset.shader==='demo')?.paperShaderMount; + return { count: cards.length, info, demoFrame: f0?Math.round(f0.getCurrentFrame()):null, + demoAnimated: f0? (f0.getCurrentFrame()*0+ (f0.currentSpeed>0)) : null }; +}); +console.log(JSON.stringify(r,null,1)); +console.log('ERRS', errs.slice(0,6)); +await b.close(); diff --git a/mountcheck.mjs b/mountcheck.mjs new file mode 100644 index 0000000..bba84f0 --- /dev/null +++ b/mountcheck.mjs @@ -0,0 +1,12 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +const errs=[]; p.on('pageerror',e=>errs.push('PE: '+e.message)); p.on('console',m=>{if(m.type()==='error')errs.push('CON: '+m.text());}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1500)); +const r = await p.evaluate(()=>{ + const wrap=document.querySelector('[data-shader-demo] .card-shader'); + return { hasMount: !!wrap?.paperShaderMount, scriptLoaded: !!window.__mountLabShaders }; +}); +console.log(JSON.stringify({r, errs}, null, 1)); +await b.close(); diff --git a/pixeltime.mjs b/pixeltime.mjs new file mode 100644 index 0000000..50ba457 --- /dev/null +++ b/pixeltime.mjs @@ -0,0 +1,20 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1200)); +await p.evaluate(()=>document.querySelector('[data-shader-demo]').scrollIntoView({block:'center'})); +await new Promise(r=>setTimeout(r,500)); +const r = await p.evaluate(()=>{ + const wrap=document.querySelector('[data-shader-demo] .card-shader'); + const m=wrap.paperShaderMount; const cv=wrap.querySelector('canvas'); + const gl=cv.getContext('webgl2',{preserveDrawingBuffer:true})||cv.getContext('webgl',{preserveDrawingBuffer:true}); + const read=()=>{ const a=new Uint8Array(4); gl.readPixels(cv.width/2|0,cv.height/2|0,1,1,gl.RGBA,gl.UNSIGNED_BYTE,a); return Array.from(a); }; + m.render(0); const t0=read(); + m.render(3000); const t1=read(); + m.render(8000); const t2=read(); + return { t0, t1, t2 }; +}); +console.log(JSON.stringify(r,null,1)); +await b.close(); diff --git a/probe.mjs b/probe.mjs new file mode 100644 index 0000000..15a3872 --- /dev/null +++ b/probe.mjs @@ -0,0 +1,26 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +const errs=[],warns=[]; +p.on('pageerror', e=>errs.push('PE '+e.message)); +p.on('requestfailed', r=>errs.push('REQFAIL '+r.url()+' '+r.failure()?.errorText)); +p.on('console', m=>{ const t=m.type(); if(t==='error') errs.push('CE '+m.text()); if(t==='warning') warns.push('W '+m.text()); }); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'networkidle2',timeout:60000}); +await new Promise(r=>setTimeout(r,1500)); +const info = await p.evaluate(()=>{ + const cs=document.querySelector('[data-shader-demo] .card-shader'); + const m=cs?.paperShaderMount; + const bg=getComputedStyle(cs).backgroundColor; + const canvas=cs?.querySelector('canvas'); + return { + hasWrap: !!cs, hasMount: !!m, + wrapBg: bg, hasCanvas: !!canvas, + canvasW: canvas?.width, canvasH: canvas?.height, + speed: m?.currentSpeed, frame: m?Math.round(m.getCurrentFrame()):null, + }; +}); +console.log('INFO', JSON.stringify(info,null,1)); +console.log('ERRS', errs.slice(0,8)); +console.log('WARNS', warns.slice(0,8)); +await b.close(); diff --git a/probe_tmp.js b/probe_tmp.js new file mode 100644 index 0000000..4ec721e --- /dev/null +++ b/probe_tmp.js @@ -0,0 +1,52 @@ +const puppeteer = require('puppeteer'); + +async function probePage(browser, url, width) { + const page = await browser.newPage(); + await page.setViewport({ width, height: 900, deviceScaleFactor: 1 }); + await page.goto(url, { waitUntil: 'networkidle0' }); + await page.waitForSelector('[data-mini-projects-list] .mini-projects-grid__item', { timeout: 15000 }).catch(()=>{}); + await new Promise(r => setTimeout(r, 700)); + const data = await page.evaluate(() => { + const list = document.querySelector('[data-mini-projects-list]'); + if (!list) return { none: true }; + const inner = list.closest('.section__inner'); + const items = [...list.querySelectorAll('.mini-projects-grid__item')].filter(i => !i.hidden); + const rowCells = (() => { + const w = list.clientWidth; + if (w >= 1180) return 6; + if (w >= 880) return 4; + if (w >= 520) return 2; + return 1; + })(); + return { + listWidth: list.clientWidth, + innerWidth: inner ? inner.clientWidth : null, + rowCells, + itemCount: items.length, + items: items.map(i => ({ + cells: i.getAttribute('data-mp-cells'), + styleW: i.style.width, + x: i.style.transform, + })), + }; + }); + await page.close(); + return data; +} + +(async () => { + const browser = await puppeteer.launch({ headless: 'new', args: ['--no-sandbox','--disable-setuid-sandbox'] }); + const widths = [600, 760, 900, 1024, 1180, 1278, 1356, 1600]; + const caseUrl = 'http://localhost:4175/cases/ai-platform-primo-rpa/'; + const homeUrl = 'http://localhost:4175/'; + for (const w of widths) { + const c = await probePage(browser, caseUrl, w); + console.log(`CASE vw=${w} inner=${c.innerWidth} rowCells=${c.rowCells} items=${c.itemCount} ` + JSON.stringify(c.items)); + } + console.log('---'); + for (const w of widths) { + const h = await probePage(browser, homeUrl, w); + console.log(`HOME vw=${w} inner=${h.innerWidth} rowCells=${h.rowCells} items=${h.itemCount} ` + JSON.stringify(h.items)); + } + await browser.close(); +})().catch(e => { console.error(e); process.exit(1); }); diff --git a/public/en/index.html b/public/en/index.html index b687a3b..304e316 100644 --- a/public/en/index.html +++ b/public/en/index.html @@ -104,7 +104,7 @@ - + diff --git a/pulse.mjs b/pulse.mjs new file mode 100644 index 0000000..da19ac2 --- /dev/null +++ b/pulse.mjs @@ -0,0 +1,20 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +const errs=[]; p.on('pageerror',e=>errs.push(e.message)); p.on('console',m=>{if(m.type()==='error')errs.push(m.text());}); +await p.setViewport({width:1100,height:1500}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1500)); +await p.evaluate(()=>document.querySelector('#sec-all-shaders')?.scrollIntoView()); +await new Promise(r=>setTimeout(r,1500)); +const r = await p.evaluate(()=>{ + const cells=[...document.querySelectorAll('[data-shader-cell="pulsingBorder"]')]; + const info=cells.map(c=>{const m=c.querySelector('.card-shader')?.paperShaderMount; return {mounted:!!m, speed:m?.currentSpeed, frame:m?Math.round(m.getCurrentFrame()):null};}); + // проверим, что noise-текстура задалась + const m0=cells[0]?.querySelector('.card-shader')?.paperShaderMount; + const texLoc=m0?m0.gl.getUniform(m0.program, m0.gl.getUniformLocation(m0.program,'u_noiseTexture')):null; + return { cellCount: cells.length, info, noiseTexSet: texLoc!==null && texLoc!==undefined }; +}); +console.log(JSON.stringify(r,null,1)); +console.log('ERRS', errs.slice(0,8)); +await b.close(); diff --git a/pure.mjs b/pure.mjs new file mode 100644 index 0000000..b032584 --- /dev/null +++ b/pure.mjs @@ -0,0 +1,14 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1000)); +await p.evaluate(()=>document.querySelector('[data-shader-demo]').scrollIntoView({block:'center'})); +await new Promise(r=>setTimeout(r,500)); +const a = await p.evaluate(()=>{ const m=document.querySelector('[data-shader-demo] .card-shader').paperShaderMount; return {frame:m.getCurrentFrame(), speed:m.currentSpeed, inVp:m.isInViewport, cs:m.currentSpeed}; }); +await new Promise(r=>setTimeout(r,2000)); +const c = await p.evaluate(()=>{ const m=document.querySelector('[data-shader-demo] .card-shader').paperShaderMount; return {frame:m.getCurrentFrame(), speed:m.currentSpeed, inVp:m.isInViewport}; }); +console.log('A', JSON.stringify(a)); console.log('C', JSON.stringify(c)); +console.log('FRAME GROWS:', c.frame>a.frame, ' (', a.frame.toFixed(1), '->', c.frame.toFixed(1), ')'); +await b.close(); diff --git a/raf.mjs b/raf.mjs new file mode 100644 index 0000000..ad5514f --- /dev/null +++ b/raf.mjs @@ -0,0 +1,32 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1200)); +await p.evaluate(()=>document.querySelector('[data-shader-demo]').scrollIntoView({block:'center'})); +await new Promise(r=>setTimeout(r,300)); +// достанем mount из WeakMap нельзя, но проверим через глобальный хук: переопределим requestAnimationFrame до загрузки? уже поздно. +// Вместо этого — вручную создадим ShaderMount и проверим frame рост + readPixels внутри rAF +const res = await p.evaluate(async()=>{ + const mod = await import('/vendor/paper-shaders/index.js'); + const wrap = document.querySelector('[data-shader-demo] .card-shader'); + const cv = wrap.querySelector('canvas'); + // создадим свой mount того же шейдера в НОВЫЙ контейнер чтобы не конфликтовать + const host = document.createElement('div'); host.style.cssText='position:fixed;left:0;top:0;width:200px;height:120px;z-index:9999'; + document.body.appendChild(host); + const m = new mod.ShaderMount(host, mod.meshGradientFragmentShader, { u_colors:['#7c5cff','#00c2c7','#ff5c8a'], u_colorsCount:3, u_distortion:0.6, u_swirl:0.4, u_grainMixer:0.08, u_grainOverlay:0.04 }, {preserveDrawingBuffer:true}, 0.2); + await new Promise(r=>setTimeout(r,500)); + const frame1 = m.getCurrentFrame(); + await new Promise(r=>setTimeout(r,500)); + const frame2 = m.getCurrentFrame(); + // читаем пиксель через свой canvas с preserveDrawingBuffer + const c2 = host.querySelector('canvas'); + const gl = c2.getContext('webgl', {preserveDrawingBuffer:true}) || c2.getContext('webgl2',{preserveDrawingBuffer:true}); + let px='n/a'; + if(gl){ const a=new Uint8Array(4); gl.readPixels(10,10,1,1,gl.RGBA,gl.UNSIGNED_BYTE,a); px=Array.from(a); } + m.dispose(); host.remove(); + return { frame1, frame2, animated: frame2>frame1, px }; +}); +console.log(JSON.stringify(res,null,1)); +await b.close(); diff --git a/readback.mjs b/readback.mjs new file mode 100644 index 0000000..b4a46c7 --- /dev/null +++ b/readback.mjs @@ -0,0 +1,27 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,800)); +const r = await p.evaluate(async ()=>{ + const mod = await import('/vendor/paper-shaders/index.js'); + const { ShaderMount, meshGradientFragmentShader, getShaderColorFromString } = mod; + const host = document.createElement('div'); host.style.cssText='width:300px;height:200px;position:relative'; + document.body.appendChild(host); + const colors = ['#7c5cff','#00c2c7','#ff5c8a','#ffb347'].map(getShaderColorFromString); + const uniforms = { u_colors: colors, u_colorsCount: colors.length, u_distortion:1.2, u_swirl:0.8, u_grainMixer:0.08, u_grainOverlay:0.04 }; + const m = new ShaderMount(host, meshGradientFragmentShader, uniforms, { preserveDrawingBuffer:true, antialias:true }, 1.0); + const gl=m.gl, pr=m.program; + const f=(n)=>{const l=gl.getUniformLocation(pr,n);const v=new Float32Array(4);gl.getUniform(pr,l,v);return Array.from(v).map(x=>+x.toFixed(3));}; + const fi=(n)=>{const l=gl.getUniformLocation(pr,n);const v=new Float32Array(1);gl.getUniform(pr,l,v);return v[0];}; + return { + u_swirl: fi('u_swirl'), + u_distortion: fi('u_distortion'), + u_colorsCount: fi('u_colorsCount'), + u_color0: f('u_colors[0]'), + u_color1: f('u_colors[1]'), + }; +}); +console.log(JSON.stringify(r,null,1)); +await b.close(); diff --git a/readu.mjs b/readu.mjs new file mode 100644 index 0000000..3340e99 --- /dev/null +++ b/readu.mjs @@ -0,0 +1,22 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1200)); +const r = await p.evaluate(()=>{ + const m=document.querySelector('[data-shader-demo] .card-shader').paperShaderMount; + const gl=m.gl; + const out=[]; + for(let i=0;i<4;i++){ + const loc=gl.getUniformLocation(m.program,'u_colors['+i+']'); + const v=new Float32Array(4); + gl.getUniform(m.program,loc,v); + out.push(Array.from(v).map(x=>+x.toFixed(3))); + } + const cnt=gl.getUniformLocation(m.program,'u_colorsCount'); + const cv=new Int32Array(1); gl.getUniform(m.program,cnt,cv); + return { uColors: out, count: cv[0] }; +}); +console.log(JSON.stringify(r,null,1)); +await b.close(); diff --git a/real.mjs b/real.mjs new file mode 100644 index 0000000..bcb5aae --- /dev/null +++ b/real.mjs @@ -0,0 +1,16 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1000)); +// принудительно рендерим два кадра с разным временем и берём toDataURL +const r = await p.evaluate(()=>{ + const m=document.querySelector('[data-shader-demo] .card-shader').paperShaderMount; + const cv=m.canvasElement; + m.render(0); const a=cv.toDataURL('image/png'); + m.render(8000); const c=cv.toDataURL('image/png'); + return { lenA:a.length, lenC:c.length, same: a===c, headA:a.slice(0,40) }; +}); +console.log(JSON.stringify(r,null,1)); +await b.close(); diff --git a/rendercheck.mjs b/rendercheck.mjs new file mode 100644 index 0000000..c69ceed --- /dev/null +++ b/rendercheck.mjs @@ -0,0 +1,18 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +const errs=[]; p.on('pageerror',e=>errs.push(e.message)); p.on('console',m=>{if(m.type()==='error')errs.push(m.text());}); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1500)); +const r = await p.evaluate(()=>{ + const m=document.querySelector('[data-shader-demo] .card-shader').paperShaderMount; + const gl=m.gl; + m.render(performance.now()); m.render(performance.now()); + const W=m.canvasElement.width,H=m.canvasElement.height; const buf=new Uint8Array(W*H*4); gl.readPixels(0,0,W,H,gl.RGBA,gl.UNSIGNED_BYTE,buf); + let bright=0,maxc=0; for(let i=0;i120)bright++; maxc=Math.max(maxc,mx);} + return { brightPixels: bright, maxChannel: maxc }; +}); +console.log(JSON.stringify(r,null,1)); +console.log('ERRS', errs.slice(0,5)); +await b.close(); diff --git a/res.mjs b/res.mjs new file mode 100644 index 0000000..6a13cfa --- /dev/null +++ b/res.mjs @@ -0,0 +1,19 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1200)); +const r = await p.evaluate(()=>{ + const m=document.querySelector('[data-shader-demo] .card-shader').paperShaderMount; + const gl=m.gl, pr=m.program; + const gf=(n)=>gl.getUniform(pr, gl.getUniformLocation(pr,n)); + return { + u_resolution: gf('u_resolution'), + u_pixelRatio: gf('u_pixelRatio'), + linkStatus: gl.getProgramParameter(pr, gl.LINK_STATUS), + vsLog: gl.getShaderInfoLog(gl.getShader(gl.VERTEX_SHADER)), + }; +}); +console.log(JSON.stringify(r,null,1)); +await b.close(); diff --git a/res2.mjs b/res2.mjs new file mode 100644 index 0000000..9bb4910 --- /dev/null +++ b/res2.mjs @@ -0,0 +1,14 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1200)); +const r = await p.evaluate(()=>{ + const m=document.querySelector('[data-shader-demo] .card-shader').paperShaderMount; + const gl=m.gl, pr=m.program; + const gf=(n)=>gl.getUniform(pr, gl.getUniformLocation(pr,n)); + return { u_resolution: gf('u_resolution'), u_pixelRatio: gf('u_pixelRatio'), canvasW: m.canvasElement.width, canvasH: m.canvasElement.height }; +}); +console.log(JSON.stringify(r,null,1)); +await b.close(); diff --git a/selfraf.mjs b/selfraf.mjs new file mode 100644 index 0000000..fed592d --- /dev/null +++ b/selfraf.mjs @@ -0,0 +1,14 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +const errs=[]; p.on('pageerror',e=>errs.push('PE '+e.message)); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1000)); +// берём toDataURL двух кадров с интервалом — мой rAF должен сдвинуть currentFrame +const a = await p.evaluate(()=>{ const m=document.querySelector('[data-shader-demo] .card-shader').paperShaderMount; return m.canvasElement.toDataURL('image/png').length; }); +await new Promise(r=>setTimeout(r,1500)); +const c = await p.evaluate(()=>{ const m=document.querySelector('[data-shader-demo] .card-shader').paperShaderMount; return m.canvasElement.toDataURL('image/png').length; }); +console.log('dataURL length t1:', a, ' t2:', c, ' CHANGED:', a!==c); +console.log('errs', errs.slice(0,3)); +await b.close(); diff --git a/setframe.mjs b/setframe.mjs new file mode 100644 index 0000000..d4f41bd --- /dev/null +++ b/setframe.mjs @@ -0,0 +1,15 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1000)); +const r = await p.evaluate(()=>{ + const m=document.querySelector('[data-shader-demo] .card-shader').paperShaderMount; + const cv=m.canvasElement; + m.setFrame(0); const a=cv.toDataURL('image/png'); + m.setFrame(10000); const c=cv.toDataURL('image/png'); + return { same: a===c, lenA:a.length, lenC:c.length }; +}); +console.log(JSON.stringify(r,null,1)); +await b.close(); diff --git a/setu.mjs b/setu.mjs new file mode 100644 index 0000000..7e29a0f --- /dev/null +++ b/setu.mjs @@ -0,0 +1,17 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1200)); +const r = await p.evaluate(()=>{ + const m=document.querySelector('[data-shader-demo] .card-shader').paperShaderMount; + const gl=m.gl; + m.setUniforms({ u_colorsCount: 4, u_colors: [[0.486,0.361,1,1],[0,0.761,0.78,1],[1,0.361,0.541,1],[1,0.702,0.278,1]] }); + gl.drawArrays(gl.TRIANGLES,0,6); + const px=new Uint8Array(4); gl.readPixels(360,220,1,1,gl.RGBA,gl.UNSIGNED_BYTE,px); + const cnt=gl.getUniformLocation(m.program,'u_colorsCount'); const cv=new Int32Array(1); gl.getUniform(m.program,cnt,cv); + return { center:Array.from(px), count: cv[0] }; +}); +console.log(JSON.stringify(r,null,1)); +await b.close(); diff --git a/setu2.mjs b/setu2.mjs new file mode 100644 index 0000000..198039e --- /dev/null +++ b/setu2.mjs @@ -0,0 +1,21 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,800)); +const r = await p.evaluate(async ()=>{ + const mod = await import('/vendor/paper-shaders/index.js'); + const { ShaderMount, meshGradientFragmentShader, getShaderColorFromString } = mod; + const host = document.createElement('div'); host.style.cssText='width:300px;height:200px;position:relative'; + document.body.appendChild(host); + const colors = ['#7c5cff','#00c2c7','#ff5c8a','#ffb347'].map(getShaderColorFromString); + const m = new ShaderMount(host, meshGradientFragmentShader, {}, { preserveDrawingBuffer:true, antialias:true }, 1.0); + m.setUniforms({ u_colors: colors, u_colorsCount: colors.length, u_distortion:1.2, u_swirl:0.8 }); + const gl=m.gl, pr=m.program; + const fi=(n)=>{const l=gl.getUniformLocation(pr,n);const v=new Float32Array(1);gl.getUniform(pr,l,v);return v[0];}; + const f=(n)=>{const l=gl.getUniformLocation(pr,n);const v=new Float32Array(4);gl.getUniform(pr,l,v);return Array.from(v).map(x=>+x.toFixed(3));}; + return { u_swirl: fi('u_swirl'), u_colorsCount: fi('u_colorsCount'), u_color0: f('u_colors[0]') }; +}); +console.log(JSON.stringify(r,null,1)); +await b.close(); diff --git a/shadow.mjs b/shadow.mjs new file mode 100644 index 0000000..cefb31e --- /dev/null +++ b/shadow.mjs @@ -0,0 +1,24 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1400}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1200)); +await p.evaluate(()=>document.querySelector('#sec-shader-grid')?.scrollIntoView()); +await new Promise(r=>setTimeout(r,800)); +const r = await p.evaluate(()=>{ + const shaderCard=document.querySelector('#sec-shader-grid .neu-card--shader'); + const plainCard=document.querySelector('#sec-shader-grid .neu-card:not(.neu-card--shader)'); + const cs=getComputedStyle(shaderCard); + const after=getComputedStyle(shaderCard,'::after'); + const plain=getComputedStyle(plainCard); + const countShadows=(s)=>s.split(/,(?![^()]*\))/).filter(x=>x.trim()).length; + return { + shaderBoxShadow: cs.boxShadow.slice(0,80)+'...', shaderShadowCount: countShadows(cs.boxShadow), + afterBoxShadow: after.boxShadow.slice(0,80)+'...', afterShadowCount: countShadows(after.boxShadow), + afterZ: after.zIndex, beforeZ: getComputedStyle(shaderCard,'::before').zIndex, + plainShadowCount: countShadows(plain.boxShadow), + }; +}); +console.log(JSON.stringify(r,null,1)); +await b.close(); diff --git a/shchk.mjs b/shchk.mjs new file mode 100644 index 0000000..43dcc12 --- /dev/null +++ b/shchk.mjs @@ -0,0 +1,24 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +const errs=[]; p.on('console',m=>{if(m.type()==='error')errs.push(m.text())}); p.on('pageerror',e=>errs.push('PE '+e.message)); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1500)); +const info = await p.evaluate(()=>{ + const hosts=[...document.querySelectorAll('#sec-shader .card-shader-host')]; + return hosts.map(h=>{ + const cv=h.querySelector('canvas'); + return { sel:h.dataset.shader, hasCanvas:!!cv, w:cv?.width, h:cv?.height, mounted:h.dataset.mounted }; + }); +}); +// снимем пиксель чтобы убедиться что шейдер реально рисует (не пустой canvas) +const pix = await p.evaluate(()=>{ + const cv=document.querySelector('#sec-shader .card-shader-host canvas'); + if(!cv) return 'no-canvas'; + const gl=cv.getContext('webgl2')||cv.getContext('webgl'); + if(!gl) return 'no-gl'; + const px=new Uint8Array(4); gl.readPixels(Math.floor(cv.width/2),Math.floor(cv.height/2),1,1,gl.RGBA,gl.UNSIGNED_BYTE,px); + return Array.from(px).join(','); +}); +console.log(JSON.stringify({info, centerPixel:pix, errs}, null, 1)); +await b.close(); diff --git a/shclick.mjs b/shclick.mjs new file mode 100644 index 0000000..bf112b3 --- /dev/null +++ b/shclick.mjs @@ -0,0 +1,22 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader'] }); +const p = await b.newPage(); +await p.setViewport({width:1280,height:1000}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1200)); +// подпишемся на клик по ссылке внутри страницы +await p.evaluate(()=>{ + window.__clicked=false; + document.querySelector('#sec-shader a.contact-card').addEventListener('click', ()=>{ window.__clicked=true; }, true); +}); +const link = await p.$('#sec-shader a.contact-card'); +const box = await link.boundingBox(); +await p.mouse.click(box.x+box.width/2, box.y+box.height/2); +await new Promise(r=>setTimeout(r,300)); +const clicked = await p.evaluate(()=>window.__clicked); +// hover тоже +await p.mouse.move(box.x+box.width/2, box.y+box.height/2); +await new Promise(r=>setTimeout(r,400)); +const hover = await p.evaluate(()=>getComputedStyle(document.querySelector('#sec-shader a.contact-card')).transform); +console.log(JSON.stringify({clicked, hover},null,1)); +await b.close(); diff --git a/shelem.mjs b/shelem.mjs new file mode 100644 index 0000000..12eb7bb --- /dev/null +++ b/shelem.mjs @@ -0,0 +1,24 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader'] }); +const p = await b.newPage(); +await p.setViewport({width:1280,height:1000}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1200)); +const r = await p.evaluate(()=>{ + const card=document.querySelector('#sec-shader a.contact-card'); + const rect=card.getBoundingClientRect(); + const x=rect.x+rect.width/2, y=rect.y+rect.height/2; + const el=document.elementFromPoint(x,y); + const path=[]; + let e=el; while(e && path.length<5){ path.push(e.className||e.tagName); e=e.parentElement; } + // что у canvas + const cv=card.querySelector('canvas'); + return { + topEl: el.className||el.tagName, + path, + canvasPE: cv?getComputedStyle(cv).pointerEvents:'', canvasZ: cv?getComputedStyle(cv).zIndex:'', + cardZ: getComputedStyle(card).zIndex, cardPos: getComputedStyle(card).position, + }; +}); +console.log(JSON.stringify(r,null,1)); +await b.close(); diff --git a/shelem2.mjs b/shelem2.mjs new file mode 100644 index 0000000..c95d14b --- /dev/null +++ b/shelem2.mjs @@ -0,0 +1,24 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader'] }); +const p = await b.newPage(); +await p.setViewport({width:1280,height:1000}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1000)); +await p.evaluate(()=>document.querySelector('#sec-shader').scrollIntoView()); +await new Promise(r=>setTimeout(r,400)); +await p.evaluate(()=>{ + window.__clicked=false; + const c=document.querySelector('#sec-shader a.contact-card'); + c.addEventListener('click', ()=>{ window.__clicked=true; }, true); +}); +const box = await p.$eval('#sec-shader a.contact-card', el=>{const r=el.getBoundingClientRect();return {x:r.x,y:r.y,w:r.width,h:r.height};}); +const x=box.x+box.w/2, y=box.y+box.h/2; +await p.mouse.move(x,y); +await new Promise(r=>setTimeout(r,400)); +const hover = await p.evaluate(()=>getComputedStyle(document.querySelector('#sec-shader a.contact-card')).transform); +await p.mouse.click(x,y); +await new Promise(r=>setTimeout(r,300)); +const clicked = await p.evaluate(()=>window.__clicked); +const top = await p.evaluate((x,y)=>{const e=document.elementFromPoint(x,y);return e?(e.className||e.tagName):'null';}, x, y); +console.log(JSON.stringify({hover, clicked, topElAtCenter: top},null,1)); +await b.close(); diff --git a/shelem3.mjs b/shelem3.mjs new file mode 100644 index 0000000..6191fa6 --- /dev/null +++ b/shelem3.mjs @@ -0,0 +1,24 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader'] }); +const p = await b.newPage(); +await p.setViewport({width:1280,height:1000}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1000)); +const meta = await p.evaluate(()=>{ + const c=document.querySelector('#sec-shader a.contact-card'); + const r=c.getBoundingClientRect(); + return { docH: document.body.scrollHeight, viewH: window.innerHeight, + cardTop: r.top, cardBottom: r.bottom, cardRect: {x:r.x,y:r.y,w:r.width,h:r.height} }; +}); +console.log('before scroll', JSON.stringify(meta)); +await p.evaluate(()=>document.querySelector('#sec-shader a.contact-card').scrollIntoView({block:'center'})); +await new Promise(r=>setTimeout(r,500)); +const after = await p.evaluate(()=>{ + const c=document.querySelector('#sec-shader a.contact-card'); + const r=c.getBoundingClientRect(); + const cx=r.x+r.width/2, cy=r.y+r.height/2; + const e=document.elementFromPoint(cx,cy); + return { cardTop: r.top, cardBottom: r.bottom, topEl: e?(e.className||e.tagName):'null', cx, cy }; +}); +console.log('after scroll', JSON.stringify(after)); +await b.close(); diff --git a/shfin.mjs b/shfin.mjs new file mode 100644 index 0000000..5b80fbe --- /dev/null +++ b/shfin.mjs @@ -0,0 +1,16 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader'] }); +const p = await b.newPage(); +const errs=[]; p.on('console',m=>{if(m.type()==='error')errs.push(m.text())}); p.on('pageerror',e=>errs.push('PE '+e.message)); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1200)); +const r = await p.evaluate(()=>{ + const card=document.querySelector('#sec-shader .card-shader-host .contact-card'); + const cs=getComputedStyle(card); + const wrap=document.querySelector('#sec-shader .card-shader'); + return { boxShadow: cs.boxShadow.slice(0,40), glow: cs.backgroundImage.slice(0,30), + shaderLayer: getComputedStyle(wrap).position, shaderZ: getComputedStyle(wrap).zIndex, + contentZ: getComputedStyle(card.querySelector('.contact-card__body')).zIndex }; +}); +console.log(JSON.stringify({r, errs},null,1)); +await b.close(); diff --git a/shfinal.mjs b/shfinal.mjs new file mode 100644 index 0000000..ab5bf6a --- /dev/null +++ b/shfinal.mjs @@ -0,0 +1,23 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader'] }); +const p = await b.newPage(); +await p.setViewport({width:1280,height:1000}); +const errs=[]; p.on('console',m=>{if(m.type()==='error')errs.push(m.text())}); p.on('pageerror',e=>errs.push('PE '+e.message)); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1000)); +const link = await p.$('#sec-shader a.contact-card'); +await link.hover(); +await new Promise(r=>setTimeout(r,400)); +const hover = await p.evaluate(()=>getComputedStyle(document.querySelector('#sec-shader a.contact-card')).transform); +let popup='none'; +p.once('popup', ()=>{ popup='popup'; }); +await link.click({delay:50}); +await new Promise(r=>setTimeout(r,300)); +const geo = await p.evaluate(()=>{ + const li=document.querySelector('#sec-shader li'); const card=li.querySelector('.contact-card'); + const cv=card.querySelector('canvas'); + return { liH:Math.round(li.getBoundingClientRect().height), cardH:Math.round(card.getBoundingClientRect().height), + canvasInCard: card.contains(cv), canvasPE: cv?getComputedStyle(cv).pointerEvents:'none' }; +}); +console.log(JSON.stringify({hover, popup, geo, errs},null,1)); +await b.close(); diff --git a/shfix.mjs b/shfix.mjs new file mode 100644 index 0000000..4d69513 --- /dev/null +++ b/shfix.mjs @@ -0,0 +1,26 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader'] }); +const p = await b.newPage(); +await p.setViewport({width:1280,height:1000}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1200)); +const info = await p.evaluate(()=>{ + const host=document.querySelector('#sec-shader .card-shader-host'); + const li=host; const card=host.querySelector('.contact-card'); const canvas=host.querySelector('canvas'); + const lr=getComputedStyle(li), cr=getComputedStyle(card), cvr=getComputedStyle(canvas); + return { + liDisplay: lr.display, liPosition: lr.position, liW: li.getBoundingClientRect().width, + cardW: card.getBoundingClientRect().width, cardH: card.getBoundingClientRect().height, + canvasPointer: cvr.pointerEvents, canvasZ: cvr.zIndex, canvasInset: cvr.position, + cardPointer: cr.pointerEvents, + }; +}); +console.log(JSON.stringify(info,null,1)); +// тест клика по ссылке +const link = await p.$('#sec-shader a.contact-card'); +const box = await link.boundingBox(); +await p.mouse.click(box.x+box.width/2, box.y+box.height/2); +await new Promise(r=>setTimeout(r,300)); +const url = p.url(); +console.log('after click url:', url, '(same page expected, external link opens new tab)'); +await b.close(); diff --git a/shlog.mjs b/shlog.mjs new file mode 100644 index 0000000..14a5bca --- /dev/null +++ b/shlog.mjs @@ -0,0 +1,21 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +const logs=[]; +p.on('console', m=>logs.push('['+m.type()+'] '+m.text())); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1000)); +const r = await p.evaluate(()=>{ + const m=document.querySelector('[data-shader-demo] .card-shader').paperShaderMount; + const gl=m.gl; + // проверим компиляцию шейдеров напрямую + const vs=gl.getParameter(gl.VERTEX_SHADER); + // считаем context lost + const lost = gl.isContextLost(); + // размер drawing buffer + const dbw=gl.drawingBufferWidth, dbh=gl.drawingBufferHeight; + return { contextLost: lost, dbW: dbw, dbH: dbh, glError: gl.getError() }; +}); +console.log('R', JSON.stringify(r)); +console.log('LOGS:\n'+logs.slice(0,15).join('\n')); +await b.close(); diff --git a/shot.mjs b/shot.mjs new file mode 100644 index 0000000..13237d1 --- /dev/null +++ b/shot.mjs @@ -0,0 +1,12 @@ +import puppeteer from 'puppeteer'; +import fs from 'fs'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1500,deviceScaleFactor:1}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1800)); +const el = await p.$('#sec-shader-grid'); +const box = await el.boundingBox(); +await el.screenshot({ path: '/tmp/matrix.png' }); +console.log('saved', box?.width, box?.height); +await b.close(); diff --git a/shot_tmp.js b/shot_tmp.js new file mode 100644 index 0000000..50c9c02 --- /dev/null +++ b/shot_tmp.js @@ -0,0 +1,14 @@ +const puppeteer = require('puppeteer'); +(async () => { + const browser = await puppeteer.launch({ headless: 'new', args: ['--no-sandbox','--disable-setuid-sandbox'] }); + const page = await browser.newPage(); + await page.setViewport({ width: 1278, height: 900, deviceScaleFactor: 1 }); + await page.goto('http://localhost:4175/cases/ai-platform-primo-rpa/', { waitUntil: 'networkidle0' }); + await page.waitForSelector('[data-mini-projects-list]'); + const el = await page.$('[data-mini-projects-list]'); + await el.evaluate(e => e.scrollIntoView({block:'center'})); + await new Promise(r => setTimeout(r, 600)); + await el.screenshot({ path: '/tmp/related_1278.png' }); + await browser.close(); + console.log('shot saved'); +})().catch(e => { console.error(e); process.exit(1); }); diff --git a/shotcmp.mjs b/shotcmp.mjs new file mode 100644 index 0000000..e434031 --- /dev/null +++ b/shotcmp.mjs @@ -0,0 +1,16 @@ +import puppeteer from 'puppeteer'; +import crypto from 'crypto'; +import fs from 'fs'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1000)); +await p.evaluate(()=>document.querySelector('[data-shader-demo]').scrollIntoView({block:'center'})); +await new Promise(r=>setTimeout(r,500)); +const h1 = await p.screenshot({encoding:'binary'}); await fs.promises.writeFile('/tmp/s1.png', h1); +await new Promise(r=>setTimeout(r,1500)); +const h2 = await p.screenshot({encoding:'binary'}); await fs.promises.writeFile('/tmp/s2.png', h2); +const m1=crypto.createHash('md5').update(h1).digest('hex'); const m2=crypto.createHash('md5').update(h2).digest('hex'); +console.log('bytes', h1.length, h2.length, 'md5equal', m1===m2); +await b.close(); diff --git a/shreal.mjs b/shreal.mjs new file mode 100644 index 0000000..b26c5d4 --- /dev/null +++ b/shreal.mjs @@ -0,0 +1,16 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader'] }); +const p = await b.newPage(); +await p.setViewport({width:1280,height:1000}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1000)); +await p.evaluate(()=>{ window.__clicked=false; const c=document.querySelector('#sec-shader a.contact-card'); c.addEventListener('click',()=>window.__clicked=true,true); }); +const link = await p.$('#sec-shader a.contact-card'); +await link.hover(); +await new Promise(r=>setTimeout(r,400)); +const hover = await p.evaluate(()=>getComputedStyle(document.querySelector('#sec-shader a.contact-card')).transform); +await link.click({delay:50}); +await new Promise(r=>setTimeout(r,300)); +const clicked = await p.evaluate(()=>window.__clicked); +console.log(JSON.stringify({hover, clicked},null,1)); +await b.close(); diff --git a/shver.mjs b/shver.mjs new file mode 100644 index 0000000..6a621f8 --- /dev/null +++ b/shver.mjs @@ -0,0 +1,32 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader'] }); +const p = await b.newPage(); +await p.setViewport({width:1280,height:1000}); +const errs=[]; p.on('console',m=>{if(m.type()==='error')errs.push(m.text())}); p.on('pageerror',e=>errs.push('PE '+e.message)); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1500)); +const geo = await p.evaluate(()=>{ + const li=document.querySelector('#sec-shader li'); + const card=li.querySelector('.contact-card'); + const canvas=li.querySelector('canvas'); + return { + liH: Math.round(li.getBoundingClientRect().height), + cardH: Math.round(card.getBoundingClientRect().height), + canvasInCard: canvas ? card.contains(canvas) : false, + canvasPE: canvas ? getComputedStyle(canvas).pointerEvents : 'no-canvas', + cardPE: getComputedStyle(card).pointerEvents, + }; +}); +// hover + click test +const link = await p.$('#sec-shader a.contact-card'); +const box = await link.boundingBox(); +await p.mouse.move(box.x+box.width/2, box.y+box.height/2); +await new Promise(r=>setTimeout(r,200)); +const hoverTransform = await p.evaluate(()=>getComputedStyle(document.querySelector('#sec-shader a.contact-card')).transform); +// click -> should open new tab (we block navigation, just check no error) +const newTarget = await Promise.race([ + new Promise(res=>p.once('popup', t=>res('popup'))), + new Promise(res=>setTimeout(()=>res('none'), 800)) +]); +console.log(JSON.stringify({geo, hoverTransform, newTarget, errs}, null, 1)); +await b.close(); diff --git a/shvis.mjs b/shvis.mjs new file mode 100644 index 0000000..59decf7 --- /dev/null +++ b/shvis.mjs @@ -0,0 +1,17 @@ +import puppeteer from 'puppeteer'; +import sharp from 'sharp'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1280,height:1400,deviceScaleFactor:1}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,2000)); +const el = await p.$('#sec-shader'); +await el.screenshot({ path: '/tmp/shader-sec.png' }); +// analyse average + stddev of color +const {data,info} = await sharp('/tmp/shader-sec.png').raw().toBuffer({resolveWithObject:true}); +let n=info.width*info.height, rs=0,gs=0,bs=0; const cols=new Set(); +for(let i=0;i>4},${data[i+1]>>4},${data[i+2]>>4}`); } +console.log('avg rgb', Math.round(rs/n),Math.round(gs/n),Math.round(bs/n)); +console.log('distinct coarse colors sampled:', cols.size); +await b.close(); diff --git a/size.mjs b/size.mjs new file mode 100644 index 0000000..b937708 --- /dev/null +++ b/size.mjs @@ -0,0 +1,19 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1200)); +const r = await p.evaluate(()=>{ + const m=document.querySelector('[data-shader-demo] .card-shader').paperShaderMount; + const wrap=m.parentElement; + return { + canvasW:m.canvasElement.width, canvasH:m.canvasElement.height, + canvasCSS: getComputedStyle(m.canvasElement).width+'x'+getComputedStyle(m.canvasElement).height, + parentW:m.parentWidth, parentH:m.parentHeight, + wrapRect: (()=>{const r=wrap.getBoundingClientRect();return {w:r.width,h:r.height};})(), + hasRO: !!m.resizeObserver, + }; +}); +console.log(JSON.stringify(r,null,1)); +await b.close(); diff --git a/src/pages/index.en.html b/src/pages/index.en.html index 5b741fe..6cf966a 100644 --- a/src/pages/index.en.html +++ b/src/pages/index.en.html @@ -11,7 +11,7 @@ {{> head-links}} - + {{> header-home}} {{> fab-stack}}
diff --git a/texts/cases/card-columns-obsidian.md b/texts/cases/card-columns-obsidian.md new file mode 100644 index 0000000..a71e77f --- /dev/null +++ b/texts/cases/card-columns-obsidian.md @@ -0,0 +1,116 @@ +# Card Columns — плагин-вид для Obsidian Bases + +- **Источник:** `public/cases/card-columns-obsidian/index.html` +- **eyebrow:** Obsidian Bases · Plugin +- **meta description:** Кейс: Card Columns, плагин-вид для Obsidian Bases. Показывает заметки как карточки, разложенные по колонкам, с фильтрацией тегов по И/ИЛИ. + +--- + +**Заметка по путям к медиа:** файлы кладутся в `public/assets/card-columns-obsidian/`, в разметке страницы пути `../assets/card-columns-obsidian/файл.webp`. Форматы: WebP (изображения, постеры), WebM + MP4 (видео). + +## Brief (блок метаданных на странице) + +- **Роль:** Продуктовый дизайн и разработка +- **Платформа:** Obsidian Bases +- **Статус:** Рабочий плагин, open source (MIT) +- **Команда:** Соло, пет-проект + +## Введение + +В Obsidian появились Bases, отдельный тип файла, который показывает заметки не текстом, а в разных видах: таблицей, карточками, деревом. Стандартный вид карточек мне не зашёл: он плоский, без группировки, и в нём не разобраться, когда заметок сотни. Я хотел видеть их раскладку по колонкам, как канбан, но без лишнего веса. Так появился плагин-вид Card Columns. + +## Контекст + +Bases, это не заметка и не отдельная база данных. Это запрос к vault-у: выбираешь заметки по фильтру, и фреймворк рисует их в выбранном виде. У самого Obsidian есть встроенный вид карточек, но он одноплановый: нет группировки по свойствам, нет нормальной фильтрации. Я решил сделать свой вид, который встраивается в переключатель Bases наравне со штатными. + +Главное ограничение было в самом фреймворке: плагин не хранит данные, а рисует то, что выдал Bases-запрос (Sort, Filter, Properties, Search из тулбара). Значит, вся логика выборки остаётся на Bases, а моя задача, только представление и навигация по нему. + +## Задача + +Спроектировать и собрать view-плагин для Obsidian Bases: заметки из запроса показываются карточками, разложенными по колонкам, а по колонкам легко ориентироваться и отбирать нужное. Вид должен жить в переключателе Bases рядом со штатными и не ломать встроенный тулбар (сортировку, фильтр, поиск). + +> **Callout (📌):** Главный риск, сделать вид, который конфликтует со штатным тулбаром Bases. Тогда пользователь теряет сортировку и поиск, ради которых Bases и берут. Этого нельзя было допустить. + +## Решение + +Плагин добавляет в Bases вид «Columns». Карточки строятся из результатов Bases-запроса, группировка задаётся свойством, а отбор, кликабельными тегами. + +### Группировка по любому свойству + +Колонки формируются из Group by в тулбаре Bases, по тегу, статусу, категории или любому frontmatter-свойству. Меняешь свойство в запросе, и доска пересобирается сама. Так одна и та же база закрывает разные разрезы: проекты по статусу, заметки по тегам, задачи по приоритету. + +### Фильтр тегов по И и ИЛИ + +Это то, ради чего плагин и задумывался. Под тулбаром, пилюли тегов, по клику они отсеивают карточки по всем колонкам сразу: + +- Левый клик по тегу, показать только его (сбрасывает остальные фильтры). +- Левый клик по следующему тегу, показать заметки с ЛЮБЫМ из выбранных (режим ИЛИ). +- Ctrl+Клик или правый клик, переключить тег, не сбрасывая выбор. +- Кнопка OR/AND, переключить между «любой из» и «все перечисленные» для нескольких тегов. +- All, снять все фильтры. + +Связка «ИЛИ-добавление левым кликом + точный toggle через Ctrl» получилась интуитивной: быстрый отбор не сбивает то, что уже выбрано. Альтернативу, вынести фильтр в модалку, отверг сразу: лишний клик и потеря контекста того, что уже отфильтровано. + +### Карточки и обложки + +По каждой заметке рисуются свойства как чипы, текст, теги, ссылки, булевы, даты. Можно показать обложку: из первой картинки в заметке или из отдельного свойства. У обложки полный контроль, соотношение сторон (Auto, 1:1, 3:2, 4:3, 16:9), рамка, вписывание (cover/contain), ориентация и место на карточке (над заголовком, под ним, после всего). + +### Что заведено в настройки + +Всю внешнюю вариативность я вынел в gear-меню, разбив на четыре секции, чтобы вид подстраивался под разные базы, а не под мой единственный сценарий. + +**General** + +- Куда открывать карточку: активная панель, всплывающее окно, новая вкладка, сплит вправо / вниз. +- Ширина карточки (px). +- Колонок в группе (1–6), сетка внутри колонки. +- Зебра колонок (чередование фона для сканирования). +- Masonry-раскладка (карточки заполняют вертикальные пустоты). + +**Title** + +- Перенос заголовка, жирный заголовок, размер шрифта (px). + +**Properties** + +- Раскладка чипов: стек (вертикально) или сетка (компактно в строку). +- Перенос многострочных значений, размер шрифта. +- Формат даты и даты-времени + локаль (Moment.js). + +**Cover** + +- Источник: нет / первая картинка в заметке / свойство-обложка. +- Стиль (без рамки / с рамкой), соотношение сторон, ориентация, вписывание, позиция на карточке. + +> **Callout (💡):** Сплит-превью сделал как в самом Obsidian: левый клик переиспользует панель, Ctrl+Клик, новую вкладку внутри сплита. Без этого на широком экране карточки неудобно просматривать рядом с доской. + +### Работает внутри штатного тулбара + +Вид цепляется к BasesView-фреймворку, поэтому сортировка, фильтр, свойства и поиск из тулбара Bases остаются рабочими. Плагин только рисует результат запроса, данные не дублируются и не зависят от отдельного хранилища. + +## Результат + +Вид закрыл мою боль: Bases-запрос теперь читается как доска, а не как плоская лента карточек. Группировка по свойству + отбор по тегам И/ИЛИ, то, чего не хватало штатному виду. Всё крутится на запросе Bases, так что перенос vault-а и синхронизация ничего не ломают. + +Метрики у пет-проекта нет, и придумывать её не стану. Проверяю проще: юзабилити на себе в ежедневной работе. Критерий, сколько действий нужно, чтобы отобрать заметки по двум тегам и найти нужную карточку. В штатном виде карточек это был скролл ленты с ручным поиском по заголовкам; здесь, два клика по пилюлям и колонка перед глазами. Если бы замерял всерьёз, считал бы время на задачу на выборке из 20–30 заметок до и после, но на своей повседневной базе разница заметна и без секундомера. + +Что сделал бы иначе. Drag-and-drop карточек между колонками не стал делать сознательно, это меняет свойство в заметке, и для view-плагина это отдельный риск целостности данных. Но вижу запрос на это у пользователей, и, возможно, добавлю как опциональный режим. А пока плагин живёт вручную (через community-каталог ещё не прошёл), это единственное, что мешает им пользоваться сходу. + +> **Callout (✅):** Главный вывод: вид для Bases получается рабочим только тогда, когда он уважает тулбар фреймворка. Я добавил представление, а не вторую систему хранения, и заметки остались обычными заметками. + +## Медиа-плейсхолдеры (в HTML закомментированы) + +| Слот | Файл | Что показывать | +|---|---|---| +| Обзор | `overview.webp` | Вид Card Columns поверх `.base` файла | +| Колонки | `board.webp` | Колонки карточек с группировкой по свойству | +| Фильтр | `filter.webm` / `filter.mp4` + `filter-poster.webp` | Фильтрация по тегам, OR и AND | +| Настройки | `settings.webp` | Gear-меню с секциями | + +Ещё один слот (`note-before.webp`) остался в шаблоне из ранней версии и сейчас не используется, удалить при вставке медиа. + +## Источники фактуры + +- Репозиторий: `https://github.com/mamadaevv/card-columns` (лицензия MIT). +- Все параметры кастомизации и логика фильтрации взяты из README репозитория. +- Drag-and-drop в плагине **отсутствует**: он есть у соседнего `ewerx/obsidian-bases-kanban`, не путать. diff --git a/two.mjs b/two.mjs new file mode 100644 index 0000000..a70dab6 --- /dev/null +++ b/two.mjs @@ -0,0 +1,14 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1200)); +const top = await p.evaluate(()=>{ const r=document.querySelector('[data-shader-demo]').getBoundingClientRect(); return r.top+window.scrollY; }); +await p.evaluate((t)=>window.scrollTo(0, t-150), top); +await new Promise(r=>setTimeout(r,700)); +await p.screenshot({path:'/tmp/t1.png'}); +await new Promise(r=>setTimeout(r,2500)); +await p.screenshot({path:'/tmp/t2.png'}); +console.log('done'); +await b.close(); diff --git a/twoframes.mjs b/twoframes.mjs new file mode 100644 index 0000000..d6fc21b --- /dev/null +++ b/twoframes.mjs @@ -0,0 +1,21 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,800)); +const r = await p.evaluate(()=>{ + const m=document.querySelector('[data-shader-demo] .card-shader').paperShaderMount; + const gl=m.gl; + // два render подряд, потому что drawArrays инициализируется лениво на 1-м вызове + m.render(performance.now()); m.render(performance.now()); + const px=new Uint8Array(4); gl.readPixels(Math.floor(m.canvasElement.width/2), Math.floor(m.canvasElement.height/2),1,1,gl.RGBA,gl.UNSIGNED_BYTE,px); + // и с разным временем + m.setFrame(0); m.render(performance.now()); m.render(performance.now()); + const px0=new Uint8Array(4); gl.readPixels(50,50,1,1,gl.RGBA,gl.UNSIGNED_BYTE,px0); + m.setFrame(10000); m.render(performance.now()); m.render(performance.now()); + const px1=new Uint8Array(4); gl.readPixels(50,50,1,1,gl.RGBA,gl.UNSIGNED_BYTE,px1); + return { centerAfter2: Array.from(px), at0: Array.from(px0), at10000: Array.from(px1) }; +}); +console.log(JSON.stringify(r,null,1)); +await b.close(); diff --git a/ucol.mjs b/ucol.mjs new file mode 100644 index 0000000..05414b5 --- /dev/null +++ b/ucol.mjs @@ -0,0 +1,20 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1200)); +const r = await p.evaluate(()=>{ + const m=document.querySelector('[data-shader-demo] .card-shader').paperShaderMount; + const gl=m.gl; + const out=[]; + for(let i=0;i<4;i++){ + const loc=gl.getUniformLocation(m.program,'u_colors['+i+']'); + const v=new Float32Array(4); + gl.getUniform(gl,m.program,loc,v); // WebGL2? попробуем + out.push(Array.from(v)); + } + return { uColors: out, colorsCount: (()=>{const l=gl.getUniformLocation(m.program,'u_colorsCount');const v=new Int32Array(1);try{gl.getUniform(gl,m.program,l,v);}catch(e){return 'err';}return v[0];})() }; +}); +console.log(JSON.stringify(r,null,1)); +await b.close(); diff --git a/uloc.mjs b/uloc.mjs new file mode 100644 index 0000000..bc49e49 --- /dev/null +++ b/uloc.mjs @@ -0,0 +1,22 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1000)); +const r = await p.evaluate(()=>{ + const m=document.querySelector('[data-shader-demo] .card-shader').paperShaderMount; + const gl=m.gl; + // u_time location + const tloc = gl.getUniformLocation(m.program, 'u_time'); + // прочитаем текущее значение u_time (если location валиден) + let tval=null; + try { const buf=new Float32Array(1); gl.getUniform(gl, m.program, tloc); } catch(e){} + // применим setFrame и проверим, что вообще рисуется (readPixels центр) + m.setFrame(500); + const px=new Uint8Array(4); + gl.readPixels(Math.floor(m.canvasElement.width/2), Math.floor(m.canvasElement.height/2), 1,1, gl.RGBA, gl.UNSIGNED_BYTE, px); + return { hasProgram: !!m.program, uTimeLocIsNull: tloc===null, centerPx: Array.from(px) }; +}); +console.log(JSON.stringify(r,null,1)); +await b.close(); diff --git a/utime.mjs b/utime.mjs new file mode 100644 index 0000000..20467cf --- /dev/null +++ b/utime.mjs @@ -0,0 +1,19 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1200)); +const r = await p.evaluate(()=>{ + const m=document.querySelector('[data-shader-demo] .card-shader').paperShaderMount; + const gl=m.gl; + const uloc=gl.getUniformLocation(m.program,'u_time'); + const read=(x,y)=>{const px=new Uint8Array(4);gl.readPixels(x,y,1,1,gl.RGBA,gl.UNSIGNED_BYTE,px);return Array.from(px);}; + const draw=()=>{gl.useProgram(m.program);gl.drawArrays(gl.TRIANGLES,0,6);}; + gl.uniform1f(uloc,0); draw(); const a=read(360,220); + gl.uniform1f(uloc,50); draw(); const c=read(360,220); + gl.uniform1f(uloc,200); draw(); const d=read(360,220); + return { t0:a, t50:c, t200:d }; +}); +console.log(JSON.stringify(r,null,1)); +await b.close(); diff --git a/verify.mjs b/verify.mjs new file mode 100644 index 0000000..2ab838b --- /dev/null +++ b/verify.mjs @@ -0,0 +1,23 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1200)); +const r = await p.evaluate(()=>{ + const m=document.querySelector('[data-shader-demo] .card-shader').paperShaderMount; + const gl=m.gl; + gl.getError(); + m.render(performance.now()); m.render(performance.now()); // прогрев drawArrays + const px=new Uint8Array(4); gl.readPixels(360,220,1,1,gl.RGBA,gl.UNSIGNED_BYTE,px); + const a=m.canvasElement.toDataURL('image/png'); + m.setFrame(0); m.render(performance.now()); m.render(performance.now()); + const p0=new Uint8Array(4); gl.readPixels(40,40,1,1,gl.RGBA,gl.UNSIGNED_BYTE,p0); + const u0=m.canvasElement.toDataURL('image/png'); + m.setFrame(10000); m.render(performance.now()); m.render(performance.now()); + const p1=new Uint8Array(4); gl.readPixels(40,40,1,1,gl.RGBA,gl.UNSIGNED_BYTE,p1); + const u1=m.canvasElement.toDataURL('image/png'); + return { centerOpaque: Array.from(px), at0: Array.from(p0), at10000: Array.from(p1), dataURLchanged: u0!==u1 }; +}); +console.log(JSON.stringify(r,null,1)); +await b.close(); diff --git a/vp.mjs b/vp.mjs new file mode 100644 index 0000000..402c6de --- /dev/null +++ b/vp.mjs @@ -0,0 +1,21 @@ +import puppeteer from 'puppeteer'; +const b = await puppeteer.launch({ args:['--no-sandbox','--disable-setuid-sandbox','--use-gl=angle','--enable-unsafe-swiftshader','--ignore-gpu-blocklist'] }); +const p = await b.newPage(); +await p.setViewport({width:1100,height:1300}); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1200)); +const r = await p.evaluate(()=>{ + const m=document.querySelector('[data-shader-demo] .card-shader').paperShaderMount; + const gl=m.gl; + const before={ w:m.canvasElement.width, h:m.canvasElement.height, vp:[gl.getParameter(gl.VIEWPORT)[0],gl.getParameter(gl.VIEWPORT)[1],gl.getParameter(gl.VIEWPORT)[2],gl.getParameter(gl.VIEWPORT)[3]] }; + // принудительно resize + render + m.handleResize?.(); + m.render(performance.now()); m.render(performance.now()); + const px=new Uint8Array(4); gl.readPixels(360,220,1,1,gl.RGBA,gl.UNSIGNED_BYTE,px); + // макс по canvas + const W=m.canvasElement.width,H=m.canvasElement.height; const buf=new Uint8Array(W*H*4); gl.readPixels(0,0,W,H,gl.RGBA,gl.UNSIGNED_BYTE,buf); + let maxc=0; for(let i=0;i{ if(['error','warning'].includes(m.type())) errs.push(m.type()+': '+m.text()); }); +p.on('pageerror',e=>errs.push('PAGEERR: '+e.message)); +await p.goto('http://10.200.201.3:4175/_lab/neu-card.html',{waitUntil:'load',timeout:60000}); +await new Promise(r=>setTimeout(r,1500)); +const r = await p.evaluate(()=>{ + const wrap=document.querySelector('[data-shader-demo] .card-shader'); + const card=document.querySelector('[data-shader-demo]'); + const cv=wrap?wrap.querySelector('canvas'):null; + // можно ли создать webgl вручную? + const test=document.createElement('canvas'); test.width=10; test.height=10; + const gl=test.getContext('webgl')||test.getContext('webgl2'); + return { + wrapW: wrap?Math.round(wrap.getBoundingClientRect().width):0, + wrapH: wrap?Math.round(wrap.getBoundingClientRect().height):0, + cardW: card?Math.round(card.getBoundingClientRect().width):0, + cardH: card?Math.round(card.getBoundingClientRect().height):0, + canvasExists: !!cv, + canvasW: cv?cv.width:0, canvasH: cv?cv.height:0, + canvasCssW: cv?Math.round(cv.getBoundingClientRect().width):0, + webglWorks: !!gl, + renderer: gl?gl.getParameter(gl.VERSION):'none', + }; +}); +console.log(JSON.stringify({r, errs}, null, 1)); +await b.close();