Skip to content

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. patterns match "the concatenation of every run's value" (Text Foundation → Content-item text runs), but a price run has no value until Resolve formats it. Whether the pattern string sees $100 CASHBACK* (formatted, after Price runs) or CASHBACK* 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 $100 untouched (no letters), but the rule that typography case applies to formatted price text at all isn't written down; a currency code like AUD shown as a fallback would be affected.