// Guided walkthrough — 17-step first-time tour for DAEV Arbiter.
// State managed in App.jsx. This file exposes:
//   - DAEV_TOUR_STEPS: the step spec
//   - TourEngine: renders the popover, manages highlight, keyboard, navigation
//   - TourPopover, TourConfetti: presentation primitives
//
// ─── Persistence + URL semantics (handover-critical) ───────────────────
// localStorage key: daev.tour.v3 —'done' | 'skipped' | absent.
//   - The old daev.tour.result single-step tour has been removed (Step 08
//     covers it now). The orphan key is also cleared on ?demo=fresh.
//
// URL params that affect tour behaviour:
//   - ?tour=1         FORCES the tour on load regardless of localStorage.
//                     Does NOT clear daev.tour.v3 —so when the visitor
//                     reloads WITHOUT ?tour=1, the tour respects their
//                     prior 'done'/'skipped' state. This is intentional:
//                     ?tour=1 is for shareable "auto-start tour" links,
//                     not a state reset. To truly reset, use ?demo=fresh.
//   - ?demo=fresh     Clears daev.tour.v2/v3 + daev.{screen,fixture,mode,seen}.
//                     Tour will auto-fire again on first paint as if first visit.
//   - ?embed=1        Suppresses the tour entirely (iframe embedding).
//
// ─── Decoupling rule ───────────────────────────────────────────────────
// The tour is a pure consumer of the `tourActions` API:
//   { setScreen, setFixtureId, setMode, setForcedTab,
//     runAudit, skipToResult, navigateToDashboard, currentScreen }
// Adding new tour steps must NEVER reach into App internals directly. If a
// new step needs a new app action, add it to tourActions in App.jsx first.

const { useState: Tu, useEffect: Te, useLayoutEffect: Tle, useRef: Tr, useMemo: Tm } = React;

const DAEV_TOUR_STEPS = [
  {
    id: 'welcome', screen: null, target: null, placement: 'center',
    overline: '// WELCOME TO DAEV ARBITER',
    heading: 'The only audit that explains why.',
    body: "Most security tools tell you what broke. DAEV tells you why it broke — and proves it mathematically. This 2-minute tour shows you every feature. You can click through at your own pace.",
    chip: null,
    nextLabel: "Let's go →",
  },
  {
    id: 'fixture-picker', screen: 'audit', target: '[data-tour="fixture-picker"]', placement: 'bottom',
    overline: '// 02 · AUDIT INPUT',
    heading: 'Eight real vulnerabilities. One causal-only.',
    body: 'Pick any fixture to load real production-style code. The teal dot marks TOCTOU race — the one SAST tools miss entirely. DAEV catches it through counterfactual reasoning, not pattern matching.',
    chip: '⚡ SAST tools return clean on the TOCTOU fixture. DAEV finds the root cause.',
    nextLabel: 'Next →',
    onEnter: ({ tourActions }) => { tourActions.setFixtureId && tourActions.setFixtureId('racecond'); },
  },
  {
    id: 'code-editor', screen: 'audit', target: '[data-tour="code-editor"]', placement: 'right',
    overline: '// 03 · SOURCE CODE',
    heading: 'Real code. Real finding line.',
    body: "The highlighted line is where the vulnerability manifests. But that's not the root cause — it's just where the symptoms appear. DAEV traces backwards to find where the causal chain starts.",
    chip: null,
    nextLabel: 'Next →',
  },
  {
    id: 'audit-mode', screen: 'audit', target: '[data-tour="audit-mode"]', placement: 'top',
    overline: '// 04 · AUDIT MODE',
    heading: 'Fast trace or full 5-agent council?',
    body: "Fast runs a single-pass causal trace — result in ~6s. Full spins up five specialist AI agents that debate the finding, run a devil's advocate challenge, and lock the verdict through a sycophancy-correction protocol.",
    chip: "🧠 No other code auditor runs a multi-agent debate. This is Pearl's Ladder, Rungs 1–3.",
    nextLabel: 'Run an audit →',
    onAdvance: ({ tourActions }) => { tourActions.runAudit && tourActions.runAudit(); },
  },
  {
    id: 'live-stream', screen: 'live', target: '[data-tour="live-stream"]', placement: 'right',
    overline: '// 05 · LIVE BUILD',
    heading: 'The engine narrates itself.',
    body: 'Every line is a real reasoning step — parsing, graph construction, taint propagation, counterfactual verification. Watch the causal chain emerge from the source code in real time.',
    chip: 'Traditional SAST produces a report after the fact. DAEV streams its proof as it builds it.',
    nextLabel: 'Next →',
  },
  {
    id: 'live-graph', screen: 'live', target: '[data-tour="live-graph"]', placement: 'left',
    overline: '// 06 · CAUSAL GRAPH — LIVE',
    heading: 'Nodes appear as the engine reasons.',
    body: 'Each node is a data entity — source, transform, sink, sanitizer. Edges are def-use relationships. The graph is being built right now, edge by edge. When the finding is confirmed, the taint path lights up red.',
    chip: null,
    nextLabel: 'Skip to result →',
    onAdvance: ({ tourActions }) => { tourActions.skipToResult && tourActions.skipToResult(); },
  },
  {
    id: 'verdict-banner', screen: 'result', target: '[data-tour="verdict-banner"]', placement: 'bottom',
    overline: '// 07 · VERDICT',
    heading: 'UNSAFE. Confidence locked.',
    body: 'The verdict is a causal judgement, not a heuristic score. Confidence is derived from the counterfactual chain — how strongly does eliminating the root cause node break the taint path?',
    chip: 'Most tools give a CVSS score. DAEV gives a causal confidence — a number you can defend to an auditor.',
    nextLabel: 'Next →',
  },
  {
    id: 'result-graph', screen: 'result', target: '[data-tour="result-graph"]', placement: 'left',
    overline: '// 08 · CAUSAL GRAPH',
    heading: 'The red path is the proof.',
    body: 'Source → transform → sink. Every red edge is a def-use relationship where tainted data flows without sanitization. Click any node to inspect its entity type, semantic role, and source line.',
    chip: null,
    nextLabel: 'Next →',
  },
  {
    id: 'node-strip', screen: 'result', target: '[data-tour="node-strip"]', placement: 'top',
    overline: '// 09 · NODE INSPECTOR',
    heading: 'Every node has identity.',
    body: 'Entity type, semantic role, source line — always visible. The ON TAINT PATH chip means this node is causally responsible for the finding. Confidence decays per hop away from the root cause.',
    chip: null,
    nextLabel: 'Next →',
  },
  {
    id: 'tab-causal', screen: 'result', target: '[data-tour="tab-causal"]', placement: 'bottom',
    overline: "// 10 · PEARL'S CAUSAL LADDER",
    heading: 'Three rungs of reasoning. SAST only reaches Rung 0.',
    body: 'Rung 1 is association — what correlates. Rung 2 is intervention — what changes if we act. Rung 3 is counterfactual — what would have happened. DAEV operates at all three.',
    chip: '"Why did this fail?" is a Rung 3 question. Pattern matching is Rung 0. This is why causal AI outperforms SAST.',
    nextLabel: 'Next →',
    onEnter: ({ tourActions }) => { tourActions.setForcedTab && tourActions.setForcedTab('causal'); },
  },
  {
    id: 'signal-reduction', screen: 'result', target: '[data-tour="signal-reduction"]', placement: 'top',
    overline: '// 11 · SIGNAL REDUCTION',
    heading: '4 SAST alerts. 1 real cause.',
    body: "Pattern matchers raised 4 signals on this file. DAEV's counterfactual proof rejected 3 as false positives and confirmed 1 as the structural root cause — with a chain you can show your auditor.",
    chip: 'False positive rate: traditional SAST 22–38%. DAEV: less than 1%.',
    nextLabel: 'Next →',
    onEnter: ({ tourActions }) => { tourActions.setForcedTab && tourActions.setForcedTab('findings'); },
  },
  {
    id: 'tab-council', screen: 'result', target: '[data-tour="tab-council"]', placement: 'bottom',
    overline: '// 12 · 5-AGENT COUNCIL',
    heading: 'Five agents. Four rounds. One verdict.',
    body: "SecurityAgent, ArchitectureAgent, PerformanceAgent, CausalIntegrityAgent, PropagationAgent each reason in isolation. Then they cross-review anonymously, run a devil's advocate challenge, and go through a sycophancy-correction protocol before the verdict locks.",
    chip: "The sycophancy-correction round is the one no other AI security tool has. It's why DAEV's council verdicts don't drift toward false consensus.",
    nextLabel: 'Next →',
    onEnter: ({ tourActions }) => { tourActions.setForcedTab && tourActions.setForcedTab('council'); },
  },
  {
    id: 'tab-sast', screen: 'result', target: '[data-tour="tab-sast"]', placement: 'bottom',
    overline: '// 13 · SAST BATTERY',
    heading: 'Seven tools running in parallel.',
    body: "Semgrep, Bandit, Radon, Lizard, Vulture, Coverage.py, pip-audit — all run inside DAEV's pipeline. You don't replace your SAST investment; DAEV wraps it and adds causal proof on top.",
    chip: null,
    nextLabel: 'Next →',
    onEnter: ({ tourActions }) => { tourActions.setForcedTab && tourActions.setForcedTab('sast'); },
  },
  {
    id: 'export-pdf', screen: 'result', target: '[data-tour="export-pdf"]', placement: 'left',
    overline: '// 14 · EVIDENCE PACK',
    heading: 'Every audit produces a signed evidence pack.',
    body: 'The PDF report is formatted for EU AI Act Article 9 / 13 compliance — Annex IV documented, audit-id signed, zero-retention inference. Hand it directly to your auditor.',
    chip: 'EU AI Act enforcement: August 2026. Every DAEV audit produces a compliance-ready dossier automatically.',
    nextLabel: 'Next →',
    onEnter: ({ tourActions }) => { tourActions.setForcedTab && tourActions.setForcedTab('export'); },
  },
  {
    id: 'aegis-stamp', screen: 'result', target: '[data-tour="aegis-stamp"]', placement: 'left',
    overline: '// 15 · CAUSAL IDENTITY',
    heading: 'Aegis signs every action.',
    body: 'When autonomous agents act on your code, Aegis records who did it, which model, and which causal chain led there. The provenance stamp at the bottom of every evidence pack is signed by the Aelethion EU certificate authority.',
    chip: 'Aegis · Coming Q3 2026 · agent-to-agent trust by default.',
    nextLabel: 'Next →',
    onEnter: ({ tourActions }) => { tourActions.setForcedTab && tourActions.setForcedTab('export'); },
  },
  {
    id: 'audit-table', screen: 'dashboard', target: '[data-tour="audit-table"]', placement: 'top',
    overline: '// 16 · AUDIT HISTORY',
    heading: 'Every audit. Searchable. Filterable.',
    body: 'All past audits are stored with verdict, audit-id, mode, duration, node count, and taint hops. Click any row to re-open the full result. Use the search bar or filter chips to find specific findings.',
    chip: null,
    nextLabel: 'Next →',
    onEnter: ({ tourActions }) => {
      tourActions.setForcedTab && tourActions.setForcedTab(null); // release tab override
      tourActions.navigateToDashboard && tourActions.navigateToDashboard();
    },
  },
  {
    id: 'finish', screen: null, target: null, placement: 'center',
    overline: '// 17 · TOUR COMPLETE',
    heading: "You've seen what causal AI looks like.",
    body: "DAEV doesn't pattern-match. It builds a causal graph of your codebase, runs counterfactual interventions, and produces a proof — not an alert. Connect your repo to run it on your own code.",
    chip: '165+ languages · 600ms median verdict · 0 false positives on DAEV\'s own 80K-line codebase.',
    nextLabel: 'Connect your repo →',
    isFinish: true,
  },
];

// ─────────────────────────────────────────────────────────────
// TourEngine — orchestrates state, target finding, navigation
// ─────────────────────────────────────────────────────────────
function TourEngine({ t, tour, setTour, tourActions }) {
  const [targetRect, setTargetRect] = Tu(null);
  const [confettiKey, setConfettiKey] = Tu(0);
  const step = tour.active ? DAEV_TOUR_STEPS[tour.step] : null;
  const reduced = typeof window !== 'undefined' && window.matchMedia
    ? window.matchMedia('(prefers-reduced-motion: reduce)').matches
    : false;

  // On step change: navigate to required screen + run onEnter side effects
  Te(() => {
    if (!tour.active || !step) return;
    if (step.screen && tourActions.currentScreen !== step.screen) {
      tourActions.setScreen(step.screen);
    }
    if (step.onEnter) {
      try { step.onEnter({ tourActions }); } catch (e) { console.warn('[tour onEnter]', e); }
    }
    if (step.isFinish) setConfettiKey(k => k + 1);
    try { window.DAEV_TRACK && window.DAEV_TRACK('tour_step_viewed', { step: tour.step, label: step.id }); } catch (_) {}
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [tour.active, tour.step]);

  // Locate target with retry loop (target may not be mounted yet after a screen change)
  Tle(() => {
    if (!tour.active || !step) return;
    if (step.placement === 'center' || !step.target) {
      setTargetRect(null);
      return;
    }

    let cancelled = false;
    let attempts = 0;
    let appliedEl = null;

    // If the previous step's onAdvance triggers a screen change (Step 4→5 or
    // Step 6→7), the target won't exist for ~1 frame. Hold the first attempt
    // for 150ms so the popover doesn't flash to centre on slow machines.
    const prevStep = tour.step > 0 ? DAEV_TOUR_STEPS[tour.step - 1] : null;
    const stepChangedScreen = prevStep && prevStep.screen && prevStep.screen !== step.screen;
    const prevHadAdvanceAction = prevStep && !!prevStep.onAdvance;
    const initialDelay = (stepChangedScreen || prevHadAdvanceAction) ? 150 : 0;

    const tick = () => {
      if (cancelled) return;
      const el = document.querySelector(step.target);
      if (el) {
        // Scroll into view if off-screen
        const rect = el.getBoundingClientRect();
        const offscreen = rect.top < 80 || rect.bottom > window.innerHeight - 80;
        if (offscreen && !reduced) {
          el.scrollIntoView({ behavior: 'smooth', block: 'center' });
        }
        // Apply highlight + measure after a frame
        requestAnimationFrame(() => {
          if (cancelled) return;
          el.classList.add('daev-tour-highlight');
          appliedEl = el;
          setTargetRect(el.getBoundingClientRect());
        });
      } else if (attempts < 12) {
        attempts++;
        setTimeout(tick, 60);
      } else {
        // Give up — fall back to centred popover
        setTargetRect(null);
      }
    };
    if (initialDelay > 0) {
      setTimeout(tick, initialDelay);
    } else {
      tick();
    }

    return () => {
      cancelled = true;
      if (appliedEl) appliedEl.classList.remove('daev-tour-highlight');
      // Belt and braces: clear any stray highlight class globally
      document.querySelectorAll('.daev-tour-highlight').forEach(n => n.classList.remove('daev-tour-highlight'));
    };
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [tour.active, tour.step]);

  // Keep popover position in sync on viewport resize
  Te(() => {
    if (!tour.active || !step || !step.target) return;
    const onResize = () => {
      const el = document.querySelector(step.target);
      if (el) setTargetRect(el.getBoundingClientRect());
    };
    window.addEventListener('resize', onResize);
    window.addEventListener('scroll', onResize, true);
    return () => {
      window.removeEventListener('resize', onResize);
      window.removeEventListener('scroll', onResize, true);
    };
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [tour.active, tour.step]);

  // Keyboard
  Te(() => {
    if (!tour.active) return;
    const onKey = (e) => {
      const tag = (e.target && e.target.tagName) || '';
      const editable = tag === 'INPUT' || tag === 'TEXTAREA' || (e.target && e.target.isContentEditable);
      if (editable) return;
      if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'Enter') {
        e.preventDefault();
        advance();
      } else if (e.key === 'Escape') {
        e.preventDefault();
        close();
      }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [tour.active, tour.step]);

  const advance = () => {
    if (!step) return;
    if (step.onAdvance) {
      try { step.onAdvance({ tourActions }); } catch (e) { console.warn('[tour onAdvance]', e); }
    }
    try { window.DAEV_TRACK && window.DAEV_TRACK('tour_step_next', { step: tour.step }); } catch (_) {}
    if (step.isFinish) {
      // "Connect your repo" CTA — navigate home, mark done
      try { localStorage.setItem('daev.tour.v3', 'done'); } catch (_) {}
      try { window.DAEV_TRACK && window.DAEV_TRACK('tour_completed'); } catch (_) {}
      try { window.DAEV_TRACK && window.DAEV_TRACK('connect_repo_clicked', { from: 'tour_finish' }); } catch (_) {}
      setTour({ active: false, step: 0, completed: true, skipped: false });
      try {
        const back = window.DAEV_BACK_HREF || '/';
        window.location.href = back;
      } catch (_) {}
      return;
    }
    const nextIdx = tour.step + 1;
    if (nextIdx >= DAEV_TOUR_STEPS.length) {
      try { localStorage.setItem('daev.tour.v3', 'done'); } catch (_) {}
      try { window.DAEV_TRACK && window.DAEV_TRACK('tour_completed'); } catch (_) {}
      // Reset forced tab on completion
      tourActions.setForcedTab && tourActions.setForcedTab(null);
      setTour({ active: false, step: 0, completed: true, skipped: false });
    } else {
      setTour({ ...tour, step: nextIdx });
    }
  };

  const skip = () => {
    try { localStorage.setItem('daev.tour.v3', 'skipped'); } catch (_) {}
    try { window.DAEV_TRACK && window.DAEV_TRACK('tour_skipped', { step: tour.step }); } catch (_) {}
    tourActions.setForcedTab && tourActions.setForcedTab(null);
    setTour({ active: false, step: 0, completed: false, skipped: true });
  };

  const close = () => {
    try { localStorage.setItem('daev.tour.v3', 'skipped'); } catch (_) {}
    try { window.DAEV_TRACK && window.DAEV_TRACK('tour_closed', { step: tour.step }); } catch (_) {}
    tourActions.setForcedTab && tourActions.setForcedTab(null);
    setTour({ active: false, step: 0, completed: false, skipped: true });
  };

  const dismissFinish = () => {
    try { localStorage.setItem('daev.tour.v3', 'done'); } catch (_) {}
    try { window.DAEV_TRACK && window.DAEV_TRACK('tour_completed', { dismissed: true }); } catch (_) {}
    tourActions.setForcedTab && tourActions.setForcedTab(null);
    setTour({ active: false, step: 0, completed: true, skipped: false });
  };

  if (!tour.active || !step) return null;

  // Welcome backdrop — full-viewport DAG behind step 1 only.
  // Embedded contexts (?embed=1) suppress this via the App-level embedMode
  // gate which prevents the tour from launching in the first place, so this
  // component only ever mounts in standalone tour contexts.
  const showWelcomeBg = tour.step === 0 && step.placement === 'center';

  return (
    <>
      {showWelcomeBg && <WelcomeBg/>}
      <TourPopover
        t={t}
        stepIndex={tour.step}
        total={DAEV_TOUR_STEPS.length}
        overline={step.overline}
        heading={step.heading}
        body={step.body}
        chip={step.chip}
        nextLabel={step.nextLabel}
        isFinish={!!step.isFinish}
        isWelcome={tour.step === 0}
        onNext={advance}
        onSkip={skip}
        onClose={close}
        onDismissFinish={dismissFinish}
        targetRect={targetRect}
        placement={step.placement}
      />
      {step.isFinish && <TourConfetti t={t} fireKey={confettiKey}/>}
    </>
  );
}

// ─────────────────────────────────────────────────────────────
// TourPopover — the floating tooltip card
// ─────────────────────────────────────────────────────────────
function TourPopover({ t, stepIndex, total, overline, heading, body, chip, nextLabel, isFinish, isWelcome, onNext, onSkip, onClose, onDismissFinish, targetRect, placement }) {
  const isCenter = placement === 'center' || !targetRect;
  const W = isWelcome ? 420 : 360;
  const isMobile = typeof window !== 'undefined' && window.innerWidth < 768;

  // Stat ticker — step 1 only. Cycles 3 stats on a 2s interval.
  const STATS = [
    '600ms median verdict',
    '<1% false positive rate',
    '80K lines · 0 missed root causes',
  ];
  const [statIdx, setStatIdx] = Tu(0);
  const reduced2 = typeof window !== 'undefined' && window.matchMedia
    ? window.matchMedia('(prefers-reduced-motion: reduce)').matches : false;
  Te(() => {
    if (!isWelcome || reduced2) return;
    const id = setInterval(() => setStatIdx(i => (i + 1) % STATS.length), 2000);
    return () => clearInterval(id);
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isWelcome]);

  // Compute popover position
  const computed = Tm(() => {
    if (isCenter || isMobile) {
      return {
        left: '50%', top: '50%',
        transform: 'translate(-50%, -50%)',
        arrow: null,
      };
    }
    const r = targetRect;
    const margin = 16;
    const arrowSize = 8;
    let left, top, arrow;
    switch (placement) {
      case 'top':
        left = r.left + r.width / 2 - W / 2;
        top  = r.top - 16;
        arrow = { side: 'bottom', x: r.left + r.width / 2 - left };
        break;
      case 'bottom':
        left = r.left + r.width / 2 - W / 2;
        top  = r.bottom + 16;
        arrow = { side: 'top', x: r.left + r.width / 2 - left };
        break;
      case 'left':
        left = r.left - W - 16;
        top  = r.top + r.height / 2 - 80;
        arrow = { side: 'right', y: r.top + r.height / 2 - top };
        break;
      case 'right':
      default:
        left = r.right + 16;
        top  = r.top + r.height / 2 - 80;
        arrow = { side: 'left', y: r.top + r.height / 2 - top };
        break;
    }
    // Collision avoidance (simple)
    const winW = window.innerWidth, winH = window.innerHeight;
    if (left < margin) left = margin;
    if (left + W > winW - margin) left = winW - W - margin;
    if (top < margin) top = margin;
    if (top > winH - 200) top = winH - 220;
    return { left, top, transform: placement === 'top' ? 'translateY(-100%)' : 'none', arrow };
  }, [targetRect, placement, isCenter, isMobile]);

  return (
    <>
      {/* Centred placement: full-screen scrim with backdrop blur */}
      {isCenter && (
        <div onClick={isFinish ? onDismissFinish : onSkip} style={{
          position:'fixed', inset:0, zIndex: 1950,
          background: t.mode === 'dark' ? 'rgba(0,0,0,0.55)' : 'rgba(15,14,12,0.32)',
          backdropFilter: 'blur(2px)',
          WebkitBackdropFilter: 'blur(2px)',
        }}/>
      )}

      <div
        className="daev-tour-pop"
        onClick={(e) => e.stopPropagation()}
        style={{
          position: 'fixed',
          width: isMobile ? 'calc(100vw - 32px)' : W + 'px',
          maxWidth: '92vw',
          left: computed.left,
          top:  computed.top,
          transform: computed.transform,
          background: t.bg1,
          border: `1px solid ${t.accent.base}`,
          borderRadius: t.radius.lg,
          boxShadow: `0 24px 56px rgba(0, 104, 118, 0.28)`,
          fontFamily: t.font.ui,
          color: t.fg0,
          zIndex: 2000,
          padding: '14px 16px 14px 16px',
        }}>
        {/* Optional arrow */}
        {computed.arrow && !isCenter && !isMobile && (
          <span style={{
            position: 'absolute',
            ...(computed.arrow.side === 'top'    ? { top: -8, left: computed.arrow.x - 8 } : {}),
            ...(computed.arrow.side === 'bottom' ? { bottom: -8, left: computed.arrow.x - 8 } : {}),
            ...(computed.arrow.side === 'left'   ? { left: -8, top: computed.arrow.y - 8 } : {}),
            ...(computed.arrow.side === 'right'  ? { right: -8, top: computed.arrow.y - 8 } : {}),
            width: 14, height: 14,
            background: t.bg1,
            borderRight: `1px solid ${t.accent.base}`,
            borderBottom: `1px solid ${t.accent.base}`,
            transform:
              computed.arrow.side === 'top'    ? 'rotate(-135deg)' :
              computed.arrow.side === 'bottom' ? 'rotate(45deg)' :
              computed.arrow.side === 'left'   ? 'rotate(135deg)' : 'rotate(-45deg)',
          }}/>
        )}

        {/* Header row */}
        <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom: 10 }}>
          <span style={{
            fontFamily: t.font.mono, fontSize: 9, fontWeight: 700,
            color: t.accent.base, letterSpacing: 1.6, textTransform: 'uppercase',
          }}>{overline}</span>
          <button onClick={onClose} aria-label="Close tour" title="Close (esc)"
            style={{
              width: 22, height: 22, padding: 0,
              background:'transparent', border:'none',
              borderRadius: t.radius.sm,
              color: t.fg2, cursor: 'pointer',
              display:'inline-flex', alignItems:'center', justifyContent:'center',
            }}>
            <Icon name="x" size={13}/>
          </button>
        </div>

        {/* Heading */}
        <div style={{
          fontFamily: t.font.ui, fontSize: 15, fontWeight: 700,
          color: t.fg0, letterSpacing: -0.015, lineHeight: 1.3,
          marginBottom: 6,
        }}>{heading}</div>

        {/* Body */}
        <div style={{
          fontFamily: t.font.ui, fontSize: 13.5, fontWeight: 400,
          color: t.fg1, lineHeight: 1.5,
          maxWidth: '42ch', marginBottom: chip ? 10 : 12,
        }}>{body}</div>

        {/* Optional accent chip */}
        {chip && (
          <div style={{
            padding: '8px 10px',
            background: `color-mix(in oklch, ${t.accent.base} 10%, ${t.bg1})`,
            border: `1px solid ${t.accent.base}`,
            borderRadius: t.radius.sm,
            fontFamily: t.font.ui, fontSize: 12, fontWeight: 500,
            color: t.fg0, lineHeight: 1.4, letterSpacing: -0.005,
            marginBottom: 12,
          }}>{chip}</div>
        )}

        {/* Welcome-only: 3-stat rolling ticker */}
        {isWelcome && (
          <div style={{
            marginBottom: 14,
            padding: '10px 12px',
            background: `color-mix(in oklch, ${t.accent.base} 8%, transparent)`,
            border: `1px solid color-mix(in oklch, ${t.accent.base} 32%, transparent)`,
            borderRadius: t.radius.sm,
            display: 'flex', alignItems: 'center', gap: 10,
            minHeight: 36,
          }}>
            <span style={{
              width: 6, height: 6, borderRadius: '50%',
              background: t.accent.base,
              boxShadow: `0 0 8px ${t.accent.base}`,
              flexShrink: 0,
              animation: reduced2 ? 'none' : 'daev-blip 1.4s ease-in-out infinite',
            }}/>
            <span key={statIdx}
              style={{
                fontFamily: t.font.mono, fontSize: 11.5, fontWeight: 600,
                color: t.accent.base,
                letterSpacing: 0.2,
                animation: reduced2 ? 'none' : 'daev-stat-fade 2s ease-in-out',
              }}>{STATS[statIdx]}</span>
            <span style={{ flex: 1 }}/>
            <span style={{
              fontFamily: t.font.mono, fontSize: 9, fontWeight: 700,
              color: t.fg3, letterSpacing: 1.2, textTransform: 'uppercase',
            }}>proof · live</span>
          </div>
        )}

        {/* Footer row: skip · dot strip · next */}
        <div style={{ display:'flex', alignItems:'center', gap: 10 }}>
          <button onClick={onSkip}
            style={{
              background:'transparent', border:'none', padding:'4px 0',
              color: t.fg3, cursor:'pointer',
              fontFamily: t.font.mono, fontSize: 11, fontWeight: 500,
              letterSpacing: 0.4,
            }}>Skip tour</button>

          <div style={{ flex: 1, display:'flex', alignItems:'center', justifyContent:'center', gap: 3 }}>
            {DAEV_TOUR_STEPS.map((_, i) => {
              const completed = i < stepIndex;
              const active = i === stepIndex;
              return (
                <span key={i} style={{
                  width: 5, height: 5, borderRadius:'50%',
                  background: (completed || active) ? t.accent.base : 'transparent',
                  border: !completed && !active ? `1px solid ${t.line2}` : 'none',
                  animation: active ? 'daev-tour-dot 1.4s ease-in-out infinite' : 'none',
                  flexShrink: 0,
                }}/>
              );
            })}
          </div>

          <span style={{
            fontFamily: t.font.mono, fontSize: 10, color: t.fg3,
            fontVariantNumeric: 'tabular-nums', whiteSpace:'nowrap',
          }}>{(stepIndex + 1)} / {total}</span>

          {/* Primary action button */}
          {!isFinish ? (
            <button onClick={onNext} style={{
              background: t.accent.base, color: '#ffffff',
              border: 'none', borderRadius: t.radius.sm,
              padding: '8px 14px',
              fontFamily: t.font.ui, fontSize: 12, fontWeight: 700,
              cursor: 'pointer', letterSpacing: 0.2,
              flexShrink: 0,
            }}>{nextLabel}</button>
          ) : (
            <div style={{ display:'flex', gap: 8, flexShrink: 0 }}>
              <button onClick={onDismissFinish} style={{
                background:'transparent', border:`1px solid ${t.line2}`, borderRadius: t.radius.sm,
                padding:'8px 12px',
                fontFamily: t.font.ui, fontSize: 12, fontWeight: 600,
                color: t.fg1, cursor:'pointer',
              }}>Keep exploring</button>
              <button onClick={onNext} style={{
                background: t.accent.base, color:'#ffffff',
                border:'none', borderRadius: t.radius.sm,
                padding:'8px 14px',
                fontFamily: t.font.ui, fontSize: 12, fontWeight: 700,
                cursor:'pointer', letterSpacing: 0.2,
              }}>{nextLabel}</button>
            </div>
          )}
        </div>

        {/* Welcome-only: keyboard shortcut hint below the button row */}
        {isWelcome && (
          <div style={{
            marginTop: 10,
            paddingTop: 10,
            borderTop: `1px dashed ${t.line}`,
            fontFamily: t.font.mono, fontSize: 10,
            color: t.fg3, letterSpacing: 0.4,
            textAlign: 'center',
          }}>
            <kbd style={{ fontFamily: t.font.mono, fontSize: 10, padding: '0 4px',
              border: `1px solid ${t.line2}`, borderRadius: 3, color: t.fg2 }}>→</kbd>
            <span style={{ margin: '0 4px' }}>/</span>
            <kbd style={{ fontFamily: t.font.mono, fontSize: 10, padding: '0 6px',
              border: `1px solid ${t.line2}`, borderRadius: 3, color: t.fg2 }}>Space</kbd>
            <span style={{ margin: '0 8px' }}>to advance</span>
            <span style={{ color: t.line2 }}>·</span>
            <span style={{ margin: '0 8px' }}></span>
            <kbd style={{ fontFamily: t.font.mono, fontSize: 10, padding: '0 6px',
              border: `1px solid ${t.line2}`, borderRadius: 3, color: t.fg2 }}>Esc</kbd>
            <span style={{ marginLeft: 6 }}>to skip</span>
          </div>
        )}
      </div>
    </>
  );
}

// ─────────────────────────────────────────────────────────────
// Confetti — brand-colour-only burst on Step 16
// ─────────────────────────────────────────────────────────────
function TourConfetti({ t, fireKey }) {
  const reduced = typeof window !== 'undefined' && window.matchMedia
    ? window.matchMedia('(prefers-reduced-motion: reduce)').matches
    : false;
  if (reduced) return null;
  // 40 particles, brand-only palette
  const particles = Tm(() => {
    const palette = [t.accent.base, '#1b9663', '#4f98a3'];
    return Array.from({ length: 40 }, (_, i) => ({
      i,
      left: Math.random() * 100,
      cx:   (Math.random() - 0.5) * 60 + 'vw',
      delay: Math.random() * 200,
      dur:   1800 + Math.random() * 1400,
      color: palette[i % palette.length],
      shape: i % 3 === 0 ? 'circle' : 'square',
      size:  6 + Math.random() * 6,
    }));
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [fireKey]);

  return (
    <div className="daev-confetti" aria-hidden="true" style={{
      position:'fixed', inset:0, zIndex: 1990, pointerEvents:'none', overflow:'hidden',
    }}>
      {particles.map(p => (
        <span key={p.i} style={{
          position:'absolute',
          left: p.left + '%',
          top: '-20px',
          width: p.size, height: p.size,
          background: p.color,
          borderRadius: p.shape === 'circle' ? '50%' : '2px',
          ['--cx']: p.cx,
          animation: `daev-confetti-fall ${p.dur}ms cubic-bezier(0.2, 0.7, 0.2, 1) ${p.delay}ms forwards`,
          opacity: 0.95,
        }}/>
      ))}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// WelcomeBg — full-viewport DAG behind the step-1 scrim.
// Renders ONLY when tour.active && tour.step === 0. Pure SVG +
// CSS, no React state, no fixture coupling. Sets the mental
// model ("real-time causal tracing system") before any copy is
// read. Dark-mode-only — the scrim forces dark regardless of
// the visitor's theme preference.
// ─────────────────────────────────────────────────────────────
function WelcomeBg() {
  // 18-node DAG: 4 sources → 6 transforms → 5 sinks + 3 routing nodes.
  // Topology echoes the racecond fixture but scaled across the viewport.
  // Coordinates are normalised 0..1 — SVG viewBox is 1000×600.
  const NODES = [
    // sources (left column)
    { id:'s1', x: 0.06, y: 0.18 },
    { id:'s2', x: 0.06, y: 0.36 },
    { id:'s3', x: 0.06, y: 0.62 },
    { id:'s4', x: 0.06, y: 0.82 },
    // transforms (mid-left)
    { id:'t1', x: 0.26, y: 0.22 },
    { id:'t2', x: 0.26, y: 0.46 },
    { id:'t3', x: 0.26, y: 0.74 },
    // routing
    { id:'r1', x: 0.46, y: 0.30 },
    { id:'r2', x: 0.46, y: 0.55 },
    { id:'r3', x: 0.46, y: 0.78 },
    // root cause node + adjacent (mid-right)
    { id:'rc', x: 0.66, y: 0.50 },  // ROOT_CAUSE
    { id:'a1', x: 0.66, y: 0.20 },
    { id:'a2', x: 0.66, y: 0.78 },
    // sinks (right column)
    { id:'k1', x: 0.92, y: 0.16 },
    { id:'k2', x: 0.92, y: 0.34 },
    { id:'k3', x: 0.92, y: 0.50 },
    { id:'k4', x: 0.92, y: 0.66 },
    { id:'k5', x: 0.92, y: 0.84 },
  ];
  const EDGES = [
    ['s1','t1'], ['s2','t1'], ['s2','t2'], ['s3','t2'], ['s3','t3'], ['s4','t3'],
    ['t1','r1'], ['t2','r1'], ['t2','r2'], ['t3','r2'], ['t3','r3'],
    ['r1','a1'], ['r1','rc'], ['r2','rc'], ['r2','a2'], ['r3','a2'], ['r3','rc'],
    ['a1','k1'], ['a1','k2'], ['rc','k3'], ['rc','k2'], ['a2','k4'], ['a2','k5'],
  ];
  // Taint path: s2 → t2 → r2 → rc → k3 (one of the sinks)
  const TAINT = new Set(['s2-t2','t2-r2','r2-rc','rc-k3']);
  const TAINT_NODES = new Set(['s2','t2','r2','rc','k3']);

  const xy = (n) => ({ x: n.x * 1000, y: n.y * 600 });

  return (
    <div aria-hidden="true" style={{
      position: 'fixed', inset: 0, zIndex: 1800,
      pointerEvents: 'none',
      overflow: 'hidden',
      background: '#0d1117',
      animation: 'daev-welcome-fade 600ms cubic-bezier(0.2, 0.9, 0.2, 1)',
    }}>
      <svg viewBox="0 0 1000 600" preserveAspectRatio="xMidYMid slice"
        style={{ width: '100%', height: '100%', display: 'block' }}>
        <defs>
          <radialGradient id="welcome-rc-glow">
            <stop offset="0%"  stopColor="#006876" stopOpacity="0.55"/>
            <stop offset="60%" stopColor="#006876" stopOpacity="0.10"/>
            <stop offset="100%" stopColor="#006876" stopOpacity="0"/>
          </radialGradient>
          <radialGradient id="welcome-rc-halo">
            <stop offset="0%"  stopColor="#C0392B" stopOpacity="0.20"/>
            <stop offset="100%" stopColor="#C0392B" stopOpacity="0"/>
          </radialGradient>
        </defs>

        {/* Edges — non-taint (faint teal, animated dash flow L→R) */}
        {EDGES.map(([from, to], i) => {
          const a = xy(NODES.find(n => n.id === from));
          const b = xy(NODES.find(n => n.id === to));
          const isTaint = TAINT.has(from + '-' + to);
          if (isTaint) return null;
          return (
            <line key={`e-${i}`}
              x1={a.x} y1={a.y} x2={b.x} y2={b.y}
              stroke="#006876"
              strokeOpacity="0.20"
              strokeWidth="1.2"
              strokeDasharray="6 8"
              strokeDashoffset="0"
              style={{ animation: `daev-welcome-flow 4s linear ${i * -0.18}s infinite` }}
            />
          );
        })}

        {/* Edges — taint path (red, 60%, animated flow) */}
        {EDGES.map(([from, to], i) => {
          const a = xy(NODES.find(n => n.id === from));
          const b = xy(NODES.find(n => n.id === to));
          const isTaint = TAINT.has(from + '-' + to);
          if (!isTaint) return null;
          return (
            <line key={`et-${i}`}
              x1={a.x} y1={a.y} x2={b.x} y2={b.y}
              stroke="#C0392B"
              strokeOpacity="0.60"
              strokeWidth="2"
              strokeDasharray="10 6"
              strokeDashoffset="0"
              style={{ animation: `daev-welcome-flow-fast 1.8s linear infinite` }}
            />
          );
        })}

        {/* Root-cause halo (red soft glow, large) */}
        {(() => {
          const rc = xy(NODES.find(n => n.id === 'rc'));
          return (
            <>
              <circle cx={rc.x} cy={rc.y} r="80" fill="url(#welcome-rc-halo)"/>
              <circle cx={rc.x} cy={rc.y} r="44" fill="url(#welcome-rc-glow)"
                style={{ animation: 'daev-welcome-rc-pulse 1.6s ease-in-out infinite' }}/>
            </>
          );
        })()}

        {/* Nodes */}
        {NODES.map(n => {
          const p = xy(n);
          const isRC = n.id === 'rc';
          const onTaint = TAINT_NODES.has(n.id);
          return (
            <g key={n.id}>
              <circle cx={p.x} cy={p.y} r={isRC ? 9 : 6}
                fill={isRC ? '#006876' : 'transparent'}
                stroke={isRC ? '#006876' : (onTaint ? '#C0392B' : '#006876')}
                strokeOpacity={isRC ? 1 : (onTaint ? 0.6 : 0.4)}
                strokeWidth={isRC ? 2 : 1.4}
                style={isRC ? { animation: 'daev-welcome-rc-scale 1.6s ease-in-out infinite', transformOrigin: `${p.x}px ${p.y}px` } : undefined}
              />
              {isRC && (
                <text x={p.x} y={p.y - 22} textAnchor="middle"
                  fontFamily='"JetBrains Mono", monospace'
                  fontSize="10" fontWeight="700"
                  letterSpacing="1.6"
                  fill="#006876" opacity="0.95">ROOT_CAUSE</text>
              )}
            </g>
          );
        })}
      </svg>
      <style>{`
        @keyframes daev-welcome-fade {
          from { opacity: 0; }
          to   { opacity: 1; }
        }
        @keyframes daev-welcome-flow {
          to { stroke-dashoffset: -56; }
        }
        @keyframes daev-welcome-flow-fast {
          to { stroke-dashoffset: -64; }
        }
        @keyframes daev-welcome-rc-pulse {
          0%, 100% { opacity: 0.55; }
          50%      { opacity: 1; }
        }
        @keyframes daev-welcome-rc-scale {
          0%, 100% { transform: scale(1); }
          50%      { transform: scale(1.18); }
        }
        @media (prefers-reduced-motion: reduce) {
          [data-welcome-bg] line,
          [data-welcome-bg] circle { animation: none !important; }
        }
      `}</style>
    </div>
  );
}

// Expose
window.DAEV_TOUR_STEPS = DAEV_TOUR_STEPS;
Object.assign(window, { TourEngine, TourPopover, TourConfetti, WelcomeBg });
