// mobile-scenes.jsx — Mobile Platforms animation scenes & sprites

const M_PALETTE = {
  cream: '#f5f0e8',
  creamDeep: '#ede6d8',
  paper: '#faf6ee',
  moss600: '#3f6b54',
  moss500: '#4d7e65',
  moss400: '#6fa37a',
  moss300: '#93b89a',
  moss200: '#b8cdbb',
  moss100: '#d7e1d4',
  amber: '#cd8a4b',
  amberSoft: '#e2a568',
  ink: '#0f1713',
  ink70: 'rgba(15,23,19,0.72)',
  ink40: 'rgba(15,23,19,0.40)',
  ink15: 'rgba(15,23,19,0.15)',
};

const M_FONTS = {
  display: '"Space Grotesk", system-ui, sans-serif',
  serif: '"Instrument Serif", Georgia, serif',
  mono: '"JetBrains Mono", ui-monospace, monospace',
};

const mSmooth = (t) => t < 0 ? 0 : t > 1 ? 1 : t * t * (3 - 2 * t);
const mMix = (a, b, t) => a + (b - a) * t;
const mEnv = (localT, duration, fadeIn = 0.4, fadeOut = 0.4) => {
  if (localT < 0 || localT > duration) return 0;
  if (localT < fadeIn) return mSmooth(localT / fadeIn);
  if (localT > duration - fadeOut) return mSmooth((duration - localT) / fadeOut);
  return 1;
};

// ─── Film grain ─────────────────────────────────────────────────────────
function MFilmGrain() {
  return (
    <div style={{
      position: 'absolute', inset: 0, pointerEvents: 'none',
      opacity: 0.09, mixBlendMode: 'multiply',
      backgroundImage: `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='1.6' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.15  0 0 0 0 0.13  0 0 0 0 0.08  0 0 0 1 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>")`,
      backgroundSize: '200px 200px',
    }} />
  );
}

function MVignette() {
  return (
    <div style={{
      position: 'absolute', inset: 0, pointerEvents: 'none',
      background: 'radial-gradient(ellipse at center, rgba(0,0,0,0) 55%, rgba(40,30,15,0.10) 100%)',
    }} />
  );
}

// ─── Corner chrome with the real logo ───────────────────────────────────
function MCornerChrome() {
  const time = useTime();
  const pulse = 0.5 + 0.5 * Math.sin(time * Math.PI * 1.2);
  return (
    <>
      {/* Top-left wordmark = real logo @ 45% opacity */}
      <img
        src="assets/digital-heroes-logo.png"
        alt=""
        style={{
          position: 'absolute', left: 56, top: 44,
          height: 56, width: 'auto',
          opacity: 0.45,
          pointerEvents: 'none',
        }}
      />

      {/* Bottom-right caption + pulse */}
      <div style={{
        position: 'absolute', right: 56, bottom: 52,
        display: 'flex', alignItems: 'center', gap: 12,
        fontFamily: M_FONTS.mono,
        fontSize: 13,
        letterSpacing: '0.02em',
        color: M_PALETTE.ink70,
        textTransform: 'uppercase',
      }}>
        <div style={{
          width: 8, height: 8, borderRadius: 4,
          background: M_PALETTE.moss500,
          opacity: 0.4 + 0.6 * pulse,
          boxShadow: `0 0 ${8 + 12 * pulse}px ${M_PALETTE.moss400}`,
        }}/>
        <span>§ 03 · mobile platforms</span>
      </div>

      {/* Top-right edition line */}
      <div style={{
        position: 'absolute', right: 56, top: 62,
        fontFamily: M_FONTS.mono,
        fontSize: 12,
        letterSpacing: '0.12em',
        color: M_PALETTE.ink40,
        textTransform: 'uppercase',
      }}>
        vol. 01 — mobile craft · ed. mmxxvi
      </div>

      {/* Bottom-left folio */}
      <div style={{
        position: 'absolute', left: 56, bottom: 52,
        fontFamily: M_FONTS.mono,
        fontSize: 12,
        letterSpacing: '0.12em',
        color: M_PALETTE.ink40,
      }}>
        fig.&nbsp;&nbsp;03·i — one team, four platforms
      </div>
    </>
  );
}

// ─── Editorial rules ────────────────────────────────────────────────────
function MEditorialFrame() {
  return (
    <>
      <div style={{ position: 'absolute', left: 56, right: 56, top: 112, height: 1, background: M_PALETTE.ink15 }}/>
      <div style={{ position: 'absolute', left: 56, right: 56, bottom: 90, height: 1, background: M_PALETTE.ink15 }}/>
    </>
  );
}

// ─── Section title ──────────────────────────────────────────────────────
function MSectionTitle() {
  const time = useTime();
  // fade during phone-settle/launch beat (7–13s)
  let opacity = 1;
  if (time >= 6.8 && time <= 13.0) {
    opacity = 1 - mSmooth(Math.min(1, (time - 6.8) / 0.8)) * 0.7;
  }
  if (time > 13.0) {
    opacity = 0.3 + mSmooth(Math.min(1, (time - 13.0) / 1.2)) * 0.7;
  }

  return (
    <div style={{
      position: 'absolute',
      left: '50%', top: 150,
      transform: 'translateX(-50%)',
      textAlign: 'center',
      opacity,
    }}>
      <div style={{
        fontFamily: M_FONTS.mono,
        fontSize: 12,
        letterSpacing: '0.28em',
        color: M_PALETTE.moss500,
        textTransform: 'uppercase',
        marginBottom: 14,
      }}>
        platforms ⸺ one team &nbsp;·&nbsp; four stacks &nbsp;·&nbsp; ship once
      </div>
      <div style={{
        fontFamily: M_FONTS.display,
        fontWeight: 700,
        fontSize: 48,
        letterSpacing: '-0.04em',
        color: M_PALETTE.ink,
        lineHeight: 1,
      }}>
        Native, cross-platform, <span style={{ fontFamily: M_FONTS.serif, fontStyle: 'italic', fontWeight: 400, color: M_PALETTE.moss600 }}>everywhere</span>.
      </div>
    </div>
  );
}

// ─── Platform chip (glass pill) ─────────────────────────────────────────
function PlatformChip({ x, y, start, name, stack, mark }) {
  const time = useTime();
  const localT = time - start;
  const t = mSmooth(Math.min(1, Math.max(0, localT / 0.7)));

  // Fade out during phone settle + reverse loop
  let exit = 0;
  if (time > 7.8) exit = mSmooth(Math.min(1, (time - 7.8) / 1.4));
  // Reverse fade-in for loop
  if (time > 13.5) exit = Math.max(0, exit - mSmooth(Math.min(1, (time - 13.5) / 1.2)));

  // Subtle drift while alive
  const drift = Math.sin((time + x * 0.01) * 0.6) * 2;
  const tx = mMix(-40, 0, t);
  const opacity = t * (1 - exit);

  return (
    <div style={{
      position: 'absolute',
      left: x, top: y,
      width: 360, height: 88,
      transform: `translate(${tx}px, ${drift}px)`,
      opacity,
      willChange: 'transform, opacity',
    }}>
      <div style={{
        width: '100%', height: '100%',
        background: 'rgba(255,253,247,0.58)',
        backdropFilter: 'blur(6px)',
        WebkitBackdropFilter: 'blur(6px)',
        border: `1px solid ${M_PALETTE.moss200}`,
        borderRadius: 999,
        padding: '0 28px 0 22px',
        display: 'flex', alignItems: 'center', gap: 18,
        boxShadow: '0 4px 18px rgba(63,107,84,0.08)',
      }}>
        {/* circular mark */}
        <div style={{
          width: 48, height: 48, borderRadius: 24,
          background: M_PALETTE.moss100,
          border: `1px solid ${M_PALETTE.moss300}`,
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          flexShrink: 0,
          fontFamily: M_FONTS.serif, fontStyle: 'italic',
          fontSize: 22, color: M_PALETTE.moss600,
          letterSpacing: '-0.02em',
        }}>
          {mark}
        </div>

        <div style={{ display: 'flex', flexDirection: 'column', gap: 2, minWidth: 0 }}>
          <div style={{
            fontFamily: M_FONTS.display,
            fontWeight: 700,
            fontSize: 22,
            letterSpacing: '-0.02em',
            color: M_PALETTE.ink,
            lineHeight: 1.1,
          }}>
            {name}
          </div>
          <div style={{
            fontFamily: M_FONTS.serif,
            fontStyle: 'italic',
            fontSize: 18,
            color: M_PALETTE.moss600,
            letterSpacing: '-0.01em',
            lineHeight: 1.1,
          }}>
            {stack}
          </div>
        </div>
      </div>
    </div>
  );
}

// ─── Data stream (curve + traveling label) ──────────────────────────────
function MDataStream({ fromX, fromY, toX, toY, start, end, label, delay = 0 }) {
  const time = useTime();
  if (time < start || time > end) return null;
  const localT = time - start;
  const appear = mSmooth(Math.min(1, localT / 0.6));
  const exit = mSmooth(Math.min(1, Math.max(0, (time - (end - 0.8)) / 0.8)));
  const opacity = appear * (1 - exit);

  const midX = (fromX + toX) / 2 + 30;
  const midY = (fromY + toY) / 2 - 30;
  const d = `M ${fromX} ${fromY} Q ${midX} ${midY} ${toX} ${toY}`;
  const dashOffset = -(time * 120);

  const bezierAt = (t) => {
    const mt = 1 - t;
    return {
      x: mt * mt * fromX + 2 * mt * t * midX + t * t * toX,
      y: mt * mt * fromY + 2 * mt * t * midY + t * t * toY,
    };
  };
  const lp = ((localT + delay) * 0.3) % 1;
  const pos = bezierAt(lp);
  const labelFade = Math.sin(lp * Math.PI);

  return (
    <>
      <svg style={{ position: 'absolute', inset: 0, pointerEvents: 'none', opacity }} width="1920" height="1080" viewBox="0 0 1920 1080">
        <path d={d} fill="none" stroke={M_PALETTE.moss400} strokeWidth="1.4" strokeDasharray="4 8" strokeDashoffset={dashOffset} opacity="0.9"/>
        <path d={d} fill="none" stroke={M_PALETTE.moss200} strokeWidth="1" opacity="0.4"/>
      </svg>
      <div style={{
        position: 'absolute',
        left: pos.x, top: pos.y,
        transform: 'translate(-50%, -50%)',
        opacity: opacity * labelFade * 0.95,
        fontFamily: M_FONTS.mono,
        fontSize: 11,
        letterSpacing: '0.14em',
        color: M_PALETTE.moss600,
        textTransform: 'uppercase',
        background: M_PALETTE.paper,
        padding: '3px 8px',
        border: `1px solid ${M_PALETTE.moss200}`,
        whiteSpace: 'nowrap',
      }}>
        {label}
      </div>
    </>
  );
}

// ─── Morphing liquid-drop hub ───────────────────────────────────────────
function MLiquidHub({ cx, cy, start, morphStart, morphEnd }) {
  const time = useTime();
  const localT = time - start;
  if (localT < 0) return null;

  const appear = mSmooth(Math.min(1, localT / 1.0));
  // Hub exists 1.5–7s, then morphs into phones
  const fadeOut = mSmooth(Math.min(1, Math.max(0, (time - morphStart) / (morphEnd - morphStart))));
  // Re-appears in reverse at 13.5s+
  let reappear = 0;
  if (time > 13.5) reappear = mSmooth(Math.min(1, (time - 13.5) / 1.2));

  const visible = Math.max((1 - fadeOut), reappear);
  if (visible < 0.01) return null;

  const w = 3;
  const r1 = 50 + Math.sin(time * w * 0.7) * 14;
  const r2 = 50 + Math.cos(time * w * 0.6 + 1) * 16;
  const r3 = 50 + Math.sin(time * w * 0.9 + 2) * 12;
  const r4 = 50 + Math.cos(time * w * 0.5 + 3) * 18;

  const size = mMix(60, 210, appear);

  return (
    <div style={{
      position: 'absolute',
      left: cx, top: cy,
      transform: `translate(-50%, -50%) scale(${visible})`,
      opacity: visible,
      willChange: 'transform, opacity',
    }}>
      <div style={{
        width: size, height: size,
        background: `radial-gradient(circle at 38% 32%, ${M_PALETTE.moss400}, ${M_PALETTE.moss600} 75%)`,
        borderRadius: `${r1}% ${100-r1}% ${r2}% ${100-r2}% / ${r3}% ${r4}% ${100-r4}% ${100-r3}%`,
        boxShadow: `
          inset -10px -14px 28px rgba(15,23,19,0.35),
          inset 8px 10px 24px rgba(255,255,255,0.12),
          0 18px 42px rgba(63,107,84,0.28)
        `,
        position: 'relative',
      }}>
        <div style={{
          position: 'absolute', left: '22%', top: '18%',
          width: '28%', height: '20%',
          background: 'radial-gradient(ellipse, rgba(255,255,255,0.5), transparent 70%)',
          borderRadius: '50%',
          filter: 'blur(4px)',
        }}/>
      </div>
    </div>
  );
}

// ─── Abstract phone silhouette (organic moss liquid-drop, tall pill) ────
// Not device chrome — a soft rounded rectangle that pulses subtly.
function PhonePod({ x, y, start, amber = false, label, caption }) {
  const time = useTime();
  const localT = time - start;
  if (localT < 0) return null;

  // Rise from hub: start compressed, rise up and settle
  const rise = mSmooth(Math.min(1, localT / 1.6));
  // Exit during reset loop
  let exit = 0;
  if (time > 13.5) exit = mSmooth(Math.min(1, (time - 13.5) / 1.2));
  // Breathing pulse
  const breathe = 1 + Math.sin(time * 1.4) * 0.008;

  // Amber launch glow (right pod only, starts at 11s)
  const amberT = amber ? mSmooth(Math.min(1, Math.max(0, (time - 10.8) / 1.2))) : 0;
  // Fade amber on loop
  const amberFade = amber ? (1 - exit) : 0;
  const amberOpacity = amberT * amberFade;

  const ty = mMix(120, 0, rise);
  const opacity = rise * (1 - exit);
  const scale = mMix(0.7, 1, rise) * breathe;

  const width = 200;
  const height = 400;

  return (
    <div style={{
      position: 'absolute',
      left: x, top: y,
      transform: `translate(-50%, -50%) translateY(${ty}px) scale(${scale})`,
      opacity,
      willChange: 'transform, opacity',
    }}>
      {/* Amber aura behind (right pod only) */}
      {amber && (
        <div style={{
          position: 'absolute',
          left: '50%', top: '50%',
          width: width * 3.2, height: height * 1.8,
          transform: 'translate(-50%, -50%)',
          background: `radial-gradient(ellipse, ${M_PALETTE.amberSoft}77 0%, ${M_PALETTE.amber}33 30%, transparent 65%)`,
          opacity: amberOpacity,
          filter: 'blur(12px)',
          pointerEvents: 'none',
          zIndex: -1,
        }}/>
      )}

      {/* The pod — organic tall drop */}
      <div style={{
        width, height,
        background: `radial-gradient(ellipse at 40% 25%, ${M_PALETTE.moss400}, ${M_PALETTE.moss600} 75%)`,
        borderRadius: '40% 40% 38% 38% / 18% 18% 20% 20%',
        boxShadow: `
          inset -8px -14px 32px rgba(15,23,19,0.38),
          inset 6px 10px 28px rgba(255,255,255,0.14),
          0 22px 48px rgba(63,107,84,0.32)
        `,
        position: 'relative',
        overflow: 'hidden',
      }}>
        {/* speaker-like line at top */}
        <div style={{
          position: 'absolute',
          left: '50%', top: 22,
          transform: 'translateX(-50%)',
          width: 46, height: 3, borderRadius: 2,
          background: 'rgba(255,255,255,0.22)',
        }}/>
        {/* soft highlight */}
        <div style={{
          position: 'absolute',
          left: '22%', top: '10%',
          width: '35%', height: '14%',
          background: 'radial-gradient(ellipse, rgba(255,255,255,0.45), transparent 70%)',
          borderRadius: '50%',
          filter: 'blur(6px)',
        }}/>
        {/* inner serif italic mark */}
        <div style={{
          position: 'absolute',
          left: 0, right: 0, top: '42%',
          textAlign: 'center',
          fontFamily: M_FONTS.serif,
          fontStyle: 'italic',
          fontSize: 42,
          color: 'rgba(255,255,255,0.55)',
          letterSpacing: '-0.02em',
        }}>
          {label}
        </div>
        {/* home indicator bar */}
        <div style={{
          position: 'absolute',
          left: '50%', bottom: 18,
          transform: 'translateX(-50%)',
          width: 60, height: 3, borderRadius: 2,
          background: 'rgba(255,255,255,0.22)',
        }}/>
      </div>

      {/* Caption below */}
      {caption && (
        <div style={{
          position: 'absolute',
          left: '50%', bottom: -42,
          transform: 'translateX(-50%)',
          fontFamily: M_FONTS.mono,
          fontSize: 11,
          letterSpacing: '0.22em',
          color: M_PALETTE.moss500,
          textTransform: 'uppercase',
          whiteSpace: 'nowrap',
        }}>
          {caption}
        </div>
      )}
    </div>
  );
}

// ─── Abstract store badge (NOT real App Store / Play logos) ─────────────
function StoreBadge({ x, y, start, eyebrow, label }) {
  const time = useTime();
  const localT = time - start;
  if (localT < 0) return null;

  const appear = mSmooth(Math.min(1, localT / 0.8));
  let exit = 0;
  if (time > 13.6) exit = mSmooth(Math.min(1, (time - 13.6) / 1.1));

  const opacity = appear * (1 - exit);
  const ty = mMix(16, 0, appear);

  return (
    <div style={{
      position: 'absolute',
      left: x, top: y,
      transform: `translate(-50%, 0) translateY(${ty}px)`,
      opacity,
      willChange: 'transform, opacity',
    }}>
      <div style={{
        padding: '14px 28px',
        background: M_PALETTE.paper,
        border: `1px solid ${M_PALETTE.moss300}`,
        borderRadius: 2,
        display: 'flex', flexDirection: 'column', gap: 4,
        minWidth: 220,
        boxShadow: '0 6px 18px rgba(63,107,84,0.10)',
        position: 'relative',
      }}>
        {/* corner ticks */}
        <div style={{ position: 'absolute', top: 5, left: 5, width: 7, height: 7, borderTop: `1px solid ${M_PALETTE.moss500}`, borderLeft: `1px solid ${M_PALETTE.moss500}` }}/>
        <div style={{ position: 'absolute', bottom: 5, right: 5, width: 7, height: 7, borderBottom: `1px solid ${M_PALETTE.moss500}`, borderRight: `1px solid ${M_PALETTE.moss500}` }}/>

        <div style={{
          fontFamily: M_FONTS.mono,
          fontSize: 10,
          letterSpacing: '0.24em',
          color: M_PALETTE.moss500,
          textTransform: 'uppercase',
        }}>
          {eyebrow}
        </div>
        <div style={{
          fontFamily: M_FONTS.display,
          fontWeight: 700,
          fontSize: 22,
          letterSpacing: '-0.02em',
          color: M_PALETTE.ink,
        }}>
          {label}
        </div>
      </div>
    </div>
  );
}

// ─── Running captions ───────────────────────────────────────────────────
function MCaption({ start, end, eyebrow, text }) {
  const time = useTime();
  if (time < start || time > end) return null;
  const localT = time - start;
  const dur = end - start;
  const opacity = mEnv(localT, dur, 0.35, 0.45);

  return (
    <div style={{
      position: 'absolute',
      left: '50%',
      bottom: 120,
      transform: 'translateX(-50%)',
      opacity,
      textAlign: 'center',
      willChange: 'opacity',
    }}>
      <div style={{
        fontFamily: M_FONTS.mono,
        fontSize: 12,
        letterSpacing: '0.24em',
        color: M_PALETTE.moss500,
        textTransform: 'uppercase',
        marginBottom: 10,
      }}>
        — {eyebrow} —
      </div>
      <div style={{
        fontFamily: M_FONTS.display,
        fontWeight: 500,
        fontSize: 22,
        letterSpacing: '-0.02em',
        color: M_PALETTE.ink70,
      }}>
        {text}
      </div>
    </div>
  );
}

// ─── Ready-for-store italic caption ─────────────────────────────────────
function ReadyCaption({ x, y, start }) {
  const time = useTime();
  const localT = time - start;
  if (localT < 0) return null;
  const appear = mSmooth(Math.min(1, localT / 0.8));
  let exit = 0;
  if (time > 13.6) exit = mSmooth(Math.min(1, (time - 13.6) / 1.0));
  const opacity = appear * (1 - exit);

  return (
    <div style={{
      position: 'absolute',
      left: x, top: y,
      transform: 'translate(-50%, 0)',
      opacity,
      textAlign: 'center',
      whiteSpace: 'nowrap',
    }}>
      <div style={{
        fontFamily: M_FONTS.serif,
        fontStyle: 'italic',
        fontWeight: 400,
        fontSize: 40,
        color: M_PALETTE.moss600,
        letterSpacing: '-0.02em',
        lineHeight: 1,
      }}>
        § ready for store
      </div>
    </div>
  );
}

// ─── Main scene ─────────────────────────────────────────────────────────
function MobilePlatformsScene() {
  const time = useTime();

  const hubX = 1250;
  const hubY = 560;

  const platforms = [
    { name: 'iOS',          stack: 'Swift + SwiftUI',      mark: 'S' },
    { name: 'Android',      stack: 'Kotlin + Compose',     mark: 'K' },
    { name: 'React Native', stack: '0.77+',                mark: 'R' },
    { name: 'Flutter',      stack: '3.x',                  mark: 'F' },
  ];

  const chipX = 220;
  const chipY0 = 280;
  const chipGap = 110;

  const chipCenters = platforms.map((_, i) => ({
    x: chipX + 360,
    y: chipY0 + i * chipGap + 44,
  }));

  const streams = [
    { from: 0, delay: 0.0, label: 'design system' },
    { from: 0, delay: 0.5, label: 'auth' },
    { from: 1, delay: 0.1, label: 'offline' },
    { from: 1, delay: 0.6, label: 'push' },
    { from: 2, delay: 0.2, label: 'payments' },
    { from: 2, delay: 0.7, label: 'analytics' },
    { from: 3, delay: 0.3, label: 'ci/cd' },
  ];

  // Phone beats
  const phoneStart = 7.0;
  const phoneLeftX = hubX - 150;
  const phoneRightX = hubX + 150;
  const phoneY = hubY;

  // Store badges
  const badgeStart = 11.2;
  const badgeY = hubY + 260;
  const badgeLeftX = hubX - 150;
  const badgeRightX = hubX + 150;

  // Source column header
  const colOpacity = mEnv(time - 0.2, 8.0, 0.6, 1.2);

  return (
    <>
      <MEditorialFrame />
      <MCornerChrome />
      <MSectionTitle />

      {/* Source column header */}
      <div style={{
        position: 'absolute', left: chipX, top: 222,
        fontFamily: M_FONTS.mono,
        fontSize: 11,
        letterSpacing: '0.28em',
        color: M_PALETTE.moss500,
        textTransform: 'uppercase',
        opacity: colOpacity,
      }}>
        ◍ &nbsp; platforms (4)
      </div>

      {/* Platform chips: stagger 0.4s */}
      {platforms.map((p, i) => (
        <PlatformChip
          key={p.name}
          x={chipX}
          y={chipY0 + i * chipGap}
          start={0.2 + i * 0.4}
          name={p.name}
          stack={p.stack}
          mark={p.mark}
        />
      ))}

      {/* Hub label */}
      <div style={{
        position: 'absolute', left: hubX, top: hubY - 220,
        transform: 'translateX(-50%)',
        fontFamily: M_FONTS.mono,
        fontSize: 11,
        letterSpacing: '0.28em',
        color: M_PALETTE.moss500,
        textTransform: 'uppercase',
        textAlign: 'center',
        opacity: mEnv(time - 1.5, 5.0, 0.6, 1.0),
      }}>
        ◍ &nbsp; one team
      </div>

      {/* Data streams 3–7s */}
      {streams.map((s, i) => (
        <MDataStream
          key={i}
          fromX={chipCenters[s.from].x}
          fromY={chipCenters[s.from].y}
          toX={hubX - 120}
          toY={hubY}
          label={s.label}
          start={3.0 + i * 0.18}
          end={7.2}
          delay={s.delay}
        />
      ))}

      {/* Liquid hub (1.5–7s main) */}
      <MLiquidHub cx={hubX} cy={hubY} start={1.5} morphStart={7.0} morphEnd={8.2} />

      {/* Two phone pods rise 7–11s and settle */}
      <PhonePod x={phoneLeftX}  y={phoneY} start={phoneStart} amber={false} label="α" caption="device · a" />
      <PhonePod x={phoneRightX} y={phoneY} start={phoneStart + 0.3} amber={true} label="β" caption="device · b" />

      {/* Store badges (11–14s) */}
      <StoreBadge x={badgeLeftX}  y={badgeY} start={badgeStart}        eyebrow="distribution · a" label="App Emblem" />
      <StoreBadge x={badgeRightX} y={badgeY} start={badgeStart + 0.3}  eyebrow="distribution · b" label="Play Emblem" />

      {/* Ready-for-store caption */}
      <ReadyCaption x={hubX} y={badgeY + 120} start={12.0} />

      {/* Running captions */}
      <MCaption start={0.4}  end={2.8}  eyebrow="step 01" text="Four platforms. One strategy." />
      <MCaption start={3.0}  end={6.8}  eyebrow="step 02" text="Shared systems flow through one team." />
      <MCaption start={7.1}  end={10.6} eyebrow="step 03" text="Two devices. Identical craft." />
      <MCaption start={11.0} end={13.4} eyebrow="launch"  text="Ready for every store." />

      <MVignette />
      <MFilmGrain />
    </>
  );
}

Object.assign(window, {
  MobilePlatformsScene, M_PALETTE, M_FONTS,
  PlatformChip, MDataStream, MLiquidHub, MCornerChrome, MSectionTitle,
  MEditorialFrame, PhonePod, StoreBadge, MCaption, ReadyCaption,
  MFilmGrain, MVignette,
});
