Skip to content
Docs
Open Promoeyes
camera-framing-devices

camera-framing-devices

Camera-framing primitives · DEVICES + UI — the companion reel to camera-framing.pug. Same `<frame>` pose track, same parameters; the targets here are device rigs, their screens, browser windows and UI panels.

Read the rig's notes
Read camera-framing.pug first for the parameter catalogue (node · fill · fov ·
padding · offset · side · bounds · mesh · mesh_match · up · zoom · smooth).
This reel is about what those parameters DO once the target is a real rig.

THE ONE RULE THAT CHANGES EVERYTHING:

  Framing a device frames its LIVE DISPLAY, not its casing and not its
  bounding box.

Two mechanisms, both in packages/engine/lib/frame-shot-target.ts:

  1. findMeshInNode — with no `mesh` given it prefers the largest surface the
     runtime tagged as a live display, which is exactly the screen of an
     <iphone> / <laptop> / <ipad> / <chrome>. Without that preference the raw
     largest-area fallback picks the CASING: a laptop's unibody shell beats
     its screen panel, and a tablet's aluminium body beats its glass. Largest
     display wins, which is also why a <chrome> frames the whole browser
     window rather than just its content pane (scene 04).

  2. frameSurfaceBasis — for a live display it solves the shot's plate from
     that surface's OWN UVs instead of assuming "local +Z is the front and
     local X/Y are width and height". That assumption holds for a flat plane
     and for almost no real device: a laptop's meshes sit under a net 90°
     pitch with the lid leaning back on top of it, and a tablet's under the
     opposite pitch with its display UVs rotated 180°. Both also carry their
     screen height on the depth axis, so a naive solve gets the DISTANCE
     wrong too, not just the angle. Scene 05 is that fix, on screen.

  A UV-solved plate is also ORIENTED: its sign already points out of the
  readable face. `side="auto"` therefore never second-guesses it — see scene
  03, where a phone turned away from the camera still frames its glass.

WHAT THIS REEL DELIBERATELY DOES NOT DO: reach inside a rig. Devices are
authored ONLY as <iphone> / <ipad> / <laptop> / <chrome> — never a
hand-wired model — and framing never names a mesh inside one. It does not
need to: "frame this device" already means "frame its screen", and
bounds="union" already means "frame the whole product". The `mesh` /
`mesh_match` selectors are for YOUR OWN authored node ids inside a container
you built; the companion reel's scene 06 is their catalogue, and nothing
here wants them.

HOW TO FRAME A PART OF A UI. There are two halves to this, and only the
first one is a limitation:

  · An item painted into a device's flat `<ui>` TEXTURE is not framable. It
    is pixels in a canvas, not a node, so there are no world bounds for a
    framed kf to solve against and `node="some-card"` never resolves.

  · But a `<ui3d overlay>` nested in a device IS framable. It mounts a real
    surface-anchored node flush on the glass, in the same pixel space as the
    screen texture, and its items are true plane meshes with world bounds.
    So the way to frame a REGION of a screen is to author that region as an
    overlay panel and frame it by id. Scene 08 is the catalogue.

The overlay form:

    iphone#phone(scale=1.2)
      ui()
        card(anchor=top-left w=512 h=1024 r=0 fill=#10162c)
      ui3d#focus-row(overlay=screen lift=0.012 width=512 height=1024)
        card(x=30 y=300 w=452 h=120 r=22 fill=#f2f4f9)

    kf(time=3 node=focus-row bounds=union fill=70 side=front)

`overlay` is only a MARKER (any value works; `screen` is the convention). The
stage covers the WHOLE screen, so you pick the sub-region purely with the
child card's x / y / w / h in screen pixels.

STATE THE STAGE EXPLICITLY, and match the device: iphone 512x1024, laptop
1280x800, ipad 768x1024, chrome 1440x900. In principle the overlay's stage is
auto-pinned to the device's screen space (promoDeviceScreenOverlays, xml-scene
3621) — but the generic ui3d defaults already stamped stage 1920x1080 onto the
source by then (applyUi3dAuthoringDefaults, xml-scene 3130), and the pin only
fills a NULL, so it never fires. Leave the stage off and every coordinate
inside the overlay is silently interpreted against 1920x1080: a card authored
at w=452 on a phone renders about a quarter of the screen wide instead of
nearly full width. (This bites the existing reference scenes too — the
banners in promos/projects/overlay-scenes/ render at roughly 27% scale for
exactly this reason.) Still do NOT author width / height (clobbered with the
canvas size for a node source) or map_* (that is the texture UV transform).

The node's own position track is in stage px with +z out of the glass, so
"0 0 0" is flush and a card can fly off the screen and land back.

Framing one: use `bounds="union"` (measure the panel's whole subtree) and
`side="front"`. An overlay card is not a tagged display, so its plate is
unoriented and `auto` may flip toward the overview point. Keep the first
framed beat a couple of seconds in — overlays attach in a deferred job once
the model has loaded, and an empty box makes the kf fall back to a coordinate
pose. Expect the shot to come out looser than the numbers suggest: a <card>
pads its plane about 40px per side for shadow bleed, so its bounds run ~80px
wider and taller than the box you authored. On a full-width row that padding
makes the card WIDER than the screen, so the width term binds the solve and
`fill` stops behaving like "percent of frame height" — the rows below sit at
70 to read as the subject. Stack overlapping overlays with `lift` (0.012,
0.013, 0.014) and `render_order`.

The OTHER idiom is a free-standing <ui3d> panel mounted on nothing: its own
node, its own place in the world. Scene 06 frames three of them; scene 07
clusters small ones in front of a phone. Use overlays when the UI belongs to
the SCREEN, panels when it belongs to the SCENE.

AUTHORING TRAPS (same two as the companion reel, still true here):
  · a `<g>` is an IDENTITY container — its position/rotation are folded into
    an SVG child context, not applied as a 3D transform. Only use one as a
    framing target (scenes 06, 07), never to place things. `<row>` is NOT a
    `<g>`: a flow container is a normal node and its transform DOES apply
    (scene 09).
  · a `<rect>` with no `position` anchors SVG-style at its top-left.
  · a label parked at the top of a scene is gone the moment the camera dives
    into a screen — so beat labels here either ride the device's own UI or
    sit on its glass, pre-rotated to the rig's yaw.

Scene map:
  01 device → screen    one <iphone> at 35° yaw, framed at 45 / 70 / 88
  02 bounds on a rig    the display panel alone vs bounds=union (whole laptop)
  03 side on a screen   a phone turned AWAY: auto still frames the glass
  04 chrome + its UI    the whole browser window, then parked with offset
  05 tilted panels      a laptop lid's lean and a tablet's rotated UVs
  06 ui3d panels        three free-standing UI surfaces, framed in turn
  07 UI beside a device a panel cluster floating in front of a phone
  08 UI ON a device     <ui3d overlay> rows on the glass, framed one by one
  09 device row         framing flow-laid cells — zero authored coordinates
  10 a MOVING device    one framed hold; the pose re-solves every frame
  11 INTO a website     a yawed browser page dived into from the side, on DOF

Preview: /packages/runtime/index.html?project=promos/primitives/camera-framing/camera-framing-devices.promo
Topic
camera
Scenes
10
Kind
reference rig
//
    Camera-framing primitives · DEVICES + UI — the companion reel to
    camera-framing.pug. Same `<frame>` pose track, same parameters; the targets
    here are device rigs, their screens, browser windows and UI panels.

    Read camera-framing.pug first for the parameter catalogue (node · fill · fov ·
    padding · offset · side · bounds · mesh · mesh_match · up · zoom · smooth).
    This reel is about what those parameters DO once the target is a real rig.

    THE ONE RULE THAT CHANGES EVERYTHING:

      Framing a device frames its LIVE DISPLAY, not its casing and not its
      bounding box.

    Two mechanisms, both in packages/engine/lib/frame-shot-target.ts:

      1. findMeshInNode — with no `mesh` given it prefers the largest surface the
         runtime tagged as a live display, which is exactly the screen of an
         <iphone> / <laptop> / <ipad> / <chrome>. Without that preference the raw
         largest-area fallback picks the CASING: a laptop's unibody shell beats
         its screen panel, and a tablet's aluminium body beats its glass. Largest
         display wins, which is also why a <chrome> frames the whole browser
         window rather than just its content pane (scene 04).

      2. frameSurfaceBasis — for a live display it solves the shot's plate from
         that surface's OWN UVs instead of assuming "local +Z is the front and
         local X/Y are width and height". That assumption holds for a flat plane
         and for almost no real device: a laptop's meshes sit under a net 90°
         pitch with the lid leaning back on top of it, and a tablet's under the
         opposite pitch with its display UVs rotated 180°. Both also carry their
         screen height on the depth axis, so a naive solve gets the DISTANCE
         wrong too, not just the angle. Scene 05 is that fix, on screen.

      A UV-solved plate is also ORIENTED: its sign already points out of the
      readable face. `side="auto"` therefore never second-guesses it — see scene
      03, where a phone turned away from the camera still frames its glass.

    WHAT THIS REEL DELIBERATELY DOES NOT DO: reach inside a rig. Devices are
    authored ONLY as <iphone> / <ipad> / <laptop> / <chrome> — never a
    hand-wired model — and framing never names a mesh inside one. It does not
    need to: "frame this device" already means "frame its screen", and
    bounds="union" already means "frame the whole product". The `mesh` /
    `mesh_match` selectors are for YOUR OWN authored node ids inside a container
    you built; the companion reel's scene 06 is their catalogue, and nothing
    here wants them.

    HOW TO FRAME A PART OF A UI. There are two halves to this, and only the
    first one is a limitation:

      · An item painted into a device's flat `<ui>` TEXTURE is not framable. It
        is pixels in a canvas, not a node, so there are no world bounds for a
        framed kf to solve against and `node="some-card"` never resolves.

      · But a `<ui3d overlay>` nested in a device IS framable. It mounts a real
        surface-anchored node flush on the glass, in the same pixel space as the
        screen texture, and its items are true plane meshes with world bounds.
        So the way to frame a REGION of a screen is to author that region as an
        overlay panel and frame it by id. Scene 08 is the catalogue.

    The overlay form:

        iphone#phone(scale=1.2)
          ui()
            card(anchor=top-left w=512 h=1024 r=0 fill=#10162c)
          ui3d#focus-row(overlay=screen lift=0.012 width=512 height=1024)
            card(x=30 y=300 w=452 h=120 r=22 fill=#f2f4f9)

        kf(time=3 node=focus-row bounds=union fill=70 side=front)

    `overlay` is only a MARKER (any value works; `screen` is the convention). The
    stage covers the WHOLE screen, so you pick the sub-region purely with the
    child card's x / y / w / h in screen pixels.

    STATE THE STAGE EXPLICITLY, and match the device: iphone 512x1024, laptop
    1280x8