Skip to content

Example: Home Appliance Cashback

Manifest row 10 · built today as betta-home-appliance-cashback

A category tab: HOME in white beside a tilted yellow APPLIANCE panel with blue text and a soft shadow, CASHBACK in white below, then two lines of small print. Everything but the panel is white on a coloured scene background, which is why the reference composite shows only the panel. The panel wipes on; every white line fades on and slides down on its own delay; HOME, APPLIANCE and CASHBACK each pulse.

flowchart TB
    subgraph scene["scene (16:9 graph)"]
        ROOT["hac-stack<br/>flowBox · column · absolute"]
        ROW["hac-title-row<br/>flowBox · row · alignAcross capHeight · flow"]
        HOME["hac-home<br/>text 'HOME' · flow · pulse"]
        APP["hac-appliance<br/>text 'APPLIANCE' on yellow · flow · rotation -5 · pulse"]
        CB["hac-cashback<br/>text 'CASHBACK' · flow · pulse"]
        FINE["hac-fine-print<br/>flowBox · column · flow"]
        FOOT["hac-footnote<br/>text · flow"]
        TERMS["hac-terms<br/>text · flow"]
        ROW -- flowIn --> ROOT
        HOME -- flowIn --> ROW
        APP -- flowIn --> ROW
        CB -- flowIn --> ROOT
        FINE -- flowIn --> ROOT
        FOOT -- flowIn --> FINE
        TERMS -- flowIn --> FINE
    end

The document

{
  "id": "scene-home-appliance",
  "durationInFrames": 75,
  "layers": [

    // ── Root column. Content-sized both ways; its box IS the lockup.
    //    One gap for the whole column (the built component uses 74 / 58 between rows — see Stress points).
    {
      "id": "hac-stack",
      "type": "flowBox",
      "layout": {
        "mode": "absolute",
        "16:9": { "x": 120, "y": 120, "w": "content", "h": "content", "anchor": "top-left" }
        // "9:16": …, "1:1": …
      },
      "props": { "direction": "column", "alignAcross": "centre", "gap": 66 }
    },

    // ── Title row: HOME beside the panel, aligned on their cap lines. The built code nudges HOME
    //    down 34px by hand; capHeight alignment says the same thing without a number.
    {
      "id": "hac-title-row",
      "type": "flowBox",
      "layout": {
        "mode": "flow",
        "16:9": { "flowIn": "hac-stack", "w": "content", "h": "content" }
      },
      "props": { "direction": "row", "alignAcross": "capHeight", "gap": 38 }
    },
    {
      "id": "hac-home",
      "type": "text",
      "layout": { "mode": "flow", "16:9": { "flowIn": "hac-title-row", "w": "content", "h": "content" } },
      "typography": { "16:9": { "family": "GT Eesti Display", "weight": 700, "size": 142, "color": "#ffffff", "case": "uppercase" } },
      "props": { "content": [ { "value": "Home" } ] },
      "animations": [
        { "animation": "slideFade", "params": { "from": "top", "distancePercent": 40, "durationInFrames": 12 },
          "at": { "mode": "cueOffset", "cue": "start", "offsetFrames": 4 } },
        { "animation": "pulse", "params": { "scale": 1.05, "periodInFrames": 40 },
          "at": { "mode": "cueOffset", "cue": "start", "offsetFrames": 30 } }
      ]
    },
    {
      "id": "hac-appliance",
      "type": "text",
      "layout": {
        "mode": "flow",
        "16:9": { "flowIn": "hac-title-row", "w": "content", "h": "content", "rotation": -5 }
      },
      "typography": { "16:9": { "family": "GT Eesti Display", "weight": 700, "size": 137, "color": "#054f8e", "case": "uppercase" } },
      "props": {
        "content": [ { "value": "Appliance" } ],
        "background": {
          "fill": "#ffd100", "radius": 4,
          "padding": { "top": 35, "right": 19, "bottom": 31, "left": 26 },   // see Stress points
        }
      },
      "effects": [ { "type": "dropShadow", "color": "#000000", "opacity": 0.26, "blurPx": 4, "offsetX": 5, "offsetY": 6 } ],
      "animations": [
        { "animation": "wipe", "params": { "direction": "from-left", "durationInFrames": 12 },
          "at": { "mode": "cueOffset", "cue": "start", "offsetFrames": 0 } },
        { "animation": "pulse", "params": { "scale": 1.05, "periodInFrames": 40 },
          "at": { "mode": "cueOffset", "cue": "start", "offsetFrames": 30 } }
      ]
    },

    // ── CASHBACK, bare white, the base size.
    {
      "id": "hac-cashback",
      "type": "text",
      "layout": { "mode": "flow", "16:9": { "flowIn": "hac-stack", "w": "content", "h": "content" } },
      "typography": { "16:9": { "family": "GT Eesti Display", "weight": 700, "size": 173, "color": "#ffffff", "case": "uppercase" } },
      "props": { "content": [ { "value": "Cashback" } ] },
      "animations": [
        { "animation": "slideFade", "params": { "from": "top", "distancePercent": 40, "durationInFrames": 12 },
          "at": { "mode": "cueOffset", "cue": "start", "offsetFrames": 10 } },
        { "animation": "pulse", "params": { "scale": 1.05, "periodInFrames": 40 },
          "at": { "mode": "cueOffset", "cue": "start", "offsetFrames": 30 } }
      ]
    },

    // ── Small print as its own tight column so the two lines sit 15px apart, not 66.
    {
      "id": "hac-fine-print",
      "type": "flowBox",
      "layout": { "mode": "flow", "16:9": { "flowIn": "hac-stack", "w": "content", "h": "content" } },
      "props": { "direction": "column", "alignAcross": "centre", "gap": 15 }
    },
    {
      "id": "hac-footnote",
      "type": "text",
      "layout": { "mode": "flow", "16:9": { "flowIn": "hac-fine-print", "w": "content", "h": "content" } },
      "typography": { "16:9": { "family": "GT Eesti Display", "weight": 400, "size": 69, "color": "#ffffff", "tracking": -0.7 } },
      "props": {
        "content": [ { "value": "With Selected Products*" } ],
        "patterns": [ { "expression": "\\*$", "typography": { "size": { "ofRoot": 0.58 }, "baseline": 0.45 } } ]
      },
      "animations": [
        { "animation": "slideFade", "params": { "from": "top", "distancePercent": 40, "durationInFrames": 12 },
          "at": { "mode": "cueOffset", "cue": "start", "offsetFrames": 16 } }
      ]
    },
    {
      "id": "hac-terms",
      "type": "text",
      "layout": { "mode": "flow", "16:9": { "flowIn": "hac-fine-print", "w": "content", "h": "content" } },
      "typography": { "16:9": { "family": "GT Eesti Display", "weight": 400, "size": 29, "color": "#ffffff", "tracking": -0.3 } },
      "props": { "content": [ { "value": "*Via Digital Visa Gift Card. Via redemption. T&Cs apply." } ] },
      "animations": [
        { "animation": "slideFade", "params": { "from": "top", "distancePercent": 40, "durationInFrames": 12 },
          "at": { "mode": "cueOffset", "cue": "start", "offsetFrames": 18 } }
      ]
    }
  ]
}
What the notes want editable Bound field Control
Text values hac-*.props.content[0].value text
Text colours hac-*.typography[ratio].color colour
Panel colour hac-appliance.props.background.fill colour
Pulse toggle per part hac-{home,appliance,cashback}.animations[pulse].params.scale1 is "off" see Stress points

Panel shadows are layer effects

Each panel above carries a drop shadow in the source artwork. A shadow is a per-layer effect, applied to the whole layer (glyphs and pill together), not a field on background — see Coordinate System & Sizing. Where text and its panel need different shadows, they become two layers: a text layer and a background layer spanning it, each with its own effects.

What the engine does with it

Resolve. Every at resolves against this scene's start. The four slideFades and the wipe expand into keyframe tracks staggered 0/4/10/16/18 frames; the three pulses expand into continuous scale tracks from frame 30. The footnote's asterisk pattern resolves its ofRoot size.

Solve. Topological order: the five text layers, then hac-title-row and hac-fine-print, then hac-stack. Each text measures at content size; the row packs HOME and APPLIANCE on their cap lines (the panel's padding is paint-only, so it's the text's cap line, not the yellow box's top); the columns stack centred. APPLIANCE's −5° is not in the packing.

Animate. Each pulsing layer scales about its own content centre. APPLIANCE composes rotation, pulse scale and the wipe's clip. Flow membership carries the root's transform (identity here).

Place. Seven positioned elements; the panel's shadow and fill paint around its solved box.

What this shows

  • capHeight alignment replaces a hand-tuned nudge. The built offset.y: 34 on HOME exists only because flex has no cap-line alignment; alignAcross: "capHeight" is the intent.
  • Paint-only padding works as designed. The gap between HOME and the panel is measured panel-edge to ink, exactly as the PSD shows, with no double-counting.
  • Invisible-in-the-reference copy is just white text, which the notes call out; nothing in the schema needs to know the scene background.

Stress points

  • No per-member spacing in a flowBox (Positioning → flowBox's layout vocabulary). The source has three distinct row gaps (74, 58, 15). A single gap forces either a nested flowBox per distinct gap or an averaged value; this page uses 66 for two of them. A per-member margin (or a per-gap list) is the obvious fix.
  • wipe is not a transform or opacity animation (Motion Graph → What animates). A left-to-right reveal is a clip-path animation. The transform-only rule cannot express it, yet every component in this catalogue uses it.
  • Pulse pivot. The built lockup scales every pulsing part about the lockup's shared centre; each member here pulses about its own content centre (Coordinate System → Pivot). A flow member cannot pin to a pinPoint (one mode per layer), so the escape hatch is unavailable to exactly the layers that need it.
  • Per-part pulse toggle has no bindable home (Positioning → Bindings target numeric leaves only). Turning an animation on and off is structural; params.scale = 1 as "off" is a hack, not a design.
  • background.shadow doesn't existbackgroundSchema has fill/opacity/radius/padding only. The brief's extents definition already names "shadow spread" as a paint-only contributor (Coordinate System → Bounds), so the field is implied but unspecified.