{
  "id": "mui_holo_card",
  "name": "Holo Card",
  "width": 800,
  "height": 540,
  "cornerRadius": 0,
  "fit": "Layout",
  "backgroundColor": "#07070b",
  "opacity": 1,
  "x": -400,
  "y": -270,
  "responsive": {
    "enabled": true,
    "mode": "constraint",
    "width": {
      "type": "fixed"
    },
    "height": {
      "type": "fixed"
    },
    "maintainAspectRatio": false,
    "scaleObjects": false,
    "breakpoints": [
      {
        "id": "bp_mobile",
        "name": "Mobile",
        "minWidth": 0,
        "maxWidth": 599
      },
      {
        "id": "bp_tablet",
        "name": "Tablet",
        "minWidth": 600,
        "maxWidth": 1023
      },
      {
        "id": "bp_desktop",
        "name": "Desktop",
        "minWidth": 1024
      }
    ]
  },
  "layoutProps": {
    "mode": "linear",
    "direction": "vertical",
    "gap": 0,
    "padding": {
      "top": 0,
      "right": 0,
      "bottom": 0,
      "left": 0
    },
    "alignItems": "stretch",
    "layoutSizingHorizontal": "fixed",
    "layoutSizingVertical": "fixed"
  },
  "seo": {
    "title": "Holo Card — Exode Motion UI",
    "description": "A luxury 3D holographic iridescent foil card with gyro pitch and yaw tilt, dynamic rainbow diffraction sheen, micro sparkle glitter, and EMV chip."
  },
  "stateMachine": {
    "inputs": [
      {
        "id": "in_holder",
        "name": "holder",
        "value": {
          "type": "Text",
          "value": "SATOSHI NAKAMOTO",
          "defaultValue": "SATOSHI NAKAMOTO"
        }
      },
      {
        "id": "in_tier",
        "name": "tier",
        "value": {
          "type": "Text",
          "value": "FOUNDER PASS // 001",
          "defaultValue": "FOUNDER PASS // 001"
        }
      },
      {
        "id": "in_number",
        "name": "number",
        "value": {
          "type": "Text",
          "value": "•••• •••• •••• 2026",
          "defaultValue": "•••• •••• •••• 2026"
        }
      },
      {
        "id": "in_speed",
        "name": "speed",
        "value": {
          "type": "Number",
          "value": 1,
          "defaultValue": 1
        }
      },
      {
        "id": "in_accent",
        "name": "accent",
        "value": {
          "type": "Text",
          "value": "#8b5cf6",
          "defaultValue": "#8b5cf6"
        }
      },
      {
        "id": "in_accent2",
        "name": "accent2",
        "value": {
          "type": "Text",
          "value": "#06b6d4",
          "defaultValue": "#06b6d4"
        }
      },
      {
        "id": "in_bg",
        "name": "bg",
        "value": {
          "type": "Text",
          "value": "#07070b",
          "defaultValue": "#07070b"
        }
      }
    ],
    "layers": [
      {
        "name": "Base",
        "entryStateId": "s_idle",
        "states": [
          {
            "id": "s_idle",
            "name": "Idle",
            "loop": true,
            "transitions": []
          }
        ]
      }
    ],
    "logicNodes": []
  },
  "animations": [],
  "physics": {
    "enabled": false,
    "gravity": {
      "x": 0,
      "y": 9.8
    }
  },
  "animationFlow": [],
  "navigation": [],
  "stateVariables": [],
  "objects": [
    {
      "id": "stage",
      "name": "Stage",
      "type": "Group",
      "transform": {
        "x": 0,
        "y": 0,
        "rotation": 0,
        "scale_x": 1,
        "scale_y": 1,
        "opacity": 1,
        "skew_x": 0,
        "skew_y": 0
      },
      "width": 800,
      "height": 540,
      "geometry": {
        "type": "Rectangle",
        "width": 800,
        "height": 540,
        "cornerRadius": 0
      },
      "cornerRadius": 0,
      "children": [],
      "clipContent": true,
      "style": {
        "fill": {
          "type": "Solid",
          "color": "#07070b",
          "opacity": 1
        },
        "effects": [
          {
            "id": "fx_holo-card",
            "type": "CustomEffect",
            "name": "Holo Card",
            "isEnabled": true,
            "params": {},
            "code": "/* Shared by every motion-ui effect (prepended to each one).\n   Rules for this file and the effects: the .bexode compiler minifies effect\n   code, so use explicit semicolons, block comments only, and never write a\n   minus followed by a negative value (it collapses to a decrement). */\n\n/* Read a live state-machine input, falling back to a default. */\nfunction inp(env, name, fallback) {\n  const v = env.inputs ? env.inputs[name] : undefined;\n  if (v === undefined || v === null || v === '') return fallback;\n  if (typeof fallback === 'number') {\n    const n = Number(v);\n    return isFinite(n) ? n : fallback;\n  }\n  if (typeof fallback === 'boolean') return v === true || v === 1 || v === 'true';\n  return v;\n}\n\n/* '#rgb' / '#rrggbb' to [r, g, b]; anything else falls back to white. */\nfunction rgbOf(hex) {\n  let h = String(hex || '').trim().replace('#', '');\n  if (h.length === 3) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];\n  if (!/^[0-9a-fA-F]{6}$/.test(h)) return [255, 255, 255];\n  const n = parseInt(h, 16);\n  return [(n >> 16) & 255, (n >> 8) & 255, n & 255];\n}\n\nfunction rgba(hex, a) {\n  const c = rgbOf(hex);\n  return 'rgba(' + c[0] + ',' + c[1] + ',' + c[2] + ',' + Math.max(0, Math.min(1, a)) + ')';\n}\n\nfunction mix(a, b, t) {\n  const x = rgbOf(a), y = rgbOf(b);\n  const r = Math.round(x[0] + (y[0] - x[0]) * t);\n  const g = Math.round(x[1] + (y[1] - x[1]) * t);\n  const bl = Math.round(x[2] + (y[2] - x[2]) * t);\n  return '#' + ((1 << 24) + (r << 16) + (g << 8) + bl).toString(16).slice(1);\n}\n\n/* A soft round glow, rendered once per colour and blitted with drawImage\n   (much cheaper than shadowBlur on every particle). */\nconst __glows = {};\nfunction glowSprite(hex) {\n  if (__glows[hex]) return __glows[hex];\n  const s = 64;\n  const c = document.createElement('canvas');\n  c.width = s; c.height = s;\n  const g = c.getContext('2d');\n  const gr = g.createRadialGradient(s / 2, s / 2, 0, s / 2, s / 2, s / 2);\n  gr.addColorStop(0, rgba(hex, 1));\n  gr.addColorStop(0.18, rgba(hex, 0.55));\n  gr.addColorStop(0.45, rgba(hex, 0.14));\n  gr.addColorStop(1, rgba(hex, 0));\n  g.fillStyle = gr;\n  g.fillRect(0, 0, s, s);\n  __glows[hex] = c;\n  return c;\n}\n\nfunction glow(ctx, hex, x, y, r, a) {\n  if (r <= 0 || a <= 0) return;\n  ctx.globalAlpha = Math.min(1, a);\n  ctx.drawImage(glowSprite(hex), x - r, y - r, r * 2, r * 2);\n  ctx.globalAlpha = 1;\n}\n\n/* Frame clock: seconds since start and a clamped delta. */\nfunction tick(S) {\n  const now = performance.now();\n  if (!S.t0) { S.t0 = now; S.last = now; }\n  S.dt = Math.min(0.05, Math.max(0.001, (now - S.last) / 1000));\n  S.last = now;\n  S.age = (now - S.t0) / 1000;\n  return S.dt;\n}\n\n/* Deterministic pseudo-random so layouts are stable across resizes. */\nfunction rng(seed) {\n  let s = seed >>> 0 || 1;\n  return function () {\n    s = (s * 1664525 + 1013904223) >>> 0;\n    return s / 4294967296;\n  };\n}\n\nfunction ease(x) { return x < 0.5 ? 4 * x * x * x : 1 + (Math.pow(2 * x - 2, 3)) / 2; }\nfunction clamp(v, a, b) { return v < a ? a : v > b ? b : v; }\n\nconst SANS = 'Inter, Helvetica, Arial, sans-serif';\nconst MONO = 'ui-monospace, Menlo, monospace';\n\nfunction roundRect(ctx, x, y, w, h, r) {\n  r = Math.min(r, w / 2, h / 2);\n  ctx.beginPath();\n  ctx.moveTo(x + r, y);\n  ctx.arcTo(x + w, y, x + w, y + h, r);\n  ctx.arcTo(x + w, y + h, x, y + h, r);\n  ctx.arcTo(x, y + h, x, y, r);\n  ctx.arcTo(x, y, x + w, y, r);\n  ctx.closePath();\n}\n\n/* Shared backdrop: flat colour, soft vignette and fine grain. */\nfunction backdrop(ctx, W, H, bg, tint, amt) {\n  ctx.fillStyle = bg;\n  ctx.fillRect(0, 0, W, H);\n  if (tint && amt > 0) {\n    const gr = ctx.createRadialGradient(W / 2, H * 0.45, 0, W / 2, H * 0.45, Math.max(W, H) * 0.7);\n    gr.addColorStop(0, rgba(tint, 0.10 * amt));\n    gr.addColorStop(1, rgba(tint, 0));\n    ctx.fillStyle = gr;\n    ctx.fillRect(0, 0, W, H);\n  }\n  const vg = ctx.createRadialGradient(W / 2, H / 2, Math.min(W, H) * 0.3, W / 2, H / 2, Math.max(W, H) * 0.8);\n  vg.addColorStop(0, 'rgba(0,0,0,0)');\n  vg.addColorStop(1, 'rgba(0,0,0,0.45)');\n  ctx.fillStyle = vg;\n  ctx.fillRect(0, 0, W, H);\n}\n\n/* Greedy word wrap with the current ctx.font. */\nfunction wrap(ctx, text, maxW) {\n  const out = [];\n  String(text).split('\\n').forEach(function (para) {\n    const words = para.split(' ');\n    let cur = '';\n    for (let i = 0; i < words.length; i++) {\n      const next = cur ? cur + ' ' + words[i] : words[i];\n      if (cur && ctx.measureText(next).width > maxW) { out.push(cur); cur = words[i]; } else cur = next;\n    }\n    out.push(cur);\n  });\n  return out;\n}\n\n/* Largest font size (up to max) at which text wraps into maxLines within maxW. */\nfunction fitFont(ctx, text, weight, family, maxW, maxLines, max, min) {\n  let fs = max;\n  while (fs > min) {\n    ctx.font = weight + ' ' + fs + 'px ' + family;\n    if (wrap(ctx, text, maxW).length <= maxLines) break;\n    fs = fs * 0.92;\n  }\n  ctx.font = weight + ' ' + fs + 'px ' + family;\n  return fs;\n}\n\n/* Smoothed pointer that idles along a slow figure-eight when the cursor is away. */\nfunction follow(S, env, W, H, k) {\n  const pt = env.pointer || {};\n  const t = S.age || 0;\n  const tx = pt.isHovered ? pt.localX : W / 2 + Math.sin(t * 0.37) * W * 0.3;\n  const ty = pt.isHovered ? pt.localY : H / 2 + Math.sin(t * 0.74) * H * 0.22;\n  if (S.px === undefined) { S.px = tx; S.py = ty; }\n  S.px += (tx - S.px) * Math.min(1, S.dt * (k || 6));\n  S.py += (ty - S.py) * Math.min(1, S.dt * (k || 6));\n  S.inside = !!pt.isHovered;\n  return pt;\n}\n\n/* Optional centred headline over a background effect. */\nfunction title(ctx, W, H, text, sub, ink, accent, bg) {\n  if (!text) return;\n  if (bg) {\n    /* Soft scrim so the headline reads over busy motion. */\n    const sc = ctx.createRadialGradient(W / 2, H / 2, 0, W / 2, H / 2, Math.min(W * 0.5, 560));\n    sc.addColorStop(0, rgba(bg, 0.72));\n    sc.addColorStop(1, rgba(bg, 0));\n    ctx.fillStyle = sc;\n    ctx.fillRect(0, 0, W, H);\n  }\n  ctx.textAlign = 'center';\n  ctx.textBaseline = 'alphabetic';\n  const maxW = Math.min(W * 0.86, 900);\n  const fs = fitFont(ctx, text, 600, SANS, maxW, 2, clamp(W * 0.065, 26, 72), 16);\n  const lines = wrap(ctx, text, maxW);\n  const lh = fs * 1.08;\n  const y0 = H / 2 - (lines.length * lh) / 2 + fs * 0.8 - (sub ? fs * 0.35 : 0);\n  ctx.fillStyle = ink;\n  lines.forEach(function (l, i) { ctx.fillText(l, W / 2, y0 + i * lh); });\n  if (sub) {\n    ctx.font = '500 ' + clamp(fs * 0.24, 11, 15) + 'px ' + MONO;\n    ctx.fillStyle = rgba(accent, 0.85);\n    ctx.fillText(String(sub).toUpperCase(), W / 2, y0 + (lines.length - 1) * lh + fs * 0.9);\n  }\n}\n\n/* Holo Card: a luxury 3D holographic iridescent foil card.\n   Features realistic 3D perspective pitch and yaw tilt, dynamic rainbow\n   diffraction glare that tracks the light angle, micro sparkle glitter,\n   embossed typography, and an electromagnetic pulse beam on click. */\nlet S = null;\n\nfunction render(ctx, env) {\n  const W = env.width, H = env.height;\n  if (!S) {\n    S = {\n      tiltX: 0,\n      tiltY: 0,\n      glarePos: 0.5,\n      pulse: 0,\n      sparkles: [],\n      ripples: [],\n      wasDown: false,\n    };\n    /* Generate 30 fixed sparkle points on the card */\n    for (let i = 0; i < 32; i++) {\n      S.sparkles.push({\n        u: Math.random() - 0.5,\n        v: Math.random() - 0.5,\n        phase: Math.random() * Math.PI * 2,\n        speed: 1.5 + Math.random() * 3,\n      });\n    }\n  }\n  const dt = tick(S);\n\n  const cardHolder = String(inp(env, 'holder', 'SATOSHI NAKAMOTO'));\n  const cardTier = String(inp(env, 'tier', 'FOUNDER PASS // 001'));\n  const cardNumber = String(inp(env, 'number', '•••• •••• •••• 2026'));\n  const accent = inp(env, 'accent', '#8b5cf6');\n  const accent2 = inp(env, 'accent2', '#06b6d4');\n  const bg = inp(env, 'bg', '#07070b');\n  const speed = clamp(inp(env, 'speed', 1), 0.1, 4);\n\n  ctx.save();\n  ctx.translate(-W / 2, -H / 2);\n\n  /* Deep Canvas Backdrop */\n  ctx.fillStyle = bg;\n  ctx.fillRect(0, 0, W, H);\n\n  const cx = W / 2, cy = H / 2;\n  const isMobile = W < 600;\n  const pt = env.pointer || {};\n  const px = pt.isHovered ? pt.localX : cx;\n  const py = pt.isHovered ? pt.localY : cy;\n  const isDown = !!(pt.isDown || pt.down);\n\n  /* Card Dimensions (standard credit card ratio 1.586) */\n  const cardW = Math.min(W * 0.88, isMobile ? 320 : 420);\n  const cardH = cardW / 1.586;\n\n  /* Target 3D Tilt Angles based on pointer distance from center */\n  const targetTiltX = pt.isHovered ? ((px - cx) / cx) * 16 : Math.sin(S.age * 1.2 * speed) * 8;\n  const targetTiltY = pt.isHovered ? ((py - cy) / cy) * 14 : Math.cos(S.age * 0.9 * speed) * 6;\n\n  S.tiltX += (targetTiltX - S.tiltX) * Math.min(1, dt * 8);\n  S.tiltY += (targetTiltY - S.tiltY) * Math.min(1, dt * 8);\n\n  /* Glare light position along the diagonal */\n  const targetGlare = (S.tiltX + S.tiltY) / 30 + 0.5;\n  S.glarePos += (targetGlare - S.glarePos) * Math.min(1, dt * 10);\n\n  /* Check click on card */\n  const inCard = pt.isHovered && Math.abs(px - cx) < cardW / 2 && Math.abs(py - cy) < cardH / 2;\n  if (!S.wasDown && isDown && inCard) {\n    S.pulse = 1;\n    S.ripples.push({ x: px, y: py, r: 0, alpha: 0.9, color: '#ffffff' });\n    if (typeof env.emit === 'function') env.emit('cardTap');\n  }\n  S.wasDown = isDown;\n\n  S.pulse = Math.max(0, S.pulse - dt * 2.5);\n\n  /* Ambient Glow Behind Card */\n  glow(ctx, accent, cx, cy, cardW * 0.85, 0.4);\n\n  /* ---------- Apply 3D Perspective Transformation ---------- */\n  ctx.save();\n  ctx.translate(cx, cy);\n\n  /* Transform matrix for 3D pitch, yaw, and slight shear */\n  const skewX = S.tiltX * 0.003;\n  const skewY = S.tiltY * 0.003;\n  ctx.transform(1, skewY, skewX, 1, S.tiltX * 1.2, S.tiltY * 1.2);\n\n  /* Card Drop Shadow */\n  ctx.save();\n  ctx.shadowColor = 'rgba(0, 0, 0, 0.65)';\n  ctx.shadowBlur = 36;\n  ctx.shadowOffsetX = -S.tiltX * 2;\n  ctx.shadowOffsetY = 16 - S.tiltY * 2;\n\n  /* Card Dark Base Body */\n  ctx.fillStyle = '#0c0d15';\n  roundRect(ctx, -cardW / 2, -cardH / 2, cardW, cardH, 20);\n  ctx.fill();\n  ctx.restore();\n\n  /* ---------- Holographic Iridescent Foil Gradient ---------- */\n  ctx.save();\n  roundRect(ctx, -cardW / 2, -cardH / 2, cardW, cardH, 20);\n  ctx.clip();\n\n  /* Rainbow diffraction grating bands */\n  const glareCenter = (S.glarePos - 0.5) * cardW * 2;\n  const holoGrad = ctx.createLinearGradient(-cardW / 2 + glareCenter, -cardH / 2, cardW / 2 + glareCenter, cardH / 2);\n  holoGrad.addColorStop(0, 'rgba(255, 0, 128, 0.15)');\n  holoGrad.addColorStop(0.2, 'rgba(255, 128, 0, 0.25)');\n  holoGrad.addColorStop(0.4, 'rgba(0, 255, 200, 0.35)');\n  holoGrad.addColorStop(0.6, 'rgba(0, 128, 255, 0.30)');\n  holoGrad.addColorStop(0.8, 'rgba(180, 0, 255, 0.25)');\n  holoGrad.addColorStop(1, 'rgba(255, 0, 128, 0.15)');\n  ctx.fillStyle = holoGrad;\n  ctx.fillRect(-cardW / 2, -cardH / 2, cardW, cardH);\n\n  /* Sweeping High-Gloss Specular Sheen Beam */\n  const beamX = glareCenter;\n  const beamGrad = ctx.createLinearGradient(beamX - 100, -cardH / 2, beamX + 100, cardH / 2);\n  beamGrad.addColorStop(0, 'rgba(255, 255, 255, 0)');\n  beamGrad.addColorStop(0.5, 'rgba(255, 255, 255, 0.45)');\n  beamGrad.addColorStop(1, 'rgba(255, 255, 255, 0)');\n  ctx.fillStyle = beamGrad;\n  ctx.fillRect(-cardW / 2, -cardH / 2, cardW, cardH);\n\n  /* Shimmering Micro Glitter Sparkles */\n  S.sparkles.forEach(function (sp) {\n    const spX = sp.u * (cardW - 30);\n    const spY = sp.v * (cardH - 30);\n    const intensity = Math.sin(S.age * sp.speed + sp.phase + S.tiltX * 0.2) * 0.5 + 0.5;\n    if (intensity > 0.6) {\n      const sparkleAlpha = (intensity - 0.6) * 2.5;\n      ctx.fillStyle = rgba('#ffffff', sparkleAlpha);\n      ctx.beginPath();\n      ctx.arc(spX, spY, 1.8, 0, Math.PI * 2);\n      ctx.fill();\n    }\n  });\n\n  /* Click Pulse Wave */\n  if (S.pulse > 0.01) {\n    glow(ctx, '#ffffff', 0, 0, cardW * (1 - S.pulse * 0.5), S.pulse * 0.6);\n  }\n\n  ctx.restore();\n\n  /* Card Outer Gradient Rim */\n  const rimGrad = ctx.createLinearGradient(-cardW / 2, -cardH / 2, cardW / 2, cardH / 2);\n  rimGrad.addColorStop(0, rgba(accent2, 0.8));\n  rimGrad.addColorStop(0.5, rgba('#ffffff', 0.5));\n  rimGrad.addColorStop(1, rgba(accent, 0.7));\n  ctx.strokeStyle = rimGrad;\n  ctx.lineWidth = 1.6;\n  roundRect(ctx, -cardW / 2, -cardH / 2, cardW, cardH, 20);\n  ctx.stroke();\n\n  /* ---------- Card Elements (EMV Chip, Logo, Text) ---------- */\n  /* 1. EMV Metallic Gold Chip */\n  const chipX = -cardW / 2 + 32;\n  const chipY = -cardH / 2 + 42;\n  const chipW = 44;\n  const chipH = 34;\n\n  const chipGrad = ctx.createLinearGradient(chipX, chipY, chipX + chipW, chipY + chipH);\n  chipGrad.addColorStop(0, '#fcd34d');\n  chipGrad.addColorStop(0.5, '#d97706');\n  chipGrad.addColorStop(1, '#b45309');\n  ctx.fillStyle = chipGrad;\n  roundRect(ctx, chipX, chipY, chipW, chipH, 6);\n  ctx.fill();\n\n  ctx.strokeStyle = '#78350f';\n  ctx.lineWidth = 1;\n  roundRect(ctx, chipX, chipY, chipW, chipH, 6);\n  ctx.stroke();\n\n  /* Chip Contact Lines */\n  ctx.beginPath();\n  ctx.moveTo(chipX + chipW * 0.45, chipY);\n  ctx.lineTo(chipX + chipW * 0.45, chipY + chipH);\n  ctx.moveTo(chipX, chipY + chipH * 0.5);\n  ctx.lineTo(chipX + chipW, chipY + chipH * 0.5);\n  ctx.stroke();\n\n  /* Contactless Wave Icon */\n  ctx.strokeStyle = 'rgba(255, 255, 255, 0.6)';\n  ctx.lineWidth = 1.5;\n  for (let w = 1; w <= 3; w++) {\n    ctx.beginPath();\n    ctx.arc(chipX + chipW + 16, chipY + chipH / 2, w * 5, -Math.PI * 0.3, Math.PI * 0.3);\n    ctx.stroke();\n  }\n\n  /* 2. Top Right Holographic Brand Emblem */\n  ctx.font = '800 18px ' + SANS;\n  ctx.textAlign = 'right';\n  ctx.textBaseline = 'middle';\n  ctx.fillStyle = '#ffffff';\n  ctx.fillText('EXODE ✦', cardW / 2 - 32, chipY + chipH / 2);\n\n  /* 3. Card Number (Embossed style) */\n  ctx.font = '700 ' + (isMobile ? 16 : 19) + 'px ' + MONO;\n  ctx.textAlign = 'left';\n  ctx.fillStyle = '#ffffff';\n  ctx.fillText(cardNumber, -cardW / 2 + 32, 22);\n\n  /* 4. Cardholder Name & Tier Tag */\n  ctx.font = '600 13px ' + MONO;\n  ctx.fillStyle = 'rgba(255, 255, 255, 0.88)';\n  ctx.fillText(cardHolder, -cardW / 2 + 32, cardH / 2 - 32);\n\n  ctx.font = '700 11px ' + MONO;\n  ctx.textAlign = 'right';\n  ctx.fillStyle = rgba(accent2, 0.95);\n  ctx.fillText(cardTier, cardW / 2 - 32, cardH / 2 - 32);\n\n  ctx.restore();\n\n  /* Animate Ripples */\n  for (let ri = S.ripples.length - 1; ri >= 0; ri--) {\n    const rip = S.ripples[ri];\n    rip.r += dt * 180;\n    rip.alpha -= dt * 2.0;\n    if (rip.alpha <= 0) {\n      S.ripples.splice(ri, 1);\n      continue;\n    }\n    ctx.strokeStyle = rgba(rip.color, rip.alpha);\n    ctx.lineWidth = 2;\n    ctx.beginPath();\n    ctx.arc(rip.x, rip.y, rip.r, 0, Math.PI * 2);\n    ctx.stroke();\n  }\n\n  ctx.restore();\n}\n"
          }
        ]
      },
      "layoutProps": {
        "layoutSizingHorizontal": "fill",
        "layoutSizingVertical": "fill"
      },
      "zIndex": 1,
      "interactions": []
    }
  ]
}
