diff --git a/packages/studio/src/components/nle/NLEPreview.test.ts b/packages/studio/src/components/nle/NLEPreview.test.ts index 1ade62366f..5eaaa09452 100644 --- a/packages/studio/src/components/nle/NLEPreview.test.ts +++ b/packages/studio/src/components/nle/NLEPreview.test.ts @@ -158,6 +158,15 @@ describe("NLEPreview", () => { globalThis.ResizeObserver = originalResizeObserver; }); + it("draws the canvas boundary on the preview stage", () => { + const view = renderPreview(); + + expect(view.stage.style.boxShadow).toBe( + "0 0 0 1px rgba(255,255,255,0.08), 0 4px 32px rgba(0,0,0,0.7)", + ); + view.cleanup(); + }); + it("pans the preview with middle mouse drag", () => { const view = renderPreview(); const target = document.createElement("div"); diff --git a/packages/studio/src/components/nle/NLEPreview.tsx b/packages/studio/src/components/nle/NLEPreview.tsx index 3806b77823..802a95232f 100644 --- a/packages/studio/src/components/nle/NLEPreview.tsx +++ b/packages/studio/src/components/nle/NLEPreview.tsx @@ -451,6 +451,9 @@ export const NLEPreview = memo(function NLEPreview({ transform: `translate3d(${toDomPrecision(initial.panX)}px, ${toDomPrecision(initial.panY)}px, 0) scale(${toDomPrecision(initial.zoomPercent / 100)})`, // resolvePreviewWheelZoom cursor math assumes center-center pivot transformOrigin: "center center", + // Keep Studio chrome outside the scaled composition iframe. Painting + // this on the iframe creates a subpixel seam at fractional fit sizes. + boxShadow: "0 0 0 1px rgba(255,255,255,0.08), 0 4px 32px rgba(0,0,0,0.7)", }} data-testid="preview-zoom-stage" > diff --git a/packages/studio/src/player/components/Player.test.ts b/packages/studio/src/player/components/Player.test.ts index 07e7df14e3..a49f75b3e2 100644 --- a/packages/studio/src/player/components/Player.test.ts +++ b/packages/studio/src/player/components/Player.test.ts @@ -22,6 +22,7 @@ class TestHyperframesPlayer extends HTMLElement { constructor() { super(); + this.attachShadow({ mode: "open" }).appendChild(this.iframeElement); const addIframeListener = this.iframeElement.addEventListener.bind(this.iframeElement); this.iframeElement.addEventListener = ((type, listener, options) => { @@ -155,6 +156,15 @@ describe("preview errors", () => { }); }); +describe("preview canvas chrome", () => { + it("does not paint Studio chrome on the scaled composition iframe", async () => { + const { player } = await mountPlayer(); + const injectedStyles = Array.from(player.shadowRoot?.querySelectorAll("style") ?? []); + + expect(injectedStyles.some((style) => style.textContent?.includes("box-shadow"))).toBe(false); + }); +}); + describe("composition loading overlay", () => { it("shows while the composition is loading", () => { expect(shouldShowCompositionLoadingOverlay(true)).toBe(true); diff --git a/packages/studio/src/player/components/Player.tsx b/packages/studio/src/player/components/Player.tsx index 50ca3794ee..da719d9ce0 100644 --- a/packages/studio/src/player/components/Player.tsx +++ b/packages/studio/src/player/components/Player.tsx @@ -281,18 +281,6 @@ export const Player = forwardRef( player.setAttribute("src", src); container.appendChild(player); - // Inject pasteboard shadow: let the shadow around the canvas bleed - // into the surrounding pasteboard area (overflow: visible on the container) - // and add a subtle outline + drop-shadow so the canvas boundary reads - // against the gray pasteboard, consistent with professional editors. - if (player.shadowRoot) { - const pasteboardStyle = document.createElement("style"); - pasteboardStyle.textContent = - ".hfp-container{overflow:visible}" + - ".hfp-iframe{box-shadow:0 0 0 1px rgba(255,255,255,0.08),0 4px 32px rgba(0,0,0,.7)}"; - player.shadowRoot.appendChild(pasteboardStyle); - } - enableInteractiveIframe(player); cleanup = () => {