// scenes-v4.jsx — Lead Delta "How It Works" 6-step animation
// Design language:
//   - Pure black background (#000)
//   - Monochrome — white + greys only
//   - Two fonts: Oxanium (numbers, labels, headers) + Inter (body)
//   - Rectangles with ~6px corner radius. No diamonds. Circles only for nodes/dots.
//   - All icons: white stroke 1.5px, no fill, consistent weight
//   - Each step ~8s. Total loop ~52s (6×8 + closing 4s).

const STEP_DUR = 5; // legacy fallback for utilities that didn't get migrated (scaled 8→5 for 1.5× speedup)
// Per-step durations tuned for read time / content density. Scaled 1.5× faster.
const _RAW_STEPS = [
  { id: 'problem',  label: 'THE PROBLEM',         num: '01', dur: 9    },
  { id: 'platform', label: 'ONE PLATFORM',        num: '02', dur: 5    },
  { id: 'profile',  label: 'PROFILE CAPTURE',     num: '03', dur: 6    },
  { id: 'qualify',  label: 'QUALIFICATION',       num: '04', dur: 7    },
  { id: 'team',     label: 'LEAD MANAGEMENT',     num: '05', dur: 7    },
  { id: 'workflow', label: 'AUTOMATED FOLLOW-UP', num: '06', dur: 7    },
  { id: 'closing',  label: 'SEE IT IN ACTION',    num: '07', dur: 4    },
];
const STEPS = (() => {
  let acc = 0;
  return _RAW_STEPS.map(s => {
    const o = { ...s, start: acc };
    acc += s.dur;
    return o;
  });
})();
const CLOSING_DUR = 5;
const TOTAL_DUR = STEPS.reduce((a, s) => a + s.dur, 0);
window.STEPS = STEPS;
window.STEP_DUR = STEP_DUR;
window.TOTAL_DUR = TOTAL_DUR;

// Canvas
const W = 1100;
const H = 619;
const CX = W / 2;
const CY = H / 2;

// ── Layout regions ──
// Each step has a header zone (top, ~120px) and a stage zone (the rest).
const HEADER_H = 138;
const STAGE_TOP = HEADER_H + 18;
const STAGE_H = H - STAGE_TOP - 28;
const STAGE_CY = STAGE_TOP + STAGE_H / 2;

// ── Helpers ──
const { useTime } = window;
const { Easing } = window;
const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
const between = (v, a, b) => v >= a && v < b;
const lerp = (a, b, t) => a + (b - a) * t;
function getTweaks() {
  return window.__TWEAKS || {};
}

// Smooth crossfade for a step's content. Returns 0..1 opacity.
// Fades in over first 0.5s, holds, fades out over last 0.5s.
function stepOpacity(t, dur = STEP_DUR) {
  if (t < 0 || t >= dur) return 0;
  const fadeIn = clamp(t / 0.5, 0, 1);
  const fadeOut = clamp((dur - t) / 0.5, 0, 1);
  return Math.min(fadeIn, fadeOut);
}

// ── Ambient particles (continuity across all steps) ──
const AMBIENT = (() => {
  const arr = [];
  let s = 0xfeed1234;
  const rnd = () => { s = (s * 1664525 + 1013904223) >>> 0; return s / 0xffffffff; };
  for (let i = 0; i < 60; i++) {
    arr.push({
      x: rnd() * W,
      y: rnd() * H,
      r: 0.5 + rnd() * 1.0,
      op: 0.12 + rnd() * 0.18,
      driftX: (rnd() - 0.5) * 8,
      driftY: (rnd() - 0.5) * 8,
      phase: rnd() * Math.PI * 2,
    });
  }
  return arr;
})();

function AmbientField({ time }) {
  return (
    <g>
      {AMBIENT.map((p, i) => {
        const dx = Math.sin(time * 0.15 + p.phase) * p.driftX;
        const dy = Math.cos(time * 0.13 + p.phase) * p.driftY;
        const op = p.op * (0.7 + 0.3 * Math.sin(time * 0.6 + p.phase));
        return (
          <circle key={i}
                  cx={p.x + dx} cy={p.y + dy}
                  r={p.r} fill="white" opacity={op} />
        );
      })}
    </g>
  );
}

// ── Per-step header (eyebrow + H1 + H2) ──
// Renders fixed at top of canvas, fades with the step.
function StepHeader({ eyebrow, h1, h2, opacity }) {
  if (opacity <= 0.01) return null;
  return (
    <foreignObject x={40} y={28} width={W - 80} height={HEADER_H - 8}
                   style={{ pointerEvents: 'none' }}>
      <div style={{
        opacity,
        transition: 'none',
      }}>
        <div style={{
          fontFamily: "'Oxanium', monospace",
          fontSize: 11,
          fontWeight: 300,
          letterSpacing: '0.28em',
          color: 'rgba(255,255,255,0.6)',
        }}>
          {eyebrow}
        </div>
        <h1 style={{
          margin: '6px 0 6px',
          fontFamily: "'Oxanium', monospace",
          fontSize: 30,
          fontWeight: 300,
          letterSpacing: '-0.005em',
          color: '#ffffff',
          lineHeight: 1.15,
        }}>
          {h1}
        </h1>
        <p style={{
          margin: 0,
          fontFamily: "'Inter', system-ui, sans-serif",
          fontSize: 14,
          fontWeight: 300,
          color: 'rgba(255,255,255,0.62)',
          lineHeight: 1.45,
          maxWidth: 760,
        }}>
          {h2}
        </p>
      </div>
    </foreignObject>
  );
}

// ── Reusable: line-art icons (white stroke 1.5, no fill) ──
function IconStroke({ d, size = 18 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none"
         style={{ display: 'block' }}>
      <path d={d} stroke="white" strokeWidth="1.5"
            strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
}
const ICON = {
  phone:    "M5 4h4l2 5-2.5 1.5a11 11 0 005 5L15 13l5 2v4a2 2 0 01-2 2A14 14 0 014 6a2 2 0 011-2z",
  sms:     "M4 5h16v10H8l-4 4V5z",
  email:    "M3 6h18v12H3z M3 6l9 7 9-7",
  cal:      "M4 6h16v14H4z M4 10h16 M8 4v4 M16 4v4",
  doc:      "M7 3h7l4 4v14H7z M14 3v4h4",
  user:     "M12 12a4 4 0 100-8 4 4 0 000 8z M4 21a8 8 0 0116 0",
  check:    "M5 13l4 4L19 7",
  bolt:     "M13 2L4 14h7l-1 8 9-12h-7l1-8z",
  arrow:    "M5 12h14 M13 6l6 6-6 6",
  trophy:   "M8 4h8v4a4 4 0 11-8 0V4z M5 4h3 M16 4h3 M9 14h6 M10 14v4 M14 14v4 M8 18h8",
  comment:  "M4 5h16v11H9l-5 4V5z",
  timer:    "M12 8v5l3 2 M12 21a8 8 0 100-16 8 8 0 000 16z M9 2h6 M12 2v3",
  upload:   "M12 16V4 M6 10l6-6 6 6 M4 18h16",
  paperclip:"M20 12l-8 8a5 5 0 11-7-7l9-9a3.5 3.5 0 015 5l-9 9a2 2 0 11-3-3l8-8",
  flag:     "M5 21V4 M5 4h12l-3 4 3 4H5",
};

// ── Closing summary frame ──
function ClosingFrame({ time }) {
  const t = time - STEPS[6].start;
  if (t < 0 || t >= CLOSING_DUR) return null;
  const op = stepOpacity(t, CLOSING_DUR);

  // Six mini panels in 2×3 grid. Each shows a tiny abstract glyph from its step.
  const cells = [
    { label: 'IDENTIFY',   glyph: 'network'  },
    { label: 'CONSOLIDATE',glyph: 'core'     },
    { label: 'DOCUMENT',   glyph: 'fields'   },
    { label: 'QUALIFY',    glyph: 'rings'    },
    { label: 'ROUTE',      glyph: 'roster'   },
    { label: 'FOLLOW UP',  glyph: 'branches' },
  ];
  const cw = 150, ch = 100;
  const gap = 16;
  const totalW = cw * 3 + gap * 2;
  const gridX = CX - totalW / 2;
  const gridY = STAGE_TOP + 10;

  // Logo + CTA appear after grid
  const logoT = clamp((t - 1.2) / 0.6, 0, 1);
  const ctaT = clamp((t - 1.8) / 0.5, 0, 1);

  return (
    <g opacity={op}>
      {/* Mini grid */}
      {cells.map((c, i) => {
        const col = i % 3, row = Math.floor(i / 3);
        const cellT = clamp((t - 0.1 - i * 0.08) / 0.5, 0, 1);
        if (cellT <= 0) return null;
        const px = gridX + col * (cw + gap);
        const py = gridY + row * (ch + gap);
        return (
          <g key={c.label} opacity={cellT}>
            <rect x={px} y={py} width={cw} height={ch} rx={6}
                  fill="rgba(255,255,255,0.03)"
                  stroke="rgba(255,255,255,0.35)" strokeWidth={1} />
            {/* Mini glyph */}
            <ClosingGlyph kind={c.glyph} cx={px + cw / 2} cy={py + ch / 2 - 8} />
            <text x={px + cw / 2} y={py + ch - 14}
                  textAnchor="middle"
                  fill="rgba(255,255,255,0.7)"
                  fontFamily="Oxanium, monospace" fontSize={10}
                  fontWeight={300} letterSpacing="0.2em">
              {c.label}
            </text>
          </g>
        );
      })}

      {/* CTA block */}
      {ctaT > 0 && (
        <g opacity={ctaT}>
          <foreignObject x={CX - 280} y={gridY + ch * 2 + gap + 24}
                         width={560} height={140}>
            <div style={{
              textAlign: 'center',
              fontFamily: "'Oxanium', monospace",
              transform: `translateY(${(1 - ctaT) * 12}px)`,
            }}>
              <div style={{
                fontSize: 24,
                fontWeight: 300,
                color: '#fff',
                letterSpacing: '-0.005em',
                marginBottom: 14,
              }}>
                See it run on your firm’s calls.
              </div>
              <button
                onClick={() => { window.parent.location.href = '/contact' }}
                style={{
                  padding: '14px 28px',
                  background: '#fff',
                  color: '#000',
                  border: 'none',
                  borderRadius: 6,
                  fontFamily: "'Oxanium', monospace",
                  fontSize: 12,
                  fontWeight: 300,
                  letterSpacing: '0.22em',
                  cursor: 'pointer',
                }}
              >
                REQUEST DEMO
              </button>
            </div>
          </foreignObject>
        </g>
      )}
    </g>
  );
}

function ClosingGlyph({ kind, cx, cy }) {
  // Simple iconographic representations of each step
  if (kind === 'network') {
    return (
      <g>
        {[[-20,-8],[10,-14],[-12,8],[18,4],[0,0]].map((p, i) => (
          <circle key={i} cx={cx + p[0]} cy={cy + p[1]} r={1.6} fill="white" />
        ))}
        <line x1={cx-20} y1={cy-8} x2={cx} y2={cy} stroke="white" strokeWidth={0.6} opacity={0.4} />
        <line x1={cx+10} y1={cy-14} x2={cx} y2={cy} stroke="white" strokeWidth={0.6} opacity={0.4} />
        <line x1={cx-12} y1={cy+8} x2={cx} y2={cy} stroke="white" strokeWidth={0.6} opacity={0.4} />
        <line x1={cx+18} y1={cy+4} x2={cx} y2={cy} stroke="white" strokeWidth={0.6} opacity={0.4} />
      </g>
    );
  }
  if (kind === 'core') {
    return (
      <g>
        <rect x={cx-8} y={cy-8} width={16} height={16} rx={3}
              fill="rgba(255,255,255,0.1)" stroke="white" strokeWidth={1.2} />
        {[[-22,-10],[22,-10],[-22,10],[22,10]].map((p, i) => (
          <g key={i}>
            <rect x={cx+p[0]-5} y={cy+p[1]-5} width={10} height={10} rx={2}
                  stroke="white" strokeWidth={0.8} fill="none" />
            <line x1={cx+(p[0]>0?p[0]-4:p[0]+4)} y1={cy+p[1]} x2={cx} y2={cy}
                  stroke="white" strokeWidth={0.5} opacity={0.4} />
          </g>
        ))}
      </g>
    );
  }
  if (kind === 'fields') {
    return (
      <g stroke="white" strokeWidth={0.9} fill="none">
        <circle cx={cx} cy={cy} r={2} fill="white" />
        {[0, 1, 2].map(i => (
          <rect key={i} x={cx-22} y={cy-12 + i*9} width={44} height={5} rx={1} opacity={0.7} />
        ))}
      </g>
    );
  }
  if (kind === 'rings') {
    return (
      <g stroke="white" fill="none">
        <circle cx={cx} cy={cy} r={6}  strokeWidth={1} />
        <circle cx={cx} cy={cy} r={12} strokeWidth={0.8} opacity={0.7} />
        <circle cx={cx} cy={cy} r={18} strokeWidth={0.6} opacity={0.45} />
        <circle cx={cx} cy={cy} r={1.5} fill="white" />
      </g>
    );
  }
  if (kind === 'roster') {
    return (
      <g>
        {[0,1,2,3].map(i => (
          <rect key={i} x={cx-22 + i*11} y={cy-7} width={9} height={14} rx={1.5}
                stroke="white" strokeWidth={0.9} fill="rgba(255,255,255,0.04)" />
        ))}
      </g>
    );
  }
  if (kind === 'branches') {
    return (
      <g stroke="white" strokeWidth={0.9} fill="none">
        <rect x={cx-4} y={cy-4} width={8} height={8} rx={1.5} fill="rgba(255,255,255,0.08)" />
        <line x1={cx+4} y1={cy} x2={cx+22} y2={cy-12} />
        <line x1={cx+4} y1={cy} x2={cx+22} y2={cy} />
        <line x1={cx+4} y1={cy} x2={cx+22} y2={cy+12} />
        <circle cx={cx+22} cy={cy-12} r={1.4} fill="white" />
        <circle cx={cx+22} cy={cy} r={1.4} fill="white" />
        <circle cx={cx+22} cy={cy+12} r={1.4} fill="white" />
      </g>
    );
  }
  return null;
}

// ── Step 1: THE PROBLEM ──
// Tangled web of dots; 4 highlighted dots stagger callouts with stats.

// Tangled point cloud — golden-spiral base + per-point radial/angular jitter
// so it reads as an organic mass, not a perfect sphere.
const SPHERE_PTS_3D = (() => {
  const N = 240;
  const arr = [];
  const golden = Math.PI * (3 - Math.sqrt(5));
  // Seeded rng for stable jitter
  let s = 0xfeedface;
  const rnd = () => { s = (s * 1664525 + 1013904223) >>> 0; return s / 0xffffffff; };
  for (let i = 0; i < N; i++) {
    const y = 1 - (i / (N - 1)) * 2;
    const r = Math.sqrt(1 - y * y);
    const theta = golden * i;
    let x = Math.cos(theta) * r;
    let z = Math.sin(theta) * r;
    let yy = y;
    // Radial jitter — squash/expand each point along its own normal
    const radial = 0.78 + rnd() * 0.42; // 0.78 to 1.20
    x *= radial; yy *= radial; z *= radial;
    // Tangential jitter — small random offset in 3D
    const jx = (rnd() - 0.5) * 0.16;
    const jy = (rnd() - 0.5) * 0.16;
    const jz = (rnd() - 0.5) * 0.16;
    arr.push({
      x: x + jx, y: yy + jy, z: z + jz,
      pulse: rnd() * Math.PI * 2,
    });
  }
  return arr;
})();

// Edges: K-nearest neighbors PLUS some longer "entangling" cross-cloud connections
// so the structure feels webbed rather than smoothly tiled.
const SPHERE_EDGES = (() => {
  const K = 4;
  const lines = [];
  const seen = new Set();
  for (let i = 0; i < SPHERE_PTS_3D.length; i++) {
    const a = SPHERE_PTS_3D[i];
    const dists = SPHERE_PTS_3D.map((b, j) => {
      if (i === j) return Infinity;
      const dx = a.x - b.x, dy = a.y - b.y, dz = a.z - b.z;
      return dx*dx + dy*dy + dz*dz;
    });
    const sorted = dists.map((d, j) => ({ d, j })).sort((x, y) => x.d - y.d);
    // K nearest
    for (let k = 0; k < K; k++) {
      const j = sorted[k].j;
      const key = i < j ? `${i}-${j}` : `${j}-${i}`;
      if (!seen.has(key)) { seen.add(key); lines.push([Math.min(i,j), Math.max(i,j)]); }
    }
    // 1 longer connection — pick from the 8th–20th-nearest range to entangle
    if (i % 3 === 0) {
      const farIdx = 8 + (i * 7) % 12;
      const j = sorted[farIdx]?.j;
      if (j !== undefined) {
        const key = i < j ? `${i}-${j}` : `${j}-${i}`;
        if (!seen.has(key)) { seen.add(key); lines.push([Math.min(i,j), Math.max(i,j)]); }
      }
    }
  }
  return lines;
})();

const PROBLEM_HILITES = [
  { i: 18,  big: '78%', label: 'of clients hire the firm that responds first' },
  { i: 78,  big: '60%', label: "of law firms don't answer the phone" },
  { i: 142, big: '64%', label: 'of leads receive no follow-up' },
  { i: 208, big: '20%', label: 'of missed calls are ever returned' },
];

function projectSphere(pt, time, cx, cy, R) {
  const yaw = time * 0.18;
  const pitch = Math.sin(time * 0.07) * 0.18 - 0.15;
  const cy_ = Math.cos(yaw), sy_ = Math.sin(yaw);
  let x = pt.x * cy_ + pt.z * sy_;
  let z = -pt.x * sy_ + pt.z * cy_;
  let y = pt.y;
  const cp = Math.cos(pitch), sp = Math.sin(pitch);
  const y2 = y * cp - z * sp;
  const z2 = y * sp + z * cp;
  y = y2; z = z2;
  const persp = 1 / (1.6 - z * 0.35);
  return { sx: cx + x * R * persp, sy: cy + y * R * persp, z };
}

function StepProblem({ time }) {
  const dur = STEPS[0].dur;
  const t = time - STEPS[0].start;
  if (t < -0.4 || t >= dur + 0.4) return null;
  const op = stepOpacity(t, dur);

  const hiliteSet = new Set(PROBLEM_HILITES.map(h => h.i));

  // Sphere position + size
  const sphereCX = CX;
  const sphereCY = STAGE_CY + 10;
  const sphereR = 195;

  // Project all 3D points → 2D for this frame
  const projected = SPHERE_PTS_3D.map(p => projectSphere(p, time, sphereCX, sphereCY, sphereR));

  // Stacked-popup timing: each appears in turn and stays visible through end of step.
  const POPUP_APPEAR = [1.0, 2.4, 3.8, 5.2];
  const POPUP_APPEAR_DUR = 0.35;

  return (
    <g opacity={op}>
      <StepHeader
        eyebrow="THE PROBLEM"
        h1="Most leads never become clients."
        h2="Not because the cases are bad — because nobody manages leads."
        opacity={1}
      />

      {/* Sphere edges */}
      <g>
        {SPHERE_EDGES.map(([a, b], i) => {
          const pa = projected[a], pb = projected[b];
          const drawT = clamp((t - 0.05 - i * 0.004) / 0.5, 0, 1);
          if (drawT <= 0) return null;
          // Depth-based opacity: average z (-1 back, +1 front)
          const avgZ = (pa.z + pb.z) / 2;
          const depthOp = 0.18 + ((avgZ + 1) / 2) * 0.42;
          const ex = pa.sx + (pb.sx - pa.sx) * drawT;
          const ey = pa.sy + (pb.sy - pa.sy) * drawT;
          return (
            <line key={i} x1={pa.sx} y1={pa.sy} x2={ex} y2={ey}
                  stroke="white" strokeWidth={0.6} opacity={depthOp} />
          );
        })}
      </g>

      {/* Sphere points */}
      {projected.map((p, i) => {
        const isHi = hiliteSet.has(i);
        const breath = 0.5 + 0.5 * Math.sin(time * 1.4 + SPHERE_PTS_3D[i].pulse);
        // Depth scaling
        const depth = (p.z + 1) / 2; // 0 (back) → 1 (front)
        const depthOp = 0.25 + depth * 0.65;
        const baseR = 1.3 + depth * 0.9;
        const r = isHi ? 4.5 + breath * 1.0 : baseR + breath * 0.3;
        const opDot = isHi ? 0.95 : depthOp;
        return (
          <g key={i}>
            {isHi && (
              <circle cx={p.sx} cy={p.sy} r={r * 2.4}
                      fill="white" opacity={0.18 + breath * 0.1} />
            )}
            <circle cx={p.sx} cy={p.sy} r={r} fill="white" opacity={opDot}
                    style={isHi ? { filter: 'drop-shadow(0 0 6px rgba(255,255,255,0.9))' } : null} />
          </g>
        );
      })}

      {/* Stacked callouts — fixed slots, L-shaped connectors */}
      {PROBLEM_HILITES.map((h, idx) => {
        const appearAt = POPUP_APPEAR[idx];
        if (t < appearAt) return null;
        const dot = projected[h.i];
        const lt = clamp((t - appearAt) / POPUP_APPEAR_DUR, 0, 1);
        const cOp = lt;
        if (cOp <= 0) return null;
        const activeIdx = idx;
        const start = appearAt;
        // Card dimensions — wide enough for "of clients hire the firm that responds first"
        const cw = 280, ch = 92;
        // Fixed slots near the sphere — TL, TR, BL, BR relative to the bubble.
        const sphereGap = 18;
        const leftCx  = sphereCX - sphereR - sphereGap - cw;
        const rightCx = sphereCX + sphereR + sphereGap;
        const topCy   = sphereCY - sphereR + 4;
        const botCy   = sphereCY + sphereR - ch - 4;
        const slots = [
          { cx: leftCx,  cy: topCy, isLeft: true,  isTop: true  },
          { cx: rightCx, cy: topCy, isLeft: false, isTop: true  },
          { cx: leftCx,  cy: botCy, isLeft: true,  isTop: false },
          { cx: rightCx, cy: botCy, isLeft: false, isTop: false },
        ];
        const slot = slots[activeIdx % 4];
        const { cx, cy, isLeft } = slot;
        // Card edge that line attaches to — vertical center of the inside edge
        const labelEdgeX = isLeft ? cx + cw : cx;
        const labelEdgeY = cy + ch / 2;
        // L-shape: line leaves dot horizontally (perpendicular to card's vertical edge),
        // travels 30% of the horizontal run, bends 90°, then vertical to card edge Y.
        // The 30% bend point is computed from the *full* horizontal distance.
        const fullHorizLen = labelEdgeX - dot.sx; // signed
        const elbowX = dot.sx + fullHorizLen * 0.30;
        const elbowY = dot.sy;
        // Then a vertical leg from elbow to card-edge Y, then a horizontal leg
        // continuation from elbow Y to the card edge X. (3-segment polyline.)
        // Segments: (dot → elbowX, dot.sy)  →  (elbowX, labelEdgeY)  →  (labelEdgeX, labelEdgeY)
        const seg1Len = Math.abs(elbowX - dot.sx);
        const seg2Len = Math.abs(labelEdgeY - elbowY);
        const seg3Len = Math.abs(labelEdgeX - elbowX);
        const totalLen = seg1Len + seg2Len + seg3Len;
        const drawT = clamp((t - start - 0.05) / 0.35, 0, 1);
        const drawnLen = totalLen * drawT;
        const s1Drawn = Math.min(drawnLen, seg1Len);
        const s2Drawn = Math.max(0, Math.min(drawnLen - seg1Len, seg2Len));
        const s3Drawn = Math.max(0, drawnLen - seg1Len - seg2Len);
        const s1EndX = dot.sx + Math.sign(elbowX - dot.sx) * s1Drawn;
        const s2EndY = elbowY + Math.sign(labelEdgeY - elbowY) * s2Drawn;
        const s3EndX = elbowX + Math.sign(labelEdgeX - elbowX) * s3Drawn;
        return (
          <g opacity={cOp}>
            {/* Origin marker at the rotating dot */}
            <circle cx={dot.sx} cy={dot.sy} r={6}
                    fill="white" opacity={0.18} />
            <circle cx={dot.sx} cy={dot.sy} r={3.2}
                    fill="white" opacity={1}
                    style={{ filter: 'drop-shadow(0 0 8px rgba(255,255,255,1))' }} />
            {/* Segment 1: horizontal from dot */}
            <line x1={dot.sx} y1={dot.sy} x2={s1EndX} y2={dot.sy}
                  stroke="white" strokeWidth={1.1} opacity={0.85} />
            {/* Segment 2: vertical from elbow */}
            {s2Drawn > 0 && (
              <line x1={elbowX} y1={elbowY} x2={elbowX} y2={s2EndY}
                    stroke="white" strokeWidth={1.1} opacity={0.85} />
            )}
            {/* Segment 3: horizontal from elbow's lower/upper end to card */}
            {s3Drawn > 0 && (
              <line x1={elbowX} y1={labelEdgeY} x2={s3EndX} y2={labelEdgeY}
                    stroke="white" strokeWidth={1.1} opacity={0.85} />
            )}
            {drawT >= 1 && (
              <circle cx={labelEdgeX} cy={labelEdgeY} r={2.5}
                      fill="white" opacity={0.9} />
            )}
            {/* Card */}
            <rect x={cx} y={cy} width={cw} height={ch} rx={2}
                  fill="rgba(0,0,0,0.78)"
                  stroke="rgba(255,255,255,0.55)" strokeWidth={0.8} />
            <foreignObject x={cx + 18} y={cy + 12} width={cw - 36} height={ch - 24}>
              <div style={{
                fontFamily: "'Oxanium', monospace",
                fontSize: 34, fontWeight: 300, color: '#fff',
                lineHeight: 1, letterSpacing: '-0.02em',
              }}>{h.big}</div>
              <div style={{
                marginTop: 6,
                fontFamily: "'Inter', system-ui, sans-serif",
                fontSize: 11, fontWeight: 300,
                color: 'rgba(255,255,255,0.7)',
                lineHeight: 1.35,
                letterSpacing: '0.01em',
              }}>&ldquo;{h.label}&rdquo;</div>
            </foreignObject>
          </g>
        );
      })}

    </g>
  );
}

// ── Step 2: ONE PLATFORM ──
// Network collapses → core square → 4 satellites with pulsing connectors.

function StepPlatform({ time }) {
  const dur = STEPS[1].dur;
  const t = time - STEPS[1].start;
  if (t < -0.4 || t >= dur + 0.4) return null;
  const op = stepOpacity(t, dur);

  const coreX = CX, coreY = STAGE_CY;

  // Phase 1 (0–2s): collapse — particles fly from random outer positions to core
  // Phase 2 (2–3s): core flash + settle
  // Phase 3 (3–5s): satellites appear stagger
  // Phase 4 (5–8s): pulses travel along connectors

  // Collapsing dots — sphere from previous step continues rotating, then collapses inward
  const collapseEls = [];
  if (t < 2.4) {
    // Frozen sphere positions at the moment Step 2 begins (use STEPS[0].start + STEP_DUR)
    const sphereTime = STEPS[1].start + Math.min(t, 0.3); // gentle continued rotation for first 0.3s
    const sphereCX = CX, sphereCY = STAGE_CY + 10, sphereR = 195;
    const proj = SPHERE_PTS_3D.map(p => projectSphere(p, sphereTime, sphereCX, sphereCY, sphereR));
    proj.forEach((p, i) => {
      const lt = clamp(t / 1.8, 0, 1);
      const e = Easing.easeInQuad(lt);
      const x = lerp(p.sx, coreX, e);
      const y = lerp(p.sy, coreY, e);
      const depth = (p.z + 1) / 2;
      const r = (1.4 + depth * 0.9) * (1 - e * 0.4);
      const op = (0.3 + depth * 0.55) * (1 - e * 0.6);
      collapseEls.push(
        <circle key={i} cx={x} cy={y} r={r} fill="white" opacity={op} />
      );
    });
  }

  // Core square — appears at ~1.7s with flash
  let core = null;
  if (t >= 1.5) {
    const ct = clamp((t - 1.5) / 0.5, 0, 1);
    const flashT = clamp((t - 1.8) / 0.4, 0, 1);
    const flashOp = (1 - flashT) * 0.9;
    const size = 80;
    const scale = Easing.easeOutBack(ct);
    core = (
      <g transform={`translate(${coreX}, ${coreY}) scale(${scale}) translate(${-coreX}, ${-coreY})`}
         style={{ filter: 'drop-shadow(0 0 14px rgba(255,255,255,0.5))' }}>
        {/* Flash */}
        {flashOp > 0 && (
          <circle cx={coreX} cy={coreY} r={size * 1.4 * (0.4 + flashT * 0.8)}
                  fill="white" opacity={flashOp * 0.4} />
        )}
        <rect x={coreX - size / 2} y={coreY - size / 2}
              width={size} height={size} rx={6}
              fill="rgba(255,255,255,0.06)"
              stroke="white" strokeWidth={1.5} />
        <image href="leaddelta-logo.png"
               x={coreX - size * 0.36} y={coreY - size * 0.36}
               width={size * 0.72} height={size * 0.72}
               preserveAspectRatio="xMidYMid meet" />
      </g>
    );
  }

  // Satellites
  const sats = [
    { dx: -260, dy: -110, label: 'INTAKE',    delay: 2.8, icon: ICON.phone },
    { dx:  260, dy: -110, label: 'QUALIFY',   delay: 3.0, icon: ICON.check },
    { dx: -260, dy:  110, label: 'MANAGE',    delay: 3.2, icon: ICON.user  },
    { dx:  260, dy:  110, label: 'FOLLOW UP', delay: 3.4, icon: ICON.cal   },
  ];

  const satEls = [];
  const lineEls = [];
  const pulseEls = [];

  sats.forEach((s, i) => {
    const tx = coreX + s.dx;
    const ty = coreY + s.dy;
    const lt = clamp((t - s.delay) / 0.5, 0, 1);
    if (lt <= 0) return;
    const sw = 130, sh = 56;
    const scale = Easing.easeOutBack(lt);
    // Connector line — draw from core outward
    const lineDrawT = clamp((t - s.delay - 0.15) / 0.4, 0, 1);
    if (lineDrawT > 0) {
      const ax = coreX + (s.dx > 0 ? 40 : -40);
      const ay = coreY + (s.dy > 0 ? 40 : -40);
      const bx = tx + (s.dx > 0 ? -sw / 2 : sw / 2);
      const by = ty;
      const ex = ax + (bx - ax) * lineDrawT;
      const ey = ay + (by - ay) * lineDrawT;
      lineEls.push(
        <line key={`l${i}`} x1={ax} y1={ay} x2={ex} y2={ey}
              stroke="white" strokeWidth={1} opacity={0.55} />
      );
    }
    // Satellite box
    satEls.push(
      <g key={`s${i}`} opacity={lt}
         transform={`translate(${tx - sw / 2}, ${ty - sh / 2}) scale(${scale})`}
         style={{ transformOrigin: `${sw / 2}px ${sh / 2}px` }}>
        <rect x={0} y={0} width={sw} height={sh} rx={6}
              fill="rgba(0,0,0,0.85)"
              stroke="white" strokeWidth={1.2} />
        <g transform={`translate(${14}, ${sh / 2 - 9})`}>
          <IconStroke d={s.icon} size={18} />
        </g>
        <text x={42} y={sh / 2 + 4}
              fill="white"
              fontFamily="Oxanium, monospace" fontSize={12}
              fontWeight={300} letterSpacing="0.2em">
          {s.label}
        </text>
      </g>
    );

    // After all satellites/lines are drawn, hold the screen — no further animations.
  });

  return (
    <g opacity={op}>
      <StepHeader
        eyebrow="THE SOLUTION"
        h1="One platform between the call and the case."
        h2="Our platform replaces your intake stack with a single AI-powered system. Every channel, every lead, every step — accountable."
        opacity={1}
      />
      {collapseEls}
      {lineEls}
      {core}
      {pulseEls}
      {satEls}
    </g>
  );
}

// ── Step 3: LEAD PROFILE ──
// Central lead dot → fields fan out radially → AI SCORE climaxes.

const PROFILE_FIELDS = [
  { key: 'NAME',           value: 'Roberto Mendes' },
  { key: 'SOURCE',         value: 'Inbound call' },
  { key: 'LANGUAGE',       value: 'English' },
  { key: 'INCIDENT',       value: 'Auto collision, I-405' },
  { key: 'INSURANCE',      value: 'State Farm' },
  { key: 'LIABILITY',      value: '85' },
  { key: 'INJURY SEVERITY',value: '74' },
  { key: 'STATUTE',        value: '2 yrs (exp Aug 2027)' },
  { key: 'EST. CASE VALUE',value: '$180k–$240k' },
];

function StepProfile({ time }) {
  const dur = STEPS[2].dur;
  const t = time - STEPS[2].start;
  if (t < -0.4 || t >= dur + 0.4) return null;
  const op = stepOpacity(t, dur);

  const cx = CX, cy = STAGE_CY;
  // No rotation — keep the profile steady.

  // Lead dot flash
  let leadDot = null;
  if (t >= 0.1) {
    const dotT = clamp((t - 0.1) / 0.4, 0, 1);
    const flashT = clamp((t - 0.2) / 0.5, 0, 1);
    leadDot = (
      <g>
        <circle cx={cx} cy={cy} r={4 + (1 - flashT) * 12}
                fill="white" opacity={(1 - flashT) * 0.5} />
        <circle cx={cx} cy={cy} r={3.5 * dotT}
                fill="white"
                style={{ filter: 'drop-shadow(0 0 10px rgba(255,255,255,0.9))' }} />
      </g>
    );
  }

  // Field positions in radial layout
  // 9 fields arranged in 2 columns to left and right of center, with spacing
  // Plus the AI SCORE big tile at right.
  // Layout: 4 fields stack on left, 4 fields stack on right, last field (AI SCORE) is the big climax bottom-right.
  // Actually let's do: 4 fields left column, 4 fields right column, AI SCORE big circle below center.
  const colSpacingY = 38;
  const fieldW = 200;
  const leftX = cx - 290;
  const rightX = cx + 90;
  const startY = cy - colSpacingY * 1.5 - 14;
  const fieldPositions = [
    { x: leftX,  y: startY,                            // NAME
      anchorX: leftX + fieldW, anchorY: startY + 14 },
    { x: leftX,  y: startY + colSpacingY,              // SOURCE
      anchorX: leftX + fieldW, anchorY: startY + colSpacingY + 14 },
    { x: leftX,  y: startY + colSpacingY * 2,          // LANGUAGE
      anchorX: leftX + fieldW, anchorY: startY + colSpacingY * 2 + 14 },
    { x: leftX,  y: startY + colSpacingY * 3,          // INCIDENT
      anchorX: leftX + fieldW, anchorY: startY + colSpacingY * 3 + 14 },
    { x: rightX, y: startY,                            // INSURANCE
      anchorX: rightX, anchorY: startY + 14 },
    { x: rightX, y: startY + colSpacingY,              // LIABILITY
      anchorX: rightX, anchorY: startY + colSpacingY + 14 },
    { x: rightX, y: startY + colSpacingY * 2,          // INJURY
      anchorX: rightX, anchorY: startY + colSpacingY * 2 + 14 },
    { x: rightX, y: startY + colSpacingY * 3,          // STATUTE
      anchorX: rightX, anchorY: startY + colSpacingY * 3 + 14 },
  ];
  // EST CASE VALUE — small below center
  const estPos = { x: cx - 110, y: startY + colSpacingY * 4 + 6,
                   anchorX: cx, anchorY: startY + colSpacingY * 4 + 20 };

  const fieldStartT = 0.5;
  const fieldStaggerS = 0.32;

  const fieldEls = [];
  const lineEls = [];

  PROFILE_FIELDS.slice(0, 8).forEach((f, i) => {
    const lt = clamp((t - fieldStartT - i * fieldStaggerS) / 0.4, 0, 1);
    if (lt <= 0) return;
    const pos = fieldPositions[i];
    const fh = 30;
    // Connector line from center to field
    const drawT = clamp((t - fieldStartT - i * fieldStaggerS - 0.05) / 0.3, 0, 1);
    if (drawT > 0) {
      const ex = cx + (pos.anchorX - cx) * drawT;
      const ey = cy + (pos.anchorY - cy) * drawT;
      lineEls.push(
        <line key={`pl${i}`} x1={cx} y1={cy} x2={ex} y2={ey}
              stroke="white" strokeWidth={0.7} opacity={0.35 * lt} />
      );
    }
    fieldEls.push(
      <g key={`pf${i}`} opacity={lt}>
        <rect x={pos.x} y={pos.y} width={fieldW} height={fh} rx={4}
              fill="rgba(0,0,0,0.7)"
              stroke="rgba(255,255,255,0.45)" strokeWidth={1} />
        <text x={pos.x + 10} y={pos.y + 13}
              fill="rgba(255,255,255,0.55)"
              fontFamily="Oxanium, monospace" fontSize={8.5}
              fontWeight={300} letterSpacing="0.18em">
          {f.key}
        </text>
        <text x={pos.x + 10} y={pos.y + 25}
              fill="white"
              fontFamily="Inter, system-ui, sans-serif" fontSize={11}
              fontWeight={300}>
          {f.value}
        </text>
      </g>
    );
  });

  // EST. CASE VALUE
  {
    const i = 8;
    const lt = clamp((t - fieldStartT - i * fieldStaggerS) / 0.4, 0, 1);
    if (lt > 0) {
      const fw = 220, fh = 30;
      const drawT = clamp((t - fieldStartT - i * fieldStaggerS - 0.05) / 0.3, 0, 1);
      const ex = cx + (estPos.anchorX - cx) * drawT;
      const ey = cy + (estPos.anchorY - cy) * drawT;
      lineEls.push(
        <line key="ple" x1={cx} y1={cy} x2={ex} y2={ey}
              stroke="white" strokeWidth={0.7} opacity={0.35 * lt} />
      );
      fieldEls.push(
        <g key="pfe" opacity={lt}>
          <rect x={estPos.x} y={estPos.y} width={fw} height={fh} rx={4}
                fill="rgba(0,0,0,0.7)"
                stroke="rgba(255,255,255,0.45)" strokeWidth={1} />
          <text x={estPos.x + 10} y={estPos.y + 13}
                fill="rgba(255,255,255,0.55)"
                fontFamily="Oxanium, monospace" fontSize={8.5}
                fontWeight={300} letterSpacing="0.18em">
            EST. CASE VALUE
          </text>
          <text x={estPos.x + 10} y={estPos.y + 25}
                fill="white"
                fontFamily="Inter, system-ui, sans-serif" fontSize={11}
                fontWeight={300}>
            {PROFILE_FIELDS[8].value}
          </text>
        </g>
      );
    }
  }

  // AI SCORE removed — it's the climax of Step 4 (Qualification) instead.

  return (
    <g opacity={op}>
      <StepHeader
        eyebrow="EVERY LEAD, FULLY DOCUMENTED"
        h1="A complete case profile. Before your team picks up."
        h2="AI captures the call, transcribes it, scores it, and generates a structured case file — automatically. Your team sees everything in seconds."
        opacity={1}
      />
      <g>
        {lineEls}
        {leadDot}
        {fieldEls}
      </g>
    </g>
  );
}

// Export step components to window for the rest in scenes-v4-b.jsx
Object.assign(window, {
  W, H, CX, CY, STAGE_TOP, STAGE_H, STAGE_CY, HEADER_H,
  STEP_DUR, STEPS, TOTAL_DUR, CLOSING_DUR,
  clamp, between, lerp, getTweaks, stepOpacity,
  AmbientField, StepHeader, IconStroke, ICON,
  StepProblem, StepPlatform, StepProfile,
  ClosingFrame,
});
