Skip to content

Portrait render shows only the top ~1080px when html/body CSS keeps the scaffold's 1920×1080 size #4001

Description

@develoverli

Describe the bug

A composition whose root declares data-width="1080" data-height="1920" renders a 1080×1920 MP4, but content is only visible in the top 1080px; everything below y=1080 is clipped and
shows the page background.

This happens constantly with agent-authored portrait (9:16 / TikTok / Reels) videos. The creation workflows scaffold with npx hyperframes init ... --example=blank without
--resolution (e.g. skills/general-video/SKILL.md, skills/motion-graphics/SKILL.md), so the project starts at 1920×1080. The agent then switches the root data-width/data-height
to portrait but leaves the template's html, body { width: 1920px; height: 1080px; overflow: hidden; } and <meta name="viewport" content="width=1920, height=1080"> untouched. Users
have to ask the agent to "fix the cropped frames" by hand.

Why:

  • Viewport and output size come from the root data attributes (producer htmlCompiler.ts, engine frameCapture.ts) → 1080×1920. Correct.
  • The runtime forces #root to 1080×1920 (core/src/runtime/init.ts, applyCompositionSizing / applyClipLayout). Correct.
  • But <body> is still 1920×1080 with overflow: hidden, so it clips the root at y=1080.

Nothing reports it:

  • hyperframes lint has no rule comparing root data-width/data-height with the html, body CSS size or the meta viewport (only root_missing_dimensions).
  • hyperframes check layout audits (canvas_overflow, panel_out_of_canvas) measure against the root rect, which the runtime already forced to the correct size, so the body clip is
    invisible to them.

In the default blank template the bug is easy to miss: the centered title sits at y≈960 (just above the cut) and html/body share the same dark background, so the repro below adds a
colored root and a bottom-anchored element.

Related but different: #587 (same symptom; cause was snapshot ignoring root dimensions, already fixed).

Link to reproduction

https://gist.github.com/develoverli/7990c44bb95b063f86fe40acf27ed3f9

Steps to reproduce

  1. npx hyperframes init repro --non-interactive --example blank
  2. Replace index.html with the file below. The only changes vs the scaffold: root set to data-width="1080" data-height="1920", #root given a red background, and a green bar
    anchored at bottom: 40px. The html, body CSS and meta viewport are left as scaffolded.
  3. npx hyperframes lint → passes, no warning
  4. npx hyperframes render
  5. Open the MP4
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=1920, height=1080" />
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>
    <style>
      * { margin: 0; padding: 0; box-sizing: border-box; }
      html,
      body {
        margin: 0;
        width: 1920px;
        height: 1080px;
        overflow: hidden;
        background: #0a0a0a;
      }
      #root {
        background: #c0392b;
        width: 100%;
        height: 100%;
        display: flex;
        align-items: center;
        justify-content: center;
        font-family: Inter, ui-sans-serif, system-ui, sans-serif;
      }
      #title { color: #f4f4f5; font-size: 72px; font-weight: 600; }
    </style>
  </head>
  <body>
    <div id="root" data-composition-id="main" data-start="0" data-duration="10"
         data-width="1080" data-height="1920">
      <div id="bottom" style="position:absolute;left:0;right:0;bottom:40px;height:200px;background:#27ae60"></div>
      <h1 id="title" class="clip" data-start="0" data-duration="10" data-track-index="0">Title</h1>
    </div>
    <script>
      const tl = gsap.timeline({ paused: true });
      window.__timelines["main"] = tl;
      tl.seek(0);
    </script>
  </body>
</html>

Expected behavior

Either the full 1080×1920 canvas is usable (red root fills the frame, green bar visible near the bottom), or hyperframes lint warns that the root dimensions disagree with the html, body CSS size / meta viewport, with a fix hint (update both, or scaffold with init --resolution portrait).

Actual behavior

Output is 1080×1920. The red root only covers y=0–1080; the rest of the frame is the dark body background, and the green bar at bottom: 40px is not visible (clipped). lint passes
with no warning. Screenshot of a frame attached.

Image

Environment

✓ Version          0.8.41 (latest)
  ✓ Node.js          v24.18.0 (win32 x64)
  ✓ CPU              12 cores · AMD Ryzen 5 7600X 6-Core Processor @ 4691MHz
  ✓ Memory           31.1 GB total · 3.5 GB available
  ✓ Disk             977.5 GB free
  ✓ Frames cache     C:\Users\develover\AppData\Local\Temp\hyperframes-extract-cache-u · 616.2 GB free at C:\Users\develover\AppData\Local\Temp · default
  ✓ Archive extractor Built into Windows
  ✓ Environment      Native terminal
  ✗ whisper-cpp      Not found (optional — needed for transcription)
                     Build with cmake: https://github.com/ggml-org/whisper.cpp#building
  ✗ TTS (Kokoro)     Not installed (optional — local voice fallback)
                     pip install kokoro-onnx soundfile
  ✗ BGM (MusicGen)   Not installed (optional — local music fallback)
                     pip install transformers torch soundfile numpy
  ✓ FFmpeg           ffmpeg 9.0.1-full_build-www.gyan.dev at C:\Users\develover\AppData\Local\Microsoft\WinGet\Packages\Gyan.FFmpeg_Microsoft.Winget.Source_8wekyb3d8bbwe\ffmpeg-9.0.1-full_build\bin\ffmpeg.exe
  ✓ FFprobe          ffprobe 9.0.1-full_build-www.gyan.dev at C:\Users\develover\AppData\Local\Microsoft\WinGet\Packages\Gyan.FFmpeg_Microsoft.Winget.Source_8wekyb3d8bbwe\ffmpeg-9.0.1-full_build\bin\ffprobe.exe
  ✓ Chrome           cache: C:\Users\develover\.cache\puppeteer\chrome-headless-shell\win64-152.0.7977.54\chrome-headless-shell-win64\chrome-headless-shell.exe
  ✓ Docker           Docker version 29.7.2, build a7dcaa6
  ✓ Docker running   Running

Additional context

Reproduced with [email protected] on Windows 11.

Proposed fix (happy to open a PR): a warning-level lint rule, e.g. root_dimensions_mismatch, in packages/lint/src/rules/core.ts next to root_missing_dimensions. It would compare
the root data-width/data-height against html, body { width/height: Npx } and <meta name="viewport" content="width=N, height=N"> on the top-level composition only (sub-composition
CSS is already scoped by the compiler). It can reuse the patterns applyResolutionPreset (cli/src/commands/init.ts) already uses to rewrite those three places together.

A complementary follow-up could pass --resolution from the brief in the workflows' init calls.

Activity

  1. miga-heygen commented on Sep 16, 2026

    @miga-heygen
    Contributor

    Reproduced, fix in progress.

  2. added a commit that references this issue on Sep 16, 2026
    b4fbc3e
  3. added a commit that references this issue on Sep 16, 2026
    eb0957a
  4. added a commit that references this issue on Sep 16, 2026
    963612b
  5. added a commit that references this issue on Sep 16, 2026
    d6fe667
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions