Canvas raster fallback, CDP rest-state screenshots, lottie overlay containment

- Visible canvases (>=48px) are captured as PNG stills at capture time
  (toDataURL, element-screenshot fallback for tainted/webgl) and emitted as
  cid-carrying <img> elements filling the captured box, so canvas-driven
  bands no longer render blank
- Source AND clone full-page screenshots now capture via CDP
  Page.captureScreenshot with captureBeyondViewport: no scroll-stitching,
  no scroll events, so scroll-linked animations can't smear mid-scrub
  states into perceptual evidence (pixel-identical to the old path on
  static pages; Playwright fallback retained)
- DittoLottie's runtime overlay stays position:absolute/inset:0 for its
  whole life and the host carries data-ditto-lottie with a scoped
  svg/canvas fit rule, so the runtime animation always fills the pinned
  per-viewport box instead of escaping to aspect height

259 tests pass (12 new), typecheck clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Samraaj Bath
2026-07-04 00:36:08 -07:00
co-authored by Claude Fable 5
parent e33354cfb5
commit 15d1e7a9e3
9 changed files with 426 additions and 8 deletions
+5
View File
@@ -32,6 +32,11 @@ button, input, select, textarea, optgroup { font: inherit; color: inherit; backg
button { cursor: pointer; }
table { border-collapse: separate; border-spacing: 0; }
img, picture, video, canvas, svg { display: inline-block; }
/* Lottie runtime fit: the player re-mounts its svg/canvas into an absolute overlay that fills
the host's captured (per-viewport, definite) box. Force that runtime media to fit the box so
an aspect-mismatched viewBox (e.g. a portrait animation in a shorter, letterboxed source box)
can't inflate past the pinned height. Scoped to the runtime-marked host only. */
[data-ditto-lottie] > div > svg, [data-ditto-lottie] > div > canvas { width: 100%; height: 100%; display: block; }
h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
/* Neutralize UA text defaults to inherit so the generator's default-skipping is
correct: a property equal to its inherited value is simply not emitted. */