fix(studio): center vertical composition thumbnails in sidebar (#820)

* feat(studio): add pasteboard background to preview viewport

Adds bg-neutral-800 to the preview viewport so the area outside the
canvas is visually distinct from the composition content — consistent
with professional video editors (Premiere, DaVinci, Figma).

* feat(studio): pasteboard background and canvas outline around preview

- NLEPreview: viewport gets bg-neutral-700 (#404040) as the pasteboard
  color surrounding the canvas — distinct from the app chrome (#0a0a0a)
- Player wrapper: drop bg-black so the pasteboard shows around the canvas
  (loading overlays still cover the area with bg-black during load)
- Player: set host background to transparent via inline style (overrides
  :host { background: #000 } in shadow DOM), and inject a style rule into
  the open shadow root so .hfp-container has overflow:visible and the
  canvas iframe gets a thin white ring + soft drop-shadow — making the
  canvas boundary legible against the pasteboard

* fix(studio): center vertical composition thumbnails in sidebar

Portrait (and other non-16:9) compositions were pinning to the top-left
of the 80x45 thumbnail slot because transform-origin was '0 0'. Compute
the centering offsets from the scaled dimensions and apply them as
left/top so any aspect ratio renders centred in the slot.
This commit is contained in:
Miguel Ángel
2026-05-14 05:05:49 +02:00
committed by GitHub
parent 3468db6f37
commit 2a2a88ce1f
@@ -8,6 +8,8 @@ interface CompositionsTabProps {
}
const DEFAULT_PREVIEW_STAGE = { width: 1920, height: 1080 };
const CARD_W = 80;
const CARD_H = 45;
const THUMBNAIL_SEEK_TIME_SECONDS = 3;
const THUMBNAIL_PLAYBACK_SYNC_ATTEMPTS = 10;
@@ -132,11 +134,13 @@ function CompCard({
const name = comp.replace(/^compositions\//, "").replace(/\.html$/, "");
const previewUrl = `/api/projects/${projectId}/preview/comp/${comp}`;
const previewScale = resolveCompositionPreviewScale({
cardWidth: 80,
cardHeight: 45,
cardWidth: CARD_W,
cardHeight: CARD_H,
stageWidth: stageSize.width,
stageHeight: stageSize.height,
});
const thumbnailOffsetX = (CARD_W - stageSize.width * previewScale) / 2;
const thumbnailOffsetY = (CARD_H - stageSize.height * previewScale) / 2;
useEffect(() => {
requestIframePlaybackSync(hovered);
@@ -166,11 +170,13 @@ function CompCard({
src={previewUrl}
sandbox="allow-scripts allow-same-origin"
loading="lazy"
className="absolute left-0 top-0 border-none pointer-events-none"
className="absolute border-none pointer-events-none"
style={{
transformOrigin: "0 0",
width: stageSize.width,
height: stageSize.height,
left: thumbnailOffsetX,
top: thumbnailOffsetY,
transform: `scale(${previewScale})`,
}}
onLoad={(e) => {