mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-14 18:01:20 +08:00
2a33c81f34
drawElementImage cannot paint CSS 3D rendering contexts: backface- visibility:hidden is ignored (flip cards capture their mirrored backface, even at rest), siblings of the 3D context drop out of the capture, and the context's background is lost. Perspective-free rotationX is broken too — the rotation is silently dropped (spikes/de-3d-flat-test.mjs). Standalone repro: spikes/de-3d-probe.mjs. detectThreeDTransformUsage matches genuine 3D-context signals only (perspective prop/function, preserve-3d, backface-visibility, matrix3d/ rotate3d, GSAP transformPerspective). Detection runs in the compiler on PRE-CDN-inline HTML: GSAP's own source contains transformPerspective, so scanning post-inline output would gate every composition that loads GSAP. Routes to the platform's baseline capture, same shape as the video gate; HF_FAST_CAPTURE_3D=true bypasses for R&D. Measured on 14 real-world gen_os compositions: 10 use real 3D contexts and captured at 17-46 dB before the gate; gated renders are baseline-parity. Also ignore the puppeteer-downloaded chrome/ tree in oxlint. Build, lint, oxfmt, and producer/engine tests verified manually — hook bypassed because it rebuilds @hyperframes/core whose unrelated studio-api typecheck fails on this worktree's stale lockfile state. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
46 lines
2.3 KiB
JavaScript
46 lines
2.3 KiB
JavaScript
import puppeteer from "puppeteer";
|
|
import { writeFileSync } from "node:fs";
|
|
const W=400,H=300;
|
|
const HTML=`<!doctype html><meta charset=utf-8>
|
|
<style>*{margin:0;padding:0}html,body{width:${W}px;height:${H}px;background:#ece8dd}
|
|
#root{position:relative;width:${W}px;height:${H}px;background:#ece8dd}
|
|
.headline{position:absolute;top:20px;left:0;width:100%;text-align:center;font:700 28px serif;color:#1a2640}
|
|
#el{position:absolute;top:100px;left:100px;width:200px;height:60px;background:#1a2640;color:#fff;
|
|
font:700 24px serif;text-align:center;line-height:60px}
|
|
#footer{position:absolute;top:220px;left:0;width:100%;text-align:center;font:700 20px serif;color:#406080}</style>
|
|
<div id=root><div class=headline>HEADLINE</div><div id=el>CARD</div><div id=footer>FOOTER</div></div>
|
|
<script>
|
|
const root=document.getElementById("root");
|
|
const canvas=document.createElement("canvas");
|
|
canvas.setAttribute("layoutsubtree","");canvas.width=${W};canvas.height=${H};
|
|
canvas.style.cssText="display:block;position:absolute;top:0;left:0";
|
|
root.parentNode.insertBefore(canvas,root);canvas.appendChild(root);
|
|
window.__cap=(tf)=>{
|
|
document.getElementById("el").style.transform=tf;
|
|
const ctx=canvas.getContext("2d");
|
|
return new Promise(r=>requestAnimationFrame(()=>setTimeout(()=>{
|
|
try{ctx.clearRect(0,0,${W},${H});ctx.drawElementImage(root,0,0);}catch(e){return r({err:String(e)});}
|
|
const url=canvas.toDataURL("image/png");ctx.clearRect(0,0,${W},${H});r({url});
|
|
},30)));
|
|
};
|
|
</script>`;
|
|
const b=await puppeteer.launch({headless:true,args:["--no-sandbox","--enable-features=CanvasDrawElement","--use-gl=angle",`--window-size=${W},${H}`]});
|
|
const p=await b.newPage();await p.setViewport({width:W,height:H});
|
|
await p.setContent(HTML,{waitUntil:"load"});
|
|
const CASES={
|
|
"none":"none",
|
|
"rotate2d":"rotate(10deg)",
|
|
"rotateX45-noPersp":"rotateX(45deg)",
|
|
"translate3dZ":"translate3d(0px, 20px, -100px)",
|
|
"gsap-entrance":"translate3d(0px, 60px, -100px) rotateX(-45deg)",
|
|
"translateZ0":"translateZ(0px)",
|
|
};
|
|
for(const [name,tf] of Object.entries(CASES)){
|
|
const r=await p.evaluate((t)=>window.__cap(t),tf);
|
|
if(r.err){console.log(name,"ERR",r.err);continue;}
|
|
writeFileSync(`/tmp/flat3d-${name}.png`,Buffer.from(r.url.split(",")[1],"base64"));
|
|
// size heuristic: blank captures are tiny
|
|
console.log(name, "bytes:", Buffer.from(r.url.split(",")[1],"base64").length);
|
|
}
|
|
await b.close();
|