// AssistantPanel.jsx — Jenny's decision flow as a vertical stack of cards that
// rise UP and out one-by-one on the right, ending in a centered success modal.
//   alert → scan (avatar grid) → candidate 1 (✗) → candidate 2 (✗) →
//   candidate 3 / Maya (✓ call) → live call → centered "Shift covered" modal.

const { useTime, Easing, clamp, interpolate } = window;

// right-side stack anchor
const STACK = { right: 168, top: 206, w: 452 };

// ── timeline (card in/out windows) ───────────────────────────────────────────
const SEQ = {
  alert:   { in: 1.8,  out: 5.2 },
  scan:    { in: 5.4,  out: 8.8 },
  cand1:   { in: 9.0,  out: 11.3 },
  cand2:   { in: 11.4, out: 13.7 },
  cand3:   { in: 13.8, out: 16.4 },
  call:    { in: 16.6, out: 21.8 },
  confirm: { in: 23.6, out: 99 },
};
const EXIT = 0.5;
const DURATION = 27.0;

function phase(t, p) {
  if (t < p.in) return null;
  const exitP = t > p.out ? clamp((t - p.out) / EXIT, 0, 1) : 0;
  if (exitP >= 1) return null;
  return { age: t - p.in, exitP };
}

// ── card shell: rises in from below, drifts UP + fades out, gentle idle float ─
function CardShell({ age, exitP, t, idle = 0, children, style }) {
  const eIn = Easing.easeOutBack(clamp(age / 0.6, 0, 1));
  const tyIn = (1 - Easing.easeOutCubic(clamp(age / 0.5, 0, 1))) * 46;
  const oIn = clamp(age / 0.28, 0, 1);
  const sIn = 0.9 + 0.1 * eIn;
  const ez = Easing.easeInCubic(exitP);
  const oOut = 1 - exitP;
  const tyOut = -ez * 86;
  const sOut = 1 - 0.06 * ez;
  const settled = clamp((age - 0.7) / 0.6, 0, 1) * (1 - exitP);
  const floatY = Math.sin(t * 1.3 + idle) * 3 * settled;
  return (
    <div style={{
      position: 'absolute', top: 0, right: 0, width: '100%',
      opacity: oIn * oOut,
      transform: `translateY(${tyIn + tyOut + floatY}px) scale(${sIn * sOut})`,
      transformOrigin: '50% 100%', willChange: 'transform, opacity',
      ...style,
    }}>{children}</div>
  );
}

const cardStyle = (TB) => ({
  background: '#fff', border: `1px solid ${TB.line}`, borderRadius: 24,
  boxShadow: '0 30px 70px rgba(28,34,42,0.20), 0 6px 16px rgba(28,34,42,0.08)',
  fontFamily: TB.sans,
});

// ── small helpers ─────────────────────────────────────────────────────────────
const Caret = ({ t, c }) => (
  <span style={{ display: 'inline-block', width: 3, height: '0.92em', marginLeft: 3, borderRadius: 2, transform: 'translateY(2px)', background: c || window.TB.key, opacity: Math.sin(t * 7) > -0.2 ? 1 : 0.15 }}></span>
);
function TypedRich({ segments, shown }) {
  let acc = 0;
  return segments.map((s, i) => {
    const start = acc; acc += s.text.length;
    const vis = clamp(shown - start, 0, s.text.length);
    if (vis <= 0) return null;
    return <span key={i} style={s.style}>{s.text.slice(0, Math.round(vis))}</span>;
  });
}
const lenOf = (segs) => segs.reduce((a, s) => a + s.text.length, 0);
const Dots = ({ t, c }) => (
  <span style={{ display: 'inline-flex', gap: 4 }}>{[0, 1, 2].map(d => <span key={d} style={{ width: 6, height: 6, borderRadius: 3, background: c || window.TB.key, opacity: 0.25 + 0.75 * Math.abs(Math.sin(t * 4 - d * 0.6)) }}></span>)}</span>
);
const ACheck = ({ c = '#fff', s = 18 }) => (
  <svg width={s} height={s} viewBox="0 0 16 16" fill="none"><path d="M3.5 8.5l3 3 6-7" stroke={c} strokeWidth="2.3" strokeLinecap="round" strokeLinejoin="round" /></svg>
);
const AX = ({ c = '#fff', s = 18 }) => (
  <svg width={s} height={s} viewBox="0 0 16 16" fill="none"><path d="M4 4l8 8M12 4l-8 8" stroke={c} strokeWidth="2.3" strokeLinecap="round" /></svg>
);

// ── agent header (shared) ────────────────────────────────────────────────────
function JennyHead({ t, sub }) {
  const TB = window.TB;
  const pulse = 0.5 + 0.5 * Math.sin(t * 3);
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
      <div style={{ width: 52, height: 52, borderRadius: 16, overflow: 'hidden', flexShrink: 0, background: '#EEF0F3', boxShadow: `0 0 0 3px #fff, 0 0 ${6 + pulse * 10}px rgba(28,34,42,0.25)`, border: `2px solid ${TB.key}` }}>
        <img src="assets/jenny.gif" alt="Jenny" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
      </div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 23, fontWeight: 700, color: TB.ink, letterSpacing: '-0.025em', lineHeight: 1 }}>Jenny</div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 7, marginTop: 7 }}>
          <span style={{ width: 8, height: 8, borderRadius: 5, background: '#1D8A28', boxShadow: `0 0 0 ${3 + pulse * 2}px rgba(29,138,40,0.18)` }}></span>
          <span style={{ fontSize: 13.5, fontWeight: 500, color: TB.ink2 }}>{sub}</span>
        </div>
      </div>
    </div>
  );
}

// ── 1. alert card ─────────────────────────────────────────────────────────────
function AlertCard({ age, exitP, t }) {
  const TB = window.TB;
  const segs = [
    { text: 'Camren cancelled', style: { color: '#CC1E1D', fontWeight: 700 } },
    { text: ' his 3\u202fPM shift today. ', style: {} },
    { text: 'Finding a replacement now.', style: { color: TB.ink2, fontWeight: 500 } },
  ];
  const shown = (age - 0.55) * 52;
  const typing = shown < lenOf(segs);
  return (
    <CardShell age={age} exitP={exitP} t={t} idle={0} style={{ ...cardStyle(TB), padding: 26 }}>
      <JennyHead t={t} sub="Shift replacement agent" />
      <div style={{ marginTop: 20, fontSize: 21, lineHeight: 1.4, fontWeight: 600, color: TB.ink, letterSpacing: '-0.01em', minHeight: 88 }}>
        <TypedRich segments={segs} shown={shown} />
        {age > 0.55 && typing && <Caret t={t} />}
      </div>
    </CardShell>
  );
}

// ── 2. scanning card (visual avatar grid) ─────────────────────────────────────
const AV_TONES = ['#D6CFC4', '#E0B9A6', '#B9C7B0', '#C9B6D6', '#A9C2D6', '#C7C2B4', '#E6C79C', '#A9CFC4', '#D6A9B5', '#B4B9D6', '#CFB7A0', '#A7CBB6'];
const SCAN_N = 26;
const PICKS = { 6: 'assets/deon.png', 13: 'assets/luka.png', 20: 'assets/maya.png' };

function ScanCard({ age, exitP, t }) {
  const TB = window.TB;
  const prog = clamp((age - 0.35) / 2.3, 0, 1);
  const current = prog * SCAN_N;
  const reviewed = Math.min(SCAN_N, Math.round(current));
  const matches = Object.keys(PICKS).filter(i => +i < current).length;
  const done = prog >= 1;
  return (
    <CardShell age={age} exitP={exitP} t={t} idle={1.1} style={{ ...cardStyle(TB), padding: 22 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 16 }}>
        <Dots t={t} />
        <span style={{ fontSize: 16, fontWeight: 650, color: TB.ink, letterSpacing: '-0.01em' }}>{done ? 'Scan complete' : 'Scanning available staff'}</span>
        <span style={{ marginLeft: 'auto', fontFamily: TB.mono, fontSize: 13, fontWeight: 500, color: TB.ink2 }}>{reviewed}/{SCAN_N}</span>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(9, 1fr)', gap: 9 }}>
        {Array.from({ length: SCAN_N }).map((_, i) => {
          const seen = i < current;
          const isActive = Math.abs(i - current) < 1 && !done;
          const isPick = PICKS[i] !== undefined;
          const picked = isPick && i < current;
          const reveal = clamp((current - i) / 0.8, 0, 1);
          const o = seen ? 1 : 0.32;
          const ring = picked ? '#1D8A28' : isActive ? TB.key : 'transparent';
          const pop = picked ? Easing.easeOutBack(clamp((current - i) / 1.2, 0, 1)) : 1;
          return (
            <div key={i} style={{ position: 'relative', paddingBottom: '100%' }}>
              <div style={{
                position: 'absolute', inset: 0, borderRadius: '50%', overflow: 'hidden',
                background: AV_TONES[(i * 5 + 3) % AV_TONES.length], opacity: o,
                boxShadow: ring !== 'transparent' ? `0 0 0 2.5px ${ring}, 0 0 0 4px #fff` : 'none',
                transform: `scale(${(picked ? 1.08 * pop : isActive ? 1.06 : 1)})`,
                transition: 'none',
              }}>
                {isPick && <img src={PICKS[i]} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover', opacity: reveal }} />}
              </div>
              {picked && (
                <span style={{ position: 'absolute', right: -2, bottom: -2, width: 15, height: 15, borderRadius: 8, background: '#1D8A28', border: '2px solid #fff', display: 'flex', alignItems: 'center', justifyContent: 'center', transform: `scale(${pop})` }}><ACheck c="#fff" s={9} /></span>
              )}
            </div>
          );
        })}
      </div>

      <div style={{ marginTop: 16, display: 'flex', alignItems: 'center', gap: 9, fontSize: 14, fontWeight: 600, color: matches > 0 ? '#16681E' : TB.ink2 }}>
        <span style={{ width: 8, height: 8, borderRadius: 4, background: matches > 0 ? '#1D8A28' : TB.ink3 }}></span>
        {done ? '3 strong matches found' : `${matches} match${matches === 1 ? '' : 'es'} so far…`}
      </div>
    </CardShell>
  );
}

// ── 3. candidate verdict cards (1-by-1) ───────────────────────────────────────
function CandidateCard({ age, exitP, t, c }) {
  const TB = window.TB;
  const match = c.verdict === 'match';
  const avPop = Easing.easeOutBack(clamp(age / 0.5, 0, 1));
  const showVerdict = age > 1.05;
  const vPop = Easing.easeOutBack(clamp((age - 1.05) / 0.45, 0, 1));
  const reasonShown = (age - 1.35) * 40;
  const accent = match ? '#1D8A28' : '#CC1E1D';
  const accentBg = match ? '#E6F6E7' : '#FBECEC';
  return (
    <CardShell age={age} exitP={exitP} t={t} idle={c.idle} style={{ ...cardStyle(TB), padding: 22, borderTop: `4px solid ${accent}` }}>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 }}>
        <span style={{ fontSize: 12, fontWeight: 700, color: TB.ink3, letterSpacing: '0.08em' }}>CANDIDATE {c.idx} / 3</span>
        <span style={{ fontSize: 22, fontWeight: 700, color: match ? '#1D8A28' : TB.ink3, letterSpacing: '-0.02em' }}>{c.score}<span style={{ fontSize: 12, fontWeight: 600 }}>%</span></span>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 15 }}>
        <div style={{ width: 64, height: 64, borderRadius: 32, overflow: 'hidden', flexShrink: 0, background: '#eee', boxShadow: `0 0 0 3px #fff, 0 0 0 ${match ? 5 : 4}px ${match ? '#1D8A28' : TB.line}`, transform: `scale(${avPop})` }}>
          <img src={c.img} alt={c.name} style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
        </div>
        <div style={{ minWidth: 0 }}>
          <div style={{ fontSize: 20, fontWeight: 700, color: TB.ink, letterSpacing: '-0.02em' }}>{c.name}</div>
          <div style={{ fontSize: 14, color: TB.ink2, marginTop: 3 }}>{c.role} · {c.dist} away</div>
        </div>
      </div>

      <div style={{ marginTop: 16, minHeight: 56 }}>
        {!showVerdict ? (
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '13px 14px', borderRadius: 13, background: '#F6F7F9', border: `1px solid ${TB.line}` }}>
            <Dots t={t} c={TB.ink3} />
            <span style={{ fontSize: 14.5, fontWeight: 600, color: TB.ink2 }}>Checking policy & availability…</span>
          </div>
        ) : (
          <div style={{ display: 'flex', alignItems: 'flex-start', gap: 12, padding: '13px 14px', borderRadius: 13, background: accentBg, transform: `scale(${0.96 + 0.04 * vPop})` }}>
            <span style={{ width: 26, height: 26, borderRadius: 13, background: accent, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, transform: `scale(${vPop})` }}>{match ? <ACheck c="#fff" s={15} /> : <AX c="#fff" s={14} />}</span>
            <div style={{ paddingTop: 1 }}>
              <div style={{ fontSize: 12, fontWeight: 700, color: accent, letterSpacing: '0.05em', marginBottom: 3, whiteSpace: 'nowrap' }}>{match ? 'BEST MATCH — CALLING NOW' : 'NOT A FIT'}</div>
              <div style={{ fontSize: 15, fontWeight: 550, color: match ? '#16681E' : '#8C1A1A', lineHeight: 1.4 }}>
                {c.reason.slice(0, Math.round(clamp(reasonShown, 0, c.reason.length)))}
                {reasonShown < c.reason.length && age > 1.35 && <Caret t={t} c={accent} />}
              </div>
            </div>
          </div>
        )}
      </div>
    </CardShell>
  );
}

const CANDS = {
  cand1: { idx: 1, name: 'Deon Carter', img: 'assets/deon.png', role: 'LPN', dist: '3.2 mi', score: 88, verdict: 'reject', reason: 'Would hit overtime — already at 41 hrs this week.', idle: 1.4 },
  cand2: { idx: 2, name: 'Luka Petrov', img: 'assets/luka.png', role: 'CNA', dist: '9.1 mi', score: 74, verdict: 'reject', reason: 'Outside the 8 mi service radius for this site.', idle: 2.1 },
  cand3: { idx: 3, name: 'Maya Okafor', img: 'assets/maya.png', role: 'RN', dist: '1.4 mi', score: 96, verdict: 'match', reason: 'Available, 0 overtime, RN-qualified, 1.4 mi away.', idle: 0.6 },
};

// ── 4. live call card (Maya conversation, typed) ──────────────────────────────
const A_CONVO = [
  { who: 'jenny', at: 0.5, text: 'Hi Maya — a 3\u202fPM shift at Autumn Care just opened. Can you take it?' },
  { who: 'maya',  at: 2.6, text: 'Yes, I can cover it.' },
  { who: 'jenny', at: 4.2, text: 'Perfect — you\u2019re booked. Thanks!' },
];

function CallCard({ age, exitP, t }) {
  const TB = window.TB;
  const confirmed = age > 5.0;
  const callSecs = Math.min(37, Math.floor(age * 3) + 4);
  return (
    <CardShell age={age} exitP={exitP} t={t} idle={2.6} style={{ ...cardStyle(TB), overflow: 'hidden', padding: 0 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 13, padding: '16px 18px', borderBottom: `1px solid ${TB.line}` }}>
        <div style={{ position: 'relative', width: 44, height: 44, flexShrink: 0 }}>
          <div style={{ width: 44, height: 44, borderRadius: 22, overflow: 'hidden' }}><img src="assets/maya.png" alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }} /></div>
          <span style={{ position: 'absolute', right: -1, bottom: -1, width: 13, height: 13, borderRadius: 7, background: '#1D8A28', border: '2.5px solid #fff' }}></span>
        </div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 16, fontWeight: 700, color: TB.ink }}>{confirmed ? 'Call ended' : 'On a call with Maya'}</div>
          <div style={{ fontSize: 13, color: TB.ink2, marginTop: 1 }}>{confirmed ? 'Maya Okafor' : 'Voice connected'}</div>
        </div>
        <div style={{ fontFamily: TB.mono, fontSize: 14, fontWeight: 500, color: confirmed ? TB.ink3 : '#1D8A28' }}>00:{String(callSecs).padStart(2, '0')}</div>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 2.5, height: 40, background: '#F6F7F9' }}>
        {Array.from({ length: 48 }).map((_, i) => {
          const amp = confirmed ? 0.12 : (0.3 + 0.7 * Math.abs(Math.sin(t * 7 + i * 0.55) * Math.cos(t * 3 + i * 0.2)));
          return <span key={i} style={{ width: 2.5, height: `${5 + amp * 22}px`, borderRadius: 2, background: confirmed ? '#C2C8D2' : TB.key, opacity: confirmed ? 0.6 : 0.5 + amp * 0.5 }}></span>;
        })}
      </div>

      <div style={{ padding: '16px 18px', display: 'flex', flexDirection: 'column', gap: 14, minHeight: 168 }}>
        {A_CONVO.map((line, i) => {
          if (age < line.at) return null;
          const isJenny = line.who === 'jenny';
          const la = age - line.at;
          const shown = la * 46;
          const typing = shown < line.text.length;
          const pop = Easing.easeOutBack(clamp(la / 0.32, 0, 1));
          return (
            <div key={i} style={{ display: 'flex', flexShrink: 0, gap: 9, alignItems: 'flex-end', justifyContent: isJenny ? 'flex-start' : 'flex-end', transform: `scale(${0.9 + 0.1 * pop})`, transformOrigin: isJenny ? 'left center' : 'right center' }}>
              {isJenny && (
                <div style={{ width: 30, height: 30, borderRadius: 15, overflow: 'hidden', flexShrink: 0, background: '#EEF0F3', border: `1.5px solid ${TB.key}`, boxShadow: '0 0 0 2px #fff' }}>
                  <img src="assets/jenny.gif" alt="Jenny" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
                </div>
              )}
              <div style={{ maxWidth: '80%' }}>
                <div style={{ fontSize: 10, fontWeight: 700, color: isJenny ? TB.key : TB.ink3, marginBottom: 4, textAlign: isJenny ? 'left' : 'right', letterSpacing: '0.06em' }}>{isJenny ? 'JENNY' : 'MAYA'}</div>
                <div style={{ fontSize: 17, lineHeight: 1.5, fontWeight: 500, width: 'max-content', maxWidth: '100%', boxSizing: 'content-box', whiteSpace: isJenny ? 'normal' : 'nowrap', padding: '11px 15px', borderRadius: 15, background: isJenny ? '#F1F3F6' : TB.key, color: isJenny ? TB.ink : '#fff', borderBottomLeftRadius: isJenny ? 5 : 15, borderBottomRightRadius: isJenny ? 15 : 5 }}>
                  {line.text.slice(0, Math.round(clamp(shown, 0, line.text.length)))}
                  {typing && <Caret t={t} c={isJenny ? TB.key : '#fff'} />}
                </div>
              </div>
            </div>
          );
        })}
      </div>
    </CardShell>
  );
}

// ── 5. centered success modal with metrics ────────────────────────────────────
const CONFETTI = Array.from({ length: 16 }).map((_, i) => {
  const ang = (i / 16) * Math.PI * 2 + (i % 3);
  const colors = ['#1D8A28', '#3F82C9', '#C68A2C', '#C76A48', '#7B5BB2'];
  return { ang, dist: 90 + (i % 4) * 34, color: colors[i % colors.length], r: 4 + (i % 3) * 2, delay: (i % 5) * 0.03 };
});

function SuccessModal({ age, t }) {
  const TB = window.TB;
  const dim = clamp(age / 0.45, 0, 1) * 0.5;
  const pop = Easing.easeOutBack(clamp(age / 0.55, 0, 1));
  const o = clamp(age / 0.3, 0, 1);
  const checkPop = Easing.easeOutBack(clamp((age - 0.15) / 0.5, 0, 1));
  const metrics = [
    { v: '37s', l: 'Time to fill' },
    { v: '$0', l: 'Pay delta' },
    { v: '0', l: 'Overtime hrs' },
    { v: '100%', l: 'Coverage' },
  ];
  return (
    <div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', pointerEvents: 'none' }}>
      <div style={{ position: 'absolute', inset: 0, background: '#141A22', opacity: dim }}></div>
      <div style={{ position: 'relative', opacity: o, transform: `scale(${0.86 + 0.14 * pop}) translateY(${(1 - pop) * 24}px)`, width: 620, background: '#fff', borderRadius: 30, padding: '46px 48px 40px', textAlign: 'center', fontFamily: TB.sans, boxShadow: '0 50px 120px rgba(20,26,34,0.45)' }}>
        {/* confetti burst */}
        {CONFETTI.map((c, i) => {
          const p = clamp((age - 0.2 - c.delay) / 0.85, 0, 1);
          const e = Easing.easeOutCubic(p);
          const x = Math.cos(c.ang) * c.dist * e;
          const y = Math.sin(c.ang) * c.dist * e - 18 * e;
          return <span key={i} style={{ position: 'absolute', left: '50%', top: 64, width: c.r * 2, height: c.r * 2, borderRadius: c.r, background: c.color, opacity: (1 - p) * 0.9, transform: `translate(${x}px, ${y}px)` }}></span>;
        })}

        <div style={{ width: 78, height: 78, borderRadius: 39, background: '#1D8A28', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto', transform: `scale(${checkPop})`, boxShadow: '0 14px 34px rgba(29,138,40,0.4)' }}>
          <ACheck c="#fff" s={40} />
        </div>

        <div style={{ fontSize: 32, fontWeight: 750, color: TB.ink, letterSpacing: '-0.03em', marginTop: 22 }}>Shift covered</div>
        <div style={{ fontSize: 17, fontWeight: 500, color: TB.ink2, marginTop: 8 }}>Maya Okafor accepted · 3:00p–11:00p · Autumn Care</div>

        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 12, marginTop: 30 }}>
          {metrics.map((m, i) => {
            const mp = Easing.easeOutBack(clamp((age - 0.5 - i * 0.1) / 0.5, 0, 1));
            return (
              <div key={i} style={{ background: '#F6F7F9', border: `1px solid ${TB.line}`, borderRadius: 16, padding: '18px 8px', opacity: clamp((age - 0.5 - i * 0.1) / 0.4, 0, 1), transform: `translateY(${(1 - mp) * 14}px)` }}>
                <div style={{ fontSize: 28, fontWeight: 750, color: i === 0 ? '#1D8A28' : TB.ink, letterSpacing: '-0.02em' }}>{m.v}</div>
                <div style={{ fontSize: 12, fontWeight: 600, color: TB.ink3, marginTop: 5, letterSpacing: '0.01em' }}>{m.l}</div>
              </div>
            );
          })}
        </div>

        <div style={{ fontSize: 14, fontWeight: 500, color: TB.ink3, marginTop: 26, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8 }}>
          <span style={{ width: 7, height: 7, borderRadius: 4, background: '#1D8A28' }}></span>
          Handled end-to-end by Jenny — no manager action needed
        </div>
      </div>
    </div>
  );
}

// ── stage ─────────────────────────────────────────────────────────────────────
function AssistantPanel() {
  const t = useTime();
  const sAlert = phase(t, SEQ.alert);
  const sScan = phase(t, SEQ.scan);
  const sC1 = phase(t, SEQ.cand1);
  const sC2 = phase(t, SEQ.cand2);
  const sC3 = phase(t, SEQ.cand3);
  const sCall = phase(t, SEQ.call);
  const sConf = phase(t, SEQ.confirm);
  return (
    <React.Fragment>
      <div style={{ position: 'absolute', right: STACK.right, top: STACK.top, width: STACK.w, pointerEvents: 'none' }}>
        {sAlert && <AlertCard {...sAlert} t={t} />}
        {sScan && <ScanCard {...sScan} t={t} />}
        {sC1 && <CandidateCard {...sC1} t={t} c={CANDS.cand1} />}
        {sC2 && <CandidateCard {...sC2} t={t} c={CANDS.cand2} />}
        {sC3 && <CandidateCard {...sC3} t={t} c={CANDS.cand3} />}
        {sCall && <CallCard {...sCall} t={t} />}
      </div>
      {sConf && <SuccessModal {...sConf} t={t} />}
    </React.Fragment>
  );
}

window.AssistantPanel = AssistantPanel;
window.A_TIMES = { T_ALERT: SEQ.alert.in, T_SCAN: SEQ.scan.in, T_CALL: SEQ.call.in, T_CONFIRM: SEQ.confirm.in };
window.A_DURATION = DURATION;
