Example: Bonus Cashback sticker¶
Manifest row 09 · built today as betta-bonus-cashback-sticker
Two rows, no separate hero: a small BONUS in blue on a yellow panel, and one wide red panel
tipped 2.5° reading $100 CASHBACK* in white with a raised asterisk. Both uppercase, no shadows,
no small print. The two panels wipe on from the left four frames apart and breathe together.
flowchart TB
subgraph scene["scene-cashback (one motion graph per ratio)"]
ROOT["sticker-root<br/>flowBox · column · absolute (120,120) · pulse"]
BONUS["bonus · text on yellow pill · flow · wipe"]
CASH["cashback<br/>text: price run + text run, on red pill · flow · wipe"]
BONUS -- flowIn --> ROOT
CASH -- flowIn --> ROOT
end
The document¶
{
"id": "scene-cashback",
"durationInFrames": 75,
"layers": [
// ── Only three layers. Nothing here sits outside the pulse, so the root carries it.
{
"id": "sticker-root",
"type": "flowBox",
"zIndex": 0,
"layout": {
"mode": "absolute",
"16:9": { "x": 120, "y": 120, "w": "content", "h": "content", "anchor": "top-left" },
"9:16": { /* own position */ },
"1:1": { /* own position */ }
},
"props": { "direction": "column", "alignAcross": "centre", "gap": 39 },
"animations": [
{ "animation": "pulse", "params": { "scale": 1.05, "periodInFrames": 40, "delay": 30 } }
]
},
{
"id": "bonus",
"type": "text",
"zIndex": 2,
"layout": {
"mode": "flow",
"16:9": { "flowIn": "sticker-root", "w": "content", "h": "content" },
"9:16": { "flowIn": "sticker-root", "w": "content", "h": "content" },
"1:1": { "flowIn": "sticker-root", "w": "content", "h": "content" }
},
"typography": {
"16:9": { "family": "GT Eesti Display", "weight": 700, "size": 117, "color": "#054f8e", "case": "uppercase", "horizontalAlign": "center" }
},
"props": {
"content": [ { "value": "Bonus" } ],
"background": { "fill": "#ffd007", "radius": 5, "padding": { "top": 60, "right": 47, "bottom": 57, "left": 46 } }
},
"animations": [
{ "animation": "wipe", "params": { "direction": "from-left", "durationInFrames": 12 },
"at": { "mode": "cueOffset", "cue": "start", "offsetFrames": 0 } }
]
},
// ── One text layer, two runs on one line: a bindable price, then " CASHBACK*". The pill,
// the tilt, the wipe, and the pulse all belong to this one layer.
{
"id": "cashback",
"type": "text",
"zIndex": 1,
"layout": {
"mode": "flow",
"16:9": { "flowIn": "sticker-root", "w": "content", "h": "content", "rotation": -2.5 },
"9:16": { "flowIn": "sticker-root", "w": "content", "h": "content", "rotation": -2.5 },
"1:1": { "flowIn": "sticker-root", "w": "content", "h": "content", "rotation": -2.5 }
},
"typography": {
"16:9": { "family": "GT Eesti Display", "weight": 700, "size": 117, "color": "#ffffff", "case": "uppercase" }
},
"props": {
"content": [
{ "price": { "amount": 100, "currency": "AUD", "locale": "en-AU" } },
{ "value": " Cashback*" }
],
"patterns": [ { "expression": "\\*$", "typography": { "size": { "ofRoot": 0.58 }, "baseline": 0.45 } } ],
"background": { "fill": "#fe2121", "radius": 5, "padding": { "top": 58, "right": 49, "bottom": 46, "left": 43 } }
},
"animations": [
{ "animation": "wipe", "params": { "direction": "from-left", "durationInFrames": 12 },
"at": { "mode": "cueOffset", "cue": "start", "offsetFrames": 4 } }
]
}
]
}
| Note | Binding target | Control |
|---|---|---|
| Editable text | bonus.props.content[0].value, cashback.props.content[1].value |
text |
| Editable amount | cashback.props.content[0].price.amount |
number |
| Change colours | bonus.props.background.fill, cashback.props.background.fill, *.typography[ratio].color |
colour |
| Pulse toggle per panel | no numeric leaf — see stress points | — |
What the engine does with it¶
Resolve. The price run formats to $ and 100; the text run follows it on the same line; the
asterisk pattern styles the last glyph of the concatenated line. Two at references resolve.
Solve. Two content-sized texts measure — cashback as one line spanning both runs, with the
lifted asterisk in its ink box. sticker-root stacks them centred with a 39px gap at (120, 120).
The 2.5° tilt stays out of packing.
Animate. The root pulses; both pills inherit it and compose it with their own resting tilt.
Place. Three elements, two pills.
What this shows¶
- The inline-price hard case, in the wild. A price run and a text run share one line, one pill, one tilt; the amount is a bindable number and the word stays editable text.
- When nothing needs to sit outside the pulse, the root is the group — the smallest lockup in the family is three layers.
Stress points¶
The family-wide wipe-versus-transform-only and unbindable-pulse-toggle points from the full stack apply. Two specific to this one:
- Patterns over a line that contains a price run.
patternsmatch "the concatenation of every run's value" (Text Foundation → Content-item text runs), but a price run has novalueuntil Resolve formats it. Whether the pattern string sees$100 CASHBACK*(formatted, after Price runs) orCASHBACK*alone (text runs only) is undefined, and the two give different match offsets for anything anchored to the start. The\*$here is end-anchored so it works either way, but the rule needs stating: patterns run after price formatting, over the formatted line. case: "uppercase"and price parts. Uppercase on the layer's typography should leave$100untouched (no letters), but the rule that typographycaseapplies to formatted price text at all isn't written down; a currency code likeAUDshown as a fallback would be affected.