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.