ecosystem-scene.jsx

// ecosystem-scene.jsx — MIZ OKI 3.5 "entire ecosystem" animation (Intelligence surface)
const { CompositionStage, useComposition, Shot, Captions, Easing, animate, clamp,
        useTweaks, TweaksPanel, TweakSection, TweakToggle } = window;

const C = {
  bg: '#050A12', navy: '#0B1C2D', cyan: '#19F5FF', cyan2: '#00C2D1', emerald: '#00FF9A',
  text: '#EAFBFF', muted: '#A7EAF0', deep: '#7CB9C0',
  border: 'rgba(25,245,255,0.18)', card: 'rgba(11,18,32,0.72)', warn: '#f59e0b',
};
const DISP = "'Cabinet Grotesk', Inter, sans-serif";
const BODY = "Inter, sans-serif";
const MONO = "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace";
const STAGES = [
  ['SENSE', '#f59e0b'], ['REASON', '#a855f7'], ['PLAN', '#4f8fff'], ['VALIDATE', '#10b981'],
  ['DECIDE', '#19F5FF'], ['ACT', '#ef4444'], ['LEARN', '#f472b6'],
];

// ── the only motion vocabulary in the piece ──
const MOTION = {
  enter: (T, at, dur = 0.7, dy = 26) => {
    const p = animate({ from: 0, to: 1, start: at, end: at + dur, ease: Easing.easeOutCubic })(T);
    return { opacity: p, transform: `translateY(${(1 - p) * dy}px)` };
  },
  pop: (T, at, dur = 0.5) => {
    const p = animate({ from: 0, to: 1, start: at, end: at + dur, ease: Easing.easeOutQuart })(T);
    return { opacity: p, transform: `scale(${0.92 + 0.08 * p})` };
  },
  draw: (T, at, dur, ease = Easing.easeInOutSine) =>
    animate({ from: 0, to: 1, start: at, end: at + dur, ease })(T),
  glide: (T, keys, ease = Easing.easeInOutCubic) => {
    if (T <= keys[0][0]) return keys[0][1];
    for (let i = 0; i < keys.length - 1; i++) {
      const [t0, v0] = keys[i], [t1, v1] = keys[i + 1];
      if (T < t1) return v0 + (v1 - v0) * ease(clamp((T - t0) / (t1 - t0), 0, 1));
    }
    return keys[keys.length - 1][1];
  },
  pulse: (T, speed = 2) => 0.72 + 0.28 * Math.sin((T * Math.PI * 2) / speed),
};

// quadratic bezier helpers (deterministic dot positions, no path measuring)
const q = (a, c, b, t) => ({
  x: (1 - t) * (1 - t) * a[0] + 2 * (1 - t) * t * c[0] + t * t * b[0],
  y: (1 - t) * (1 - t) * a[1] + 2 * (1 - t) * t * c[1] + t * t * b[1],
});
const qd = (a, c, b) => `M ${a[0]} ${a[1]} Q ${c[0]} ${c[1]} ${b[0]} ${b[1]}`;

const Flow = ({ a, c, b, p, color = C.cyan, w = 3, op = 0.7 }) => (
  <path d={qd(a, c, b)} fill="none" stroke={color} strokeWidth={w} pathLength="1"
    strokeDasharray="1" strokeDashoffset={1 - clamp(p, 0, 1)} strokeLinecap="round"
    opacity={op * clamp(p * 4, 0, 1)} />
);
const Mote = ({ a, c, b, tt, color = C.cyan, r = 7 }) => {
  if (tt <= 0.001 || tt >= 0.999) return null;
  const pt = q(a, c, b, tt);
  return <circle cx={pt.x} cy={pt.y} r={r} fill={color} style={{ filter: `drop-shadow(0 0 10px ${color})` }} />;
};
const Tick = ({ p, color, size = 20 }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" style={{ opacity: clamp(p * 2, 0, 1), flex: 'none' }}>
    <path d="M4 12.5 L10 18 L20 6" fill="none" stroke={color} strokeWidth="3" strokeLinecap="round"
      strokeLinejoin="round" pathLength="1" strokeDasharray="1" strokeDashoffset={1 - clamp(p, 0, 1)} />
  </svg>
);

const SRC = [
  { x: 250, y: 250, label: 'ERP', tag: 'SAP S/4' },
  { x: 520, y: 190, label: 'CRM', tag: 'Salesforce' },
  { x: 230, y: 500, label: 'GA4', tag: 'event stream' },
  { x: 540, y: 450, label: 'Google Ads', tag: 'auction' },
  { x: 260, y: 740, label: 'Meta', tag: 'CAPI' },
  { x: 530, y: 800, label: 'Email', tag: 'ESP' },
];
const NODE = [1000, 540];
// knowledge graph — nervous system (GN[0] = ingest node, rendered as the div hub)
const GN = [
  { x: 1000, y: 540, r: 11 },
  { x: 1180, y: 430, r: 16, label: 'Customer' },
  { x: 1110, y: 630, r: 10, label: 'Account' },
  { x: 1090, y: 315, r: 10, label: 'Journey' },
  { x: 1320, y: 545, r: 14, label: 'Intent' },
  { x: 1265, y: 705, r: 12, label: 'Campaign' },
  { x: 1430, y: 425, r: 10, label: 'Product' },
  { x: 1395, y: 645, r: 9 },
  { x: 1235, y: 250, r: 8, label: 'Channel' },
  { x: 1055, y: 765, r: 8 },
];
const GEDGES = [[0,1],[0,2],[0,3],[0,9],[1,4],[1,2],[3,8],[4,5],[4,7],[4,6],[5,7],[2,9],[6,7],[3,1]];
const PULSE_EDGES = [4, 7, 9, 1, 6, 10];
const RAIL_X = i => 1650 + i * 164;           // chip left; w 150
const CARD = { x: 2860, y: 296, w: 560, h: 488 };
const CHAN = [
  { y: 250, label: 'Google Ads', metric: 'bid strategy → tROAS +12%' },
  { y: 466, label: 'Meta CAPI', metric: 'audience synced · 48k users' },
  { y: 682, label: 'Email ESP', metric: 'send scheduled · 09:00' },
];
const ARC = { a: [3715, 850], c: [2600, 1180], b: [1330, 780] };

const EVIDENCE = [
  { src: 'GRAPH', text: 'Intent cohort 2.4k accounts · LII 0.78' },
  { src: 'GA4', text: 'Brand-term CVR +18% over 14 days' },
  { src: 'ADS', text: 'Impression share lost to budget: 31%' },
  { src: 'CAUSAL', text: 'Incrementality 0.42 ± 0.06 (ghost bids)' },
];
const GATES = ['Budget cap ≤ $50K', 'Change window OK', 'Tier-2 auto-approve'];

function ZoneLabel({ T, at, x, y, children }) {
  return (
    <div style={{ position: 'absolute', left: x, top: y, fontFamily: MONO, fontSize: 21,
      letterSpacing: '0.22em', color: C.deep, ...MOTION.enter(T, at, 0.8, 14) }}>{children}</div>
  );
}

function Piece({ showCaptions }) {
  const { T, CUES, authoredTotal } = useComposition();
  const G = CUES.Graph, P = CUES.Pipeline, D = CUES.Decision, A = CUES.Act, L = CUES.Learn, X = CUES.Close;

  // camera (world 3600×1080 → viewport 1920×1080)
  const cx = MOTION.glide(T, [[0, 520], [G - 0.6, 620], [G + 0.7, 1150], [P - 0.5, 1220], [P + 0.8, 1980], [D - 0.3, 2430],
    [D + 0.9, 3140], [A - 0.4, 3140], [A + 0.8, 3400], [L - 0.3, 3400], [L + 1.4, 2050], [X + 0.5, 2050], [X + 3.0, 2050]]);
  const sc = MOTION.glide(T, [[0, 1.05], [G - 0.6, 1.02], [G + 0.7, 1.12], [P - 0.5, 1.1], [P + 0.8, 1.18], [D - 0.3, 1.14],
    [D + 0.9, 1.26], [A - 0.4, 1.26], [A + 0.8, 1.1], [L - 0.3, 1.1], [L + 1.4, 0.44], [X + 0.5, 0.44], [X + 3.0, 0.465]]);
  const worldDim = MOTION.glide(T, [[X - 0.3, 1], [X + 0.8, 0.16]]);
  const gf = MOTION.glide(T, [[authoredTotal - 0.7, 1], [authoredTotal - 0.06, 0]], Easing.easeInOutSine);

  // pipeline pulse position + per-stage light time
  const px = MOTION.glide(T, [[P + 0.5, 1570], [P + 7.2, 2810]], Easing.linear);
  const lightAt = i => P + 0.5 + ((1725 + 164 * i - 1570) / 1240) * 6.7;
  const glowScale = MOTION.glide(T, [[D - 0.5, 1], [D + 0.5, 0.35]]);

  // learn feedback
  const arcP = MOTION.draw(T, L + 0.4, 2.2);
  const arcT = MOTION.draw(T, L + 0.9, 2.4);
  const reGlow = MOTION.glide(T, [[L + 3.0, 0], [L + 3.5, 1], [L + 4.5, 0]], Easing.easeInOutSine);

  const badgeFlip = D + 4.8;
  const verified = clamp(animate({ from: 0, to: 1, start: badgeFlip, end: badgeFlip + 0.4, ease: Easing.easeOutQuart })(T), 0, 1);

  return (
    <div data-screen-label={`t=${Math.floor(T)}s`}
      style={{ position: 'absolute', inset: 0, overflow: 'hidden', fontFamily: BODY, background: C.bg }}>
      {/* fixed atmosphere: layered gradient + accent washes + dot grid */}
      <div style={{ position: 'absolute', inset: 0, background: `linear-gradient(180deg, ${C.navy}, ${C.bg})` }} />
      <div style={{ position: 'absolute', inset: 0, background:
        'radial-gradient(900px 600px at 12% 8%, rgba(25,245,255,0.09), transparent 65%), radial-gradient(900px 600px at 88% 6%, rgba(0,255,154,0.07), transparent 65%)' }} />
      <div style={{ position: 'absolute', inset: 0, opacity: 0.5, backgroundImage:
        'radial-gradient(rgba(25,245,255,0.09) 1px, transparent 1px)', backgroundSize: '48px 48px' }} />

      <div style={{ opacity: gf, position: 'absolute', inset: 0 }}>
        {/* ── WORLD ── */}
        <div style={{ position: 'absolute', left: 0, top: 0, width: 4100, height: 1080, opacity: worldDim,
          transform: `translate(${960 - cx * sc}px, ${540 - 540 * sc}px) scale(${sc})`, transformOrigin: '0 0' }}>

          <svg width="4100" height="1080" style={{ position: 'absolute', left: 0, top: 0, overflow: 'visible' }}>
            {/* sources → node */}
            {SRC.map((s, i) => {
              const a = [s.x + 260, s.y + 44], c = [(s.x + 260 + NODE[0]) / 2 + 40, s.y + 44 + (540 - s.y - 44) * 0.2];
              return <g key={i}>
                <Flow a={a} c={c} b={NODE} p={MOTION.draw(T, 1.6 + i * 0.22, 0.9)} />
                <Mote a={a} c={c} b={NODE} tt={MOTION.draw(T, 2.7 + i * 0.2, 1.0, Easing.easeInOutQuad)} />
              </g>;
            })}
            {/* knowledge graph — edges, nervous pulses, entity nodes */}
            {GEDGES.map(([i1, i2], k) => {
              const n1 = GN[i1], n2 = GN[i2];
              return <Flow key={'ge' + k} a={[n1.x, n1.y]} c={[(n1.x + n2.x) / 2, (n1.y + n2.y) / 2]} b={[n2.x, n2.y]}
                p={MOTION.draw(T, G + 0.5 + k * 0.09, 0.5)} w={2} op={0.4} />;
            })}
            {T > G + 2.4 && PULSE_EDGES.map((e, j) => {
              const [i1, i2] = GEDGES[e], n1 = GN[i1], n2 = GN[i2];
              return <Mote key={'gp' + j} a={[n1.x, n1.y]} c={[(n1.x + n2.x) / 2, (n1.y + n2.y) / 2]} b={[n2.x, n2.y]}
                tt={(T * 0.5 + j * 0.37) % 1} r={4.5} />;
            })}
            {GN.map((n, i) => i === 0 ? null : (
              <g key={'gn' + i} opacity={MOTION.pop(T, G + 0.15 + i * 0.14, 0.5).opacity}>
                <circle cx={n.x} cy={n.y} r={n.r} fill="rgba(25,245,255,0.13)" stroke={C.cyan} strokeWidth="1.5"
                  opacity={0.5 + 0.5 * MOTION.pulse(T + i * 0.4)}
                  style={reGlow > 0.01 ? { filter: `drop-shadow(0 0 ${14 * reGlow}px ${C.emerald})` } : null} />
                {n.label && <text x={n.x} y={n.y - n.r - 10} textAnchor="middle" fill={C.muted} fontSize="19"
                  fontFamily={BODY} opacity={MOTION.draw(T, G + 1.5 + i * 0.06, 0.5)}>{n.label}</text>}
              </g>
            ))}
            {/* trunk into rail */}
            <Flow a={[1334, 545]} c={[1490, 542]} b={[1650, 540]} p={MOTION.draw(T, P + 0.1, 0.5)} w={4} />
            {/* pipeline pulse */}
            {T > P + 0.4 && T < P + 7.4 &&
              <circle cx={px} cy={540} r={8} fill={C.cyan} opacity={MOTION.glide(T, [[P + 0.4, 0], [P + 0.7, 1], [P + 7.1, 1], [P + 7.4, 0]], Easing.linear)}
                style={{ filter: `drop-shadow(0 0 12px ${C.cyan})` }} />}
            {/* decision → channels */}
            {CHAN.map((ch, i) => {
              const a = [CARD.x + CARD.w, 540], b = [3550, ch.y + 74], c = [(a[0] + b[0]) / 2 + 20, (540 + b[1]) / 2];
              return <g key={i}>
                <Flow a={a} c={c} b={b} p={MOTION.draw(T, A + 0.2 + i * 0.15, 0.8)} color={C.cyan2} />
                <Mote a={a} c={c} b={b} tt={MOTION.draw(T, A + 0.7 + i * 0.15, 0.7, Easing.easeInOutQuad)} color={C.cyan2} />
              </g>;
            })}
            {/* learn arc back to sources */}
            <Flow a={ARC.a} c={ARC.c} b={ARC.b} p={arcP} color={C.emerald} w={4} op={0.65} />
            <Mote a={ARC.a} c={ARC.c} b={ARC.b} tt={arcT} color={C.emerald} r={9} />
          </svg>

          <ZoneLabel T={T} at={0.9} x={300} y={140}>SIGNALS</ZoneLabel>
          <ZoneLabel T={T} at={G + 0.3} x={1000} y={135}>KNOWLEDGE GRAPH · NERVOUS SYSTEM</ZoneLabel>
          <ZoneLabel T={T} at={P + 0.3} x={1880} y={400}>SRPVDAL PIPELINE</ZoneLabel>
          <ZoneLabel T={T} at={D + 0.3} x={CARD.x} y={236}>DECISION RECORD</ZoneLabel>
          <ZoneLabel T={T} at={A + 0.3} x={3550} y={180}>EXECUTION</ZoneLabel>

          {/* source chips */}
          {SRC.map((s, i) => {
            const isCRM = i === 1;
            const swapP = isCRM ? MOTION.draw(T, G + 3.2, 0.6) : 0;
            const ap = MOTION.pop(T, 0.25 + i * 0.28);
            return (
            <div key={i} style={{ position: 'absolute', left: s.x, top: s.y, width: 260, height: 88,
              borderRadius: 12, border: `1px solid ${C.border}`, background: C.card,
              display: 'flex', alignItems: 'center', gap: 16, padding: '0 20px',
              opacity: ap.opacity * (1 - 0.35 * swapP), transform: ap.transform }}>
              <div style={{ width: 10, height: 10, borderRadius: 999, background: swapP > 0.5 ? C.emerald : C.cyan,
                opacity: T > 1.4 + i * 0.2 ? MOTION.pulse(T) : 0.15, flex: 'none',
                boxShadow: `0 0 10px ${swapP > 0.5 ? C.emerald : C.cyan}` }} />
              <div>
                <div style={{ fontSize: 26, fontWeight: 700, color: C.text, lineHeight: 1.2 }}>{s.label}</div>
                <div style={{ fontFamily: MONO, fontSize: 18, color: C.deep, position: 'relative' }}>
                  <span style={{ opacity: 1 - swapP }}>{s.tag}</span>
                  {isCRM && <span style={{ position: 'absolute', left: 0, top: 0, opacity: swapP, color: C.emerald,
                    whiteSpace: 'nowrap' }}>absorbed → graph</span>}
                </div>
              </div>
            </div>
            );
          })}

          {/* convergence node */}
          <div style={{ position: 'absolute', left: NODE[0] - 14, top: 540 - 14, width: 28, height: 28,
            borderRadius: 999, background: 'rgba(25,245,255,0.14)', border: `1.5px solid ${C.cyan}`,
            boxShadow: `0 0 ${18 * MOTION.pulse(T)}px rgba(25,245,255,0.6)`, ...MOTION.pop(T, 2.3) }} />

          {/* live-graph badge */}
          <div style={{ position: 'absolute', left: 1128, top: 183, display: 'flex', alignItems: 'center', gap: 10,
            padding: '7px 18px', borderRadius: 999, border: `1px solid ${C.border}`, background: C.card,
            ...MOTION.pop(T, G + 2.6, 0.5) }}>
            <div style={{ width: 9, height: 9, borderRadius: 999, background: C.emerald,
              boxShadow: `0 0 8px ${C.emerald}`, opacity: MOTION.pulse(T) }} />
            <div style={{ fontFamily: MONO, fontSize: 17, letterSpacing: '0.14em', color: C.text }}>LIVE GRAPH</div>
          </div>

          {/* SRPVDAL rail */}
          {STAGES.map(([name, color], i) => {
            const li = clamp(animate({ from: 0, to: 1, start: lightAt(i), end: lightAt(i) + 0.45, ease: Easing.easeOutCubic })(T), 0, 1);
            const learnGlow = i === 6 ? reGlow : 0;
            const glow = Math.max(li * glowScale, learnGlow);
            return (
              <div key={name} style={{ position: 'absolute', left: RAIL_X(i), top: 505, width: 150, height: 70,
                borderRadius: 12, border: `1px solid ${li > 0.05 ? color : C.border}`, background: C.card,
                display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 2,
                boxShadow: `0 0 ${26 * glow}px ${color}55`,
                ...MOTION.enter(T, P + 0.15 + i * 0.07, 0.6, 18) }}>
                <div style={{ fontFamily: MONO, fontSize: 15, color: li > 0.05 ? color : C.deep }}>0{i + 1}</div>
                <div style={{ fontFamily: DISP, fontSize: 21, fontWeight: 800, letterSpacing: '0.06em',
                  color: li > 0.05 ? C.text : C.muted }}>{name}</div>
              </div>
            );
          })}

          {/* decision record card */}
          <div style={{ position: 'absolute', left: CARD.x, top: CARD.y, width: CARD.w,
            borderRadius: 16, border: `1px solid ${C.border}`, background: 'rgba(11,18,32,0.9)',
            boxShadow: '0 16px 48px rgba(0,0,0,0.45)', padding: 28, ...MOTION.pop(T, D + 0.1, 0.7) }}>
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
              <div style={{ fontFamily: MONO, fontSize: 20, color: C.cyan }}>DEC-4128</div>
              <div style={{ position: 'relative', height: 34 }}>
                <div style={{ opacity: 1 - verified, padding: '5px 16px', borderRadius: 999, border: `1px solid ${C.warn}66`,
                  color: C.warn, fontFamily: MONO, fontSize: 17, letterSpacing: '0.08em' }}>PENDING</div>
                <div style={{ position: 'absolute', inset: 0, opacity: verified, transform: `scale(${0.9 + 0.1 * verified})`,
                  padding: '5px 16px', borderRadius: 999, border: `1px solid ${C.emerald}88`, background: 'rgba(0,255,154,0.08)',
                  color: C.emerald, fontFamily: MONO, fontSize: 17, letterSpacing: '0.08em', display: 'flex',
                  alignItems: 'center', gap: 8 }}>VERIFIED</div>
              </div>
            </div>
            <div style={{ fontFamily: DISP, fontSize: 29, fontWeight: 800, color: C.text, margin: '10px 0 18px',
              lineHeight: 1.2 }}>Shift $42K/mo to Brand Search</div>
            {EVIDENCE.map((e, i) => (
              <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 14, padding: '11px 0',
                borderTop: `1px solid rgba(25,245,255,0.10)`, ...MOTION.enter(T, D + 0.9 + i * 0.55, 0.6, 16) }}>
                <div style={{ fontFamily: MONO, fontSize: 15, color: C.cyan2, width: 74, flex: 'none',
                  letterSpacing: '0.1em' }}>{e.src}</div>
                <div style={{ fontSize: 22, color: C.muted }}>{e.text}</div>
              </div>
            ))}
            <div style={{ borderTop: `1px solid rgba(25,245,255,0.10)`, paddingTop: 16, marginTop: 4 }}>
              <div style={{ fontFamily: MONO, fontSize: 16, letterSpacing: '0.18em', color: C.deep,
                marginBottom: 12, ...MOTION.enter(T, D + 3.0, 0.5, 10) }}>POLICY GATE</div>
              <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>
                {GATES.map((g, i) => (
                  <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '7px 14px',
                    borderRadius: 999, border: `1px solid rgba(16,185,129,0.4)`, background: 'rgba(16,185,129,0.07)',
                    ...MOTION.pop(T, D + 3.3 + i * 0.4, 0.45) }}>
                    <Tick p={MOTION.draw(T, D + 3.4 + i * 0.4, 0.4, Easing.easeOutCubic)} color="#10b981" size={18} />
                    <div style={{ fontSize: 19, color: C.muted }}>{g}</div>
                  </div>
                ))}
              </div>
            </div>
          </div>

          {/* execution channels */}
          {CHAN.map((ch, i) => (
            <div key={i} style={{ position: 'absolute', left: 3550, top: ch.y, width: 330, height: 148,
              borderRadius: 12, border: `1px solid ${C.border}`, background: C.card, padding: '18px 22px',
              ...MOTION.pop(T, A + 0.15 + i * 0.15, 0.5) }}>
              <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
                <div style={{ fontSize: 25, fontWeight: 700, color: C.text }}>{ch.label}</div>
                <Tick p={MOTION.draw(T, A + 1.6 + i * 0.4, 0.5, Easing.easeOutCubic)} color={C.emerald} size={24} />
              </div>
              <div style={{ fontFamily: MONO, fontSize: 16, letterSpacing: '0.12em', marginTop: 8,
                color: C.emerald, ...MOTION.enter(T, A + 1.7 + i * 0.4, 0.5, 8) }}>EXECUTED</div>
              <div style={{ fontSize: 19, color: C.deep, marginTop: 6,
                ...MOTION.enter(T, A + 1.9 + i * 0.4, 0.5, 8) }}>{ch.metric}</div>
            </div>
          ))}
        </div>

        {/* ── CLOSE overlay (screen space) ── */}
        <div style={{ position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column',
          alignItems: 'center', justifyContent: 'center', gap: 30, pointerEvents: 'none',
          opacity: T > X - 0.2 ? 1 : 0 }}>
          <div style={{ fontFamily: DISP, fontSize: 112, fontWeight: 800, letterSpacing: '-0.02em',
            color: C.text, lineHeight: 1, ...MOTION.pop(T, X + 0.25, 0.6) }}>
            MIZ OKI <span style={{ color: C.cyan }}>3.5</span>
          </div>
          <div style={{ fontFamily: DISP, fontSize: 52, fontWeight: 800, letterSpacing: '-0.02em',
            color: C.text, textAlign: 'center', maxWidth: 1200, lineHeight: 1.15,
            ...MOTION.enter(T, X + 0.7, 0.7) }}>
            Enterprise decisions at the{' '}
            <span style={{ background: `linear-gradient(90deg, ${C.cyan}, ${C.emerald})`,
              WebkitBackgroundClip: 'text', backgroundClip: 'text', color: 'transparent' }}>speed of data.</span>
          </div>
          <div style={{ display: 'flex', gap: 64, marginTop: 18 }}>
            {[['50–75×', 'Faster Decisions'], ['18–35%', 'Leakage Prevented'], ['3.2mo', 'Avg Payback'], ['89%', 'Automation Coverage']]
              .map(([v, l], i) => (
                <div key={i} style={{ textAlign: 'center', ...MOTION.enter(T, X + 1.2 + i * 0.18, 0.6, 20) }}>
                  <div style={{ fontFamily: DISP, fontSize: 46, fontWeight: 800, color: C.cyan }}>{v}</div>
                  <div style={{ fontSize: 21, color: C.muted, marginTop: 4 }}>{l}</div>
                </div>
              ))}
          </div>
        </div>

        {showCaptions && <Captions items={[
          { at: 0.7, until: G - 0.3, text: 'Fragmented signals — ERP, CRM, ads, email.' },
          { at: G + 0.4, until: G + 2.9, text: 'A living knowledge graph replaces static CRM.' },
          { at: G + 3.1, until: P - 0.3, text: 'Real-time, all-knowing — behind every decision.' },
          { at: P + 0.5, until: P + 5.5, text: 'One governed pipeline: SENSE through LEARN.' },
          { at: D + 0.4, until: D + 3.0, text: 'Every decision carries its evidence.' },
          { at: D + 3.2, until: A - 0.2, text: 'Verified against policy before it runs.' },
          { at: A + 0.4, until: A + 4.2, text: 'Executes in your existing systems.' },
          { at: L + 0.5, until: X - 0.4, text: 'Outcomes write back. The graph learns.' },
        ]} />}
      </div>
    </div>
  );
}

function EcosystemAnimation() {
  const [t, setTweak] = useTweaks(window.TWEAK_DEFAULTS);
  return (
    <div style={{ width: '100%', height: '100%' }}>
      <CompositionStage width={1920} height={1080} scenes={window.OM_SCENES}
        playback={window.OM_PLAYBACK} bg={C.bg}>
        <Piece showCaptions={t.captions} />
      </CompositionStage>
      <TweaksPanel>
        <TweakSection label="Video" />
        <TweakToggle label="Captions" value={t.captions} onChange={v => setTweak('captions', v)} />
        <TweakToggle label="Motion editor" value={t.motionEditor} onChange={v => setTweak('motionEditor', v)} />
      </TweaksPanel>
    </div>
  );
}
window.EcosystemAnimation = EcosystemAnimation;
← All docsView source on GitHub →