Skip to content
Docs
Open Promoeyes
expand-input

expand-input

EXPAND-INPUT primitives — thirteen scenes on the one thing an input does that no still can fake: it RECEIVES something, and then it changes size.

Read the rig's notes
THE SPINE IS THE ANCHOR. Every expansion in this reel is the same two
channels — a `scale` and the `position` that holds one edge still — and the
only thing that differs is WHICH edge is nailed. For a shell of height H
driven to scale s, the offset is:

  top edge pinned      position.y = -H(1-s)/2      S2 composer, S5 palette,
                                                   S6 calendar, S10 dropzone
  bottom edge pinned   position.y = +H(1-s)/2      S3 reply box
  centre pinned        (no position channel)       S7 voice pill
  left edge pinned     position.x = -W(1-s)/2      S11 rail
  two edges at once    both of the above           S8 formula editor
  neither (a fly)      size AND centre both move   S9 compose sheet
  no scale at all      the SCENE boundary does it  S12 ask → answer

MEASURED vs KEYED — the one real fork in the grammar.
  A WORLD flow container (`row`/`column`/`grid`/`stack`/`isle`/`wheel`)
  re-measures its children EVERY FRAME (lib/flow-layout-3d.ts), so a child
  animating scale 0.001 → 1 physically pushes its siblings. That is S4, and
  nothing in that row has an authored x.
  A ui STAGE resolves its Yoga pass ONCE at build (layout/flex-scene.ts).
  Nothing inside a `<ui>` or `<ui3d>` ever reflows. Every other scene here
  therefore keys its geometry by hand — not for want of trying.

FOUR THINGS A ui ITEM CANNOT ANIMATE, all of them found the hard way:
  1. `w` / `h`      — there is no size channel. Author the FINAL box and
                      drive `scale` + `position`; that is the whole reel.
  2. `fill`/`stroke`— not channels. A colour change is two stacked cards
                      crossfading (S6's selected days, S9's ink swap).
  3. `columns`      — picks a program, not a value. Four-up → three-up is
                      two authored sets on one beat (S11).
  4. `assembly`     — shards a NODE. A day cell is a ui item inside a
                      stage, so S6's month is 31 authored staggers instead.

PAINT ORDER vs DEPTH. Two coplanar items in ONE `ui3d(overlay=screen)`
stack by DFS declaration order, and a second stage does NOT break the tie —
applyOverlayRenderOrder restarts its index per stage. S1 lifts its hero with
`render_order=1` (a real z-index, +1000 indices for the whole subtree).
S8 sidesteps the question entirely: two separate free-standing panels at
two world z, so they occlude by geometry and no order is argued about.

LOOK · light mode throughout. Near-white page, indigo accent (#4f46e5),
slate ink (#0f172a / #12151c), muted (#94a3b8). Haze is PIGMENT (the
glow-sprite default, NormalBlending) — additive lobes are invisible on a
bright ground, and so are additive morph particles, which is why S12 sets
`morph-blend=normal` and an explicit `morph-color`.

  layer  colour    base size (x the scene's k)   opacity   role
  hz1    #a2abd4   1180 x 980                    0.34      upper-left wash
  hz2    #b3a2d2    980 x 820                    0.30      lower-right wash
  hz3    #96a2ca   1120 x 900                    0.28      left cushion
  hz4    #a89bcb    900 x 760                    0.26      upper-right cushion
  hz5    #93a9cd   1260 x 1020                   0.22      full-frame floor
  hz6    #c3aecd    840 x 700                    0.24      lower-left cushion
  k = 1.0 phone scenes · 1.4-1.7 panel and page scenes. All six carry the
  same `match` keys the whole way, so every gap morphs the set.
  bk1..6  #6366f1 / #22d3ee / #f59e0b / #a78bfa — S1 ONLY, 16-58mm dots
  parked 190-430mm BEHIND the phone and held at 22% opacity until the rack
  starts, so they arrive as bokeh discs rather than as sharp specks.

CAMERA GRAMMAR · every shot is an explicit `frame` keyframe (absolute
position/look_at — a camera is never rebased). Three staging distances:
  phone   ~400mm   S1, S9
  panel   ~1050-1150mm   S2, S3, S4, S6, S7, S10, S12
  page    ~1200-1400mm   S5, S8, S11
S1 is the only scene carrying DOF: it opens at focus 406 / dof_range 900
(nothing blurs) and the pop racks it to focus 210 / dof_range 100 /
dof_blur 24 — the phone is then 150mm behind the focal plane, 1.5 ranges
out, gone. Writing no `focus` anywhere else means no DOF pass is injected.

TYPEWRITER · always a `typewriter` PROPERTY channel (0→1), never an
`<animate effect=…>` track — the channel form is what applyItemProperties
feeds to text.ts's redraw, and the caret rides the reveal for free. Key it
BACKWARDS (1 → 0.3) and the line un-types, which is how S5 grows its
palette back.

DEVICE / STAGE MILLIMETRES
  iphone      159.5 tall, glass 71 x 154, glass plane at local z = +6.3
  overlay     `width`/`height` on a ui3d(overlay=screen) IS the glass in mm
  free ui3d   `position` is the stage's BOTTOM-LEFT corner, 1 stage unit
              = 1 world mm

Scene map · 13 scenes, 131.5s
  01 s1-search     11.0  search + popout + bokeh rack    → crash-out
  02 s2-compose    12.5  minimal AI line → workspace     → slow-shutter
  03 s3-autogrow   11.0  one line-height per wrap        → whip-up
  04 s4-tags       10.5  the input yields width (world)  → whip-left
  05 s5-palette    11.5  grows, shrinks, and grows back  → crash-in
  06 s6-calendar   10.5  expansion into structure        → rack-focus
  07 s7-voice      10.5  a pill swells on two axes       → dolly-zoom-out
  08 s8-cell       10.5  escaping a 153 x 56 cell        → whip-right
  09 s9-sheet      10.5  pill takes the whole screen     → spin-in
  10 s10-drop       9.5  expanding to CATCH an arrival   → foreground-whip
  11 s11-rail      10.5  the input pushes the page       → whip-up
  12 s12-ask        5.5  a field with nowhere to grow    → match-move 1.1
  13 s12-answer     7.5  it becomes the answer           → none

Capture: nx run tools:capture:webgl -- --project promos/primitives/input/expand-input.promo --width 1920 --height 1080
Preview: /packages/runtime/index.html?project=promos/primitives/input/expand-input.promo
Topic
uis
Scenes
13
Kind
reference rig
//
    EXPAND-INPUT primitives — thirteen scenes on the one thing an input does
    that no still can fake: it RECEIVES something, and then it changes size.

    THE SPINE IS THE ANCHOR. Every expansion in this reel is the same two
    channels — a `scale` and the `position` that holds one edge still — and the
    only thing that differs is WHICH edge is nailed. For a shell of height H
    driven to scale s, the offset is:

      top edge pinned      position.y = -H(1-s)/2      S2 composer, S5 palette,
                                                       S6 calendar, S10 dropzone
      bottom edge pinned   position.y = +H(1-s)/2      S3 reply box
      centre pinned        (no position channel)       S7 voice pill
      left edge pinned     position.x = -W(1-s)/2      S11 rail
      two edges at once    both of the above           S8 formula editor
      neither (a fly)      size AND centre both move   S9 compose sheet
      no scale at all      the SCENE boundary does it  S12 ask → answer

    MEASURED vs KEYED — the one real fork in the grammar.
      A WORLD flow container (`row`/`column`/`grid`/`stack`/`isle`/`wheel`)
      re-measures its children EVERY FRAME (lib/flow-layout-3d.ts), so a child
      animating scale 0.001 → 1 physically pushes its siblings. That is S4, and
      nothing in that row has an authored x.
      A ui STAGE resolves its Yoga pass ONCE at build (layout/flex-scene.ts).
      Nothing inside a `<ui>` or `<ui3d>` ever reflows. Every other scene here
      therefore keys its geometry by hand — not for want of trying.

    FOUR THINGS A ui ITEM CANNOT ANIMATE, all of them found the hard way:
      1. `w` / `h`      — there is no size channel. Author the FINAL box and
                          drive `scale` + `position`; that is the whole reel.
      2. `fill`/`stroke`— not channels. A colour change is two stacked cards
                          crossfading (S6's selected days, S9's ink swap).
      3. `columns`      — picks a program, not a value. Four-up → three-up is
                          two authored sets on one beat (S11).
      4. `assembly`     — shards a NODE. A day cell is a ui item inside a
                          stage, so S6's month is 31 authored staggers instead.

    PAINT ORDER vs DEPTH. Two coplanar items in ONE `ui3d(overlay=screen)`
    stack by DFS declaration order, and a second stage does NOT break the tie —
    applyOverlayRenderOrder restarts its index per stage. S1 lifts its hero with
    `render_order=1` (a real z-index, +1000 indices for the whole subtree).
    S8 sidesteps the question entirely: two separate free-standing panels at
    two world z, so they occlude by geometry and no order is argued about.

    LOOK · light mode throughout. Near-white page, indigo accent (#4f46e5),
    slate ink (#0f172a / #12151c), muted (#94a3b8). Haze is PIGMENT (the
    glow-sprite default, NormalBlending) — additive lobes are invisible on a
    bright ground, and so are additive morph particles, which is why S12 sets
    `morph-blend=normal` and an explicit `morph-color`.

      layer  colour    base size (x the scene's k)   opacity   role
      hz1    #a2abd4   1180 x 980                    0.34      upper-left wash
      hz2    #b3a2d2    980 x 820                    0.30      lower-right wash
      hz3    #96a2ca   1120 x 900                    0.28      left cushion
      hz4    #a89bcb    900 x 760                    0.26      upper-right cushion
      hz5    #93a9cd   1260 x 1020                   0.22      full-frame floor
      hz6    #c3aecd    840 x 700                    0.24      lower-left cushion
      k = 1.0 phone scenes · 1.4-1.7 panel and page scenes. All six carry the
      same `match` keys the whole way, so every gap morphs the set.
      bk1..6  #6366f1 / #22d3ee / #f59e0b / #a78bfa — S1 ONLY, 16-58mm dots
      parked 190-430mm BEHIND the phone and held at 22% opacity until the rack
      starts, so they arrive as bokeh discs rather than as sharp specks.